The dashboard content grids used alignItems:"start", so cards in a row sized to
their own content (ragged bottoms). Switch to alignItems:"stretch" so each row
shares one height, and make the wrapped cards actually fill the cell:
- DashProfile / DashSessions: motion-div + Card height:100% (the inner card was
shorter than its stretched cell).
- Right column (Aktivní projekty + Nabídky): cards flex:1 so the stacked pair
fills the column to match the neighbouring single cards.
CSS-only; no logic change. tsc clean, lint 0, 669 tests pass. Verified live via
Playwright (3-col row cells equal at 607; bottom 2-col cards equal at 257).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Adds an optional per-line-item percentage discount ("Sleva") to offers and
issued invoices, on both the detail-page editor and the PDF.
- DB: discount Decimal(5,2) DEFAULT 0 on quotation_items + invoice_items
(migration 20260613195833_add_item_discount).
- Totals/VAT: new shared lineNet() (qty × price × (1 − discount/100)); offers'
NET total and invoices' subtotal AND VAT now compute on the discounted net
(every getBase/enrichQuotation/stats path). Backward compatible: discount 0
is identical to before.
- Schemas: discount = numberInRange(0,100), default 0; persisted on
create/update (+ offer duplicate).
- Detail editors: editable "Sleva %" column — offers via DocumentItemsEditor's
new opt-in showDiscount prop (issued orders unaffected); invoices in their own
editor. Spinners hidden + 7rem column so "100" is fully visible. Read-only
invoice view shows the column when any line has a discount.
- PDFs (offers + invoices): conditional "Sleva"/"Discount" column rendered only
when an item has a discount; line/total/VAT always use the discounted net.
Tests: +16 (money, totals incl. VAT-on-discounted-net, schema range, PDF
column present/absent). Full suite 665 pass, tsc -b clean, lint 0. Editor
verified live via Playwright. Design spec in docs/superpowers/specs/.
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>
unused-vars (25): leftover imports/types across pages, routes and services;
write-only sickHours accumulator; unused holidayCount pair; test helpers
authPatch/authDelete; TOut dropped from ApiMutationOptions (the hook keeps
its own generic — no call-site changes); requireAuth no longer importable
in customers/warehouse routes (matches the no-bare-auth-reads convention).
useless-assignment (5): initializers proven overwritten on every path
(systemQty x2, hours, writtenSize -> let x: number) and the seed's dead
adminUser reassignment (create kept, assignment dropped).
useless-escape (4): [eE+\-] -> [eE+-] and [\/...] -> [/...] — identical
semantics.
Behavior-neutral; lint 102 -> 68 warnings (0 errors), suite 641 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Migration 20260612234500: CHECK (quantity >= 0) on sklad_batches and
sklad_item_locations — the DB itself now rejects negative stock, so a
future C2-class bug (cumulative decrements driving a batch negative and
silently vanishing from totals) becomes a loud error instead of hidden
corruption. All three databases verified clean of negative rows before
the migration.
audit_logs.created_at TIMESTAMP(0) -> DATETIME(0): TIMESTAMP dies in 2038
and converts through the session timezone; existing wall times preserved
by the MODIFY.
Pinned by db-constraints.test.ts (4 tests, each watched fail pre-migration:
negative insert/update/location all succeeded before, 2098 audit timestamp
was rejected before).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sidebar (user-approved design, docs/superpowers/specs/2026-06-12-sidebar-
icons-design.md): all 31 menu items get unique contextual stroke glyphs in
rounded-square tiles tinted per section (Docházka blue, Kniha jízd teal,
Administrativa amber, Sklad violet, Systém slate; new teal/violet/slate
palette entries with channel tokens in both schemes). Replaces the old set
where 13 items shared a glyph (3x people, 3x sliders, ...) and Faktury was a
dollar sign. Odin stays the only solid + animated tile. Uniqueness pinned by
navdata-icons.test.ts (tsconfig.test.json gains jsx for the .tsx import).
Headers: ProjectDetail and OrderDetail now match the Offer/Invoice shell —
number rendered in secondary color at regular weight next to the bold
entity word, and flexWrap on both left header boxes so the title drops
below Zpět on phones instead of overflowing the viewport (the reported
mobile bug on received orders).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes every CRITICAL/HIGH finding from the 2026-06-09 full-codebase audit
(REVIEW_FINDINGS.md); each fix went through independent spec + code-quality
review. Plan and per-task log: docs/superpowers/plans/2026-06-10-audit-high-fix-pass.md
- attendance: schemas accept the combined local datetimes the forms/service
use (new dateTimeString helpers in schemas/common.ts), breaks persist on
create, AttendanceCreate submit rebuilt — every submit 400'd since 519edce
- 2fa: backup codes wired to /totp/backup-verify (+ remember-me parity),
enrollment QR generated locally via qrcode (CSP-blocked external service
also leaked the secret), dashboard shows per-user enrollment, not policy
- invoices/orders: per-line VAT survives re-saves (numberOr 0-respecting
coercion in formatters.ts), billing_text persists on update, issued-order
status transitions update UI gates
- trips: real pagination on all 3 pages, GET /trips/stats server aggregate
(shared buildTripsWhere + legacy distance coalesce), vehicle_id applies on
PUT with both-vehicle odometer recompute, print rebuilt (sync window.open,
escaped template, server totals)
- orders api: attachment_data PDF blob excluded from all non-binary reads
- warehouse: unit field is a Select over UnitEnum, receipt attachments
downloadable via new authenticated GET route
- downloads: shared RFC 5987 contentDisposition helper — Czech filenames no
longer 500 (warehouse, received-invoices, orders endpoints)
- misc: block-env hook actually blocks (exit 2 + stderr), project create
works with empty dates, NaN filter guards on trips endpoints
- deps: remove unused concurrently (clears both critical advisories), pin
@hono/node-server >=1.19.13 via overrides (clears the 3 moderates without
the Prisma 6 downgrade), drop deprecated @types stubs
Gates: tsc -b clean - vitest 30 files / 342 tests (31 new) - eslint 0 errors
- build OK
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>