Files
alexei.dolgolyov 6a8f374678 feat: observability, per-receiver Telegram options, oversized-video fallback
Operability:
- Correlation IDs end-to-end: shared dispatch_id between log lines and
  EventLog rows (event/watcher/scheduled/deferred/action/HA/command paths)
  and a new X-Request-Id middleware that normalizes inbound ids and binds
  request_id into log context.
- dispatch_summary block merged into EventLog.details: per-target
  success/failure counts plus Telegram media delivered/skipped/failed and
  truncated error lists, so partial outcomes surface in the UI.
- Diagnostic mode: admin can flip one module to DEBUG for a bounded
  window with auto-revert (in-memory only; setup_logging() resets on
  boot, lifespan reverts on shutdown). New /diagnostic-mode endpoints
  plus DiagnosticsCassette UI on the settings page.

Telegram:
- Per-receiver options: disable_notification (silent send) and
  message_thread_id (forum-topic routing), wired through the dispatcher
  via a ContextVar so all four send sites (sendMessage / sendPhoto-Video-
  Document / sendMediaGroup / cache-hit POST) pick them up.
- send_large_videos_as_documents target setting: bypass the 50 MB
  sendVideo cap by falling back to sendDocument for oversized videos.
- sendMediaGroup byte-budget enforcement (TELEGRAM_MAX_GROUP_TOTAL_BYTES,
  45 MB) with per-item fallback on chunk failure so a stale file_id no
  longer silently drops a cached asset.

Tests:
- New: diagnostic_mode, dispatch_summary, request_correlation,
  telegram_media_group_partial, telegram_per_send_options.

Docs:
- .claude/reviews/: six-axis production-readiness review of v0.8.1.
- .claude/docs/functional-review-2026-05-28.md: focused review of
  Telegram/Immich/logging subsystems.
2026-05-28 15:19:31 +03:00

35 KiB
Raw Permalink Blame History

UI / UX Design Review — Notify Bridge frontend

Reviewed: 2026-05-22 Scope: SvelteKit frontend at frontend/, "Aurora / Glass" aesthetic, en + ru locales. Reviewer method: Read app.css, +layout.svelte, dashboard, login, setup, providers, targets, users, settings (parent), settings/IdentityCassette, notification-trackers, template-configs, actions, bots, plus shared components (Card, Button, Modal, ConfirmModal, AuthLayout, PageHeader, EmptyState, Loading, Snackbar). Cross-cutting Grep passes for inputs, border-radius, ARIA, sort, hex colors.


