@adia-ai/a2ui-compose 0.5.5 → 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,46 @@ 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._
14
-
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`).
15
50
  ## [0.5.5] - 2026-05-14
16
51
 
17
52
  ### Changed — §179 (v0.5.5) — principled `deprecated:` yaml field
@@ -27,7 +62,6 @@ Build pipeline (`scripts/build/components.mjs::compileProp`) collects deprecatio
27
62
  Demonstrated on `Avatar.name`. Verified via `buildSystemPrompt`: `name:String[] (deprecated, do not use)` surfaces in CORPUS CONTEXT.
28
63
 
29
64
  Commit `0379dd498`. See root CHANGELOG and journal §179 for full context.
30
-
31
65
  ## [0.5.4] - 2026-05-14
32
66
 
33
67
  ### Fixed — §163 transpiler prop-fidelity; catalog-driven extractor closes the harvester drop class (v0.5.4)
@@ -201,7 +235,6 @@ No compose source change in §173/§175 — both detect future drift in compose'
201
235
  catalog-vs-prompt + registry-vs-catalog invariants.
202
236
 
203
237
  ### Changed — `version`: `0.5.3` → `0.5.4`.
204
-
205
238
  ## [0.5.3] - 2026-05-14
206
239
 
207
240
  ### Fixed — Deterministic chunk-loading order in zettel composition library (§160, v0.5.3)
@@ -224,7 +257,6 @@ Finalizes the v0.6.0 deprecation schedule for the last two `_debug.*` fields tha
224
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.
225
258
 
226
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).
227
-
228
260
  ## [0.5.2] - 2026-05-13
229
261
 
230
262
  ### Changed — `plan` graduates from `_debug.*` to first-class on free-form-composed result (§107a infra, v0.5.2)
@@ -259,7 +291,6 @@ Expected impact: lifts substitution ratio 27.4% → ~35-40% (target ≥30%); F1
259
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.
260
292
 
261
293
  Default behavior unchanged when no override set — the v0.5.1 §108 Haiku pin holds for every consumer that doesn't explicitly override.
262
-
263
294
  ## [0.5.1] - 2026-05-13
264
295
 
265
296
  ### Added — Free-form composer INTENT-PARAPHRASE block + paraphrase-retry (§106, v0.5.1)
@@ -281,7 +312,6 @@ Consumers passing `ctx.llmAdapter` keep getting their adapter via the mint-failu
281
312
  ### Coverage at v0.5.1 cut
282
313
 
283
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).
284
-
285
315
  ## [0.5.0] - 2026-05-13
286
316
 
287
317
  ### Added — Free-form composer auto-grouping (§103, v0.5.0)
@@ -299,17 +329,14 @@ Schema validation rejects unknown target keys; downgrade warnings fire when a ta
299
329
  ### Coverage at v0.5.0 cut
300
330
 
301
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).
302
-
303
332
  ## [0.4.9] - 2026-05-13
304
333
 
305
334
  _No pending changes._
306
-
307
335
  ## [0.4.8] - 2026-05-12
308
336
 
309
337
  ### Fixed — `strategies/zettel/generator-adapter.js` `ensureBooted()` race (§87a, v0.4.8)
310
338
 
311
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.
312
-
313
340
  ## [0.4.7] - 2026-05-12
314
341
 
315
342
  ### Changed — `strategies/monolithic/_shared.js` `getComponentCatalog()` reads canonical catalog (§72)
@@ -317,7 +344,6 @@ The zettel composition library's lazy boot had a race condition where concurrent
317
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.
318
345
 
319
346
  Closes the §65 carry-over from v0.4.6 — `@adia-ai/a2ui-corpus` `patterns/` + `compositions/` are now deleted from disk + tarball.
320
-
321
347
  ## [0.4.6] - 2026-05-12
322
348
 
323
349
  ### Changed — `core/` retirement follow-through (§64, v0.4.6)
@@ -353,7 +379,6 @@ Closes the catalog-drift surface that §56 explicitly deferred ("different schem
353
379
  5. `const` fields (e.g. `{const: 'Button'}`) are discriminator-only. Skip.
354
380
 
355
381
  Source: `packages/a2ui/compose/strategies/monolithic/_shared.js` (+91, -2 lines, commit `afda98f5`).
356
-
357
382
  ## [0.4.5] - 2026-05-12
358
383
 
359
384
  ### Changed — GenUI overhaul prompt-engineering (§56, v0.4.5)
@@ -369,7 +394,6 @@ Source: `packages/a2ui/compose/strategies/monolithic/_shared.js` (+91, -2 lines,
369
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.
370
395
 
371
396
  See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the v0.4.5 overhaul arc + apps/genui/CHANGELOG.md `[Unreleased]` for the per-§ rollup.
372
-
373
397
  ## [0.4.4] - 2026-05-12
374
398
 
375
399
  ### Changed
@@ -386,7 +410,6 @@ See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the v0.4.5 ove
386
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.
387
411
 
388
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.
389
-
390
413
  ## [0.4.3] - 2026-05-11
391
414
 
392
415
  ### Fixed
@@ -396,7 +419,6 @@ See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the cross-cutt
396
419
  ### Lockstep
397
420
 
398
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.
399
-
400
422
  ## [0.4.2] - 2026-05-11
401
423
 
402
424
  ### Ride-along (no source changes)
@@ -404,7 +426,6 @@ See root [CHANGELOG.md `[Unreleased]`](../../../CHANGELOG.md) for the cross-cutt
404
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.
405
427
 
406
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.
407
-
408
429
  ## [0.4.1] - 2026-05-10
409
430
 
410
431
  ### Ride-along (no source changes)
@@ -412,7 +433,6 @@ Internal `@adia-ai/*` dep ranges stay at `^0.4.0` (patch-cut asymmetry — `^0.4
412
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.
413
434
 
414
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.
415
-
416
436
  ## [0.4.0] - 2026-05-10
417
437
 
418
438
  ### Ride-along (no source changes)
@@ -420,25 +440,21 @@ Internal `@adia-ai/*` dep ranges bumped from `^0.4.0` to `^0.4.1`. See root [CHA
420
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.
421
441
 
422
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.
423
-
424
443
  ## [0.3.6] - 2026-05-10
425
444
 
426
445
  ### Ride-along (no source changes)
427
446
 
428
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.
429
-
430
448
  ## [0.3.5] - 2026-05-07
431
449
 
432
450
  ### Ride-along (no source changes)
433
451
 
434
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.
435
-
436
453
  ## [0.3.4] - 2026-05-07
437
454
 
438
455
  ### Ride-along (no source changes)
439
456
 
440
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.
441
-
442
458
  ## [0.3.3] - 2026-05-07
443
459
 
444
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`).
@@ -483,7 +499,6 @@ Lockstep version bump only — source byte-identical to v0.3.3. Internal `@adia-
483
499
  for the lifetime of the process. Trade-offs: good for long-running
484
500
  MCP, bad for tests/hot-reload. To force a reload, call `loadAll()`
485
501
  directly. (closes backlog #100)
486
-
487
502
  ## [0.3.2] - 2026-05-06
488
503
 
489
504
  **9-package lockstep patch cut to v0.3.2.** All lockstep members share
@@ -510,7 +525,6 @@ Internal `@adia-ai/*` dep ranges unchanged at `^0.3.0`.
510
525
  ### Changed
511
526
 
512
527
  - `version`: `0.3.1` → `0.3.2`.
513
-
514
528
  ## [0.3.1] - 2026-05-06
515
529
 
516
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).
@@ -521,7 +535,6 @@ This package itself ships **no source changes** in v0.3.1. The cut bumps version
521
535
 
522
536
  - `version`: `0.3.0` → `0.3.1`.
523
537
  - Internal `@adia-ai/*` dep ranges: unchanged at `^0.3.0` (covers `0.3.1` under semver — patch-cut asymmetry).
524
-
525
538
  ## [0.3.0] - 2026-05-05
526
539
 
527
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`.
@@ -567,7 +580,6 @@ This is a **minor cut on top of v0.2.5 with one BREAKING change**: the `./llm` s
567
580
  ```
568
581
 
569
582
  Plus rewrite imports as shown above. The change is mechanical.
570
-
571
583
  ## [0.2.5] - 2026-05-04
572
584
 
573
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).
@@ -578,7 +590,6 @@ This is a **patch cut on top of v0.2.4, no BREAKING changes.** Substantive conte
578
590
 
579
591
  - `version`: `0.2.4` → `0.2.5`.
580
592
  - Internal `@adia-ai/*` dep ranges: unchanged at `^0.2.0` (covers `0.2.5` under semver — patch-cut asymmetry).
581
-
582
593
  ## [0.2.4] - 2026-05-04
583
594
 
584
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).
@@ -597,7 +608,6 @@ _Nothing yet._
597
608
  ---
598
609
 
599
610
  ---
600
-
601
611
  ## [0.2.3] - 2026-05-04
602
612
 
603
613
  **Lockstep cut.** All 8 published `@adia-ai/*` packages bump
@@ -615,7 +625,6 @@ Patch cut — no breaking changes.
615
625
  ### No source changes
616
626
 
617
627
  `@adia-ai/a2ui-compose` source is byte-identical to `0.2.2`. The cut bumps version + the internal dep range entries only.
618
-
619
628
  ## [0.2.2] - 2026-05-02
620
629
 
621
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.
@@ -639,7 +648,6 @@ Patch cut — no breaking changes.
639
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`.
640
649
 
641
650
  ---
642
-
643
651
  ## [0.2.1] - 2026-05-02
644
652
 
645
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.
@@ -678,7 +686,6 @@ The synthesizer now captures a `retrievalTrace` per attempt: Tier-1 hits with sc
678
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.
679
687
 
680
688
  ---
681
-
682
689
  ## [0.2.0] - 2026-05-02
683
690
 
684
691
  **Lockstep cut + boundary cleanup.** All 8 published `@adia-ai/*`
@@ -733,7 +740,6 @@ cycle, but should update on next touch:
733
740
  ```
734
741
 
735
742
  ---
736
-
737
743
  ## [0.1.0] - 2026-04-28
738
744
 
739
745
  **Multi-turn refinement engine (Phase A).** Adds three new modules to the
@@ -820,7 +826,6 @@ Additive surface; no breaking changes. Existing consumers calling
820
826
  `composeFromIntent` continue to work unchanged.
821
827
 
822
828
  ---
823
-
824
829
  ## [0.0.1] - 2026-04-24
825
830
 
826
831
  First public release. Framework-agnostic compose engine for the
@@ -879,7 +884,6 @@ behind a plug-in engine registry.
879
884
  - Stale internal-identifier references in comments + doc strings swept to the current naming.
880
885
 
881
886
  ---
882
-
883
887
  ## [0.1.0] — internal baseline (unreleased)
884
888
 
885
889
  Initial version at the time the monorepo was established. Contains:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adia-ai/a2ui-compose",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
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": {
@@ -903,6 +903,15 @@ export function mergeCanvasDiff(priorComponents, diffComponents) {
903
903
  const modifiedIds = new Set();
904
904
  const newComponents = [];
905
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
+
906
915
  for (const dc of diffComponents) {
907
916
  if (!dc || !dc.id) continue;
908
917
 
@@ -920,7 +929,7 @@ export function mergeCanvasDiff(priorComponents, diffComponents) {
920
929
  const LAYOUT_CONTAINERS = new Set(['Grid', 'Row', 'Column', 'Stack']);
921
930
  if (dc.component && dc.component !== existing.component &&
922
931
  (LAYOUT_CONTAINERS.has(existing.component) || LAYOUT_CONTAINERS.has(dc.component))) {
923
- 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 });
924
933
  delete dc.component;
925
934
  }
926
935
  const merged = { ...existing, ...dc };
@@ -934,6 +943,14 @@ export function mergeCanvasDiff(priorComponents, diffComponents) {
934
943
  }
935
944
  }
936
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
+
937
954
  // Build result: prior components (minus deleted) + new components
938
955
  const result = [];
939
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
- }