Commit Graph

7 Commits

Author SHA1 Message Date
Developer 09b9c45665 SheetForm dirty-state confirm + wire isDirty into all form consumers (R4.5)
SheetForm gains an isDirty prop. When true, any close attempt (Cancel
button, header X, Radix overlay click, Escape) opens a 'Discard changes?'
ConfirmDialog instead of discarding unsaved edits. Radix dismiss callbacks
(onEscapeKeyDown, onPointerDownOutside) are intercepted when dirty so the
guard applies uniformly.

All four form consumers now compute and pass isDirty:
- ServicePage: name/enabled/config differ from the persisted instance.
- Settings machine editor: field-by-field draft vs editingMachine
  (create mode is always dirty; secret write-only fields excluded).
- Message compose: subject non-empty, body differs from default, or
  attachments present.
- WidgetConfigDialog: draft !== null (only draft mode is guarded; list
  mode has nothing to discard).

Tests: 3 new SheetForm dirty-guard cases (prompt on cancel, abort discard,
clean close when not dirty) + one focused dirty-guard test per consumer.
122 tests pass; lint/build green.

Refs openspec/changes/mobile-responsive-parity/verify-report.md residual
risk #1.
2026-06-26 15:31:30 +00:00
Developer 30f1b6e6db Touch-target audit: 44px minimum on mobile interactive elements (Slice 9)
Apply the mobile-touch-target CSS class to 40 interactive elements across
12 files. The class applies min-height/min-width:44px only below md
(max-width:767px), satisfying WCAG 2.5.5 / Apple HIG on touch devices.
Desktop behavior is unchanged.

Audit log (before -> after hit-area):
- App.tsx: hamburger/dark-mode/sign-out (32/32/28 -> 44)
- Dashboard.tsx: shortcut open/edit/delete (28 -> 44), enabled switch (18 -> 44)
- Media.tsx: mobile pagination prev/next (28 -> 44)
- FileBrowser.impl.tsx: 'Open Settings' alert button (28 -> 44)
- UsersPage.impl.tsx: compose toolbar bold/italic/link/list (32 -> 44),
  attachment remove button (16 -> 44)
- Settings.tsx: machine switch (18 -> 44), clear/add-machine buttons (28 -> 44),
  reset-db checkboxes x3 (16 -> 44)
- Actions.tsx: 'Add action' button (28 -> 44)
- ServicePage.tsx: service enabled switch (18 -> 44)
- ServicesPage.tsx: service switch/open-link/delete-icon (18/28/32 -> 44)
- ObservabilityPage.tsx: retry + 4 asChild link buttons (28 -> 44)
- WidgetConfigDialog.tsx: 4 icon buttons (32 -> 44), 2 switches (18 -> 44),
  2 add-widget buttons (28 -> 44)
- SessionActivityPanel.tsx: 'Open in Users' button (28 -> 44)

Deliberately skipped: default-size text buttons (32px, borderline), desktop-only
sidebar toggle, DataTable internals (desktop-only below md), Select triggers.
Dashboard anchor pills and HoverEditButton already had the class from Slices 1/2.

