Commit Graph

294 Commits

Author SHA1 Message Date
Vasyka 7c0c728347 PDF: remove part codes; payments: pre-fill amount with balance due
- PDF parts table: removed Cod/article column, widened Denumire column,
  fixed colspan in tfoot from 4 to 3
- PaymentsRelationManager: amount field now defaults to (total - paidAmount)
  so it pre-fills with the remaining balance; always editable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:23:06 +00:00
Vasyka b0433b66c7 Recalculate WO total after saving discount_pct
afterSave() calls recalcTotal() so discount_pct changes are applied
to the total immediately on save, then fillForm() refreshes the
displayed total field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 07:12:37 +00:00
Vasyka 5610282078 Fix sort_order: sequential 1,2,3 + renormalize after every save
- saving: always shift >= new position by +1 (no conflict check needed)
- saved: renormalize all records to 1, 2, 3... via direct DB update
  (no model events triggered) to close any gaps left by the move
- Migration updated to use step 1 instead of 10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 06:59:14 +00:00
Vasyka 2e082d4aad Normalize labor category sort_order + fix increment step to 10
Migration renumbers all categories per company as 10, 20, 30… to
eliminate existing duplicates. Observer now increments by 10 instead
of 1 to leave headroom between positions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 06:53:10 +00:00
Vasyka 04cab25ac5 Shift labor category sort_order on conflict when saving
When a category is saved with a sort_order already occupied by another
record in the same company, all records at that position and above are
incremented by 1 before the save, inserting the new one cleanly at the
requested position.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 06:45:50 +00:00
Vasyka b7653f600e Disable pagination on labor catalog so all categories are visible
With collapsedGroupsByDefault() the 386 rows are fetched but not
rendered in DOM until expanded — no perf issue, all 17 group headers
now visible without needing to navigate pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:59:03 +00:00
Vasyka 2341dd846b Collapse all labor groups by default — use correct Table method
collapsedGroupsByDefault() is on Table, not Group::collapsedByDefault()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:47:26 +00:00
Vasyka f987071a6d Collapse all labor groups by default on page load
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:23:42 +00:00
Vasyka 7ed60b1dbf Restructure catalog to root categories + collapsible groups
- Migration: replace 45 leaf categories (diag.scan, engine.mech…) with
  17 root categories (diag, engine…); update labors.category by taking
  the prefix before the first dot; delete the leaf LaborCategory rows
- ImportLaborCatalogCommand: rewritten to use root categories directly
  so future imports go to the right level from the start
- LaborResource: add ->collapsible() to groups so user can collapse/
  expand each category section to see only headers or only one group
- entrypoint.sh: remove one-time catalog import (was already done)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:17:14 +00:00
Vasyka cbf8deabf3 Fix labor table grouping colspan + auto-import catalog on deploy
- LaborResource: hide category column from data rows by default
  (toggleable) so group headers span all columns cleanly; the group
  header already shows the localized category name
- ImportLaborCatalogCommand: add --all flag to iterate every company,
  refactor into importForCompany() helper, keep idempotent (skips
  existing codes, updates only with --force)
- entrypoint.sh: run catalog:import-labors --all after each deploy so
  all 386 operations + 45 categories are seeded automatically

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 05:07:32 +00:00
Vasyka 5e923459f7 Three features: labor group headers, status picker, catalog import
- LaborResource: add titlePrefixedWithLabel(false) so group headers show
  just the localized category name (not "Categorie: Шины"); remove static
  cache from closure to avoid Octane cross-request locale bleed
- WorkOrderDashboard: add changeStatus() Livewire method + Alpine.js
  dropdown on the status badge so status can be changed directly from
  the dashboard without going to the full edit form
- New artisan command catalog:import-labors {company} that reads
  database/data/labor-catalog.json (386 operations, 45 leaf categories)
  and upserts labor_categories + labors for the given tenant; supports
  --force to update existing records by code
