@12ui/design 0.2.36 → 0.2.38

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.
Files changed (101) hide show
  1. package/SKILL.md +1 -1
  2. package/dist/branch-client.d.ts +12 -0
  3. package/dist/branch-client.d.ts.map +1 -1
  4. package/dist/branch-client.js.map +1 -1
  5. package/dist/branch-completion.d.ts.map +1 -1
  6. package/dist/branch-completion.js +1 -0
  7. package/dist/branch-completion.js.map +1 -1
  8. package/dist/branch-execution-types.d.ts +3 -0
  9. package/dist/branch-execution-types.d.ts.map +1 -1
  10. package/dist/branch-execution.d.ts.map +1 -1
  11. package/dist/branch-execution.js +13 -0
  12. package/dist/branch-execution.js.map +1 -1
  13. package/dist/branch-identity.d.ts +1 -1
  14. package/dist/branch-identity.js +1 -1
  15. package/dist/branch-plan.d.ts +3 -1
  16. package/dist/branch-plan.d.ts.map +1 -1
  17. package/dist/branch-plan.js +2 -0
  18. package/dist/branch-plan.js.map +1 -1
  19. package/dist/branch-run-record.d.ts +18 -0
  20. package/dist/branch-run-record.d.ts.map +1 -1
  21. package/dist/branch-run-record.js.map +1 -1
  22. package/dist/branch-winner.d.ts.map +1 -1
  23. package/dist/branch-winner.js +7 -0
  24. package/dist/branch-winner.js.map +1 -1
  25. package/dist/cli-branch-command.d.ts.map +1 -1
  26. package/dist/cli-branch-command.js +61 -7
  27. package/dist/cli-branch-command.js.map +1 -1
  28. package/dist/cli-help/design.d.ts +1 -1
  29. package/dist/cli-help/design.d.ts.map +1 -1
  30. package/dist/cli-help/design.js +1 -0
  31. package/dist/cli-help/design.js.map +1 -1
  32. package/dist/cli-help/index.d.ts +1 -1
  33. package/dist/cli-prototype-command.d.ts +3 -2
  34. package/dist/cli-prototype-command.d.ts.map +1 -1
  35. package/dist/cli-prototype-command.js +9 -3
  36. package/dist/cli-prototype-command.js.map +1 -1
  37. package/dist/cli-review-command.js +2 -2
  38. package/dist/cli-review-command.js.map +1 -1
  39. package/dist/index.d.ts +2 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/prototype-branch-contract.d.ts +67 -0
  44. package/dist/prototype-branch-contract.d.ts.map +1 -0
  45. package/dist/prototype-branch-contract.js +403 -0
  46. package/dist/prototype-branch-contract.js.map +1 -0
  47. package/dist/prototype-poststep.d.ts +27 -0
  48. package/dist/prototype-poststep.d.ts.map +1 -0
  49. package/dist/prototype-poststep.js +97 -0
  50. package/dist/prototype-poststep.js.map +1 -0
  51. package/dist/prototype-run-record.d.ts +92 -0
  52. package/dist/prototype-run-record.d.ts.map +1 -0
  53. package/dist/prototype-run-record.js +236 -0
  54. package/dist/prototype-run-record.js.map +1 -0
  55. package/dist/prototype-runner.d.ts +13 -1
  56. package/dist/prototype-runner.d.ts.map +1 -1
  57. package/dist/prototype-runner.js +79 -20
  58. package/dist/prototype-runner.js.map +1 -1
  59. package/open-design.json +2 -2
  60. package/package.json +1 -1
  61. package/prototype-kit/analyze.mjs +48 -6
  62. package/prototype-kit/build.mjs +249 -65
  63. package/prototype-kit/inventory.mjs +290 -2
  64. package/prototype-kit/lib/holding-composition.mjs +67 -0
  65. package/prototype-kit/lib/nav-active-state.mjs +67 -0
  66. package/prototype-kit/lib/review-framing.mjs +72 -0
  67. package/prototype-kit/lib/runtime-check.mjs +361 -28
  68. package/prototype-kit/lib/shell-consistency.mjs +68 -0
  69. package/prototype-kit/lib/shell-stitching.mjs +221 -0
  70. package/prototype-kit/runtime/proto.css +8 -3
  71. package/prototype-kit/tests/README.md +4 -1
  72. package/prototype-kit/tests/adversarial-runtime.mjs +85 -2
  73. package/prototype-kit/tests/fixtures/holding-runtime/PROVENANCE.md +22 -0
  74. package/prototype-kit/tests/fixtures/holding-runtime/band-ownership-fire.html +10 -0
  75. package/prototype-kit/tests/fixtures/holding-runtime/band-ownership-silent.html +10 -0
  76. package/prototype-kit/tests/fixtures/holding-runtime/declared-width-fire.html +11 -0
  77. package/prototype-kit/tests/fixtures/holding-runtime/declared-width-silent.html +11 -0
  78. package/prototype-kit/tests/fixtures/holding-runtime/ground-registration-mismatch-fire.html +5 -0
  79. package/prototype-kit/tests/fixtures/holding-runtime/ground-registration-mismatch-silent.html +5 -0
  80. package/prototype-kit/tests/fixtures/holding-runtime/holding-contrast-fire.html +5 -0
  81. package/prototype-kit/tests/fixtures/holding-runtime/holding-contrast-silent.html +5 -0
  82. package/prototype-kit/tests/fixtures/holding-runtime/positioned-ground-fire.html +5 -0
  83. package/prototype-kit/tests/fixtures/holding-runtime/positioned-ground-silent.html +5 -0
  84. package/prototype-kit/tests/fixtures/holding-runtime/shell-geometry-fire.html +5 -0
  85. package/prototype-kit/tests/fixtures/holding-runtime/shell-geometry-silent.html +5 -0
  86. package/prototype-kit/tests/fixtures/holding-runtime/shell-intersection-fire.html +5 -0
  87. package/prototype-kit/tests/fixtures/holding-runtime/shell-intersection-silent.html +5 -0
  88. package/prototype-kit/tests/fixtures/runtime/history.html +1 -1
  89. package/prototype-kit/tests/fixtures/shell-nav-runtime/PROVENANCE.md +6 -0
  90. package/prototype-kit/tests/fixtures/shell-nav-runtime/active-fire.html +3 -0
  91. package/prototype-kit/tests/fixtures/shell-nav-runtime/proto-manifest.json +20 -0
  92. package/prototype-kit/tests/fixtures/shell-nav-runtime/shell-fire.html +3 -0
  93. package/prototype-kit/tests/fixtures/shell-nav-runtime/shell-silent.html +3 -0
  94. package/prototype-kit/tests/fixtures/shell-nav-runtime/unlinked-silent.html +3 -0
  95. package/prototype-kit/tests/fixtures/three-shell/pages/p0-home.html +3 -3
  96. package/prototype-kit/tests/fixtures/three-shell/pages/p1-reports.html +3 -3
  97. package/prototype-kit/tests/lib/run-process.mjs +8 -2
  98. package/prototype-kit/tests/offline-check.mjs +143 -0
  99. package/prototype-kit/tests/population-sweep.mjs +26 -7
  100. package/prototype-kit/tests/rules-check.mjs +80 -0
  101. package/prototype-kit/verify-runtime.mjs +5 -1