No new tests (the class applies via @media which jsdom doesn't honor).
116 tests pass; lint/build green.

Refs openspec/changes/mobile-responsive-parity/ (spec R6, tasks slice 9).
2026-06-26 14:37:40 +00:00
Developer 7808822a55 Mobile message compose + WidgetConfigDialog SheetForms (Slice 8)
Below md, both the message-compose Dialog and the WidgetConfigDialog
render inside a SheetForm instead of a centered Dialog.

Message compose (UsersPage.impl.tsx): the form body (subject, formatting
toolbar, HTML textarea, preview, attachments) is extracted into a shared
composeBody const consumed by both SheetForm (mobile) and Dialog
(desktop). SheetForm wired with title, onSave=handleSend (which already
closes on success per R4.5), onCancel=closeCompose, isPending,
saveDisabled, saveLabel='Send message'.

WidgetConfigDialog: the draftBody const is shared between branches. The
two-mode flow (list vs draft) maps to dynamic SheetForm props -- list
mode ('Dashboard widgets' / Done / Cancel both close), draft mode
('Add/Edit widget' / Save widget / Cancel=reset back to list). The
inline Back/Save buttons are hidden on mobile (!isMobile) since the
SheetForm footer provides them.

Desktop (md+) is token-identical for both components -- the
isComposeMobile (900px) fullscreen styling on compose is preserved for
the 768-900px band. The large diff (~860 lines) is dominated by
extraction/re-indentation of shared form bodies into consts; the
behavioral delta is ~80 lines.

Tests: 3 new (compose mobile send/subject, WidgetConfigDialog desktop +
mobile titles/Done). 116 tests pass; lint/build green.

Refs openspec/changes/mobile-responsive-parity/ (spec R4, tasks slice 8).
2026-06-26 14:17:30 +00:00
Developer 2eb649eceb Mobile Users + Backups tables: stacked cards + selection (Slice 5)
Below md, the Users directory and the three Backups tables render as
MobileCardRow cards:

- UsersPage: display name primary; username/activity/email fields. Each
  card carries a selection checkbox (44px via mobile-touch-target) in the
  actions slot with stopPropagation so toggling selection does not open
  the drawer; card-body tap still opens the drawer.
- BackupAlertsTable: alert message primary; severity/type/created fields;
  Acknowledge action preserved in actions slot.
- BackupJobsTable: job name primary; source/schedule/last-status fields
  (joins latestRuns into a JobCardRow).
- BackupRunsTable: run job_id primary; status/duration/size/started fields;
  status-filter Select renders above both layouts (preserved on mobile).

Desktop (md+) is byte-for-byte identical for all four components -- the
UsersPage diff is dominated by re-indenting the existing Table into the
isMobile ternary else branch.

Fix from Slice 5 review: MobileCardRow now renders the clickable card as
<div role=button tabIndex=0> with Enter/Space keyboard handling instead
of <button>, so nesting a Radix Checkbox (which renders a <button>) in
the actions slot produces valid HTML. The desktop-parity argument for
<button>-in-<button> did not hold (desktop rows are <tr>, not buttons).

Cross-cutting: useIsMobile hardened with typeof window.matchMedia guard
(safe in real browsers; only changes jsdom crash -> false). The file-local
900px compose hook was renamed useComposeViewport to avoid collision with
the shared 768px useIsMobile.

Tests: BackupJobsTable test file added (was untested), UsersPage mobile
selection round-trip + stopPropagation, mobile card render across all
four components. 105 tests pass; lint/build green.

Refs openspec/changes/mobile-responsive-parity/ (spec R3, tasks slice 5).
2026-06-26 13:24:19 +00:00
Developer 04f2e59c92 feat(frontend): slice 6b — Users compose dialog + 9 icons (finish Users)
Web UI rework. Completes the Users migration (6a directory + 6b compose):
- Compose dialog off @mui: shadcn Dialog family + Input (subject) +
  Textarea (html body) + Separator + Label; IconButton -> Button ghost
- 9 @mui/icons-material -> lucide-react: Close->X, AttachFile->Paperclip,
  FormatBold->Bold, FormatItalic->Italic, Link->Link,
  FormatListBulleted->List, MailOutlined->Mail, Send->Send,
  DeleteOutlined->Trash2
- Rich-text compose parity: markup insertion, attachments (FormData),
  queue-status polling, send via useSendUserMessage
- UsersPage.impl.tsx now has ZERO @mui imports

Gate: build + lint + test green (20 files / 46 tests).
2026-06-17 16:18:49 +00:00
Developer 1e23c07a20 feat(frontend): slice 6a — Users directory surface + drawer (shadcn)
Web UI rework. Slice 6a (force-split; 6b = compose dialog next):
- UsersPage.impl.tsx directory surface off @mui: shadcn Table family +
  Checkbox + Badge (status: success=chart-2/destructive/secondary) +
  Avatar + Tooltip + Progress + Alert/Button/Stack/Typography
- MUI Drawer -> shadcn Sheet side="right" for user detail drawer
  (buildUserDrawerModel rendering preserved)
- Selection-across-pagination + search/filter parity preserved
- Compose-dialog MUI subset (Dialog/TextField/Divider/IconButton +
  9 icons) intentionally LEFT for slice 6b

Gate: build + lint + test green.
2026-06-17 14:36:07 +00:00
alex 8f0a9650b0 refactor frontend and backend modules 2026-05-04 22:57:57 +02:00