- Add "Status actualizat" translation to ru/en JSON

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-08 04:38:35 +00:00
Vasyka 06fac16026 Fix labor catalog group headers to show localized category names
Defines a proper Tables\Grouping\Group with getTitleFromRecordUsing so
group headers use the locale-aware label from LaborCategory::optionsForSelect()
instead of the raw DB value with Filament's hardcoded "Category:" prefix.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 13:07:34 +00:00
Vasyka 116a53d81d Localize category column and filter label in LaborResource table
formatStateUsing looks up localized category name from LaborCategory
so 'Anvelope' shows as 'Шины' (ru) / 'Tires' (en). Filter label
uses __('Categorie') to avoid the auto-derived 'Category' (English).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:56:23 +00:00
Vasyka 9ee17fa918 Fix LaborCategoryResource: use Schemas\Components\Section + add translations
- Section was Filament\Schemas\Components\Section not Forms\Components\Section
- Added ru/en translations for page title, form labels, nav label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:46:03 +00:00
Vasyka 1b34a3c217 Add multilingual support to labor categories (RO/RU/EN)
- Migration: adds name_ro, name_ru, name_en columns; copies name→name_ro;
  seeds RU and EN translations for all 11 default categories
- LaborCategory: label() returns localized name, optionsForSelect() keyed
  by canonical name (stored in labors.category) with localized label
- LaborCategoryResource: 3-column form with RO/RU/EN inputs; table shows
  all three columns side by side
- LaborResource: category select and filter use optionsForSelect()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:37:39 +00:00
Vasyka 818ae29b20 Fix LaborCategoryResource: remove unavailable BulkActionGroup class
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:29:53 +00:00
Vasyka a2ddea2529 Add LaborCategory resource — manage labor categories from UI
- Creates labor_categories table (company-scoped) seeded with 11 defaults
- LaborCategory model with BelongsToTenant
- Full Filament CRUD resource with drag-to-reorder (sort_order)
- LaborResource category Select now reads from DB instead of hardcoded const
- Navigation: 'Categorii manopere' in Service group, sort 33

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:25:19 +00:00
Vasyka 1cf1dc2f54 Fix LaborResource breadcrumb/heading to show 'Catalog manopere'
getPluralModelLabel now uses nav.label.Norme-ore (already translated
to 'Catalog manopere'/'Каталог работ') so breadcrumb and page heading
match the sidebar navigation label.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:15:49 +00:00
Vasyka aba1fcc372 Fix client/vehicle inline creation and labor catalog visibility
- Migration: make clients.phone nullable (was NOT NULL, broke inline create)
- Vehicle createOptionUsing: use Get callback instead of $livewire->data
  to reliably get client_id in the vehicle inline form
- Remove plate->required() on inline vehicle form (not required in DB)
- Rename 'Norme-ore' → 'Catalog manopere'/'Каталог работ' in navigation
  so users can find the labor catalog easily

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-07 12:08:14 +00:00
Vasyka 6848e58f14 Translate PDF footer 'Document generat automat' for ru/en locales
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:31:55 +00:00
Vasyka 221e311c08 Fix subcontract jobs not appearing in PDF
Load subcontractJobs via explicit query (bypassing TenantScope but
scoped manually by work_order_id + company_id) to avoid relation
caching issues. Pass as separate blade variable \$subcontractJobs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:29:55 +00:00
Vasyka 2caac4ae8e Add subcontract jobs section to work order PDF
Loads subcontractJobs.subcontractor relation and renders a new table
in the PDF after parts: description, subcontractor, category, client price.
Adds ru/en translations for the new section labels.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:24:59 +00:00
Vasyka 4c7cacc765 Fix PDF filename double WO prefix, title translation, view cache clear
- WorkOrderPdfService::filename() removed duplicate 'WO-' prefix
  ($wo->number already starts with 'WO-')