@@ -0,0 +1,221 @@
1
+ import { declaredActiveMarkers, linkedNavForUnit } from './nav-active-state.mjs';
2
+ import { needsCanonicalShell, shellForBand } from './shell-consistency.mjs';
3
+ import { shouldTransplantShell } from './shells.mjs';
4
+ import { normalizeText } from './text.mjs';
5
+
6
+ function bandRoot(document, key) {
7
+ const roots = [...document.querySelectorAll('.ly-band[data-ly-band]')];
8
+ if (roots.length === 0) return key === 'native' ? document.body : null;
9
+ return roots.find((root) => root.getAttribute('data-ly-band') === key) || null;
10
+ }
11
+
12
+ function shellHost(root) {
13
+ const screen = root?.querySelector('.generated-screen') || (root?.tagName === 'BODY' ? root : null);
14
+ return screen?.querySelector('.ly-anchor-canvas') || screen || null;
15
+ }
16
+
17
+ function navItems(shell, navRows, region) {
18
+ const names = new Set(navRows.filter((row) => row.region === region).map((row) => normalizeText(row.text)));
19
+ return (shell?.items || []).filter((item) => item.text && names.has(normalizeText(item.text)));
20
+ }
21
+
22
+ function markerSnapshots({ document, shell, markers, rulesFor, styleText }) {
23
+ const css = styleText(document);
24
+ return markers.flatMap((marker) => {
25
+ const element = document.getElementById(marker.id);
26
+ if (!element) return [];
27
+ return [{
28
+ ...marker,
29
+ html: element.outerHTML,
30
+ css: rulesFor(css, marker.id),
31
+ item: shell.items.find((item) => item.id === marker.id),
32
+ targetItem: shell.items.find((item) => item.id === marker.targetId),
33
+ }];
34
+ });
35
+ }
36
+
37
+ function removeMarkers(document, markers) {
38
+ for (const marker of markers) document.getElementById(marker.id)?.remove();
39
+ }
40
+
41
+ function placeMarker({ shellElement, shell, marker, targetItem, extraCss }) {
42
+ shellElement.insertAdjacentHTML('beforeend', marker.html);
43
+ if (marker.css) extraCss.push(marker.css);
44
+ const sourceTarget = marker.targetItem;
45
+ const source = marker.item;
46
+ if (!source) return;
47
+ const desiredX = targetItem && sourceTarget ? targetItem.x + (source.x - sourceTarget.x) : source.x;
48
+ const desiredY = targetItem && sourceTarget ? targetItem.y + (source.y - sourceTarget.y) : source.y;
49
+ extraCss.push(`#${marker.id}{position:absolute!important;left:${desiredX - shell.box[0]}px!important;top:${desiredY - shell.box[1]}px!important;`
50
+ + `width:${source.w}px!important;height:${source.h}px!important;margin:0!important;translate:none!important}`);
51
+ }
52
+
53
+ function decisionForRegion({ unit, donorPage, recipientPage, region, canonicalShell }) {
54
+ if (recipientPage.pageId === donorPage.pageId) return { commit: false, reason: 'canonical-page' };
55
+ const semantic = unit.states.flatMap((state) => state.shell).find((shell) => shell.region === region);
56
+ if (!semantic) return { commit: false, reason: 'absent-analyzed-region' };
57
+ if (!shouldTransplantShell(semantic.box, canonicalShell.box)) {
58
+ return { commit: false, reason: 'incompatible-analyzed-geometry' };
59
+ }
60
+ const pairs = [];
61
+ for (const band of recipientPage.bands || []) {
62
+ const recipient = shellForBand(recipientPage, band.key, region);
63
+ const canonical = shellForBand(donorPage, band.key, region);
64
+ if (!recipient || !canonical) return { commit: false, reason: `missing-${band.key}-declaration` };
65
+ if (!shouldTransplantShell(recipient.box, canonical.box)) {
66
+ return { commit: false, reason: `incompatible-${band.key}-geometry` };
67
+ }
68
+ pairs.push({ key: band.key, recipient, canonical });
69
+ }
70
+ return { commit: pairs.some((pair) => needsCanonicalShell(pair.recipient, pair.canonical)), reason: 'compatible', pairs };
71
+ }
72
+
73
+ /**
74
+ * Atomically commits one donor page's complete region across every declared
75
+ * band of a compatible member page. A failed band proof makes the whole region
76
+ * a literal no-write path.
77
+ */
78
+ export function stitchUnitShells({
79
+ document,
80
+ donorDocument,
81
+ donorPage,
82
+ recipientPage,
83
+ unit,
84
+ canonicalShell,
85
+ navRows,
86
+ rulesFor,
87
+ styleText,
88
+ }) {
89
+ const extraCss = [];
90
+ const bands = new Map((recipientPage.bands || []).map((band) => [band.key, { key: band.key, regions: [] }]));
91
+ const decisions = [];
92
+ const sourceMarkerSnapshots = new Map();
93
+ for (const region of Object.keys(canonicalShell || {})) for (const band of recipientPage.bands || []) {
94
+ const sourceShell = shellForBand(recipientPage, band.key, region);
95
+ if (!sourceShell) continue;
96
+ const sourceItems = navItems(sourceShell, navRows, region);
97
+ sourceMarkerSnapshots.set(`${band.key}:${region}`, markerSnapshots({
98
+ document,
99
+ shell: sourceShell,
100
+ markers: declaredActiveMarkers(sourceShell, sourceItems),
101
+ rulesFor,
102
+ styleText,
103
+ }));
104
+ }
105
+ let changes = 0;
106
+ for (const [region, canonicalSemantic] of Object.entries(canonicalShell || {})) {
107
+ const decision = decisionForRegion({
108
+ unit,
109
+ donorPage,
110
+ recipientPage,
111
+ region,
112
+ canonicalShell: canonicalSemantic,
113
+ });
114
+ decisions.push({ region, commit: decision.commit, reason: decision.reason });
115
+ const pairs = decision.pairs || (recipientPage.bands || []).flatMap((band) => {
116
+ const recipient = shellForBand(recipientPage, band.key, region);
117
+ const canonical = shellForBand(donorPage, band.key, region);
118
+ return recipient && canonical ? [{ key: band.key, recipient, canonical }] : [];
119
+ });
120
+ for (const pair of pairs) {
121
+ const sourceNavItems = navItems(pair.recipient, navRows, region);
122
+ const canonicalNavItems = navItems(pair.canonical, navRows, region);
123
+ const sourceMarkers = declaredActiveMarkers(pair.recipient, sourceNavItems);
124
+ const canonicalMarkers = declaredActiveMarkers(pair.canonical, canonicalNavItems);
125
+ const linkedNav = linkedNavForUnit({ navRows, unit, sourceMarkers, region });
126
+ const outputNavItems = decision.commit ? canonicalNavItems : sourceNavItems;
127
+ const targetItem = linkedNav
128
+ ? outputNavItems.find((item) => normalizeText(item.text) === normalizeText(linkedNav.text)) || null
129
+ : null;
130
+ let outputShellId = pair.recipient.containerId;
131
+ let outputMarkers = sourceMarkers;
132
+ if (decision.commit) {
133
+ const sourceSnapshots = sourceMarkerSnapshots.get(`${pair.key}:${region}`) || [];
134
+ const donorSnapshots = markerSnapshots({
135
+ document: donorDocument,
136
+ shell: pair.canonical,
137
+ markers: canonicalMarkers,
138
+ rulesFor,
139
+ styleText,
140
+ });
141
+ const recipientElement = document.getElementById(pair.recipient.containerId);
142
+ const donorElement = donorDocument.getElementById(pair.canonical.containerId);
143
+ const root = bandRoot(document, pair.key);
144
+ const host = shellHost(root);
145
+ const alreadyPlaced = document.getElementById(pair.canonical.containerId);
146
+ if (!donorElement || !root || !host || (!root.contains(recipientElement) && !root.contains(alreadyPlaced))) {
147
+ throw new Error(`Canonical shell commit lost declared ${region} DOM in band ${pair.key}.`);
148
+ }
149
+ if (recipientElement && root.contains(recipientElement)) {
150
+ const recipientId = recipientElement.id;
151
+ recipientElement.insertAdjacentHTML('beforebegin', donorElement.outerHTML);
152
+ recipientElement.replaceChildren();
153
+ if (recipientId === pair.canonical.containerId) {
154
+ const placeholderId = `${recipientId}-proto-shell-space`;
155
+ const placeholderCss = rulesFor(styleText(document), recipientId)
156
+ .replaceAll(`#${recipientId}`, `#${placeholderId}`);
157
+ recipientElement.id = placeholderId;
158
+ if (placeholderCss) extraCss.push(placeholderCss);
159
+ }
160
+ recipientElement.setAttribute('data-proto-shell-space', region);
161
+ recipientElement.setAttribute('aria-hidden', 'true');
162
+ extraCss.push(`#${recipientElement.id}{visibility:hidden!important;pointer-events:none!important}`);
163
+ }
164
+ outputShellId = pair.canonical.containerId;
165
+ const outputShell = document.getElementById(outputShellId);
166
+ if (!outputShell || !root.contains(outputShell)) {
167
+ throw new Error(`Canonical shell commit did not place ${region} in band ${pair.key}.`);
168
+ }
169
+ extraCss.push(rulesFor(styleText(donorDocument), pair.canonical.containerId));
170
+ const donorBand = donorPage.bands.find((band) => band.key === pair.key);
171
+ const parentShell = donorBand?.shells.find((candidate) => {
172
+ if (candidate.containerId === pair.canonical.containerId) return false;
173
+ const candidateElement = donorDocument.getElementById(candidate.containerId);
174
+ return candidateElement?.contains(donorElement);
175
+ }) || null;
176
+ if (!parentShell && outputShell.parentElement !== host) host.appendChild(outputShell);
177
+ const [x, y] = parentShell
178
+ ? [
179
+ pair.canonical.box[0] - parentShell.box[0],
180
+ pair.canonical.box[1] - parentShell.box[1],
181
+ ]
182
+ : pair.canonical.box;
183
+ extraCss.push(`#${pair.canonical.containerId}{position:absolute!important;left:${x}px!important;top:${y}px!important;`
184
+ + 'margin:0!important;z-index:100000!important}');
185
+ removeMarkers(document, canonicalMarkers);
186
+ const matchingSource = linkedNav
187
+ ? sourceSnapshots.find((marker) => normalizeText(marker.targetText) === normalizeText(linkedNav.text))
188
+ : null;
189
+ const markerToPlace = matchingSource || (linkedNav ? donorSnapshots[0] : null);
190
+ if (markerToPlace) {
191
+ placeMarker({ shellElement: outputShell, shell: pair.canonical, marker: markerToPlace, targetItem, extraCss });
192
+ outputMarkers = [{ ...markerToPlace, id: markerToPlace.id }];
193
+ } else if (!linkedNav) {
194
+ for (const marker of sourceSnapshots) {
195
+ placeMarker({ shellElement: outputShell, shell: pair.canonical, marker, targetItem: null, extraCss });
196
+ }
197
+ outputMarkers = sourceSnapshots;
198
+ } else outputMarkers = [];
199
+ changes++;
200
+ }
201
+ const activeMarker = linkedNav
202
+ ? outputMarkers.find((marker) => normalizeText(marker.targetText) === normalizeText(linkedNav.text))
203
+ || outputMarkers[0]
204
+ || null
205
+ : null;
206
+ bands.get(pair.key)?.regions.push({
207
+ region,
208
+ shellId: outputShellId,
209
+ canonicalSignature: pair.canonical.signature,
210
+ consistencyStatus: decision.commit || decision.reason === 'canonical-page' ? 'canonical' : 'silent',
211
+ activeMarkerIds: outputMarkers.map((marker) => marker.id),
212
+ active: linkedNav && targetItem && activeMarker
213
+ ? { status: 'linked', navText: linkedNav.text, navId: targetItem.id, markerId: activeMarker.id }
214
+ : linkedNav ? { status: 'unproven', navText: linkedNav.text, navId: targetItem?.id || null, markerId: null }
215
+ : { status: 'silent', navText: null, navId: null, markerId: null },
216
+ changed: decision.commit,
217
+ });
218
+ }
219
+ }
220
+ return { extraCss, bands: [...bands.values()], decisions, changes };
221
+ }
@@ -1,7 +1,8 @@
1
1
  /* Prototype runtime chrome. Colours/fonts come from CSS variables set from the page's own tokens. */
