mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Add aggregation, binning, granularity, sort, and stacking to the Chart Builder
- Per-channel transforms: aggregate (sum/mean/median/min/max), quantitative bin, and temporal timeUnit granularity; bin and aggregate are mutually exclusive. A field-less "Count of records" measure (Voyager's count(*)). - Chart-level sort (rank a categorical axis by its measure) and stacking (zero / 100% normalize), each shown only when it applies. - Field type is a fixed N|O|Q|T segmented control with the column's invalid types disabled; SegmentedControl gains APG-correct disabled options. - A crowded-category-axis warning (a raw measure drawing one mark per row over a large dataset) and a disabled-Create hint (says why it's disabled). - Drop the Create success toast — the new snippet is immediately visible. - Docs: spec §06, research-doc §8 backlog (incl. the cardinality/extent profiling TODO), architecture 01 (stable-selector rule) and 05 (builder-local preview), and a profiling breadcrumb.
This commit is contained in:
@@ -87,6 +87,26 @@ const { activeModal, uiTheme } = useAppStore(
|
||||
);
|
||||
```
|
||||
|
||||
**`useShallow` only helps when the elements are stable.** It shallow-compares the
|
||||
result — array elements (or object values) by `Object.is`. A selector that
|
||||
**computes** a fresh collection of fresh objects each call (e.g.
|
||||
`useShallow((s) => buildWarnings(s.config))`) defeats it: every element is a new
|
||||
reference, so the result never compares equal, `useSyncExternalStore` re-renders
|
||||
forever, and React throws _"Maximum update depth exceeded"_ (a white screen). A
|
||||
selector must return a **primitive** or a **stored reference** — never a freshly
|
||||
built array/object. Derive computed collections in the component with `useMemo`
|
||||
over a stable slice instead:
|
||||
|
||||
```tsx
|
||||
const config = useChartBuilderStore((s) => s.config); // stored ref, stable between updates
|
||||
const warnings = useMemo(() => builderWarnings(config), [config]); // recompute only on change
|
||||
```
|
||||
|
||||
This is a render-time loop, so core/store unit tests stay green and miss it. A bare
|
||||
`react-dom/client` + `react`'s `act` mount test catches it with **no test-library
|
||||
dependency** — mount the component in the looping config and assert it doesn't throw
|
||||
(prove the guard by reverting the fix first). See `ChartBuilderModal.test.tsx`.
|
||||
|
||||
### Reading/writing outside components
|
||||
|
||||
Services, orchestration, infrastructure, and tests use the store object directly —
|
||||
|
||||
@@ -352,6 +352,18 @@ blank mid-edit.
|
||||
- **Don't** render synchronously on every keystroke.
|
||||
- **Don't** await a render inside an input/keydown handler.
|
||||
|
||||
### A second preview surface: the Chart Builder
|
||||
|
||||
The editor's `LivePreview` is **bound to the snippet editor** — it reads `SnippetStore`
|
||||
(shown spec), `AppStore` (fit mode/theme), and `PreviewStore` (shared error). The
|
||||
**Chart Builder modal** needs a preview of a _different_ spec source (its config), so it
|
||||
does **not** reuse `LivePreview`; it runs its own small debounced render over the same
|
||||
`chart-renderer.renderSpec` + `prepareSpecForRender`, with **local** error state (never the
|
||||
shared `PreviewStore`, which would cross-talk with the editor). Two preview surfaces, one
|
||||
renderer service. Builder flow: `chart-builder.ts` (pure spec assembler) → `ChartBuilderStore`
|
||||
(config + create) → `ChartBuilderModal`'s `BuilderPreview`. Reach for a reusable preview
|
||||
component only if a _third_ surface appears.
|
||||
|
||||
---
|
||||
|
||||
## 6. Rendering Contract Lives Upstream (reference)
|
||||
|
||||
Reference in New Issue
Block a user