@oscharko-dev/keiko-quality-intelligence 0.2.0

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 (203) hide show
  1. package/dist/.tsbuildinfo +1 -0
  2. package/dist/__tests__/_fixtureLoader.d.ts +9 -0
  3. package/dist/__tests__/_fixtureLoader.d.ts.map +1 -0
  4. package/dist/__tests__/_fixtureLoader.js +75 -0
  5. package/dist/domain/assertions.d.ts +61 -0
  6. package/dist/domain/assertions.d.ts.map +1 -0
  7. package/dist/domain/assertions.js +134 -0
  8. package/dist/domain/coverageRelevance.d.ts +73 -0
  9. package/dist/domain/coverageRelevance.d.ts.map +1 -0
  10. package/dist/domain/coverageRelevance.js +155 -0
  11. package/dist/domain/deduplication.d.ts +17 -0
  12. package/dist/domain/deduplication.d.ts.map +1 -0
  13. package/dist/domain/deduplication.js +95 -0
  14. package/dist/domain/figma/a11yBaseline.d.ts +17 -0
  15. package/dist/domain/figma/a11yBaseline.d.ts.map +1 -0
  16. package/dist/domain/figma/a11yBaseline.js +218 -0
  17. package/dist/domain/figma/cleanToScreenIr.d.ts +3 -0
  18. package/dist/domain/figma/cleanToScreenIr.d.ts.map +1 -0
  19. package/dist/domain/figma/cleanToScreenIr.js +62 -0
  20. package/dist/domain/figma/codeTargetAdapter.d.ts +30 -0
  21. package/dist/domain/figma/codeTargetAdapter.d.ts.map +1 -0
  22. package/dist/domain/figma/codeTargetAdapter.js +27 -0
  23. package/dist/domain/figma/color.d.ts +31 -0
  24. package/dist/domain/figma/color.d.ts.map +1 -0
  25. package/dist/domain/figma/color.js +99 -0
  26. package/dist/domain/figma/emissionPlan.d.ts +56 -0
  27. package/dist/domain/figma/emissionPlan.d.ts.map +1 -0
  28. package/dist/domain/figma/emissionPlan.js +87 -0
  29. package/dist/domain/figma/htmlCssAdapter.d.ts +13 -0
  30. package/dist/domain/figma/htmlCssAdapter.d.ts.map +1 -0
  31. package/dist/domain/figma/htmlCssAdapter.js +452 -0
  32. package/dist/domain/figma/index.d.ts +19 -0
  33. package/dist/domain/figma/index.d.ts.map +1 -0
  34. package/dist/domain/figma/index.js +31 -0
  35. package/dist/domain/figma/irTypes.d.ts +156 -0
  36. package/dist/domain/figma/irTypes.d.ts.map +1 -0
  37. package/dist/domain/figma/irTypes.js +8 -0
  38. package/dist/domain/figma/links.d.ts +6 -0
  39. package/dist/domain/figma/links.d.ts.map +1 -0
  40. package/dist/domain/figma/links.js +102 -0
  41. package/dist/domain/figma/navGraph.d.ts +74 -0
  42. package/dist/domain/figma/navGraph.d.ts.map +1 -0
  43. package/dist/domain/figma/navGraph.js +315 -0
  44. package/dist/domain/figma/normalize.d.ts +7 -0
  45. package/dist/domain/figma/normalize.d.ts.map +1 -0
  46. package/dist/domain/figma/normalize.js +252 -0
  47. package/dist/domain/figma/prune.d.ts +15 -0
  48. package/dist/domain/figma/prune.d.ts.map +1 -0
  49. package/dist/domain/figma/prune.js +65 -0
  50. package/dist/domain/figma/screenDetect.d.ts +8 -0
  51. package/dist/domain/figma/screenDetect.d.ts.map +1 -0
  52. package/dist/domain/figma/screenDetect.js +35 -0
  53. package/dist/domain/figma/screenIrTestBaseline.d.ts +52 -0
  54. package/dist/domain/figma/screenIrTestBaseline.d.ts.map +1 -0
  55. package/dist/domain/figma/screenIrTestBaseline.js +326 -0
  56. package/dist/domain/figma/semanticNaming.d.ts +24 -0
  57. package/dist/domain/figma/semanticNaming.d.ts.map +1 -0
  58. package/dist/domain/figma/semanticNaming.js +67 -0
  59. package/dist/domain/figma/sourceNode.d.ts +24 -0
  60. package/dist/domain/figma/sourceNode.d.ts.map +1 -0
  61. package/dist/domain/figma/sourceNode.js +26 -0
  62. package/dist/domain/figma/tokens.d.ts +11 -0
  63. package/dist/domain/figma/tokens.d.ts.map +1 -0
  64. package/dist/domain/figma/tokens.js +148 -0
  65. package/dist/domain/figma/visionAugmentation.d.ts +14 -0
  66. package/dist/domain/figma/visionAugmentation.d.ts.map +1 -0
  67. package/dist/domain/figma/visionAugmentation.js +48 -0
  68. package/dist/domain/intentDerivation.d.ts +21 -0
  69. package/dist/domain/intentDerivation.d.ts.map +1 -0
  70. package/dist/domain/intentDerivation.js +126 -0
  71. package/dist/domain/policyProfile.d.ts +37 -0
  72. package/dist/domain/policyProfile.d.ts.map +1 -0
  73. package/dist/domain/policyProfile.js +94 -0
  74. package/dist/domain/requirementExcerpt.d.ts +9 -0
  75. package/dist/domain/requirementExcerpt.d.ts.map +1 -0
  76. package/dist/domain/requirementExcerpt.js +39 -0
  77. package/dist/domain/staleness.d.ts +56 -0
  78. package/dist/domain/staleness.d.ts.map +1 -0
  79. package/dist/domain/staleness.js +313 -0
  80. package/dist/domain/testDesignModel.d.ts +38 -0
  81. package/dist/domain/testDesignModel.d.ts.map +1 -0
  82. package/dist/domain/testDesignModel.js +264 -0
  83. package/dist/domain/testQualityRubric.d.ts +20 -0
  84. package/dist/domain/testQualityRubric.d.ts.map +1 -0
  85. package/dist/domain/testQualityRubric.js +38 -0
  86. package/dist/domain/validation.d.ts +7 -0
  87. package/dist/domain/validation.d.ts.map +1 -0
  88. package/dist/domain/validation.js +145 -0
  89. package/dist/export/adapters/alm.d.ts +4 -0
  90. package/dist/export/adapters/alm.d.ts.map +1 -0
  91. package/dist/export/adapters/alm.js +75 -0
  92. package/dist/export/adapters/csv.d.ts +5 -0
  93. package/dist/export/adapters/csv.d.ts.map +1 -0
  94. package/dist/export/adapters/csv.js +55 -0
  95. package/dist/export/adapters/index.d.ts +13 -0
  96. package/dist/export/adapters/index.d.ts.map +1 -0
  97. package/dist/export/adapters/index.js +15 -0
  98. package/dist/export/adapters/jira.d.ts +5 -0
  99. package/dist/export/adapters/jira.d.ts.map +1 -0
  100. package/dist/export/adapters/jira.js +79 -0
  101. package/dist/export/adapters/json.d.ts +3 -0
  102. package/dist/export/adapters/json.d.ts.map +1 -0
  103. package/dist/export/adapters/json.js +54 -0
  104. package/dist/export/adapters/markdown.d.ts +3 -0
  105. package/dist/export/adapters/markdown.d.ts.map +1 -0
  106. package/dist/export/adapters/markdown.js +88 -0
  107. package/dist/export/adapters/plaintext.d.ts +3 -0
  108. package/dist/export/adapters/plaintext.d.ts.map +1 -0
  109. package/dist/export/adapters/plaintext.js +65 -0
  110. package/dist/export/adapters/polarion.d.ts +4 -0
  111. package/dist/export/adapters/polarion.d.ts.map +1 -0
  112. package/dist/export/adapters/polarion.js +67 -0
  113. package/dist/export/adapters/qtest.d.ts +4 -0
  114. package/dist/export/adapters/qtest.d.ts.map +1 -0
  115. package/dist/export/adapters/qtest.js +78 -0
  116. package/dist/export/adapters/qualityCenter.d.ts +3 -0
  117. package/dist/export/adapters/qualityCenter.d.ts.map +1 -0
  118. package/dist/export/adapters/qualityCenter.js +56 -0
  119. package/dist/export/adapters/spreadsheetSafeCsv.d.ts +36 -0
  120. package/dist/export/adapters/spreadsheetSafeCsv.d.ts.map +1 -0
  121. package/dist/export/adapters/spreadsheetSafeCsv.js +157 -0
  122. package/dist/export/adapters/traceability.d.ts +34 -0
  123. package/dist/export/adapters/traceability.d.ts.map +1 -0
  124. package/dist/export/adapters/traceability.js +142 -0
  125. package/dist/export/adapters/xray.d.ts +4 -0
  126. package/dist/export/adapters/xray.d.ts.map +1 -0
  127. package/dist/export/adapters/xray.js +72 -0
  128. package/dist/export/formats.d.ts +29 -0
  129. package/dist/export/formats.d.ts.map +1 -0
  130. package/dist/export/formats.js +34 -0
  131. package/dist/export/index.d.ts +4 -0
  132. package/dist/export/index.d.ts.map +1 -0
  133. package/dist/export/index.js +10 -0
  134. package/dist/export/serialize.d.ts +17 -0
  135. package/dist/export/serialize.d.ts.map +1 -0
  136. package/dist/export/serialize.js +56 -0
  137. package/dist/export/textSafety.d.ts +15 -0
  138. package/dist/export/textSafety.d.ts.map +1 -0
  139. package/dist/export/textSafety.js +30 -0
  140. package/dist/generation/candidateBounds.d.ts +10 -0
  141. package/dist/generation/candidateBounds.d.ts.map +1 -0
  142. package/dist/generation/candidateBounds.js +14 -0
  143. package/dist/generation/index.d.ts +4 -0
  144. package/dist/generation/index.d.ts.map +1 -0
  145. package/dist/generation/index.js +20 -0
  146. package/dist/generation/parseGeneratedCandidates.d.ts +27 -0
  147. package/dist/generation/parseGeneratedCandidates.d.ts.map +1 -0
  148. package/dist/generation/parseGeneratedCandidates.js +253 -0
  149. package/dist/generation/prompt.d.ts +16 -0
  150. package/dist/generation/prompt.d.ts.map +1 -0
  151. package/dist/generation/prompt.js +151 -0
  152. package/dist/generation/requirementsIngestion.d.ts +21 -0
  153. package/dist/generation/requirementsIngestion.d.ts.map +1 -0
  154. package/dist/generation/requirementsIngestion.js +70 -0
  155. package/dist/hardening/index.d.ts +6 -0
  156. package/dist/hardening/index.d.ts.map +1 -0
  157. package/dist/hardening/index.js +8 -0
  158. package/dist/hardening/oversizeGuards.d.ts +21 -0
  159. package/dist/hardening/oversizeGuards.d.ts.map +1 -0
  160. package/dist/hardening/oversizeGuards.js +35 -0
  161. package/dist/hardening/pathSafety.d.ts +19 -0
  162. package/dist/hardening/pathSafety.d.ts.map +1 -0
  163. package/dist/hardening/pathSafety.js +61 -0
  164. package/dist/hardening/promptInjectionScrub.d.ts +17 -0
  165. package/dist/hardening/promptInjectionScrub.d.ts.map +1 -0
  166. package/dist/hardening/promptInjectionScrub.js +72 -0
  167. package/dist/index.d.ts +22 -0
  168. package/dist/index.d.ts.map +1 -0
  169. package/dist/index.js +44 -0
  170. package/dist/ingestion/adfParser.d.ts +61 -0
  171. package/dist/ingestion/adfParser.d.ts.map +1 -0
  172. package/dist/ingestion/adfParser.js +262 -0
  173. package/dist/ingestion/index.d.ts +6 -0
  174. package/dist/ingestion/index.d.ts.map +1 -0
  175. package/dist/ingestion/index.js +10 -0
  176. package/dist/ingestion/sourceMixPlanning.d.ts +36 -0
  177. package/dist/ingestion/sourceMixPlanning.d.ts.map +1 -0
  178. package/dist/ingestion/sourceMixPlanning.js +65 -0
  179. package/dist/ingestion/sourceReconciliation.d.ts +39 -0
  180. package/dist/ingestion/sourceReconciliation.d.ts.map +1 -0
  181. package/dist/ingestion/sourceReconciliation.js +74 -0
  182. package/dist/ingestion/untrustedContentNormalisation.d.ts +23 -0
  183. package/dist/ingestion/untrustedContentNormalisation.d.ts.map +1 -0
  184. package/dist/ingestion/untrustedContentNormalisation.js +121 -0
  185. package/dist/ingestion/workspaceAdapter.d.ts +55 -0
  186. package/dist/ingestion/workspaceAdapter.d.ts.map +1 -0
  187. package/dist/ingestion/workspaceAdapter.js +113 -0
  188. package/dist/review/auditEvents.d.ts +61 -0
  189. package/dist/review/auditEvents.d.ts.map +1 -0
  190. package/dist/review/auditEvents.js +50 -0
  191. package/dist/review/fourEyes.d.ts +24 -0
  192. package/dist/review/fourEyes.d.ts.map +1 -0
  193. package/dist/review/fourEyes.js +45 -0
  194. package/dist/review/index.d.ts +5 -0
  195. package/dist/review/index.d.ts.map +1 -0
  196. package/dist/review/index.js +14 -0
  197. package/dist/review/lifecyclePolicy.d.ts +21 -0
  198. package/dist/review/lifecyclePolicy.d.ts.map +1 -0
  199. package/dist/review/lifecyclePolicy.js +38 -0
  200. package/dist/review/stateMachine.d.ts +28 -0
  201. package/dist/review/stateMachine.d.ts.map +1 -0
  202. package/dist/review/stateMachine.js +71 -0
  203. package/package.json +31 -0
@@ -0,0 +1,452 @@
1
+ // Framework-agnostic HTML/CSS CodeTargetAdapter — the first-slice code target (Epic #750, Issue #755).
2
+ //
3
+ // Renders the target-neutral emission plan (emissionPlan.ts) to clean, semantic HTML plus a CSS
4
+ // custom-property stylesheet built from the design tokens (#752). No framework, router, or component
5
+ // library: each element role maps to a semantic HTML tag, each design token to a CSS variable, and
6
+ // each screen's routing hints (#811) to a `<nav>` of plain anchors carrying the trigger as a data
7
+ // attribute. The adapter consumes token VARIABLES — it never re-derives or hard-codes raw values
8
+ // beyond emitting the token table itself.
9
+ //
10
+ // Output is a reviewable proposal (an ordered file list): `index.html` (links every screen),
11
+ // `tokens.css` (the `:root` custom-property table), and one `screens/<id>.html` per screen. Pure: no
12
+ // IO, no model, no clock — a given plan yields a byte-identical artifact. All text and attribute values
13
+ // are HTML-escaped so the reviewable artifact cannot inject markup, and unsafe Unicode format chars
14
+ // (bidi-override / zero-width / C0-C1 / DEL) are stripped from every emitted string before escaping —
15
+ // these are not HTML metacharacters and would otherwise survive into the artifact, enabling
16
+ // Trojan-source spoofing and zero-width-split secrets that evade redaction (same invariant the QI atom
17
+ // path enforces). The TAB/LF/CR trio and all ordinary text survive, so clean boards are unchanged.
18
+ //
19
+ // CSS value handling: fontFamily tokens are emitted as quoted strings with embedded double-quotes
20
+ // escaped and control/injection characters ('{', '}', ';', '</', '*/', newlines) stripped, so a
21
+ // hostile font name cannot break out of the custom-property declaration. Color tokens are validated
22
+ // against /^#[0-9a-fA-F]{3,8}$/ and numeric tokens as finite numbers; invalid values are dropped
23
+ // rather than emitted.
24
+ //
25
+ // Screen file names: Figma ids may contain ':' (Windows-invalid) and ';' (URI scheme risk in hrefs).
26
+ // Stored evidence can also be tampered with, so screen ids are normalized to one safe POSIX path
27
+ // segment ([A-Za-z0-9_-]) before they become CodeFile.path / href material. Collisions after
28
+ // substitution are resolved by a numeric suffix. All relative hrefs inside screen HTML are prefixed
29
+ // with './' so they are relative to the screens/ directory, not ambiguous URI-scheme fragments. The
30
+ // raw screen id is preserved in the data-screen-id attribute.
31
+ //
32
+ // Layout / sizing / cornerRadius / typography (from IrNode, threaded through EmissionElement):
33
+ // For nodes with auto-layout, a deterministic CSS class is emitted (name = "n-" + sanitized node id)
34
+ // carrying display:flex, flex-direction, gap, padding, and border-radius. TEXT nodes with typography
35
+ // matching a tokens.css entry reference var(--font-N). fill-sized nodes emit flex:1 / width:100% on
36
+ // the relevant axis; hug is the default (no output).
37
+ //
38
+ // What IS reproduced: auto-layout direction, gap, padding, border-radius, font (via token var or
39
+ // inline).
40
+ // What is NOT reproduced: absolute positioning, constraints, effects (shadows/blur), image fills
41
+ // beyond refs, grid layout, overflow, z-ordering, component variants.
42
+ import { stripUnsafeFormatChars } from "../assertions.js";
43
+ const ADAPTER_NAME = "html-css";
44
+ const INDENT = " ";
45
+ const HTML_ESCAPES = {
46
+ "&": "&amp;",
47
+ "<": "&lt;",
48
+ ">": "&gt;",
49
+ '"': "&quot;",
50
+ "'": "&#39;",
51
+ };
52
+ // Strip unsafe Unicode format chars (bidi-override / zero-width / C0-C1 / DEL) BEFORE HTML-escaping.
53
+ // These are NOT HTML metacharacters, so escaping alone passes them verbatim into the reviewable
54
+ // artifact — enabling Trojan-source spoofing and zero-width-split secrets that evade redaction. This
55
+ // mirrors the QI atom-text invariant (stripUnsafeFormatChars). Clean text is unchanged — the TAB/LF/CR
56
+ // trio and all ordinary/accented/CJK/emoji code points survive — so deterministic output stays
57
+ // byte-identical for non-hostile boards.
58
+ const escapeHtml = (value) => stripUnsafeFormatChars(value).replace(/[&<>"']/gu, (char) => HTML_ESCAPES[char] ?? char);
59
+ const indent = (depth) => INDENT.repeat(depth);
60
+ // ─── Fix #7: safe screen file names ──────────────────────────────────────────
61
+ //
62
+ // Figma ids contain ':' (invalid on Windows file paths) and INSTANCE ids contain ';' which is parsed
63
+ // as a URI scheme separator in sibling hrefs (e.g. "I123:456;789:12.html" → opaque URI). A tampered
64
+ // stored snapshot could also contain slashes, backslashes, or other path metacharacters. Normalize to
65
+ // a single artifact-relative filename segment. Ids are unique before substitution so collisions are
66
+ // rare, but a numeric suffix is appended defensively.
67
+ const SAFE_SCREEN_FILE_RE = /[^A-Za-z0-9_-]/gu;
68
+ function sanitizeScreenFileName(screenId) {
69
+ const cleaned = stripUnsafeFormatChars(screenId)
70
+ .replace(SAFE_SCREEN_FILE_RE, "-")
71
+ .replace(/-+/gu, "-")
72
+ .replace(/^-|-$/gu, "");
73
+ return cleaned.length > 0 ? cleaned : "screen";
74
+ }
75
+ function buildSafeNameIndex(screens) {
76
+ const seen = new Map();
77
+ const result = new Map();
78
+ for (const screen of screens) {
79
+ const base = sanitizeScreenFileName(screen.screenId);
80
+ const count = seen.get(base) ?? 0;
81
+ seen.set(base, count + 1);
82
+ result.set(screen.screenId, count === 0 ? base : `${base}-${String(count)}`);
83
+ }
84
+ return result;
85
+ }
86
+ // ─── Fix #8: CSS value sanitization ──────────────────────────────────────────
87
+ //
88
+ // fontFamily is emitted as a CSS quoted string. Embedded double-quotes are escaped as \\22 (the
89
+ // CSS hex escape for ") and injection sequences ('{', '}', ';', '</', '*/', newlines, control
90
+ // chars) are stripped, so a hostile font name cannot break out of the declaration.
91
+ // Unicode escapes for control characters (U+0000-U+001F) and DEL (U+007F) avoid the no-control-regex
92
+ // lint rule while matching the same character set at runtime.
93
+ // eslint-disable-next-line no-control-regex
94
+ const CSS_INJECTION_RE = /[{};]|<\/|\*\/|[\u0000-\u001f\u007f]/gu;
95
+ const safeFontFamily = (family) => {
96
+ // Strip unsafe Unicode format chars first — bidi/zero-width/C1 are NOT covered by CSS_INJECTION_RE
97
+ // (which only strips C0/DEL + structural injection sequences) — then escape embedded quotes. Same
98
+ // egress invariant as escapeHtml: these chars would otherwise survive into the quoted CSS string.
99
+ const cleaned = stripUnsafeFormatChars(family)
100
+ .replace(CSS_INJECTION_RE, "")
101
+ .replace(/"/gu, "\\22 ");
102
+ return `"${cleaned}"`;
103
+ };
104
+ // Valid CSS hex color: 3, 4, 6, or 8 hex digits.
105
+ const HEX_COLOR_RE = /^#[0-9a-fA-F]{3,8}$/u;
106
+ const isSafeColor = (value) => HEX_COLOR_RE.test(value);
107
+ // ─── Map a target-neutral element role to a semantic HTML tag ─────────────────
108
+ // Containers become <section>; everything else is the closest semantic element.
109
+ const TAG_BY_ROLE = {
110
+ button: "button",
111
+ input: "input",
112
+ link: "a",
113
+ text: "p",
114
+ image: "img",
115
+ container: "section",
116
+ };
117
+ // Roles that render as void (self-closing) elements with no children/text.
118
+ const VOID_ROLES = new Set(["input", "image"]);
119
+ const colorVar = (index) => `--color-${String(index + 1)}`;
120
+ const spaceVar = (index) => `--space-${String(index + 1)}`;
121
+ const radiusVar = (index) => `--radius-${String(index + 1)}`;
122
+ const fontVar = (index) => `--font-${String(index + 1)}`;
123
+ // Typography key used to match per-node typography against the global token table.
124
+ const typographyKey = (t) => `${t.fontFamily}|${String(t.fontSize)}|${String(t.fontWeight)}|${String(t.lineHeight ?? "")}`;
125
+ const typographyTokenKey = (t) => `${t.fontFamily}|${String(t.fontSize)}|${String(t.fontWeight)}|${String(t.lineHeight)}`;
126
+ const buildTokenLookups = (tokens) => {
127
+ const colorMap = new Map();
128
+ tokens.colors.forEach((token, i) => {
129
+ colorMap.set(token.value, colorVar(i));
130
+ });
131
+ const fontMap = new Map();
132
+ tokens.typography.forEach((token, i) => {
133
+ fontMap.set(typographyTokenKey(token), fontVar(i));
134
+ });
135
+ const spaceMap = new Map();
136
+ tokens.spacing.forEach((token, i) => {
137
+ spaceMap.set(token.value, spaceVar(i));
138
+ });
139
+ const radiusMap = new Map();
140
+ tokens.radius.forEach((token, i) => {
141
+ radiusMap.set(token.value, radiusVar(i));
142
+ });
143
+ return { colorVar: colorMap, fontVar: fontMap, spaceVar: spaceMap, radiusVar: radiusMap };
144
+ };
145
+ // ─── Per-node CSS class generation ───────────────────────────────────────────
146
+ //
147
+ // A deterministic class name is derived from the node id by replacing non-alphanumeric characters
148
+ // with "-" and prefixing "n-". When two raw ids sanitize to the same slug, append a stable raw-id
149
+ // hash to the later class so distinct Figma nodes cannot alias onto one CSS selector.
150
+ const sanitizeIdForClass = (id) => id.replace(/[^a-zA-Z0-9]/gu, "-");
151
+ const classHash = (id) => {
152
+ let hash = 0x811c9dc5;
153
+ for (let i = 0; i < id.length; i += 1) {
154
+ hash ^= id.charCodeAt(i);
155
+ hash = Math.imul(hash, 0x01000193);
156
+ }
157
+ return (hash >>> 0).toString(36);
158
+ };
159
+ const nodeClassBase = (id) => `n-${sanitizeIdForClass(id) || "node"}`;
160
+ const nodeClass = (id, ctx) => {
161
+ const existing = ctx.classMap.get(id);
162
+ if (existing !== undefined)
163
+ return existing;
164
+ const base = nodeClassBase(id);
165
+ const owner = ctx.usedClasses.get(base);
166
+ const cls = owner === undefined || owner === id ? base : `${base}-${classHash(id)}`;
167
+ ctx.usedClasses.set(cls, id);
168
+ ctx.classMap.set(id, cls);
169
+ return cls;
170
+ };
171
+ const ALIGN_CSS = {
172
+ start: "flex-start",
173
+ center: "center",
174
+ end: "flex-end",
175
+ "space-between": "space-between",
176
+ };
177
+ // Resolve a numeric spacing or radius value to a CSS token var or an inline px literal.
178
+ const spaceValue = (value, lookups) => {
179
+ const varName = lookups.spaceVar.get(value);
180
+ return varName !== undefined ? `var(${varName})` : `${String(value)}px`;
181
+ };
182
+ const radiusValue = (value, lookups) => {
183
+ const varName = lookups.radiusVar.get(value);
184
+ return varName !== undefined ? `var(${varName})` : `${String(value)}px`;
185
+ };
186
+ // Build CSS declarations for a layout node. Returns undefined when nothing would be emitted.
187
+ const layoutDeclarations = (layout, lookups) => {
188
+ const decls = ["display: flex;", `flex-direction: ${layout.mode};`];
189
+ if (layout.itemSpacing !== undefined && Number.isFinite(layout.itemSpacing)) {
190
+ decls.push(`gap: ${spaceValue(layout.itemSpacing, lookups)};`);
191
+ }
192
+ if (layout.padding !== undefined) {
193
+ const [top, right, bottom, left] = layout.padding;
194
+ const t = spaceValue(top, lookups);
195
+ const r = spaceValue(right, lookups);
196
+ const b = spaceValue(bottom, lookups);
197
+ const l = spaceValue(left, lookups);
198
+ decls.push(`padding: ${t} ${r} ${b} ${l};`);
199
+ }
200
+ if (layout.primaryAlign !== undefined) {
201
+ decls.push(`justify-content: ${ALIGN_CSS[layout.primaryAlign]};`);
202
+ }
203
+ if (layout.counterAlign !== undefined) {
204
+ decls.push(`align-items: ${ALIGN_CSS[layout.counterAlign]};`);
205
+ }
206
+ return decls;
207
+ };
208
+ // Build CSS declarations for sizing (fill = flex:1, hug = nothing [default], fixed = nothing here).
209
+ const sizingDeclarations = (sizing) => {
210
+ const decls = [];
211
+ if (sizing.horizontal === "fill")
212
+ decls.push("width: 100%;");
213
+ if (sizing.vertical === "fill")
214
+ decls.push("flex: 1;");
215
+ return decls;
216
+ };
217
+ // Build CSS declarations for typography. Prefer token var when matched; inline otherwise.
218
+ const typographyDeclarations = (typo, lookups) => {
219
+ const fontVarName = lookups.fontVar.get(typographyKey(typo));
220
+ if (fontVarName !== undefined) {
221
+ return [`font: var(${fontVarName});`];
222
+ }
223
+ // Inline fallback: validate each value before emitting.
224
+ const decls = [];
225
+ if (Number.isFinite(typo.fontWeight))
226
+ decls.push(`font-weight: ${String(typo.fontWeight)};`);
227
+ if (Number.isFinite(typo.fontSize))
228
+ decls.push(`font-size: ${String(typo.fontSize)}px;`);
229
+ if (typo.lineHeight !== undefined && Number.isFinite(typo.lineHeight)) {
230
+ decls.push(`line-height: ${String(typo.lineHeight)}px;`);
231
+ }
232
+ if (typo.fontFamily.length > 0)
233
+ decls.push(`font-family: ${safeFontFamily(typo.fontFamily)};`);
234
+ return decls;
235
+ };
236
+ // Walk the element tree, collect CSS rules, populate classMap.
237
+ const collectStyles = (element, ctx) => {
238
+ const decls = [
239
+ ...(element.layout !== undefined ? layoutDeclarations(element.layout, ctx.lookups) : []),
240
+ ...(element.sizing !== undefined ? sizingDeclarations(element.sizing) : []),
241
+ ...(element.cornerRadius !== undefined && Number.isFinite(element.cornerRadius)
242
+ ? [`border-radius: ${radiusValue(element.cornerRadius, ctx.lookups)};`]
243
+ : []),
244
+ ...(element.typography !== undefined
245
+ ? typographyDeclarations(element.typography, ctx.lookups)
246
+ : []),
247
+ ];
248
+ if (decls.length > 0) {
249
+ const cls = nodeClass(element.id, ctx);
250
+ ctx.rules.push(`.${cls} {`);
251
+ for (const decl of decls)
252
+ ctx.rules.push(` ${decl}`);
253
+ ctx.rules.push("}");
254
+ }
255
+ // renderElement discards the children of void-role elements (image/input), so collecting their
256
+ // styles would emit orphaned CSS rules referencing classes that appear on no rendered element.
257
+ // Skip the recursion for void roles to keep the stylesheet aligned with the emitted HTML.
258
+ if (!VOID_ROLES.has(element.role)) {
259
+ for (const child of element.children)
260
+ collectStyles(child, ctx);
261
+ }
262
+ };
263
+ // ─── HTML element rendering ───────────────────────────────────────────────────
264
+ // Fix #6: additionally emit data-node-id so the element's IR origin is traceable in the HTML output.
265
+ // True when this element renders any visible text — its own text or a descendant TEXT node's text.
266
+ // A button/link is usually a container whose visible label lives in a child TEXT node, so checking
267
+ // only the element's OWN `text` misses it (WCAG 4.1.2 / 2.5.3).
268
+ const hasRenderedText = (element) => element.text !== undefined || element.children.some(hasRenderedText);
269
+ function elementAttributes(element, classMap) {
270
+ const parts = [
271
+ `data-role="${escapeHtml(element.role)}"`,
272
+ `data-name="${escapeHtml(element.displayName)}"`,
273
+ `data-node-id="${escapeHtml(element.id)}"`,
274
+ ];
275
+ const cls = classMap.get(element.id);
276
+ if (cls !== undefined)
277
+ parts.push(`class="${escapeHtml(cls)}"`);
278
+ if (element.role === "link")
279
+ parts.push('href="#"');
280
+ if (element.role === "input")
281
+ parts.push(`aria-label="${escapeHtml(element.displayName)}"`);
282
+ if (element.role === "image")
283
+ parts.push(`alt="${escapeHtml(element.displayName)}"`);
284
+ // A button/link with NO visible text (icon-only) gets no accessible name from its content; fall
285
+ // back to the structural display name so the artifact stays screen-reader navigable (WCAG 4.1.2).
286
+ // But when it DOES render visible text (own or in a child TEXT node), adding aria-label would
287
+ // OVERRIDE that visible label for assistive tech (and break label-in-name, WCAG 2.5.3) — so skip it.
288
+ if ((element.role === "button" || element.role === "link") && !hasRenderedText(element)) {
289
+ parts.push(`aria-label="${escapeHtml(element.displayName)}"`);
290
+ }
291
+ return parts.join(" ");
292
+ }
293
+ function renderElement(element, depth, classMap) {
294
+ const tag = TAG_BY_ROLE[element.role];
295
+ const attributes = elementAttributes(element, classMap);
296
+ if (VOID_ROLES.has(element.role)) {
297
+ return [`${indent(depth)}<${tag} ${attributes} />`];
298
+ }
299
+ const lines = [`${indent(depth)}<${tag} ${attributes}>`];
300
+ if (element.text !== undefined)
301
+ lines.push(`${indent(depth + 1)}${escapeHtml(element.text)}`);
302
+ for (const child of element.children)
303
+ lines.push(...renderElement(child, depth + 1, classMap));
304
+ lines.push(`${indent(depth)}</${tag}>`);
305
+ return lines;
306
+ }
307
+ // Fix #7: hrefs use the sanitized name and are prefixed with './' so they resolve relative to the
308
+ // screens/ directory and cannot be misinterpreted as URI schemes.
309
+ function renderNav(navTargets, safeNames, depth) {
310
+ if (navTargets.length === 0)
311
+ return [];
312
+ const lines = [`${indent(depth)}<nav aria-label="Screen navigation">`];
313
+ for (const target of navTargets) {
314
+ const safeName = safeNames.get(target.toScreenId) ?? sanitizeScreenFileName(target.toScreenId);
315
+ const href = `./${escapeHtml(safeName)}.html`;
316
+ const trigger = escapeHtml(target.trigger);
317
+ const label = escapeHtml(target.toScreenName);
318
+ lines.push(`${indent(depth + 1)}<a href="${href}" data-trigger="${trigger}">${label}</a>`);
319
+ }
320
+ lines.push(`${indent(depth)}</nav>`);
321
+ return lines;
322
+ }
323
+ // Fix #7: screen file uses safe name; raw id is preserved in data-screen-id for traceability.
324
+ function renderScreenHtml(screen, safeNames, lookups) {
325
+ // Collect per-node styles first so classMap is populated before HTML rendering.
326
+ const ctx = {
327
+ lookups,
328
+ classMap: new Map(),
329
+ usedClasses: new Map(),
330
+ rules: [],
331
+ };
332
+ collectStyles(screen.root, ctx);
333
+ const styleBlock = ctx.rules.length > 0
334
+ ? [
335
+ `${indent(2)}<style>`,
336
+ ...ctx.rules.map((line) => `${indent(2)}${line}`),
337
+ `${indent(2)}</style>`,
338
+ ]
339
+ : [];
340
+ const title = escapeHtml(screen.screenName);
341
+ const body = [
342
+ ...renderNav(screen.navTargets, safeNames, 3),
343
+ `${indent(3)}<main data-screen-id="${escapeHtml(screen.screenId)}">`,
344
+ ...renderElement(screen.root, 4, ctx.classMap),
345
+ `${indent(3)}</main>`,
346
+ ];
347
+ return [
348
+ "<!doctype html>",
349
+ '<html>',
350
+ `${indent(1)}<head>`,
351
+ `${indent(2)}<meta charset="utf-8" />`,
352
+ `${indent(2)}<title>${title}</title>`,
353
+ `${indent(2)}<link rel="stylesheet" href="../tokens.css" />`,
354
+ ...styleBlock,
355
+ `${indent(1)}</head>`,
356
+ `${indent(1)}<body>`,
357
+ ...body,
358
+ `${indent(1)}</body>`,
359
+ "</html>",
360
+ "",
361
+ ].join("\n");
362
+ }
363
+ // Fix #7: index links use safe names for the href path but display the human-readable screen name.
364
+ function renderIndexHtml(screens, safeNames) {
365
+ const links = screens.map((screen) => {
366
+ const safeName = safeNames.get(screen.screenId) ?? sanitizeScreenFileName(screen.screenId);
367
+ return (`${indent(3)}<li><a href="screens/${escapeHtml(safeName)}.html">` +
368
+ `${escapeHtml(screen.screenName)}</a></li>`);
369
+ });
370
+ return [
371
+ "<!doctype html>",
372
+ '<html>',
373
+ `${indent(1)}<head>`,
374
+ `${indent(2)}<meta charset="utf-8" />`,
375
+ `${indent(2)}<title>Screens</title>`,
376
+ `${indent(2)}<link rel="stylesheet" href="tokens.css" />`,
377
+ `${indent(1)}</head>`,
378
+ `${indent(1)}<body>`,
379
+ `${indent(2)}<nav aria-label="All screens">`,
380
+ `${indent(3)}<ul>`,
381
+ ...links,
382
+ `${indent(3)}</ul>`,
383
+ `${indent(2)}</nav>`,
384
+ `${indent(1)}</body>`,
385
+ "</html>",
386
+ "",
387
+ ].join("\n");
388
+ }
389
+ // Fix #8: validate color before emit; drop invalid tokens rather than emitting them.
390
+ const colorLine = (token, index) => isSafeColor(token.value) ? `${indent(1)}${colorVar(index)}: ${token.value};` : undefined;
391
+ const spaceLine = (token, index) => Number.isFinite(token.value)
392
+ ? `${indent(1)}${spaceVar(index)}: ${String(token.value)}px;`
393
+ : undefined;
394
+ const radiusLine = (token, index) => Number.isFinite(token.value)
395
+ ? `${indent(1)}${radiusVar(index)}: ${String(token.value)}px;`
396
+ : undefined;
397
+ // Fix #8: fontFamily is sanitized via safeFontFamily (quoted + injection chars stripped).
398
+ // Weight, size, lineHeight are validated as finite numbers before emit.
399
+ const fontLine = (token, index) => {
400
+ if (!Number.isFinite(token.fontWeight) ||
401
+ !Number.isFinite(token.fontSize) ||
402
+ !Number.isFinite(token.lineHeight)) {
403
+ return undefined;
404
+ }
405
+ return (`${indent(1)}${fontVar(index)}: ${String(token.fontWeight)} ${String(token.fontSize)}px/` +
406
+ `${String(token.lineHeight)}px ${safeFontFamily(token.fontFamily)};`);
407
+ };
408
+ function renderTokensCss(tokens) {
409
+ const lines = [
410
+ ...tokens.colors.map(colorLine).filter((l) => l !== undefined),
411
+ ...tokens.spacing.map(spaceLine).filter((l) => l !== undefined),
412
+ ...tokens.radius.map(radiusLine).filter((l) => l !== undefined),
413
+ ...tokens.typography.map(fontLine).filter((l) => l !== undefined),
414
+ ];
415
+ return [
416
+ "/* Design tokens (deterministic, from the Figma Snapshot Screen-IR). */",
417
+ ":root {",
418
+ ...lines,
419
+ "}",
420
+ "",
421
+ ].join("\n");
422
+ }
423
+ function emitHtmlCss(plan) {
424
+ const safeNames = buildSafeNameIndex(plan.screens);
425
+ const lookups = buildTokenLookups(plan.tokens);
426
+ const files = [
427
+ { path: "index.html", contents: renderIndexHtml(plan.screens, safeNames) },
428
+ { path: "tokens.css", contents: renderTokensCss(plan.tokens) },
429
+ ...plan.screens.map((screen) => {
430
+ const safeName = safeNames.get(screen.screenId) ?? sanitizeScreenFileName(screen.screenId);
431
+ return {
432
+ path: `screens/${safeName}.html`,
433
+ contents: renderScreenHtml(screen, safeNames, lookups),
434
+ };
435
+ }),
436
+ ];
437
+ return { adapterName: ADAPTER_NAME, files };
438
+ }
439
+ /**
440
+ * The framework-agnostic HTML/CSS adapter — the only adapter shipped in the first slice. Renders the
441
+ * target-neutral plan to semantic HTML per screen, a `tokens.css` custom-property table, and an
442
+ * `index.html`. Pure and deterministic: a given plan yields a byte-identical artifact.
443
+ *
444
+ * Layout fidelity: nodes with auto-layout emit display:flex + direction + gap + padding + radius in a
445
+ * per-screen `<style>` block; TEXT nodes with matching typography tokens emit var(--font-N); fill-sized
446
+ * nodes emit flex:1 / width:100%. Absolute positioning, constraints, effects, and image content are
447
+ * not reproduced.
448
+ */
449
+ export const htmlCssAdapter = {
450
+ name: ADAPTER_NAME,
451
+ emit: emitHtmlCss,
452
+ };
@@ -0,0 +1,19 @@
1
+ export { cleanScopedNodesToScreenIr } from "./cleanToScreenIr.js";
2
+ export { parseDesignTokens } from "./tokens.js";
3
+ export { parseScreenIr, deriveScreenTestBaseline, renderBaselineText, } from "./screenIrTestBaseline.js";
4
+ export type { ScreenTestBaseline, StructuralTestCategory, StructuralTestItem, StructuralTestOutcome, } from "./screenIrTestBaseline.js";
5
+ export { mergeVisionHints } from "./visionAugmentation.js";
6
+ export type { VisionMergeResult } from "./visionAugmentation.js";
7
+ export { deriveNavGraph, deriveNavFlows, deriveRoutingHints, deriveNavTestItemsByScreen, } from "./navGraph.js";
8
+ export type { NavEdge, NavFlow, NavGraph, NavNode, RoutingHint, UnresolvedLink, } from "./navGraph.js";
9
+ export { buildEmissionPlan } from "./emissionPlan.js";
10
+ export type { CodeEmissionPlan, EmissionElement, EmissionInput, EmissionNavTarget, EmissionRole, ScreenEmission, } from "./emissionPlan.js";
11
+ export { emitCode } from "./codeTargetAdapter.js";
12
+ export type { CodeArtifact, CodeFile, CodeTargetAdapter } from "./codeTargetAdapter.js";
13
+ export { applyNaming } from "./semanticNaming.js";
14
+ export type { SemanticNamingProvider, SemanticNamingRequest } from "./semanticNaming.js";
15
+ export { htmlCssAdapter } from "./htmlCssAdapter.js";
16
+ export { deriveA11yTestItemsByScreen, relativeLuminance, contrastRatio, meetsContrastAa, } from "./a11yBaseline.js";
17
+ export type { FigmaSourceNode } from "./sourceNode.js";
18
+ export type { AlignItems, BoundingBox, ColorToken, DesignTokens, ImageFillRef, InteractionHint, InterScreenLink, IrLayout, IrNode, IrSizing, IrTypography, LayoutMode, LayoutSizing, RadiusToken, ReductionReport, ScreenIr, ScreenIrResult, SpacingToken, TypographyToken, } from "./irTypes.js";
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/domain/figma/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAC;AAGlE,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIhD,OAAO,EACL,aAAa,EACb,wBAAwB,EACxB,kBAAkB,GACnB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACV,kBAAkB,EAClB,sBAAsB,EACtB,kBAAkB,EAClB,qBAAqB,GACtB,MAAM,2BAA2B,CAAC;AAGnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAKjE,OAAO,EACL,cAAc,EACd,cAAc,EACd,kBAAkB,EAClB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AACvB,YAAY,EACV,OAAO,EACP,OAAO,EACP,QAAQ,EACR,OAAO,EACP,WAAW,EACX,cAAc,GACf,MAAM,eAAe,CAAC;AAMvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,iBAAiB,EACjB,YAAY,EACZ,cAAc,GACf,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAClD,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAExF,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,YAAY,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAEzF,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,OAAO,EACL,2BAA2B,EAC3B,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,YAAY,EACV,UAAU,EACV,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,eAAe,EACf,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,WAAW,EACX,eAAe,EACf,QAAQ,EACR,cAAc,EACd,YAAY,EACZ,eAAe,GAChB,MAAM,cAAc,CAAC"}
@@ -0,0 +1,31 @@
1
+ // Public barrel for the Quality Intelligence Figma Screen-IR sub-namespace (Epic #750, Issue #752).
2
+ //
3
+ // Pure-domain cleaner: raw scoped Figma node tree → lean per-screen IR + deduped design tokens +
4
+ // raw inter-screen links + reduction report. No IO, no network, no model. Downstream stages import
5
+ // these types: #753 (snapshot evidence), #754 (QI source), #811 (nav graph, from `links`), #812
6
+ // (a11y), #755 (codegen, from `tokens`).
7
+ export { cleanScopedNodesToScreenIr } from "./cleanToScreenIr.js";
8
+ // Tolerant re-hydration of the persisted, opaque design-tokens artifact (#752) for design-to-code
9
+ // (#755), which reads the STORED snapshot. Total + defensive; same shape as `extractDesignTokens`.
10
+ export { parseDesignTokens } from "./tokens.js";
11
+ // Structural test-baseline derivation + the defensive Screen-IR parser for the snapshot's opaque
12
+ // `irJson` (Issue #754). Deterministic, model-free: same IR → byte-identical baseline.
13
+ export { parseScreenIr, deriveScreenTestBaseline, renderBaselineText, } from "./screenIrTestBaseline.js";
14
+ // Vision merge that structurally enforces "vision augments, never overrides the IR" (Issue #754).
15
+ export { mergeVisionHints } from "./visionAugmentation.js";
16
+ // Deterministic navigation/flow graph + routing hints + per-screen nav test items (Issue #811).
17
+ // Pure, model-free: derived from the IR's inter-screen links; composes into the QI run additively
18
+ // through `deriveScreenTestBaseline`'s `extraItems` seam.
19
+ export { deriveNavGraph, deriveNavFlows, deriveRoutingHints, deriveNavTestItemsByScreen, } from "./navGraph.js";
20
+ // Deterministic design-to-code emission: target-neutral plan + pluggable CodeTargetAdapter seam +
21
+ // the framework-agnostic HTML/CSS adapter + the model-only-for-naming port (Issue #755). Pure,
22
+ // model-free: the structural emission is fully deterministic; the naming port only renames elements
23
+ // and never changes structure. Consumes design tokens (#752) and routing hints (#811).
24
+ export { buildEmissionPlan } from "./emissionPlan.js";
25
+ export { emitCode } from "./codeTargetAdapter.js";
26
+ export { applyNaming } from "./semanticNaming.js";
27
+ export { htmlCssAdapter } from "./htmlCssAdapter.js";
28
+ // Deterministic accessibility baseline + WCAG contrast math (Issue #812). Pure, model-free: derived
29
+ // from the Screen-IR's colour/box fields; composes into the QI run additively through
30
+ // `deriveScreenTestBaseline`'s `extraItems` seam, ALONGSIDE the navigation items above.
31
+ export { deriveA11yTestItemsByScreen, relativeLuminance, contrastRatio, meetsContrastAa, } from "./a11yBaseline.js";
@@ -0,0 +1,156 @@
1
+ /** A best-effort structural/role hint for a kept node. Never load-bearing downstream. */
2
+ export type InteractionHint = "button" | "input" | "link" | "text" | "image" | "container";
3
+ export interface BoundingBox {
4
+ readonly x: number;
5
+ readonly y: number;
6
+ readonly width: number;
7
+ readonly height: number;
8
+ }
9
+ /** A reference to an image fill on a node (the imageRef the provider assigns to the fill). */
10
+ export interface ImageFillRef {
11
+ readonly imageRef: string;
12
+ }
13
+ /** Auto-layout direction for a flex container. */
14
+ export type LayoutMode = "row" | "column";
15
+ /** Simplified alignment shorthand for a flex axis. */
16
+ export type AlignItems = "start" | "center" | "end" | "space-between";
17
+ /**
18
+ * Auto-layout properties for a FRAME/COMPONENT/INSTANCE that has `layoutMode` set. Optional and
19
+ * additive: absent when the node has no auto-layout. NEVER folded into the snapshot integrity hash
20
+ * (#753/#735) — it is codegen metadata, not structural drift identity.
21
+ */
22
+ export interface IrLayout {
23
+ /** Flex direction derived from Figma `layoutMode` (HORIZONTAL → row, VERTICAL → column). */
24
+ readonly mode: LayoutMode;
25
+ /** Gap between children, from `itemSpacing`. */
26
+ readonly itemSpacing?: number;
27
+ /** Padding: [top, right, bottom, left]. Absent when all four are zero/absent. */
28
+ readonly padding?: readonly [number, number, number, number];
29
+ /** Primary-axis alignment (Figma `primaryAxisAlignItems`). */
30
+ readonly primaryAlign?: AlignItems;
31
+ /** Cross-axis alignment (Figma `counterAxisAlignItems`). */
32
+ readonly counterAlign?: AlignItems;
33
+ }
34
+ /** Whether a dimension is fixed, fills available space, or hugs content. */
35
+ export type LayoutSizing = "fixed" | "hug" | "fill";
36
+ /**
37
+ * Per-axis sizing mode (Figma `layoutSizingHorizontal` / `layoutSizingVertical`). Optional and
38
+ * additive; absent when the node has no auto-layout parent or when sizing is not explicitly set.
39
+ * Hash-neutral like {@link IrLayout}.
40
+ */
41
+ export interface IrSizing {
42
+ readonly horizontal?: LayoutSizing;
43
+ readonly vertical?: LayoutSizing;
44
+ }
45
+ /**
46
+ * Per-TEXT-node typography properties, only present on TEXT nodes. Matches what `tokens.ts`
47
+ * already extracts globally (fontFamily, fontSize, fontWeight, lineHeight). Optional and additive;
48
+ * absent for non-TEXT nodes or when the `style` block is missing. Hash-neutral like
49
+ * {@link IrLayout}.
50
+ */
51
+ export interface IrTypography {
52
+ readonly fontFamily: string;
53
+ readonly fontSize: number;
54
+ readonly fontWeight: number;
55
+ readonly lineHeight?: number;
56
+ }
57
+ /** A kept node in a screen's normalized structural tree. */
58
+ export interface IrNode {
59
+ readonly id: string;
60
+ readonly name: string;
61
+ readonly type: string;
62
+ readonly interactionHint: InteractionHint;
63
+ readonly text?: string;
64
+ readonly boundingBox?: BoundingBox;
65
+ /**
66
+ * The node's solid text-fill colour as normalized `#RRGGBB[AA]`, for a TEXT node (Issue #812).
67
+ * Optional and additive: absent when the node carries no solid text fill. NEVER folded into the
68
+ * snapshot integrity hash (#753/#735) — it is a11y-derivation metadata, not structural identity.
69
+ */
70
+ readonly textColor?: string;
71
+ /**
72
+ * The node's solid background-fill colour as normalized `#RRGGBB[AA]` (Issue #812). Used as the
73
+ * nearest-ancestor background when computing deterministic text-vs-background contrast. Optional,
74
+ * additive, and hash-neutral like {@link IrNode.textColor}.
75
+ */
76
+ readonly backgroundColor?: string;
77
+ /**
78
+ * Auto-layout properties (direction, gap, padding, alignment). Optional and additive: absent when
79
+ * the node has no auto-layout. Hash-neutral — codegen metadata, not structural drift identity.
80
+ */
81
+ readonly layout?: IrLayout;
82
+ /**
83
+ * Per-axis sizing mode. Optional and additive: absent when no sizing context exists.
84
+ * Hash-neutral like {@link IrNode.layout}.
85
+ */
86
+ readonly sizing?: IrSizing;
87
+ /**
88
+ * Corner radius in pixels. Optional and additive: absent when zero or absent from source.
89
+ * Hash-neutral like {@link IrNode.layout}.
90
+ */
91
+ readonly cornerRadius?: number;
92
+ /**
93
+ * Per-TEXT typography (fontFamily, fontSize, fontWeight, optional lineHeight). Optional and
94
+ * additive: absent for non-TEXT nodes or when the style block is missing. Hash-neutral like
95
+ * {@link IrNode.layout}.
96
+ */
97
+ readonly typography?: IrTypography;
98
+ readonly imageFills: readonly ImageFillRef[];
99
+ readonly children: readonly IrNode[];
100
+ }
101
+ export interface ScreenIr {
102
+ readonly id: string;
103
+ readonly name: string;
104
+ readonly root: IrNode;
105
+ }
106
+ export interface ColorToken {
107
+ readonly id: string;
108
+ readonly kind: "color";
109
+ readonly value: string;
110
+ }
111
+ export interface TypographyToken {
112
+ readonly id: string;
113
+ readonly kind: "typography";
114
+ readonly fontFamily: string;
115
+ readonly fontSize: number;
116
+ readonly fontWeight: number;
117
+ readonly lineHeight: number;
118
+ }
119
+ export interface SpacingToken {
120
+ readonly id: string;
121
+ readonly kind: "spacing";
122
+ readonly value: number;
123
+ }
124
+ export interface RadiusToken {
125
+ readonly id: string;
126
+ readonly kind: "radius";
127
+ readonly value: number;
128
+ }
129
+ /** Deduped, stable-ordered design tokens — emitted as part of and alongside the IR (for #755). */
130
+ export interface DesignTokens {
131
+ readonly colors: readonly ColorToken[];
132
+ readonly typography: readonly TypographyToken[];
133
+ readonly spacing: readonly SpacingToken[];
134
+ readonly radius: readonly RadiusToken[];
135
+ }
136
+ /** A raw inter-screen transition. The flow graph is derived downstream (#811); we carry the link. */
137
+ export interface InterScreenLink {
138
+ readonly sourceNodeId: string;
139
+ readonly trigger: string;
140
+ readonly targetNodeId: string;
141
+ }
142
+ /** How much of the raw subtree was dropped. `removedRatio` is rounded deterministically. */
143
+ export interface ReductionReport {
144
+ readonly inputNodeCount: number;
145
+ readonly keptNodeCount: number;
146
+ readonly removedNodeCount: number;
147
+ readonly removedRatio: number;
148
+ }
149
+ /** The full per-board result of cleaning a scoped Figma node subtree. */
150
+ export interface ScreenIrResult {
151
+ readonly screens: readonly ScreenIr[];
152
+ readonly tokens: DesignTokens;
153
+ readonly links: readonly InterScreenLink[];
154
+ readonly reduction: ReductionReport;
155
+ }
156
+ //# sourceMappingURL=irTypes.d.ts.map