diff --git a/.claude/skills/council/SKILL.md b/.claude/skills/council/SKILL.md index e024da3..3146fca 100644 --- a/.claude/skills/council/SKILL.md +++ b/.claude/skills/council/SKILL.md @@ -35,15 +35,16 @@ the heuristic auto-trigger missed, but that's the same one-off sweep, not a stan Pull the **minimal** relevant member(s) for the decision at hand — do **not** sweep all four; that wastes tokens and dilutes the answer. Map the decision to its seat(s): -| Decision in front of you | Primary seat → then | -| ---------------------------------------------------------- | ------------------------------------------------------ | -| Error / failure / empty-state / notification **copy** | **GOV.UK** → Carbon notifications → NN/g heuristic #9 | -| Keyboard / focus / ARIA roles of an **interactive widget** | **WAI-ARIA APG** → Carbon component | -| **Latency / feedback / loading / progress** budgets | **NN/g** response-time limits → Carbon loading pattern | -| **Forms / validation / destructive-action** flow | **GOV.UK** → Carbon | -| General **usability** gut-check on a flow | **NN/g** 10 heuristics | -| **Visual** styling (type, spacing, colour, component look) | **Carbon** + our `docs/architecture/09` | -| **Which chart** for the data/intent (chart-type choice) | **FT Visual Vocabulary** + **Datawrapper** | +| Decision in front of you | Primary seat → then | +| ---------------------------------------------------------------- | ------------------------------------------------------ | +| Error / failure / empty-state / notification **copy** | **GOV.UK** → Carbon notifications → NN/g heuristic #9 | +| Keyboard / focus / ARIA roles of an **interactive widget** | **WAI-ARIA APG** → Carbon component | +| **Latency / feedback / loading / progress** budgets | **NN/g** response-time limits → Carbon loading pattern | +| **Forms / validation / destructive-action** flow | **GOV.UK** → Carbon | +| General **usability** gut-check on a flow | **NN/g** 10 heuristics | +| **Visual** styling (type, spacing, colour, component look) | **Carbon** + our `docs/architecture/09` | +| **Which chart** for the data/intent (chart-type choice) | **FT Visual Vocabulary** + **Datawrapper** | +| **PWA / offline / install / SW-update / storage-persistence** UX | **web.dev** (+ vite-plugin-pwa / Workbox for the API) | Then: read the cited file(s), extract the **specific** principle, and report it back with a **citation (member + file path)** and a one-line "how it lands in Astrolabe." Don't @@ -54,14 +55,15 @@ paraphrase the whole source — quote the rule that decides the question. All paths are under `/Users/oleh/code/reference/`. Treat clones as **inspiration, not law** — they drift; the published guidance is the truth, the clone is the fast index. -| Member | Path | Authoritative for | How to query | -| ------------------------------ | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **IBM Carbon** | `carbon-website/src/pages/` | Notification taxonomy, status levels, empty/loading states, content basics, data-viz styling | grep `.mdx` under `components/notification`, `patterns/{empty-states,loading,status-indicator}-pattern`, `guidelines/content` | -| **GOV.UK Design System** | `govuk-design-system/src/` | Error & validation messages, failure pages, forms, plain-language content, accessibility | `index.md` under `components/{error-message,error-summary,notification-banner}`, `patterns/{problem-with-the-service-pages,service-unavailable-pages,check-answers}`, `accessibility/` | -| **WAI-ARIA APG** | `aria-practices/content/patterns/` | Keyboard interaction, focus management, ARIA roles/states for widgets | `/-pattern.html` — e.g. `dialog-modal`, `alertdialog`, `alert`, `listbox`, `menu-button`, `disclosure`, `switch`, `tabs`, `tooltip`, `windowsplitter` | -| **Nielsen Norman (distilled)** | `principles/nielsen-norman.md` | 10 usability heuristics; response-time / feedback budgets (0.1s / 1s / 10s) | read directly — it is short and curated | -| **FT Visual Vocabulary** | `chart-doctor/visual-vocabulary/` | Chart choice: data-relationship taxonomy (Magnitude, Correlation, Change-over-Time, Ranking, Distribution, Deviation, Part-to-whole, Spatial, Flow) → chart type | read `README.md` — the taxonomy is prose; each category gives a "use when…" definition + recommended chart types | -| **Datawrapper (distilled)** | `principles/datawrapper.md` | Chart choice in plain language; practical rules of thumb (bar-is-safe-default, line-vs-column, circles hard to compare, size = quantity) | read directly — short and curated; pairs with the FT clone | +| Member | Path | Authoritative for | How to query | +| ------------------------------ | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **IBM Carbon** | `carbon-website/src/pages/` | Notification taxonomy, status levels, empty/loading states, content basics, data-viz styling | grep `.mdx` under `components/notification`, `patterns/{empty-states,loading,status-indicator}-pattern`, `guidelines/content` | +| **GOV.UK Design System** | `govuk-design-system/src/` | Error & validation messages, failure pages, forms, plain-language content, accessibility | `index.md` under `components/{error-message,error-summary,notification-banner}`, `patterns/{problem-with-the-service-pages,service-unavailable-pages,check-answers}`, `accessibility/` | +| **WAI-ARIA APG** | `aria-practices/content/patterns/` | Keyboard interaction, focus management, ARIA roles/states for widgets | `/-pattern.html` — e.g. `dialog-modal`, `alertdialog`, `alert`, `listbox`, `menu-button`, `disclosure`, `switch`, `tabs`, `tooltip`, `windowsplitter` | +| **Nielsen Norman (distilled)** | `principles/nielsen-norman.md` | 10 usability heuristics; response-time / feedback budgets (0.1s / 1s / 10s) | read directly — it is short and curated | +| **FT Visual Vocabulary** | `chart-doctor/visual-vocabulary/` | Chart choice: data-relationship taxonomy (Magnitude, Correlation, Change-over-Time, Ranking, Distribution, Deviation, Part-to-whole, Spatial, Flow) → chart type | read `README.md` — the taxonomy is prose; each category gives a "use when…" definition + recommended chart types | +| **Datawrapper (distilled)** | `principles/datawrapper.md` | Chart choice in plain language; practical rules of thumb (bar-is-safe-default, line-vs-column, circles hard to compare, size = quantity) | read directly — short and curated; pairs with the FT clone | +| **web.dev (distilled)** | `principles/web-dev.md` | PWA/offline: service-worker update flow (`registerType: 'prompt'`), persistent storage (`navigator.storage.persist()`), quota/`estimate()`, installability — with the exact vite-plugin-pwa/Workbox API we use | read directly — short and curated; cites local `web-dev/`, `vite-plugin-pwa/`, `workbox/` clones | ## Close the loop @@ -85,10 +87,16 @@ contract, not the external source. have never been applied before. This is the one event that reopens the one-off sweep (see _Scope_); after it, pointwise maintains the new seat like the rest. -Candidate future seats (not yet seated): **Shopify Polaris** (UX-writing depth), -**web.dev** (perceived performance / PWA / offline UX — seat at M6 per the plan). +Candidate future seats (not yet seated): **Shopify Polaris** (UX-writing depth). Seated at M4 (chart choice — our domain): **FT Visual Vocabulary** (clone) + **Datawrapper** (distilled). Backfill is scoped to the Chart Builder itself (new surface — no pre-existing chart-choice code to reconcile), so the seating debt is discharged by building the builder against this canon rather than a separate sweep. + +Seated at M6 (PWA/offline — our domain): **web.dev** (distilled, with +vite-plugin-pwa/Workbox for the exact API). Backfill reconciled the existing PWA wiring: +it surfaced that `registerType: 'prompt'` was configured but never consumed (no update +prompt) and that `navigator.storage.persist()` was never requested — both fixed in M6 and +recorded in `docs/architecture/10`. Remaining gap (logged, not silently passed): the web +app manifest ships no icons, so the app is not yet installable — a design-asset task. diff --git a/docs/IMPLEMENTATION-PLAN.md b/docs/IMPLEMENTATION-PLAN.md index 36604e7..6f1b234 100644 --- a/docs/IMPLEMENTATION-PLAN.md +++ b/docs/IMPLEMENTATION-PLAN.md @@ -437,11 +437,13 @@ entries). `DONATE_URL` pending the real link). - **Offline/installable:** verify the SW + manifest give a working offline + installed app. **⏳ Remaining** — needs manual verification in a running/installed app. -- **Council** — seat **web.dev** for the PWA/offline/storage surfaces none of the seated +- **Council** — ~~seat **web.dev** for the PWA/offline/storage surfaces none of the seated members cover: the service-worker **update-available** prompt (`registerType: 'prompt'`), storage **persistence** (`navigator.storage.persist()`), and the quota **estimate** - (`StorageManager.estimate()`). **⏳ Remaining** (storage monitor is its consumer; seating - triggers a one-off backfill — see [`/council`](../.claude/skills/council/SKILL.md)). + (`StorageManager.estimate()`)~~ ✅ seated + backfilled (update-prompt toast + `persist()` + request; estimate already wired). **⏳ Remaining gap:** manifest ships no icons → not yet + installable (design-asset task). See [`/council`](../.claude/skills/council/SKILL.md) and + [arch 10](architecture/10-interaction-and-feedback.md). **Manual checks:** keyboard-only run-through; reload restores view from URL; offline reload works; install as standalone; reduced-motion honored. **⏳ Still owed** diff --git a/docs/architecture/10-interaction-and-feedback.md b/docs/architecture/10-interaction-and-feedback.md index 1ac67ae..175b0d8 100644 --- a/docs/architecture/10-interaction-and-feedback.md +++ b/docs/architecture/10-interaction-and-feedback.md @@ -320,6 +320,20 @@ Datasets (⌘/Ctrl+K), or check the dataset name in your spec."_ — instead of must match the actual cause (NN/g #9, GOV.UK error-message). The thrown `DatasetNotFoundError` carries `datasetName` so the surface can name it. +**Resolved — service-worker update prompt & persistent storage (web.dev seat).** The build +uses `registerType: 'prompt'`, so a new service worker waits and never takes over a running +session on its own — the app **must** tell the user, or "ask before updating" silently means +"never update." `orchestration/pwa.ts` consumes `virtual:pwa-register` and surfaces +`onNeedRefresh` as a **durable** (non-auto-dismissing) info toast with a **Reload** action +that calls `updateSW()`; `onOfflineReady` is a transient success toast. Separately, browser +storage is best-effort and evictable under pressure, which for a local-first workspace is data +loss — so we request `navigator.storage.persist()` once at startup +(`infrastructure/storage-persist`), feature-detected and silent on denial (Chromium decides +automatically; nothing for the user to act on). _(Consulted via /council → web.dev, with the +exact API from vite-plugin-pwa/Workbox; see `reference/principles/web-dev.md`. This bullet is +the contract; cite it, not the source.) **Known gap:** the manifest ships no icons, so the app +is not yet installable — a design-asset task, logged not passed._ + ## 6. Motion & accessibility as default Not features to add later — the baseline every surface is built on.