From 9810b4dc1b8ab37e2b032c6a06e955cab46eb247 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Tue, 24 Mar 2026 09:28:51 +0100 Subject: [PATCH] docs(04): capture phase context --- .../phases/04-ux-improvements/04-CONTEXT.md | 118 ++++++++++++ .../04-ux-improvements/04-DISCUSSION-LOG.md | 181 ++++++++++++++++++ 2 files changed, 299 insertions(+) create mode 100644 .planning/phases/04-ux-improvements/04-CONTEXT.md create mode 100644 .planning/phases/04-ux-improvements/04-DISCUSSION-LOG.md diff --git a/.planning/phases/04-ux-improvements/04-CONTEXT.md b/.planning/phases/04-ux-improvements/04-CONTEXT.md new file mode 100644 index 0000000..35bee78 --- /dev/null +++ b/.planning/phases/04-ux-improvements/04-CONTEXT.md @@ -0,0 +1,118 @@ +# Phase 4: UX Improvements - Context + +**Gathered:** 2026-03-24 +**Status:** Ready for planning + + +## Phase Boundary + +Deliver UX features that make the dashboard genuinely usable at scale: bulk dismiss (all + per-group), search and filter across updates, new-update indicators (badge, tab title, toast, highlight), and accessibility fixes (theme toggle, always-visible drag handle). No new database tables — bulk dismiss adds Store methods; search/filter is client-side; indicators use localStorage. + + + + +## Implementation Decisions + +### Bulk dismiss (BULK-01, BULK-02) +- **D-01:** Add two new Store methods: `AcknowledgeAll() (count int, err error)` and `AcknowledgeByTag(tagID int) (count int, err error)` — consistent with existing `AcknowledgeUpdate(image)` pattern +- **D-02:** Two new API endpoints: `POST /api/updates/acknowledge-all` and `POST /api/updates/acknowledge-by-tag` (with `tag_id` in body) — returning the count of dismissed items +- **D-03:** UI placement: "Dismiss All" button in the header/stats area; "Dismiss Group" button in each TagSection header next to the existing delete button +- **D-04:** Confirmation: modal/dialog confirmation for dismiss-all (high-impact action); inline confirm pattern (matching existing tag delete) for per-group dismiss + +### Search and filter (SRCH-01 through SRCH-04) +- **D-05:** Client-side filtering only — all data is already in memory from polling, no new API endpoints needed +- **D-06:** Filter bar placed above the sections list, below the stats row +- **D-07:** Controls: text search input (filters by image name), status dropdown (all/pending/acknowledged), tag dropdown (all/specific tag/untagged), sort dropdown (date/name/registry) +- **D-08:** Filters do not persist across page reloads — reset on each visit (dashboard is a quick-glance tool) + +### New-update indicators (INDIC-01 through INDIC-04) +- **D-09:** Pending update badge/counter displayed in the Header component next to the "Diun Dashboard" title — always visible +- **D-10:** Browser tab title reflects pending count: `"DiunDash (N)"` when N > 0, `"DiunDash"` when zero +- **D-11:** Toast notification when new updates arrive during polling — auto-dismiss after 5 seconds with manual dismiss button; non-stacking (latest update replaces previous toast) +- **D-12:** "New since last visit" detection via localStorage timestamp — store `lastVisitTimestamp` on page unload; updates with `received_at` after that timestamp get a visual highlight +- **D-13:** Highlight style: subtle left border accent (e.g., `border-l-4 border-amber-500`) on ServiceCard for new-since-last-visit items + +### Accessibility and theme (A11Y-01, A11Y-02) +- **D-14:** Light/dark theme toggle placed in the Header bar next to the refresh button — icon button (sun/moon) +- **D-15:** Theme preference persisted in localStorage; on first visit, respects `prefers-color-scheme` media query; removes the hardcoded `classList.add('dark')` from `main.tsx` +- **D-16:** Drag handle on ServiceCard always visible at reduced opacity (`opacity-40`), full opacity on hover — removes the current `opacity-0 group-hover:opacity-100` pattern + +### Claude's Discretion +- Toast component implementation (custom or shadcn/ui Sonner) +- Exact filter bar layout and responsive breakpoints +- Animation/transition details for theme switching +- Whether to show a count in the per-group dismiss button (e.g., "Dismiss 3") +- Sort order default (most recent first vs alphabetical) + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Store interface and handler patterns +- `pkg/diunwebhook/store.go` -- Store interface (9 methods; new bulk methods extend this) +- `pkg/diunwebhook/sqlite_store.go` -- SQLiteStore implementation (pattern for new methods) +- `pkg/diunwebhook/postgres_store.go` -- PostgresStore implementation (must also get new methods) +- `pkg/diunwebhook/server.go` -- Server struct and handler registration (new endpoints go here) + +### Frontend components affected +- `frontend/src/App.tsx` -- Root component (filter state, bulk dismiss wiring, layout changes) +- `frontend/src/hooks/useUpdates.ts` -- Polling hook (toast detection, bulk dismiss callbacks, tab title) +- `frontend/src/components/Header.tsx` -- Header (badge counter, theme toggle, dismiss-all button) +- `frontend/src/components/TagSection.tsx` -- Tag sections (per-group dismiss button) +- `frontend/src/components/ServiceCard.tsx` -- Service cards (new-update highlight, drag handle fix) +- `frontend/src/main.tsx` -- Entry point (theme initialization logic change) + +### Requirements +- `.planning/REQUIREMENTS.md` -- BULK-01, BULK-02, SRCH-01-04, INDIC-01-04, A11Y-01, A11Y-02 + + + + +## Existing Code Insights + +### Reusable Assets +- `Button` component (`frontend/src/components/ui/button.tsx`): use for dismiss-all and per-group dismiss buttons +- `Badge` component (`frontend/src/components/ui/badge.tsx`): use for pending count badge in header +- `cn()` utility (`frontend/src/lib/utils.ts`): conditional class composition for highlight styles +- `timeAgo()` utility (`frontend/src/lib/time.ts`): already used in ServiceCard, relevant for toast messages +- `AcknowledgeButton` component: existing per-item dismiss pattern to follow for bulk buttons + +### Established Patterns +- `useUpdates` hook: centralized data fetching + state management -- extend with bulk dismiss, toast detection, and tab title side effects +- Optimistic updates: used for tag assignment -- apply same pattern for bulk dismiss (update UI immediately, fire API call) +- Polling at 5s intervals: toast detection can diff previous vs current poll results +- Dark mode via Tailwind `class` strategy: theme toggle adds/removes `dark` class on `document.documentElement` +- No global state library: filter state lives in `App.tsx` via `useState`, passed as props + +### Integration Points +- `cmd/diunwebhook/main.go`: register 2 new routes on the mux +- `store.go`: add `AcknowledgeAll` and `AcknowledgeByTag` to Store interface +- `sqlite_store.go` + `postgres_store.go`: implement new Store methods in both dialects +- `server.go`: add handler methods for bulk acknowledge endpoints +- `App.tsx`: add filter state, wire filter bar component, pass bulk dismiss callbacks +- `Header.tsx`: add pending count badge, theme toggle button, dismiss-all button +- `main.tsx`: replace hardcoded dark mode with localStorage + prefers-color-scheme logic + + + + +## Specific Ideas + +No specific requirements -- open to standard approaches. The existing shadcn/ui + Tailwind dark mode setup provides the foundation for theme toggling. + + + + +## Deferred Ideas + +None -- discussion stayed within phase scope. + + + +--- + +*Phase: 04-ux-improvements* +*Context gathered: 2026-03-24 via auto mode* diff --git a/.planning/phases/04-ux-improvements/04-DISCUSSION-LOG.md b/.planning/phases/04-ux-improvements/04-DISCUSSION-LOG.md new file mode 100644 index 0000000..d719e31 --- /dev/null +++ b/.planning/phases/04-ux-improvements/04-DISCUSSION-LOG.md @@ -0,0 +1,181 @@ +# Phase 4: UX Improvements - Discussion Log + +> **Audit trail only.** Do not use as input to planning, research, or execution agents. +> Decisions are captured in CONTEXT.md -- this log preserves the alternatives considered. + +**Date:** 2026-03-24 +**Phase:** 04-ux-improvements +**Areas discussed:** Bulk dismiss scope, Search/filter architecture, New-update detection, Theme toggle behavior +**Mode:** auto (all decisions auto-selected) + +--- + +## Bulk Dismiss Scope + +| Option | Description | Selected | +|--------|-------------|----------| +| New Store methods + dedicated endpoints | Add AcknowledgeAll and AcknowledgeByTag to Store interface with new HTTP endpoints | ✓ | +| Batch image list from frontend | Frontend sends list of image names to a generic bulk-dismiss endpoint | | +| Reuse existing single-dismiss in loop | Frontend calls existing PATCH /api/updates/{image} for each item | | + +**User's choice:** [auto] New Store methods + dedicated endpoints (recommended default) +**Notes:** Consistent with existing per-image dismiss pattern. Server-side bulk is more efficient and keeps frontend simple. + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Tag ID parameter for per-group dismiss | Server looks up which images belong to the tag | ✓ | +| Send list of images from frontend | Frontend determines which images are in the group | | + +**User's choice:** [auto] Tag ID parameter (recommended default) +**Notes:** Server already knows tag-image relationships, fewer bytes over the wire. + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Dismiss-all in header, dismiss-group in TagSection header | Natural placement near existing controls | ✓ | +| All bulk actions in a separate toolbar | Dedicated action bar for bulk operations | | + +**User's choice:** [auto] Dismiss-all in header area, dismiss-by-group in each TagSection header (recommended default) + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Confirmation dialog for all, inline for per-group | Dismiss-all gets modal; per-group matches existing tag-delete pattern | ✓ | +| No confirmation for either | Fast but risky | | +| Confirmation for both | Consistent but slower workflow | | + +**User's choice:** [auto] Yes, confirmation dialog for dismiss-all; inline confirm for per-group (recommended default) + +--- + +## Search/Filter Architecture + +| Option | Description | Selected | +|--------|-------------|----------| +| Client-side filtering | All data already in memory from polling; filter in React state | ✓ | +| Server-side with query params | Add filter params to GET /api/updates endpoint | | +| Hybrid (client with server fallback) | Client-side now, server-side when data grows | | + +**User's choice:** [auto] Client-side filtering (recommended default) +**Notes:** All data is fetched via 5s polling. No need for server-side filtering at this scale. + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Filter bar above sections, below stats | Standard placement, visible without scrolling | ✓ | +| Collapsible sidebar filters | More space but hidden by default | | +| Inline per-section filters | Distributed, harder to use across groups | | + +**User's choice:** [auto] Filter bar above the sections list (recommended default) + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Text search + status + tag + sort dropdowns | Covers all SRCH requirements | ✓ | +| Text search only | Minimal, doesn't cover SRCH-02/03/04 | | + +**User's choice:** [auto] Search text input + status dropdown + tag dropdown + sort dropdown (recommended default) + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| No persistence (reset on reload) | Simpler, dashboard is quick-glance tool | ✓ | +| Persist in URL params | Shareable/bookmarkable filters | | +| Persist in localStorage | Remembers across visits | | + +**User's choice:** [auto] No persistence -- reset on reload (recommended default) + +--- + +## New-Update Detection + +| Option | Description | Selected | +|--------|-------------|----------| +| localStorage timestamp | Store last visit time client-side, compare with received_at | ✓ | +| Server-side last-seen tracking | Track per-user last-seen on server | | +| Session-only (no persistence) | Only detect new items arriving during current session | | + +**User's choice:** [auto] localStorage timestamp (recommended default) +**Notes:** Single-user tool, no server changes needed. Simple and effective. + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Auto-dismiss after 5s with dismiss button | Non-intrusive, doesn't pile up | ✓ | +| Sticky until manually dismissed | Persistent but can pile up | | +| No toast, badge only | Minimal notification | | + +**User's choice:** [auto] Auto-dismiss after 5 seconds with dismiss button (recommended default) + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Header badge + tab title | Always visible, covers INDIC-01 and INDIC-02 | ✓ | +| Stats card only | Already partially exists | | + +**User's choice:** [auto] In the header next to title + browser tab title (recommended default) + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Subtle left border accent | Visible but not overwhelming | ✓ | +| Background color change | More prominent | | +| Pulsing dot indicator | Animated, attention-grabbing | | + +**User's choice:** [auto] Subtle left border accent on ServiceCard (recommended default) + +--- + +## Theme Toggle Behavior + +| Option | Description | Selected | +|--------|-------------|----------| +| Header bar, next to refresh button | Compact, always accessible | ✓ | +| Footer | Less prominent | | +| Settings page | Requires new page | | + +**User's choice:** [auto] Header bar, next to refresh button (recommended default) + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| localStorage with prefers-color-scheme fallback | Standard pattern, no server involvement | ✓ | +| Cookie-based | SSR-friendly but not needed here | | +| No persistence | Resets every visit | | + +**User's choice:** [auto] localStorage with prefers-color-scheme fallback (recommended default) + +--- + +| Option | Description | Selected | +|--------|-------------|----------| +| Always visible at reduced opacity | Accessible without cluttering UI | ✓ | +| Always fully visible | More prominent but noisier | | +| Keep hover-only | Current behavior, accessibility issue | | + +**User's choice:** [auto] Always visible at reduced opacity, full opacity on hover (recommended default) + +--- + +## Claude's Discretion + +- Toast component implementation (custom or shadcn/ui Sonner) +- Exact filter bar layout and responsive breakpoints +- Animation/transition details for theme switching +- Whether to show a count in the per-group dismiss button +- Sort order default + +## Deferred Ideas + +None -- discussion stayed within phase scope.