2
- .proto-clickable { cursor: pointer; }
2
+ .proto-clickable { color: inherit; cursor: pointer; }
3
3
  .proto-clickable:hover { background-color: rgba(0, 0, 0, 0.035); }
4
- p.proto-clickable:hover { text-decoration: underline; text-underline-offset: 3px; }
4
+ a.proto-clickable:hover { text-decoration: underline; text-underline-offset: 3px; }
5
+ .proto-clickable:focus-visible { outline: 2px solid var(--proto-accent); outline-offset: 3px; }
5
6
 
6
7
  #proto-overlay-root { position: absolute; inset: 0; z-index: 2147483000; pointer-events: none; }
7
8
  #proto-overlay-root > * { pointer-events: auto; }
@@ -37,10 +38,14 @@ p.proto-clickable:hover { text-decoration: underline; text-underline-offset: 3px
37
38
  .proto-holding-sub { font-size: 15px; line-height: 1.5; color: var(--proto-muted); margin: 0 0 28px; max-width: 560px; }
38
39
  .proto-holding-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
39
40
  .proto-holding-card { background: var(--proto-surface); border: 1px solid var(--proto-border); border-radius: var(--proto-radius); padding: 20px; min-height: 140px; }
40
- .proto-holding-card-title { font-size: 14px; font-weight: 600; color: var(--proto-heading); margin: 0 0 10px; }
41
41
  .proto-holding-card-line { height: 10px; border-radius: 5px; background: rgba(0,0,0,0.06); margin: 8px 0; }
42
42
  .proto-holding-card-line.short { width: 60%; }
43
43
  .proto-holding-note { margin-top: 24px; font-size: 12px; color: var(--proto-muted); }
44
+ .proto-holding-block[data-proto-holding-layout="mobile"] { max-width:calc(100vw - 40px); }
45
+ .proto-holding-block[data-proto-holding-layout="mobile"] .proto-holding-cards { grid-template-columns:1fr; gap:10px; }
46
+ .proto-holding-block[data-proto-holding-layout="mobile"] .proto-holding-card { min-height:72px; padding:14px; }
47
+ .proto-holding-block[data-proto-holding-layout="mobile"] .proto-holding-sub { margin-bottom:18px; }
48
+ .proto-holding-block[data-proto-holding-layout="mobile"] .proto-holding-note { margin-top:16px; }
44
49
 
45
50
  @keyframes proto-fade { from { opacity: 0; } to { opacity: 1; } }
46
51
  @keyframes proto-pop { from { opacity: 0; transform: translateY(4px) scale(0.985); } to { opacity: 1; transform: none; } }
@@ -25,4 +25,7 @@ node population-sweep.mjs \
25
25
  The JSONL is append-only and resumable by run fingerprint. A row records the
26
26
  failed stage or, for successful builds, real/holding page counts, action counts,
27
27
  runtime page errors, broken targets, shell-size residuals, and same-page
28
- deep-link/back behavior.
28
+ deep-link/back behavior. Runtime loads every generated page at 1536 and 390;
29
+ holding routes additionally load every declared band's representative width and
30
+ record active-band ownership, viewport-specific host/shell geometry and intersection, exact emitted
31
+ positioned-scene registration/neutralization, and holding-copy contrast.
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { chromium } from '@playwright/test';
6
+ import { checkRuntime } from '../lib/runtime-check.mjs';
6
7
  import { startStaticServer } from '../lib/static-server.mjs';
7
8
 
8
9
  const outIndex = process.argv.indexOf('--out');
@@ -66,16 +67,60 @@ const deepLinkBack = {
66
67
  afterBack: historyPage.url(),
67
68
  historyLength: [beforeLength, afterLength],
68
69
  };
69
- await historyPage.locator('#next').click();
70
+ const backLink = historyPage.getByRole('link', { name: 'Back to destination' });
71
+ await backLink.focus();
72
+ const accessibleBack = {
73
+ roleCount: await backLink.count(),
74
+ focused: await backLink.evaluate((element) => element === document.activeElement),
75
+ };
76
+ await historyPage.keyboard.press('Enter');
70
77
  await historyPage.waitForURL('**/history-destination.html');
71
78
  const afterPageClick = historyPage.url();
72
79
  await historyPage.goBack({ waitUntil: 'domcontentloaded', timeout: 2_000 });
73
80
  const pageBack = { afterPageClick, afterBack: historyPage.url() };
74
81
  await historyPage.close();
75
82
 
83
+ const holdingRuntime = await checkRuntime({
84
+ browser,
85
+ baseUrl: server.baseUrl,
86
+ relativeProtoPath: 'tests/fixtures/holding-runtime',
87
+ protoDir: path.join(kitDir, 'tests', 'fixtures', 'holding-runtime'),
88
+ });
89
+ const holdingPage = (file) => holdingRuntime.pages.find((page) => page.file === file);
90
+ const failuresFor = (file) => (holdingPage(file)?.viewports || []).flatMap((viewport) => (
91
+ (viewport.composition?.failures || []).map((failure) => ({ width: viewport.width, failure }))
92
+ ));
93
+ const holdingComposition = {
94
+ bandOwnershipFire: failuresFor('band-ownership-fire.html'),
95
+ bandOwnershipSilent: failuresFor('band-ownership-silent.html'),
96
+ shellIntersectionFire: failuresFor('shell-intersection-fire.html'),
97
+ shellIntersectionSilent: failuresFor('shell-intersection-silent.html'),
98
+ positionedGroundFire: failuresFor('positioned-ground-fire.html'),
99
+ positionedGroundSilent: failuresFor('positioned-ground-silent.html'),
100
+ declaredWidthFire: failuresFor('declared-width-fire.html'),
101
+ declaredWidthSilent: failuresFor('declared-width-silent.html'),
102
+ groundRegistrationMismatchFire: failuresFor('ground-registration-mismatch-fire.html'),
103
+ groundRegistrationMismatchSilent: failuresFor('ground-registration-mismatch-silent.html'),
104
+ shellGeometryFire: failuresFor('shell-geometry-fire.html'),
105
+ shellGeometrySilent: failuresFor('shell-geometry-silent.html'),
106
+ holdingContrastFire: failuresFor('holding-contrast-fire.html'),
107
+ holdingContrastSilent: failuresFor('holding-contrast-silent.html'),
108
+ };
109
+
110
+ const shellNavRuntime = await checkRuntime({
111
+ browser,
112
+ baseUrl: server.baseUrl,
113
+ relativeProtoPath: 'tests/fixtures/shell-nav-runtime',
114
+ protoDir: path.join(kitDir, 'tests', 'fixtures', 'shell-nav-runtime'),
115
+ });
116
+ const shellNavigation = {
117
+ shellConsistencyFailures: shellNavRuntime.shellConsistencyFailures,
118
+ activeNavigationFailures: shellNavRuntime.activeNavigationFailures,
119
+ };
120
+
76
121
  await browser.close();
77
122
  await server.close();
78
- const result = { doubleBinding, nestedBinding, dropdownGuard, hoverStacking, deepLinkBack, pageBack };
123
+ const result = { doubleBinding, nestedBinding, dropdownGuard, hoverStacking, deepLinkBack, accessibleBack, pageBack, holdingComposition, shellNavigation };
79
124
  if (process.argv.includes('--assert')) {
80
125
  assert.deepEqual(doubleBinding, { overlayRoots: 1, modals: 1 }, 'runtime must bind exactly once when injected twice');
81
126
  assert.deepEqual(nestedBinding, { modals: 1, menus: 0 }, 'the innermost action must stop a parent binding from also firing');
@@ -83,7 +128,45 @@ if (process.argv.includes('--assert')) {
83
128
  assert.deepEqual(hoverStacking, { stackedOpened: 1, plainOpened: 1 }, 'hover styling must not reorder either overlapping or ordinary actions');
84
129
  assert.equal(afterLength, beforeLength + 1, 'same-page navigation must add one history entry');
85
130
  assert.equal(deepLinkBack.afterBack, historyStart, 'browser Back must return to the pre-anchor URL');
131
+ assert.deepEqual(accessibleBack, { roleCount: 1, focused: true }, 'Back must be a native focusable link that activates with Enter');
86
132
  assert.equal(pageBack.afterBack, historyStart, 'browser Back must return from a different prototype page');
133
+ assert.deepEqual(holdingComposition.bandOwnershipFire, [{ width: 390, failure: 'holding-surface-count' }], 'must fire: a visible responsive band without exactly one holding surface is gated');
134
+ assert.deepEqual(holdingComposition.bandOwnershipSilent, [], 'must stay silent: one holding surface in every responsive band passes');
135
+ assert.deepEqual(holdingComposition.shellIntersectionFire, [
136
+ { width: 1536, failure: 'shell-intersection' },
137
+ { width: 390, failure: 'shell-intersection' },
138
+ ], 'must fire: a base-state shell intersecting the holding rectangle is gated at both widths');
139
+ assert.deepEqual(holdingComposition.shellIntersectionSilent, [], 'must stay silent: a measured footer outside the holding rectangle passes at both widths');
140
+ assert.deepEqual(holdingComposition.positionedGroundFire, [
141
+ { width: 1536, failure: 'donor-ground-active' },
142
+ { width: 390, failure: 'donor-ground-active' },
143
+ ], 'must fire: an explicitly registered positioned-scene ground cannot paint on a holding route');
144
+ assert.deepEqual(holdingComposition.positionedGroundSilent, [], 'must stay silent: an undeclared ground fact is never inferred from paint or selector names');
145
+ assert.deepEqual(holdingComposition.declaredWidthFire, [
146
+ { width: 768, failure: 'holding-surface-count' },
147
+ ], 'must fire: every declared representative breakpoint is audited, including tablet');
148
+ assert.deepEqual(holdingComposition.declaredWidthSilent, [], 'must stay silent: a three-band holding with one surface per band passes');
149
+ assert.deepEqual(holdingComposition.groundRegistrationMismatchFire, [
150
+ { width: 1536, failure: 'ground-registration-mismatch' },
151
+ { width: 390, failure: 'ground-registration-mismatch' },
152
+ ], 'must fire: runtime cannot replace the emitted ground fact with config-only optimism');
153
+ assert.deepEqual(holdingComposition.groundRegistrationMismatchSilent, [], 'must stay silent: the emitted and inventoried positioned-scene fact agrees after neutralization');
154
+ assert.deepEqual(holdingComposition.shellGeometryFire, [
155
+ { width: 390, failure: 'shell-geometry-mismatch' },
156
+ ], 'must fire: freezing a nonzero desktop host origin at mobile width is gated');
157
+ assert.deepEqual(holdingComposition.shellGeometrySilent, [], 'must stay silent: viewport-specific measured host and shell geometry passes at both widths');
158
+ assert.deepEqual(holdingComposition.holdingContrastFire, [
159
+ { width: 1536, failure: 'holding-contrast' },
160
+ { width: 390, failure: 'holding-contrast' },
161
+ ], 'must fire: unreadable product copy on the selected neutral surface is gated');
162
+ assert.deepEqual(holdingComposition.holdingContrastSilent, [], 'must stay silent: readable holding copy passes at both widths');
163
+ assert.equal(shellNavigation.shellConsistencyFailures.length, 2, 'must fire: one structurally divergent member shell is gated at desktop and mobile widths');
164
+ assert.deepEqual(shellNavigation.shellConsistencyFailures.map((failure) => failure.group), [
165
+ '1536:native:header',
166
+ '390:native:header',
167
+ ], 'must stay silent: identical canonical shell members share one runtime fingerprint at each width');
168
+ assert.equal(shellNavigation.activeNavigationFailures.length, 2, 'must fire: a visible marker under the wrong linked nav item is gated at both widths');
169
+ assert.ok(shellNavigation.activeNavigationFailures.every((failure) => failure.file === 'active-fire.html'), 'must stay silent: already-correct and unlinked source ink produce no active-navigation failure');
87
170
  }
88
171
  const json = JSON.stringify(result, null, 2) + '\n';
89
172
  if (outFile) fs.writeFileSync(outFile, json);
@@ -0,0 +1,22 @@
1
+ # Holding-composition paired fixtures
2
+
3
+ These reduced fixtures freeze the exact three pairs named by the
4
+ `ws2-prototype-holding-leak` population study. They deliberately keep only the
5
+ DOM and paint needed to exercise the production runtime gate.
6
+
7
+ | Guard | Must fire provenance | Must stay silent provenance | Reduced fixtures |
8
+ |---|---|---|---|
9
+ | Active band owns holding content | Salt Marsh `holding-our-story`, Field Notes `holding-journal`, and the 16 progress v2b/v2c holdings | worker-v189 Salt `holding-about`, plus both repaired r2 outputs | `band-ownership-fire.html` / `band-ownership-silent.html` |
10
+ | Retained shell intersects holding | Salt Marsh `holding-our-story` base footer | Field Notes `holding-journal` absolute footer | `shell-intersection-fire.html` / `shell-intersection-silent.html` |
11
+ | Declared positioned-scene ground survives | Field Notes `holding-journal` donor plate | Salt Marsh flat donor ground | `positioned-ground-fire.html` / `positioned-ground-silent.html` |
12
+
13
+ The population and r2 replays exercise the named full artifacts. These fixtures
14
+ are the deterministic suite form: each must-fire changes exactly one gate
15
+ condition, and each must-stay-silent preserves the adjacent condition that the
16
+ study requires the gate not to infer.
17
+
18
+ The additional declared-width, ground-registration-mismatch, shell-geometry,
19
+ and holding-contrast pairs protect the evidence chain itself: every measured
20
+ band is visited, the runtime declaration agrees with the emitted producer fact,
21
+ screen geometry is converted into the composition host's coordinate frame, and
22
+ product copy remains readable on the selected neutral surface.
@@ -0,0 +1,10 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.ly-band{display:none;position:relative;height:1024px}.generated-screen{position:relative;height:1024px;background:#fff}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ @media(max-width:959px){.ly-band[data-ly-band="mobile-390"]{display:block}}
4
+ @media(min-width:960px){.ly-band[data-ly-band="native"]{display:block}}
5
+ [data-proto-holding-surface]{position:absolute;left:40px;top:180px;width:300px;height:240px}
6
+ </style></head><body>
7
+ <div class="ly-band" data-ly-band="mobile-390"><div class="generated-screen"></div></div>
8
+ <div class="ly-band" data-ly-band="native"><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
9
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"mobile-390","shellIds":[],"groundRegistration":null},{"key":"native","shellIds":[],"groundRegistration":null}]}</script>
10
+ </body></html>
@@ -0,0 +1,10 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.ly-band{display:none;position:relative;height:1024px}.generated-screen{position:relative;height:1024px;background:#fff}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ @media(max-width:959px){.ly-band[data-ly-band="mobile-390"]{display:block}}
4
+ @media(min-width:960px){.ly-band[data-ly-band="native"]{display:block}}
5
+ [data-proto-holding-surface]{position:absolute;left:40px;top:180px;width:300px;height:240px}
6
+ </style></head><body>
7
+ <div class="ly-band" data-ly-band="mobile-390"><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
8
+ <div class="ly-band" data-ly-band="native"><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
9
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"mobile-390","shellIds":[],"groundRegistration":null},{"key":"native","shellIds":[],"groundRegistration":null}]}</script>
10
+ </body></html>
@@ -0,0 +1,11 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.ly-band{display:none;height:1024px}.generated-screen{height:1024px;background:#fff}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ @media(max-width:600px){.ly-band[data-ly-band="mobile"]{display:block}}
4
+ @media(min-width:601px) and (max-width:1000px){.ly-band[data-ly-band="tablet"]{display:block}}
5
+ @media(min-width:1001px){.ly-band[data-ly-band="native"]{display:block}}
6
+ </style></head><body>
7
+ <div class="ly-band" data-ly-band="mobile"><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
8
+ <div class="ly-band" data-ly-band="tablet"><div class="generated-screen"></div></div>
9
+ <div class="ly-band" data-ly-band="native"><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
10
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"mobile","viewportWidth":390,"shellIds":[],"groundRegistration":null},{"key":"tablet","viewportWidth":768,"shellIds":[],"groundRegistration":null},{"key":"native","viewportWidth":1536,"shellIds":[],"groundRegistration":null}]}</script>
11
+ </body></html>
@@ -0,0 +1,11 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.ly-band{display:none;height:1024px}.generated-screen{height:1024px;background:#fff}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ @media(max-width:600px){.ly-band[data-ly-band="mobile"]{display:block}}
4
+ @media(min-width:601px) and (max-width:1000px){.ly-band[data-ly-band="tablet"]{display:block}}
5
+ @media(min-width:1001px){.ly-band[data-ly-band="native"]{display:block}}
6
+ </style></head><body>
7
+ <div class="ly-band" data-ly-band="mobile"><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
8
+ <div class="ly-band" data-ly-band="tablet"><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
9
+ <div class="ly-band" data-ly-band="native"><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
10
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"mobile","viewportWidth":390,"shellIds":[],"groundRegistration":null},{"key":"tablet","viewportWidth":768,"shellIds":[],"groundRegistration":null},{"key":"native","viewportWidth":1536,"shellIds":[],"groundRegistration":null}]}</script>
11
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.generated-screen{height:1024px;background:#fff}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","viewportWidth":1536,"shellIds":[],"groundRegistration":"positioned-scene"}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.generated-screen{height:1024px;background:#fff}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen" data-ly-ground-registration="positioned-scene"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","viewportWidth":1536,"shellIds":[],"groundRegistration":"positioned-scene"}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.generated-screen{height:1024px;background:#fff}.proto-holding-title,.proto-holding-sub{color:#eee}
3
+ </style></head><body><div class="generated-screen" data-ly-ground-registration="positioned-scene"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","viewportWidth":1536,"shellIds":[],"groundRegistration":"positioned-scene"}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.generated-screen{height:1024px;background:#fff}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen" data-ly-ground-registration="positioned-scene"><main data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","viewportWidth":1536,"shellIds":[],"groundRegistration":"positioned-scene"}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0;height:1024px}.generated-screen{position:relative;height:1024px;background-color:#fff;background-image:linear-gradient(#123,#456)}.holding{position:absolute;left:40px;top:120px;width:300px;height:240px}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen" data-ly-ground-registration="positioned-scene"><main class="holding" data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","shellIds":[],"groundRegistration":"positioned-scene"}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0;height:1024px}.generated-screen{position:relative;height:1024px;background-color:#fff;background-image:linear-gradient(#123,#456)}.holding{position:absolute;left:40px;top:120px;width:300px;height:240px}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen"><main class="holding" data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","shellIds":[],"groundRegistration":null}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.generated-screen{height:1024px;background:#fff}.host{position:relative;margin-left:max(0px,calc((100vw - 1000px)/2));width:1000px;height:1024px;zoom:min(1,calc(100vw / 1000px))}.shell{position:absolute;left:30px;top:100px;width:300px;height:50px}.holding{position:absolute;left:30px;top:300px}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen"><div class="host" data-proto-holding-band="native"><header id="measured-header" class="shell"></header><main class="holding" data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","viewportWidth":1536,"hostOrigins":[{"viewportWidth":1536,"origin":[268,0]},{"viewportWidth":390,"origin":[268,0]}],"shellIds":["measured-header"],"shellBoxes":[{"id":"measured-header","box":[30,100,300,50]}],"groundRegistration":null}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0}.generated-screen{height:1024px;background:#fff}.host{position:relative;margin-left:max(0px,calc((100vw - 1000px)/2));width:1000px;height:1024px;zoom:min(1,calc(100vw / 1000px))}.shell{position:absolute;left:30px;top:100px;width:300px;height:50px}.holding{position:absolute;left:30px;top:300px}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen"><div class="host" data-proto-holding-band="native"><header id="measured-header" class="shell"></header><main class="holding" data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","viewportWidth":1536,"hostOrigins":[{"viewportWidth":1536,"origin":[268,0]},{"viewportWidth":390,"origin":[0,0]}],"shellIds":["measured-header"],"shellBoxes":[{"id":"measured-header","box":[30,100,300,50]}],"groundRegistration":null}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0;height:1024px}.generated-screen{position:relative;height:1024px;background:#fff}.shell{position:absolute;left:0;top:0;width:100%;height:100px}.holding{position:absolute;left:40px;top:60px;width:300px;height:240px}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen"><header id="base-header" class="shell"></header><main class="holding" data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","shellIds":["base-header"],"groundRegistration":null}]}</script>
5
+ </body></html>
@@ -0,0 +1,5 @@
1
+ <!doctype html><html><head><style>
2
+ html,body{margin:0;height:1024px}.generated-screen{position:relative;height:1024px;background:#fff}.shell{position:absolute;left:0;top:900px;width:100%;height:100px}.holding{position:absolute;left:40px;top:120px;width:300px;height:240px}.proto-holding-title,.proto-holding-sub{color:#111}
3
+ </style></head><body><div class="generated-screen"><footer id="base-footer" class="shell"></footer><main class="holding" data-proto-holding-surface="true"><h1 class="proto-holding-title">Holding</h1><p class="proto-holding-sub">Planned destination.</p></main></div>
4
+ <script id="proto-state" type="application/json">{"holding":true,"holdingBands":[{"key":"native","shellIds":["base-footer"],"groundRegistration":null}]}</script>
5
+ </body></html>
@@ -1,5 +1,5 @@
1
1
  <!doctype html><html><head><meta charset="utf-8"><style>body{margin:0}section{height:900px;padding:20px}</style></head><body>
2
- <section><button id="jump" data-proto-action="navigate" data-proto-target="history.html#target">Jump</button><button id="next" data-proto-action="navigate" data-proto-target="history-destination.html">Next page</button></section>
2
+ <section><button id="jump" data-proto-action="navigate" data-proto-target="history.html#target">Jump</button><a id="next" href="history-destination.html" data-proto-action="navigate" data-proto-target="history-destination.html" aria-label="Back to destination">Back</a></section>
3
3
  <section id="target"><h1>Target</h1></section>
4
4
  <script id="proto-state" type="application/json">{"unit":"history","tokens":{},"contentFile":null}</script>
5
5
  <script src="../../../runtime/proto.js"></script>
@@ -0,0 +1,6 @@
1
+ # Shell and navigation runtime fixture
2
+
3
+ Reduced synthetic pair for the measured prototype runtime contract. The fire
4
+ pages introduce one structural shell mismatch and one misplaced active marker;
5
+ the silent pages keep identical shells, including one page with deliberately
6
+ unlinked source ink.
@@ -0,0 +1,3 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><style>
2
+ html,body{margin:0;width:100%;height:100%}.shell{position:relative;width:100%;height:72px;background:#fff}.nav{position:absolute;top:20px;font:16px Arial}.menu{left:30px}.journal{left:130px}.ink{position:absolute;left:130px;top:42px;width:40px;height:3px;background:#a43}
3
+ </style></head><body><header id="active-fire-header" class="shell"><span id="active-fire-menu" class="nav menu">Menu</span><span id="active-fire-journal" class="nav journal">Journal</span><span id="active-fire-ink" class="ink"></span></header></body></html>
@@ -0,0 +1,20 @@
1
+ {
2
+ "units": [
3
+ {
4
+ "id": "shell-silent",
5
+ "shellBands": [{"key":"native","regions":[{"region":"header","shellId":"shell-silent-header","canonicalSignature":"fixture","consistencyStatus":"canonical","activeMarkerIds":["shell-silent-ink"],"active":{"status":"linked","navText":"Menu","navId":"shell-silent-menu","markerId":"shell-silent-ink"}}]}]
6
+ },
7
+ {
8
+ "id": "unlinked-silent",
9
+ "shellBands": [{"key":"native","regions":[{"region":"header","shellId":"unlinked-silent-header","canonicalSignature":"fixture","consistencyStatus":"canonical","activeMarkerIds":["unlinked-silent-ink"],"active":{"status":"silent","navText":null,"navId":null,"markerId":null}}]}]
10
+ },
11
+ {
12
+ "id": "shell-fire",
13
+ "shellBands": [{"key":"native","regions":[{"region":"header","shellId":"shell-fire-header","canonicalSignature":"fixture","consistencyStatus":"canonical","activeMarkerIds":["shell-fire-ink"],"active":{"status":"linked","navText":"Menu","navId":"shell-fire-menu","markerId":"shell-fire-ink"}}]}]
14
+ },
15
+ {
16
+ "id": "active-fire",
17
+ "shellBands": [{"key":"native","regions":[{"region":"header","shellId":"active-fire-header","canonicalSignature":"fixture","consistencyStatus":"canonical","activeMarkerIds":["active-fire-ink"],"active":{"status":"linked","navText":"Menu","navId":"active-fire-menu","markerId":"active-fire-ink"}}]}]
18
+ }
19
+ ]
20
+ }
@@ -0,0 +1,3 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><style>
2
+ html,body{margin:0;width:100%;height:100%}.shell{position:relative;width:100%;height:72px;background:#fff}.nav{position:absolute;top:20px;font:16px Arial}.menu{left:30px}.journal{left:130px}.extra{position:absolute;right:20px;top:20px;font:16px Arial}.ink{position:absolute;left:30px;top:42px;width:40px;height:3px;background:#a43}
3
+ </style></head><body><header id="shell-fire-header" class="shell"><span id="shell-fire-menu" class="nav menu">Menu</span><span id="shell-fire-journal" class="nav journal">Journal</span><span id="shell-fire-extra" class="extra">Overflow</span><span id="shell-fire-ink" class="ink"></span></header></body></html>
@@ -0,0 +1,3 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><style>
2
+ html,body{margin:0;width:100%;height:100%}.shell{position:relative;width:100%;height:72px;background:#fff}.nav{position:absolute;top:20px;font:16px Arial}.menu{left:30px}.journal{left:130px}.ink{position:absolute;left:30px;top:42px;width:40px;height:3px;background:#a43}
3
+ </style></head><body><header id="shell-silent-header" class="shell"><span id="shell-silent-menu" class="nav menu">Menu</span><span id="shell-silent-journal" class="nav journal">Journal</span><span id="shell-silent-ink" class="ink"></span></header></body></html>
@@ -0,0 +1,3 @@
1
+ <!doctype html><html><head><meta charset="utf-8"><style>
2
+ html,body{margin:0;width:100%;height:100%}.shell{position:relative;width:100%;height:72px;background:#fff}.nav{position:absolute;top:20px;font:16px Arial}.menu{left:30px}.journal{left:130px}.ink{position:absolute;left:130px;top:42px;width:40px;height:3px;background:#a43}
3
+ </style></head><body><header id="unlinked-silent-header" class="shell"><span id="unlinked-silent-menu" class="nav menu">Menu</span><span id="unlinked-silent-journal" class="nav journal">Journal</span><span id="unlinked-silent-ink" class="ink"></span></header></body></html>