Region System Follow-Ups
DL#201 — Region system follow-ups (backlog, for review)
Written for AI agents. See Log Methodology Note below for details.
Status: PROMOTED. All four items reviewed with the user and split into their own DLs:
- Item 1 → DL#202 (template-side
jay-contentmarker) — building. - Item 2 → DL#203 (CSS
@scope (…) to (…)donut) — building. - Item 3 → blog post
docs/blog/from-a-component-system-to-a-region-system.md— drafted. - Item 4 → DL#204 (design-system index in the agent-kit) — design ready.
This file is retained as the original holding pen (the background/options/prior-art for each item). The decided designs live in the promoted DLs above.
Original holding-pen contents follow. A todo/design holding pen for four follow-ups raised while reviewing DL#196 (validated inline composition) + DL#200 (prefer design-system elements). Each item is captured with its background, the decision to make, a tentative lean, and prior art.
Related: #196 (flatten / drift / sync), #200 (prefer design-system elements validation), #85 (plugins-index), #197 (why jay-stack is headless-only / regions over components).
Item 1 — Should content edits require an override marker? (likely: no)
Background. Today checkRegionDrift (DL#196, validate.ts) reports every unmarked facet that differs
from source — including a children facet that is only a text edit or inline-content enrichment. To silence
it the author must mark override="children". DL#200 then separately classifies overrides as content vs
non-content (the CONTENT_TAGS allowlist) for REGION-OVERRIDE-NON-CONTENT. So the two rules disagree on the
same edit: DL#200 says "editing text / enriching with <strong>/<img> is expected and fine," while DL#196
drift still nags for an override="children" marker on exactly that edit.
Decision to make. Should a drift that is purely content (text / src / alt, or net-new DOM made
only of CONTENT_TAGS — the same classifier DL#200 added) be accepted without an override marker — i.e.
not reported by checkRegionDrift at all?
Tentative lean: yes, don't require markers for content. Content edits are the whole point of flattening;
making them silent removes marker noise and aligns the two rules. But it changes sync semantics: today an
unmarked facet is re-flattened (overwritten) by sync; if content edits become "accepted + unmarked," sync
must preserve them instead — otherwise sync silently reverts the author's text. That is the crux: the
DL#196 invariant is "unmarked ⇒ sync overwrites." Breaking it only for content means sync needs a content
classifier too, or content edits need an implicit/auto override.
Options.
- (a) Keep current (content drift warns, needs
override="children"). Simplest; noisy. - (b) Suppress content drift in
checkRegionDriftand teachsyncto preserve content facets (reuseaddedElementTags+CONTENT_TAGSon both sides). Consistent, more work, must re-verify the sync invariant. - (c) Auto-mark: on
sync/first-fill, stampoverride="children"wherever content legitimately diverges. Keeps the invariant literally true but clutters markup.
Prior art / constraints. DL#196 §5 "sync = overwrite-with-holes, holes = marked facets"; the invariant
"validate-clean ⇔ sync-clean." addedElementTags + CONTENT_TAGS already exist (DL#200) and are the natural
classifier to share. Must trace one content edit through applyOverrides/fillRegions in materialise.ts
before committing — this is exactly the runtime path where the invariant could break.
Item 2 — Scope a region's CSS with @scope (.from) to (.to) to exclude child regions
Background. The materialiser wraps each region's CSS in @scope (.<ref>) (DL#196). A plain @scope (.ref)
matches all descendants — including the roots and interiors of nested <jay:X> child regions
flattened inside it. So a parent region's descendant selectors can bleed into a child region's DOM, which
breaks the "each region owns its styles" story and complicates coalescing.
Decision to make. Should region CSS use the CSS scoping limit (donut) form —
@scope (.<ref>) to (<child-region-anchors>) — so a region's styles stop at the boundary of each nested
child region?
Tentative lean: yes, if browser support is acceptable. to (…) is the purpose-built mechanism for "style
this subtree but not these inner islands." The to selector list would be the scope-anchor classes of the
region's direct child regions (we already stamp each region's ref as a class on its root — DL#196 §"the
ref is a real class"). This makes region isolation structural rather than relying on authors not writing
bleeding selectors.
Open sub-questions.
- Browser support / target baseline for
@scope … to. If insufficient, is a build-time transform (hoist/limit rewrite) viable, or do we fall back to:not()guards? toneeds the child region anchors known at materialise time — we have them (nested<jay:X>roots), but thetolist must be recomputed onsyncwhen nesting changes, andcheckRegionCssScopingmust learn the new canonical shape (it currently enforces@scope (.ref) { … }).- Interaction with coalescing (DL#196 §4): same-template regions coalesce into one selector-list block — the
tolist would differ per region if their child nesting differs, which could prevent coalescing. Need a rule for whentolists are compatible.
Prior art. materialise.ts + diff-css.ts already parse and emit @scope; scopeReadyCss rewrites root
selectors to :scope. The child-region anchors are the same classes Item-1/DL#196 rely on.
Item 3 — Blog post: "From a component system to a region system"
Background. DL#196/#197 landed a real thesis worth writing up publicly: regions (flatten a template into the consuming file, own the copy, validate/sync against source) vs. the classic component system (reference a component, pass props, pre-build override hooks).
Thesis / talking points (from the user). Both approaches solve the same two problems — logic encapsulation and a design system — but regions add:
- Locality of concerns — markup, its bindings, and its per-instance edits all live in the same file, not split across a component definition + N call sites.
- Flexibility without pre-planning — no need to pre-design override points and thread override props
through the component API for every tweak a consumer might want; the consumer edits the flattened copy
directly, and
validate/synckeep it honest against the source. - Trade-off to be honest about: page files grow and copies duplicate source — which is exactly what drift
(
validate) and re-flatten (sync) exist to manage.
Decision to make. Scope/outline, audience (framework authors vs. app devs), and how much to lean on #196's flatten/drift/sync diagrams. Deliverable is prose, not code — lowest risk; can be drafted anytime.
Item 4 — A region / design-system index in the agent-kit
Background. Agents choosing a <jay:X> to flatten have no catalog of which components ship templates
and what each template is for. The agent-kit already generates plugins-index.yaml (DL#85) listing plugin
contracts/actions/services, but it does not list templates.
Decision to make. Add a region / design-system index — either as new fields on the existing
plugins-index entries or a sibling index — that lists, for every headless component (local + plugin): its
contract, and each .jay-html template it ships (there can be several variants per contract — DL#200
Item: "two templates = two design-system elements").
Tentative design.
- Extend
PluginContractEntry(stack-server-build/lib/contract-materializer.ts:39) withtemplates?: { path: string; title?: string; description?: string }[], or emit a paralleldesign-system-index.yaml. - Use the template jay-html
<title>as the region's description so an agent can pick the right variant ("Compact card" vs. "Feature card"). (If<title>is empty, fall back to the contract description.) - Cover local components (sibling
.jay-htmlnext to the contract — the DL#200 availability heuristichasTemplateForContractFile) and plugin-advertised templates. - Feeds both the designer agent-kit (which template to flatten) and DL#200's
REGION-NOT-LINKEDsuggestion (name the exacttemplate=path instead of a placeholder).
Prior art. run-agent-kit.ts (listContracts, PluginsIndex printing); contract-materializer.ts
(PluginsIndexEntry / PluginContractEntry); DL#200's availability check already locates sibling templates —
this index is the generalized, catalogued form of that same lookup.
Suggested next step
Review each item; promote Items 1, 2, 4 to their own DLs (they touch runtime/compiler/index generation and
need the usual prior-art + runtime-trace rigor). Item 3 is a writing task, not a design. Items 1 and 2 both
touch materialise.ts/sync invariants — trace them through the real runtime before committing.
Log Methodology Note
Note: These design logs are written primarily for AI agents as part of the Design Log methodology and made accessible here for human readers. The language and structure are optimized for machine consumption — expect precise, specification-style prose rather than narrative documentation.