prisma / @prisma/client 6.19.2 -> 7.8.0; added @prisma/adapter-mariadb 7.8.0 and
mariadb 3.5.2 (the runtime driver).
Prisma 7 breaking changes handled (verified via Prisma's own tooling — the
official upgrade-guide summary had hallucinated package names, so I confirmed
everything against `prisma validate`/`generate` and the installed type defs):
- The Rust query engine is removed; the runtime connection is now made through a
driver adapter. src/config/database.ts wires PrismaMariaDb, parsing
DATABASE_URL into an explicit mariadb pool config so the literal `@` in our
password (which a naive connection-string parser mishandles) is correct.
- datasource `url` is no longer allowed in schema.prisma -> moved to a new
prisma.config.ts (schema + datasource.url + migrations.seed). Prisma 7 no
longer auto-loads .env, so the config does `import "dotenv/config"`.
- The deprecated package.json#prisma seed config was removed (now in the config).
- seed.ts and the two maintenance scripts now use the shared adapter-wired client.
The legacy `prisma-client-js` generator STILL works under v7 (generates to
@prisma/client as before), so NO import-path changes were needed across the 16
@prisma/client import sites — switching to the new prisma-client/output generator
is not required for us. No prisma.$use() middleware exists. Node 24 / TS 5.9
exceed the v7 minimums (20.19 / 5.4).
No migrations were run (your DBs are untouched); removing datasource.url is a
config change, not DDL, so this phase needs no new migration.
Gates: tsc 0 | build 0 | vitest 247/247 against app_test (proves the mariadb
adapter works at runtime, incl. raw SELECT...FOR UPDATE locking and Decimal/VAT)
| eslint 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
react / react-dom 18.3.1 -> 19.2.7; @types/react 18.3.31 -> 19.2.17;
@types/react-dom 18.3.7 -> 19.2.3 (latest stable 19.2.x).
Only breaking change hitting our code: React 19 removed the no-arg useRef<T>()
overload. AppShell's logoutTimer -> useRef<T | undefined>(undefined) — the same
transform the official `types-react-codemod preset-19` (useRef-required-initial)
applies. No other preset-19 transforms were relevant (already on createRoot; no
ReactDOM.render, string refs, propTypes/defaultProps on function components).
No Next.js / RSC in this project (it's a Vite SPA served by Fastify), so those
React 19 concerns don't apply here. MUI v7, framer-motion 12, TanStack Query 5,
react-router-dom 6 and react-quill-new all installed cleanly against React 19
(no peer-dependency conflicts).
Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CLAUDE.md: new "2026-06-09 audit" enforced-rules section (mandatory coercion
helpers, deterministic id-tiebreak ordering, $queryRaw FOR-UPDATE locking
discipline, uniqueness-in-transaction, guard reads, Rules-of-Hooks, seed prod
guard), lint/typecheck/format commands, and the app_test/.env.test testing
section with the hard-throw guard.
README rewritten from the placeholder stub (setup/run/build/test/gates/migrations).
REVIEW.md / REVIEW_FINDINGS.md / REVIEW_FIXES.md: the full audit record — 277
files reviewed, ~362 findings, every one fixed and verified.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From the 2026-06-09 full-codebase audit. Warehouse line->header relations
CASCADE on delete; line->master-data and the financial FKs are explicit
RESTRICT; UNIQUE on receipt/issue/session numbers; new indexes on hot FK
columns (sklad_issues/receipts, bank_accounts); dropped a duplicate
audit_logs created_at index. Applied to dev `app` and `app_test`.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Odin currently advertises general Q&A via the /chat path, which burns API
credits on open-ended questions. Guard it: a text-only message makes NO AI
call — it gets a canned reply telling the user to attach an invoice. The
invoice path (attachments → extract) is unchanged. Removing the guard
re-enables the general /chat path for a later phase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ensureActive() created the conversation and invalidated the list before the
server-side auto-title was applied (that happens on the message append), so
the sidebar showed "Nová konverzace" until a refresh. Move the
["ai","conversations"] invalidation to persist()'s success path — after the
append (and thus the auto-title) lands.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Composer rebuilt claude-style: a rounded pill with a multiline auto-growing
textarea and attach/send ICON buttons (vertically centered), so there's room
to type on mobile (was squeezed between two text buttons).
- Conversation list collapses into a slide-in Drawer below md, with a menu
button in the chat header; chat is full-width on mobile.
- Each new message bubble animates in (fade + slide-up; fade-only under
prefers-reduced-motion) via framer-motion, so sent messages and Odin's
replies appear smoothly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Swap Fraunces → Newsreader (literary serif, full Czech, optical sizing).
- Split the greeting so the user's first name renders in brand-red italic.
- Add a subtle staggered fade-in (framer-motion), skipped under
prefers-reduced-motion.
- New description that names Odin and covers chat + invoice import.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the gradient "Odin" wordmark from the new-conversation hero and make the
personalized greeting the centerpiece in Fraunces (optical-sized serif), with
an italic Fraunces description — a refined, literary, claude-like treatment.
Loads Fraunces (upright + italic) via the existing Google Fonts link.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Time-based Czech greeting + the user's first name (e.g. "Dobrý večer, Admin")
above the big gradient "Odin" wordmark, with a stylish description line. Drop
the redundant mark from the hero (it lives on the nav item + thinking state).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The mark already lives on the nav menu item; the conversation sidebar's brand
no longer duplicates it — just a larger gradient "Odin" wordmark above the new
chat button.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the small empty-state with a large centred hero — the animated Odin
mark + a big gradient "Odin" wordmark (responsive clamp font) and the helper
line beneath.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the chat-bubble glyph on the "Odin" nav item with the OdinMark, and
make the mark's SVG sizing !important so the nav's `& svg { width:18 }` rule
can't shrink it (its `size` prop stays authoritative everywhere).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A self-contained animated SVG mark in BOHA red: a 4-point AI spark (Odin) that
slow-rotates and breathes, a data node orbiting a faint ring (the assistant at
the centre of the business system). An "idle" and a "thinking" state (faster
orbit + red glow); honours prefers-reduced-motion. Used as the sidebar brand,
the empty-state hero, and the busy/thinking indicator.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace top tabs with a left sidebar (New chat + scrollable conversation
list, per-row rename/delete menu, active highlight), chat on the right.
- Fix duplicate-empty-conversations bug: "Nová konverzace" now just opens an
empty composer; a DB row is created only on the first successful message
(and a failed AI call no longer leaves an orphan conversation). Repeated
clicks never spawn empties, and nothing empty survives a refresh.
- Land on a fresh new chat on mount; deleting the open conversation returns
to new chat.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the 1100px max-width cap and size the chat shell to fill the content
area (100dvh - 100px = top bar + main bottom padding), so Odin uses the whole
page now that it owns the route.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final review caught two minor issues:
- saveInvoice invalidated ["received-invoices"], which matches no query (the
received list/stats are keyed ["invoices","received",…]); use ["invoices"]
so the list/stats actually refresh after an import.
- The Save button hit /received-invoices (requires invoices.create) while Odin
is gated only on ai.use; disable Uložit (with a hint) when the user lacks
invoices.create so the UI never offers a 403 action. Dormant today (ai.use is
admin-only and admin bypasses checks) but correct going forward.
Co-Authored-By: Claude Opus 4.8 (1M context) <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>
Add four presentational components + shared types under
src/admin/components/odin/ as props-driven building blocks for the
multi-conversation Odin page (Task 6 wires state/queries).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove single-thread getChatHistory/appendChatMessages/clearChatHistory and the
three /history routes; add listConversations, createConversation, getConversation-
Messages, appendConversationMessages, renameConversation, deleteConversation with
ownership checks and auto-title; wire six /conversations[/:id] routes.
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>
Final review flagged that the multipart limits set only fileSize, so a
single /extract-invoices request could carry unbounded PDF parts. The
per-batch budget re-check already bounds total spend, but this bounds the
work (one vision call per file) of any single request.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Admin-only Claude (Sonnet 4.6) chat embedded on the dashboard under the
welcome text. Attach received-invoice PDFs → extract (vision + structured
output) → confirm/edit → save via the existing received-invoices endpoint.
$50/month admin-editable budget enforced via an ai_usage table + pre-call
guard. New @anthropic-ai/sdk dep, ANTHROPIC_API_KEY, ai.use permission;
one migration. No system-data tools (Phase 2). Ships as v2.1.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The attendance card listed only employees with a record today (present /
break / clocked-out / on-leave). Now every active employee appears;
those with no record today show as "absent" (Nepřítomen), so the card
reflects the whole team. Loads active users (was a bare count) and
appends the unrecorded ones.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously notes were hidden when a cell held 2-3 records (a density
choice). Show each record's note regardless of count — drop the
now-unused showNote prop on PlanRangeChips.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Assign one project/category to many employees over a date range in one
action. "Hromadné přiřazení" on /plan-work: employee checklist +
include-weekends toggle, mirrors the single-create form. Creates range
entries grouped by contiguous eligible days per employee (weekends-off →
per-work-week chunks); per-day cap skips days already at 3; 92-day range
cap. Reuses createEntry; summary audit + result counts. No migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Assign one project/category to many employees over a date range in one
action. Modal mirrors single-create + employee checklist + "include
weekends" toggle. Stores range entries grouped by contiguous eligible
days per employee (weekends-off → per-work-week chunks); per-day cap
skips days already at 3. POST /plan/entries/bulk + bulkCreateEntries
(reuses createEntry); summary audit + result counts. No migration.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>