Executive summary

  • Aurora design language is real and distinctive. Newsreader display serif + Geist variable sans + Geist Mono, conic-gradient brand orb, animated radial-gradient aurora background (body::before 28s drift), gradient pill chips, glow-pulse dots, and the lavender/orchid/mint/citrus/coral/sky palette together give the product a clear visual identity. This is not generic admin-template AI slop — the dashboard hero, signal-stream rows, provider deck, and the PageHeader "subpage-hero" pattern all carry intentional character that the user will remember.
  • Consistency is the weakest axis. Five overlapping card container abstractions (.hero-card, .panel, .glass, Card.svelte, settings .cassette/.identity) re-implement the same frosted-glass recipe with diverging radius (22 / 18 / 14 / 12 px) and padding (1.25/1.4 vs 1.3/1.4 vs 2/2.4 rem). A --radius: 1rem token is declared but unused. Pick one card module + one radius scale (e.g. --radius-card: 22px, --radius-input: 12px, --radius-pill: 999px).
  • Forms have not been migrated to Aurora. ~71 occurrences across 17 files still use the legacy raw class string border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)] instead of the global input { ... } rule already in app.css (which uses --color-input-bg, --color-rule-strong, 0.625rem radius, glow focus ring). Result: rounded-md (6px) fields next to rounded-2xl (22px) cards, solid opaque backgrounds inside frosted-glass cards. Removing the override class would auto-restyle every form to match. HIGH priority, mostly mechanical.
  • Hardcoded hex colors leak through. Snackbar uses #059669 / #ef4444 / #3b82f6 / #f59e0b instead of --color-mint/coral/sky/citrus. ConfirmModal uses a raw rgba(239, 68, 68, 0.3) glow. Actions page uses #059669 for the enabled dot. All bypass theming — they will look wrong in light theme.
  • Snackbar is invisible to screen readers. No role="status" / aria-live="polite" / aria-live="assertive" on the toast container. Critical confirmations (saved, deleted, error) are never announced. HIGH accessibility fix, one-line.
  • No aria-current="page" anywhere in the nav — active state is conveyed only visually (border-radius bar + glow). Active state has no accessible name.
  • No sortable columns, no multi-select bulk actions, anywhere in the app. Lists rely entirely on IconGridSelect sort widgets (newest / oldest, etc.) and per-row icon buttons. For a notification routing system that may accumulate dozens of trackers / targets / configs, this scales poorly.
  • Localization parity is solid string-for-string (en.json = ru.json = 1577 lines). Russian renders the same characters but several places (hero title, brand row with provider name, stat-card label/value flex) have no length-guard for the longer Russian translations — visible truncation/wrapping likely.
  • Onboarding is a single screen. After /setup lands you on / with 0 providers and a hero saying "all clear" — the most important first-run moment shows nothing to do. No checklist, no empty-dashboard CTA panel, no tour.
  • Power-user feature standout: ⌘K SearchPalette is present and wired through the topbar, global provider filter, and reduced-motion media-query support. These three deserve credit and should be more discoverable (no in-app hint they exist).

Findings by area

1. Design quality vs generic AI aesthetic

F-DESIGN-01 — Aurora identity is strong and self-consistent at the macro level [LOW / commendation]

  • Files: frontend/src/app.css, frontend/src/routes/+layout.svelte, frontend/src/routes/+page.svelte
  • State: Newsreader display serif italic with linear-gradient text-clip is used in hero titles, panel titles, modal titles. Conic brand orb is unique. Aurora drift on body::before is a 28s slow loop that's never busy. The "signal" / "wires" / "on watch" / "pulse" / "stream" / "compose" semantic naming on the dashboard is editorial, not generic admin copy.
  • Verdict: Keep all of this. Lean further into it on the subpages — most list pages currently default back to plain "PageHeader + Card list" without inheriting the dashboard's editorial flavor.

F-DESIGN-02 — Italic-serif emphasis loses impact on smaller subpage titles [LOW]

  • Files: frontend/src/lib/components/PageHeader.svelte (lines 132147)
  • State: subpage-hero__title is 2.15rem with italic emphasis on a gradient. At that size the gradient italic word is legible but loses the editorial drama it has at the 3rem dashboard hero. Russian translations (em words like «операторы») sometimes look cramped because letter-spacing -0.025em is shared with the much larger dashboard hero.
  • Suggestion: Use a separate letter-spacing scale per font size step, or drop italic emphasis on titles below ~2rem and use color-only emphasis there.

2. Visual consistency

F-CONSIST-01 — Five overlapping card abstractions [HIGH]

F-CONSIST-02 — Border-radius drift, no scale [HIGH]

  • Files: frontend/src/routes/+layout.svelte, frontend/src/routes/+page.svelte, frontend/src/app.css
  • State: Radii used: 22, 18, 14, 12, 11, 10, 9, 8, 7, 6, 3, 2 px + 0.3, 0.5, 0.625, 0.85, 1 rem + 9999px. --radius: 1rem is declared in the theme but only re-declared — no component reads it.
  • Suggestion: Define and use --radius-card: 22px; --radius-panel: 18px; --radius-pill: 999px; --radius-input: 12px; --radius-chip: 8px; --radius-tile: 6px;. Refactor in passes — start with Card.svelte, Button.svelte, Modal.svelte, ConfirmModal.svelte.

