Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…av, consent contract, retention field, config cache, cookie Secure) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Match the AppLayout inner-content padding convention used by the other pages (e.g. Pixels/Index); the new pages rendered flush against the sidebar/top. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The snippet has no own-banner branch yet, so own_banner must not be selectable. Add ConsentMode::selectableCases/Options/Values (excluding OwnBanner), use them in the Site create/edit forms and SiteRequest validation. The enum keeps the case for v1.1. Adds tests asserting the option is absent from the form and rejected by validation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…CSRF) The snippet used navigator.sendBeacon, which always sends in credentials 'include' mode; the CORS spec forbids a wildcard Access-Control-Allow-Origin for credentialed requests, so cross-origin beacons were blocked at preflight. - mx.js: send via fetch with credentials 'omit' + Content-Type text/plain (a CORS simple request → no preflight, and '*' is valid for any domain). - Controller: decode the raw text/plain JSON body before validating. - CSRF: exempt 'a/*' — customer sites cannot carry this app's token. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Cache-busting a snippet embedded on third-party sites can't use hashed URLs or ?v= — the embed is fixed. Follow the vendor pattern (GA/Plausible/ Fathom): a stable URL whose updates propagate via cache headers, not URL changes. Move mx.js out of public/ and serve it from GET /mx.js with Cache-Control: public, max-age=3600, stale-while-revalidate=86400 and an ETag, so expiry is a cheap 304 revalidation and edits roll out within the hour without touching any customer's embed code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h/per-view storage) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ium, campaignShare)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on + RecordEventJob)
…aign) + totalSessions
…e + project resolver
…vice metric Replace the hand-rolled (browser, os) DB::raw aggregate in GetLinkStatsTool with separate by_browser/by_os breakdowns via StatisticsAggregator::breakdown(), matching the single-column breakdowns already used by StatisticsController and UrlController (topBrowsers/topOs). No combined-tuple metric exists elsewhere in the app, so by_device is dropped in favor of by_browser + by_os.
Field wasn't receiving errors.name from useForm, so a server-side validation failure round-tripped silently with no message and no aria-invalid/aria-describedby linkage.
…/regenerate actions Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… tokens section Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add lang/{en,de,fr,nl}/reports.php catalogs (index/form/types/frequencies/email/report
sub-arrays) for the upcoming scheduled reporting feature, and a "reports" key in the
common.php nav group used by the sidebar. Pure i18n — no code changes.
Adds the scheduled_reports table (ULID pk, project/creator FKs, frequency/period/format/recipient columns, next_run_at index), the ScheduledReport model with array/bool/datetime casts and project()/creator() relations, config/reports.php send_hour setting, and Project::scheduledReports().
Deterministic scheduling-math for scheduled reporting (Task 3 of the plan):
both classes take the reference instant as a parameter instead of calling
now(), so results are fully reproducible in tests.
- PeriodResolver::resolve() maps last_7_days/last_30_days/last_90_days to a
rolling [now-N, now] window, and previous_month to the full prior
calendar month; throws InvalidArgumentException on unknown periods.
- NextRunCalculator::next() computes the next send instant strictly after a
given time, in config('app.timezone'), for daily/weekly/monthly
frequencies, clamping day_of_month to 28 to avoid month-length issues.
$now->subMonth() from a day-29/30/31 date uses Carbon's overflowing month arithmetic and can land back in the *same* month (e.g. 2026-03-31 minus one month -> 2026-03-03), so previous_month resolved to the current, still- accumulating month instead of the completed prior one on those days. Fix: subtract the month from startOfMonth() instead, where subtracting a month is always safe regardless of day-of-month. Adds regression tests for day-31 in both a 31-day month (May -> April) and around the Feb boundary (March -> February).
Adds the report-type abstraction for scheduled reporting: ReportData (struct), ReportType (contract), ReportTypeRegistry (container-resolved singleton, throws InvalidArgumentException on unknown key), and the 3 implementations — ProjectSummaryReport, LinkReport, SiteAnalyticsReport — each gathering data via the existing StatisticsAggregator / AnalyticsAggregator / GoalAggregator. Renames the pre-existing, unrelated on-demand PDF report struct App\Reports\ReportData (used by ReportController's /reports/download routes) to App\Reports\LegacyReportData to free up the ReportData name for the new scheduled-reporting struct, per the plan's shared reference. No behavior change to the legacy download feature. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t parallel stack) Reverts the parallel report-data stack from 07f539d: deletes the new title/periodLabel/kpis/breakdowns/series ReportData struct, restores LegacyReportData back to App\Reports\ReportData (original scope/title/subtitle/rangeLabel/generatedAt/totalClicks/uniqueClicks/ timeSeries/breakdowns/topLinks/recentClicks shape + toArray()), and re-points ReportDataService + the 2 template/branding tests at it — the on-demand /reports/download PDF feature is byte-for-byte back to its pre-07f539d behavior (ReportTemplateRenderTest, Admin/BrandingTemplatesTest pass unchanged). Rebuilds ReportType/ReportTypeRegistry per the revised interface (validateSubject/subjectLabel/title/viewData/pdfView/emailView/csv) as thin adapters over the existing subsystem instead of a second data-gathering layer: ProjectSummaryReport and LinkReport call ReportDataService::forProject()/forUrl()->toArray() directly; SiteAnalyticsReport keeps its own AnalyticsAggregator+GoalAggregator-backed array shape (visits/page_views/goals/top_events/series), converting the ReportDateRange to a day count since those aggregators only support trailing-N-day windows. A small WritesCsv trait shares CSV-escaping across the three csv() implementations. Rewrites tests/Feature/Reports/ReportTypesTest.php for the new interface. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds resources/views/reports/site.blade.php (new PDF Blade extending
reports.layout, matching project/link style: KPI grid, page-views trend
table, goals table, top-events table) so SiteAnalyticsReport has a PDF
view. reports.project/reports.link/layout/partials are unchanged.
Adds resources/views/reports/email/{layout,project,link,site}.blade.php
as a fully separate, email-client-safe rendering path: table-based
layout, inline styles only, no <canvas>/JS/Tailwind. Section labels are
localized via the existing reports.report.* catalog (Task 1).
Adds tests/Feature/Reports/ReportViewsTest.php as a render-only smoke
gate (no Browsershot) covering all 6 views plus an empty-data case.
…PDF + CSV) Adds the queued send path for scheduled reports: SendScheduledReport resolves the report's ReportType, validates the subject still exists, resolves the period into a ReportDateRange, builds viewData, optionally renders CSV/PDF per report.formats, and mails ScheduledReportMail to the recipients (or an override list for send-now). Scheduling bookkeeping (last_sent_at/next_run_at) is intentionally left untouched here.
each() delegates to chunk(), which re-runs the same WHERE per page with an offset; since the callback advances next_run_at, processed rows drop out of the WHERE and >1000 due reports in one run would cause page 2 to overshoot and skip still-due rows. Load the due set once via get() and iterate the in-memory collection instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the Reports/{Index,Create,Edit} Inertia pages and a shared
ReportForm partial (mirrors Links/partials/LinkForm) that wires up
type/subject/frequency/period/formats/recipients/active against the
Task-8 ScheduledReportController routes, plus a "Reports" sidebar nav
entry. Fully tokenized and localized via existing reports.* keys.
…-summary key
- Add reports.index.active/inactive to all 4 locales so the Index
active-toggle pill shows real text ("Active"/"Inactive") instead of
always "Active" with only color signaling state.
- Add reports.form.save_error_title (reusing each locale's existing
links.form.save_error_title wording) so ReportForm no longer borrows
a cross-domain i18n key for its ErrorSummary title.
- Give the Index active-toggle button an aria-label/title.
…ge frankenphp CVEs - guzzlehttp/guzzle 7.15.1 -> 7.15.3 (CVE-2026-69246) - league/commonmark 2.8.2 -> 2.9.0 (CVE-2026-71488 + 3 GHSA DoS advisories) - .trivyignore: kin-openapi (CRITICAL) + grpc (HIGH) live in the FrankenPHP base-image binary, not app deps; documented, temporary, base-image-fixable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
v2brings a full UI refresh plus three substantial new capabilities. All work landed via subagent-driven development with per-task and whole-branch review.Highlights
Design system + localization
@/Components/uikit (Button/LinkButton, Input/Select/Checkbox/Field, PageHeader, TableCard, Flash, Pagination, …).aria-invalid/aria-describedby, Buttonaria-busy, focus rings).Owned QR styling engine
qr-code-stylingdependency with an owned SVG renderer (resources/js/lib/qr/— matrix → shapes → render → export + scannability), usingqrcode-generatorfor the matrix.MCP server + API tokens
laravel/mcpserver atPOST /mcp/marketix(Sanctum personal-access-token auth + throttle).CreateLink/CreateQrCodeactions).Scheduled reporting
ReportTyperegistry reusing the existingReportDataService+spatie/laravel-pdf; hourlyreports:dispatch-duescheduler; pureNextRunCalculator/PeriodResolver.Verification
ddev npm run buildgreen (tsc + vite).ddev php artisan test— 571 passed (2142 assertions).Notes / follow-ups
Project::forceDelete()FK violation via LogsActivity — ticket before any hard-delete feature.🤖 Generated with Claude Code