- PDF <title> now uses __('FIȘĂ DE LUCRU') so it shows in user locale
- entrypoint: view:clear before view:cache to avoid stale compiled views

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:19:59 +00:00
Vasyka 9df834ffa1 Fix PDF locale and missing translations for ru/en
- Set app locale from user preference in PDF route before rendering
- Wrap hardcoded strings (TOTAL, Subtotal, Achitat etc.) with __()
- Translate status values via __() in blade
- Add ru/en translations: statuses, totals, payment lines

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:14:54 +00:00
Vasyka a556282581 Add missing PDF translations for Russian and English
Strings used in work-order PDF (Nr., Nume:, Telefon:, Marca/Model:,
Data deschiderii:, Km la intrare:, subtotals) were falling back to
Romanian in ru/en locale. Added all missing keys.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:09:28 +00:00
Vasyka 63ef482514 Fix Romanian diacritics (ș ț) and Cyrillic in DomPDF-generated PDFs
DejaVu Sans lacks glyphs for U+0219 (ș) and U+021B (ț). Fix: render
view to HTML first, replace with cedilla variants (U+015F/U+0163) that
DejaVu Sans supports, then load HTML. Set default_font to DejaVu Sans.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:03:24 +00:00
Vasyka 8a359f4440 Fix Romanian diacritics (ș ț) and Cyrillic in DomPDF-generated PDFs
DejaVu Sans lacks glyphs for U+0219 (ș) and U+021B (ț). Fix: render
view to HTML first, replace with cedilla variants (U+015F/U+0163) that
DejaVu Sans supports, then load HTML. Set default_font to DejaVu Sans.
Creates storage/fonts for DomPDF font cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 12:03:15 +00:00
Vasyka 9be4aab7e5 Refactor WO diagnostic: move all DB queries inside modal, add try/catch
Queries now run only when modal is opened — no risk of page-load
exception hiding the button. Button is always visible for admin/owner.
Adds restore action inside modal footer.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:44:12 +00:00
Vasyka 24bdc36913 Expand diagnostic modal to show all companies with WO counts
Helps identify cross-tenant WO leakage — shows each company's slug,
active count, deleted count, and whether it's the current tenant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:39:11 +00:00
Vasyka 5fa55f4bff Add restore button for soft-deleted WOs in admin diagnostic
Soft-deleted WOs (WO-26-0005..0008) are missing from admin view.
A new restore action lets the admin recover them without CLI access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:32:14 +00:00
Vasyka 8e32444a13 Fix diagnostic button visibility for admin users
Replace isAdmin() (Spatie-dependent) with direct role check and add
TenantManager fallback using hostname subdomain so the button appears
even if TenantManager was not resolved for the current request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:22:53 +00:00
Vasyka 0efe2e0bff Add diagnostic button to WO list for admins to inspect DB state
Shows total WO count broken down by tenant/other-company/deleted across
all records without global scopes, helping debug missing WO visibility.
Also adds a recover action when orphaned WOs are found in wrong company.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:20:23 +00:00
Vasyka 416b379d27 feat: add one-click orphaned WO recovery button for admins
Instead of relying on terminal/artisan commands, admins see a warning
button on the WO list when orphaned WOs exist (wrong company_id due to
Octane singleton bug). One click moves them all to the current tenant.
Button only shows if there are orphaned WOs and user is admin/owner.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:11:45 +00:00
Vasyka 6e18da755d fix(data): v2 WO company_id fix — also covers test companies with 1-2 users
Previous migration skipped companies with ≥1 user. Now moves WOs from
any company whose slug/name contains 'test' or has ≤2 users, which
covers the single test account scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:06:28 +00:00
Vasyka 2323f61d43 fix(data): migrate WOs from ghost tenants into psauto company
One-time data migration that moves work orders created with wrong
company_id (Octane singleton race condition) back into psauto.
Only moves WOs from companies that have zero users (ghost/test tenants),
so legitimate separate companies are not affected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 11:02:39 +00:00
Vasyka c2f87a468f fix: TenantManager scoped() + explicit company_id in WO create
FrankenPHP/Octane reuses the same PHP process for concurrent requests.
TenantManager registered as singleton() was shared between requests —
a concurrent request from another tenant could overwrite the current
tenant context mid-flight, causing records to be saved with the wrong
company_id.

Fix 1: Change singleton() to scoped() so Octane flushes TenantManager
at the end of each request (fresh instance per request lifecycle).

