@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,31 @@
1
+ import { type FigmaSourceNode } from "./sourceNode.js";
2
+ /**
3
+ * Whether a Figma paint contributes a visible colour. Figma omits `visible` when a paint is shown, so
4
+ * only an explicit `visible:false` hides it (mirrors {@link isHidden} for nodes). A paint-level
5
+ * `opacity <= 0` or a SOLID `color.a <= 0` is fully transparent and likewise contributes no visible colour.
6
+ * Used so neither the reported text/background colour (#812) nor an extracted colour token (#752) is
7
+ * ever taken from a paint that does not render.
8
+ */
9
+ export declare const isVisiblePaint: (paint: Record<string, unknown>) => boolean;
10
+ /** Project a Figma paint's `color` ({r,g,b,a} in 0..1) to normalized `#RRGGBB[AA]` (alpha < 1 only). */
11
+ export declare const paintColorToHex: (paint: Record<string, unknown>) => string | undefined;
12
+ /**
13
+ * The first VISIBLE SOLID paint's normalized hex from a node's paint array (`fills`/`strokes`), if
14
+ * any. Hidden (`visible:false`) and fully-transparent paints are skipped so the reported colour is
15
+ * one that actually renders; an empty/absent paint array yields `undefined` (resolved downstream as a
16
+ * coverage notice, never a fabricated colour).
17
+ */
18
+ export declare const firstSolidPaintHex: (node: FigmaSourceNode, key: string) => string | undefined;
19
+ /** Parsed sRGB channels in the 0..255 range. */
20
+ export interface Rgb {
21
+ readonly r: number;
22
+ readonly g: number;
23
+ readonly b: number;
24
+ }
25
+ export interface Rgba extends Rgb {
26
+ /** Alpha in the 0..1 range. */
27
+ readonly a: number;
28
+ }
29
+ export declare const parseHexRgba: (hex: string) => Rgba | undefined;
30
+ export declare const parseHexRgb: (hex: string) => Rgb | undefined;
31
+ //# sourceMappingURL=color.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../../src/domain/figma/color.ts"],"names":[],"mappings":"AAkBA,OAAO,EAAkC,KAAK,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAOvF;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,OAU/D,CAAC;AAEF,wGAAwG;AACxG,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,MAAM,GAAG,SAYzE,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,eAAe,EAAE,KAAK,MAAM,KAAG,MAAM,GAAG,SAWhF,CAAC;AAEF,gDAAgD;AAChD,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,IAAK,SAAQ,GAAG;IAC/B,+BAA+B;IAC/B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,eAAO,MAAM,YAAY,GAAI,KAAK,MAAM,KAAG,IAAI,GAAG,SAOjD,CAAC;AAGF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,GAAG,GAAG,SAK/C,CAAC"}
@@ -0,0 +1,99 @@
1
+ // Deterministic colour helpers shared across token extraction (#752) and a11y contrast (#812).
2
+ //
3
+ // One canonical projection of a Figma SOLID paint to a normalized `#RRGGBB[AA]` hex string, plus the
4
+ // inverse parse used by the WCAG contrast math. Pure and content-free: the same paint always yields
5
+ // the same hex, and the same hex always parses to the same channels. No IO, no model.
6
+ //
7
+ // Empirical note (#838, verified content-free against real enterprise boards): a real Figma TEXT
8
+ // node carries its colour as an INLINE SOLID paint in its own `fills` array for the overwhelming
9
+ // majority of nodes (82–99% across the sampled boards). The earlier hypothesis that colour lives in
10
+ // a `fillStyleId` / `styles.fill` style reference did NOT hold — fill-style-referenced text still
11
+ // inlines the resolved SOLID paint, so `fills` is the single source of truth and no style-table
12
+ // resolution is needed. The residual nodes carry an EMPTY `fills:[]` (a typography-only `styles.text`
13
+ // reference, no own paint); those are genuinely unresolvable and correctly degrade to an a11y
14
+ // coverage notice rather than a fabricated colour. The historical "0 textColor on real boards"
15
+ // symptom was caused by the depth-fetch gap (#837), not by where the colour lives: the text-bearing
16
+ // nodes simply never survived into the IR. The one real robustness gap here is paint VISIBILITY —
17
+ // an explicitly hidden or fully-transparent paint must never become the reported colour.
18
+ import { asNode, readNumber, readString } from "./sourceNode.js";
19
+ const channel = (value) => Math.round(Math.min(1, Math.max(0, value)) * 255)
20
+ .toString(16)
21
+ .padStart(2, "0");
22
+ /**
23
+ * Whether a Figma paint contributes a visible colour. Figma omits `visible` when a paint is shown, so
24
+ * only an explicit `visible:false` hides it (mirrors {@link isHidden} for nodes). A paint-level
25
+ * `opacity <= 0` or a SOLID `color.a <= 0` is fully transparent and likewise contributes no visible colour.
26
+ * Used so neither the reported text/background colour (#812) nor an extracted colour token (#752) is
27
+ * ever taken from a paint that does not render.
28
+ */
29
+ export const isVisiblePaint = (paint) => {
30
+ if (paint.visible === false)
31
+ return false;
32
+ const opacity = readNumber(paint.opacity);
33
+ if (opacity !== undefined && opacity <= 0)
34
+ return false;
35
+ const color = asNode(paint.color);
36
+ if (color !== undefined) {
37
+ const alpha = readNumber(color.a);
38
+ if (alpha !== undefined && alpha <= 0)
39
+ return false;
40
+ }
41
+ return true;
42
+ };
43
+ /** Project a Figma paint's `color` ({r,g,b,a} in 0..1) to normalized `#RRGGBB[AA]` (alpha < 1 only). */
44
+ export const paintColorToHex = (paint) => {
45
+ const color = asNode(paint.color);
46
+ if (color === undefined)
47
+ return undefined;
48
+ const r = readNumber(color.r);
49
+ const g = readNumber(color.g);
50
+ const b = readNumber(color.b);
51
+ if (r === undefined || g === undefined || b === undefined)
52
+ return undefined;
53
+ const colorAlpha = readNumber(color.a) ?? 1;
54
+ const paintOpacity = readNumber(paint.opacity) ?? 1;
55
+ const a = Math.min(1, Math.max(0, colorAlpha)) * Math.min(1, Math.max(0, paintOpacity));
56
+ const base = `#${channel(r)}${channel(g)}${channel(b)}`;
57
+ return a < 1 ? `${base}${channel(a)}` : base;
58
+ };
59
+ /**
60
+ * The first VISIBLE SOLID paint's normalized hex from a node's paint array (`fills`/`strokes`), if
61
+ * any. Hidden (`visible:false`) and fully-transparent paints are skipped so the reported colour is
62
+ * one that actually renders; an empty/absent paint array yields `undefined` (resolved downstream as a
63
+ * coverage notice, never a fabricated colour).
64
+ */
65
+ export const firstSolidPaintHex = (node, key) => {
66
+ const paints = node[key];
67
+ if (!Array.isArray(paints))
68
+ return undefined;
69
+ for (const paint of paints) {
70
+ const record = asNode(paint);
71
+ if (record === undefined || readString(record.type) !== "SOLID")
72
+ continue;
73
+ if (!isVisiblePaint(record))
74
+ continue;
75
+ const hex = paintColorToHex(record);
76
+ if (hex !== undefined)
77
+ return hex;
78
+ }
79
+ return undefined;
80
+ };
81
+ // Parse `#RRGGBB[AA]` into rendered colour channels. Returns undefined for a malformed value so the
82
+ // caller emits a coverage notice rather than crashing.
83
+ export const parseHexRgba = (hex) => {
84
+ if (!/^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/u.test(hex))
85
+ return undefined;
86
+ const r = Number.parseInt(hex.slice(1, 3), 16);
87
+ const g = Number.parseInt(hex.slice(3, 5), 16);
88
+ const b = Number.parseInt(hex.slice(5, 7), 16);
89
+ const a = hex.length === 9 ? Number.parseInt(hex.slice(7, 9), 16) / 255 : 1;
90
+ return { r, g, b, a };
91
+ };
92
+ // Legacy RGB parser for callers that intentionally want the opaque leading colour.
93
+ export const parseHexRgb = (hex) => {
94
+ const parsed = parseHexRgba(hex);
95
+ if (parsed === undefined)
96
+ return undefined;
97
+ const { r, g, b } = parsed;
98
+ return { r, g, b };
99
+ };
@@ -0,0 +1,56 @@
1
+ import type { DesignTokens, IrLayout, IrSizing, IrTypography, InteractionHint, ScreenIr } from "./irTypes.js";
2
+ import type { RoutingHint } from "./navGraph.js";
3
+ /** The target-neutral role of an element — mirrors the IR `interactionHint`; no framework tag. */
4
+ export type EmissionRole = InteractionHint;
5
+ /** A target-neutral element in a screen's emission tree. `displayName` is a structural default. */
6
+ export interface EmissionElement {
7
+ readonly id: string;
8
+ readonly role: EmissionRole;
9
+ /** Semantic display name — a structural default here; the naming port may override it (never structure). */
10
+ readonly displayName: string;
11
+ readonly text?: string;
12
+ /** Auto-layout properties threaded from the IR node; absent when the node has no auto-layout. */
13
+ readonly layout?: IrLayout;
14
+ /** Per-axis sizing mode threaded from the IR node; absent when not set. */
15
+ readonly sizing?: IrSizing;
16
+ /**
17
+ * Corner radius in pixels threaded verbatim from the IR node; absent when the IR omits it. The IR
18
+ * cleaner (normalize) already drops a zero radius upstream, so this is never 0 in practice.
19
+ */
20
+ readonly cornerRadius?: number;
21
+ /** Per-TEXT typography threaded from the IR node; absent for non-TEXT nodes. */
22
+ readonly typography?: IrTypography;
23
+ readonly children: readonly EmissionElement[];
24
+ }
25
+ /** A resolved outgoing navigation target for a screen, derived from the routing hints (#811). */
26
+ export interface EmissionNavTarget {
27
+ readonly trigger: string;
28
+ readonly toScreenId: string;
29
+ readonly toScreenName: string;
30
+ }
31
+ /** One screen reduced to its emission tree plus its outgoing navigation targets. */
32
+ export interface ScreenEmission {
33
+ readonly screenId: string;
34
+ readonly screenName: string;
35
+ readonly root: EmissionElement;
36
+ readonly navTargets: readonly EmissionNavTarget[];
37
+ }
38
+ /** The full target-neutral emission plan an adapter renders to a concrete code target. */
39
+ export interface CodeEmissionPlan {
40
+ readonly screens: readonly ScreenEmission[];
41
+ readonly tokens: DesignTokens;
42
+ }
43
+ /** The inputs to a code-emission pass: the screens, their tokens, and their routing hints. */
44
+ export interface EmissionInput {
45
+ readonly screens: readonly ScreenIr[];
46
+ readonly tokens: DesignTokens;
47
+ readonly hints: readonly RoutingHint[];
48
+ }
49
+ /**
50
+ * Build the target-neutral emission plan from the Screen-IR, design tokens, and routing hints. Pure,
51
+ * deterministic, model-free: preserves the IR's child order, carries the tokens through verbatim, and
52
+ * attaches each screen's resolved outgoing navigation targets. The same input yields a byte-identical
53
+ * plan, so the emitted code is reproducible.
54
+ */
55
+ export declare function buildEmissionPlan(input: EmissionInput): CodeEmissionPlan;
56
+ //# sourceMappingURL=emissionPlan.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emissionPlan.d.ts","sourceRoot":"","sources":["../../../src/domain/figma/emissionPlan.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EACV,YAAY,EACZ,QAAQ,EAER,QAAQ,EACR,YAAY,EACZ,eAAe,EACf,QAAQ,EACT,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,kGAAkG;AAClG,MAAM,MAAM,YAAY,GAAG,eAAe,CAAC;AAE3C,mGAAmG;AACnG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,4GAA4G;IAC5G,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,iGAAiG;IACjG,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;IAC3B,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,gFAAgF;IAChF,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CAC/C;AAED,iGAAiG;AACjG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACnD;AAED,0FAA0F;AAC1F,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IAC5C,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;CAC/B;AAED,8FAA8F;AAC9F,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAC;IACtC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;CACxC;AAyDD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,CAYxE"}
@@ -0,0 +1,87 @@
1
+ // Target-neutral design-to-code emission plan (Epic #750, Issue #755).
2
+ //
3
+ // Pure domain: a Screen-IR (#752) + deduped design tokens (#752) + framework-agnostic routing hints
4
+ // (#811) are reduced — with NO model and NO IO — to a single, target-NEUTRAL emission plan: a
5
+ // per-screen element tree (role + structural default name + text + child order preserved), each
6
+ // screen's outgoing navigation targets, and the design-token set carried through verbatim for the
7
+ // adapter to theme. A concrete `CodeTargetAdapter` renders this plan; the plan itself encodes no
8
+ // framework, tag, or router vocabulary, so future adapters (MUI, a component library) plug in WITHOUT
9
+ // changing this module.
10
+ //
11
+ // Generic by construction: the only structural signal read is `interactionHint` (a #752 role hint)
12
+ // and the node's own `name`/`text`. No screen name, mask style, or copy is special-cased.
13
+ // Deterministic: the plan carries no timestamp and preserves the IR's stable order, so the same
14
+ // input yields a byte-identical plan (and thus byte-identical emitted code).
15
+ //
16
+ // Layout / sizing / cornerRadius / typography fields from the IR are threaded through unchanged so
17
+ // that concrete adapters (htmlCssAdapter) can emit visual-fidelity CSS without re-reading the IR.
18
+ // They are carried verbatim and never reinterpreted here.
19
+ // A structural default display name: the node's own name when non-empty, else a role-based fallback.
20
+ // Never empty, never model-invented — so emission always has a usable name with no model present.
21
+ function structuralDisplayName(node) {
22
+ const trimmed = node.name.trim();
23
+ if (trimmed.length > 0)
24
+ return trimmed;
25
+ const text = node.text?.trim();
26
+ if (text !== undefined && text.length > 0)
27
+ return text;
28
+ return node.interactionHint;
29
+ }
30
+ // Screen names are evidence text too; a tampered/partial record can carry blank names. Keep the
31
+ // first-slice artifact reviewable and navigable without model help by falling back to the screen id.
32
+ function structuralScreenName(screen) {
33
+ const trimmed = screen.name.trim();
34
+ if (trimmed.length > 0)
35
+ return trimmed;
36
+ const id = screen.id.trim();
37
+ return id.length > 0 ? id : "Screen";
38
+ }
39
+ function toElement(node) {
40
+ const text = node.text?.trim();
41
+ return {
42
+ id: node.id,
43
+ role: node.interactionHint,
44
+ displayName: structuralDisplayName(node),
45
+ ...(text !== undefined && text.length > 0 ? { text } : {}),
46
+ ...(node.layout !== undefined ? { layout: node.layout } : {}),
47
+ ...(node.sizing !== undefined ? { sizing: node.sizing } : {}),
48
+ ...(node.cornerRadius !== undefined ? { cornerRadius: node.cornerRadius } : {}),
49
+ ...(node.typography !== undefined ? { typography: node.typography } : {}),
50
+ children: node.children.map(toElement),
51
+ };
52
+ }
53
+ // Resolve a screen's outgoing nav targets from the routing hints, mapping each hint's `toScreenId`
54
+ // to a screen name. A hint targeting an unknown screen falls back to the id as its display name.
55
+ function navTargetsFor(screenId, hints, nameByScreenId) {
56
+ const hint = hints.find((h) => h.screenId === screenId);
57
+ if (hint === undefined)
58
+ return [];
59
+ return hint.transitions.map((transition) => {
60
+ const fallbackName = transition.toScreenId.trim();
61
+ return {
62
+ trigger: transition.trigger,
63
+ toScreenId: transition.toScreenId,
64
+ toScreenName: nameByScreenId.get(transition.toScreenId) ??
65
+ (fallbackName.length > 0 ? fallbackName : "Screen"),
66
+ };
67
+ });
68
+ }
69
+ /**
70
+ * Build the target-neutral emission plan from the Screen-IR, design tokens, and routing hints. Pure,
71
+ * deterministic, model-free: preserves the IR's child order, carries the tokens through verbatim, and
72
+ * attaches each screen's resolved outgoing navigation targets. The same input yields a byte-identical
73
+ * plan, so the emitted code is reproducible.
74
+ */
75
+ export function buildEmissionPlan(input) {
76
+ const nameByScreenId = new Map(input.screens.map((s) => [s.id, structuralScreenName(s)]));
77
+ const screens = input.screens.map((screen) => {
78
+ const screenName = structuralScreenName(screen);
79
+ return {
80
+ screenId: screen.id,
81
+ screenName,
82
+ root: toElement(screen.root),
83
+ navTargets: navTargetsFor(screen.id, input.hints, nameByScreenId),
84
+ };
85
+ });
86
+ return { screens, tokens: input.tokens };
87
+ }
@@ -0,0 +1,13 @@
1
+ import type { CodeTargetAdapter } from "./codeTargetAdapter.js";
2
+ /**
3
+ * The framework-agnostic HTML/CSS adapter — the only adapter shipped in the first slice. Renders the
4
+ * target-neutral plan to semantic HTML per screen, a `tokens.css` custom-property table, and an
5
+ * `index.html`. Pure and deterministic: a given plan yields a byte-identical artifact.
6
+ *
7
+ * Layout fidelity: nodes with auto-layout emit display:flex + direction + gap + padding + radius in a
8
+ * per-screen `<style>` block; TEXT nodes with matching typography tokens emit var(--font-N); fill-sized
9
+ * nodes emit flex:1 / width:100%. Absolute positioning, constraints, effects, and image content are
10
+ * not reproduced.
11
+ */
12
+ export declare const htmlCssAdapter: CodeTargetAdapter;
13
+ //# sourceMappingURL=htmlCssAdapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"htmlCssAdapter.d.ts","sourceRoot":"","sources":["../../../src/domain/figma/htmlCssAdapter.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAA0B,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAqfxF;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,EAAE,iBAG5B,CAAC"}