Compound software
The compound-software thesis — the catalog grows without breaking compositions
Atelier's commercial bet is that adding a building block never breaks an existing composition. Three independent registries make this concrete, and all three are live:
Any entity renders in any widget. The surface plane compiles a
page_templateinto an ordered list of declarativeBlockdescriptors, each dispatched to aWidgetKindby a tester/priority resolveWidget registry inside ONE presentation-only renderer. A new widget is a new registry entry with a tester; it cannot break existing pages because dispatch is by priority over known-good keys (invariant TH-08: only known-good registry-key literals reach the DOM). Entities and widgets are decoupled by theBindingbetween them.Any action fires from any surface. An
action_placementrow projects anaction_typeonto a surface as a button or form. Read-time availability reuses the exact same gate criteria the submit lane enforces, so a CTA shown is an action permitted — they cannot drift. A new placement adds a row; the action and its gates are unchanged.Any aggregate is a view; any view is a surface. Aggregates are ontology VIEWS (the only sanctioned aggregate primitive), surfaced either as Metabase cards (staff) or
source_kind:viewpublic feeds (citizen). A new KPI is a view row plus a surface row — never a bespoke endpoint.
Why the catalog can grow safely: every primitive is declared as a row class with a registry key, and the seam between authoring and rendering is Block[] + registry keys — not bespoke components. The design-system stays presentation-only; editing/authoring metadata lives in a separate host layer keyed to the same render registry (the SOTA pattern — Puck/Builder/Sanity). This is the structural reason a tenant can fork the catalog and extend its copy without the platform author's future additions colliding: additions are additive registry entries and additive rows, governed by additive-only schema evolution (the ontology never deletes a column).
Where the thesis is not yet fully true (named honestly): the admin host drives the converged renderer only for aggregate charts today — KPI cards and Metabase embeds are still bespoke, so "one renderer drives admin" is charts-only, not total. Block.children grid nesting is reserved, not shipped. And there is no single enumerable widget catalog yet (three overlapping lists: _entries, WIDGET_ENTRIES, AGGREGATE_CHART_CANDIDATES). These are convergence debt, not design breaks.