Builder demo library: 322 example pages/sections + 14 behaviour suites (schemas + actions + controls that run) β /builder/demos - #578
Conversation
β¦component-block pages A code-generated catalog (schemas/webpageDemos.ts) builds a few hundred example webpages from family Γ layout Γ tone tables β 28 section families, 20 full-page recipes, and component-block compositions β every one of which clears validateThingtimeCrystal(['webpage']) unchanged (asserted for the whole catalog in the unit test, with headroom under the block/byte caps). - GET /api/v1/webpages/demos (public, rate-limited) lists the catalog with families, counts, and a per-deployment seeded flag; slug= returns one demo's crystal for cloning through the ordinary /things write path. - POST /api/v1/admin/webpages/seed-demos upserts every demo as a system-owned public webpage thing (shareId webpage-demo-<slug>) through the same reconciling, genuineness-fenced upsert the site-page seed uses (now shared); the seed census gains siteSeeded/demosSeeded/demosTotal (contract 1.1.0). - /builder/demos gallery: paints the catalog instantly, kind/family/search filters synced to the URL, lazily-mounted scaled live thumbnails, Show more pagination, full-size preview modal, "Use template" cloning into the viewer's own page, /p/ + builder links once seeded, admin seed button; the Builder header links to it and the route joins the site-page seed table. - API docs, capability manifest, apiTests (webpages group), and TESTING.md cover the new endpoints. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Row containers flex their children, so a ghost pill like "Read the docs" wrapped onto two lines in the hero cta row β the button helper now sets white-space: nowrap. The gallery card meta counted root blocks (always 1 for a wrapped section); it now reports the whole tree via countDemoBlocks. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Browsers throttle IntersectionObserver callbacks in hidden documents, so a
gallery opened in a background tab painted empty cards until it was
fronted β the thumbnail now also mounts on a 1.2s fallback timer (the page
is paginated, so that is at most 36 cheap renders). Pricing amounts in the
narrow three-tier columns wrapped mid-word ("Cust om"); they are nowrap now.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
β¦s that run Data Thing + Component Thing + Action Thing = program. Each suite (schemas/behaviourSuites.ts) bundles the shapes, ttAction-bound controls, declarative programs, sample data, and a builder page around one story β guestbook, rsvp, poll, tasks, leads, inventory, feedback, tickets, habits, orders, bookmarks, check-in, expenses, hiring β and exercises the whole vocabulary: things.create with $now, things.search, things.get + things.update by id, actions.invoke chains consuming the parent budget, $step refs and ttConcat composition, enum/number inputs coerced from control args. One definition materialises two ways, because of who may run what: - system: the admin seed writes public copies (schema-demo-*, component-demo-*, action-demo-*, data-demo-*, webpage-demo-suite-*) through the site seed's reconciling upsert, now kind-aware. Every part is browsable on its kind's page, and a signed-in viewer runs a seeded action from /actions (it mints THEIR data things against the public schema id). - own: "Install suite" clones the bundle into the viewer's things via /things in dependency order (schemas β controls β actions β data stamped with the new schema ids β page), schemas referenced by name and children by actionKey, so a ttAction click on their page resolves owner-only and runs their own program end to end β zero dependence on the seed. The unit test validates all 250 materialised crystals (both modes) against their kind gates and checks that controls bind only their suite's actions. GET /api/v1/webpages/demos lists suites (contract 1.1.0) and suite=<key> returns the installable bundle; the gallery gains a π§ͺ Suites tab with catalog-rendered previews (controls drawn from the suite's own component crystals, no seed needed), Install, and /p/ + /actions links once seeded. Header/footer link labels no longer wrap mid-word. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
β¦ + 14 behaviour suites (schemas + actions + controls that run) β /buildβ¦
|
π€ Lopu review β Checks are green on this exact head (25 pass / 56 skipped / 0 failing β including Nice work. Generating the catalog as code and validating every materialised crystal through the real write gate β in both I paid particular attention to the surface this PR opens: it seeds public One real bug, fixed in the worktree
Against the real catalogs, a fully seeded deployment reports: Both admin census endpoints return this object, and demosSeeded: await seededCount('demo', 'suite'),The gallery was never affected β Nothing covered the census, so I added I also corrected some stale integers in the new Two notes, your call β I left the code alone
Also verified322 demos (274 section / 40 page / 8 component) + 14 suites / 125 parts, with zero shareId, uniqueKey, or demoβsuite page-id collisions; every suite data entry resolves to a declared schema key; no
Nothing blocking left. π§±β¨ |
Lopu repository reviewLopu reviewed this PR against develop as Thingtime's principal PR and repository manager. Using Claude Opus 5. Lopu found no justified local change to publish from this review pass. PR #578 β Builder demo library: 322 example pages/sections + 14 behaviour suitesCompared: Checks at review time: no failures. What I reviewedThe v2 action vocabulary in FindingsI found no correctness or security defect worth changing. The parts most
Validation(First pass reported 5 failures, all Observations (no change made)
No changes were needed; I made none. |
π€ Lopu review β approve; base retargeted
|
β¦ + 14 behaviour suites (schemas + actions + controls that run) β /buildβ¦
π€ Lopu review β
|
| ref | exists? | real key |
|---|---|---|
thingtime-button-solid |
β | β |
thingtime-button-outline |
β | β |
thingtime-card |
β | thingtime-card-basic |
thingtime-badge |
β | thingtime-badge-solid |
thingtime-input |
β | thingtime-input-text |
thingtime-avatar |
β | thingtime-avatar-status |
The comment on that constant says an unresolved ref "renders as edit-chrome placeholders" β it doesn't. ComponentBlockView returns null when there's no chrome, so for a viewer an unresolved ref draws nothing:
if (!component || !crystal?.render) {
// live viewers see nothing β the "not found" card is edit chrome
if (!chrome) return null;So on a seeded deployment /p/webpage-demo-components-cards-{paper,ink} would have shown a heading and no cards at all, and the form/mixed demos would have lost their inputs, badge and avatar β 6 of the 8 component-block demos visibly broken.
Fixed: pointed the refs at the six real componentKeys (all confirmed public as component-<key>), corrected the avatar block's args (thingtime-avatar-status takes initials, not name β small monogram() helper), and rewrote the comment to say what actually happens. Slugs are unchanged, so the seed just refreshes the eight docs in place. Added a unit test that pins every component block to COMPONENT_DEMO_REFS and rejects a family stem β verified load-bearing (reverting one key fails it with 'thingtime-card looks like a family stem, not a seeded componentKey').
2. π The gallery never resolved component refs at all
DemoCard passed no componentsByRef to Thumb, and previewComponents was {} for demo previews. Suite cards already solve this with suiteComponentsByRef, but library components live in the DB, not in code β so the whole π§© Component blocks chip previewed as empty frames, thumbnail and modal, even for the two keys that did resolve.
Fixed in the idiom already here: exported resolveBlockComponents(viewer, blocks) from webpages.ts (the existing batched resolver, for a block list that isn't a stored page), returned components[] + refs from GET /api/v1/webpages/demos, and folded them client-side through the existing buildComponentsByRef helper. Resolved anonymously on purpose so the endpoint keeps its documented "nothing here is per-viewer" contract β one extra bounded query (β€48 docs), after validation. New webpages-demos-library-components apiTest asserts resolution is all-or-nothing: where the library is seeded every ref must resolve (so a future dangling key fails), and on a fresh CI DB none do, which passes.
3. Typecheck ratchet was +1 over baseline
The PR head alone measures 109 vs baseline 108. The new error is in this PR's own test: assert.equal(validated.ok, true, β¦) from node:assert/strict narrows via asserts actual is T, which makes the following if (validated.ok === false) continue; provably dead (TS2367). Swapped to assert.ok(validated.ok, β¦), which narrows the union and makes the guard unnecessary. Back at baseline (108).
4. Two react-hooks/rules-of-hooks errors
const useTemplate = async (demo) => β¦ is a plain function, but the use prefix makes ESLint treat it as a hook, so both call sites errored. Renamed to copyTemplate β the button still reads "Use template β¨". Lint isn't wired into CI today, so this was latent rather than failing.
Left for you to call
- The seed is 447 sequential documents in one admin POST β a converged re-run costs ~894 Mongo round trips (447 upserts + 447
findOne). It's idempotent and self-healing so a serverless timeout is just "press Seed again", but there's nomaxDurationinvercel.json, and abulkWritebatch would collapse this to a handful of round trips if a deployment ever does time out. - Response size β unfiltered
GET /webpages/demoswas ~141 KB, ~156 KB with the component crystals, on a public 120/min limit, while the gallery only consumes the seeded flags and the components. A seeded-slugs-only projection would cut ~90%. Left alone: "one endpoint, whole catalog" is the documented contract and the apiTests assertdemos.length === total. Thumb's 1.2 s fallback timer mounts every card regardless of viewport, defeating the IntersectionObserver for off-screen cards β bounded byPAGE_SIZEtoday but it grows with each "Show more". Gating it ondocument.visibilityState === 'hidden'keeps the hidden-document guarantee without the blanket mount.- Seeded suite pages are look-but-don't-touch β clicking a control on
/p/webpage-demo-suite-<key>404s for anyone who hasn't installed. That's the right posture (delegated ttAction resolution is owner-only) but the page gives no hint; a line of copy pointing at Install suite would close the loop. contractVersion: '1.1.0'on the brand-newwebpages-demosendpoint β a new capability normally starts at1.0.0. Nothing consumes it yet, so harmless; left as your choice. (admin-webpages-seed's 1.0.0 β 1.1.0 is correctly additive.)- The behaviourSuites test validates
materialized.data[].crystal, but seed and install both write it withschemaIdstamped on top. I checked the real seed-shaped crystal for all 14 suites against thedatagate β all pass, and everydata.schemanames a declared suite schema β so no live defect, but worth folding into the test.
Validation
Installed the worktree's deps and ran, after the changes: test:schemas 120 β, test:webpages 29 β, test:api-capabilities 4 β, test:components 16 β, test:things 24 β, typecheck ratchet at baseline (108), eslint clean on all 8 touched files, vite build β with the catalog still absent from the entry chunk (builder-demos chunk 119 809 B). Also confirmed out-of-band that all six replacement componentKeys resolve to public component-<key> system things β the exact arm resolveComponents matches for an anonymous viewer.
Nice work. With the component refs pointing at real keys and the gallery actually resolving them, the π§© category finally shows what it's advertising.
β Lopu, Thingtime's PR and repository steward
π§Ή Develop S3 preview removedThe PR-specific alias and every workflow-created develop deployment were removed when this PR closed. The ordinary generated Vercel Preview remains available on the shared development runtime. |
β¦ thumb fallback The seed upserted ~450 documents one at a time β two Mongo round trips each, which at serverless-to-Atlas latency is a multi-minute request a function timeout would cut short. It now reads every destination id in one query and writes one unordered bulkWrite (inserts for missing docs, genuineness-filtered updates for drifted ones), keeping the same created/refreshed/unchanged/skipped accounting: a refresh whose filter matched nothing lost a race to a foreign doc and counts as skipped, a duplicate-key insert lost a race to a concurrent seed and counts as unchanged. Locally 447 docs converge in 0.18s (was 3.2s). Suite pages carry a visible callout that their controls run the viewer's OWN programs and point at Install suite, since a seeded copy is look-but-don't-touch (delegated ttAction resolution is owner-only). The thumbnail fallback timer now arms only while the document is hidden, so a visible gallery keeps lazy mounting for off-screen cards. The new webpages-demos endpoint starts at contract 1.0.0. The suites test now validates the schemaId-stamped data crystal the seed and installer actually write. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Follow-up to the review's "left for you to call" list β pushed as
Checks: |
β¦ layout sweep to zero
Thumbnails: the gallery canvas is laid out at 760px and scaled to the card;
a fixed 0.42 clipped the right edge in any grid column under 319px. The
scale now follows the measured card width (ResizeObserver).
Buttons do something. Text blocks gain an optional `href`: the registry
gate screens it like a media src plus mailto:/tel: (javascript:, data:,
plain http and protocol-relative refused), the renderer wraps the styled
text in an anchor (external targets drop the opener, the edit canvas never
navigates), the inspector gets a Link field, and every demo pill links
(solid β /register, ghost β /docs) with nav labels linking to their slugs.
Suite controls run or install. useTtActionClicks gains onUnowned: when the
executor refuses a delegated click because the viewer owns no such action,
a demo surface may install the suite and answer true, and the SAME click
re-runs (pure runDelegatedAction, unit-tested; owner-only resolution and
every other failure path untouched). Seeded suite pages β detected by the
reserved webpage-demo-suite- id, since system docs project no author β are
live for signed-in viewers, as is the gallery preview modal; both use one
shared installSuite helper with the Install button.
Layout sweep across all 322 demos and 14 suites, driven by a DOM audit
(wrapped pills by text-node line count, overflow by scrollWidth, uncentered
wide pills): Main's `* { white-space: pre-wrap }` beat block css on the
text element, so the renderer forwards white-space to the text (important);
the row model flexes every child equally, so pills and row labels opt out
with flex: 0 0 auto; standalone pills shrink-wrap via align (pricing-card
buttons are intentionally full-width with centered labels). 86 flagged
cards β 0.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Round 2 β thumbnails, working buttons, layout sweepThumbnails: the gallery canvas is laid out at 760px and scaled to the card; the scale was fixed (0.42), so any grid column narrower than 319px clipped the right edge. It now follows the measured card width ( Buttons do something (builder/actions architecture, additive):
Layout sweep (all 322 demos + 14 suites): root causes were (1) Verification: Coordination: the pokeworld session (Pokeworld + StarsAlign app suites on this PR) owns the executor + action grammar; nothing here touches those. It will merge this push and build on |
β¦en, filtered search + astro pack
Apps-on-Thingtime groundwork for the Pokeworld + StarsAlign suites:
- registry: closed step vocabulary gains `compute`, `things.delete`
(new `things.delete` capability), `each` (child action per element,
`$item`/`$index` bound, β€20), `fail` (authored refusal) and a per-step
`when` guard (falsy skips; a guarded `return` is an early exit). Step
values accept `{ ttExpr: [fn, β¦args] }` over the closed function
catalogue in schemas/actionExpressions.ts (math, logic, text, list with
lambdas, object, date, random, domain packs). `things.search` gains
scope own|public (public requires a schema), where/match/sort/offset.
New refs `$viewer.id|username`. Caps: 40 steps, 100 ops ceiling.
- executor: implements the above; expression budget shared per run; pack
calls count as ops; seeded system schemas resolve by name.
- actionInspect: describes the new ops; "No deletes" only when the program
declares no things.delete.
- componentTemplate: nested scope with dotted-path tokens, ttEach (per
element, β€160), ttIf ops, ttFormat; HtmlThingRenderer allowlists named
form fields + svg text/g and renders fields uncontrolled.
- webpages: `/p/<pageKey>` resolves the viewer's own twin ahead of the
system page; component blocks accept a `source` action binding; pages
carry `suiteKey`.
- behaviourSuites: multi-page `pages` + `app` metadata, APP_SUITES registry.
- packs/astro: the StarsAlign ephemeris/engine/school ported server-side
(astronomy-engine), 47 tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
β¦o claude/pokeworld-starsalign-thingtime-40cd5f
An authored grid column count is the desktop layout: a 3β4 column section on a phone squeezed every cell to a few characters wide. The renderer now folds grids to one column below the sm breakpoint and at most two below md, keeping the authored count from md up. Suite pages lay their controls out as a wrapping row (each at least 300px) instead of a two-column grid, so they stack on narrow screens. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 11:07 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 12:12 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 13:08 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 13:59 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
|
π€ Lopu is rebasing this stack β work started at 15:23 UTC. Expected to finish around 15:38 UTC (most rebases still land in 5-15 minutes; the hosted-runner ceiling is 360). On success this branch is force-pushed onto its new base and a result comment follows β no manual action is needed meanwhile. |
|
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 06:41 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 07:21 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 15:09 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
|
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 02:44 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
π€ Lopu detected merge conflictsStatus: Work detected β Lopu is taking ownership. Current phase: Entering Lopu's serialized PR-resolution queue. Estimated completion: around 03:31 UTC (~20 minutes; this adjusts as the queue moves). Next automatic check-in: within 10 minutes while work remains active. You can stay on this PR; there is no need to find the Actions run. Time conversion (UTC source)
Los Angeles and Melbourne use their real IANA time zones, so PDT/PST and AEST/AEDT offsets change automatically. Lopu queue and PR pulse
Related PR context
Exact branch pair: Timeline
|
β Develop S3 preview ready
e87f9ff3developThe alias passed the develop bucket CORS preflight and a final live PR/SHA fence.
Generic Vercel Preview deployments use the shared development runtime; this controller adds the stable exact-SHA alias and marker-scoped cleanup.
Builder demo library β 322 example pages/sections + 14 behaviour suites (schemas + actions + controls that run)
A code-generated, deterministic demo library, browsable at /builder/demos, seedable as system things, and cloneable/installable into any viewer's own things through the ordinary things write path.
Part 1 β webpage demos (
remix/app/schemas/webpageDemos.ts, isomorphic, pure)validateThingtimeCrystal(['webpage'])unchanged (unit-tested for the whole catalog, with block/byte headroom, slug grammar, family coverage, determinism).Part 2 β behaviour suites (
remix/app/schemas/behaviourSuites.ts)Data Thing + Component Thing + Action Thing = program. Each of the 14 suites bundles schemas, ttAction-bound controls, declarative actions, sample data, and a builder page around one story β guestbook, rsvp, poll, tasks, leads, inventory, feedback, tickets, habits, orders, bookmarks, check-in, expenses, hiring β and together they exercise the whole action vocabulary:
things.createwith$now,things.search,things.get+things.updateby id,actions.invokechains (parent budget),$steprefs,ttConcatcomposition, enum/number inputs coerced from control args.One definition materialises two ways, because of who may run what:
schema-demo-*,component-demo-*,action-demo-*,data-demo-*,webpage-demo-suite-*) through the site seed's reconciling upsert (now kind-aware). Every part is browsable on/schemas,/components,/actions,/p/; a signed-in viewer runs a seeded action from/actions(it mints their data things against the public schema id)./thingsin dependency order (schemas β controls β actions β data stamped with the new schema ids β page), schemas referenced by name and child actions by actionKey, so a ttAction click on their page resolves owner-only (source: 'component') and runs their own program end to end β with zero dependence on the seed.The unit test validates all 250 materialised crystals (both modes) against their kind gates and checks that every control binds only its own suite's actions.
API
GET /api/v1/webpages/demos(public,webpages.demosrate limit, contract 1.1.0): catalog + families + per-deploymentseededflags;family=/kind=filters;slug=adds a demo's crystal;suites[]always;suite=<key>adds the installable own-mode bundle. 400/404 on bad shapes/unknown keys.POST /api/v1/admin/webpages/seed-demos(+ GET census): seeds every demo page and every suite part; the census gainssiteSeeded/demosSeeded/demosTotal/suitesSeeded/suitesTotal;admin-webpages-seedbumps to 1.1.0 (additive).webpages-group apiTests.Gallery (
/builder/demos)/p/+β‘ /actionslinks once seeded; admins get a one-tap Seed button. Builder header links to the library;/builder/demosjoins the site-page seed table.Verification
test:schemas119 β (webpageDemos 6/6 + behaviourSuites 4/4),test:api-capabilities4/4 β,test:webpages29 β, Vitebuild:clientβ.apiTests --group webpages18/18 β against the worktree dev stack./p/and builder canvas, suites tab β desktop + mobile, no horizontal overflow.Post-merge
Run
POST /api/v1/admin/webpages/seed-demosonce per deployment (or press Seed on /builder/demos as an admin) so every demo opens at/p/webpage-demo-<slug>and every suite part is browsable.π€ Generated with Claude Code