F-CONSIST-03 — Hardcoded hex colors bypass theming [HIGH]

  • Files:
  • State: These colors are NOT the Aurora palette — #059669 is emerald-600, our mint is #7ee8c4. In light theme the user sees green-on-green that wasn't intended.
  • Suggestion: Replace all status hexes with --color-mint/coral/sky/citrus/orchid. Add a stylelint rule color-no-hex scoped to src/**/*.svelte to prevent regression.

F-CONSIST-04 — Form input styling not migrated to Aurora [HIGH]

F-CONSIST-05 — ConfirmModal duplicates Button.svelte logic [MEDIUM]

  • Files: frontend/src/lib/components/ConfirmModal.svelte
  • State: Its .confirm-btn-cancel and .confirm-btn-delete re-implement what Button variant="secondary" and Button variant="danger" already provide. The danger button even uses raw rgba(239,68,68,...) instead of --color-error-fg.
  • Suggestion: <Button variant="secondary" onclick={oncancel}>{cancel}</Button> and <Button variant="danger" onclick={onconfirm}>{confirm}</Button>. Removes ~35 lines of CSS.

F-CONSIST-06 — AuthLayout uses a different glass recipe [MEDIUM]

  • Files: frontend/src/lib/components/AuthLayout.svelte (line 68 .auth-card)
  • State: border-radius: 1rem, padding: 2rem, backdrop-filter: blur(8px) (vs the 28px elsewhere), plus its own auth-bg gradient mesh + 32px-grid background that nothing else in the app uses. Has its own .auth-input / .auth-submit / .auth-label / .auth-error design language.
  • State pt 2: Login/setup ends up looking more like generic SaaS than the dashboard does. The brand orb from the sidebar isn't on the login screen — instead a small lavender mdi-lan icon in a square.
  • Suggestion: Reuse the conic brand orb. Use the same glass recipe (28px blur, 22px radius) for .auth-card. Either drop the dot-grid .auth-grid (it reads as a generic "futuristic SaaS" template) or use it as a deliberate flair on the dashboard hero too.

3. Information hierarchy

  • Files: frontend/src/routes/+page.svelte lines 571645
  • State: All four stat cards have the same visual weight, same accent intensity (STAT_ACCENTS[idx]), and rotate accents by index. When the global provider filter is active the first stat card morphs into a "literal value" card showing provider name (1rem font, very different visual). The accent rotation creates a rainbow row that doesn't carry meaning — events total has no semantic reason to be orchid vs. providers being lavender.
  • Suggestion: Tie accent color to entity type (providers=primary, trackers=mint, targets=sky, throughput=citrus) so the same accent recurs throughout the app for the same concept. Keep the morph behavior but design a distinct "filtered context" stat-card variant — a smaller, narrower chip — so it doesn't compete visually.

F-HIER-02 — Hero title and meter compete for attention at desktop width [LOW]

  • Files: frontend/src/routes/+page.svelte lines 10471068, 10781086
  • State: Both the .hero-title and .hero-meter-value are 3rem 500-weight in two different fonts. Side-by-side they create two focal points.
  • Suggestion: Shrink .hero-meter-value to 2.4rem and use it as a secondary read; let the editorial title be the single dominant element.

F-HIER-03 — Pulse chart panel rarely meaningful on first launch [LOW]

  • Files: frontend/src/routes/+page.svelte lines 909927
  • State: On a fresh install the chart is an empty 0-events grid taking 250-400px vertical space. No empty-state copy inside EventChart.
  • Suggestion: When chartDays has all-zero values, replace with a small "No events recorded in the last 30 days — once a tracker fires, the pulse will appear here" inline empty state.

