Rekapitulace (accountant notes, confirmed by owner 2026-07):
- Odpracováno = all worked hours EXCEPT hours worked on holidays
(was floor(worked/8)×8 — a 3h day printed as 0h)
- Vč. svátků a přesčasů = ALL worked hours; line2 − line1 = Svátek
(was worked + vacation − 8h×worked-holidays — could print less than
actually worked, and vacation inflated it)
- Přesčas = max(0, worked − fond), month-level, never negative
(was the sub-8h remainder + vacation — printed 3h overtime for a
3h month, 8h overtime for pure vacation, and −6h when a holiday
was worked)
- Svátek = hours actually WORKED on holiday dates
(was 8h per UNworked holiday — opposite meaning)
- So/Ne, Práce v noci, Dovolená, Fond unchanged (verified correct)
Single shared computeMzdaSummary now feeds BOTH the print and the
admin screen (they had drifted: capped vs uncapped holiday adjustment).
Print day table: one row PER RECORD — days with two shifts (real in
prod data) silently dropped all but the last record from the table
while the totals counted them all.
+7 spec tests (accountant's own examples); print snapshot updated to
the new spec values.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two bugs in the attendance admin print (Tisk on Správa docházky):
1. Viewer-timezone date shift: day rows are built from date-only strings
('2026-06-01'), which new Date() parses as UTC midnight per spec; local
formatting then rendered the PREVIOUS day for any viewer west of UTC —
a June print opened with '31. 5. Neděle' (a May row holding June 1's
punches) and every weekday name + weekend tint shifted by one. Czech
viewers were unaffected (UTC+1/+2), US viewers always hit it.
formatDate / getCzechWeekday / isWeekendDate now parse the calendar day
from the string parts — identical output in every timezone.
2. Every time cell carried a bogus date prefix ('1.6. 08:00'): the
overnight-only guard compared the extracted date part against the raw
wire shift_date ('2026-06-01T02:00:00') — never equal. Now compares
normalized date parts; the prefix fires only for true overnight punches.
Verified end-to-end against the production June payload; +8 regression
tests (timezone-proof by construction — parts-based, no UTC parse).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- NEW shared StatusChipMenu: clickable status chip opens a dense menu of valid
next states; picks confirm via the shared ConfirmDialog (danger variant,
loading, stays open on failure); plain chip without edit permission;
row-click-safe (stopPropagation both directions).
- Offers list: draft -> Aktivovat (number assignment noted, PDF archived after
finalize like the detail); active -> 'Vytvořit objednávku…' (opens the
existing create-order modal — 'ordered' stays owned by that flow) +
Zneplatnit; ordered -> Zneplatnit.
- ReceivedOrders list: Zahájit realizaci / Dokončit / Stornovat / Obnovit with
cascade notes; Czech quote pairs fixed („…“); OrderDetail transition button
says 'Obnovit' when reopening.
- Projects list + ProjectDetail: status combobox replaced by transition
buttons (Dokončit/Zrušit/Obnovit projekt) rendered from valid_transitions;
cascade notes only when a linked order will actually change; save no longer
carries status; busy states symmetric.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- projects.service: VALID_TRANSITIONS (aktivni->dokonceny/zruseny,
terminal->aktivni reopen), tolerant of legacy statuses; getProject returns
valid_transitions; updateProject validates transitions (invalid_transition
token -> Czech 400 in the route).
- NEW project->order cascade, transactional with the project update: finishing/
cancelling a project completes/cancels its linked received order — only while
the order is still open (never resurrects storno, never cancels completed);
reopen never cascades. Direct tx write, no service recursion; cascaded order
change gets its own audit row.
- orders.service: reopen edges (dokoncena/stornovana -> v_realizaci);
syncProjectStatus is reopen-aware (reopen skips project sync) and returns
the cascaded projects for per-project audit rows in the route.
- orders.schema: fix phantom 'zrusena' enum token -> canonical 'stornovana'
(every storno PUT through the route 400ed with a raw English Zod message;
latent until now because no UI sent it) + route-level regression test.
- NEW project-order-status-sync.test.ts: 15 tests — both cascade directions,
guards, reopen-no-cascade, legacy tolerance, forward-sync regression.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Review panel: flex children no longer shrink (flexShrink 0) — with 2+
extracted invoices the cards kept compressing to fit 35vh, squashing the
field grids with nothing to scroll; panel now scrolls, maxHeight 40vh.
- Drag & drop onto the chat column: depth-counted dragenter/leave (no
flicker), Files-only, refused while busy, PDF/image filter with a Czech
hint toast when nothing usable was dropped, channel-alpha overlay with
dashed primary border ('Přetáhněte soubory sem'), staged through the same
path as the paperclip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
POST /trips honored body.user_id for any trips.record holder — impersonation
on create (reads were already scoped). Now an explicit Czech 403 unless the
caller has the manager capability; manager on-behalf flow unchanged. +4 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- PROJECT_STATUS added to documentStatus.ts (aktivni=info, dokonceny=success,
zruseny=error — app convention); Projects/ProjectDetail local maps deleted;
status Select derives its MenuItems from the shared map.
- ProjectDetail linked-order status now rendered via the received-order map
(was the project map with zero key overlap → raw DB token).
- Cancelled label unified across received/issued orders in documentStatus.ts.
- 'Chyba pripojeni' → 'Chyba připojení' (Dashboard, AuthContext).
- Dashboard lazy-loaded like every other route — its 7 subcomponents leave
the Login critical path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Marking an invoice paid (issued + received lists) now confirms via
ConfirmDialog with loading guard — was a single unguarded chip click on a
terminal transition; chips got affordance tooltips.
- Invoices list PDF icon hidden for drafts (no number → /file 404s),
matching Offers/IssuedOrders.
- Search debounced (300ms) on Invoices/ReceivedInvoices (list + totals).
- Orders tabs create buttons renamed 'Nová vydaná/přijatá objednávka'
(were identical for two document types); issued empty-state CTA aligned;
draft delete dialog uses documentNumberLabel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Shared Tabs: variant=scrollable + auto scroll buttons — the 5 status-filter
tabs no longer clip unreachable at ~360px (desktop rendering unchanged).
- TitleSync: browser tab shows the active page ('Faktury · BOHA'; ambiguous
labels qualified with their section, e.g. 'Záznam – Docházka · BOHA');
index.html fallback now 'BOHA Admin'.
- Skip-to-content link (visually hidden, visible on focus) + id/tabIndex on
<main> — keyboard users skip the 248px sidebar.
- MUI csCZ locale merged into the theme (kills built-in English strings like
'No options'); Czech noOptionsText on Customer/Supplier pickers.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- OrderDetail/InvoiceDetail/LeaveRequests mutations now invalidate the same
domain sets as their list-page twins (orders+offers+projects+invoices /
+projects / +users+dashboard) — no more stale project/order labels after
a detail-page action.
- USER_INVALIDATE hoisted to lib/queries/users.ts; DashProfile self-edit now
refreshes trips/attendance/leave/projects like an admin edit.
- Global QueryCache.onError toast: failed first-loads no longer masquerade as
empty lists (data-present refetches stay silent; Unauthorized skipped; 4s
rate limit; meta.suppressGlobalErrorToast opt-out used by offer/issued-order
detail + the local TOTP QR query).
- Users/Vehicles active-toggle mutations toast on error (was silent revert);
clickable status chips got affordance tooltips (incl. warehouse parity).
- AuditLog: refetchOnMount always + staleTime 0 (mutations write audit rows
but nothing invalidates the key); search debounced; dead auditLogOptions
module removed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Items are no longer required on issued orders — an order can be issued
purely from its rich-text sections (Obsah).
- DocumentItemsEditor: opt-in allowEmpty prop lets the list go to zero
rows (default false keeps the offers/invoices at-least-one-item rule).
- IssuedOrderDetail: allowEmpty on the editor; a saved order with no
items reopens empty (not a blank starter row); submit guard now
requires at least one item OR a non-empty section, not an item.
- PDF: with no items the billing heading, items table and total row are
omitted entirely — a sections-only order shows only its Obsah.
- Service: finalize (draft->sent) and create-as-non-draft reject a
completely blank order (no items AND no sections) with a Czech 400
(empty_document); drafts may still be saved empty as WIP.
- Tests: sections-only finalize + blank-order rejection + sections-only
PDF render; existing finalize fixtures given minimal content.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review: add selected_custom_fields to offers/issued-orders
NON_STATUS_UPDATE_FIELDS so a non-editable document rejects selection
edits (status-only transitions still pass); tighten Zod cap 200->50 to
stay within VARCHAR(255).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>
Critical (data integrity):
- warehouse inventory confirm: throw (not return) inside $transaction so a
failed deficit line rolls back the surplus corrective receipt — retries
no longer accumulate phantom stock
- warehouse issue confirm: validate batches against the COMBINED quantity
of all lines (duplicate FIFO-resolved lines drove batches negative)
- attendance delete: restore vacation_used/sick_used for the deleted day
(in-transaction, clamped at 0)
High:
- auth refresh: terminated sessions (replaced_at only) get a plain 401 —
the theft branch (family revocation) now fires only on replaced_by_hash
- POST /users strips role_id for non-admin callers (mirrors PUT guard)
- issued-order transition flushes unsaved edits via the full save payload
when dirty; server contract (items+status in one PUT) pinned
- received-invoices list: usePaginatedQuery + pager (rows 26+ unreachable)
- received-invoice dates: nullableIsoDateString + NaN guard before NAS save
(Czech-format dates corrupted month/year, orphaned NAS files)
- leave approval skips Czech public holidays and books each calendar year's
hours against its own balance (mirrors createLeave)
Medium/Low (classes):
- 52 Zod caps aligned to DB column widths across 7 schemas (over-cap input
500ed at Prisma instead of a Czech 400)
- FK pre-validation: projects update + warehouse receipts/issues return
Czech 400s instead of P2003 500s
- invoice PDF degrades gracefully when the CNB rate is unavailable
(recap omitted instead of 500 + lost NAS archival)
- date boundaries: local-day filters (warehouse lists/reports, audit-log),
@db.Date coercion on invoice dates
- plan updateEntry re-checks the per-cell cap (self-excluding)
- {id} tiebreaks on customers/received-invoices/warehouse-items sorts;
/items honors the client sort param
- htmlToPdf relaunches once when the shared browser died mid-render
- offer number release parses the year from the document number (cross-year
finalize+delete left permanent sequence gaps)
- trips/vehicles km fields integer-coerced; AI budget regated to
settings.company|settings.system; Settings System tab no longer clobbers
Firma numbering patterns; draft invoices hide the dead PDF button;
dashboard quick-trip invalidates ["vehicles"]; TOTP secret cap 64;
audit-log + invoice month buckets day-shift fixes
Docs: corrected the stale "Chromium has no CSS margin-box footers" claim
(html-to-pdf.ts + CLAUDE.md — margin boxes render since Chrome 131); audit
report M3 withdrawn accordingly.
~65 new pinning tests; every finding reproduced RED against the real test
DB before its fix. Suite: 58 files / 634 tests green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One 0.2s enter-only fade (opacity + 6px rise) in the kit TabPanel covers
every tab switch (M3 clean-fade pattern; CSS animation so the global
reduced-motion kill switch applies). Orders and Invoices view switchers
now render through TabPanel (also fixes their dangling aria-controls).
Removed the inconsistent bespoke framer entrances inside tab content
(CompanySettings cascade, ReceivedInvoices card). Filter-style status
tabs (Projects/Offers/Invoices) deliberately keep the refetch dim only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
get_document_totals gains type=received_invoices: whole-set gross sums per
currency + total_czk via the same toCzk logic as the stats route (unknown
currencies skipped + reported), month/year via the literal columns (bare
year supported), unpaid/paid filter, period echo. invoices.view joined the
coarse gate (the per-type re-check keeps other families locked); the tool
description routes 'kolik jsme utratili' questions here.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Quill 2's semantic HTML saves a blank line as a truly empty <p></p> (no
<br> placeholder) — verified in the DB (invoice_sections hold <p></p>,
zero <p><br>) — which collapses to nothing outside the editor.
cleanQuillHtml (PDF path) and the new restoreQuillBlankLines (order/
invoice read-only previews) restore the placeholder at render time, so
existing stored content is covered retroactively.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- section/Quill overrides, footers and the shared header now lead with
'Segoe UI' (installed on prod) instead of Tahoma (not installed — prod
silently rendered sections and footers in Noto Sans, mismatching the
Segoe UI body; dev previews showed real Tahoma and never matched prod)
- cleanQuillHtml no longer deletes ALL style attributes: color and
background-color survive with strictly validated literal values (hex,
rgb/rgba, keyword) so editor font colors finally reach the PDF; url(),
expression() and every other declaration are still stripped — new test
file pins both the kept colors and the attack vectors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The 'Podrobny popis' textareas in the shared items editor and the invoice
page now grow with content like the rich-text editor (fixed rows={2} ->
minRows/maxRows autosize), capped at 16 rows; the manual vertical resize
handle stays.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- SPZ matching normalized on both sides (4SY7039 finds '4SY 7039') in
list_vehicles and the list_trips vehicle filter; JS-side name matching
diacritic-folded to keep utf8mb4_unicode_ci parity; ICO typed with spaces
matches the space-less stored value (suppliers + customers)
- get_top_customers: whole-table per-customer counts of invoices (sans
drafts) / orders / projects, top 20 + customers_with_records, optional
year, per-type permission re-checks — answers 'pro koho pracujeme nejvic'
exactly instead of refusing
- find_employee, find_supplier and stock search now return total_matching
(stock search also gained its missing deterministic orderBy); system
prompt teaches the model to answer counts from total_matching and use
aggregation tools instead of summing 20-row list pages
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Seven read-only tools: leave balance (own/attendance.balances parity), work
fund + overtime (getWorkfund), project hours report (attendance.manage),
supplier lookup (inactive only for warehouse.manage — picker-route parity),
vehicles with the route's odometer rule, whole-set document totals per
currency (fixes the sum-of-20-list-rows trap; bare month defaults the year
and the applied period is echoed — adversarial review caught the silent
all-time-as-monthly figure), and received-invoice detail with match count.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Five read-only detail tools (offer, order, issued order, invoice, project)
looked up by document number (partial match). They call the same getX(id)
services the detail routes use under the same .view permissions (parity
verified route-by-route), and return compact payloads: line items capped at
50 rows with full totals, is_included_in_total respected, rich text
stripped to plain excerpts, invoice VAT broken down per the editor rule
(per-item rate + apply_vat).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A PTR reload lands while the standalone-PWA viewport is still settling and
re-races the --app-height measurement (Chromium settles without firing
resize), bringing the scroll room back ~50% of the time. The immersive page
is fixed-viewport, so overscroll-behavior-y: none on html+body removes the
gesture (and the race trigger) entirely; restored on route leave.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Installed PWAs (MIUI especially) compute svh/dvh against a viewport that
includes system UI the layout viewport doesn't have, so the immersive page
still scrolled in standalone mode. AppShell now measures window.innerHeight
into --app-height (refreshed on resize/visualViewport, covering the
minimize-restore stale-viewport Chrome bug and the keyboard), and the
immersive shell + chat size from var(--app-height, 100svh).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Xiaomi Chrome keeps 100dvh at the large-viewport size while the URL bar
overlays the page, so the shell's 100dvh min-height left the document one
bar-height taller than the screen (scrollable + a strip under the
composer). The immersive route now sizes the document to exactly 100svh
with hidden overflow down the chain — body scroll is impossible regardless
of the browser's dvh interpretation. Other routes unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- bubbles spring in anchored at their avatar corner; thinking state is a
typing bubble (three bouncing dots) that springs in/out where the reply
lands; reduced-motion falls back to opacity fades
- mobile /odin is immersive: AppShell hides its header and main padding
below md, the chat owns 100svh full-bleed with safe-area insets and a
back arrow (history back, home on deep links)
- desktop card border kept divider-colored via longhand border props (a
responsive border shorthand reset the color to black)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Assistant bubbles use the animated OdinMark instead of the plain 'O'
circle; the 'Pracuji…' row drops its horizontal padding and matches the
28px mark size, so the thinking mark sits exactly in the avatar column.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The system prompt now lists the data areas the caller's permissions filtered
out, so the model tells the user explicitly they lack rights to that module
(and can ask the admin) instead of guessing the feature does not exist or
pointing them at a module they cannot open. Tool-less users get the same
explicit phrasing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A failed call followed by a successful retry showed two identical chips
(one orange). Retries are loop mechanics: the trace now carries one entry
per tool with ok = delivered-at-least-once, for live chips and persisted
history alike (also keeps meta.tools under its 30-entry cap).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- the sent message now leaves the input the moment the optimistic bubble
appears (restored along with attachments if the request fails)
- text selected in the primary-filled user bubble inverts to
white-on-primary instead of vanishing into the global ::selection style
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- find_employee: name -> user_id with route-parity population scoping
(attendance perms see plan users, trips perms see drivers sans usernames,
full directory only with users.view; trips.history unlocks nothing)
- get_attendance_summary: day-range detail with worked hours (shared
calcWorkedHours, break subtracted) + totals; attendance.manage also
unlocks the tool
- list_trips: kniha jizd with route-identical non-manager self-scoping and
the stats-endpoint km coalesce; bare month defaults to current year
- get_work_plan: resolved plan grid rows; off-grid users need
attendance.manage (grid-route parity)
- executeTool flags handler {error} results not-ok (consistent denial chips)
- system prompt: resolve employee names via find_employee first (gated on
the tool being present)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- PlanGrid maxHeight gets a 360px floor: calc(100dvh - 240px) left
<160px in phone landscape and the rows vanished under the sticky
header
- Phones get a person picker above the plan grid (one column at a
time, defaults to the logged-in user) - the multi-person grid never
fit a portrait viewport; desktop unchanged
- Odin chat height uses 100svh instead of 100dvh: dvh grows live as
Android Chrome collapses its address bar, so the page always allowed
a chrome-height scroll; svh sizes for the bar-visible viewport
(verified zero overflow at 390x844 via Playwright)
- Plan toolbar stacks into full-width rows on xs (headerActionsSx
pattern from the detail pages); Dnes/arrows and Tyden/Mesic stay
paired on one row each
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>