From b721790af15148c7014f40c9f14ca578d195f9d2 Mon Sep 17 00:00:00 2001 From: Marty Nelson Date: Sat, 20 Jun 2026 21:05:32 -0700 Subject: [PATCH] docs: prune completed items from TODO.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes done work: ListBlock design+impl (KeyedList shipped), chronos package (removed), Controller/Updater removal, and the done rerenderer follow-ons (Controller/Updater removable, ListBlock variants, valhalla rerenderer tests). Condenses the typing section (valhalla now typechecks; remaining is precision). Updates dom-info → thoth paths. Keeps genuine follow-ons: attr/prop table, Channel (broadcast/cancel/role), typing precision, compose, platform tracking, docs + downstream wre-dashboards migration, dom-info validation CI, vitest bugs, eslint review. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01156Jwbhv8ue51zDZdvdmbM --- TODO.md | 180 +++++++++++++++----------------------------------------- 1 file changed, 48 insertions(+), 132 deletions(-) diff --git a/TODO.md b/TODO.md index 99bbdbd..4644680 100644 --- a/TODO.md +++ b/TODO.md @@ -1,42 +1,25 @@ # Azoth TODO -## Rerenderer (2.0 centerpiece — design captured, pre-spike) - -Design: `docs/design/rerenderer.md`. Re-execution + identity-keyed -cache; per-call-site factories (closure identity as key); ordinal reuse -for loops; lists-shrink-branches-sleep prune rule; narrowest-scope -convention (components run once and RETURN their rerenderable); -per-type update dispatch in compose. Spike order in the doc's open -questions. Naming bikeshed (Rerender vs Rerenderer) deferred. - -Related blocks decision: KeyedBlock renames to **ListBlock** -(js-framework-benchmark holdover name). AnchorBlock remains a cut -candidate; Toggle TBD. - -### ListBlock design — settled 2026-06-17 - -Two list strategies, distinguished by *node lifecycle* (not by "store -per-item state or not" — the per-row bound closure is unavoidable; what's -shared is the one `makeBind` factory = the single render path): - -1. **Node-per-item, keyed (THE primary, ~95% of lists).** A rerenderer per - row, keyed by id — "swap a rerenderer in per row." Reuses the existing - rerenderer wholesale; no new core mechanism. Update/remove/swap/select - by key. Most lists are 20–few-hundred rows. Migrating here is what lets - the legacy Controller/Updater + injectable/getBound relocate-path in - renderer.js be deleted. - - Optional cap: recycle a bounded pool of on-hand nodes (paging / - keep-N-around) — still interp 1, just with a ceiling. Not virtualization. -2. **Recycled pool / virtualization (large lists, FUTURE, separate block).** - K nodes ≪ N items; rebind the pool to a data window on scroll. Different - node lifecycle, so a distinct block — not a mode toggle on ListBlock. - Paint-dodge for offscreen rows: `content-visibility` (modern form of the - old `display`-hidden trick). - -Implementation increment when picked up: rewrite KeyedBlock → ListBlock as a -`Map` over the shared row template; then remove the -now-unused Controller/Updater/getBound legacy path; then block tests -(js-framework-benchmark-style add/update/swap/remove/select). +## Blocks / rerenderer follow-ons + +Landed: **KeyedList** shipped (abstract base + `KeyedUList`/`KeyedOList`/ +`KeyedTable` leaves; add/addAll/insert/update/move/remove/clear/has/get/keyFor; +controller pattern; `Map` per-row model). Legacy +`Controller`/`Updater` + injectable/`getBound` path removed. Design: +`docs/design/keyed-list.md`, `frame-primitives.md`. + +Open: +- **Rerenderer naming bikeshed** (Rerender vs Rerenderer) — deferred. +- **AnchorBlock** cut candidate; **Toggle** disposition — decide. +- **VirtualList** (recycled-pool / virtualization, FUTURE, separate block) — + `docs/design/VirtualList.md` + `frame-primitives.md`. +- **`stopRerenderer`** (name TBD): opt a subtree out of reuse (mint fresh nodes) + within a rerenderer path. The custom-element frame turned out NOT to need an + explicit reset (see keyed-list.md), but a general subtree opt-out may still + want it. Future; design open. +- **Teaching docs**: tie the init→update patterns (module-factory / class / + object UIComponent / plain render fn) to realistic UI-layout management — + worked examples, not mechanism explainers. ## Attribute vs property table (2.0 release-gating) @@ -82,37 +65,6 @@ Related: reply to wooorm's open question on property-information #21 project, Feb 2025, awaiting Marty's answer). Comment first; PR if receptive; Azoth doesn't wait on the outcome. -## Chronos - -### Consolidate `generator` into maya `pushable` - -The maya package now exports `pushable()` (in `@azothjs/maya/channels`) -which is the same push-to-pull bridge `chronos.generator()` provides — -minus the transform parameter (moved to call site / Channel.as / -iterator helpers). pushable is canonical going forward. - -To finish the consolidation: -- Migrate `wre-dashboards` (and any other downstream) off - `azoth/chronos/generators` to `azoth/maya/channels`. The transform - argument used in `AgentSearch.jsx` line 14 becomes either an explicit - `await` at the call site or a `Channel.as` transform. -- Remove `chronos/generators/generator.js` once no consumers remain. -- The chronos package's role narrows further (or dissolves) — open - design question, not a blocker. - -### Multicast disposition - -`Multicast` is still in chronos but has no production callers (the old -chronos `channels/branch.js` that used it is gone). Either: - -- Keep as scaffolding for a future iterators-with-many-consumers - primitive, OR -- Defer entirely to RxJS / TC39 Observable for fan-out and remove. - -The EventTarget-based broadcast helper sketched as a future utility -(see Channel section) may obviate Multicast entirely — EventTarget is -naturally multi-listener, which is the actual ask. - ## Channel ### Multi-listener broadcast helper (future utility) @@ -170,42 +122,26 @@ any DOM" separation-of-concerns pattern deserves a documented example (search box + paging + results in one module, Channel out — no component-tree demands). Connects to the hypermedia thesis. -## Components - -### Typing review — d.ts + JSDoc for consumer type info +## Components / typing — precision pass (follow-on) -Doing valhalla in `.tsx` was the forcing function that uncovered gaps -between runtime behavior and the type definitions. The runtime accepts -considerably more than the types currently model. The review: +valhalla now typechecks fully (`.tsx`, 0 errors) — the forcing function is done: +`jsx.d.ts` has HTML + SVG + custom-element tags (hyphen-indexed) and +`JSX.ElementType` (function + class components returning DOM or a rerenderable); +`DOMChild` includes `Channel`; maya's types reach valhalla via `allowJs` +inference. Remaining is *depth* (optional, on-demand): -**`DOMChild` (in `packages/azoth/jsx.d.ts`) is incomplete.** Today it's -`string | number | boolean | Node | null | undefined | DOMChild[]`. compose -also accepts at runtime — and should be reflected in the type: -- `Promise` -- `AsyncIterable` -- `ReadableStream` -- Observable-shaped (`{ subscribe(...) }`) -- Render objects (`{ render(props?, childNodes?) }`) -- Function references (invoked with no args by compose) -- `Channel` instances -- `IGNORE` sentinel -- `bigint` +**`DOMChild` still incomplete** — compose also accepts at runtime: `Promise`, +`AsyncIterable`, `ReadableStream`, Observable-shaped, render objects, function +refs, `IGNORE` sentinel, `bigint`. (Channel added.) The `as unknown as +JSX.Element` cast in `channels.test.tsx` surfaces it. -The mismatch surfaces concretely at `packages/valhalla/channels.test.tsx:317` -where an observable-in-slot needs an `as unknown as JSX.Element` cast. +**`` props are `any`** — its constructor param is untyped, so neither +`new Channel({…})` nor `` deep-checks props. Typing the constructor +(JSDoc **on Channel** — types-closer-to-the-object) unlocks both: `eventType` ⟺ +EventTarget source, `map` ⟺ array source, `error`/`as` return match, `append`. -**`` props need typed signatures with constraint relationships:** -- `eventType` required ⟺ `source` is an `EventTarget` -- `map` meaningful only when source produces arrays -- `error` transform returns must match what `as` returns -- `append` boolean-presence semantics (JSX attribute style) - -JSDoc with `@template` + conditional types is one path; per-component -`.d.ts` overloads another. Open question. - -**`pushable`, render-object form, class-component form, function-component -form** also need typed surfaces. The public API is stable enough now that -this work pays off; it's a real next step. +**Per-form prop typing** — `pushable`, render-object, class-/function-component +surfaces. `@template`/conditional types vs per-component `.d.ts` — open. ## Compose @@ -265,26 +201,6 @@ part-groups + the Rerenderer's apply-values mechanism) to offer the direction conversation. Watch the repo; consider a position write-up once 2.0 ships. -### Rerenderer follow-ons - -- Controller/Updater likely removable once blocks migrate onto - Rerenderer (legacy injectable path in renderer.js goes with them). -- ListBlock may need variant behavior — or simply swaps a rerenderer - in per row. Decide during the blocks increment. -- Valhalla API-level rerenderer tests (JSX-driven, crazy scenarios) - once increments (b)+(c) land — that's where confidence accrues; - refine code after. -- `stopRerenderer` (name TBD): a way, within a rerenderer path, to toggle - the flow back to create-new-DOM mode for a subtree — opt out of reuse - where each call should mint fresh nodes. Future feature; design open. -- Teaching: the rerenderer *mechanism* doesn't need corpus budget — it's - init-then-update, a plain JS/CS pattern (module factory, class - constructor/render, object initialize/render) LLMs already know; the - rerenderer just links it to JSX-as-DOM. What needs examples is tying - those patterns to real-world UI layout management. Write those for the - docs (component-as-module-factory, class/object UIComponent, plain - render fn — each shown in a realistic layout), not mechanism explainers. - ### WICG Observable + `EventTarget.prototype.when()` Chrome 135+ has shipped Observable; Firefox is implementing. When @@ -332,24 +248,24 @@ What migrates when it does: This migration doubles as the validation pass for the 2.0 migration story — first real consumer upgrade. -## dom-info +## dom-info (folded into thoth — `packages/thoth/dom-info/`) ### Browser-validation suites — on-demand, not in CI -dom-info's `dom-props` / `events` / `svg` suites probe the pinned Chromium -to confirm the lifted platform data still matches. They're excluded from -the default run (`pnpm test`) and run on demand via `pnpm test:validate` -(`VALIDATE=true`). They change only on a dependency or Chromium bump. +The `dom-props` / `events` / `svg` suites probe the pinned Chromium to confirm +the lifted platform data still matches. Excluded from the default run; run on +demand via `pnpm test:validate` (`VALIDATE=true`). They change only on a +dependency or Chromium bump. Open follow-ons: - Wire `test:validate` into a CI job gated to PRs/merges that touch - `packages/dom-info/**` or bump the relevant deps (property-information, - html/svg-element-attributes, html/svg/mathml-tag-names, playwright) — - rather than every push. -- A "deps current?" check (renovate, or a scheduled `pnpm outdated` on - those packages) to signal when re-validation + data regeneration is due. -- dom-info has no README yet; document the data sources + validation - workflow when the package is readied for standalone publish. + `packages/thoth/dom-info/**` or bump the relevant deps (property-information, + html/svg-element-attributes, html/svg/mathml-tag-names, playwright) — rather + than every push. +- A "deps current?" check (renovate, or scheduled `pnpm outdated` on those + packages) to signal when re-validation + data regeneration is due. +- No README for the dom-info module yet; document the data sources + validation + workflow (now under thoth). ## External (not Azoth code)