Fix 2: Explicitly set company_id in mutateFormDataBeforeCreate() as an
extra guard — even if the creating event fires in the wrong context,
the value from the request's own TenantManager call takes precedence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:49:18 +00:00
Vasyka c359c66827 chore: add tenant:fix-wo-company command to reassign WOs to correct tenant
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:45:32 +00:00
Vasyka ebc4fe0f23 fix: use role column directly in WO query — drop isMechanic() Spatie dep
isMechanic() calls hasAnyRole(['mechanic']) via Spatie which checks the
model_has_roles table with team_id scoping. If roles are not correctly
seeded for a tenant, admins/owners could accidentally pass the mechanic
filter and see only WOs where master_id = their own id (i.e. nothing).

Now uses $user->role === 'mechanic' directly — no Spatie involved —
so admin/owner/manager always see all WOs in their tenant.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:40:19 +00:00
Vasyka 7b1d296bd4 chore: add tenant:diagnose artisan command for production DB debugging
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:33:38 +00:00
Vasyka 4a896118b9 fix: simplify WO visibility — only mechanics are restricted to own WOs
Previous implementation relied on Spatie canDo() for all roles, which
caused admins/managers to see nothing if permissions weren't seeded.
Now: mechanics see only their assigned WOs (master_id = self); every
other role (admin, manager, receptionist, etc.) sees all WOs in the
tenant — TenantScope already prevents cross-tenant access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:25:55 +00:00
Vasyka a618f0af32 feat: implement per-role WO visibility via getEloquentQuery
Admin/owner and users with work_orders.view_all see all work orders.
Users with only work_orders.view_own_assigned (mechanics) see only
work orders where master_id = their own user ID.
Roles without either permission see nothing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-06 10:16:55 +00:00
Vasyka 0af8f2a755 fix: revert client_id to options() — relationship() broke initial list loading
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-04 20:09:44 +00:00
Vasyka 3c7e5fffbf improve WO form: inline client/vehicle create, mechanic filter, category translation, PDF emoji fix
- client_id field: switched to relationship() with createOptionForm so new clients
  can be created inline (name, phone, email); search also matches by phone
- vehicle_id field: added createOptionForm (make, model, plate, year, VIN); newly
  created vehicle is automatically linked to the currently selected client
- master_id field: now shows only active users with role mechanic/master
- WorksRelationManager: labor catalog select marked optional; added createOptionForm
  so new catalog entries can be added without leaving the WO form
- SubcontractJobsRelationManager: category table column now translates value via __()
  and shows explicit label(__('Categorie'))
- PDF work-order: replaced 📞 / ✉ / ⚠ emojis with plain-text equivalents so
  DomPDF/DejaVu Sans renders them correctly instead of showing "????"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-09-04 19:56:40 +00:00
Vasyka 2ca6ab58a1 feat(nav): FAQ shows all 8 sections + drag-drop reorder in Settings
FAQ sections were only 4 (service/crm/depozit/finante) even though
the sidebar has 8. Added the missing 4 to Hints::AREAS with proper
icons + accent colors:
  📊 Analiză (pink)   📣 Marketing (orange)
  🛒 Magazin (cyan)   ⚙️ Admin (slate)

Retagged 15 hints that were shoved into wrong areas as a workaround
(admin.*/marketing.*/shop.*/analytics.* now use the matching bucket).
FAQ blade reads accent color from the AREAS registry instead of a
hardcoded local map.

Also added per-tenant navigation group ordering:
- Settings → new "Ordine meniu lateral" section with a Filament
  Repeater that has reorderable buttons (drag/arrow buttons) but no
  add/delete (fixed list of 8 groups)
- Persisted to settings.nav_group_order as an array of keys
- New ConfigureNavGroups middleware (runs after ResolveTenant on
  the web stack) reads the current tenant's order and calls
  $panel->navigationGroups([...]) so the sidebar renders in that
  order per-request

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 11:09:24 +00:00
Vasyka 346fcf968f fix(faq): toggle worked server-side but click did nothing — nested forms
Filament pages wrap their content in an outer <form>. My inline
<form method="POST"> for the hint toggle + reset was nested inside
that outer form — invalid HTML, so browsers dropped my submit and
either did nothing or ran the outer Livewire form's handler.

Replaced both forms with plain <button type="button"> + onclick
fetch() POST + window.location.reload(). Controller now also returns
JSON when Accept: application/json (fetch sends that) instead of a
back() redirect.