4. Navigation & wayfinding

  • Files: frontend/src/routes/+layout.svelte lines 498533, 591597, 632658
  • State: Active state is conveyed via .active class + a gradient left-bar div. Screen readers cannot announce it. Grep for aria-current across the whole frontend: zero matches.
  • Suggestion: Add aria-current={isActive(child.href) ? 'page' : undefined} to every nav <a>.

F-NAV-02 — No breadcrumb on subpages [MEDIUM]

  • Files: frontend/src/lib/components/PageHeader.svelte
  • State: The crumb prop only renders a single mono-uppercase tag (e.g. "ROUTING · AUTOMATION") — it's decorative, not navigational. There's no actual breadcrumb chain. For /template-configs, /command-template-configs, /tracking-configs, /command-configs, etc., a user landing via deep link has no parent-link to return to.
  • Suggestion: Make the crumb a real breadcrumb (≤3 levels: Notifications → Templates or Commands → Configs). Render the prior level as a clickable <a>.

F-NAV-03 — Deep linking via ?type=<targetType> and ?tab=<botType> doesn't update page title [LOW]

  • State: /targets?type=email and /bots?tab=matrix change the active sidebar item but the <PageHeader> title for those pages is generic ("Targets" / "Bots").
  • Suggestion: When activeType is set, derive the title from it: "Email targets" / "Matrix bots". Improves browser tab titles and the in-page title.

F-NAV-04 — Collapsed sidebar tooltip wraps for long Russian translations [LOW]

  • State: Tooltips for collapsed sidebar nav items use the browser-native title= attribute, which gives no glass-style chip. They will use the OS tooltip styling, which clashes with the Aurora aesthetic and clips long ru labels.
  • Suggestion: Build a small custom tooltip component (or use existing portal helper) for collapsed-sidebar nav. Keep title as fallback for prefers-reduced-motion users.

5. Form UX

F-FORM-01 — No inline field-level validation, only post-submit error banners [MEDIUM]

F-FORM-02 — Save feedback inconsistent across pages [MEDIUM]

  • Files: Settings uses a sticky SaveBar with dirty tracking (frontend/src/routes/settings/+page.svelte lines 7784, 208214). Most other forms have inline Save buttons inside the card. Some show snackbar success ("snack.userCreated"), some don't.
  • Suggestion: Standardize: (a) inline "Save" inside the card plus (b) snackbar success message plus (c) optional sticky SaveBar for multi-field admin forms. Document the pattern in .claude/docs/frontend-architecture.md.

F-FORM-03 — Forms auto-name from descriptor but offer no way to unlock it back to auto-name [LOW]

  • Files: frontend/src/routes/providers/+page.svelte lines 136141 + 303; frontend/src/routes/actions/+page.svelte lines 5056
  • State: Once user types in the Name field, nameManuallyEdited becomes true and the auto-fill stops permanently — no way to ask "go back to default name".
  • Suggestion: Add a tiny "↺ reset" link next to the name input when nameManuallyEdited && form.name !== descriptor.defaultName.

F-FORM-04 — No optimistic UI; rows disappear / appear only after server roundtrip [LOW]

  • State: After delete/create, pages refetch via cache.fetch(true). Visible 200-400ms blank state.
  • Suggestion: Optimistic insert/remove in the cache stores, with snackbar undo for destructive ops.

F-FORM-05 — Login form omits autofocus on username [LOW]


6. Modals & overlays

F-MODAL-01 — Modal.svelte is well-built [LOW / commendation]

  • Files: frontend/src/lib/components/Modal.svelte
  • State: Portal mount, focus trap, focus restoration, Escape, Tab cycling, aria-modal="true", aria-labelledby, body scroll containment via overscroll-behavior: contain, transition (250ms in/out), 80vh max-height. This is the strongest single component in the codebase.
  • Verdict: Reuse as the foundation for every overlay. Currently BlockedByModal, EventDetailModal, SharedLinkModal, ConfirmModal all do — good.

