@adia-ai/adia-ui-forge 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +16 -2
- package/agents/framework-verifier.md +13 -5
- package/bin/release-pretag-docs-gate +226 -0
- package/hooks/hooks.json +10 -0
- package/package.json +1 -1
- package/skills/adia-a2ui/SKILL.md +10 -2
- package/skills/adia-a2ui/references/pipeline-overview.md +1 -1
- package/skills/adia-author/SKILL.md +1 -0
- package/skills/adia-author/references/anti-patterns.md +7 -7
- package/skills/adia-author/references/api-contract.md +9 -9
- package/skills/adia-author/references/authoring-cycle.md +6 -6
- package/skills/adia-author/references/code-style.md +4 -4
- package/skills/adia-author/references/lifecycle-patterns.md +8 -8
- package/skills/adia-author/references/token-contract.md +5 -5
- package/skills/adia-author/references/worked-example.md +16 -16
- package/skills/adia-author/references/yaml-contract.md +1 -1
- package/skills/adia-deploy/SKILL.md +44 -0
- package/skills/adia-deploy/evals/routing-corpus.json +146 -0
- package/skills/adia-deploy/references/deploy-playbooks.md +1 -1
- package/skills/adia-dogfood/SKILL.md +64 -11
- package/skills/adia-dogfood/evals/routing-corpus.json +146 -0
- package/skills/adia-gen-review/SKILL.md +55 -0
- package/skills/adia-gen-review/evals/routing-corpus.json +146 -0
- package/skills/adia-llm-internals/evals/routing-corpus.json +146 -0
- package/skills/adia-release/SKILL.md +12 -5
- package/skills/adia-release/evals/evals.json +24 -0
- package/skills/adia-release/references/changelog-discipline.md +4 -4
- package/skills/adia-release/references/cut-procedure.md +45 -11
- package/skills/adia-release/references/gates-catalog.md +3 -3
- package/skills/adia-release/references/recovery-paths.md +4 -2
- package/skills/adia-release/scripts/bump.mjs +32 -1
- package/skills/adia-release/scripts/dispatch-publish.mjs +49 -11
- package/skills/adia-release/scripts/gate-roster.mjs +71 -0
- package/skills/adia-release/scripts/insert-stub.mjs +35 -1
- package/skills/adia-release/scripts/promote-unreleased.mjs +35 -1
- package/skills/adia-release/scripts/release-pack.mjs +284 -53
- package/skills/adia-release/scripts/tag-lockstep.mjs +29 -7
- package/skills/adia-ssr/SKILL.md +110 -0
- package/skills/adia-ssr/evals/audit-report.md +63 -0
- package/skills/adia-ssr/evals/routing-corpus.json +138 -0
- package/skills/adia-ssr/references/consumer-workarounds.md +63 -0
- package/skills/adia-ssr/references/failure-shapes.md +122 -0
- package/skills/adia-ssr/references/guard-patterns.md +142 -0
- package/skills/adia-ssr/references/status-ledger.md +35 -0
- package/skills/adia-ssr/references/test-without-linkedom.md +113 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# adia-ssr skill audit (FLOOR)
|
|
2
|
+
|
|
3
|
+
Skill: `packages/plugins/adia-ui-forge/skills/adia-ssr/` · Standards: skill-authoring-standards · Lint: clean
|
|
4
|
+
Verdict: **PASS**
|
|
5
|
+
|
|
6
|
+
Species: knowledge (`disable-model-invocation: false`, `user-invocable: false`) — sibling in
|
|
7
|
+
shape to `packages/plugins/adia-ui-factory/skills/adia-tokens/`, confirmed.
|
|
8
|
+
|
|
9
|
+
| ID | Verdict | Severity | Evidence (file:line) | Fix |
|
|
10
|
+
|----|---------|----------|----------------------|-----|
|
|
11
|
+
| R1 Behavior delta | PASS | — | `SKILL.md:67-73` (caveat: don't invent a component-local workaround for gh#284 — deleting it removes the one line steering Claude away from a plausible wrong answer); `SKILL.md:36-38` (gh#288 dependency on gh#284 — deleting it, Claude could treat #288 as independently actionable); `SKILL.md:51` (test-without-linkedom consult row — deleting it loses the exact routing signal to the one file that explains why the repo's default test env can't reproduce SSR bugs) | none |
|
|
12
|
+
| R2 Trigger fidelity | PASS | — | Description (`SKILL.md:3-10`) phrasings "does X work under SSR", "why does X crash/disappear when server-rendered", "is this connect-time read safe" are the literal `should_route_to_ssr: true` phrases in `evals/routing-corpus.json:20,35,90`; fences ("NOT for implementing a fix (adia-author) or consumer host/hydration wiring (adia-host...)") correctly repel `ssr-adversarial-01/02/05` (routing-corpus.json:104-107, 110-114, 130-135) | none |
|
|
13
|
+
| R3 Species/dial agreement | PASS | — | `SKILL.md:11-12` both dials explicit and correct for knowledge species; name `adia-ssr` follows the established local `adia-*` topic-noun convention (matches sibling `adia-tokens`, `adia-a2ui`) rather than the generic `x-patterns` grammar — acceptable under the project's own precedent | none |
|
|
14
|
+
| R4 Register | PASS | — | Load-bearing lines cite exact file:line, PR, and issue numbers rather than describing generically, e.g. `SKILL.md:63-66` ("`UIElement.connectedCallback`... calls `stamp(result, this)`... currently line 194") — verified accurate, see Notes | none |
|
|
15
|
+
| R5 No restatement | PASS | — | No drift-pair found against `component-token-contract.md` / `component-implementation-patterns.md` / AGENTS.md — none cover SSR. Pack explicitly fences out generic web-components-SSR theory (`SKILL.md:97-99`), so it doesn't restate model-general knowledge either | none |
|
|
16
|
+
| R6 Position | MINOR | minor | Body is only ~108 lines (~2,000 tokens, well inside the 5,000-token survival window, so no practical compaction risk) — but structurally, "Deviation doctrine" and "Boundaries" (`SKILL.md:75-99`, gate-shaped content) sit *after* the "Worked example" (`SKILL.md:55-74`), inverted from the "contracts/gates first, examples last" rule | Move Deviation doctrine + Boundaries above the Worked example on next semantic edit; not urgent given total size |
|
|
17
|
+
| R8 Quantities | PASS | — | `127+ primitives`, `24 component/trait/module files`, `12 files` calling `this.internals.*`, `~110-line shim`, dated `2026-07-17`, gh#284-288 — all load-bearing counts are numeric, none vague | none |
|
|
18
|
+
|
|
19
|
+
R7 (Contracts): N/A — knowledge species, not procedural/command.
|
|
20
|
+
|
|
21
|
+
## Notes — factual grounding (beyond the R1-R8 checklist, but relevant to a knowledge pack's core promise)
|
|
22
|
+
|
|
23
|
+
Spot-checked every concrete claim against the actual repo state rather than trusting the pack's
|
|
24
|
+
own self-description:
|
|
25
|
+
|
|
26
|
+
- `packages/web-components/core/template.js:194` — `export function stamp(result, container)` is
|
|
27
|
+
exactly where `failure-shapes.md` and the Worked example say it is.
|
|
28
|
+
- `packages/web-components/core/element.js` — `attachInternals` shim (`this.internals = typeof
|
|
29
|
+
this.attachInternals === 'function' ? this.attachInternals() : NOOP_INTERNALS`),
|
|
30
|
+
`adoptedStyleSheets` guard (`if (!('adoptedStyleSheets' in document)) return;`), and the
|
|
31
|
+
`connectedCallback` → `stamp()` call all match `guard-patterns.md` and `failure-shapes.md`
|
|
32
|
+
verbatim.
|
|
33
|
+
- `packages/web-modules/shell/admin-sidebar/admin-sidebar.js` — `#syncCollapsedFromWidth` (zero-rect
|
|
34
|
+
`return` before the decision) and `#setupChildResizeObserver` (`typeof ResizeObserver ===
|
|
35
|
+
'undefined'` guard, deferred correction) match `guard-patterns.md` §3's cited code exactly,
|
|
36
|
+
including `SNAP_THRESHOLD`.
|
|
37
|
+
- `packages/web-components/core/element.test.js:259` — the `describe('UIElement — SSR
|
|
38
|
+
browser-API absence (gh#285)'` block matches `test-without-linkedom.md`'s cited delete/try/finally
|
|
39
|
+
pattern.
|
|
40
|
+
- `git log`: PR #292 (gh#285) and PR #290 (gh#286) both merged 2026-07-17, matching every date
|
|
41
|
+
cited in `status-ledger.md` and `consumer-workarounds.md`.
|
|
42
|
+
- No `linkedom` in any `package.json` in the tree — confirms `test-without-linkedom.md`'s central
|
|
43
|
+
claim.
|
|
44
|
+
- Live `gh issue view` for 284-288 today: #284 OPEN, #285 CLOSED, #286 CLOSED, #287 OPEN, #288
|
|
45
|
+
OPEN — matches `status-ledger.md`'s table exactly, row for row, including the "#287 is
|
|
46
|
+
unrelated" framing.
|
|
47
|
+
|
|
48
|
+
Every citable fact in this pack checked out. This is unusually high grounding for a hand-authored
|
|
49
|
+
knowledge pack — worth naming as the standard other `adia-*` knowledge skills should be held to.
|
|
50
|
+
|
|
51
|
+
## Top 3
|
|
52
|
+
|
|
53
|
+
1. **Ship as-is.** Zero blocking or major findings; lint clean; every code/issue citation
|
|
54
|
+
independently verified against the live repo and `gh issue view` — the pack's core promise
|
|
55
|
+
("cited to the actual shipped/open code") is actually true today, not just claimed.
|
|
56
|
+
2. **Routing corpus is well-formed and matches its own spec** — 12 trigger / 5 adversarial phrases
|
|
57
|
+
(29.4% adversarial, spec says ~29%), all 5 reference files covered by `expected_shape`, and the
|
|
58
|
+
3 adversarial near-misses (gh#287 bundler bug, a2ui pipeline, generic hydration theory) are
|
|
59
|
+
genuinely hard boundary cases, not straw men.
|
|
60
|
+
3. **One minor, non-blocking structural nit (R6):** the gate-shaped "Deviation doctrine" and
|
|
61
|
+
"Boundaries" sections trail the "Worked example" instead of leading it. Harmless at this body
|
|
62
|
+
size (~2K tokens, nowhere near the 5K compaction cutoff) — fix opportunistically on the next
|
|
63
|
+
semantic edit to this file, not worth a dedicated pass.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"name": "adia-ssr routing accuracy corpus",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"purpose": "Routing-eval corpus for adia-ssr (adia-forge plugin). Each phrase declares whether adia-ssr SHOULD be the routing target. Scored by the estate's TF-IDF routing-eval runner against the skill's description (heuristic token overlap).",
|
|
6
|
+
"scoring_notes": "Heuristic signal, not ground truth. Treat misroutes as a prompt to tighten the skill description, never as a reason to keyword-stuff it. Real harness routing is LLM-driven.",
|
|
7
|
+
"license": "internal",
|
|
8
|
+
"scope": "adia-ssr routing — does this phrase activate adia-ssr?",
|
|
9
|
+
|
|
10
|
+
"minimums_per_spec": {
|
|
11
|
+
"trigger_phrases": 12,
|
|
12
|
+
"adversarial_phrases": 5,
|
|
13
|
+
"task_shapes_covered": "failure-shapes, guard-patterns, status-ledger, test-without-linkedom, consumer-workarounds",
|
|
14
|
+
"adversarial_fraction": "~29%"
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
"phrases": [
|
|
18
|
+
{
|
|
19
|
+
"id": "ssr-trigger-01",
|
|
20
|
+
"phrase": "why does text-ui render empty when we server-render it with Astro",
|
|
21
|
+
"should_route_to_ssr": true,
|
|
22
|
+
"expected_shape": "failure-shapes",
|
|
23
|
+
"rationale": "Canonical shape-2 symptom (projected-text content loss) — the worked example in SKILL.md."
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "ssr-trigger-02",
|
|
27
|
+
"phrase": "our SSR pass crashes on attachInternals not being a function",
|
|
28
|
+
"should_route_to_ssr": true,
|
|
29
|
+
"expected_shape": "guard-patterns",
|
|
30
|
+
"rationale": "Directly names the highest-leverage shape-1 crash site the pack documents."
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "ssr-trigger-03",
|
|
34
|
+
"phrase": "does select-ui work under linkedom",
|
|
35
|
+
"should_route_to_ssr": true,
|
|
36
|
+
"expected_shape": "failure-shapes",
|
|
37
|
+
"rationale": "The pack's core orienting question — 'does X work under SSR'."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "ssr-trigger-04",
|
|
41
|
+
"phrase": "admin-sidebar snaps collapsed on every server-rendered page load",
|
|
42
|
+
"should_route_to_ssr": true,
|
|
43
|
+
"expected_shape": "failure-shapes",
|
|
44
|
+
"rationale": "Shape-3 symptom (connect-time measurement treated as confirmed) — the gh#286 precedent."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"id": "ssr-trigger-05",
|
|
48
|
+
"phrase": "is gh#284 fixed yet, can we drop the attribute-only SSR registration restriction",
|
|
49
|
+
"should_route_to_ssr": true,
|
|
50
|
+
"expected_shape": "status-ledger",
|
|
51
|
+
"rationale": "Direct status-ledger ask, names the issue and the exact consumer workaround."
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "ssr-trigger-06",
|
|
55
|
+
"phrase": "how do we test that a new ResizeObserver guard actually works without installing linkedom",
|
|
56
|
+
"should_route_to_ssr": true,
|
|
57
|
+
"expected_shape": "test-without-linkedom",
|
|
58
|
+
"rationale": "Canonical test-methodology ask this pack's most concrete axis exists for."
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "ssr-trigger-07",
|
|
62
|
+
"phrase": "new ResizeObserver() throws under our DOM-shim SSR environment",
|
|
63
|
+
"should_route_to_ssr": true,
|
|
64
|
+
"expected_shape": "guard-patterns",
|
|
65
|
+
"rationale": "Shape-1 crash, observer-construction variant — guard-patterns §1 shape (a)/(b)/(c)."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "ssr-trigger-08",
|
|
69
|
+
"phrase": "table-ui and chart-ui always render empty in our server-rendered HTML even though the data is set",
|
|
70
|
+
"should_route_to_ssr": true,
|
|
71
|
+
"expected_shape": "failure-shapes",
|
|
72
|
+
"rationale": "Shape-4 (property-only components, gh#288), explicitly named as blocked on shape 2."
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "ssr-trigger-09",
|
|
76
|
+
"phrase": "what's the actual root cause of the SSR content-loss bug, container CEs losing their nested children",
|
|
77
|
+
"should_route_to_ssr": true,
|
|
78
|
+
"expected_shape": "failure-shapes",
|
|
79
|
+
"rationale": "Shape-2 container-CE symptom, the other half of the worked example's shape."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "ssr-trigger-10",
|
|
83
|
+
"phrase": "can our app finally delete the 110-line browser-API shim now that 0.8.5 is out",
|
|
84
|
+
"should_route_to_ssr": true,
|
|
85
|
+
"expected_shape": "consumer-workarounds",
|
|
86
|
+
"rationale": "Direct ask about a named consumer workaround's deletability."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": "ssr-trigger-11",
|
|
90
|
+
"phrase": "is it safe to call getBoundingClientRect() synchronously in connectedCallback",
|
|
91
|
+
"should_route_to_ssr": true,
|
|
92
|
+
"expected_shape": "guard-patterns",
|
|
93
|
+
"rationale": "Shape-3 general-principle ask ('unknown vs confirmed'), independent of any specific component."
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "ssr-trigger-12",
|
|
97
|
+
"phrase": "document.adoptedStyleSheets is undefined and our component library crashes on import",
|
|
98
|
+
"should_route_to_ssr": true,
|
|
99
|
+
"expected_shape": "guard-patterns",
|
|
100
|
+
"rationale": "Shape-1 crash, the adoptStyles() call site specifically."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "ssr-adversarial-01",
|
|
104
|
+
"phrase": "add hydration-aware stamping so connectedCallback stops overwriting existing children",
|
|
105
|
+
"should_route_to_ssr": false,
|
|
106
|
+
"expected_alternative": "adia-author",
|
|
107
|
+
"rationale": "This is IMPLEMENTING the gh#284 fix, not asking whether/why something fails — routes to the builder, not this answers-only pack."
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": "ssr-adversarial-02",
|
|
111
|
+
"phrase": "set up Astro's rendering mode and hydration boundaries for our admin app",
|
|
112
|
+
"should_route_to_ssr": false,
|
|
113
|
+
"expected_alternative": "adia-host",
|
|
114
|
+
"rationale": "Consumer-side host/hydration wiring — explicitly fenced to adia-host in the boundaries section."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"id": "ssr-adversarial-03",
|
|
118
|
+
"phrase": "explain how custom element hydration works in general, independent of any specific framework",
|
|
119
|
+
"should_route_to_ssr": false,
|
|
120
|
+
"expected_alternative": "general knowledge, no pack",
|
|
121
|
+
"rationale": "Generic web-components/SSR theory not grounded in THIS repo's own code/issues — explicitly out of scope per the boundaries section."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"id": "ssr-adversarial-04",
|
|
125
|
+
"phrase": "the icon registry comes back empty because of a workspace-hoisting import.meta.glob path bug",
|
|
126
|
+
"should_route_to_ssr": false,
|
|
127
|
+
"expected_alternative": "adia-author (gh#287, a bundler/dep-optimizer bug, not one of the three SSR lifecycle shapes)",
|
|
128
|
+
"rationale": "gh#287 is explicitly named in status-ledger.md as unrelated to this pack's failure taxonomy — a near-miss adversarial by design."
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"id": "ssr-adversarial-05",
|
|
132
|
+
"phrase": "wire up the a2ui generation pipeline to run inside a server-rendered page",
|
|
133
|
+
"should_route_to_ssr": false,
|
|
134
|
+
"expected_alternative": "adia-a2ui",
|
|
135
|
+
"rationale": "A2UI runtime/pipeline concern, not the UIElement lifecycle this pack is scoped to — explicitly fenced in the boundaries section."
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# The consumer's current workarounds — what they look like, why they're fragile
|
|
2
|
+
|
|
3
|
+
Two real consumers — adiav2's `admin-portal-fe` and `factory-dashboard` — SSR AdiaUI
|
|
4
|
+
via Astro 5 + `custom-elements-ssr` (linkedom). Each open issue names a workaround
|
|
5
|
+
they currently maintain in their OWN app code (not in this framework). Knowing the
|
|
6
|
+
shape of each matters for two reasons: (1) it tells you what "done" looks like — the
|
|
7
|
+
workaround should become deletable once the underlying gh issue closes; (2) it's the
|
|
8
|
+
concrete evidence for WHY each open issue matters, beyond the abstract description.
|
|
9
|
+
|
|
10
|
+
## The ~110-line browser-API shim (gh#285 — now deletable, unconfirmed)
|
|
11
|
+
|
|
12
|
+
Before every component module import, the consumer's SSR entry point ran a
|
|
13
|
+
prototype-patching shim: stubbed `attachInternals`, defined four no-op Observer
|
|
14
|
+
classes (`ResizeObserver`/`IntersectionObserver`/`MutationObserver`/`PerformanceObserver`),
|
|
15
|
+
and force-assigned `document.adoptedStyleSheets = []`. Two named fragilities:
|
|
16
|
+
- **Fragile import order** — the shim MUST execute before any component module
|
|
17
|
+
loads, or the "deep-render pass" crashes anyway. This already broke once in a
|
|
18
|
+
production bundle (per the issue) — a bundler/tree-shaking change that reordered
|
|
19
|
+
imports silently reintroduced the crash.
|
|
20
|
+
- **A global patch, not scoped** — patching `HTMLElement.prototype` /
|
|
21
|
+
`globalThis.ResizeObserver` affects EVERY component and every other library in the
|
|
22
|
+
same process, not just AdiaUI's.
|
|
23
|
+
|
|
24
|
+
**Status as of gh#285's fix (PR #292, merged 2026-07-17): this shim should no longer
|
|
25
|
+
be necessary** — the framework guards its own call sites now. This pack's own routing
|
|
26
|
+
corpus / the issue thread is where confirmation from the consumer would land; don't
|
|
27
|
+
assume it's been removed without checking the issue's comment thread for that
|
|
28
|
+
confirmation, since a consumer removing a workaround is THEIR change, not something
|
|
29
|
+
this fix does automatically.
|
|
30
|
+
|
|
31
|
+
## Attribute-only SSR registration restriction (gh#284 — likely no longer necessary, unconfirmed)
|
|
32
|
+
|
|
33
|
+
The consumer restricts server-side custom-element registration to components whose
|
|
34
|
+
visible content derives ENTIRELY from attributes — `button-ui text="…"`, `icon-ui
|
|
35
|
+
name="…"`. Every container (`admin-shell`, `admin-sidebar`, `nav-ui`) and every
|
|
36
|
+
projected-text component (`text-ui`, `avatar-ui`, `badge-ui`, and by extension
|
|
37
|
+
anything using `<component>slotted text</component>` markup) must register
|
|
38
|
+
CLIENT-side only, upgrading after first paint.
|
|
39
|
+
|
|
40
|
+
**Status as of gh#284's narrowing (2026-07-17): this restriction may no longer be
|
|
41
|
+
needed** — every component it names (and every other shipped component with real
|
|
42
|
+
content) currently derives its visible content from properties/attributes only,
|
|
43
|
+
never from light-DOM children, so `stamp()`'s destructive replace never fires
|
|
44
|
+
against real content for any of them (see [`failure-shapes.md`](failure-shapes.md)
|
|
45
|
+
§2 for the empirical survey). This did NOT ship as a `stamp()` fix — it's a
|
|
46
|
+
narrowing based on the CURRENT shape of the component set, backed by a forward
|
|
47
|
+
static audit (`scripts/dev/audit-template-child-conflict.mjs`) that would catch a
|
|
48
|
+
future regression. As with gh#285's shim: don't assume the consumer has actually
|
|
49
|
+
relaxed this restriction without checking the issue thread for their confirmation
|
|
50
|
+
— a consumer removing their own workaround is their change, not something this
|
|
51
|
+
narrowing does automatically. If the consumer's linkedom/`custom-elements-ssr`
|
|
52
|
+
setup surfaces something this repo's own repro didn't (a real second-connect
|
|
53
|
+
timing difference, an app-level component not covered by this framework), that's
|
|
54
|
+
new information worth feeding back into the issue, not something to assume away.
|
|
55
|
+
|
|
56
|
+
## No workaround exists yet for gh#288 (property-only components)
|
|
57
|
+
|
|
58
|
+
The consumer's current behavior for `table-ui`/`chart-ui`/`select-ui` with
|
|
59
|
+
programmatic content is simply: render empty in the SSR response, then a per-page
|
|
60
|
+
wiring script does `customElements.whenDefined(...) → getElementById → assign
|
|
61
|
+
properties` after hydration. This is boilerplate the consumer repeats per page, not a
|
|
62
|
+
shim with a specific fragility of its own beyond "structurally excluded from SSR" —
|
|
63
|
+
see [`failure-shapes.md`](failure-shapes.md) §4.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# SSR failure shapes — symptom → root cause → status
|
|
2
|
+
|
|
3
|
+
Three distinct root-cause classes have surfaced from real SSR consumers (adiav2's
|
|
4
|
+
`admin-portal-fe` and `factory-dashboard`, server-rendering AdiaUI via Astro 5 +
|
|
5
|
+
`custom-elements-ssr`, which runs on linkedom — a DOM shim with no layout engine and
|
|
6
|
+
missing many browser APIs). A new SSR bug report almost always maps onto one of
|
|
7
|
+
these three; misclassifying it (e.g. treating a measurement-timing bug as a missing-API
|
|
8
|
+
bug) sends the fix to the wrong place. Check symptom against this table first.
|
|
9
|
+
|
|
10
|
+
## 1 · Browser-only API called unconditionally → crash
|
|
11
|
+
|
|
12
|
+
**Symptom:** the SSR pass throws — often at construction, before any component-specific
|
|
13
|
+
code runs. Stack trace points at `attachInternals`, `new ResizeObserver(...)`, `new
|
|
14
|
+
IntersectionObserver(...)`, `new MutationObserver(...)`, `new PerformanceObserver(...)`,
|
|
15
|
+
or a write to `document.adoptedStyleSheets`.
|
|
16
|
+
|
|
17
|
+
**Root cause:** the call site assumes the API exists. linkedom implements none of these —
|
|
18
|
+
not a partial/quirky implementation, an absence. Any unconditional call throws
|
|
19
|
+
`TypeError` or `ReferenceError` (undefined global) immediately.
|
|
20
|
+
|
|
21
|
+
**Status: FIXED** (gh#285, PR #292, merged 2026-07-17). `UIElement`'s constructor
|
|
22
|
+
(`packages/web-components/core/element.js`) and 24 component/trait/module files had
|
|
23
|
+
this shape. The fix pattern (feature-detect + fallback matched to how the reference is
|
|
24
|
+
used downstream) is [`guard-patterns.md`](guard-patterns.md) — apply that pattern to
|
|
25
|
+
any NEW file that construct-calls one of these APIs; don't re-derive the shape from
|
|
26
|
+
scratch.
|
|
27
|
+
|
|
28
|
+
## 2 · `connectedCallback` destructively re-stamps existing DOM → content loss
|
|
29
|
+
|
|
30
|
+
**Symptom (as originally reported):** the component renders in the SSR HTML response
|
|
31
|
+
as an EMPTY or STRUCTURALLY WRONG tag — not a crash, a silent loss. Two named
|
|
32
|
+
variants:
|
|
33
|
+
- A **container** (`admin-shell`, `admin-sidebar`, `nav-ui`) loses its nested custom-element
|
|
34
|
+
children — `<nav-item-ui>` inside `<nav-ui>` simply isn't in the response.
|
|
35
|
+
- A **projected-text** leaf (`<text-ui>Adia Admin</text-ui>`, `<avatar-ui>A</avatar-ui>`,
|
|
36
|
+
`<badge-ui>warning</badge-ui>`) goes out as an empty tag — the authored text is gone.
|
|
37
|
+
|
|
38
|
+
**Root cause (still real):** `UIElement.connectedCallback` (`packages/web-components/core/element.js`)
|
|
39
|
+
runs `stamp(result, this)` (the `stamp` function in `packages/web-components/core/template.js`,
|
|
40
|
+
currently at line 194) with the OWN template's output — this OVERWRITES the element's
|
|
41
|
+
existing children rather than adopting/patching them. In a real browser this is
|
|
42
|
+
correct and invisible: nothing exists inside the element yet at first connect. Under
|
|
43
|
+
SSR, `connectedCallback` runs a SECOND TIME against the server-parsed DOM (which
|
|
44
|
+
already has real, meaningful children from the HTML response) — the stamp silently
|
|
45
|
+
replaces them.
|
|
46
|
+
|
|
47
|
+
**Status: NARROWED, downgraded 2026-07-17** (gh#284). Verified against 0.8.4 — the
|
|
48
|
+
mechanism is real (a synthetic repro, a component with a non-null template AND
|
|
49
|
+
projected text, does lose it), but **every named example in the issue currently
|
|
50
|
+
has `static template = () => null`**: `admin-shell`, `admin-sidebar`, `nav-ui`,
|
|
51
|
+
`text-ui`, `badge-ui`, `avatar-ui` all skip `stamp()` entirely (`if (result)
|
|
52
|
+
stamp(result, this)` — `null` never enters the branch). A framework-wide survey
|
|
53
|
+
(150 components) found every component with a NON-null template derives its
|
|
54
|
+
visible content from properties/attributes only (`check-ui`'s `label=`,
|
|
55
|
+
`switch-ui`'s `label=`/`hint=`, `skip-nav`'s `text=`) — never from light-DOM
|
|
56
|
+
children — so the destructive replace, where it does fire, only ever regenerates
|
|
57
|
+
identical, template-owned content. Zero shipped components are exposed to the
|
|
58
|
+
originally-reported symptom right now.
|
|
59
|
+
|
|
60
|
+
**What shipped instead of a `stamp()` fix:** the two candidate directions the issue
|
|
61
|
+
named (adopt-and-patch, or skip-on-marker) were explicitly rejected as
|
|
62
|
+
disproportionate — "changes the render lifecycle of every primitive in the
|
|
63
|
+
framework (127+), not a mechanical guard sweep" — for a risk with zero current
|
|
64
|
+
instances. Shipped a forward-looking STATIC AUDIT instead:
|
|
65
|
+
`scripts/dev/audit-template-child-conflict.mjs` — flags (critical) any future
|
|
66
|
+
component pairing a non-null `static template` with a yaml `slots.default` entry
|
|
67
|
+
(the container shape), and (advisory) a non-null template paired with a body-text
|
|
68
|
+
usage example (the projected-text shape, no mechanical yaml signal to check). Wired
|
|
69
|
+
into `adia-author`'s structural-gate sequence. This is an operator ruling (not a
|
|
70
|
+
unilateral call) — see gh#284's comment thread for the full reasoning and the
|
|
71
|
+
empirical survey it's based on.
|
|
72
|
+
|
|
73
|
+
**Current consumer workaround still exists but may be over-conservative:**
|
|
74
|
+
`adiav2`'s SSR component registration is restricted to attribute-only-content
|
|
75
|
+
components (`button-ui text="…"`, `icon-ui name="…"`) — per this narrowing, EVERY
|
|
76
|
+
currently-shipped component (container or leaf) with real content already
|
|
77
|
+
qualifies as attribute/property-driven, so the restriction may no longer be
|
|
78
|
+
necessary for 0.8.4+. Don't assume this without the consumer confirming it via the
|
|
79
|
+
issue thread — see [`consumer-workarounds.md`](consumer-workarounds.md).
|
|
80
|
+
|
|
81
|
+
## 3 · A connect-time layout MEASUREMENT is meaningless before real layout exists
|
|
82
|
+
|
|
83
|
+
**Symptom:** a component makes a decision (a boolean state, a mode, a snapped value)
|
|
84
|
+
by synchronously reading `getBoundingClientRect()` (or similar) inside `connected()`,
|
|
85
|
+
and that decision comes out WRONG — not crashed, not empty, just incorrect — in any
|
|
86
|
+
environment where real layout hasn't happened yet. This includes linkedom (no layout
|
|
87
|
+
engine at all — always returns a 0×0 rect) but ALSO a real browser connecting an
|
|
88
|
+
element before its first layout pass (inside a `display:none` ancestor, for instance) —
|
|
89
|
+
SSR is the environment that surfaces it reliably, but the bug is not SSR-specific.
|
|
90
|
+
|
|
91
|
+
**Root cause:** treating "the rect read 0" as equivalent to "the rect really is 0" —
|
|
92
|
+
they are NOT the same fact. A zero rect from a shim/pre-layout read means "unknown,"
|
|
93
|
+
and a decision derived from "unknown" as if it were "confirmed small" is a category
|
|
94
|
+
error, not a rendering gap.
|
|
95
|
+
|
|
96
|
+
**Status: FIXED for `admin-sidebar`'s specific instance** (gh#286, PR #290, merged
|
|
97
|
+
2026-07-17) — see [`guard-patterns.md`](guard-patterns.md) §3 for the fix shape
|
|
98
|
+
(treat a zero read as unknown, defer to the component's own `ResizeObserver`'s first
|
|
99
|
+
real tick). **The general pattern is NOT swept framework-wide** — any OTHER component
|
|
100
|
+
that derives a persistent decision from a synchronous connect-time measurement
|
|
101
|
+
carries the same latent bug, undiscovered until someone hits it. If you're
|
|
102
|
+
investigating a "wrong initial state under SSR" report that ISN'T a missing-API crash
|
|
103
|
+
(shape 1) or a content-loss (shape 2), check whether the component reads a rect/size
|
|
104
|
+
synchronously at connect and treat that as the working hypothesis first.
|
|
105
|
+
|
|
106
|
+
## 4 · Feature gap, not a bug — property-only components can't seed from SSR HTML
|
|
107
|
+
|
|
108
|
+
**Symptom:** not a bug report at all — a `table-ui`/`chart-ui`/`select-ui` with
|
|
109
|
+
programmatic-only content (`.columns`, `.data`, `.options` set as JS properties)
|
|
110
|
+
renders correctly in the browser but is STRUCTURALLY ABSENT from the SSR HTML response,
|
|
111
|
+
because JS property assignments don't serialize into server-rendered markup. The
|
|
112
|
+
component pops in empty and fills in after a post-hydration wiring script runs.
|
|
113
|
+
|
|
114
|
+
**Root cause:** no declarative (HTML-serializable) form of the data these components
|
|
115
|
+
need exists yet — property-only content is a deliberate API shape for CONSUMERS with a
|
|
116
|
+
live client, not a gap in any individual component's code.
|
|
117
|
+
|
|
118
|
+
**Status: OPEN, blocked on shape 2** (gh#288). A JSON-script-child or attribute form
|
|
119
|
+
is proposed but explicitly depends on #284's resolution — reading a declarative payload
|
|
120
|
+
at connect time is pointless if the very next thing `connectedCallback` does is
|
|
121
|
+
overwrite whatever DOM it finds (including a `<script type="application/json">` child)
|
|
122
|
+
with the stamp. Don't attempt #288 in isolation.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Guard patterns — the established fix shape per failure shape
|
|
2
|
+
|
|
3
|
+
Cites the actual shipped code (gh#285 PR #292, gh#286 PR #290) as the pattern to copy
|
|
4
|
+
for a NEW instance of the same failure class — don't re-derive the shape, match it.
|
|
5
|
+
|
|
6
|
+
## §1 · A browser-only API constructor/method call
|
|
7
|
+
|
|
8
|
+
**Shape:** `typeof <Global> !== 'undefined'` (for globals: `ResizeObserver`,
|
|
9
|
+
`IntersectionObserver`, `MutationObserver`, `PerformanceObserver`) or `typeof
|
|
10
|
+
this.<method> === 'function'` (for instance methods: `attachInternals`) or `'<prop>'
|
|
11
|
+
in <object>` (for object properties: `document.adoptedStyleSheets`) guards the call;
|
|
12
|
+
the ELSE branch is picked by what downstream code does with the reference — there is
|
|
13
|
+
no one boilerplate to paste everywhere.
|
|
14
|
+
|
|
15
|
+
**Three concrete shapes, all live in the repo:**
|
|
16
|
+
|
|
17
|
+
**(a) Field assigned, used later with `?.` on teardown** — the majority case. Guard the
|
|
18
|
+
construction; every later reference to the field must already (or now needs to) use
|
|
19
|
+
optional chaining, since the field can legitimately stay unset:
|
|
20
|
+
```js
|
|
21
|
+
// packages/web-modules/shell/admin-sidebar/admin-sidebar.js — #setupChildResizeObserver
|
|
22
|
+
#setupChildResizeObserver() {
|
|
23
|
+
this.#childRO = new ResizeObserver((entries) => { ... });
|
|
24
|
+
this.#childRO.observe(this);
|
|
25
|
+
}
|
|
26
|
+
// guarded:
|
|
27
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
28
|
+
this.#childRO = new ResizeObserver((entries) => { ... });
|
|
29
|
+
this.#childRO.observe(this);
|
|
30
|
+
}
|
|
31
|
+
// disconnectedCallback: this.#childRO?.disconnect(); ← already needs the `?.`
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**(b) Early-return no-op** — when the entire trait/method's purpose IS the observer;
|
|
35
|
+
nothing else in the function is worth running without it:
|
|
36
|
+
```js
|
|
37
|
+
// packages/web-components/traits/resize-observer/resize-observer.js
|
|
38
|
+
setup({ host }) {
|
|
39
|
+
if (typeof ResizeObserver === 'undefined') return () => {};
|
|
40
|
+
// ... the rest of the trait, only reached when the API exists
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**(c) Module-level `const`, ternary at declaration** — when the observer is a
|
|
45
|
+
singleton constructed at import time, not per-instance:
|
|
46
|
+
```js
|
|
47
|
+
// packages/web-components/core/data-stream.js
|
|
48
|
+
const observer = typeof MutationObserver !== 'undefined'
|
|
49
|
+
? new MutationObserver((mutations) => { ... })
|
|
50
|
+
: null;
|
|
51
|
+
// every later use: observer?.observe(...) / bootstrap() no-ops via the same check
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**The `this.internals` special case — a shim, not `undefined`.** `UIElement`'s
|
|
55
|
+
constructor (`packages/web-components/core/element.js`) needed a DIFFERENT answer
|
|
56
|
+
than "guard and leave unset," because ~12 files across the framework call
|
|
57
|
+
`this.internals.setValidity(...)` / `.setFormValue(...)` unconditionally (form
|
|
58
|
+
participation is a routine, expected thing for `UIFormElement` subclasses to do).
|
|
59
|
+
Leaving `this.internals` as `undefined` when `attachInternals` doesn't exist would
|
|
60
|
+
just relocate the crash one level deeper, into every one of those 12 consumers' first
|
|
61
|
+
validation call. The actual fix is a frozen no-op object matching the EXACT surface
|
|
62
|
+
those consumers call (grepped, not guessed: `setFormValue`, `setValidity`,
|
|
63
|
+
`checkValidity`, `reportValidity`, `form`, `labels`, `validity`, `validationMessage`,
|
|
64
|
+
`willValidate`):
|
|
65
|
+
```js
|
|
66
|
+
const NOOP_INTERNALS = Object.freeze({
|
|
67
|
+
setFormValue() {}, setValidity() {},
|
|
68
|
+
checkValidity() { return true; }, reportValidity() { return true; },
|
|
69
|
+
get form() { return null; }, get labels() { return []; },
|
|
70
|
+
get validity() { return {}; }, get validationMessage() { return ''; },
|
|
71
|
+
get willValidate() { return false; },
|
|
72
|
+
});
|
|
73
|
+
// constructor:
|
|
74
|
+
this.internals = typeof this.attachInternals === 'function'
|
|
75
|
+
? this.attachInternals()
|
|
76
|
+
: NOOP_INTERNALS;
|
|
77
|
+
```
|
|
78
|
+
**When a NEW browser-only API needs the same treatment:** ask "do consumers assume the
|
|
79
|
+
returned object/value is always truthy and call methods on it unconditionally?" If
|
|
80
|
+
yes, a no-op shim matching the ACTUAL consumed surface (grep for it, don't guess) is
|
|
81
|
+
the right answer, not leaving the field `undefined`. If the API is purely
|
|
82
|
+
fire-and-forget infrastructure with no return value anyone depends on (most
|
|
83
|
+
Observers), a plain construction guard (shapes a/b/c above) is sufficient — no shim
|
|
84
|
+
needed.
|
|
85
|
+
|
|
86
|
+
## §2 · Destructive `stamp()` on connect (gh#284 — narrowed to a static audit, not a stamp() patch)
|
|
87
|
+
|
|
88
|
+
There is no `stamp()`/`connectedCallback` fix, and per the 2026-07-17 narrowing
|
|
89
|
+
there isn't meant to be one — the mechanism is real but zero shipped components
|
|
90
|
+
are exposed (see [`failure-shapes.md`](failure-shapes.md) §2 for the full survey).
|
|
91
|
+
The shipped guard is `scripts/dev/audit-template-child-conflict.mjs`: it flags a
|
|
92
|
+
NEW component that pairs a non-null `static template` with a yaml `slots.default`
|
|
93
|
+
entry (critical) or a body-text usage example (advisory) — i.e. it prevents the
|
|
94
|
+
conflict shape from being reintroduced, rather than patching the render lifecycle
|
|
95
|
+
every one of the 150 components goes through. **A component author who hits this
|
|
96
|
+
audit's finding fixes it by making the template `() => null`** (the pattern every
|
|
97
|
+
current children-accepting component already uses — compose via CSS + `render()`'s
|
|
98
|
+
own surgical DOM manipulation, matching `avatar-ui`'s `#imgEl`/`#initialsEl`
|
|
99
|
+
pattern) — NOT by inventing a component-local stamp-skip flag or a marker
|
|
100
|
+
attribute; either of those drifts from the established convention and isn't
|
|
101
|
+
covered by this audit's "safe" classification. If a genuinely new component NEEDS
|
|
102
|
+
both a non-null template AND real consumer children (unlike anything shipped
|
|
103
|
+
today), route through `adia-author` before authoring it — that's a real design
|
|
104
|
+
question, not a mechanical fix.
|
|
105
|
+
|
|
106
|
+
## §3 · A connect-time measurement is "unknown," not "confirmed"
|
|
107
|
+
|
|
108
|
+
**Shape:** treat a zero (or otherwise clearly-bogus) synchronous read as "no signal
|
|
109
|
+
yet" and DEFER the decision to whatever async correction mechanism the component
|
|
110
|
+
already has — don't add a new one if an existing `ResizeObserver`/similar already
|
|
111
|
+
watches the same dimension.
|
|
112
|
+
```js
|
|
113
|
+
// packages/web-modules/shell/admin-sidebar/admin-sidebar.js — #syncCollapsedFromWidth
|
|
114
|
+
#syncCollapsedFromWidth() {
|
|
115
|
+
const w = this.getBoundingClientRect().width;
|
|
116
|
+
if (w === 0) return; // ← unknown, not "confirmed collapsed" —
|
|
117
|
+
this.collapsed = w <= SNAP_THRESHOLD; // leaves whatever connected() already
|
|
118
|
+
} // restored (persisted state, or default)
|
|
119
|
+
|
|
120
|
+
// the component's EXISTING ResizeObserver callback (already watching this.host for
|
|
121
|
+
// an unrelated reason — flipping select-ui placement) gains ONE more line, so its
|
|
122
|
+
// first REAL tick (post-hydration, or after the display:none ancestor is revealed)
|
|
123
|
+
// resolves what connect-time couldn't:
|
|
124
|
+
#setupChildResizeObserver() {
|
|
125
|
+
this.#childRO = new ResizeObserver((entries) => {
|
|
126
|
+
for (const entry of entries) {
|
|
127
|
+
const w = entry.contentBoxSize[0].inlineSize;
|
|
128
|
+
if (w > 0) this.collapsed = w <= SNAP_THRESHOLD; // ← the deferred correction
|
|
129
|
+
// ...existing unrelated logic continues unchanged...
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
The key move: reuse the observer that's ALREADY there rather than adding a
|
|
135
|
+
purpose-built one — a second observer watching the same element for two unrelated
|
|
136
|
+
reasons is a maintenance smell, and per §1 above, a NEW observer needs its own
|
|
137
|
+
construction guard anyway.
|
|
138
|
+
|
|
139
|
+
## Verify targets for a new guard
|
|
140
|
+
|
|
141
|
+
See [`test-without-linkedom.md`](test-without-linkedom.md) for how to prove a new
|
|
142
|
+
guard actually works — deleting the real API in a test, not mocking it.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# SSR support — status ledger
|
|
2
|
+
|
|
3
|
+
**This file drifts fast — re-verify against the live issue tracker
|
|
4
|
+
(`gh issue view <n>`) before trusting it for anything beyond a quick orientation.**
|
|
5
|
+
Every row was true as of 2026-07-17; a closed issue below may have re-opened, a PR
|
|
6
|
+
number may have been superseded by a follow-up fix, etc. This ledger is a map, not
|
|
7
|
+
the territory — the territory is `gh issue list` / `gh issue view`.
|
|
8
|
+
|
|
9
|
+
| Issue | Failure shape | Status | Shipped in | Notes |
|
|
10
|
+
|---|---|---|---|---|
|
|
11
|
+
| [#285](https://github.com/adiahealth/gen-ui-kit/issues/285) | §1 — browser-only API called unconditionally | **CLOSED** | PR #292 (merged 2026-07-17) | `UIElement` constructor + `adoptStyles()` + 22 component/trait/module files guarded; 10 more already correct from an earlier pass |
|
|
12
|
+
| [#286](https://github.com/adiahealth/gen-ui-kit/issues/286) | §3 — connect-time measurement treated as confirmed | **CLOSED** (for `admin-sidebar` specifically) | PR #290 (merged 2026-07-17) | The GENERAL pattern (any other component reading a rect/size synchronously at connect) is NOT swept — only this one instance is fixed |
|
|
13
|
+
| [#284](https://github.com/adiahealth/gen-ui-kit/issues/284) | §2 — destructive `stamp()` on connect | **NARROWED, downgraded 2026-07-17** | `scripts/dev/audit-template-child-conflict.mjs` | Verified against 0.8.4: EVERY component that accepts light-DOM children (nav-ui, admin-shell, admin-sidebar, text-ui, badge-ui, avatar-ui — every example the issue names) has `static template = () => null`, so `stamp()` never runs for them; every component with a non-null template derives content from properties only. The mechanism is still real (a synthetic repro confirms it), but zero shipped components are exposed. Fixing `stamp()`/`connectedCallback` itself was rejected as disproportionate (touches every primitive's render lifecycle for a currently-zero-instance risk) in favor of a static audit gate that catches a FUTURE component reintroducing the conflict shape. Operator ruling, not a unilateral call — see gh#284's comment thread. |
|
|
14
|
+
| [#288](https://github.com/adiahealth/gen-ui-kit/issues/288) | §4 — property-only components can't seed from SSR HTML | **OPEN, blocked on #284's ORIGINAL scope** | — | #284's narrowing doesn't resolve this — #288 is about JS-property-only content (table-ui `.columns`, select-ui `.options`), a different gap from destructive stamping. Still needs its own declarative-data-channel design; don't attempt in isolation. |
|
|
15
|
+
| [#287](https://github.com/adiahealth/gen-ui-kit/issues/287) | Unrelated to the three SSR shapes above — a Phosphor-icon `import.meta.glob` path failure under workspace hoisting, plus the dep-optimizer silently emptying the icon registry | **CLOSED** | PR #294 (merged 2026-07-17) | Root cause confirmed empirically (a from-scratch pnpm workspace repro): the glob's leading `/` joined to Vite's configured root, which only reaches a transitive dependency's assets when hoisted there. Fixed by switching to an entry-file-relative glob (`../node_modules/...`). A second, independent cause (Vite's dev-server `optimizeDeps` pre-bundling) has no package-side fix — documented as a consumer `optimizeDeps.exclude` workaround. A bundler/workspace-resolution bug, not an SSR-lifecycle bug — still doesn't route through this pack's failure taxonomy. |
|
|
16
|
+
|
|
17
|
+
## What "done" looks like for the open items
|
|
18
|
+
|
|
19
|
+
- **#284's original scope is closed as a non-issue against 0.8.4** — see the ledger
|
|
20
|
+
row above. Re-open only if a FUTURE component's yaml/class.js trips
|
|
21
|
+
`audit-template-child-conflict.mjs`, or if `adiav2`'s production workaround
|
|
22
|
+
(`consumer-workarounds.md`) turns out to be catching something this repo's
|
|
23
|
+
synthetic repro didn't (confirm via the issue thread before assuming so).
|
|
24
|
+
- **#288 done** = a declarative data channel exists. It no longer strictly depends
|
|
25
|
+
on #284 shipping a `stamp()`-level fix (there isn't one), but a real design still
|
|
26
|
+
needs to answer "what happens if a future property-only component's data arrives
|
|
27
|
+
after a template that already stamped" — don't hand-wave that gap away just
|
|
28
|
+
because #284 narrowed.
|
|
29
|
+
|
|
30
|
+
## Re-syncing this ledger
|
|
31
|
+
|
|
32
|
+
When any row's status changes, update the table AND re-check whether
|
|
33
|
+
[`consumer-workarounds.md`](consumer-workarounds.md)'s claims about what's "now
|
|
34
|
+
deletable" are still accurate — a workaround section that says "should no longer be
|
|
35
|
+
necessary" for a shape that's since re-opened is a false manifest, not a stale detail.
|