@adia-ai/a2ui-compose 0.5.4 → 0.5.6

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/CHANGELOG.md CHANGED
@@ -7,11 +7,61 @@ transpiler, evals. Retrieval + validation now ship as sibling
7
7
  packages (`@adia-ai/a2ui-retrieval`, `@adia-ai/a2ui-validator`) so
8
8
  non-compose consumers can depend on them without pulling the
9
9
  generator graph.
10
-
11
10
  ## [Unreleased]
12
11
 
13
12
  _No pending changes._
13
+ ## [0.5.6] - 2026-05-14
14
+
15
+ ### Removed — §195 (v0.5.6) — retired fragment-graph iteration codepath
16
+
17
+ Deleted `packages/a2ui/compose/strategies/zettel/synthesizer.js`. The
18
+ file shipped the `synthesizeComposition` function as a throwing stub
19
+ since §37 (v0.4.7, 2026-05-12, fragment retirement). The dangling
20
+ caller in `generator-adapter.js` (iteration branch, turn ≥ 2 + LLM
21
+ present) caught the throw and auto-fired an `iteration-synthesis-failure`
22
+ issue ticket on every multi-turn refinement — surfaced as an auto-fired
23
+ bug at `.brain/audit-history/issues/2026-05-14-iteration-synthesis-failed-on-turn-3-for-4bfb.json`.
24
+
25
+ ### Changed — §195 (v0.5.6) — weak-retrieval branch bridges to chunk-zettel
26
+
27
+ Replaced the `synthesizeComposition()` call in `generateZettel`'s
28
+ weak-retrieval-with-LLM branch with a bridge to
29
+ `chunk-synthesizer.js::composeFromIntent` (the §37 successor codepath).
30
+ Return shape is preserved (`messages`, `validation`, `strategy:
31
+ 'composition-synthesized'`, `fragments_used: []`, `synthesized_template`,
32
+ `synthesis`). Fragment instances are no longer tracked on this path —
33
+ the chunk-bridge emits a single html-bearing component. Consumers that
34
+ need fragment-level resolution should wire via `engine: 'chunk-zettel'`
35
+ directly.
36
+
37
+ The `composition-iterated` strategy is retired — turn ≥ 2 on
38
+ `engine: 'zettel'` now takes the same path as turn 1. True
39
+ history-aware iteration lives in `chunk-refiner.js::refineFromIntent`
40
+ behind `engine: 'chunk-zettel'`.
41
+
42
+ ### Added — §195 (v0.5.6) — smoke probe
43
+
44
+ NEW `scripts/smoke-iteration-synthesis.mjs` (npm run
45
+ `smoke:iteration-synthesis`) verifies the §195 invariants at three
46
+ layers: filesystem (`synthesizer.js` deleted), source
47
+ (`generator-adapter.js` has no surviving reference to the retired
48
+ identifiers), and runtime (4 sequential turns against the same
49
+ sessionId neither throw nor emit `composition-iterated`).
50
+ ## [0.5.5] - 2026-05-14
51
+
52
+ ### Changed — §179 (v0.5.5) — principled `deprecated:` yaml field
53
+
54
+ Three new yaml schema fields under each prop:
55
+
56
+ - **`deprecated`** (boolean, default `false`) — when `true`, the LLM prompt builder appends `(deprecated, do not use)` to the prop's line in CORPUS CONTEXT.
57
+ - **`deprecated_since`** (string) — version when deprecation was introduced.
58
+ - **`deprecated_reason`** (string) — human-readable migration guidance.
14
59
 
60
+ Build pipeline (`scripts/build/components.mjs::compileProp`) collects deprecation metadata and inlines onto the prop schema. Prompt builder (`packages/a2ui/compose/strategies/monolithic/_shared.js`) reads `v.deprecated === true` FIRST; falls back to `/deprecated/i.test(v.description)` (the v0.5.4 §167a substring detection) for back-compat. Existing §167a-tagged props continue to work without yaml changes.
61
+
62
+ Demonstrated on `Avatar.name`. Verified via `buildSystemPrompt`: `name:String[] (deprecated, do not use)` surfaces in CORPUS CONTEXT.
63
+
64
+ Commit `0379dd498`. See root CHANGELOG and journal §179 for full context.
15
65
  ## [0.5.4] - 2026-05-14
16
66
 
17
67
  ### Fixed — §163 transpiler prop-fidelity; catalog-driven extractor closes the harvester drop class (v0.5.4)
@@ -185,7 +235,6 @@ No compose source change in §173/§175 — both detect future drift in compose'
185
235
  catalog-vs-prompt + registry-vs-catalog invariants.
186
236
 
187
237
  ### Changed — `version`: `0.5.3` → `0.5.4`.
188
-
189
238
  ## [0.5.3] - 2026-05-14
190
239
 
191
240
  ### Fixed — Deterministic chunk-loading order in zettel composition library (§160, v0.5.3)