The server-side flip was always correct — verified via a Feature
test that posts to /app/hints/toggle and asserts the user column
flipped from true → false.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 11:04:24 +00:00
Vasyka 6dba81502b fix(faq): replace confusing status pill with iOS-style toggle switch
Previous pill button showed "● ACTIVE" or "○ INACTIVE" but users read
it as a status label, not a clickable action — nobody clicked to
disable.

New UI: 46×24 iOS-style switch that slides between grey (OFF) and
green (ON) with a sliding white knob, plus a text label next to it.
The whole switch is a form button — clicking flips hints_enabled.
Title attribute + label make both directions obvious: "Click pentru
a DEZACTIVA" when on, "Click pentru a ACTIVA" when off.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 10:59:33 +00:00
Vasyka 0716b70bfa feat(faq): video tutorial + full-width layout + wider grid
- New tenant setting: settings.faq_video_url. TextInput on Settings
  page → Liste configurabile (accepts YouTube watch URL, youtu.be
  short URL, YouTube shorts/embed URL, or Vimeo URL).
- Faq page has getVideoEmbedUrl() that converts recognized URLs to
  their /embed/ variant. Unknown URLs fall back to a "?" card with
  a direct link. Admins see a "configure in Settings" nudge when no
  URL is set.
- Video renders as a 16:9 iframe capped at 480px height, above the
  hero.
- .faq-shell is now width:100% (was max-width:1200px) so FAQ uses
  all available horizontal space.
- .faq-grid switched from strict 2 columns to
  repeat(auto-fit, minmax(420px, 1fr)) so wide screens get 3 columns
  and narrow screens stack.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 10:50:40 +00:00
Vasyka cae2904c76 feat(faq): visual redesign — hero, chip toggles, 2-col colored cards
Replaced the flat vertical list with:
- Hero strip (gradient blue) with title, subtitle, and 2 stat pills
  showing total topics + section count
- Controls bar in a card: search input with inline icon, pill-shaped
  toggle button (green when hints ON), reset button with icon
- 2-column responsive grid of area cards. Each card has a colored
  header (service=blue, crm=purple, depozit=amber, finante=green)
  with icon + name + count badge
- Details/summary items use +/− indicators, tinted background when
  open, hover state; body has pill-styled links and green next-step
- Full dark-mode support
- Empty state with search icon

Same content, much easier to scan and looks like a proper help center.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 10:43:33 +00:00
Vasyka dbac930523 fix(hints): position "?" next to page title, fix Admin pages missing
Switched render hook from PAGE_HEADER_ACTIONS_BEFORE to
PAGE_HEADER_HEADING_AFTER:
- Previous hook lived inside the action buttons container which is
  skipped on pages without header actions (most Admin pages: Settings,
  Integrations, API Tokens, Backup, Activity log).
- Previous hook also pushed the icon to the right side on pages that
  did have actions (Clienți, Mașini, Calendar list), which looked
  disconnected from the title.

New hook renders "?" immediately after the page heading text on every
page, always visible. Also fixed one URL prefix mismatch:
app/pipeline → app/pipeline-board (actual Filament page slug).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 10:39:37 +00:00
Vasyka d7f1a43400 feat(hints): auto-inject on every main page via URL→key registry
Registry now covers ~50 hints across Service, CRM, Depozit, Finanțe,
Admin, Marketing, Analiză, Shop. Every main tenant page has an entry.

New Hints::forPath(\$path) does str_starts_with matching against a
curated URL prefix map. First match wins so specific paths (e.g.
/work-orders/create) beat broader ones (/work-orders).

A single render hook on PAGE_HEADER_ACTIONS_BEFORE looks up the
current request path and, if it matches, renders a "?" icon next to
the page header actions. Blades that already have <x-hint> tags
embedded (WO dashboard, Calendar, Pipeline, Excel wizard) keep those
on top of the auto-injected header hint.

Net effect: users see "?" icons everywhere in the panel now, and
adding coverage for a new page is a 2-line change (registry entry +
one URL prefix in forPath).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-09-01 10:20:26 +00:00