F-MODAL-02 — Modal backdrop has role="button" [LOW]

  • Files: frontend/src/lib/components/Modal.svelte line 96
  • State: The backdrop is a <div> with role="button", tabindex="-1", and an onclick to close. That's a common pattern to silence Svelte's a11y warnings, but a screen reader announces "Close, button" twice (once for backdrop, once for the explicit X button).
  • Suggestion: Drop role="button" and aria-label from the backdrop; the explicit Close button is enough. Or use <button class="modal-backdrop"> instead of a div.

F-MODAL-03 — Modal panel uses solid #131520 instead of glass [LOW]

  • Files: frontend/src/lib/components/Modal.svelte lines 150151
  • State: --modal-solid-bg: #131520; is a deliberate choice (probably for readability) but it breaks visual consistency with the rest of the app. The Aurora drift behind it is invisible.
  • Suggestion: Use var(--color-glass-elev) over the blurred backdrop. Or, if the solid choice was deliberate, document why so the next developer doesn't "fix" it.

F-MODAL-04 — Confirm-modal "delete" hover uses raw rgba [MEDIUM]


7. Empty / loading / error states

F-STATE-01 — Loading.svelte is a single shimmer pattern [MEDIUM]

  • Files: frontend/src/lib/components/Loading.svelte
  • State: Three or four 4rem shimmer bars. Used as <Loading /> on virtually every page including hero pages. Doesn't match the actual layout the user will see — looks like a row list even on settings.
  • Suggestion: Add layout-aware variants: <Loading shape="hero" />, <Loading shape="grid" cols={4} />, <Loading shape="list" rows={5} />. Reduces layout shift on first paint.

F-STATE-02 — EmptyState.svelte is plain and undifferentiated [MEDIUM]

  • Files: frontend/src/lib/components/EmptyState.svelte
  • State: 10-line component: dimmed icon + message. No CTA, no illustration, no flavor. The dashboard's inline .empty-state (lines 13001319 of +page.svelte) is richer (has a CTA link) but isn't reused.
  • Suggestion: Extend EmptyState to accept a cta slot and a tone (with subtle gradient blob behind the icon). On /providers empty: "No providers yet — connect Immich, Nextcloud, or Home Assistant to start tracking events" with an "+ Add provider" CTA.

F-STATE-03 — Many list pages have no error-recovery action [MEDIUM]

  • Files: Throughout — most pages have a loadError state that renders <Card><ErrorBanner /></Card> but no "Retry" button.
  • Suggestion: ErrorBanner should accept an onRetry prop and surface a retry button. Standardize across pages.

F-STATE-04 — EventChart no empty state [LOW]

  • See F-HIER-03.

8. Accessibility

F-A11Y-01 — Snackbar has no aria-live [HIGH]

  • Files: frontend/src/lib/components/Snackbar.svelte lines 3563
  • State: Snack container is a plain <div use:portal>. Success / error toasts never reach screen readers. Three other files have proper aria-live; this critical one doesn't.
  • Fix: <div use:portal class="snackbar-container" role="region" aria-live="polite" aria-label={t('snackbar.region')}>. Use aria-live="assertive" for snack.type === 'error'.
  • See F-NAV-01.

F-A11Y-03 — Custom focus outlines partially overridden [MEDIUM]

  • Files: frontend/src/app.css lines 237241 (global button:focus-visible outline 2px primary + offset 2px), frontend/src/routes/+layout.svelte line 894 (.nav-link { border-radius: 12px !important }), frontend/src/routes/+page.svelte lines 13511354 (.signal-row--clickable:focus-visible { outline-offset: -2px }).
  • State: Inverted offset -2px makes the focus ring sit inside the row, which against the glass-strong hover-bg ends up nearly invisible at certain accent positions.
  • Suggestion: Use outline-offset: 2px consistently with a box-shadow: 0 0 0 2px var(--color-glass) ringer if needed for contrast.

