Skip to content

feat(ui): themed confirm dialogs, mobile nav + backend safety fixes#147

Merged
ShogyX merged 3 commits into
mainfrom
fix/confirm-dialogs-mobile-nav-backend
May 28, 2026
Merged

feat(ui): themed confirm dialogs, mobile nav + backend safety fixes#147
ShogyX merged 3 commits into
mainfrom
fix/confirm-dialogs-mobile-nav-backend

Conversation

@ShogyX
Copy link
Copy Markdown
Owner

@ShogyX ShogyX commented May 28, 2026

Summary

Follow-up to #146 (UI bug fixes), landing the higher-value UX / accessibility and backend-safety improvements surfaced by the same full app audit. All changes are verified against the live backend (Vite dev proxy + Playwright) with the full unit suite + targeted backend tests green.

UX / accessibility

  • Themed confirm dialogs — replaced native window.confirm() with a promise-based useConfirm() hook backed by the app's Modal (focus trap, Esc, body-scroll lock, dark-mode tokens) across all 11 destructive call sites: rules, integrations, notification channels, automation schedules, tags, optimization profiles (incl. the 409 force-delete), housekeeping, account "sign out other sessions", and the dashboard + rules suggestion dismissals. Delete actions get a red (danger) confirm button, and it's never auto-focused so a stray Enter can't fire a destructive action. The hook degrades to the native dialog when no <ConfirmProvider> is mounted (isolated unit tests), so existing tests keep passing unchanged. ConfirmDialog.tsx, AppProviders.tsx (+ 11 sites)
  • Mobile navigation — at ≤640px the sidebar was display:none and navigation depended entirely on the ⌘K palette, unreachable on a touch device. Added a Topbar hamburger that opens the sidebar as a slide-in drawer with a dimmed tap-to-dismiss backdrop and auto-close on navigation. AppShell.tsx, Sidebar.tsx, Topbar.tsx, shell.css
  • Notification dropdown — anchored to the topbar row (positioned ancestor) and clamped to the viewport so the 380px panel can't overflow / cause horizontal scroll on narrow screens. shell.css
  • DataGrid font drift — bumped the primitive's hardcoded 13px/11px to the 14px/11.5px design baseline so it matches the hand-rolled tables. DataGrid.tsx

Backend hardening

  • VirusTotal free-tier-safe default (app/automation/jobs.py) — the per-minute drain cap fell back to 20 when lookups_per_minute was unset (e.g. configs predating the field), silently exceeding VirusTotal's 4/min free-tier limit → 429 → ~24h backoff. Now falls back to the free-tier ceiling (4), which is already the documented schema default.
  • Accurate reap status (app/worker.py) — reap_stale_scans now emits each run's real prior status (queued vs running) in the scan.reaped WS event instead of a hardcoded "queued".

Verification

  • tsc --noEmit clean · eslint . --max-warnings=0 clean · vitest run 552 passed / 3 skipped
  • Backend (deployed venv): test_virustotal_drain / test_virustotal_quota / test_scan_reaper / test_virustotal_stage1934 passed; changed modules import-smoke clean.
  • Live (Vite dev proxy → running backend, Playwright):
    • themed confirm dialog opens with title + Cancel and closes with no side effects (native confirm gone)
    • mobile (390px): hamburger shows → drawer opens → a link navigates and closes the drawer → backdrop tap closes it
    • notification dropdown fits within the viewport at 390px (left 8 / right 374 of 390)
    • regression walk across 15 routes: zero console / page / HTTP errors

Reviewer checklist

  • cd frontend && npm ci && npm run typecheck && npm run lint && npm test
  • Any delete/dismiss shows the themed dialog (not native confirm); Cancel is a no-op, Confirm performs the action
  • Narrow to phone width → hamburger → drawer opens / navigates / closes (link + backdrop)
  • Bell dropdown stays on-screen at narrow widths

Not included (deferred follow-ups)

  • Sonarr/Radarr history sortKey: the cursor compares by id but sorts by date; switching to sortKey=id needs validation against a live Sonarr/Radarr before merging.
  • Optimization retry contract: retriable_states includes "completed", but the UI never offers retry on completed items, so there's no user-facing bug; left as-is to avoid surprising direct API callers.
  • Skeleton loaders for table/card loads (larger change).

🤖 Generated with Claude Code

ShogyX and others added 3 commits May 28, 2026 17:05
Replace native window.confirm() with a promise-based useConfirm() hook
backed by the app's themed Modal (focus trap, Esc, body-scroll lock,
dark-mode tokens) instead of the browser's unstyled native dialog.

Converts all 11 live delete/dismiss/clear call sites: rules,
integrations, notification channels, automation schedules, tags,
optimization profiles (incl. the 409 force-delete), housekeeping,
account "sign out other sessions", and the dashboard + rules suggestion
dismissals. Delete actions get a red (danger) confirm button; the
confirm button is never auto-focused so a stray Enter can't fire a
destructive action.

The hook degrades to the native dialog when no <ConfirmProvider> is
mounted (isolated unit tests rendering a component out of the shell),
so the real app always gets the themed dialog while existing tests keep
passing unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Mobile navigation: add a Topbar hamburger that opens the sidebar as a
  slide-in drawer (dimmed tap-to-dismiss backdrop, auto-close on route
  change) at <=640px, where the rail is otherwise display:none and
  navigation depended entirely on the ⌘K palette — unreachable on a
  touch device with no keyboard.
- Notification dropdown: give the topbar row a positioned ancestor so
  the dropdown anchors to the bell cluster, and clamp its width to the
  viewport so the 380px panel can't overflow / cause horizontal scroll
  on narrow screens.
- DataGrid: bump the primitive's hardcoded 13px/11px body/header to the
  14px/11.5px design baseline so it matches the hand-rolled tables.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… status

- VirusTotal drain: when neither an explicit batch_size (manual drain)
  nor a configured lookups_per_minute is present, fall back to the
  free-tier per-minute ceiling (4) — which is already the documented
  schema default for lookups_per_minute — instead of 20. The old
  fallback silently exceeded VirusTotal's 4/min free-tier limit on
  configs created before the field existed, tripping a 429 and the
  next-midnight backoff that stalls all VT lookups for ~24h.
- reap_stale_scans: emit each reaped run's real prior status (queued vs
  running) in the scan.reaped WS event instead of a hardcoded "queued".

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@ShogyX ShogyX merged commit d1c9652 into main May 28, 2026
7 checks passed
@ShogyX ShogyX deleted the fix/confirm-dialogs-mobile-nav-backend branch May 29, 2026 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant