@12ui/design 0.2.47 → 0.2.49

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 (159) hide show
  1. package/SKILL.md +1 -1
  2. package/dist/branch-execution.js +2 -2
  3. package/dist/branch-execution.js.map +1 -1
  4. package/dist/branch-page-conversion.d.ts +4 -1
  5. package/dist/branch-page-conversion.d.ts.map +1 -1
  6. package/dist/branch-page-conversion.js +27 -17
  7. package/dist/branch-page-conversion.js.map +1 -1
  8. package/dist/branch-run-record.d.ts +2 -0
  9. package/dist/branch-run-record.d.ts.map +1 -1
  10. package/dist/branch-run-record.js.map +1 -1
  11. package/dist/cli-branch-command.d.ts +2 -0
  12. package/dist/cli-branch-command.d.ts.map +1 -1
  13. package/dist/cli-branch-command.js +3 -0
  14. package/dist/cli-branch-command.js.map +1 -1
  15. package/dist/cli-convert-command.js +2 -2
  16. package/dist/cli-convert-command.js.map +1 -1
  17. package/dist/cli-draft-command.d.ts +11 -5
  18. package/dist/cli-draft-command.d.ts.map +1 -1
  19. package/dist/cli-draft-command.js +37 -35
  20. package/dist/cli-draft-command.js.map +1 -1
  21. package/dist/cli-help/design.d.ts +10 -0
  22. package/dist/cli-help/design.d.ts.map +1 -1
  23. package/dist/cli-help/design.js +29 -0
  24. package/dist/cli-help/design.js.map +1 -1
  25. package/dist/cli-help/index.d.ts +21 -0
  26. package/dist/cli-help/index.d.ts.map +1 -1
  27. package/dist/cli-help/index.js +18 -2
  28. package/dist/cli-help/index.js.map +1 -1
  29. package/dist/cli-help/meta.d.ts +10 -0
  30. package/dist/cli-help/meta.d.ts.map +1 -1
  31. package/dist/cli-help/meta.js +20 -0
  32. package/dist/cli-help/meta.js.map +1 -1
  33. package/dist/cli-next-command.d.ts.map +1 -1
  34. package/dist/cli-next-command.js +26 -14
  35. package/dist/cli-next-command.js.map +1 -1
  36. package/dist/cli-progress.d.ts +2 -0
  37. package/dist/cli-progress.d.ts.map +1 -1
  38. package/dist/cli-progress.js +2 -0
  39. package/dist/cli-progress.js.map +1 -1
  40. package/dist/cli-redesign-command.d.ts +42 -0
  41. package/dist/cli-redesign-command.d.ts.map +1 -0
  42. package/dist/cli-redesign-command.js +520 -0
  43. package/dist/cli-redesign-command.js.map +1 -0
  44. package/dist/cli-run.d.ts.map +1 -1
  45. package/dist/cli-run.js +16 -0
  46. package/dist/cli-run.js.map +1 -1
  47. package/dist/cli-spend-command.d.ts +20 -0
  48. package/dist/cli-spend-command.d.ts.map +1 -0
  49. package/dist/cli-spend-command.js +46 -0
  50. package/dist/cli-spend-command.js.map +1 -0
  51. package/dist/cli-usage.d.ts.map +1 -1
  52. package/dist/cli-usage.js +9 -1
  53. package/dist/cli-usage.js.map +1 -1
  54. package/dist/conversion-receipt-store.d.ts +2 -2
  55. package/dist/conversion-receipt-store.d.ts.map +1 -1
  56. package/dist/conversion-receipt-store.js +11 -5
  57. package/dist/conversion-receipt-store.js.map +1 -1
  58. package/dist/conversion-receipt.d.ts +2 -3
  59. package/dist/conversion-receipt.d.ts.map +1 -1
  60. package/dist/conversion-receipt.js +17 -12
  61. package/dist/conversion-receipt.js.map +1 -1
  62. package/dist/draft-dispatch-attempt.d.ts +0 -7
  63. package/dist/draft-dispatch-attempt.d.ts.map +1 -1
  64. package/dist/draft-dispatch-attempt.js +1 -25
  65. package/dist/draft-dispatch-attempt.js.map +1 -1
  66. package/dist/draft-identity.d.ts +2 -0
  67. package/dist/draft-identity.d.ts.map +1 -1
  68. package/dist/draft-identity.js +1 -0
  69. package/dist/draft-identity.js.map +1 -1
  70. package/dist/draft-run.d.ts +2 -0
  71. package/dist/draft-run.d.ts.map +1 -1
  72. package/dist/draft-run.js +4 -3
  73. package/dist/draft-run.js.map +1 -1
  74. package/dist/package-client.d.ts +11 -0
  75. package/dist/package-client.d.ts.map +1 -1
  76. package/dist/package-client.js.map +1 -1
  77. package/dist/package-submission.d.ts +12 -0
  78. package/dist/package-submission.d.ts.map +1 -1
  79. package/dist/package-submission.js +5 -0
  80. package/dist/package-submission.js.map +1 -1
  81. package/dist/prototype-label-artifact.d.ts +29 -0
  82. package/dist/prototype-label-artifact.d.ts.map +1 -0
  83. package/dist/prototype-label-artifact.js +104 -0
  84. package/dist/prototype-label-artifact.js.map +1 -0
  85. package/dist/prototype-runner.d.ts +1 -0
  86. package/dist/prototype-runner.d.ts.map +1 -1
  87. package/dist/prototype-runner.js +18 -2
  88. package/dist/prototype-runner.js.map +1 -1
  89. package/dist/redesign-brand-asset-inventory.d.ts +71 -0
  90. package/dist/redesign-brand-asset-inventory.d.ts.map +1 -0
  91. package/dist/redesign-brand-asset-inventory.js +318 -0
  92. package/dist/redesign-brand-asset-inventory.js.map +1 -0
  93. package/dist/redesign-capture.d.ts +57 -0
  94. package/dist/redesign-capture.d.ts.map +1 -0
  95. package/dist/redesign-capture.js +181 -0
  96. package/dist/redesign-capture.js.map +1 -0
  97. package/dist/redesign-pinned-asset.d.ts +20 -0
  98. package/dist/redesign-pinned-asset.d.ts.map +1 -0
  99. package/dist/redesign-pinned-asset.js +35 -0
  100. package/dist/redesign-pinned-asset.js.map +1 -0
  101. package/dist/redesign-plan-client.d.ts +49 -0
  102. package/dist/redesign-plan-client.d.ts.map +1 -0
  103. package/dist/redesign-plan-client.js +90 -0
  104. package/dist/redesign-plan-client.js.map +1 -0
  105. package/dist/redesign-plan-identity.d.ts +8 -0
  106. package/dist/redesign-plan-identity.d.ts.map +1 -0
  107. package/dist/redesign-plan-identity.js +16 -0
  108. package/dist/redesign-plan-identity.js.map +1 -0
  109. package/dist/redesign-plan.d.ts +30 -0
  110. package/dist/redesign-plan.d.ts.map +1 -0
  111. package/dist/redesign-plan.js +99 -0
  112. package/dist/redesign-plan.js.map +1 -0
  113. package/dist/redesign-workspace.d.ts +142 -0
  114. package/dist/redesign-workspace.d.ts.map +1 -0
  115. package/dist/redesign-workspace.js +82 -0
  116. package/dist/redesign-workspace.js.map +1 -0
  117. package/dist/run-journal-commands.d.ts.map +1 -1
  118. package/dist/run-journal-commands.js +16 -0
  119. package/dist/run-journal-commands.js.map +1 -1
  120. package/dist/spend-client.d.ts +97 -0
  121. package/dist/spend-client.d.ts.map +1 -0
  122. package/dist/spend-client.js +31 -0
  123. package/dist/spend-client.js.map +1 -0
  124. package/dist/spend-report-lines.d.ts +4 -0
  125. package/dist/spend-report-lines.d.ts.map +1 -0
  126. package/dist/spend-report-lines.js +131 -0
  127. package/dist/spend-report-lines.js.map +1 -0
  128. package/open-design.json +2 -2
  129. package/package.json +2 -2
  130. package/prototype-kit/build.mjs +9 -3
  131. package/prototype-kit/lib/action-target.mjs +26 -0
  132. package/prototype-kit/lib/local-document.mjs +16 -0
  133. package/prototype-kit/lib/runtime-check.mjs +83 -19
  134. package/prototype-kit/lib/shell-runtime-equivalence.mjs +41 -0
  135. package/prototype-kit/polish/harm.mjs +27 -5
  136. package/prototype-kit/polish/measure.mjs +101 -5
  137. package/prototype-kit/polish/overrides.mjs +32 -3
  138. package/prototype-kit/polish/polish.mjs +23 -11
  139. package/prototype-kit/polish/rules/align.mjs +73 -3
  140. package/prototype-kit/polish/rules/left-rail.mjs +2 -3
  141. package/prototype-kit/polish/rules/section-breathing.mjs +0 -1
  142. package/prototype-kit/polish/tests/fixtures/align/expect.json +1 -1
  143. package/prototype-kit/polish/tests/fixtures/align/fire.html +4 -1
  144. package/prototype-kit/polish/tests/fixtures/align/silent.html +8 -0
  145. package/prototype-kit/polish/tests/fixtures/align/zoom-constrained.html +17 -0
  146. package/prototype-kit/polish/tests/fixtures/harm/left-rail-contained.html +16 -0
  147. package/prototype-kit/polish/tests/fixtures/harm/left-rail-overflow.html +16 -0
  148. package/prototype-kit/polish/tests/fixtures/harm/nested-actions.html +6 -0
  149. package/prototype-kit/polish/tests/fixtures/harm/unidentified-parent.html +3 -0
  150. package/prototype-kit/polish/tests/fixtures/shell-seam/fire.html +1 -1
  151. package/prototype-kit/polish/tests/harm-check.mjs +131 -2
  152. package/prototype-kit/polish/tests/rules-check.mjs +7 -2
  153. package/prototype-kit/runtime/proto.js +4 -1
  154. package/prototype-kit/tests/adversarial-runtime.mjs +3 -0
  155. package/prototype-kit/tests/align-rule-check.mjs +105 -0
  156. package/prototype-kit/tests/fixtures/interaction-runtime/review-bottom-action.html +8 -0
  157. package/prototype-kit/tests/offline-check.mjs +17 -1
  158. package/prototype-kit/tests/rules-check.mjs +67 -1
  159. package/prototype-kit/tests/shell-stitching-runtime.mjs +15 -8
@@ -0,0 +1,97 @@
1
+ import { type ConversionConfig } from './config.js';
2
+ /**
3
+ * The account spend contract, as the CLI reads it.
4
+ *
5
+ * The service answers `GET /api/v1/spend` with rollups it computed; the CLI
6
+ * never re-derives a total from the run list, because the run list is capped
7
+ * and the rollups are not. Fields the service may withhold — settled provider
8
+ * cost, which is an operator figure — arrive as null, and the CLI reports them
9
+ * as withheld rather than as zero.
10
+ */
11
+ export type SpendFundingSource = 'wallet' | 'sponsored' | 'admin' | 'unattributed';
12
+ export type SpendFunctionality = 'convert' | 'draft' | 'branch' | 'search';
13
+ export type SpendCostEvidence = 'provider_reported' | 'estimated' | 'zero_cost_reuse' | 'legacy_unknown';
14
+ export type SpendAmounts = {
15
+ chargedMicros: number;
16
+ listPriceMicros: number;
17
+ providerCostMicros: number | null;
18
+ };
19
+ export type SpendRun = SpendAmounts & {
20
+ runId: string;
21
+ functionality: SpendFunctionality;
22
+ fundingSource: SpendFundingSource;
23
+ sponsorshipGrantId: string | null;
24
+ costEvidence: SpendCostEvidence;
25
+ providerCostEvents: number;
26
+ firstEventAt: string;
27
+ lastEventAt: string;
28
+ };
29
+ export type SpendDay = SpendAmounts & {
30
+ day: string;
31
+ runs: number;
32
+ };
33
+ export type SpendFundingTotal = SpendAmounts & {
34
+ fundingSource: SpendFundingSource;
35
+ sponsorshipGrantId: string | null;
36
+ runs: number;
37
+ };
38
+ export type SpendWallet = {
39
+ status: 'ok';
40
+ accountStatus: string;
41
+ availableMicros: number;
42
+ reservedMicros: number;
43
+ debtMicros: number;
44
+ lifetimeFundedMicros: number;
45
+ lifetimeCapturedMicros: number;
46
+ lifetimeReversedMicros: number;
47
+ } | {
48
+ status: 'unavailable';
49
+ };
50
+ export type SpendQuotaBucket = {
51
+ limit: number;
52
+ used: number;
53
+ remaining: number;
54
+ available: boolean;
55
+ };
56
+ export type SpendQuota = {
57
+ tier: string;
58
+ windowStart: string;
59
+ resetsAt: string;
60
+ buckets: Record<string, SpendQuotaBucket>;
61
+ } | null;
62
+ export type SpendReport = {
63
+ account: {
64
+ accountId: string;
65
+ kind: 'user' | 'organization';
66
+ operator: boolean;
67
+ };
68
+ window: {
69
+ days: number;
70
+ start: string;
71
+ end: string;
72
+ unit: string;
73
+ };
74
+ currency: string;
75
+ providerCostVisibility: 'operator' | 'restricted';
76
+ totals: SpendAmounts & {
77
+ runs: number;
78
+ };
79
+ funding: SpendFundingTotal[];
80
+ days: SpendDay[];
81
+ runs: SpendRun[];
82
+ truncated: boolean;
83
+ wallet: SpendWallet;
84
+ quota: SpendQuota;
85
+ };
86
+ export type SpendRequest = {
87
+ days?: number;
88
+ limit?: number;
89
+ signal?: AbortSignal;
90
+ };
91
+ type Fetch = typeof fetch;
92
+ export declare const fetchAccountSpend: (request?: SpendRequest, options?: {
93
+ config?: ConversionConfig;
94
+ fetch?: Fetch;
95
+ }) => Promise<SpendReport>;
96
+ export {};
97
+ //# sourceMappingURL=spend-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-client.d.ts","sourceRoot":"","sources":["../src/spend-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { declareClientVersion } from './client-version.js';
2
+ import { appendClientAuthGuidance } from './client-auth-guidance.js';
3
+ import { readConversionConfig } from './config.js';
4
+ export const fetchAccountSpend = async (request = {}, options = {}) => {
5
+ const config = options.config ?? readConversionConfig();
6
+ const fetchImpl = options.fetch ?? fetch;
7
+ const query = new URLSearchParams();
8
+ if (request.days !== undefined)
9
+ query.set('days', String(request.days));
10
+ if (request.limit !== undefined)
11
+ query.set('limit', String(request.limit));
12
+ const suffix = query.size > 0 ? `?${query.toString()}` : '';
13
+ const headers = declareClientVersion(new Headers({
14
+ accept: 'application/json',
15
+ authorization: `Bearer ${config.apiKey}`,
16
+ }));
17
+ const response = await fetchImpl(`${config.origin}/api/v1/spend${suffix}`, {
18
+ headers,
19
+ ...(request.signal ? { signal: request.signal } : {}),
20
+ });
21
+ if (!response.ok) {
22
+ const detail = await response.text().catch(() => '');
23
+ throw new Error(appendClientAuthGuidance(`12ui spend request failed (${response.status}): ${detail.slice(0, 500)}`, response.status));
24
+ }
25
+ const body = await response.json().catch(() => null);
26
+ if (!body || typeof body !== 'object' || !body.account || !Array.isArray(body.runs)) {
27
+ throw new Error('12ui returned an unreadable spend report');
28
+ }
29
+ return body;
30
+ };
31
+ //# sourceMappingURL=spend-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-client.js","sourceRoot":"","sources":["../src/spend-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAyB,MAAM,aAAa,CAAC;AA6F1E,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EACpC,UAAwB,EAAE,EAC1B,UAAwD,EAAE,EACpC,EAAE;IACxB,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,oBAAoB,EAAE,CAAC;IACxD,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IACzC,MAAM,KAAK,GAAG,IAAI,eAAe,EAAE,CAAC;IACpC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACxE,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IAC3E,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC5D,MAAM,OAAO,GAAG,oBAAoB,CAAC,IAAI,OAAO,CAAC;QAC/C,MAAM,EAAE,kBAAkB;QAC1B,aAAa,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;KACzC,CAAC,CAAC,CAAC;IACJ,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,MAAM,CAAC,MAAM,gBAAgB,MAAM,EAAE,EAAE;QACzE,OAAO;QACP,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC,CAAC;IACH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,wBAAwB,CACtC,8BAA8B,QAAQ,CAAC,MAAM,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EACzE,QAAQ,CAAC,MAAM,CAChB,CAAC,CAAC;IACL,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAuB,CAAC;IAC3E,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QACpF,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { SpendReport } from './spend-client.js';
2
+ export declare const formatUsdMicros: (micros: number | null) => string;
3
+ export declare const spendReportLines: (report: SpendReport) => string[];
4
+ //# sourceMappingURL=spend-report-lines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-report-lines.d.ts","sourceRoot":"","sources":["../src/spend-report-lines.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,WAAW,EAEZ,MAAM,mBAAmB,CAAC;AAe3B,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,GAAG,IAAI,KAAG,MAKvD,CAAC;AAmFF,eAAO,MAAM,gBAAgB,GAAI,QAAQ,WAAW,KAAG,MAAM,EAyD5D,CAAC"}
@@ -0,0 +1,131 @@
1
+ /**
2
+ * The human rendering of a spend report, as pure functions over the payload.
3
+ *
4
+ * Formatting lives here rather than in the command so the exact lines an
5
+ * operator reads can be asserted without a network, and so one rule decides how
6
+ * money is written: money is never rounded to a figure that reads as zero when
7
+ * it is not. A $0.0004 embedding call prints as $0.0004, because a ledger that
8
+ * silently rounds spend to $0.00 is the instrument that caused the doubt this
9
+ * command exists to settle.
10
+ */
11
+ const MICROS_PER_DOLLAR = 1_000_000;
12
+ export const formatUsdMicros = (micros) => {
13
+ if (micros === null)
14
+ return '—';
15
+ const dollars = micros / MICROS_PER_DOLLAR;
16
+ if (micros !== 0 && Math.abs(dollars) < 0.01)
17
+ return `$${dollars.toFixed(4)}`;
18
+ return `$${dollars.toFixed(2)}`;
19
+ };
20
+ /**
21
+ * Labels for the vocabularies the service owns.
22
+ *
23
+ * Both maps are read through {@link labelled}, which falls back to the raw
24
+ * value: the service coins ledger labels as its evidence vocabulary changes,
25
+ * and a report that crashes — or silently blanks a column — on a label this
26
+ * build has not heard of is worse than one that prints the new label as-is.
27
+ */
28
+ const FUNDING_LABELS = {
29
+ wallet: 'wallet',
30
+ sponsored: 'sponsored',
31
+ admin: 'operator',
32
+ unattributed: 'unattributed',
33
+ };
34
+ const EVIDENCE_LABELS = {
35
+ provider_reported: 'settled',
36
+ estimated: 'estimated',
37
+ zero_cost_reuse: 'reused',
38
+ legacy_unknown: 'unknown',
39
+ };
40
+ const labelled = (labels, value) => (labels[value] ?? value);
41
+ const day = (timestamp) => timestamp.slice(0, 10);
42
+ const padded = (value, width) => value.padEnd(width, ' ');
43
+ const rightPadded = (value, width) => value.padStart(width, ' ');
44
+ /** Money columns are right-aligned so magnitudes line up; labels stay left. */
45
+ const table = (headings, rows, rightAligned) => {
46
+ const widths = headings.map((heading, column) => Math.max(heading.length, ...rows.map((row) => (row[column] ?? '').length)));
47
+ const line = (cells) => cells
48
+ .map((cell, column) => (rightAligned.has(column)
49
+ ? rightPadded(cell ?? '', widths[column] ?? 0)
50
+ : padded(cell ?? '', widths[column] ?? 0)))
51
+ .join(' ')
52
+ .trimEnd();
53
+ return [line(headings), ...rows.map(line)];
54
+ };
55
+ const fundingSummary = (report) => {
56
+ if (report.funding.length === 0)
57
+ return 'Funding: nothing settled in this window.';
58
+ const parts = report.funding.map((entry) => {
59
+ const grant = entry.sponsorshipGrantId ? `, grant ${entry.sponsorshipGrantId}` : '';
60
+ return `${labelled(FUNDING_LABELS, entry.fundingSource)} ${formatUsdMicros(entry.chargedMicros)}`
61
+ + ` (${entry.runs} ${entry.runs === 1 ? 'run' : 'runs'}${grant})`;
62
+ });
63
+ return `Funding: ${parts.join(' · ')}`;
64
+ };
65
+ const walletLine = (report) => {
66
+ const { wallet } = report;
67
+ if (wallet.status !== 'ok') {
68
+ return 'Wallet: unavailable — billing did not answer; ledger figures above are unaffected.';
69
+ }
70
+ return `Wallet: ${formatUsdMicros(wallet.availableMicros)} available,`
71
+ + ` ${formatUsdMicros(wallet.reservedMicros)} reserved,`
72
+ + ` lifetime ${formatUsdMicros(wallet.lifetimeFundedMicros)} funded`
73
+ + ` / ${formatUsdMicros(wallet.lifetimeCapturedMicros)} charged`
74
+ + (wallet.accountStatus === 'active' ? '' : ` (account ${wallet.accountStatus})`);
75
+ };
76
+ const quotaLine = (report) => {
77
+ const { quota } = report;
78
+ if (!quota)
79
+ return 'Quota: unavailable.';
80
+ const buckets = Object.entries(quota.buckets)
81
+ .map(([bucket, value]) => `${bucket} ${value.used}/${value.limit}`)
82
+ .join(' · ');
83
+ return `Quota (${quota.tier}): ${buckets} — resets ${quota.resetsAt}`;
84
+ };
85
+ export const spendReportLines = (report) => {
86
+ const restricted = report.providerCostVisibility !== 'operator';
87
+ const lines = [
88
+ `Spend for ${report.account.accountId} · last ${report.window.days}`
89
+ + ` ${report.window.days === 1 ? 'day' : 'days'}`
90
+ + ` (${day(report.window.start)} to ${day(report.window.end)}, UTC)`,
91
+ `Settled: ${formatUsdMicros(report.totals.chargedMicros)} charged`
92
+ + ` · ${formatUsdMicros(report.totals.listPriceMicros)} at list price`
93
+ + (restricted
94
+ ? ' · provider cost withheld (operator figure)'
95
+ : ` · ${formatUsdMicros(report.totals.providerCostMicros)} provider cost`)
96
+ + ` · ${report.totals.runs} ${report.totals.runs === 1 ? 'run' : 'runs'}`,
97
+ fundingSummary(report),
98
+ walletLine(report),
99
+ quotaLine(report),
100
+ ];
101
+ if (report.days.length > 0) {
102
+ lines.push('', ...table(['Day', 'Runs', 'Charged', ...(restricted ? [] : ['Provider'])], report.days.map((entry) => [
103
+ entry.day,
104
+ String(entry.runs),
105
+ formatUsdMicros(entry.chargedMicros),
106
+ ...(restricted ? [] : [formatUsdMicros(entry.providerCostMicros)]),
107
+ ]), new Set(restricted ? [1, 2] : [1, 2, 3])));
108
+ }
109
+ if (report.runs.length > 0) {
110
+ lines.push('', ...table(['Run', 'Work', 'Funding', 'Charged', ...(restricted ? [] : ['Provider']), 'Evidence', 'Last event'], report.runs.map((run) => [
111
+ run.runId,
112
+ run.functionality,
113
+ run.sponsorshipGrantId
114
+ ? `${labelled(FUNDING_LABELS, run.fundingSource)} ${run.sponsorshipGrantId}`
115
+ : labelled(FUNDING_LABELS, run.fundingSource),
116
+ formatUsdMicros(run.chargedMicros),
117
+ ...(restricted ? [] : [formatUsdMicros(run.providerCostMicros)]),
118
+ labelled(EVIDENCE_LABELS, run.costEvidence),
119
+ run.lastEventAt,
120
+ ]), new Set(restricted ? [3] : [3, 4])));
121
+ if (report.truncated) {
122
+ lines.push(`Showing ${report.runs.length} of ${report.totals.runs} runs; totals above cover the`
123
+ + ' whole window. Raise --limit to list more.');
124
+ }
125
+ }
126
+ else {
127
+ lines.push('', 'No settled work in this window.');
128
+ }
129
+ return lines;
130
+ };
131
+ //# sourceMappingURL=spend-report-lines.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spend-report-lines.js","sourceRoot":"","sources":["../src/spend-report-lines.ts"],"names":[],"mappings":"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"}
package/open-design.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "specVersion": "1.0.0",
4
4
  "name": "12ui-design",
5
5
  "title": "12UI Design",
6
- "version": "0.2.47",
6
+ "version": "0.2.49",
7
7
  "description": "Explore corpus-grounded interface directions, choose one, and convert it into production-ready responsive or native artifacts with 12UI.",
8
8
  "author": {
9
9
  "name": "Just Every",
@@ -49,7 +49,7 @@
49
49
  "command": "npx",
50
50
  "args": [
51
51
  "-y",
52
- "@12ui/design@0.2.47",
52
+ "@12ui/design@0.2.49",
53
53
  "mcp"
54
54
  ]
55
55
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12ui/design",
3
- "version": "0.2.47",
3
+ "version": "0.2.49",
4
4
  "type": "module",
5
5
  "description": "The canonical 12ui CLI, SDK, MCP server, and visual interface design skills",
6
6
  "main": "dist/index.js",
@@ -23,7 +23,7 @@
23
23
  "start": "node dist/cli.js",
24
24
  "test": "vitest",
25
25
  "test:run": "vitest run",
26
- "test:prototype": "node prototype-kit/polish/tests/rules-check.mjs && node prototype-kit/polish/tests/harm-check.mjs && node prototype-kit/polish/tests/breathing-check.mjs && node prototype-kit/polish/tests/report-check.mjs && node prototype-kit/tests/rules-check.mjs && node prototype-kit/tests/offline-check.mjs && node prototype-kit/tests/shell-stitching-runtime.mjs && node prototype-kit/tests/adversarial-runtime.mjs --assert",
26
+ "test:prototype": "node prototype-kit/polish/tests/rules-check.mjs && node prototype-kit/polish/tests/harm-check.mjs && node prototype-kit/polish/tests/breathing-check.mjs && node prototype-kit/polish/tests/report-check.mjs && node prototype-kit/tests/rules-check.mjs && node prototype-kit/tests/offline-check.mjs && node prototype-kit/tests/shell-stitching-runtime.mjs && node prototype-kit/tests/shell-stitching-runtime.mjs --reintroduce-defect && node prototype-kit/tests/adversarial-runtime.mjs --assert",
27
27
  "typecheck": "tsc --noEmit && tsc -p tsconfig.type-tests.json",
28
28
  "prepublishOnly": "npm run build"
29
29
  },
@@ -4,7 +4,7 @@ import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { parseHTML } from 'linkedom';
6
6
  import { resolveDropdownAction } from './lib/actions.mjs';
7
- import { restoreActionPointerReachability } from './lib/action-target.mjs';
7
+ import { restoreActionPointerReachability, selectUniqueActionOwners } from './lib/action-target.mjs';
8
8
  import {
9
9
  HOLDING_SHELL_ATTRIBUTE,
10
10
  holdingBlockGeometry,
@@ -13,6 +13,7 @@ import {
13
13
  shellPlacementCss,
14
14
  } from './lib/holding-composition.mjs';
15
15
  import { injectReviewFraming } from './lib/review-framing.mjs';
16
+ import { removeRemoteHeadLinks } from './lib/local-document.mjs';
16
17
  import {
17
18
  isolateTransplantedShell,
18
19
  rulesFor,
@@ -156,6 +157,7 @@ const report = {
156
157
  unbound: [],
157
158
  invalidDropdowns: [],
158
159
  actionDemotions: [],
160
+ duplicateActionOwners: [],
159
161
  shellSizeDelta: [],
160
162
  shellAmbiguities: model.shellAmbiguities || [],
161
163
  suppressedShellOverlaps: [],
@@ -234,7 +236,9 @@ function actionsFor(s) {
234
236
  if (a) acts.set(c.id, a);
235
237
  else report.counts.none++;
236
238
  }
237
- return acts;
239
+ const normalized = selectUniqueActionOwners(loadDoc(s.file).document, acts);
240
+ report.duplicateActionOwners.push(...normalized.removed.map((removal) => ({ state: s.stateId, ...removal })));
241
+ return normalized.actions;
238
242
  }
239
243
 
240
244
  function bindAction(el, action) {
@@ -410,6 +414,7 @@ for (const u of units) {
410
414
  const cfg = { unit: u.id, states: u.states.map((s) => s.stateId), tokens: model.tokens, contentFile: `${u.id}.json`, activeNav: activeNav?.text || null };
411
415
  document.body.insertAdjacentHTML('beforeend', `<script id="proto-state" type="application/json">${JSON.stringify(cfg).replace(/</g, '\\u003c')}</script><script src="proto.js"></script>`);
412
416
  if (review) injectReviewFraming(document, review);
417
+ removeRemoteHeadLinks(document);
413
418
  fs.writeFileSync(path.join(outDir, outputFileOfUnit(u)), '<!doctype html>\n' + document.documentElement.outerHTML);
414
419
  fs.writeFileSync(path.join(outDir, 'content', u.id + '.json'), JSON.stringify(content, null, 1));
415
420
  report.units.push({
@@ -612,6 +617,7 @@ for (const u of units) {
612
617
  const cfg = { unit: hslug, holding: true, holdingBands: bandDeclarations, tokens: model.tokens, contentFile: null, activeNav: navMatch?.text || null };
613
618
  document.body.insertAdjacentHTML('beforeend', `<script id="proto-state" type="application/json">${JSON.stringify(cfg)}</script><script src="proto.js"></script>`);
614
619
  if (review) injectReviewFraming(document, review);
620
+ removeRemoteHeadLinks(document);
615
621
  fs.writeFileSync(path.join(outDir, outputFile), '<!doctype html>\n' + document.documentElement.outerHTML);
616
622
  }
617
623
  console.error(`holding pages: ${usedHoldings.size}`);
@@ -623,5 +629,5 @@ fs.writeFileSync(path.join(outDir, 'index.html'), `<!doctype html><meta http-equ
623
629
  const manifestHoldings = review
624
630
  ? [...usedHoldings.entries()].map(([holding, title]) => [holding.replace(/\.html$/, ''), title])
625
631
  : [...usedHoldings.entries()];
626
- fs.writeFileSync(path.join(outDir, 'proto-manifest.json'), JSON.stringify({ units: report.units, nav: navRows.map((n) => ({ text: n.text, target: n.target, holding: n.holding, rowId: n.rowId })), holdings: manifestHoldings, counts: report.counts, unbound: report.unbound, invalidDropdowns: report.invalidDropdowns, ...(report.actionDemotions.length > 0 ? { actionDemotions: report.actionDemotions } : {}), shellSizeDelta: report.shellSizeDelta, shellAmbiguities: report.shellAmbiguities, suppressedShellOverlaps: report.suppressedShellOverlaps, skippedShellTransplants: report.skippedShellTransplants, replacedShellActions: report.replacedShellActions, shellActionRemaps: report.shellActionRemaps, scriptDirections: states.map(({ stateId, scriptDirection, writingMode }) => ({ stateId, scriptDirection, writingMode })), canonicalShell: model.canonicalShell, canonicalShellPage: report.canonicalShellPage, shellConsistency: report.shellConsistency, ...(review ? { review } : {}) }, null, 1));
632
+ fs.writeFileSync(path.join(outDir, 'proto-manifest.json'), JSON.stringify({ units: report.units, nav: navRows.map((n) => ({ text: n.text, target: n.target, holding: n.holding, rowId: n.rowId })), holdings: manifestHoldings, counts: report.counts, unbound: report.unbound, invalidDropdowns: report.invalidDropdowns, ...(report.actionDemotions.length > 0 ? { actionDemotions: report.actionDemotions } : {}), ...(report.duplicateActionOwners.length > 0 ? { duplicateActionOwners: report.duplicateActionOwners } : {}), shellSizeDelta: report.shellSizeDelta, shellAmbiguities: report.shellAmbiguities, suppressedShellOverlaps: report.suppressedShellOverlaps, skippedShellTransplants: report.skippedShellTransplants, replacedShellActions: report.replacedShellActions, shellActionRemaps: report.shellActionRemaps, scriptDirections: states.map(({ stateId, scriptDirection, writingMode }) => ({ stateId, scriptDirection, writingMode })), canonicalShell: model.canonicalShell, canonicalShellPage: report.canonicalShellPage, shellConsistency: report.shellConsistency, ...(review ? { review } : {}) }, null, 1));
627
633
  console.error('counts', JSON.stringify(report.counts), 'unbound', report.unbound.length);
@@ -8,3 +8,29 @@ export function restoreActionPointerReachability(element) {
8
8
  element.setAttribute('data-proto-pointer-restored', 'true');
9
9
  return true;
10
10
  }
11
+
12
+ /**
13
+ * Give one semantic action to one rendered owner. A label response can name
14
+ * both a layout wrapper and one or more controls inside it. When descendants
15
+ * carry the same action kind, only the terminal-most elements are real action
16
+ * targets; retaining the wrapper adds a duplicate listener and an impossible
17
+ * hit-test identity. Different nested action kinds remain independent.
18
+ */
19
+ export function selectUniqueActionOwners(document, actions) {
20
+ const selected = new Map(actions);
21
+ const removed = [];
22
+ for (const [wrapperId, wrapperAction] of actions) {
23
+ const wrapper = document.getElementById(wrapperId);
24
+ if (!wrapper) continue;
25
+ const descendants = [...wrapper.querySelectorAll('[id]')].filter((candidate) => (
26
+ actions.get(candidate.id)?.action === wrapperAction.action
27
+ ));
28
+ if (descendants.length === 0) continue;
29
+ const ownerIds = descendants.filter((candidate) => ![...candidate.querySelectorAll('[id]')].some((nested) => (
30
+ actions.get(nested.id)?.action === wrapperAction.action
31
+ ))).map((candidate) => candidate.id);
32
+ selected.delete(wrapperId);
33
+ removed.push({ wrapperId, ownerIds, action: wrapperAction.action });
34
+ }
35
+ return { actions: selected, removed };
36
+ }
@@ -0,0 +1,16 @@
1
+ const REMOTE_HREF = /^(?:https?:)?\/\//iu;
2
+
3
+ /**
4
+ * Prototype files are owner-review artifacts that must open directly from
5
+ * disk. Remote head links turn an otherwise self-contained page into a noisy
6
+ * runtime failure when the review browser correctly blocks the network.
7
+ */
8
+ export function removeRemoteHeadLinks(document) {
9
+ let removed = 0;
10
+ for (const link of document.querySelectorAll('head link[href]')) {
11
+ if (!REMOTE_HREF.test(link.getAttribute('href')?.trim() ?? '')) continue;
12
+ link.remove();
13
+ removed += 1;
14
+ }
15
+ return removed;
16
+ }
@@ -1,6 +1,11 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
+ import {
5
+ compareCanonicalShellSamples,
6
+ SHELL_RUNTIME_GEOMETRY_EPSILON_PX,
7
+ } from './shell-runtime-equivalence.mjs';
8
+
4
9
  function errorText(error) {
5
10
  return error instanceof Error ? error.message : String(error);
6
11
  }
@@ -21,7 +26,23 @@ function configuredWidths(protoDir, file) {
21
26
  async function smokeInteraction(page, selector, assertion) {
22
27
  const locator = page.locator(selector).first();
23
28
  if (!await locator.count()) return { present: false, ok: true };
29
+ let reviewClearanceAdded = false;
24
30
  try {
31
+ reviewClearanceAdded = await locator.evaluate((element) => {
32
+ const framing = document.getElementById('proto-review-framing');
33
+ if (!framing) return false;
34
+ const box = framing.getBoundingClientRect();
35
+ const target = element.getBoundingClientRect();
36
+ const overlaps = Math.min(box.right, target.right) - Math.max(box.left, target.left) > 0
37
+ && Math.min(box.bottom, target.bottom) - Math.max(box.top, target.top) > 0;
38
+ if (!overlaps) return false;
39
+ const clearance = document.createElement('div');
40
+ clearance.setAttribute('data-proto-runtime-clearance', 'true');
41
+ clearance.setAttribute('aria-hidden', 'true');
42
+ clearance.style.cssText = `display:block;position:relative;flex:none;width:1px;height:${box.height + 36}px;pointer-events:none`;
43
+ document.body.appendChild(clearance);
44
+ return true;
45
+ });
25
46
  await locator.scrollIntoViewIfNeeded();
26
47
  // Playwright considers an element "in view" even when a fixed review
27
48
  // panel covers every point in its box. Center it before hit-testing so
@@ -55,6 +76,10 @@ async function smokeInteraction(page, selector, assertion) {
55
76
  return { present: true, ok, point, opened };
56
77
  } catch (error) {
57
78
  return { present: true, ok: false, error: errorText(error) };
79
+ } finally {
80
+ if (reviewClearanceAdded) {
81
+ await page.locator('[data-proto-runtime-clearance="true"]').evaluateAll((nodes) => nodes.forEach((node) => node.remove())).catch(() => null);
82
+ }
58
83
  }
59
84
  }
60
85
 
@@ -286,7 +311,12 @@ export async function checkRuntime({ browser, baseUrl, relativeProtoPath, protoD
286
311
  const host = screen?.querySelector('.ly-anchor-canvas') || screen;
287
312
  const hostZoom = host ? parseFloat(getComputedStyle(host).zoom) || 1 : 1;
288
313
  const round = (value) => Math.round(value * 10) / 10;
289
- const visualSignature = async (shell, declaration) => {
314
+ const digestJson = async (value) => {
315
+ const bytes = new TextEncoder().encode(JSON.stringify(value));
316
+ const digest = await crypto.subtle.digest('SHA-256', bytes);
317
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
318
+ };
319
+ const visualSample = async (shell, declaration) => {
290
320
  const rootBox = shell.getBoundingClientRect();
291
321
  const markerIds = new Set(declaration.activeMarkerIds || []);
292
322
  const visualNodes = [shell, ...shell.querySelectorAll('*')]
@@ -306,10 +336,10 @@ export async function checkRuntime({ browser, baseUrl, relativeProtoPath, protoD
306
336
  label: node.getAttribute('alt') || node.getAttribute('aria-label') || '',
307
337
  source: node.getAttribute('src') || '',
308
338
  box: [
309
- round((box.left - rootBox.left) / hostZoom),
310
- round((box.top - rootBox.top) / hostZoom),
311
- round(box.width / hostZoom),
312
- round(box.height / hostZoom),
339
+ (box.left - rootBox.left) / hostZoom,
340
+ (box.top - rootBox.top) / hostZoom,
341
+ box.width / hostZoom,
342
+ box.height / hostZoom,
313
343
  ],
314
344
  style: [
315
345
  style.display, node === shell ? 'shell-root' : style.position,
@@ -319,9 +349,13 @@ export async function checkRuntime({ browser, baseUrl, relativeProtoPath, protoD
319
349
  ],
320
350
  };
321
351
  }).filter((node) => node.box[2] > 0 && node.box[3] > 0);
322
- const bytes = new TextEncoder().encode(JSON.stringify({ region: declaration.region, visualNodes }));
323
- const digest = await crypto.subtle.digest('SHA-256', bytes);
324
- return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
352
+ const appearanceNodes = visualNodes.map(({ box, ...appearance }) => appearance);
353
+ const diagnosticNodes = visualNodes.map((node) => ({ ...node, box: node.box.map(round) }));
354
+ return {
355
+ appearanceSignature: await digestJson({ region: declaration.region, visualNodes: appearanceNodes }),
356
+ diagnosticSignature: await digestJson({ region: declaration.region, visualNodes: diagnosticNodes }),
357
+ geometry: visualNodes.map((node) => node.box),
358
+ };
325
359
  };
326
360
  for (const declaration of declared.regions || []) {
327
361
  const shell = document.getElementById(declaration.shellId);
@@ -329,15 +363,16 @@ export async function checkRuntime({ browser, baseUrl, relativeProtoPath, protoD
329
363
  failures.push({ kind: 'shell-consistency', region: declaration.region, reason: 'missing-declared-shell' });
330
364
  continue;
331
365
  }
332
- const actualSignature = await visualSignature(shell, declaration);
366
+ const sample = await visualSample(shell, declaration);
333
367
  const shellBox = shell.getBoundingClientRect();
334
368
  const hostBox = host?.getBoundingClientRect() || { left: 0, top: 0 };
335
- const actualGeometry = [
336
- round((shellBox.left - hostBox.left) / hostZoom),
337
- round((shellBox.top - hostBox.top) / hostZoom),
338
- round(shellBox.width / hostZoom),
339
- round(shellBox.height / hostZoom),
369
+ const comparisonGeometry = [
370
+ (shellBox.left - hostBox.left) / hostZoom,
371
+ (shellBox.top - hostBox.top) / hostZoom,
372
+ shellBox.width / hostZoom,
373
+ shellBox.height / hostZoom,
340
374
  ];
375
+ const actualGeometry = comparisonGeometry.map(round);
341
376
  let activeMatches = true;
342
377
  if (declaration.active?.status === 'linked') {
343
378
  const marker = document.getElementById(declaration.active.markerId);
@@ -364,8 +399,11 @@ export async function checkRuntime({ browser, baseUrl, relativeProtoPath, protoD
364
399
  regions.push({
365
400
  region: declaration.region,
366
401
  shellId: declaration.shellId,
367
- actualSignature,
402
+ actualSignature: sample.diagnosticSignature,
368
403
  actualGeometry,
404
+ appearanceSignature: sample.appearanceSignature,
405
+ visualGeometry: sample.geometry,
406
+ comparisonGeometry,
369
407
  inventorySampleSignature: declaration.canonicalSignature,
370
408
  consistencyStatus: declaration.consistencyStatus || 'canonical',
371
409
  activeStatus: declaration.active?.status || 'silent',
@@ -427,23 +465,48 @@ export async function checkRuntime({ browser, baseUrl, relativeProtoPath, protoD
427
465
  .map((failure) => ({ file: page.file, width: viewport.width, ...failure })) || []
428
466
  )));
429
467
  const shellRuntimeGroups = new Map();
468
+ const shellConsistencyTolerances = [];
430
469
  for (const page of pages) for (const viewport of page.viewports) {
431
470
  if (!unitByFile.has(page.file)) continue;
432
471
  for (const region of viewport.shellState?.regions || []) {
433
472
  if (region.consistencyStatus !== 'canonical') continue;
434
473
  const key = `${viewport.width}:${viewport.shellState.activeBand}:${region.region}`;
435
474
  if (!shellRuntimeGroups.has(key)) shellRuntimeGroups.set(key, []);
436
- shellRuntimeGroups.get(key).push({ file: page.file, signature: region.actualSignature, geometry: region.actualGeometry });
475
+ shellRuntimeGroups.get(key).push({
476
+ file: page.file,
477
+ signature: region.actualSignature,
478
+ geometry: region.actualGeometry,
479
+ appearanceSignature: region.appearanceSignature,
480
+ visualGeometry: region.visualGeometry,
481
+ shellGeometry: region.comparisonGeometry,
482
+ });
437
483
  }
438
484
  }
439
485
  for (const [key, entries] of shellRuntimeGroups) {
440
- const fingerprints = new Set(entries.map((entry) => JSON.stringify([entry.signature, entry.geometry])));
441
- if (fingerprints.size > 1) shellConsistencyFailures.push({
486
+ const reference = entries[0];
487
+ const comparisons = entries.slice(1).map((entry) => compareCanonicalShellSamples(reference, entry));
488
+ const equivalent = comparisons.every((comparison) => comparison.equivalent);
489
+ const pagesInGroup = entries.map(({ file, signature, geometry }) => ({ file, signature, geometry }));
490
+ if (!equivalent) shellConsistencyFailures.push({
442
491
  kind: 'shell-consistency',
443
492
  reason: 'prototype-shell-signature-mismatch',
444
493
  group: key,
445
- pages: entries,
494
+ pages: pagesInGroup,
446
495
  });
496
+ else {
497
+ const diagnosticFingerprints = new Set(entries.map((entry) => JSON.stringify([entry.signature, entry.geometry])));
498
+ if (diagnosticFingerprints.size > 1) shellConsistencyTolerances.push({
499
+ group: key,
500
+ epsilonPx: SHELL_RUNTIME_GEOMETRY_EPSILON_PX,
501
+ maxGeometryDelta: Math.max(0, ...comparisons.map((comparison) => comparison.maxGeometryDelta || 0)),
502
+ pages: pagesInGroup,
503
+ });
504
+ }
505
+ }
506
+ for (const page of pages) for (const viewport of page.viewports) for (const region of viewport.shellState?.regions || []) {
507
+ delete region.appearanceSignature;
508
+ delete region.visualGeometry;
509
+ delete region.comparisonGeometry;
447
510
  }
448
511
  const activeNavigationFailures = pages.flatMap((page) => page.viewports.flatMap((viewport) => (
449
512
  viewport.shellState?.failures?.filter((failure) => failure.kind === 'active-navigation')
@@ -458,6 +521,7 @@ export async function checkRuntime({ browser, baseUrl, relativeProtoPath, protoD
458
521
  interactionFailures: pages.filter((page) => page.dropdown?.ok === false || page.modal?.ok === false).map((page) => page.file),
459
522
  compositionFailures,
460
523
  shellConsistencyFailures,
524
+ shellConsistencyTolerances,
461
525
  activeNavigationFailures,
462
526
  deepLinkBack,
463
527
  };