@@ -208,7 +257,6 @@ Finalizes the v0.6.0 deprecation schedule for the last two `_debug.*` fields tha
208
257
  **Scheduled removal**: v0.6.0 drops the `_debug` block entirely from the free-form-composed result shape. The dialog-recorder will read first-class fields directly. v0.5.3 is the **migration window** — any external consumers reading `_debug.attempts` / `_debug.warnings` should switch to the first-class fields before v0.6.0 ships.
209
258
 
210
259
  **Internal verification**: zero live in-repo consumers (`grep -rn '_debug\?\.attempts\|_debug\.attempts\|_debug\?\.warnings\|_debug\.warnings' apps/ playgrounds/ catalog/ packages/` returns only the deprecation comment itself).
211
-
212
260
  ## [0.5.2] - 2026-05-13
213
261
 
214
262
  ### Changed — `plan` graduates from `_debug.*` to first-class on free-form-composed result (§107a infra, v0.5.2)
@@ -243,7 +291,6 @@ Expected impact: lifts substitution ratio 27.4% → ~35-40% (target ≥30%); F1
243
291
  `strategies/registry.js` reads model priority chain at call-time (not module-load): `ctx.model` > `process.env.FREE_FORM_MODEL_OVERRIDE` > `FREE_FORM_MODEL_DEFAULT` (Haiku 4.5). Enables `eval-diff.mjs --model <id>` to run the Haiku-vs-Opus A/B for §127 without env-dance or static-import-ordering hazards. `FREE_FORM_MODEL` constant renamed to `FREE_FORM_MODEL_DEFAULT` to reflect the new priority chain.
244
292
 
245
293
  Default behavior unchanged when no override set — the v0.5.1 §108 Haiku pin holds for every consumer that doesn't explicitly override.
246
-
247
294
  ## [0.5.1] - 2026-05-13
248
295
 
249
296
  ### Added — Free-form composer INTENT-PARAPHRASE block + paraphrase-retry (§106, v0.5.1)
@@ -265,7 +312,6 @@ Consumers passing `ctx.llmAdapter` keep getting their adapter via the mint-failu
265
312
  ### Coverage at v0.5.1 cut
266
313
 