F-A11Y-04 — prefers-reduced-motion is honored — commendation [LOW]

  • Files: frontend/src/app.css lines 484507, frontend/src/routes/+layout.svelte lines 837840
  • State: Aurora drift, brand-version pulse, stagger entrances, signal-row hover transitions, paginator transitions all gated. Smooth scroll override too. Solid implementation.

F-A11Y-05 — Color contrast risk on glass surfaces [MEDIUM]

  • State: --color-muted-foreground: #b6b2d4 on --color-glass: rgba(255,255,255,0.04) over the aurora gradient. In the brightest hot-spot of the aurora background (where the #b8a7ff lavender peaks), #b6b2d4 may fail WCAG AA (4.5:1 for body text). Hasn't been measured.
  • Suggestion: Run a contrast pass with --color-muted-foreground against the brightest part of the aurora background. Likely need to bump it to ~#cfcae8 for dark theme.

F-A11Y-06 — Toggle switch has no label association [LOW]

  • Files: frontend/src/app.css lines 513556
  • State: .toggle-switch wraps an <input type="checkbox"> and a visual .toggle-track <span>. There's no visible label text or aria-label requirement in the global utility. Callers may forget to pass one.
  • Suggestion: Lift into a <Toggle> component requiring a label prop.

9. Responsive design

F-RESP-01 — Sidebar collapse breakpoint is fine; mobile bottom nav covers gracefully [LOW / commendation]

  • Files: frontend/src/routes/+layout.svelte lines 589668, 11361168
  • State: Below 767px the desktop sidebar hides and mobile bottom-nav appears with primary 4 keys + search + more. Mobile "More" panel mirrors the full desktop tree. Solid.

F-RESP-02 — Hero meter wraps awkwardly between 720880px [LOW]

  • Files: frontend/src/routes/+page.svelte lines 11191130
  • State: Below 880px the hero collapses to one column, but the meter row pills wrap to a third row on Russian translations of "providers/targets/armed".
  • Suggestion: Add an intermediate breakpoint (max-width: 1024px) where pill labels switch from "5 providers" to a tooltip-only count.

F-RESP-03 — Stat-card grid drops to 1 column at sm: [MEDIUM]

  • Files: frontend/src/routes/+page.svelte line 590 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4
  • State: Between 6401024px stat cards are 2-wide. At tablet sizes the cards become huge and dilute the dashboard density.
  • Suggestion: Cap stat-card max-width at ~300px or switch to auto-fit, minmax(200px, 1fr) so they don't grow uncontrollably.

F-RESP-04 — List rows don't gracefully truncate webhook URLs on mobile [LOW]

  • Files: frontend/src/routes/providers/+page.svelte lines 392410
  • State: Secondary text line shows full webhook URL with break-all which on very narrow viewports gives a 4-line wrap.
  • Suggestion: Use the shortenUrl() helper (already defined for the meta-tile path) on the narrow-screen secondary line too.

10. Onboarding

F-ONBOARD-01 — Setup → empty dashboard with no guidance [HIGH]

  • Files: frontend/src/routes/setup/+page.svelte, frontend/src/routes/+page.svelte
  • State: After /setup the user lands on / with 0 providers, hero says "all clear" (literally "Nothing to do"). Wasted first impression.
  • Suggestion: First-run detection (providersCache.items.length === 0 && targetsCache.items.length === 0) replaces the dashboard hero with a 3-4 step "Getting started" checklist: (1) Add a provider · (2) Connect a bot · (3) Create a target · (4) Wire your first tracker. Each step is a CTA card. Persist completion to localStorage so it disappears once finished.

F-ONBOARD-02 — No in-app discovery of ⌘K palette [MEDIUM]

  • Files: frontend/src/routes/+layout.svelte lines 678682
  • State: Topbar shows ⌘K / Ctrl K chip but only that. No "Press ⌘K to jump to any page" hint anywhere.
  • Suggestion: First-visit toast: "Tip: Press ⌘K from anywhere to search providers, trackers, and pages". Dismissible.
  • Files: frontend/src/routes/login/+page.svelte
  • State: Plain username + password. For self-hosted users who lost the admin password, there's no link to the recovery docs.
  • Suggestion: Small "Need help?" link to docs (the /docs route exists).

