docs(04): capture phase context

This commit is contained in:
2026-03-24 09:28:51 +01:00
parent 46614574c4
commit 9810b4dc1b
2 changed files with 299 additions and 0 deletions

View File

@@ -0,0 +1,118 @@
# Phase 4: UX Improvements - Context
**Gathered:** 2026-03-24
**Status:** Ready for planning
<domain>
## 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.
</domain>
<decisions>
## 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)
</decisions>
<canonical_refs>
## 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
</canonical_refs>
<code_context>
## 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
</code_context>
<specifics>
## Specific Ideas
No specific requirements -- open to standard approaches. The existing shadcn/ui + Tailwind dark mode setup provides the foundation for theme toggling.
</specifics>
<deferred>
## Deferred Ideas
None -- discussion stayed within phase scope.
</deferred>
---
*Phase: 04-ux-improvements*
*Context gathered: 2026-03-24 via auto mode*

View File

@@ -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.