16 Commits

Author SHA1 Message Date
9a4072129a perf(web): add route-level loading skeletons for instant tab switching
The 'switching is laggy with many accounts' report — root cause is that
both /page.tsx and /users/page.tsx are Server Components that block on
the API fetch before sending any HTML. During the wait, the previous
route stays frozen (no spinner, no feedback) — the user perceives a 'lag'
that grows with row count.

App Router's loading.tsx convention solves this: Next.js renders it
INSTANTLY on navigation, then streams in the real RSC tree once the data
fetch resolves. The skeleton matches the shape of the real shell + a few
placeholder rows so the swap is layout-stable.

Files:
- web/components/table-skeleton.tsx — shared skeleton (PageHead + N rows)
- web/app/loading.tsx — used for /
- web/app/users/loading.tsx — used for /users

If row counts keep growing past a few hundred and the table itself
becomes the bottleneck (vs the network fetch this addresses), the next
step is pagination: accept ?limit=&offset= on /acc/ and /user/ in
cm_api.py and add a 'Load more' button (or a virtual list) at the
table-component layer.
2026-05-03 11:14:13 +08:00
eb297e977e fix(web): don't apply break-all when EditableCell has a custom renderView
The break-all on the value span was added so long URLs (link column)
wrap inside the narrow mobile cards. But it was also forcing
character-by-character breaks inside the StatusBadge (e.g., 'available'
splitting into 'ava\nila\nble' on narrow screens). Skip break-all when
renderView is provided — those callers render their own atomic widgets
(badges) that should never break.
2026-05-03 10:19:02 +08:00
d94dfc7f9a fix(web-auth): sign out works, drop passkey settings UI, add password reveal
- nav: the menu's onClick={setOpen(false)} on the Sign-out submit button
  was racing the form POST — React unmounted the form before the request
  flushed, so logout silently no-op'd. Drop the onClick; the Server
  Action's redirect to /cm-auth tears the menu down naturally.
- nav: drop the 'Passkey settings' link (passkey UI is gone).
- Delete web/app/cm-passkeys/. The WebAuthn Server Actions in
  auth-actions.ts are unreachable now (hasPasskeysForLogin always returns
  false in practice — no enrollment path), so the 'Sign in with passkey'
  button on /cm-auth never renders. The action handlers stay in case we
  reinstate enrollment later; they're dead code but harmless.
- auth-form: add an eye-toggle button on the password field that flips
  type=password ↔ text. tabIndex=-1 so Tab still goes input → submit
  without stopping at the toggle. Right-padded the input (pr-10) so the
  glyph doesn't overlap typed characters.
2026-05-03 10:17:54 +08:00
b4c526bf9f feat(web): nav account menu with sign out + passkey settings link 2026-05-03 08:31:00 +08:00
9771bb72c5 fix(web): move mobile status back into card header next to username
Earlier change made the badge the editable trigger and demoted it
into the body's Status row. That separated status from the row
identifier on mobile, which read as 'where is this status from?'.

Move the EditableCell-with-StatusBadge back into the card header,
right after the username, and drop the body Status row entirely.
Mobile now matches desktop's information density: identifier +
status badge inline, edit via badge click.
2026-05-03 08:17:23 +08:00
fe26878b38 fix(web): drop duplicate status — badge IS the editable trigger
The status column was rendering both a StatusBadge and a separate
EditableCell next to it, so 'done' showed twice in the same cell.
Adds a renderView prop to EditableCell so callers can override the
view-mode display; status now uses the badge as its visual, click-
to-edit behavior intact. Mobile card header drops its standalone
badge for the same reason — the body's Status row now shows the
badge inline.
2026-05-03 08:13:51 +08:00
6c984b6200 fix(web): lock body scroll while modal is open
Native <dialog> in iOS Safari (and a few other browsers) doesn't
prevent the page underneath from scrolling when the user scrolls
inside the dialog or near its edges. Save and restore body overflow
on open/close so the background stays put. Stays correct for stacked
dialogs because we save the previous value rather than blanket-reset
to ''.
2026-05-03 08:12:35 +08:00
3bfd35ef8d fix(web): PWA notch safe-area + skip autoFocus on touch devices
Adds viewportFit: 'cover' so the PWA can draw under the notch /
Dynamic Island when installed. Nav and Toast read env(safe-area-inset-*)
to keep their content out of the hardware cutouts (no-op on browsers
without a notch — env() resolves to 0).

Replaces autoFocus on the first field of CreateAccountDialog and
CreateUserDialog with a useEffect that only focuses on pointer devices
(matchMedia '(hover: hover) and (pointer: fine)'). Phones no longer
get the soft keyboard popping the instant a dialog opens.
2026-05-02 21:26:42 +08:00
eebbcb3db2 feat(web): success toast on confirmed create/delete
Adds a small top-centered <Toast> that fires only when the Server
Action returns { ok: true } (i.e., the DB write actually succeeded).
Auto-dismisses after 3s.

Wires both create dialogs (CreateAccountDialog, CreateUserDialog) with
an onSuccess callback that the table parent uses to push the toast,
and the delete confirm-flow does the same. Inline-edit success stays
quiet (no toast) — only add/delete trigger it, per the requested
scope.
2026-05-02 21:20:25 +08:00
e3ac94cada feat(web): manual create flow with input dialog for acc and user
api-server gets /create-acc-data and /create-user-data POST routes
that INSERT into the respective tables with required-field validation.
Frontend adds an 'Add' button next to Refresh in each table head;
opens a native <dialog> form with all fields. Inputs use 16px font on
phone (sm:text-[13px] desktop) so iOS doesn't auto-zoom.

A small form-dialog-shell helper centralizes the modal chrome,
field label, and input class so create-account-dialog and
create-user-dialog stay focused on their fields and validation.
2026-05-02 21:19:24 +08:00
e507714dc5 feat(web): delete with confirm dialog + fix iOS auto-zoom on edit
Adds × delete button per row in both tables (desktop column +
mobile card header). Click → native <dialog> confirm modal with
Esc/backdrop-cancel, destructive red button, error inline.
Wires deleteAccount/deleteUser Server Actions calling the new
api-server routes; revalidatePath refreshes the list on success.

EditableCell input switches to text-base (16px) on phone (sm:text-[13px]
above 640px), preventing iOS Safari auto-zoom-on-focus that was
shifting the layout when the soft keyboard appeared.
2026-05-02 21:17:19 +08:00
f13e3993e9 feat(web): reskin to refined SaaS aesthetic (per Dribbble reference)
Drop the brutalist hazard-tape vocabulary in favor of refined modern
SaaS: white cards on zinc-50, soft ring-1 zinc-200 borders (no hard
2px black), rounded-full pills, sans for chrome + mono for tabular
data, emerald replacing yellow as the saturated accent. Theme color
shifts to zinc-900 with an emerald dot on the icon.
2026-05-02 21:15:02 +08:00
3fe33772ce fix(web): editable cell wraps long values instead of overflowing
Long URLs in the link column would overflow on mobile because
truncate + inline-flex without min-w-0 expanded the cell beyond the
card width. Switch to flex+items-start, min-w-0 on the value span,
break-all so unbreakable strings wrap. Edit hint stays pinned right
with shrink-0.
2026-05-02 21:08:19 +08:00
7a5c00d08a feat(web): add layout, nav, and error boundary (frontend-design) 2026-05-02 21:01:15 +08:00
7b97e593e5 feat(web): add data tables and editable-cell primitive (frontend-design) 2026-05-02 20:56:49 +08:00
0ebd35f964 feat(web): add 30s auto-refresh client component 2026-05-02 20:50:55 +08:00