mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Docs: move point-in-time records to exploration/, trim build-narration, lift open items to plan
This commit is contained in:
@@ -64,6 +64,7 @@ docs/
|
|||||||
├── spec/ # Authoritative behavioral specification (00–10) — the WHAT
|
├── spec/ # Authoritative behavioral specification (00–10) — the WHAT
|
||||||
├── architecture/ # Architecture playbook (00–10) — the HOW (self-contained)
|
├── architecture/ # Architecture playbook (00–10) — the HOW (self-contained)
|
||||||
│ └── visual-specimen.html # Standalone token sandbox + reusable-primitive catalog
|
│ └── visual-specimen.html # Standalone token sandbox + reusable-primitive catalog
|
||||||
|
├── exploration/ # Point-in-time records (research, reviews, scope memos) — not maintained
|
||||||
├── IMPLEMENTATION-PLAN.md # Milestone sequence (M0–M6)
|
├── IMPLEMENTATION-PLAN.md # Milestone sequence (M0–M6)
|
||||||
└── WHY-A-SEPARATE-REBUILD.md
|
└── WHY-A-SEPARATE-REBUILD.md
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ Vite + React + Zustand + TypeScript + Vitest (happy-dom) + vite-plugin-pwa.
|
|||||||
`src/core` ↔ `src/app` split, `useAppStore`, design tokens, three-pane placeholder
|
`src/core` ↔ `src/app` split, `useAppStore`, design tokens, three-pane placeholder
|
||||||
shell, first core module (`format-detection`) with tests.
|
shell, first core module (`format-detection`) with tests.
|
||||||
|
|
||||||
**Verified:** `npm run typecheck`, `npm test` (14 passing), `npm run build` (PWA SW generated).
|
**Verified:** `npm run typecheck`, `npm test`, `npm run build` (PWA SW generated).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ and the companion `visual-specimen.html`.
|
|||||||
- Keyboard focus ring visible; text/UI contrast passes AA in light and dark.
|
- Keyboard focus ring visible; text/UI contrast passes AA in light and dark.
|
||||||
- No placeholder styling remains on the M1 surfaces.
|
- No placeholder styling remains on the M1 surfaces.
|
||||||
|
|
||||||
**Verified:** `typecheck` + `test` (61 passing, incl. `vega-themes.test.ts`) +
|
**Verified:** `typecheck` + `test` (incl. `vega-themes.test.ts`) +
|
||||||
`build` (Plex woff2, all script subsets, bundled & precached via the PWA
|
`build` (Plex woff2, all script subsets, bundled & precached via the PWA
|
||||||
`globPatterns`). Both themes screenshotted via the real
|
`globPatterns`). Both themes screenshotted via the real
|
||||||
app (chrome + Monaco + chart all repaint on theme flip); focus ring visible.
|
app (chrome + Monaco + chart all repaint on theme flip); focus ring visible.
|
||||||
@@ -216,7 +216,7 @@ assistance, and the fit-mode rendering contract.
|
|||||||
- Invalid spec shows inline error; autocomplete suggests Vega-Lite properties.
|
- Invalid spec shows inline error; autocomplete suggests Vega-Lite properties.
|
||||||
- Each fit mode resizes the chart as specified; choice survives reload.
|
- Each fit mode resizes the chart as specified; choice survives reload.
|
||||||
|
|
||||||
**Verified:** `typecheck` + `test` (83 passing — `rendering` fit-mode incl.
|
**Verified:** `typecheck` + `test` (`rendering` fit-mode incl.
|
||||||
nested layer/concat/facet specs, `SnippetStore` draft/publish/revert/editorView,
|
nested layer/concat/facet specs, `SnippetStore` draft/publish/revert/editorView,
|
||||||
`settings-store` `previewFitMode` round-trip) + `build` (PWA, 41 precache
|
`settings-store` `previewFitMode` round-trip) + `build` (PWA, 41 precache
|
||||||
entries) + `eslint` clean. Implementation notes: editing now writes the
|
entries) + `eslint` clean. Implementation notes: editing now writes the
|
||||||
@@ -290,9 +290,9 @@ the reference.
|
|||||||
**Goal:** no-JSON chart composition from a dataset → a new snippet.
|
**Goal:** no-JSON chart composition from a dataset → a new snippet.
|
||||||
|
|
||||||
> **Enhancement push (post-M4).** The forward plan now lives in
|
> **Enhancement push (post-M4).** The forward plan now lives in
|
||||||
> [`docs/chart-builder-enhancement-scope.md`](chart-builder-enhancement-scope.md) — it merges
|
> [`docs/exploration/chart-builder-enhancement-scope.md`](exploration/chart-builder-enhancement-scope.md) — it merges
|
||||||
> the Tier-B backlog ([`chart-builder-research.md`](chart-builder-research.md) §8) with the
|
> the Tier-B backlog ([`chart-builder-research.md`](exploration/chart-builder-research.md) §8) with the
|
||||||
> Lyra interaction review ([`lyra-review.md`](lyra-review.md)) and sets a **Tier-C** target.
|
> Lyra interaction review ([`lyra-review.md`](exploration/lyra-review.md)) and sets a **Tier-C** target.
|
||||||
> Shipped beyond the Tier-B floor so far: per-channel aggregate/bin/`timeUnit`, sort/stack;
|
> Shipped beyond the Tier-B floor so far: per-channel aggregate/bin/`timeUnit`, sort/stack;
|
||||||
> **actionable hints** (one-click warning fixes); and a builder UX/perf batch (near-fullscreen
|
> **actionable hints** (one-click warning fixes); and a builder UX/perf batch (near-fullscreen
|
||||||
> modal, canvas preview + canvas max-dimension guard, data-aware default pre-population). See
|
> modal, canvas preview + canvas max-dimension guard, data-aware default pre-population). See
|
||||||
@@ -326,7 +326,7 @@ canon): this is where Astrolabe stops being a pass-through JSON editor and start
|
|||||||
chart-shaped suggestions/defaults, so "_which chart, and why_" becomes a decision the app
|
chart-shaped suggestions/defaults, so "_which chart, and why_" becomes a decision the app
|
||||||
owns — the one thing Carbon's data-viz styling doesn't cover. Rather than a styling-only
|
owns — the one thing Carbon's data-viz styling doesn't cover. Rather than a styling-only
|
||||||
seating, we ran a full **research-first** pass (FT + Datawrapper + the formal engines
|
seating, we ran a full **research-first** pass (FT + Datawrapper + the formal engines
|
||||||
**Draco** and **Voyager**), recorded in [`docs/chart-builder-research.md`](chart-builder-research.md),
|
**Draco** and **Voyager**), recorded in [`docs/exploration/chart-builder-research.md`](exploration/chart-builder-research.md),
|
||||||
and chose the **Tier B "smart + guarded"** design: smart default mark for the data shape,
|
and chose the **Tier B "smart + guarded"** design: smart default mark for the data shape,
|
||||||
valid-type-locked field-type menus, Size-channel discipline, and non-blocking guidance.
|
valid-type-locked field-type menus, Size-channel discipline, and non-blocking guidance.
|
||||||
The convergent rules and citations live in that doc; the spec (§06) was amended to match.
|
The convergent rules and citations live in that doc; the spec (§06) was amended to match.
|
||||||
@@ -372,7 +372,7 @@ debounces only keystroke (`shownText`-only) changes.
|
|||||||
**Deferred at the time to M5/M6 (per §02):** Search, Sort controls + persistence, two
|
**Deferred at the time to M5/M6 (per §02):** Search, Sort controls + persistence, two
|
||||||
distinct empty-state messages, Storage Monitor — all now delivered in M6.
|
distinct empty-state messages, Storage Monitor — all now delivered in M6.
|
||||||
|
|
||||||
**Verified:** `typecheck` + `test` (287 passing — `snippet` duplicate factory,
|
**Verified:** `typecheck` + `test` (`snippet` duplicate factory,
|
||||||
`SnippetStore` rename/comment/duplicate, a `SnippetLibrary` render test guarding the
|
`SnippetStore` rename/comment/duplicate, a `SnippetLibrary` render test guarding the
|
||||||
auto-save effect against a render loop) + `eslint` clean + `build` (PWA, 41 precache
|
auto-save effect against a render loop) + `eslint` clean + `build` (PWA, 41 precache
|
||||||
entries).
|
entries).
|
||||||
@@ -507,6 +507,28 @@ not a silent drift:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Open items (carried from the exploration memos)
|
||||||
|
|
||||||
|
Deferred features whose reasoning lives in `docs/exploration/`; pulled here so the backlog
|
||||||
|
is in the maintained plan, not the archive.
|
||||||
|
|
||||||
|
**Chart theming** (`exploration/chart-theming-scope.md`):
|
||||||
|
|
||||||
|
- **User font upload** — FontFace-from-IndexedDB tier; theme `fonts: { family, source: 'file' }`.
|
||||||
|
- **Google Fonts opt-in CDN tier** — keyless catalog, opt-in only.
|
||||||
|
- **SVG export font embedding** — embed face data so exported SVGs render off-app.
|
||||||
|
- **Theme↔font pairing metadata** — a suggestion nicety.
|
||||||
|
- **Built-in expressive theme preset gallery** — e.g. "Editorial", "Terminal", "Sketch".
|
||||||
|
|
||||||
|
**Chart Builder** (`exploration/chart-builder-enhancement-scope.md`):
|
||||||
|
|
||||||
|
- **Transform-aware data inspector** — show resolved post-transform rows
|
||||||
|
(`exploration/data-inspector-exploration.md`).
|
||||||
|
- **Field-chip drag-and-drop** — click/keyboard-first shipped; drag deferred.
|
||||||
|
- **Calculated-field autocomplete popup** — Monaco-style completion for expressions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Architecture reference
|
## Architecture reference
|
||||||
|
|
||||||
The **how** behind each milestone is documented self-containedly in
|
The **how** behind each milestone is documented self-containedly in
|
||||||
|
|||||||
@@ -22,11 +22,8 @@ about user-facing widgets. At that overlap, one rule keeps them from drifting:
|
|||||||
and never contradict it.** A playbook bullet may _name_ the behavior in one clause and cite
|
and never contradict it.** A playbook bullet may _name_ the behavior in one clause and cite
|
||||||
the spec, then spend its words on the _how_ (the role, the keys, the focus move) and the
|
the spec, then spend its words on the _how_ (the role, the keys, the focus move) and the
|
||||||
_why_ (the council/canon citation). When a playbook bullet and the spec disagree, the
|
_why_ (the council/canon citation). When a playbook bullet and the spec disagree, the
|
||||||
**spec wins** and the bullet is the bug.
|
**spec wins** and the bullet is the bug. Restatement is the leak: two docs describing the
|
||||||
|
same behavior in their own words drift into contradiction; one cites the other instead.
|
||||||
_(This bit us once: arch §10 described "keep the library list, title-only" while spec §02
|
|
||||||
said "replace the entire list with the onboarding canvas" — the two halves were authored from
|
|
||||||
opposite mental models. The restatement, not the separation, was the leak.)_
|
|
||||||
|
|
||||||
## How to use this playbook
|
## How to use this playbook
|
||||||
|
|
||||||
|
|||||||
@@ -30,12 +30,9 @@ A store is a module that calls `create<State>()` once and exports the resulting
|
|||||||
hook. The state object holds both **data fields** and **action functions**.
|
hook. The state object holds both **data fields** and **action functions**.
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// src/app/stores/AppStore.ts
|
|
||||||
import { create } from 'zustand';
|
import { create } from 'zustand';
|
||||||
import type { UiTheme } from '@core/theme'; // defined in core; charts key off it too
|
|
||||||
|
|
||||||
export type ModalName = 'datasets' | 'about' | 'donate' | 'chartBuilder' | 'extract';
|
|
||||||
|
|
||||||
|
// ModalName is the union of the app's modal identifiers; UiTheme is defined in core.
|
||||||
export interface AppState {
|
export interface AppState {
|
||||||
uiTheme: UiTheme;
|
uiTheme: UiTheme;
|
||||||
activeModal: ModalName | null;
|
activeModal: ModalName | null;
|
||||||
@@ -297,36 +294,17 @@ export const useSnippetStore = create<SnippetState>((set, get) => ({
|
|||||||
}));
|
}));
|
||||||
```
|
```
|
||||||
|
|
||||||
The component is thin — it selects state and calls actions:
|
The component is thin — it selects state narrowly and wires events to actions, with no
|
||||||
|
mutation logic of its own:
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import { useShallow } from 'zustand/react/shallow';
|
|
||||||
import { useSnippetStore } from '../stores/SnippetStore';
|
|
||||||
|
|
||||||
export function SnippetList() {
|
export function SnippetList() {
|
||||||
const { snippets, activeSnippetId } = useSnippetStore(
|
const { snippets, activeSnippetId } = useSnippetStore(
|
||||||
useShallow((s) => ({ snippets: s.snippets, activeSnippetId: s.activeSnippetId })),
|
useShallow((s) => ({ snippets: s.snippets, activeSnippetId: s.activeSnippetId })),
|
||||||
);
|
);
|
||||||
const select = useSnippetStore((s) => s.select);
|
const select = useSnippetStore((s) => s.select); // stable identity — select actions individually
|
||||||
const remove = useSnippetStore((s) => s.remove);
|
const remove = useSnippetStore((s) => s.remove);
|
||||||
|
// render: one row per snippet, each calling select(id) / remove(id) on the events.
|
||||||
return (
|
|
||||||
<ul>
|
|
||||||
{snippets.map((s) => (
|
|
||||||
<li key={s.id} aria-current={s.id === activeSnippetId} onClick={() => select(s.id)}>
|
|
||||||
{s.name}
|
|
||||||
<button
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
remove(s.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -508,11 +486,8 @@ couples every intermediate component to data it doesn't use.
|
|||||||
Each store exposes a `reset()` action that returns its fields to initial values
|
Each store exposes a `reset()` action that returns its fields to initial values
|
||||||
(used on "new workspace", sign-out, or test teardown). Because every fact is a
|
(used on "new workspace", sign-out, or test teardown). Because every fact is a
|
||||||
single source field with no hand-maintained duplicates, reset is a flat `set(...)`
|
single source field with no hand-maintained duplicates, reset is a flat `set(...)`
|
||||||
of the initial values; selector-derived values recompute on their own.
|
of the initial values (as in `SnippetStore` above); selector-derived values
|
||||||
|
recompute on their own.
|
||||||
```ts
|
|
||||||
reset: () => set({ snippets: [], activeSnippetId: null, draftSpec: '' });
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ export async function readOriginUsage(): Promise<number | undefined> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**The bar is decorative; the legend is the data.** There is no `role="meter"` — a meter needs a meaningful maximum, which a composition with no fixed ceiling lacks (APG → meter). The legend's text labels + sizes are the accessible source of truth, so meaning never rests on hue (WCAG 1.4.1). Dropping the old 0.8/0.95 "almost full" thresholds is deliberate: they keyed off the untrustworthy quota; the genuine out-of-room event surfaces at save time (below), where it is accurate. (Resolves the former spec §02 ↔ code "whole-origin vs. snippet budget" divergence; council resolution recorded in `docs/architecture/10`.)
|
**The bar is decorative; the legend is the data.** There is no `role="meter"` — a meter needs a meaningful maximum, which a composition with no fixed ceiling lacks (APG → meter). The legend's text labels + sizes are the accessible source of truth, so meaning never rests on hue (WCAG 1.4.1). There are no "almost full" percentage thresholds — a fraction would key off the untrustworthy quota; the genuine out-of-room event surfaces at save time (below), where it is accurate.
|
||||||
|
|
||||||
### Fail loudly, never silently lose data
|
### Fail loudly, never silently lose data
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ above it is data; everything below it is a Vega `View` we own and must tear down
|
|||||||
> the main Live Preview) reads runtime rows via `view.data(name)`. It rides this same
|
> the main Live Preview) reads runtime rows via `view.data(name)`. It rides this same
|
||||||
> boundary: a `RenderHandle.data()` accessor wraps the view (like `toImageURL`), so no
|
> boundary: a `RenderHandle.data()` accessor wraps the view (like `toImageURL`), so no
|
||||||
> component touches the raw view. Parked, not built — see
|
> component touches the raw view. Parked, not built — see
|
||||||
> [`docs/data-inspector-exploration.md`](../data-inspector-exploration.md).
|
> [`docs/exploration/data-inspector-exploration.md`](../exploration/data-inspector-exploration.md).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -126,8 +126,7 @@ async function rerender(node: HTMLElement, spec: TopLevelSpec, config: Config) {
|
|||||||
crisp/inspectable/copyable and stays the default for the editor's LivePreview. But an
|
crisp/inspectable/copyable and stays the default for the editor's LivePreview. But an
|
||||||
SVG chart renders one DOM node per mark, so a many-mark chart (e.g. the Chart Builder's
|
SVG chart renders one DOM node per mark, so a many-mark chart (e.g. the Chart Builder's
|
||||||
default one-bar-per-row on a 10k-row dataset) costs **seconds** of main-thread
|
default one-bar-per-row on a 10k-row dataset) costs **seconds** of main-thread
|
||||||
layout/paint per render (measured ~6.5s on 9994 rows; the chart paints _after_ it first
|
layout/paint per render (the chart paints _after_ it first appears, freezing the tab). The **Chart Builder preview** therefore passes
|
||||||
appears, freezing the tab). The **Chart Builder preview** therefore passes
|
|
||||||
`renderSpec(…, { renderer: 'canvas' })` — canvas is a single node and paints in
|
`renderSpec(…, { renderer: 'canvas' })` — canvas is a single node and paints in
|
||||||
milliseconds. The raster trade-off is invisible for an ephemeral preview, and image
|
milliseconds. The raster trade-off is invisible for an ephemeral preview, and image
|
||||||
export (`view.toImageURL`) is renderer-agnostic.
|
export (`view.toImageURL`) is renderer-agnostic.
|
||||||
@@ -165,7 +164,7 @@ background, the categorical color range. Astrolabe ships one config per UI theme
|
|||||||
so charts visually belong to the app rather than looking like stock Vega-Lite.
|
so charts visually belong to the app rather than looking like stock Vega-Lite.
|
||||||
`src/core/vega-themes.ts` is the single source of truth; each house config is
|
`src/core/vega-themes.ts` is the single source of truth; each house config is
|
||||||
**two merged layers** (the full audit and forward plan live in
|
**two merged layers** (the full audit and forward plan live in
|
||||||
[`docs/chart-theming-scope.md`](../chart-theming-scope.md)):
|
[`docs/exploration/chart-theming-scope.md`](../exploration/chart-theming-scope.md)):
|
||||||
|
|
||||||
- **Base** (`lightBaseConfig`/`darkBaseConfig`) — the legibility minimum:
|
- **Base** (`lightBaseConfig`/`darkBaseConfig`) — the legibility minimum:
|
||||||
`background: 'transparent'` (the pane shows through) plus guide colors on the
|
`background: 'transparent'` (the pane shows through) plus guide colors on the
|
||||||
|
|||||||
@@ -254,12 +254,9 @@ the Draft/Published view) is a **radio group**, never a row of `aria-pressed` to
|
|||||||
|
|
||||||
- `role="radio"`/`aria-checked`, a **roving tabindex** (only the selected option is a tab
|
- `role="radio"`/`aria-checked`, a **roving tabindex** (only the selected option is a tab
|
||||||
stop), and Arrow/Home/End to move-and-select (APG → Radio Group). One widget so the keyboard
|
stop), and Arrow/Home/End to move-and-select (APG → Radio Group). One widget so the keyboard
|
||||||
model is defined once. _(Tabs were a candidate for Draft/Published; we chose radio group for
|
model is defined once. _(Not a toggle switch: APG defines `role="switch"` as on/off of a **single** setting, but
|
||||||
consistency with the other segmented controls and to avoid tabpanel wiring to Monaco. A
|
Draft/Published selects between two **named peer views** with no natural "on" side — a radio
|
||||||
**toggle switch** was also weighed and rejected: APG defines `role="switch"` as on/off of a
|
group is the right semantics. Reserve the switch for genuine on/off settings.)_ A per-option
|
||||||
**single** setting, but Draft/Published selects between two **named peer views** with no
|
|
||||||
natural "on" side — a radio group is the right semantics. Reserve the switch for genuine
|
|
||||||
on/off settings. Consulted via /council → APG switch / radio-group / tabs.)_ A per-option
|
|
||||||
`title` (tooltip for a terse label) doubles as the option's accessible name, so it must
|
`title` (tooltip for a terse label) doubles as the option's accessible name, so it must
|
||||||
**lead with the visible label** ("Original — the natural size from the spec"), or
|
**lead with the visible label** ("Original — the natural size from the spec"), or
|
||||||
speech-input users can't address the control they see (WCAG 2.5.3 label-in-name).
|
speech-input users can't address the control they see (WCAG 2.5.3 label-in-name).
|
||||||
@@ -302,9 +299,9 @@ show real measured sizes instead.
|
|||||||
- **Unavailable degrades, not disappears.** Snippets + datasets are measured from our own data, so
|
- **Unavailable degrades, not disappears.** Snippets + datasets are measured from our own data, so
|
||||||
they always show; only the **App** segment (which needs the origin estimate) drops out when the
|
they always show; only the **App** segment (which needs the origin estimate) drops out when the
|
||||||
Storage Manager API is absent.
|
Storage Manager API is absent.
|
||||||
- **No proactive "almost full" warning.** Dropping the old 0.8/0.95 thresholds is intentional —
|
- **No proactive "almost full" warning.** A percentage gauge would key off the untrustworthy
|
||||||
they keyed off the untrustworthy quota, and a fake fuel gauge fails NN/g #1 (_visibility of system
|
quota, and a fake fuel gauge fails NN/g #1 (_visibility of system status_) more than it
|
||||||
status_) more than it serves it. The genuine out-of-room event surfaces at **save time** as an
|
serves it. The genuine out-of-room event surfaces at **save time** as an
|
||||||
actionable error (`services/storage-errors.ts` → recover by deleting), satisfying NN/g #9.
|
actionable error (`services/storage-errors.ts` → recover by deleting), satisfying NN/g #9.
|
||||||
|
|
||||||
_(Consulted via /council → WAI-ARIA APG `meter`, FT Visual Vocabulary + Datawrapper (part-to-whole),
|
_(Consulted via /council → WAI-ARIA APG `meter`, FT Visual Vocabulary + Datawrapper (part-to-whole),
|
||||||
@@ -356,7 +353,7 @@ generous preview. Each card **renders live through the shared `chart-renderer`**
|
|||||||
embed path; each card owns its `RenderHandle` and finalizes on unmount — the per-card nodes are
|
embed path; each card owns its `RenderHandle` and finalizes on unmount — the per-card nodes are
|
||||||
independent, so they don't touch `LivePreview`'s single-host serialization), and adds as an
|
independent, so they don't touch `LivePreview`'s single-host serialization), and adds as an
|
||||||
ordinary snippet. Empty stays calm and positive, never an error (NN/g aesthetic-and-minimalist;
|
ordinary snippet. Empty stays calm and positive, never an error (NN/g aesthetic-and-minimalist;
|
||||||
§3 empty ≠ error). Council resolutions for the canvas: (1) **each card preview is decorative** —
|
§3 empty ≠ error). Three rules govern the canvas. (1) **Each card preview is decorative** —
|
||||||
`aria-hidden`, skipped by screen readers (Carbon empty-states a11y / WCAG decorative images);
|
`aria-hidden`, skipped by screen readers (Carbon empty-states a11y / WCAG decorative images);
|
||||||
the card **name + one-line description + a uniquely-labelled `Add` button** ("Add Bar chart",
|
the card **name + one-line description + a uniquely-labelled `Add` button** ("Add Bar chart",
|
||||||
APG button) carry the meaning, so AT users reach no dead end. (2) The canvas is the **single
|
APG button) carry the meaning, so AT users reach no dead end. (2) The canvas is the **single
|
||||||
@@ -386,7 +383,7 @@ primary next step ("Add a dataset" → the Datasets create form) — never a dea
|
|||||||
dataset-row "Build Chart" stays as the contextual shortcut; the onboarding canvas gains the
|
dataset-row "Build Chart" stays as the contextual shortcut; the onboarding canvas gains the
|
||||||
data-first door ("Build a chart from your data") beside its primary. _(Consulted via
|
data-first door ("Build a chart from your data") beside its primary. _(Consulted via
|
||||||
/council → NN/g #6/#7, Carbon empty-states; recorded in
|
/council → NN/g #6/#7, Carbon empty-states; recorded in
|
||||||
docs/chart-builder-enhancement-scope.md §3 · 3D. This bullet is the contract; cite it, not
|
docs/exploration/chart-builder-enhancement-scope.md §3 · 3D. This bullet is the contract; cite it, not
|
||||||
the source.)_
|
the source.)_
|
||||||
|
|
||||||
**Resolved — one live region per shared message.** When the same error feeds two surfaces
|
**Resolved — one live region per shared message.** When the same error feeds two surfaces
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# docs/exploration
|
||||||
|
|
||||||
|
Point-in-time records: research, tool reviews, and scope/planning memos written to think a
|
||||||
|
decision through. Unlike `docs/spec/` (the contract — the _what_) and `docs/architecture/`
|
||||||
|
(the maintained _how_), nothing here is kept current — each file reflects what was known
|
||||||
|
when it was written. Decisions that survived are folded into the spec and architecture;
|
||||||
|
these remain as the reasoning behind them.
|
||||||
|
|
||||||
|
Active follow-up work is tracked in [`../IMPLEMENTATION-PLAN.md`](../IMPLEMENTATION-PLAN.md),
|
||||||
|
not here. Exact figures in these files (timings, line counts, test counts) are frozen
|
||||||
|
snapshots, not live numbers.
|
||||||
|
|
||||||
|
- `chart-builder-research.md` — cross-source chart-choice research (Draco, Voyager, FT Visual Vocabulary, Datawrapper).
|
||||||
|
- `lyra-review.md` — review of vega/lyra for Chart Builder interaction ideas.
|
||||||
|
- `chart-builder-enhancement-scope.md` — consolidated Tier-B → Tier-C forward plan for the Chart Builder.
|
||||||
|
- `chart-theming-scope.md` — chart theming plan and slice breakdown.
|
||||||
|
- `monetization-and-sync-exploration.md` — monetization + BYO-cloud-sync direction memo.
|
||||||
|
- `data-inspector-exploration.md` — transform-aware data-inspector direction.
|
||||||
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
> for chart-builder enhancement work — it merges the research backlog from
|
> for chart-builder enhancement work — it merges the research backlog from
|
||||||
> [`chart-builder-research.md`](./chart-builder-research.md) §8 (the M4 decision and its
|
> [`chart-builder-research.md`](./chart-builder-research.md) §8 (the M4 decision and its
|
||||||
> deferred items) with the interaction ideas from [`lyra-review.md`](./lyra-review.md) §5,
|
> deferred items) with the interaction ideas from [`lyra-review.md`](./lyra-review.md) §5,
|
||||||
> read against the current spec ([`spec/06-chart-builder.md`](./spec/06-chart-builder.md))
|
> read against the current spec ([`spec/06-chart-builder.md`](../spec/06-chart-builder.md))
|
||||||
> and the shipped code (`src/core/chart-builder.ts`).
|
> and the shipped code (`src/core/chart-builder.ts`).
|
||||||
>
|
>
|
||||||
> **Goal (the brief):** a **rapid, intuitive GUI for building Vega-Lite specs**, with
|
> **Goal (the brief):** a **rapid, intuitive GUI for building Vega-Lite specs**, with
|
||||||
@@ -58,7 +58,7 @@ mapping those to friendly labels and choosing which to expose is the main design
|
|||||||
|
|
||||||
- Seed: builder **1D** data preview — see
|
- Seed: builder **1D** data preview — see
|
||||||
[`chart-builder-enhancement-scope.md`](./chart-builder-enhancement-scope.md) (§3 1D).
|
[`chart-builder-enhancement-scope.md`](./chart-builder-enhancement-scope.md) (§3 1D).
|
||||||
- Renderer/preview home: [`architecture/05`](./architecture/05-rendering-theming-preview.md)
|
- Renderer/preview home: [`architecture/05`](../architecture/05-rendering-theming-preview.md)
|
||||||
(the `RenderHandle` boundary the `view.data` accessor extends).
|
(the `RenderHandle` boundary the `view.data` accessor extends).
|
||||||
- Behaviour, once built, is specified in `docs/spec/04` (Live Preview) and `docs/spec/06`
|
- Behaviour, once built, is specified in `docs/spec/04` (Live Preview) and `docs/spec/06`
|
||||||
(the builder preview), per "spec follows code".
|
(the builder preview), per "spec follows code".
|
||||||
+1
-1
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
## 1. The tension this collides with
|
## 1. The tension this collides with
|
||||||
|
|
||||||
A login + encrypted-DB system isn't a neutral feature add. [`SOUL.md`](../SOUL.md) makes
|
A login + encrypted-DB system isn't a neutral feature add. [`SOUL.md`](../../SOUL.md) makes
|
||||||
"no account" a **named value**, and lists the opposite under _What We're Not_:
|
"no account" a **named value**, and lists the opposite under _What We're Not_:
|
||||||
|
|
||||||
- Value #5, _Own Your Data_: "Your library is a file you control, **not a row in someone's
|
- Value #5, _Own Your Data_: "Your library is a file you control, **not a row in someone's
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
The Chart Builder is a visual, no-JSON way to compose a Vega-Lite chart from a selected dataset. The user picks a mark type and maps the dataset's columns to encoding channels; the builder produces a complete Vega-Lite spec and saves it as a new snippet that references the dataset. It is intended for users who want to start a chart quickly without hand-writing JSON in the _Spec Editor & Draft/Published Workflow_.
|
The Chart Builder is a visual, no-JSON way to compose a Vega-Lite chart from a selected dataset. The user picks a mark type and maps the dataset's columns to encoding channels; the builder produces a complete Vega-Lite spec and saves it as a new snippet that references the dataset. It is intended for users who want to start a chart quickly without hand-writing JSON in the _Spec Editor & Draft/Published Workflow_.
|
||||||
|
|
||||||
> **Design level — "smart + guarded" (Tier B).** The builder is field-first — the user works from a shelf of the dataset's columns and drops them onto encoding channels — and stays within the inputs below, but it is not a dumb composer: it picks a sensible default mark for the data shape, offers only field types valid for each column, keeps unsuitable channel mappings out of reach, and surfaces non-blocking guidance for encodings that render poorly. These behaviors are derived from cross-source chart-choice research recorded in [`docs/chart-builder-research.md`](../chart-builder-research.md) (the convergence of Draco, Voyager, the FT Visual Vocabulary, and Datawrapper). On top of this "smart + guarded" base sits the **intent-first front door** (Tier C — _what do you want to show?_, see _Intent_ below): an on-ramp that recommends a whole chart from the user's stated intent, without replacing the mark-first builder beneath it.
|
> **Design level — "smart + guarded" (Tier B).** The builder is field-first — the user works from a shelf of the dataset's columns and drops them onto encoding channels — and stays within the inputs below, but it is not a dumb composer: it picks a sensible default mark for the data shape, offers only field types valid for each column, keeps unsuitable channel mappings out of reach, and surfaces non-blocking guidance for encodings that render poorly. These behaviors are derived from cross-source chart-choice research recorded in [`docs/exploration/chart-builder-research.md`](../exploration/chart-builder-research.md) (the convergence of Draco, Voyager, the FT Visual Vocabulary, and Datawrapper). On top of this "smart + guarded" base sits the **intent-first front door** (Tier C — _what do you want to show?_, see _Intent_ below): an on-ramp that recommends a whole chart from the user's stated intent, without replacing the mark-first builder beneath it.
|
||||||
|
|
||||||
## Opening
|
## Opening
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ The section is ordered **input → shaping** so the distinction reads at a glanc
|
|||||||
### Data preview
|
### Data preview
|
||||||
|
|
||||||
- The first item in the section: a collapsible, **read-only** sample of the dataset's first rows (capped), with a per-column **type chip** in each header. It lets the user sanity-check inferred types against the actual values _before_ building — exactly when type inference is most likely to surprise. Editing the data is out of scope. A non-tabular payload (a single JSON object, TopoJSON, an unfetched URL) has no rows to show.
|
- The first item in the section: a collapsible, **read-only** sample of the dataset's first rows (capped), with a per-column **type chip** in each header. It lets the user sanity-check inferred types against the actual values _before_ building — exactly when type inference is most likely to surprise. Editing the data is out of scope. A non-tabular payload (a single JSON object, TopoJSON, an unfetched URL) has no rows to show.
|
||||||
- The preview shows the dataset's **raw source rows** — _before_ the filters and calculated fields below are applied; it does not show derived columns. Its position above those controls makes that explicit: it is the input, not the result. (The transformed result is what the right-hand chart renders. A transform-aware "data inspector" that shows the resolved rows is a planned future direction — see [`docs/data-inspector-exploration.md`](../data-inspector-exploration.md).)
|
- The preview shows the dataset's **raw source rows** — _before_ the filters and calculated fields below are applied; it does not show derived columns. Its position above those controls makes that explicit: it is the input, not the result. (The transformed result is what the right-hand chart renders. A transform-aware "data inspector" that shows the resolved rows is a planned future direction — see [`docs/exploration/data-inspector-exploration.md`](../exploration/data-inspector-exploration.md).)
|
||||||
|
|
||||||
### Filters
|
### Filters
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ These controls appear only when they apply:
|
|||||||
|
|
||||||
### Guidance (non-blocking)
|
### Guidance (non-blocking)
|
||||||
|
|
||||||
The builder surfaces short, plain-language hints for configurations that render but read poorly — advisory only, never blocking the **Create Snippet** action (validation below is the sole gate). A hint states the _problem_; where there is an obvious remedy, it also offers one or more **one-click fix** buttons that apply the change to the configuration (e.g. _Aggregate as Sum_, _Swap X/Y_, _Switch to Point_, _Stack_, _Remove colour_). A fix is an offer, never a forced change — applying it updates the config and the hint re-derives away. Interaction/accessibility of these actions follows [`architecture/10`](../architecture/10-interaction-and-feedback.md) §5 (polite announcement, focus moved off the removed button). These follow the chart-choice research ([`docs/chart-builder-research.md`](../chart-builder-research.md)) and include, for example:
|
The builder surfaces short, plain-language hints for configurations that render but read poorly — advisory only, never blocking the **Create Snippet** action (validation below is the sole gate). A hint states the _problem_; where there is an obvious remedy, it also offers one or more **one-click fix** buttons that apply the change to the configuration (e.g. _Aggregate as Sum_, _Swap X/Y_, _Switch to Point_, _Stack_, _Remove colour_). A fix is an offer, never a forced change — applying it updates the config and the hint re-derives away. Interaction/accessibility of these actions follows [`architecture/10`](../architecture/10-interaction-and-feedback.md) §5 (polite announcement, focus moved off the removed button). These follow the chart-choice research ([`docs/exploration/chart-builder-research.md`](../exploration/chart-builder-research.md)) and include, for example:
|
||||||
|
|
||||||
- A **Line**, **Area**, or **Heatmap** mark with only one axis mapped (both axes are needed to draw it).
|
- A **Line**, **Area**, or **Heatmap** mark with only one axis mapped (both axes are needed to draw it).
|
||||||
- A **Bar/Line/Area** whose X and Y are both categories (nothing to measure).
|
- A **Bar/Line/Area** whose X and Y are both categories (nothing to measure).
|
||||||
|
|||||||
@@ -126,4 +126,4 @@ A **CustomTheme** is a user-named Vega-Lite config saved in the library and offe
|
|||||||
| `created` | ISO-timestamp string | When the theme was first created. |
|
| `created` | ISO-timestamp string | When the theme was first created. |
|
||||||
| `modified` | ISO-timestamp string | When the theme was last changed. |
|
| `modified` | ISO-timestamp string | When the theme was last changed. |
|
||||||
|
|
||||||
Selection is keyed by `id` (not name) so renaming a theme never invalidates the persisted `ui.chartTheme`. A persisted `custom:<id>` whose record no longer exists is not an error: charts render with the house style until the record appears (themes hydrate asynchronously), and deleting the actively-selected theme resets the selection to `astrolabe` explicitly. Custom themes are not yet included in the _Import & Export_ envelope (planned; see `docs/chart-theming-scope.md`).
|
Selection is keyed by `id` (not name) so renaming a theme never invalidates the persisted `ui.chartTheme`. A persisted `custom:<id>` whose record no longer exists is not an error: charts render with the house style until the record appears (themes hydrate asynchronously), and deleting the actively-selected theme resets the selection to `astrolabe` explicitly. Custom themes travel in the _Import & Export_ envelope alongside snippets and datasets (spec §08).
|
||||||
|
|||||||
Reference in New Issue
Block a user