11. Microcopy

F-COPY-01 — Dashboard hero copy is editorial — commendation [LOW]

  • "Live · throughput 24h · armed · providers" reads more like a control-room dashboard than CRUD admin. Keep doing this on the rest of the app.

F-COPY-02 — Many subpages use literal entity-name copy [MEDIUM]

  • E.g. "Add provider" / "Add target" / "Add tracker" / "Add user". Editorial would be "Connect a provider" / "Define a target" / "Wire a tracker" / "Invite a user". Lean into verbs that match the dashboard's "wires / signals / on watch" vocabulary.

F-COPY-03 — Russian translations match en line-count but no length QA visible [LOW]

  • File sizes match exactly (1577 lines each). That's just structural parity, not visual parity. Russian tends to be 20-30% longer for the same concept; flagged places likely have layout issues (hero title em, stat-card values, sidebar nav labels).
  • Suggestion: Set up a Playwright snapshot test that switches locale=ru and screenshots dashboard + a representative list page to catch overflow visually.

12. Localization parity

F-LOCALE-01 — "Notify Bridge" wordmark stays in English [LOW / correct]

  • Brand. Don't translate.

F-LOCALE-02 — Provider type label not localized in list rows [LOW]

  • Files: frontend/src/routes/providers/+page.svelte line 391
  • State: Type pill shows raw provider.type value (e.g. "immich", "nextcloud") — not localized.
  • Suggestion: Use getDescriptor(type).defaultName or t(\providers.type${PascalName}`)` which exists per project conventions.

F-LOCALE-03 — Mixed Cyrillic glitches in source [LOW]

  • Files: frontend/src/routes/login/+page.svelte line 42 (— instead of em-dash in a comment), frontend/src/routes/users/+page.svelte line 166 (В· instead of ·)
  • State: Encoding-corrupt characters in source comments and one user-facing dot. Pre-existing — files were probably edited with the wrong encoding at some point.
  • Suggestion: Grep †/ В· across the repo and fix. Add a pre-commit hook that fails on non-UTF8 chars in .svelte / .ts / .json.

13. Power-user features

F-POWER-01 — No sortable columns anywhere [MEDIUM]

  • Confirmed by Grep: no aria-sort / sortable / onSort in the codebase. Lists are sorted by IconGridSelect widget (newest / oldest / name).
  • Suggestion: For long lists (trackers, targets), add column-header sort affordance. Even minimal: clicking the "Name" or "Provider" header re-sorts. Use cache state so sort persists across nav.

F-POWER-02 — No multi-select bulk actions [MEDIUM]

  • Grep for bulkAction / selectAll: only the locale files contain those strings (likely as i18n keys that are never used). No checkbox UI.
  • Suggestion: Add a checkbox column on targets, notification-trackers, command-trackers, actions pages. Bulk-enable / bulk-delete are the obvious ones.

F-POWER-03 — ⌘K palette is the strongest power feature, under-promoted [MEDIUM]

  • See F-ONBOARD-02.

F-POWER-04 — Sidebar group expand/collapse is persisted but no "expand all / collapse all" [LOW]

  • Files: frontend/src/routes/+layout.svelte lines 263269
  • Suggestion: Add a right-click menu on a group header, or a tiny "collapse all" icon at the bottom of the nav rail.

F-POWER-05 — No keyboard shortcuts beyond ⌘K [LOW]

  • Suggestion: n for new, g + p for "go providers", g + t for trackers, ? to show shortcut sheet. Document in the palette.

Production polish checklist (top 15, prioritized)

  1. [HIGH] Add role="status" aria-live="polite" to Snackbar container; assertive for error toasts. (F-A11Y-01) — one-line fix.
  2. [HIGH] Add aria-current="page" to every nav link in +layout.svelte. (F-NAV-01, F-A11Y-02)
  3. [HIGH] Mass-replace the legacy form-input class (border border-[var(--color-border)] rounded-md text-sm bg-[var(--color-background)]) with nothing — let the global input { ... } style win. 17 files, ~71 occurrences. (F-CONSIST-04)
  4. [HIGH] Replace hardcoded hex colors (#059669, #ef4444, #3b82f6, #f59e0b, rgba(239,68,68,...)) with Aurora palette tokens in Snackbar.svelte, ConfirmModal.svelte, actions/+page.svelte, and any remaining sites. (F-CONSIST-03)
  5. [HIGH] First-run onboarding: when providersCache.items.length === 0, replace dashboard hero with a 4-step "Getting started" checklist. (F-ONBOARD-01)
  6. [HIGH] Consolidate the 5 glass-card abstractions into a single <GlassPanel variant=...> component; delete redundant ::after overlays. (F-CONSIST-01)
  7. [HIGH] Introduce a radius scale (--radius-card / panel / pill / input / chip / tile) and refactor Card.svelte, Button.svelte, Modal.svelte, ConfirmModal.svelte to use it. (F-CONSIST-02)
  8. [MEDIUM] Rewrite ConfirmModal.svelte to use <Button variant="secondary"> and <Button variant="danger"> instead of its own buttons. (F-CONSIST-05)
  9. [MEDIUM] Add layout-aware <Loading shape="hero|grid|list"> variants to reduce first-paint layout shift. (F-STATE-01)
  10. [MEDIUM] Extend <EmptyState> with cta slot and provider-/tracker-/target-specific copy + a contextual CTA. (F-STATE-02)
  11. [MEDIUM] Visual length-QA pass for Russian — at least dashboard hero, providers list, settings hero, stat-cards. Playwright screenshot test. (F-COPY-03, F-LOCALE-02)
  12. [MEDIUM] Implement column-header sort on notification-trackers, targets, actions. Persist in cache state. (F-POWER-01)
  13. [MEDIUM] Add multi-select bulk actions (enable/disable, delete) to targets, notification-trackers, command-trackers. (F-POWER-02)
  14. [MEDIUM] Audit contrast: --color-muted-foreground over brightest aurora peak; likely bump dark-theme value from #b6b2d4 to ~#cfcae8. (F-A11Y-05)
  15. [MEDIUM] Replace inline browser-native title= tooltips on the collapsed sidebar with a custom Aurora-styled tooltip (using the existing portal helper). (F-NAV-04)

Quick wins (bonus, under an hour each)

  • Add autofocus to the username input on /login. (F-FORM-05)
  • Fix вЂ" / В· Cyrillic encoding glitches in login/+page.svelte and users/+page.svelte. (F-LOCALE-03)
  • Drop role="button" from Modal backdrop. (F-MODAL-02)
  • Replace provider.type raw label in provider list rows with localized descriptor name. (F-LOCALE-02)
  • Add inline empty-state copy to EventChart when all chartDays values are 0. (F-HIER-03)

What's working — keep doing it

  • The conic-gradient brand orb, animated aurora background, Newsreader italic emphasis, gradient pill chips, glow-pulse dots — distinctive identity.
  • Modal.svelte (focus trap, restore, portal, escape, scroll containment).
  • prefers-reduced-motion honored across every animation surface.
  • Global ⌘K search palette, global provider filter, persisted sidebar state, persisted nav-group expansion.
  • Editorial copy on dashboard (signal stream, on watch, pulse, wires, compose).
  • Snackbar with detail-toggle expansion for error context.
  • Mobile "More" panel that mirrors the full desktop nav tree.
  • 6-file template-variable sync rule honored by project conventions.
  • i18n parity at 1577 lines for both locales.

End of review.