Commit Graph

10 Commits

Author SHA1 Message Date
BOHA
91072548d4 fix(odin): disable pull-to-refresh on the immersive mobile page
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>
2026-06-11 09:37:55 +02:00
BOHA
1293da7f25 fix(odin): measured --app-height for standalone-PWA viewports
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>
2026-06-11 08:48:57 +02:00
BOHA
f509e24942 fix(odin): kill body scroll on immersive mobile (MIUI dvh quirk)
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>
2026-06-11 08:42:04 +02:00
BOHA
4c8ed39d85 feat(odin): chat animations + immersive fullscreen mobile view
- 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>
2026-06-11 08:31:28 +02:00
BOHA
78cd7cf2d8 fix(shell): use 100dvh instead of 100vh so mobile has no phantom scroll
AppShell sized the shell with minHeight:100vh while #root (GlobalStyles) and the
Odin pane (OdinChat) use 100dvh. On mobile 100vh = the large viewport (address
bar hidden), a chrome-bar taller than the visible 100dvh, so the shell floor
pushed the document a few px over and html{overflow-x:hidden} (the page scroller)
turned it into a small downward scroll. Most visible on /odin (a fixed 100dvh
pane that otherwise never scrolls the page). Fixes every page on mobile; desktop
unchanged (100vh==100dvh there).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 21:27:10 +02:00
BOHA
6147131aad chore(deps): upgrade React 18.3 -> 19.2.7
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>
2026-06-09 06:51:43 +02:00
BOHA
519edce373 fix: 2026-06-09 full-codebase audit hardening
Validation: shared NaN-guarded Zod coercion helpers in schemas/common.ts replace
the raw number|string transform idiom across every schema (the root-cause NaN bug
class); emailOrEmpty + lenient isoDateString/timeString.

Security: roles privilege-escalation closed; refresh-token family revocation on
reuse; TOTP uses config params; read endpoints permission-guarded; received-invoices
gross VAT on all paths; orders-pdf custom-items authz.

Concurrency: $queryRaw SELECT...FOR UPDATE locks in ascending-id order (warehouse
confirm/cancel, attendance lockUserRow); uniqueness checks moved into create
transactions (TOCTOU -> 409); deterministic id tiebreak on second-precision
timestamp ordering (plan resolveCell/resolveGrid, warehouse FIFO).

Frontend: Rules-of-Hooks fixed across ~14 pages + PlanCellModal; UTC-date persisted
fields; dashboard invalidation gaps; stale-closure confirm bugs.

Tooling/tests: ESLint flat config (react-hooks/rules-of-hooks = error) + Prettier;
tsconfig.test.json so tsc -b type-checks the tests; removed 3 dead deps; npm audit
fix (8 -> 3). Suite 195 -> 247 (happy-path auth, FIFO oldest-first, flakiness fixes),
isolated on app_test via .env.test with a hard-throw setup guard.

Gates: tsc 0 | build 0 | vitest 247/247 | eslint 0 errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 06:45:26 +02:00
BOHA
58da09cdc0 chore(mui): swap remaining admin-spinner loaders for kit LoadingState
Replace the three remaining hand-written admin-loading/admin-spinner
markup blocks with the kit <LoadingState /> component:
- AdminApp.tsx Suspense fallback
- AppShell.tsx loading branch
- PlanGrid.tsx no-data branch (plan-* classes untouched)

After this the only remaining admin-* classes in TSX are
admin-rich-editor (RichEditor/Quill) and admin-rich-text-view
(sanitized HTML in Order/Invoice detail).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 08:50:39 +02:00
BOHA
177d3f1902 fix(mui): stop dark/light oscillation (single data-theme owner) + animated ThemeToggle + white avatars
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>
2026-06-07 07:39:14 +02:00
BOHA
9dbb5dbaab feat(mui): AppShell — responsive Drawer + topbar, guards + logout animation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 20:11:52 +02:00