267
314
  Post-§106 prompt-tuning + §108 picker pin + §109 first-class graduation, free-form composer coverage measured at **~96-97%** on the 100-intent held-out set (up from §104's 92%). New AGENTS.md regression threshold floor: `cov≥96%, avg≥85, F1≥0.60` (§115 trip-wire baseline).
268
-
269
315
  ## [0.5.0] - 2026-05-13
270
316
 
271
317
  ### Added — Free-form composer auto-grouping (§103, v0.5.0)
@@ -283,17 +329,14 @@ Schema validation rejects unknown target keys; downgrade warnings fire when a ta
283
329
  ### Coverage at v0.5.0 cut
284
330
 
285
331
  After §93 (layout regrowth) + §94 (forms regrowth) + §103 (auto-grouping) + §104 (structural substitutions + v0.5.1 deferred regrowth fold-in), free-form composer coverage measured at **92% on the 100-intent held-out set** (up from 21% at §92 baseline). New AGENTS.md regression threshold floor: `cov≥80%, avg≥85, F1≥0.45` (§97 rebaseline).
286
-
287
332
  ## [0.4.9] - 2026-05-13
288
333
 
289
334
  _No pending changes._
290
-
291
335
  ## [0.4.8] - 2026-05-12
292
336
 
293
337
  ### Fixed — `strategies/zettel/generator-adapter.js` `ensureBooted()` race (§87a, v0.4.8)
294
338
 
295
339
  The zettel composition library's lazy boot had a race condition where concurrent `ensureBooted()` calls could each kick off a separate boot promise. Under contention (multiple requests landing simultaneously at server cold-start), the composition map could be partially populated when consumers reached the synchronous getters. Fix: memoize the boot promise on first call so all subsequent callers await the same promise. Pairs with the v0.4.8 §87 honest-floor eval threshold rebaseline — without the boot race fix, the 1% rebaseline was actually undercounting due to occasional empty-map reads.
296
-
297
340
  ## [0.4.7] - 2026-05-12
298
341
 
299
342
  ### Changed — `strategies/monolithic/_shared.js` `getComponentCatalog()` reads canonical catalog (§72)
@@ -301,7 +344,6 @@ The zettel composition library's lazy boot had a race condition where concurrent
301
344
  The legacy reader of `@adia-ai/a2ui-corpus/patterns/_components.json` has been migrated to read `@adia-ai/a2ui-corpus/catalog-a2ui_0_9.json` (the canonical v0.9 catalog, already the package root export). Per-component aliases are now lifted from `components[name].x-adiaui.synonyms.tags`. Same legacy output shape (`{ <Name>: { aliases: string[] } }`); zero behavior change for downstream callers.
302
345
 
303
346
  Closes the §65 carry-over from v0.4.6 — `@adia-ai/a2ui-corpus` `patterns/` + `compositions/` are now deleted from disk + tarball.
304
-
305
347
  ## [0.4.6] - 2026-05-12
306
348
 
307
349
  ### Changed — `core/` retirement follow-through (§64, v0.4.6)
@@ -337,7 +379,6 @@ Closes the catalog-drift surface that §56 explicitly deferred ("different schem
337
379
  5. `const` fields (e.g. `{const: 'Button'}`) are discriminator-only. Skip.
338
380
 
339
381
  Source: `packages/a2ui/compose/strategies/monolithic/_shared.js` (+91, -2 lines, commit `afda98f5`).
340
-
341
382
  ## [0.4.5] - 2026-05-12
342
383
 
343
384
  ### Changed — GenUI overhaul prompt-engineering (§56, v0.4.5)
@@ -353,7 +394,6 @@ Source: `packages/a2ui/compose/strategies/monolithic/_shared.js` (+91, -2 lines,
353
394
  - **`strategies/monolithic/generate-pro.js` — STRUCTURAL REFERENCE prose enriched.** Prior copy: "a real production block from the codebase matched this intent." New copy: "this chunk was retrieved from the AdiaUI training corpus (annotated production HTML, harvested from real app pages). It matched your intent on keyword/domain ranking." Threads chunk `metadata.domain`, `metadata.description`, `metadata.keywords` into the prompt when present. Reframes "do not copy the HTML" as "the chunk represents the SHAPE the user wants; instantiate it with their content" — more actionable framing.
354
395
 
355
396
  See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the v0.4.5 overhaul arc + apps/genui/CHANGELOG.md `[Unreleased]` for the per-§ rollup.
356
-
357
397
  ## [0.4.4] - 2026-05-12
358
398
 
359
399
  ### Changed
@@ -370,7 +410,6 @@ See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the v0.4.5 ove
370
410
  - **MCP server `mcp/server.js` (§37).** Removed `get_fragment` tool, simplified `zettel_stats`, updated imports + boot logs + engine description to reflect the post-fragment world.
371
411
 
372
412
  See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the cross-cutting arc narrative + [docs/journal/2026/05/2026-05-12.md](../../../docs/journal/2026/05/2026-05-12.md) §§ 37 / 38 / 41 / 47 for per-§ details.
373
-
374
413
  ## [0.4.3] - 2026-05-11
375
414
 
376
415
  ### Fixed
@@ -380,7 +419,6 @@ See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the cross-cutt
380
419
  ### Lockstep
381
420
 
382
421
  9-package coordinated PATCH cut to v0.4.3 (per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy)). Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4.0` covers `0.4.x` under semver). Source change scoped to `core/generator.js` + `strategies/zettel/state-cache.js`. Rides alongside `@adia-ai/web-components` v0.4.3 (input-ui locale + thousands grouping + hold-to-repeat). See root [CHANGELOG.md `## [0.4.3]`](../../../CHANGELOG.md) for the cut narrative.
383
-
384
422
  ## [0.4.2] - 2026-05-11
385
423
 
386
424
  ### Ride-along (no source changes)
@@ -388,7 +426,6 @@ See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the cross-cutt
388
426
  Lockstep PATCH cut alongside `@adia-ai/web-components@0.4.2` (`<input-ui type="number">` rewrite drops native `<input type=number>` wrapping) + `@adia-ai/web-modules@0.4.2` (`<editor-sidebar>` grid-track width-mirror fix). Source byte-identical to v0.4.1.
389
427
 
390
428
  Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4.0` covers `0.4.x` under semver). See root [CHANGELOG.md `## [0.4.2]`](../../../CHANGELOG.md) for the cut narrative.
391
-
392
429
  ## [0.4.1] - 2026-05-10
393
430
 
394
431
  ### Ride-along (no source changes)
@@ -396,7 +433,6 @@ Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4
396
433
  Lockstep PATCH cut alongside `@adia-ai/web-modules@0.4.1` (simple cluster) + `@adia-ai/a2ui-validator@0.4.1` (Phase 3 foundation) + `@adia-ai/a2ui-corpus@0.4.1` (fragment metrics reconciliation). Source byte-identical to v0.4.0.
397
434
 
398
435
  Internal `@adia-ai/*` dep ranges bumped from `^0.4.0` to `^0.4.1`. See root [CHANGELOG.md `## [0.4.1]`](../../../CHANGELOG.md) for the cut narrative.
399
-
400
436
  ## [0.4.0] - 2026-05-10
401
437
 
402
438
  ### Ride-along (no source changes)
@@ -404,25 +440,21 @@ Internal `@adia-ai/*` dep ranges bumped from `^0.4.0` to `^0.4.1`. See root [CHA
404
440
  Lockstep MINOR cut alongside `@adia-ai/web-modules@0.4.0` (ADR-0024 legacy shell shapes retired). Source byte-identical to v0.3.6.
405
441
 
406
442
  Internal `@adia-ai/*` dep ranges bumped from `^0.3.0` to `^0.4.0`. See root [CHANGELOG.md `## [0.4.0]`](../../../CHANGELOG.md) for the cut narrative.
407
-
408
443
  ## [0.3.6] - 2026-05-10
409
444
 
410
445
  ### Ride-along (no source changes)
411
446
 
412
447
  Lockstep version bump only — source byte-identical to v0.3.5. Internal `@adia-ai/*` dep ranges remain at `^0.3.0`. See root [CHANGELOG.md `## [0.3.6]`](../../../CHANGELOG.md) for the cut narrative.
413
-
414
448
  ## [0.3.5] - 2026-05-07
415
449
 
416
450
  ### Ride-along (no source changes)
417
451
 
418
452
  Lockstep version bump only — source byte-identical to v0.3.4. Internal `@adia-ai/*` dep ranges remain at `^0.3.0`. See root [CHANGELOG.md `## [0.3.5]`](../../../CHANGELOG.md) for the cut narrative.
419
-
420
453
  ## [0.3.4] - 2026-05-07
421
454
 
422
455
  ### Ride-along (no source changes)
423
456
 
424
457
  Lockstep version bump only — source byte-identical to v0.3.3. Internal `@adia-ai/*` dep ranges remain at `^0.3.0`. See root [CHANGELOG.md `## [0.3.4]`](../../../CHANGELOG.md) for the cut narrative.
425
-
426
458
  ## [0.3.3] - 2026-05-07
427
459
 
428
460
  **Lockstep cut.** All 9 published `@adia-ai/*` packages now share version `0.3.3`, governed by [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` ranges stay at `^0.3.0` (patch-cut asymmetry — caret floats `0.3.x`).
@@ -467,7 +499,6 @@ Lockstep version bump only — source byte-identical to v0.3.3. Internal `@adia-
467
499
  for the lifetime of the process. Trade-offs: good for long-running
468
500
  MCP, bad for tests/hot-reload. To force a reload, call `loadAll()`
469
501
  directly. (closes backlog #100)
470
-
471
502
  ## [0.3.2] - 2026-05-06
472
503
 
473
504
  **9-package lockstep patch cut to v0.3.2.** All lockstep members share
@@ -494,7 +525,6 @@ Internal `@adia-ai/*` dep ranges unchanged at `^0.3.0`.
494
525
  ### Changed
495
526
 
496
527
  - `version`: `0.3.1` → `0.3.2`.
497
-
498
528
  ## [0.3.1] - 2026-05-06
499
529
 
500
530
  **9-package lockstep patch cut.** All 9 published `@adia-ai/*` packages bump 0.3.0 → 0.3.1 per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges remain at `^0.3.0` (covers `0.3.1` under semver — patch-cut asymmetry).
@@ -505,7 +535,6 @@ This package itself ships **no source changes** in v0.3.1. The cut bumps version
505
535
 
506
536
  - `version`: `0.3.0` → `0.3.1`.
507
537
  - Internal `@adia-ai/*` dep ranges: unchanged at `^0.3.0` (covers `0.3.1` under semver — patch-cut asymmetry).
508
-
509
538
  ## [0.3.0] - 2026-05-05
510
539
 
511
540
  **9-package lockstep cut + LLM subpath dropped.** All 9 published `@adia-ai/*` packages bump 0.2.5 → 0.3.0 per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges bump `^0.2.0` → `^0.3.0`.
@@ -551,7 +580,6 @@ This is a **minor cut on top of v0.2.5 with one BREAKING change**: the `./llm` s
551
580
  ```
552
581
 
553
582
  Plus rewrite imports as shown above. The change is mechanical.
554
-
555
583
  ## [0.2.5] - 2026-05-04
556
584
 
557
585
  **8-package lockstep cut.** All 8 published `@adia-ai/*` packages bump 0.2.4 → 0.2.5 per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges remain at `^0.2.0` (covers 0.2.5 under semver — patch-cut asymmetry).
@@ -562,7 +590,6 @@ This is a **patch cut on top of v0.2.4, no BREAKING changes.** Substantive conte
562
590
 
563
591
  - `version`: `0.2.4` → `0.2.5`.
564
592
  - Internal `@adia-ai/*` dep ranges: unchanged at `^0.2.0` (covers `0.2.5` under semver — patch-cut asymmetry).
565
-
566
593
  ## [0.2.4] - 2026-05-04
567
594
 
568
595
  **8-package lockstep cut.** All 8 published `@adia-ai/*` packages bump 0.2.3 → 0.2.4 per [`docs/specs/package-architecture.md` § 15](../../../docs/specs/package-architecture.md#15-versioning-policy). Internal `@adia-ai/*` dep ranges remain at `^0.2.0` (covers 0.2.4 under semver — patch-cut asymmetry).
@@ -581,7 +608,6 @@ _Nothing yet._
581
608
  ---
582
609
 
583
610
  ---
584
-
585
611
  ## [0.2.3] - 2026-05-04
586
612
 
587
613
  **Lockstep cut.** All 8 published `@adia-ai/*` packages bump
@@ -599,7 +625,6 @@ Patch cut — no breaking changes.
599
625
  ### No source changes
600
626
 
601
627
  `@adia-ai/a2ui-compose` source is byte-identical to `0.2.2`. The cut bumps version + the internal dep range entries only.
602
-
603
628
  ## [0.2.2] - 2026-05-02
604
629
 
605
630
  **Lockstep cut + reasoning-panel emission fixes.** All 8 published `@adia-ai/*` packages bump 0.2.1 → 0.2.2 per [`docs/specs/package-architecture.md` § 15](../../../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
@@ -623,7 +648,6 @@ Patch cut — no breaking changes.
623
648
  Companion fix in `@adia-ai/a2ui-retrieval` Unreleased (web-research.js EXPLICIT/IMPLICIT pattern split). Both surfaces feed the same reasoning-panel deception class — five distinct bugs caught + fixed in commit `9986c71e`.
624
649
 
625
650
  ---
626
-
627
651
  ## [0.2.1] - 2026-05-02
628
652
 
629
653
  **Lockstep cut + scope-drift gate at composer time + skeleton harvest + Tier-1 block filter + high-resolution session ticket trace.** All 8 published `@adia-ai/*` packages bump 0.2.0 → 0.2.1 per [`docs/specs/package-architecture.md` § 15](../../../../docs/specs/package-architecture.md#15-versioning-policy). Patch cut — no breaking changes.
@@ -662,7 +686,6 @@ The synthesizer now captures a `retrievalTrace` per attempt: Tier-1 hits with sc
662
686
  `issue-reporter.js` renders this as a sibling Markdown ticket alongside the JSON; sections cover header, description, reproduction, component count, retrieval log table, LLM attempts (raw responses), user prompt, composer plan, generated HTML preview, warnings, ops history, environment. A maintainer can replay any flagged session from the ticket alone.
663
687
 
664
688
  ---
665
-
666
689
  ## [0.2.0] - 2026-05-02
667
690
 
668
691
  **Lockstep cut + boundary cleanup.** All 8 published `@adia-ai/*`
@@ -717,7 +740,6 @@ cycle, but should update on next touch:
717
740
  ```
718
741
 
719
742
  ---
720
-
721
743
  ## [0.1.0] - 2026-04-28
722
744
 
723
745
  **Multi-turn refinement engine (Phase A).** Adds three new modules to the
@@ -804,7 +826,6 @@ Additive surface; no breaking changes. Existing consumers calling
804
826
  `composeFromIntent` continue to work unchanged.
805
827
 
806
828
  ---
807
-
808
829
  ## [0.0.1] - 2026-04-24
809
830
 
810
831
  First public release. Framework-agnostic compose engine for the
@@ -863,7 +884,6 @@ behind a plug-in engine registry.
863
884
  - Stale internal-identifier references in comments + doc strings swept to the current naming.
864
885
 
865
886
  ---
866
-
867
887
  ## [0.1.0] — internal baseline (unreleased)
868
888
 
869
889
  Initial version at the time the monorepo was established. Contains:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-compose",
3
- "version": "0.5.4",
4
- "description": "AdiaUI A2UI compose engine framework-agnostic. Takes natural-language intents + a catalog and produces A2UI protocol messages. Pairs with `@adia-ai/a2ui-retrieval` (intent classification, catalog lookup) and `@adia-ai/a2ui-validator` (schema + semantic checks).",
3
+ "version": "0.5.6",
4
+ "description": "AdiaUI A2UI compose engine \u2014 framework-agnostic. Takes natural-language intents + a catalog and produces A2UI protocol messages. Pairs with `@adia-ai/a2ui-retrieval` (intent classification, catalog lookup) and `@adia-ai/a2ui-validator` (schema + semantic checks).",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./index.js",
@@ -307,7 +307,8 @@ KEY RULES:
307
307
  // but never read here, so deprecated props (Avatar.name, Field.persist
308
308
  // alias, etc.) looked indistinguishable from canonical props. LLM
309
309
  // would pick e.g. <avatar-ui name="..."> instead of text="...".
310
- if (v.description && /deprecated/i.test(v.description)) desc += ' (deprecated, do not use)';
310
+ // §179 (v0.5.5): principled `deprecated:` field wins; falls back to §167a substring detection for back-compat.
311
+ if (v.deprecated === true || (v.description && /deprecated/i.test(v.description))) desc += ' (deprecated, do not use)';
311
312
  return desc;
312
313
  }).join(', ') : '';
313
314
  const events = a2uiData.events?.length ? ` events: ${a2uiData.events.join(', ')}` : '';
@@ -350,7 +351,8 @@ KEY RULES:
350
351
  // but never read here, so deprecated props (Avatar.name, Field.persist
351
352
  // alias, etc.) looked indistinguishable from canonical props. LLM
352
353
  // would pick e.g. <avatar-ui name="..."> instead of text="...".
353
- if (v.description && /deprecated/i.test(v.description)) desc += ' (deprecated, do not use)';
354
+ // §179 (v0.5.5): principled `deprecated:` field wins; falls back to §167a substring detection for back-compat.
355
+ if (v.deprecated === true || (v.description && /deprecated/i.test(v.description))) desc += ' (deprecated, do not use)';
354
356
  return desc;
355
357
  }).join(', ') : '';
356
358
  const events = comp.events?.length ? ` events: ${comp.events.join(', ')}` : '';
@@ -901,6 +903,15 @@ export function mergeCanvasDiff(priorComponents, diffComponents) {
901
903
  const modifiedIds = new Set();
902
904
  const newComponents = [];
903
905
 
906
+ // §190b (v0.5.6) — Aggregate blocked-layout-type-change warnings.
907
+ // The for-loop below can fire `console.warn` once per blocked component,
908
+ // producing 5-10 line bursts when the LLM tries to restructure a form's
909
+ // fields (typical case: 5 Field→Column blocks for a 5-field form).
910
+ // Collect blocks into an array; emit a single aggregated warning after
911
+ // the loop. Closes the 2026-05-14 user-reported "buglabels" intent
912
+ // emitting 6 consecutive blocks.
913
+ const blockedLayoutChanges = [];
914
+
904
915
  for (const dc of diffComponents) {
905
916
  if (!dc || !dc.id) continue;
906
917
 
@@ -918,7 +929,7 @@ export function mergeCanvasDiff(priorComponents, diffComponents) {
918
929
  const LAYOUT_CONTAINERS = new Set(['Grid', 'Row', 'Column', 'Stack']);
919
930
  if (dc.component && dc.component !== existing.component &&
920
931
  (LAYOUT_CONTAINERS.has(existing.component) || LAYOUT_CONTAINERS.has(dc.component))) {
921
- console.warn(`[mergeCanvasDiff] Blocked layout type change: ${existing.component}→${dc.component} on id="${dc.id}". Preserving original type.`);
932
+ blockedLayoutChanges.push({ id: dc.id, from: existing.component, to: dc.component });
922
933
  delete dc.component;
923
934
  }
924
935
  const merged = { ...existing, ...dc };
@@ -932,6 +943,14 @@ export function mergeCanvasDiff(priorComponents, diffComponents) {
932
943
  }
933
944
  }
934
945
 
946
+ // §190b — single aggregated warning for blocked layout changes.
947
+ if (blockedLayoutChanges.length > 0) {
948
+ const summary = blockedLayoutChanges
949
+ .map(b => `${b.from}→${b.to} on id="${b.id}"`)
950
+ .join(', ');
951
+ console.warn(`[mergeCanvasDiff] Blocked ${blockedLayoutChanges.length} layout type change${blockedLayoutChanges.length === 1 ? '' : 's'}: ${summary}. Preserving original types.`);
952
+ }
953
+
935
954
  // Build result: prior components (minus deleted) + new components
936
955
  const result = [];
937
956
  for (const c of priorComponents) {
@@ -197,8 +197,12 @@ describe('§168 mergeCanvasDiff — iteration handoff invariants', () => {
197
197
  const result = mergeCanvasDiff(prior, llmOutput);
198
198
  // Grid type preserved despite LLM's attempted change
199
199
  expect(result.find(c => c.id === 'root').component).toBe('Grid');
200
+ // §190b — warning is aggregated: "Blocked N layout type change(s): …"
200
201
  expect(warn).toHaveBeenCalledWith(
201
- expect.stringContaining('Blocked layout type change')
202
+ expect.stringContaining('Blocked 1 layout type change')
203
+ );
204
+ expect(warn).toHaveBeenCalledWith(
205
+ expect.stringContaining('Grid→Column on id="root"')
202
206
  );
203
207
  warn.mockRestore();
204
208
  });
@@ -3,35 +3,66 @@
3
3
  *
4
4
  * generate({ intent, mode, llmAdapter, sessionId }) -> { messages, validation, strategy, ...extra }
5
5
  *
6
- * Three reasoning layers:
6
+ * Two reasoning layers (post-§195, v0.5.6):
7
7
  * 1. Retrieval (always available) — keyword-rank the corpus, resolve top composition.
8
- * 2. LLM synthesis (when llmAdapter provided AND retrieval weak) — compose a new
9
- * composition from fragments, have the composer resolve it into A2UI messages.
10
- * 3. Session-aware iteration (when sessionId provided AND prior turns exist) —
11
- * pass prior-turn history to the LLM so follow-ups ("add a button", "hydrate
12
- * with real images") modify the existing canvas instead of regenerating.
8
+ * 2. LLM synthesis (when llmAdapter provided AND retrieval weak) — bridge to
9
+ * chunk-zettel's `composeFromIntent` (the §37 successor codepath) which
10
+ * produces a single-shot html composition from the chunk corpus.
11
+ *
12
+ * Session iteration is currently NOT history-aware in this engine. Prior turns
13
+ * are recorded for analytics + drift tracking, but turn≥2 takes the same code
14
+ * path as turn 1 (fresh retrieval → fresh synthesis). Full history-aware
15
+ * iteration (multi-turn refinement modifying an existing canvas) lives in the
16
+ * `chunk-zettel` engine via `chunk-refiner.js::refineFromIntent` — wire it via
17
+ * `engine: 'chunk-zettel'` rather than `engine: 'zettel'`.
18
+ *
19
+ * The prior fragment-graph iteration codepath (`synthesizeComposition` with
20
+ * `historySummary`) was retired in §37 (2026-05-12) when fragments retired.
21
+ * §195 (v0.5.6) cleaned up the dangling caller — turn≥2 no longer throws +
22
+ * auto-fires a `iteration-synthesis-failure` issue on every multi-turn turn.
13
23
  *
14
24
  * Strategy labels in the return:
15
25
  * - composition-match — fresh retrieval, strong match, emitted verbatim
16
- * - composition-synthesized — fresh LLM composition (no prior turns)
17
- * - composition-iterated — LLM modified prior turn's template
26
+ * - composition-synthesized — chunk-zettel single-shot synthesis
18
27
  * - fragment-candidates — retrieval weak + no LLM, returning atoms only
19
- * - synthesis-failed — LLM tried and failed validation
28
+ * - synthesis-failed — chunk-zettel tried and failed validation
20
29
  */
21
30
  import {
22
31
  getComposition,
23
32
  searchAll,
24
33
  } from './composition-library.js';
25
34
  import { resolveComposition, templateToMessages } from './composer.js';
26
- import { synthesizeComposition } from './synthesizer.js';
27
35
  import {
28
36
  recordTurn,
29
37
  getTurns,
30
- buildHistorySummary,
31
38
  } from './session-store.js';
32
- import { autoReport } from './issue-reporter.js';
33
39
  import { validateSchema } from '../../../validator/validator.js';
34
40
 
41
+ // Lazy-load the chunk-synthesizer bridge. It imports chunk-corpus data
42
+ // via composition-library's dual-mode loader, which is already top-level
43
+ // awaited; importing it here statically would not break anything but the
44
+ // async-import keeps the cold-start surface tight for the strong-match
45
+ // (no-LLM) path that doesn't need synthesis at all.
46
+ async function bridgeToChunkSynthesis({ intent, llmAdapter }) {
47
+ const { composeFromIntent } = await import('./chunk-synthesizer.js');
48
+ const result = await composeFromIntent({ intent, llmAdapter, maxAttempts: 2 });
49
+ // Shape-adapt chunk-synthesizer's `{ html, plan, source, ... }` to the
50
+ // synthesis-branch's prior `{ messages, template, synthesis }` contract.
51
+ const messages = result.html
52
+ ? [{ type: 'updateComponents', components: [{ id: 'chunk-root', component: 'article', html: result.html }] }]
53
+ : [];
54
+ return {
55
+ messages,
56
+ template: result.plan ? [{ $chunk: 'composeFromIntent', plan: result.plan }] : [],
57
+ synthesis: {
58
+ source: result.source,
59
+ score: result.score,
60
+ warnings: result.warnings || [],
61
+ scopeDrift: result.scopeDrift || null,
62
+ },
63
+ };
64
+ }
65
+
35
66
  // Composition library auto-loads at module import time via top-level `await
36
67
  // loadAll()` in composition-library.js (§72 dual-mode loader, commit
37
68
  // `76dbcff2`). The previous `ensureBooted()` here called `loadAll()` WITHOUT
@@ -72,62 +103,17 @@ function toUpdateComponentsMessages(template) {
72
103
  }
73
104
 
74
105
  export async function generateZettel({ intent, mode = 'instant', llmAdapter = null, sessionId = null } = {}) {
75
- // ── Session-aware iteration (turn > 1) ──
76
- // If we have prior turns AND an LLM, the user is almost certainly modifying
77
- // the existing canvas NEVER pick a fresh retrieved composition. Go straight
78
- // to synthesis with history context. This is what makes follow-ups work.
106
+ // ── Session iteration note (post-§195, v0.5.6) ──
107
+ // Fragment-graph history-aware iteration (`synthesizeComposition` with
108
+ // `historySummary`) was retired in §37 when fragments retired. Turn≥2 now
109
+ // takes the same path as turn 1 fresh retrieval → fresh chunk-synthesis.
110
+ // For true history-aware iteration (modify-an-existing-canvas), wire the
111
+ // request via `engine: 'chunk-zettel'` which uses `chunk-refiner.js`.
112
+ //
113
+ // Prior turns are still recorded (analytics + drift tracking via `getDrift`)
114
+ // but the iteration BRANCH is gone — no more spurious
115
+ // `iteration-synthesis-failure` auto-fires on every multi-turn request.
79
116
  const priorTurns = sessionId ? getTurns(sessionId) : [];
80
- const hasHistory = priorTurns.length > 0;
81
-
82
- if (hasHistory && llmAdapter) {
83
- try {
84
- const historySummary = buildHistorySummary(sessionId, 3);
85
- const synth = await synthesizeComposition({ intent, llmAdapter, historySummary });
86
- const validation = validateSchema(synth.messages, { intent });
87
- const fragments = (synth.template || []).filter((n) => n.$fragment).map((n) => n.$fragment);
88
- recordTurn(sessionId, {
89
- intent,
90
- messages: synth.messages,
91
- template: synth.template,
92
- composition: null,
93
- strategy: 'composition-iterated',
94
- fragments,
95
- validation,
96
- });
97
- return {
98
- messages: synth.messages,
99
- validation,
100
- strategy: 'composition-iterated',
101
- retrieval: { hit: false, rank: null, candidate: null, reason: `iteration on turn ${priorTurns.length + 1}` },
102
- composition: null,
103
- fragments_used: fragments,
104
- synthesized_template: synth.template,
105
- synthesis: synth.synthesis,
106
- sessionTurns: priorTurns.length + 1,
107
- };
108
- } catch (err) {
109
- // If iteration synthesis fails, fall through to the normal path. Record
110
- // the failure so the next turn can see we tried, and auto-fire an issue
111
- // ticket so synthesis-owners can investigate the failure post-hoc.
112
- console.error('[zettel] iteration synthesis failed:', err.message);
113
- try {
114
- await autoReport(
115
- 'iteration-synthesis-failure',
116
- {
117
- intent,
118
- turn: priorTurns.length + 1,
119
- state_id: sessionId,
120
- body: `Auto-fired by generator-adapter. Iteration synthesis threw on turn ${priorTurns.length + 1}.\n\nError: \`${err.message}\``,
121
- tags: ['generator-adapter', `turn-${priorTurns.length + 1}`],
122
- },
123
- { evalMode: mode === 'eval' }
124
- );
125
- } catch (reportErr) {
126
- // Never let issue-reporting crash the request path.
127
- console.error('[zettel] autoReport failed:', reportErr.message);
128
- }
129
- }
130
- }
131
117
 
132
118
  const hits = searchAll(intent, { limit: 5 });
133
119
  const composition = hits.find((h) => h.type === 'composition');
@@ -163,19 +149,22 @@ export async function generateZettel({ intent, mode = 'instant', llmAdapter = nu
163
149
  };
164
150
  }
165
151
 
166
- // ── Weak/no retrieval: try LLM synthesis if available (fresh, no history) ──
152
+ // ── Weak/no retrieval: bridge to chunk-zettel synthesis (the §37 successor) ──
167
153
  if (llmAdapter && mode !== 'instant-only') {
168
154
  try {
169
- const synth = await synthesizeComposition({ intent, llmAdapter });
155
+ const synth = await bridgeToChunkSynthesis({ intent, llmAdapter });
170
156
  const validation = validateSchema(synth.messages, { intent });
171
- const fragments = (synth.template || []).filter((n) => n.$fragment).map((n) => n.$fragment);
157
+ // Chunk-bridge produces an html-bearing single component, not
158
+ // resolved fragment instances. `fragments_used` is empty by design;
159
+ // consumers tracking fragment usage should switch to the
160
+ // chunk-zettel engine which surfaces `_debug.plan` directly.
172
161
  recordTurn(sessionId, {
173
162
  intent,
174
163
  messages: synth.messages,
175
164
  template: synth.template,
176
165
  composition: null,
177
166
  strategy: 'composition-synthesized',
178
- fragments,
167
+ fragments: [],
179
168
  validation,
180
169
  });
181
170
  return {
@@ -189,7 +178,7 @@ export async function generateZettel({ intent, mode = 'instant', llmAdapter = nu
189
178
  reason: composition ? `top score ${composition.score} below threshold ${STRONG_MATCH_THRESHOLD}` : 'no composition retrieved',
190
179
  },
191
180
  composition: null,
192
- fragments_used: fragments,
181
+ fragments_used: [],
193
182
  synthesized_template: synth.template,
194
183
  synthesis: synth.synthesis,
195
184
  candidates: hits,
@@ -1,22 +0,0 @@
1
- /**
2
- * Composition synthesizer — RETIRED (§37, 2026-05-12).
3
- *
4
- * Previously: when zettel retrieval was weak, this called the LLM to
5
- * assemble a NEW composition from the fragment catalog (technique B/C
6
- * fragment-graph synthesis). Fragments are retired and there's no
7
- * fragment catalog to draw from anymore. The deterministic path
8
- * (`resolveComposition` on a retrieved composition) still works.
9
- *
10
- * Until a chunk-based synthesis fallback lands, this export throws on
11
- * call. Callers should catch and fall through to monolithic-pro's
12
- * generation path. generator-adapter.js handles this gracefully.
13
- */
14
-
15
- export async function synthesizeComposition() {
16
- const err = new Error(
17
- 'synthesizeComposition is retired (§37 fragment retirement). ' +
18
- 'Use chunk-zettel or monolithic-pro for LLM-driven composition.'
19
- );
20
- err.code = 'SYNTHESIZER_RETIRED';
21
- throw err;
22
- }