When a new version is deployed, open tabs no longer silently run stale code
until a manual F5.
- Server stamps every response with X-App-Version (src/middleware/version.ts,
onSend hook in server.ts).
- Client bakes its build version via Vite define (__APP_VERSION__); apiFetch
compares the header to it (no polling — piggybacks existing traffic) and
latches a mismatch in a small store (appVersion.ts).
- UpdateBanner: a persistent bottom Snackbar "Je k dispozici nová verze
aplikace. — Obnovit" → location.reload(). User-initiated, so an in-progress
form is never interrupted.
- lazyWithReload: every React.lazy page reloads once if its hashed chunk 404s
after a deploy (sessionStorage-guarded against loops), fixing the "old chunk
gone" error screen.
Approach chosen after researching current SPA practice (version header + banner
beats polling / a service worker for a non-PWA admin app).
Tests: +4 (server header, client store latch/notify). Full suite 669 pass,
tsc -b clean, lint 0. Verified live via Playwright (header emitted; banner
appears on a simulated mismatch). Detection applies to deploys from the NEXT
release onward (current clients lack the detection code).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
UI fix:
- Close-only modals showing a redundant second close button now use
hideCancel: ReceivedInvoices paid-detail modal (was two identical "Zavřít"
buttons) and PlanCellModal "Den je součástí rozsahu".
- Add modal-duplicate-close test enforcing close-only modals set hideCancel.
Lint: cleared all 68 warnings → 0.
- preserve-caught-error: attach { cause } in ai.service / exchange-rates.
- no-require-imports: package.json version read via fs (APP_VERSION) instead
of require(), avoiding a rootDir-expanding static JSON import.
- react-hooks/exhaustive-deps (11): ref-in-cleanup copies, derived-value
useMemo wrapping, PlanGrid field extraction, stable nextKey useCallback,
AuthContext documented cycle-break.
- no-explicit-any (53): precise route param/Prisma types, generic enrich*()
preserving payload shape, minimal vite module type, frontend body/query-key
types, SystemInfo for Settings.
Refactor (test enablement): shift-form types moved to dependency-free
shiftFormTypes.ts so the print-HTML builders are unit-testable without the
component graph; characterization test pins their output.
Gates: 649 tests pass, tsc -b clean, lint 0. Verified touched flows live
via Playwright (PlanWork CRUD + optimistic cache, warehouse form keys,
Settings system info, invoice detail).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@fastify/static's default Cache-Control management overwrote the
setHeaders values with 'public, max-age=0' (verified on prod after the
v2.4.8 deploy) - the README requires disabling it for custom headers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After a release the old hashed chunks are deleted, so an open tab from
the previous build failed its next lazy-page import until a manual
refresh. Three-part fix per the Vite deploy guide:
- vite:preloadError listener reloads the page once (10s sessionStorage
guard against loops) so stale tabs heal themselves
- static cache headers: /assets/* immutable for a year (content-hashed
names), index.html no-cache so new visits always see fresh chunk refs
- missing /assets/* now 404 instead of getting the SPA index.html
fallback (HTML-as-JS masked the failure)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New /api/admin/ares proxy (browser cannot reach ares.gov.cz - CORS+CSP):
GET /ico/:ico and GET /search?q= map the MFCR REST API to the form
shape (street incl. orientation numbers, "511 01" PSC format, DIC).
Guarded by customers.create/edit + warehouse.manage; token errors map
to Czech messages. Shared AresAdornment button sits in the Nazev and
ICO fields of both modals: ICO mode fills directly, name mode searches
with a result picker. Tested against live ARES (BOHA, ICO 22599851).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Mobile work-plan (Plán prací) responsive fixes — desktop unchanged:
- Toolbar stacks into clean full-width rows on ≤640px (nav group,
centered range label, full-width segmented view toggle, full-width
category button); drops the fixed 230px label min-width and the
margin-left:auto that made the toolbar ragged on phones.
- Grid scroll container: 100dvh-based max-height, momentum scroll, and
overscroll containment so a fling does not bounce the whole page.
- ≤480px tightens columns (date col 72px via the colgroup <col>, person
cols 132px) so ~2 people are comfortably visible at 360–390px.
- Touch (@media hover:none): faint persistent + on empty editable
cells, since the hover-reveal add hint never fires on a phone.
Dev: `npm run dev` (backend) now listens on 3050 instead of 3000 to
avoid colliding with other local apps; CLAUDE.md dev-port note updated.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace hand-coded skeleton CSS/JSX with boneyard-js auto-generated bones
- Remove skeleton.css and @keyframes shimmer from base.css
- Add <Skeleton> wrappers with fixtures to all 25+ page components
- Generate 20 bone captures via boneyard CLI (CDP auth-gated capture)
- Refactor data fetching from useEffect+useState to TanStack Query
- Extract query hooks into src/admin/lib/queries/ and apiAdapter
- Add usePaginatedQuery hook replacing useApiCall/useListData
- Fix parseFloat || 0 anti-pattern in OfferDetail and OffersTemplates inputs
- Fix customer_id mandatory validation on offer creation
- Fix leave-requests comma-separated status filter (Prisma enum in: [])
- Add cross-entity cache invalidation for orders/offers/invoices/projects
- Make rate limits configurable via env vars (RATE_LIMIT_MAX, RATE_LIMIT_REFRESH, etc.)
- Add boneyard.config.json with routes and breakpoints
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- feat: order confirmation PDF generation with VAT support
- feat: order confirmation modal with custom item editing
- fix: attendance negative duration clamping and switchProject timing
- fix: Quill editor locked to Tahoma 14px, PDF heading sizes
- fix: invoice/offer PDF font consistency (Tahoma enforcement)
- fix: invoice alert cron improvements
- fix: NAS financials manager edge cases
- refactor: numbering service with unique sequence constraints
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- System settings page with tabs: Security, System, Firma
- Configurable attendance rules (break thresholds, rounding) from DB
- Configurable document numbering with template patterns ({YYYY}/{PREFIX}/{NNN})
- Dynamic logo upload (light/dark variants) served from DB instead of static files
- Email settings (SMTP from/name, alert/leave emails) configurable in UI
- Currency and VAT rate lists configurable, used across all modules
- Permissions simplified: offers.settings + settings.roles + settings.security → settings.manage
- Leaflet bundled locally, removed unpkg.com from CSP
- Silent catch blocks fixed with proper logging
- console.log replaced with app.log.info in server.ts
- Schema renamed: company-settings.schema → settings.schema
- App info section: version, Node.js, uptime, memory, DB status, NAS status
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switching months quickly on received invoices triggered rate limit
due to multiple API calls per navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Daily cron (8:00 AM) checks created and received invoices
- Alerts 3 days before due date and on due date
- Summary email to INVOICE_ALERT_EMAIL with grouped tables
- Tracks sent alerts in invoice_alert_log to prevent duplicates
- node-cron scheduler runs inside the app process
- Favicon files copied from PHP project
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- NAS storage for created invoices (PDF via puppeteer), received invoices,
and offers with auto-save on create/edit
- Deterministic file paths derived from DB fields (no file_path column needed)
- Separate NAS mount points: NAS_FINANCIALS_PATH, NAS_OFFERS_PATH
- Invoice language field (cs/en) stored per invoice, replaces lang modal
- Invoices list filtered by month/year matching KPI card selection
- Centralized date helpers (src/utils/date.ts) replacing all .toISOString()
calls that returned UTC instead of local time
- Attendance project switching uses exact time (not rounded)
- Comment cleanup: removed ~100 unnecessary/Czech comments
- Removed as-any casts in orders and attendance
- Prisma migrations: add invoice language, drop received_invoices BLOB columns
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>