feat(audit): Batch 9 — surface primitives (empty-state, banner, truncation)
Shared UI primitives to end the "two ways to do X" drift on surfaces: - EmptyState (ui/EmptyState.tsx): one centered empty block — optional focal icon/badge + Body1 message + optional muted hint, with a `compact` variant for in-content placeholders. Adopted by Downloads, History, Library (L116), and — compact — the Terminal log, Diagnostics, and TemplateManager (L117). - Banner (ui/Banner.tsx) + LinkSuggestion (ui/LinkSuggestion.tsx): one tinted notification row (icon + truncating/wrap content + actions, tone brand/warning) replacing five hand-rolled style blocks — the DownloadBar copied-link suggestion / channel nudge / duplicate warning and the Library copied-link suggestion (UI19). - useTextStyles().truncate: shared single-line-ellipsis utility, adopted in Library rows + the playlist panel + Banner; `title` now also sets minWidth:0 so it shrinks in a flex row (L126). - L102: Settings "Default format" now uses the same SegmentedControl (Video/ Audio) + quality Select as the DownloadBar — one mental model; the combined `kind|quality` string encoding is gone. Resolved by verified convention (documented in code / CODE-AUDIT.md): - L103 busy = in-button icon→Spinner everywhere (done in L134; no adjacent- spinner pattern survives). - L115 list-row actions icon-only+tooltip vs card-toolbar actions labelled. - L120 Fluent <Card> for static content cards; tokenized surface recipe for interactive/list-item cards. - UI20 toggles = solid brand, list-selection = brand-tint (rule noted on SegmentedControl); the cited Library pill is gone via UI18. L128 (build-time DCE of preview seeds) deferred to Batch 14 with PERF8. Verified: typecheck (node+web) + 268 tests + eslint + production build green; touched files prettier-clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+77
-11
@@ -136,6 +136,22 @@ unchecked** — button size-by-role is subjective; current usage already broadly
|
||||
large, row/toolbar small, settings default), so specific tweaks join the watched visual pass. All green:
|
||||
typecheck (node+web) + 268 tests + eslint + production build; touched files prettier-clean.
|
||||
|
||||
**Session 2026-07-01 pass 6 (Batch 9 — surface primitives):** three new shared UI primitives —
|
||||
[EmptyState](src/renderer/src/components/ui/EmptyState.tsx) (**L116/L117**: one centered empty block — icon +
|
||||
`Body1` message + optional muted hint, with a `compact` variant for in-content placeholders — adopted by
|
||||
Downloads/History/Library and, compact, by the Terminal log + Diagnostics), [Banner](src/renderer/src/components/ui/Banner.tsx)
|
||||
+ [LinkSuggestion](src/renderer/src/components/ui/LinkSuggestion.tsx) (**UI19**: one tinted notification row
|
||||
replacing five hand-rolled style blocks — the DownloadBar suggestion/channel-nudge/dup-warning + the Library
|
||||
copied-link suggestion), and a shared `useTextStyles().truncate` (**L126**: single-line-ellipsis util, adopted
|
||||
in Library rows + the playlist panel + Banner). Plus **L102** (Settings "Default format" now uses the same
|
||||
`SegmentedControl` + quality `Select` as the DownloadBar — one mental model; the `kind|quality` string encoding
|
||||
is gone), and four resolved-by-verified-convention: **L103** (busy = in-button icon→Spinner everywhere; done in
|
||||
L134), **L115** (list-row actions icon-only+tooltip vs card-toolbar actions labelled — role-appropriate),
|
||||
**L120** (Fluent `<Card>` for static content cards, the Batch-6 tokenized surface recipe for interactive/
|
||||
list-item cards), and **UI20** (toggles = solid brand, list-selection = brand-tint; the cited Library pill is
|
||||
gone via UI18). **L128 deferred to Batch 14** (build-time DCE of preview seeds, grouped with PERF8). All green:
|
||||
typecheck (node+web) + 268 tests + eslint + production build; touched files prettier-clean.
|
||||
|
||||
**Deliberately deferred** (need live-app/visual verification or are larger refactors the
|
||||
audit itself defers to 1.x): the wire-or-cut features (M5/M6/UX1), the god-file/store
|
||||
refactors (C1, C2, H1), the SIMP* helpers + shared-UI-token work, the CC* consolidations,
|
||||
@@ -800,10 +816,18 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
- [x] **L101 — No z-index scale.** CommandPalette and Hint both hardcode `zIndex: 1000`.
|
||||
*Fixed: `Z` = { overlay: 1000, tooltip: 1100 }. CommandPalette's backdrop is `Z.overlay`, Hint's bubble is
|
||||
`Z.tooltip` (above overlays so a tooltip is never clipped by one).*
|
||||
- [ ] **L102 — Two mental models for "format."** Settings sets it via one combined Video/Audio dropdown
|
||||
- [x] **L102 — Two mental models for "format."** Settings sets it via one combined Video/Audio dropdown
|
||||
(`FORMAT_OPTIONS`); the DownloadBar uses a Video/Audio segmented control + a separate quality dropdown.
|
||||
- [ ] **L103 — Two busy-indicator patterns.** Fetch/Index swap the button icon to a `Spinner`; the
|
||||
*Fixed: the Settings "Default format" control ([DownloadsCard.tsx](src/renderer/src/components/settings/DownloadsCard.tsx))
|
||||
now uses the same shared `SegmentedControl` (Video/Audio) + quality `Select` as the DownloadBar, so both
|
||||
screens present one mental model. The combined `FORMAT_OPTIONS`/`kind|quality` string encoding is gone —
|
||||
the toggle writes `defaultKind` and the dropdown writes `defaultVideoQuality`/`defaultAudioQuality` directly.*
|
||||
- [x] **L103 — Two busy-indicator patterns.** Fetch/Index swap the button icon to a `Spinner`; the
|
||||
Settings check/update buttons keep their icon and render a *separate* adjacent `Spinner`.
|
||||
*Resolved by L134 (Batch 8), verified this batch: every busy button swaps its own icon to a
|
||||
`<Spinner size="tiny">` (Fetch/Index/Check-for-new/yt-dlp update/app-update/PO-mint/version-check); the only
|
||||
remaining `<Spinner>`s are standalone status-row indicators (the "Fetching…"/"Waiting to start…"/indexing
|
||||
rows), not a button + adjacent spinner. No separate-adjacent-spinner pattern survives.*
|
||||
- [ ] **L104 — History isn't virtualized.** It renders all filtered rows, while Downloads and Library use `VirtualList`.
|
||||
- [x] **L105 — `MediaKind` declared twice.** In both [ipc.ts](src/shared/ipc.ts) and
|
||||
[store/downloads.ts](src/renderer/src/store/downloads.ts); components import it from both. Single source it.
|
||||
@@ -841,15 +865,37 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
`srcTitle`) became `<Text>` — size-identical since Fluent's provider base font is already `fontSizeBase300`.
|
||||
The duplicated semibold-ellipsis title style (QueueItem/HistoryView/DownloadBar `previewTitle`) is now the
|
||||
shared `useTextStyles().title`.*
|
||||
- [ ] **L115 — Equivalent row actions labeled inconsistently** — "Re-index" is icon+text; the
|
||||
comparable "Retry"/"Remove"/"Re-download" are icon-only.
|
||||
- [ ] **L116 — Empty-state structure varies** — Downloads (icon + line), History (colored badge + line + sub-hint), Library (icon + line, no sub-hint).
|
||||
- [ ] **L117 — Terminal/error-log "empty" aren't centered blocks** like the other empty states (inline text instead).
|
||||
- [x] **L115 — Equivalent row actions labeled inconsistently** — "Re-index" is icon+text; the
|
||||
comparable "Retry"/"Remove"/"Re-download" are icon-only. *Resolved (convention verified + role-appropriate):
|
||||
the two live in different UI contexts, and each context is internally consistent. **List-row item actions**
|
||||
(QueueItem's Pause/Cancel/Retry/Remove/Open, History's Re-download/Open/Show/Remove) are icon-only with a
|
||||
`Hint` tooltip + a ≥40px hit target — a compact, repeated-per-row affordance. **Card/toolbar actions** (the
|
||||
Library source-detail row: Refresh/Remove/Download, the Downloads/History headers) are labeled icon+text —
|
||||
a one-per-card toolbar where the label aids scanning. The former "Re-index" is the Library card toolbar's
|
||||
"Refresh", correctly labeled alongside its siblings. No screen mixes the two within one context.*
|
||||
- [x] **L116 — Empty-state structure varies** — Downloads (icon + line), History (colored badge + line + sub-hint), Library (icon + line, no sub-hint).
|
||||
*Fixed: one shared [EmptyState](src/renderer/src/components/ui/EmptyState.tsx) — a centered column with an
|
||||
optional focal `icon` (a hero glyph or History's colored badge), a `Body1` message at normal foreground, and
|
||||
an optional muted `hint`. Adopted by Downloads, History, and Library, so the three now share one structure
|
||||
(History keeps its badge + hint; Downloads/Library pass a hero glyph). The per-view `empty`/`emptyHint`
|
||||
styles are deleted.*
|
||||
- [x] **L117 — Terminal/error-log "empty" aren't centered blocks** like the other empty states (inline text instead).
|
||||
*Fixed: both now render the shared `EmptyState` in its `compact` variant (smaller inset, for a placeholder
|
||||
inside an already-populated screen). The Terminal log centers "No output yet." in the log box (a `logEmpty`
|
||||
flex modifier); Diagnostics centers "No errors yet." — so both read as empty states rather than a stray line
|
||||
of inline text.*
|
||||
- [ ] **L118 — Cross-component focus via hardcoded id.** App focuses the URL field with
|
||||
`document.getElementById('aerofetch-url')` — brittle coupling into DownloadBar's markup.
|
||||
- [x] **L119 — "No items" copy varies** — "No … yet" (most) vs "Output will appear here." (Terminal) vs "No errors logged." (Diagnostics).
|
||||
- [ ] **L120 — Fluent `Card` vs hand-styled `div` cards.** Only Settings/Onboarding use `<Card>`; every
|
||||
other card surface is a bespoke styled `<div>`.
|
||||
- [x] **L120 — Fluent `Card` vs hand-styled `div` cards.** Only Settings/Onboarding use `<Card>`; every
|
||||
other card surface is a bespoke styled `<div>`. *Resolved (convention documented + tokens applied):
|
||||
static content cards (Settings cards, Onboarding) use Fluent `<Card>`; interactive & list-item surfaces
|
||||
(the download bar, QueueItem, History/Library rows, the Library source card, the command palette) use the
|
||||
Batch-6 tokenized surface recipe — `RADIUS.card`/`surface`, `ELEVATION`, `SPACE` padding on a styled `<div>`
|
||||
— because Fluent's `Card` imposes its own internal padding/gap/interaction model that a custom row layout
|
||||
fights. Forcing `<Card>` onto those would regress layout for no gain; the divergence L120 flags is now a
|
||||
documented tier split, and the styled surfaces share the same radius/elevation tokens. (The larger "wrap
|
||||
every recurring pattern" primitive work stays UI15's incremental track.)*
|
||||
- [x] **L121 — Raw scrim literal.** CommandPalette backdrop is `rgba(0,0,0,0.32)` (the only raw rgba in
|
||||
the renderer), identical in light/dark — low contrast over a dark UI. *Fixed: `SCRIM` in tokens.ts aliases
|
||||
Fluent's semantic `colorBackgroundOverlay` (the dialog scrim); the raw rgba is gone and the palette
|
||||
@@ -867,10 +913,18 @@ token system + shared primitives (UI/SIMP — high value, larger effort) · i18n
|
||||
`` fontSize: `${ICON.control}px` `` on the few makeStyles containers) — no more divergent magic numbers.
|
||||
Glyphs sized to a box rather than to this scale (brand/source tiles, the tiny `watchBadge` icon, thumbnail
|
||||
placeholders) are documented exclusions and stay proportional.*
|
||||
- [ ] **L126 — Uneven text truncation** — some titles/metas use `nowrap`+ellipsis, others wrap; no shared truncation utility.
|
||||
- [x] **L126 — Uneven text truncation** — some titles/metas use `nowrap`+ellipsis, others wrap; no shared truncation utility.
|
||||
*Fixed: a shared `useTextStyles().truncate` ([text.ts](src/renderer/src/components/ui/text.ts)) — single-line
|
||||
clip with ellipsis + `minWidth:0` — sits alongside the existing `title` (which is `truncate` + semibold).
|
||||
Adopted for the ad-hoc copies: Library `rowTitle`, the DownloadBar playlist `plItemTitle`/`plHeaderText`
|
||||
(the header is now `title`), and the shared `Banner` content (UI19). `title` also gained `minWidth:0` so it
|
||||
shrinks correctly in a flex row. (Remaining correct-but-inline truncations migrate to the util incrementally.)*
|
||||
- [x] **L127 — `Notification` sets no icon** — completion toasts used the default Electron icon though `getAppIconPath()` existed. *Fixed with W13 (cached `getAppIconImage()` passed as `icon` at both notification sites).*
|
||||
- [ ] **L128 — Preview seed/mock data ships in production.** The `PREVIEW` runtime check can't be
|
||||
tree-shaken, so each store's seed arrays + fake ticker are bundled into the Electron renderer.
|
||||
*Deferred to Batch 14 (perf/bundle): this is a build-time dead-code-elimination concern (gate the seed
|
||||
imports behind `import.meta.env` so Rollup drops them from the packaged renderer), grouped with PERF8's
|
||||
code-splitting where the lazy-loading is set up. Left open on purpose.*
|
||||
- [x] **L129 — `Hint align` is silently ignored for left/right placements** yet callers still pass it.
|
||||
*Fixed: `HintProps` is now a discriminated union — `align` is only accepted with `placement` `top`/`bottom`;
|
||||
passing it with `left`/`right` is a compile error instead of a silent no-op. No caller needed changing (the
|
||||
@@ -1147,14 +1201,26 @@ are an undefined spacing/radius/type scale and a drift between Fluent components
|
||||
non-primary button).*
|
||||
- [x] **UI18 — Two status-chip systems.** QueueItem uses Fluent `Badge`; LibraryView uses custom
|
||||
color `pill` spans for the same item-status concept (and labels differ — see M8). **Standard:** one shared status-chip component + label map.
|
||||
- [ ] **UI19 — Suggestion-banner CSS duplicated.** Identical `suggestion`/`suggestionText` styles in
|
||||
- [x] **UI19 — Suggestion-banner CSS duplicated.** Identical `suggestion`/`suggestionText` styles in
|
||||
DownloadBar and LibraryView. **Standard:** a shared `LinkSuggestion` component (also kills drift).
|
||||
*Fixed: a shared [Banner](src/renderer/src/components/ui/Banner.tsx) (leading icon + truncating/`wrap`
|
||||
content + trailing actions, `tone` = brand/warning) and a [LinkSuggestion](src/renderer/src/components/ui/LinkSuggestion.tsx)
|
||||
built on it. The DownloadBar's copied-link suggestion and the Library's are now one `<LinkSuggestion>`; the
|
||||
DownloadBar's channel/playlist nudge (`wrap`) and duplicate warning (`tone="warning"`) reuse the base
|
||||
`Banner`. Five hand-rolled style blocks (`suggestion`/`suggestionText`/`channelHint`/`channelHintText`/
|
||||
`dupRow`) are deleted — the tint/padding/radius are identical, so no visual change.*
|
||||
|
||||
### Colors & selection
|
||||
|
||||
- [ ] **UI20 — "Selected/active" styling is split.** Solid brand (`colorBrandBackground` +
|
||||
- [x] **UI20 — "Selected/active" styling is split.** Solid brand (`colorBrandBackground` +
|
||||
on-brand text) for DownloadBar/Sidebar segments, but brand-tint (`colorBrandBackground2` +
|
||||
`colorBrandForeground2`) for Sidebar nav, CommandPalette item, Library pill. **Standard:** one active treatment per control class.
|
||||
*Resolved (rule named + verified): one active treatment **per control class** — **toggles** (the shared
|
||||
`SegmentedControl`, used by both the DownloadBar kind toggle and the Sidebar theme switch after UI14) use
|
||||
**solid brand**; **selection within a list** (Sidebar nav item, CommandPalette active option) uses
|
||||
**brand-tint**. Each class is now internally consistent, and the rule is documented on
|
||||
[SegmentedControl](src/renderer/src/components/ui/SegmentedControl.tsx). The "Library pill" the item cited is
|
||||
gone — UI18 replaced it with the shared `StatusChip`, so there's no third treatment left to reconcile.*
|
||||
- [ ] **UI21 — Sidebar active-nav rail is expanded-only.** The inset brand box-shadow shows only when
|
||||
expanded; collapsed relies on tint alone. **Standard:** a consistent active indicator in both states.
|
||||
- [ ] **UI22 — Brand-icon tiles differ.** `mark` (on-brand on solid brand) vs `srcIcon`
|
||||
|
||||
Reference in New Issue
Block a user