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>
Wire OdinTabs/OdinThread/InvoiceReviewCard/OdinComposer into a single
OdinChat orchestrator; mount it on /odin (maxWidth 1100); delete the
old DashAssistant and its aiHistoryOptions query helper.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Promote the AI chat from a dashboard widget to a dedicated page:
- new sidebar item "Odin" under the Přehled section, gated on ai.use, → /odin
- new /odin route + Odin page (reuses the assistant component, with a light
ai.use guard)
- remove the widget from the dashboard
- rebrand the assistant header "Asistent" → "Odin" and give the thread more
height now that it owns the page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The amount on a received invoice is the GROSS total the user pays (VAT
included), and VAT is the portion contained within it — not a net base with
VAT added on top. Switch the formula from amount*rate/100 to the
VAT-inclusive amount*rate/(100+rate) via a shared, tested vatFromGross()
helper used by both the create (manual upload + AI import share this
endpoint) and edit paths. Rate 0 → no VAT.
- AI extraction now asks Claude for the total INCLUDING VAT (was "základ bez
DPH"), reinforced in the structured-output schema description.
- Label the amount field "Částka s DPH" in the AI review card and the manual
upload/edit forms so the gross convention is explicit.
- Add unit tests with the user-confirmed figures (22 542,91 @ 21% → 3 912,41,
base 18 630,50) plus other rates and the no-VAT case.
Existing rows keep their stored (gross) amounts; their vat_amount recomputes
on next edit. A one-shot backfill can correct historical vat_amount if wanted.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Server-side, per-user chat history (the thread now survives reload):
- new ai_chat_messages table + migration; getChatHistory/appendChatMessages/
clearChatHistory; GET/POST/DELETE /api/admin/ai/history (ai.use-guarded);
service + HTTP tests (isolation, ordering, clear, perm, validation).
- DashAssistant loads the thread on mount and persists each turn; "Vymazat"
clears it. gcTime:0 so each mount re-reads the DB (no stale-cache resurrection);
composer gated until history settles + seed flag flipped on submit (no
seed/submit race clobbering a freshly-sent turn).
UX + correctness fixes from review/feedback:
- review cards moved OUT of the scrollable thread into a "Faktury k potvrzení"
section so Uložit is always reachable; chat auto-scroll no longer fires on
field edits.
- chat bubble text colour set on the Typography (GlobalStyles pins `p` to
text.secondary, which was overriding the inherited bubble colour → unreadable).
- never clear input/staged PDFs on a failed submit (rollback + keep), so a
budget error can't wipe staged invoices; roll back the optimistic user turn.
- stable attachment ids (no crypto.randomUUID — undefined over plain HTTP).
- editable Datum splatnosti + Popis fields; client-side extraction summary.
Security: DashProfile clipboard copy now uses an execCommand fallback (works
over plain HTTP) and only shows the success toast when the copy actually
succeeded — previously it falsely claimed to copy 2FA codes / the TOTP secret.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clearer, formal (matches the app's Vy/Vaše tone) and category-agnostic
(reads fine for work and absence days) so the employee plainly sees what
they're assigned to. Was "Můj plán na dnešek".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a "Můj plán na dnešek" card near the clock-in: for the logged-in
user it shows today's work-plan entry — category (coloured dot + label),
the project/site (the "where"), the note, and a "součást rozsahu …" badge
only for genuinely multi-day ranges. A muted "Pro dnešek nemáte
naplánováno." empty state when nothing is scheduled.
Server: GET /api/admin/dashboard now returns today_plan, reusing
plan.service resolveCell (override-beats-range precedence) for the current
user + today's local (Prague) date, enriched with the category label +
colour so the widget needs no extra query. Gated by attendance.record /
attendance.manage (the plan permission); omitted otherwise. No migration.
Verified in Chrome: renders today's entry (Práce · OBJ-2026-006 — koko)
with the category colour. tsc -b --noEmit, npm run build, vitest 152/152.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The labelled icon-badge tiles were inconsistent: some used a same-hue
light tile + colored glyph (e.g. the Settings 2FA row: success.light tile
+ success.main glyph — low contrast, the reported "badly visible" icon),
and the solid-tile ones used the bright .main fill in dark mode without
darkening, so a white glyph on bright green/amber was low-contrast too
(DashProfile's success badge was ~1.9:1).
Add a single `iconBadgeSx(color)` helper (theme.ts): solid `<color>.main`
fill + white glyph, darkened in dark mode (FILLED_DARK_BG) so white stays
legible; `default` = neutral grey. Applied to every badge so they can't
drift: StatCard, DashActivityFeed, DashProfile (2FA badge), Dashboard
(2FA banner icon), AttendanceHistory (month tile), and the Settings 2FA
row tile (now solid green when required / grey when optional + white lock,
instead of the low-contrast light-green tile). Also switched DashProfile's
backup-codes warning callout from warning.light to a subtle warning wash
so its amber text stays readable in dark mode.
tsc -b --noEmit, npm run build, vitest 152/152 all clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Removed MuiColorSchemeSync: it called MUI setMode which ALSO wrote data-theme, fighting ThemeContext (the other writer) → theme flip-flop loop. ThemeContext is now the sole owner; MUI's cssVariables CSS (scoped by [data-theme]) applies from the attribute, no JS bridge needed. New animated ThemeToggle kit button (sun/moon crossfade+rotate) replaces the static AppShell toggle. DashAttendanceToday avatars now solid-fill + white initials (matches StatCard/activity badges).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BulkAttendanceModal, ShiftFormModal, OrderConfirmationModal, DashQuickActions
trip modal, the Attendance leave modal, and DashProfile's 3 modals (edit
profile, 2FA setup, 2FA disable) now use the shared FormModal instead of
hand-rolled overlays, inheriting the canonical open/close animation, blurred
backdrop, ESC-to-close, click-outside, scroll-lock, staggered reveal, mobile
full-screen and reduced-motion. Footers stay pinned via the real FormModal
footer + submitDisabled; multi-step modals (2FA setup, order confirmation)
drive title/footer/close-guards per step. Behaviour preserved.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
11 form defaults across 6 files used new Date().toISOString().split('T')[0] (the UTC date), which lands on yesterday during the post-midnight Prague window (UTC+1/+2). Added todayLocalStr() (local YYYY-MM-DD) in formatters and replaced them. Future-date expressions (e.g. invoice due_date +14d) left untouched.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Change attendance idx_attendance_user_date from unique to index (allow multiple shifts per day)
- Reset migrations to single baseline init migration
- Add seed script with admin user (admin/admin)
- Update CLAUDE.md with migration workflow documentation
- Various frontend fixes (queries, pages, hooks)
Co-Authored-By: Claude Opus 4.7 <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>