@radicool/throughline 0.24.0 → 0.26.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 (62) hide show
  1. package/README.md +8 -4
  2. package/adapters/codex/AGENTS.md +2 -0
  3. package/adapters/codex/prompts/check-figma.md +41 -0
  4. package/adapters/codex/prompts/component-builder.md +5 -3
  5. package/adapters/codex/prompts/compose-figma.md +59 -0
  6. package/adapters/codex/prompts/document-component.md +3 -2
  7. package/adapters/cursor/.cursor/commands/check-figma.md +41 -0
  8. package/adapters/cursor/.cursor/commands/compose-figma.md +59 -0
  9. package/adapters/cursor/.cursor/commands/document-component.md +3 -2
  10. package/adapters/cursor/.cursor/rules/component-builder.mdc +5 -3
  11. package/adapters/generic/AGENTS.md +2 -0
  12. package/adapters/generic/commands/check-figma.md +41 -0
  13. package/adapters/generic/commands/compose-figma.md +59 -0
  14. package/adapters/generic/commands/document-component.md +3 -2
  15. package/adapters/generic/skills/component-builder/SKILL.md +5 -3
  16. package/package.json +1 -1
  17. package/references/doc-card-builder.md +62 -15
  18. package/references/figma-adherence.md +463 -0
  19. package/references/figma-checks.md +106 -43
  20. package/references/figma-compose.md +896 -0
  21. package/scripts/README.md +8 -2
  22. package/scripts/build-doc-card-builder.mjs +16 -0
  23. package/scripts/build-figma-checks.mjs +208 -23
  24. package/scripts/doc-card-payload.mjs +115 -0
  25. package/scripts/install.mjs +7 -1
  26. package/scripts/lib/doc-card-plan.mjs +37 -0
  27. package/scripts/lib/doc-card-render.figma.js +9 -15
  28. package/scripts/lib/figma-adherence-read.figma.js +228 -0
  29. package/scripts/lib/figma-adherence.mjs +146 -0
  30. package/scripts/lib/figma-checks-read.figma.js +43 -43
  31. package/scripts/lib/figma-checks.mjs +16 -5
  32. package/scripts/lib/figma-compose.figma.js +326 -0
  33. package/scripts/lib/figma-compose.mjs +317 -0
  34. package/scripts/lib/figma-variables-read.figma.js +39 -0
  35. package/scripts/lib/fixtures/figma-adherence/clean.json +90 -0
  36. package/scripts/lib/fixtures/figma-adherence/detached-library.json +116 -0
  37. package/scripts/lib/fixtures/figma-adherence/ds01-gaps.json +84 -0
  38. package/scripts/lib/fixtures/figma-adherence/inert-all.json +45 -0
  39. package/scripts/lib/fixtures/figma-adherence/mixed-text.json +61 -0
  40. package/scripts/lib/fixtures/figma-adherence/nothing-scanned.json +49 -0
  41. package/scripts/lib/fixtures/figma-adherence/off-scale-gap.json +65 -0
  42. package/scripts/lib/fixtures/figma-adherence/override-raw.json +72 -0
  43. package/scripts/lib/fixtures/figma-adherence/padding-raw.json +74 -0
  44. package/scripts/lib/fixtures/figma-adherence/raw-colour-token.json +70 -0
  45. package/scripts/lib/fixtures/figma-adherence/root-radius.json +88 -0
  46. package/scripts/lib/fixtures/figma-adherence/skip-colour.json +86 -0
  47. package/scripts/lib/fixtures/figma-adherence/zygarden-override-bound.json +85 -0
  48. package/scripts/lib/fixtures/figma-adherence/zygarden-text-raw.json +208 -0
  49. package/scripts/lib/fixtures/figma-compose/casing-and-suffix.json +8 -0
  50. package/scripts/lib/fixtures/figma-compose/fill-in-unlaid-slot.json +7 -0
  51. package/scripts/lib/fixtures/figma-compose/hug-on-fixed-component.json +34 -0
  52. package/scripts/lib/fixtures/figma-compose/inventory.json +295 -0
  53. package/scripts/lib/fixtures/figma-compose/many-errors.json +8 -0
  54. package/scripts/lib/fixtures/figma-compose/missing-component.json +7 -0
  55. package/scripts/lib/fixtures/figma-compose/name-taken.json +6 -0
  56. package/scripts/lib/fixtures/figma-compose/padding-sides.json +23 -0
  57. package/scripts/lib/fixtures/figma-compose/radius-kind.json +27 -0
  58. package/scripts/lib/fixtures/figma-compose/raw-number.json +5 -0
  59. package/scripts/lib/fixtures/figma-compose/sign-in-ok.json +16 -0
  60. package/scripts/lib/fixtures/figma-compose/slot-ok.json +9 -0
  61. package/scripts/lib/fixtures/figma-compose/unknown-prop-and-option.json +7 -0
  62. package/scripts/lib/fixtures/figma-compose/wrong-kind.json +5 -0
@@ -0,0 +1,39 @@
1
+ // Figma plugin-API helpers for reading colour variables, shared by the Figma
2
+ // checks reader and the screen reader. This file is NOT a Node module:
3
+ // build-figma-checks.mjs inlines it into both generated scripts
4
+ // (references/figma-checks.md, references/figma-adherence.md). It depends on
5
+ // nothing else, so it carries its own mode-name fold. Async getters only
6
+ // (references/figma-scripting.md). Untested in Node, like the readers.
7
+
8
+ const foldModeName = (s) => String(s).toLowerCase().replace(/[^a-z0-9]/g, '');
9
+
10
+ function colorHex(value) {
11
+ if (!value || typeof value !== 'object' || !('r' in value)) return null;
12
+ const byte = (x) => Math.round(x * 255).toString(16).padStart(2, '0');
13
+ const alpha = value.a === undefined ? 1 : value.a;
14
+ return `#${byte(value.r)}${byte(value.g)}${byte(value.b)}${alpha < 1 ? byte(alpha) : ''}`;
15
+ }
16
+
17
+ // The value of `variable` in the mode named `modeName` (after the fold), else
18
+ // its collection's default mode, following aliases up to 10 hops.
19
+ async function resolveColor(variable, modeName, hops) {
20
+ if (!variable || hops > 10) return null;
21
+ const collection = await figma.variables.getVariableCollectionByIdAsync(variable.variableCollectionId);
22
+ if (!collection) return null;
23
+ const mode = collection.modes.find((m) => foldModeName(m.name) === foldModeName(modeName));
24
+ const value = variable.valuesByMode[mode ? mode.modeId : collection.defaultModeId];
25
+ if (value && value.type === 'VARIABLE_ALIAS') {
26
+ return resolveColor(await figma.variables.getVariableByIdAsync(value.id), modeName, hops + 1);
27
+ }
28
+ return colorHex(value);
29
+ }
30
+
31
+ // { modes: { [modeName]: hex | null }, defaultMode } over the variable's own collection.
32
+ async function variableColors(variable) {
33
+ const collection = await figma.variables.getVariableCollectionByIdAsync(variable.variableCollectionId);
34
+ if (!collection) return { modes: {}, defaultMode: undefined };
35
+ const modes = {};
36
+ for (const mode of collection.modes) modes[mode.name] = await resolveColor(variable, mode.name, 0);
37
+ const fallback = collection.modes.find((m) => m.modeId === collection.defaultModeId);
38
+ return { modes, defaultMode: fallback ? fallback.name : undefined };
39
+ }
@@ -0,0 +1,90 @@
1
+ {
2
+ "covers": "everything bound passes with no advisories",
3
+ "snapshot": {
4
+ "fileKey": "FILE",
5
+ "screens": [
6
+ {
7
+ "id": "0:1",
8
+ "name": "Screen"
9
+ }
10
+ ],
11
+ "nodes": [
12
+ {
13
+ "id": "1:1",
14
+ "path": "Templates / Screen / Node",
15
+ "isRoot": false,
16
+ "inInstance": false,
17
+ "overridden": [],
18
+ "paints": [
19
+ {
20
+ "kind": "fill",
21
+ "hex": "#ffffff",
22
+ "bound": true,
23
+ "styled": false
24
+ },
25
+ {
26
+ "kind": "stroke",
27
+ "hex": "#111827",
28
+ "bound": false,
29
+ "styled": true
30
+ }
31
+ ],
32
+ "mixedPaints": false,
33
+ "gap": {
34
+ "value": 12,
35
+ "bound": true,
36
+ "renders": true
37
+ },
38
+ "padding": [
39
+ {
40
+ "side": "top",
41
+ "value": 16,
42
+ "bound": true
43
+ }
44
+ ],
45
+ "radius": [
46
+ {
47
+ "corner": "all",
48
+ "value": 8,
49
+ "bound": true
50
+ }
51
+ ],
52
+ "detached": null
53
+ }
54
+ ],
55
+ "tokens": {
56
+ "colours": {
57
+ "#ffffff": [
58
+ "color/bg/canvas"
59
+ ],
60
+ "#111827": [
61
+ "color/text/primary"
62
+ ]
63
+ },
64
+ "gap": {
65
+ "8": [
66
+ "spacing/2"
67
+ ],
68
+ "12": [
69
+ "spacing/3"
70
+ ],
71
+ "16": [
72
+ "spacing/4"
73
+ ]
74
+ },
75
+ "radius": {
76
+ "8": [
77
+ "radius/md"
78
+ ],
79
+ "32": [
80
+ "radius/xl"
81
+ ]
82
+ }
83
+ }
84
+ },
85
+ "expected": {
86
+ "pass": true,
87
+ "failures": [],
88
+ "advisories": 0
89
+ }
90
+ }
@@ -0,0 +1,116 @@
1
+ {
2
+ "covers": "01. Design System 2026-10-13: a frame detached from a library component, and one from a local component on another page; raw values inside a detached frame are not reported again",
3
+ "snapshot": {
4
+ "fileKey": "FILE",
5
+ "screens": [
6
+ {
7
+ "id": "0:1",
8
+ "name": "Screen"
9
+ }
10
+ ],
11
+ "nodes": [
12
+ {
13
+ "id": "4:1",
14
+ "path": "Archtypes / hud-Jordan",
15
+ "isRoot": false,
16
+ "inInstance": false,
17
+ "overridden": [],
18
+ "paints": [],
19
+ "mixedPaints": false,
20
+ "gap": null,
21
+ "padding": [],
22
+ "radius": [],
23
+ "detached": {
24
+ "kind": "library",
25
+ "name": null,
26
+ "id": null,
27
+ "key": "4412e315f15a0af7a26382d2b05781f7974a30d9"
28
+ },
29
+ "underDetached": true
30
+ },
31
+ {
32
+ "id": "4:2",
33
+ "path": "Archtypes / hud-maria",
34
+ "isRoot": false,
35
+ "inInstance": false,
36
+ "overridden": [],
37
+ "paints": [],
38
+ "mixedPaints": false,
39
+ "gap": null,
40
+ "padding": [],
41
+ "radius": [],
42
+ "detached": {
43
+ "kind": "local",
44
+ "name": null,
45
+ "id": "9:9",
46
+ "key": null
47
+ },
48
+ "underDetached": true
49
+ },
50
+ {
51
+ "id": "4:3",
52
+ "path": "Archtypes / hud-Jordan / Card",
53
+ "isRoot": false,
54
+ "inInstance": false,
55
+ "overridden": [],
56
+ "paints": [
57
+ {
58
+ "kind": "fill",
59
+ "hex": "#ffffff",
60
+ "bound": false,
61
+ "styled": false
62
+ }
63
+ ],
64
+ "mixedPaints": false,
65
+ "gap": null,
66
+ "padding": [
67
+ {
68
+ "side": "top",
69
+ "value": 16,
70
+ "bound": false
71
+ }
72
+ ],
73
+ "radius": [],
74
+ "detached": null,
75
+ "underDetached": true
76
+ }
77
+ ],
78
+ "tokens": {
79
+ "colours": {
80
+ "#ffffff": [
81
+ "color/bg/canvas"
82
+ ],
83
+ "#111827": [
84
+ "color/text/primary"
85
+ ]
86
+ },
87
+ "gap": {
88
+ "8": [
89
+ "spacing/2"
90
+ ],
91
+ "12": [
92
+ "spacing/3"
93
+ ],
94
+ "16": [
95
+ "spacing/4"
96
+ ]
97
+ },
98
+ "radius": {
99
+ "8": [
100
+ "radius/md"
101
+ ],
102
+ "32": [
103
+ "radius/xl"
104
+ ]
105
+ }
106
+ }
107
+ },
108
+ "expected": {
109
+ "pass": false,
110
+ "failures": [
111
+ "detached-component",
112
+ "detached-component"
113
+ ],
114
+ "advisories": 0
115
+ }
116
+ }
@@ -0,0 +1,84 @@
1
+ {
2
+ "covers": "01. Design System 2026-10-13: a rendering gap of 12 fails naming the token; a one-child gap of 8 is not checked",
3
+ "snapshot": {
4
+ "fileKey": "FILE",
5
+ "screens": [
6
+ {
7
+ "id": "0:1",
8
+ "name": "Screen"
9
+ }
10
+ ],
11
+ "nodes": [
12
+ {
13
+ "id": "2:1",
14
+ "path": "Templates / hero / Frame 6",
15
+ "isRoot": false,
16
+ "inInstance": false,
17
+ "overridden": [],
18
+ "paints": [],
19
+ "mixedPaints": false,
20
+ "gap": {
21
+ "value": 12,
22
+ "bound": false,
23
+ "renders": true
24
+ },
25
+ "padding": [],
26
+ "radius": [],
27
+ "detached": null
28
+ },
29
+ {
30
+ "id": "2:2",
31
+ "path": "Templates / hero / container-left",
32
+ "isRoot": false,
33
+ "inInstance": false,
34
+ "overridden": [],
35
+ "paints": [],
36
+ "mixedPaints": false,
37
+ "gap": {
38
+ "value": 8,
39
+ "bound": false,
40
+ "renders": false
41
+ },
42
+ "padding": [],
43
+ "radius": [],
44
+ "detached": null
45
+ }
46
+ ],
47
+ "tokens": {
48
+ "colours": {
49
+ "#ffffff": [
50
+ "color/bg/canvas"
51
+ ],
52
+ "#111827": [
53
+ "color/text/primary"
54
+ ]
55
+ },
56
+ "gap": {
57
+ "8": [
58
+ "spacing/2"
59
+ ],
60
+ "12": [
61
+ "spacing/3"
62
+ ],
63
+ "16": [
64
+ "spacing/4"
65
+ ]
66
+ },
67
+ "radius": {
68
+ "8": [
69
+ "radius/md"
70
+ ],
71
+ "32": [
72
+ "radius/xl"
73
+ ]
74
+ }
75
+ }
76
+ },
77
+ "expected": {
78
+ "pass": false,
79
+ "failures": [
80
+ "raw-dimension"
81
+ ],
82
+ "advisories": 0
83
+ }
84
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "covers": "a file with no colour or dimension variables fails both inert checks",
3
+ "tokens": {
4
+ "colours": {},
5
+ "gap": {},
6
+ "radius": {}
7
+ },
8
+ "snapshot": {
9
+ "fileKey": "FILE",
10
+ "screens": [
11
+ {
12
+ "id": "0:1",
13
+ "name": "Screen"
14
+ }
15
+ ],
16
+ "nodes": [
17
+ {
18
+ "id": "1:1",
19
+ "path": "Templates / Screen / Node",
20
+ "isRoot": false,
21
+ "inInstance": false,
22
+ "overridden": [],
23
+ "paints": [],
24
+ "mixedPaints": false,
25
+ "gap": null,
26
+ "padding": [],
27
+ "radius": [],
28
+ "detached": null
29
+ }
30
+ ],
31
+ "tokens": {
32
+ "colours": {},
33
+ "gap": {},
34
+ "radius": {}
35
+ }
36
+ },
37
+ "expected": {
38
+ "pass": false,
39
+ "failures": [
40
+ "colour-rule-inert",
41
+ "dimension-rule-inert"
42
+ ],
43
+ "advisories": 0
44
+ }
45
+ }
@@ -0,0 +1,61 @@
1
+ {
2
+ "covers": "a text node with mixed paints is an advisory, not a pass and not a crash",
3
+ "snapshot": {
4
+ "fileKey": "FILE",
5
+ "screens": [
6
+ {
7
+ "id": "0:1",
8
+ "name": "Screen"
9
+ }
10
+ ],
11
+ "nodes": [
12
+ {
13
+ "id": "1:1",
14
+ "path": "Templates / Screen / Node",
15
+ "isRoot": false,
16
+ "inInstance": false,
17
+ "overridden": [],
18
+ "paints": [],
19
+ "mixedPaints": true,
20
+ "gap": null,
21
+ "padding": [],
22
+ "radius": [],
23
+ "detached": null
24
+ }
25
+ ],
26
+ "tokens": {
27
+ "colours": {
28
+ "#ffffff": [
29
+ "color/bg/canvas"
30
+ ],
31
+ "#111827": [
32
+ "color/text/primary"
33
+ ]
34
+ },
35
+ "gap": {
36
+ "8": [
37
+ "spacing/2"
38
+ ],
39
+ "12": [
40
+ "spacing/3"
41
+ ],
42
+ "16": [
43
+ "spacing/4"
44
+ ]
45
+ },
46
+ "radius": {
47
+ "8": [
48
+ "radius/md"
49
+ ],
50
+ "32": [
51
+ "radius/xl"
52
+ ]
53
+ }
54
+ }
55
+ },
56
+ "expected": {
57
+ "pass": true,
58
+ "failures": [],
59
+ "advisories": 1
60
+ }
61
+ }
@@ -0,0 +1,49 @@
1
+ {
2
+ "covers": "no nodes read fails",
3
+ "snapshot": {
4
+ "fileKey": "FILE",
5
+ "screens": [
6
+ {
7
+ "id": "0:1",
8
+ "name": "Screen"
9
+ }
10
+ ],
11
+ "nodes": [],
12
+ "tokens": {
13
+ "colours": {
14
+ "#ffffff": [
15
+ "color/bg/canvas"
16
+ ],
17
+ "#111827": [
18
+ "color/text/primary"
19
+ ]
20
+ },
21
+ "gap": {
22
+ "8": [
23
+ "spacing/2"
24
+ ],
25
+ "12": [
26
+ "spacing/3"
27
+ ],
28
+ "16": [
29
+ "spacing/4"
30
+ ]
31
+ },
32
+ "radius": {
33
+ "8": [
34
+ "radius/md"
35
+ ],
36
+ "32": [
37
+ "radius/xl"
38
+ ]
39
+ }
40
+ }
41
+ },
42
+ "expected": {
43
+ "pass": false,
44
+ "failures": [
45
+ "nothing-scanned"
46
+ ],
47
+ "advisories": 0
48
+ }
49
+ }
@@ -0,0 +1,65 @@
1
+ {
2
+ "covers": "Zygarden 2026-10-13: a 200 gap no token has is an advisory",
3
+ "snapshot": {
4
+ "fileKey": "FILE",
5
+ "screens": [
6
+ {
7
+ "id": "0:1",
8
+ "name": "Screen"
9
+ }
10
+ ],
11
+ "nodes": [
12
+ {
13
+ "id": "1:1",
14
+ "path": "Templates / bg",
15
+ "isRoot": false,
16
+ "inInstance": false,
17
+ "overridden": [],
18
+ "paints": [],
19
+ "mixedPaints": false,
20
+ "gap": {
21
+ "value": 200,
22
+ "bound": false,
23
+ "renders": true
24
+ },
25
+ "padding": [],
26
+ "radius": [],
27
+ "detached": null
28
+ }
29
+ ],
30
+ "tokens": {
31
+ "colours": {
32
+ "#ffffff": [
33
+ "color/bg/canvas"
34
+ ],
35
+ "#111827": [
36
+ "color/text/primary"
37
+ ]
38
+ },
39
+ "gap": {
40
+ "8": [
41
+ "spacing/2"
42
+ ],
43
+ "12": [
44
+ "spacing/3"
45
+ ],
46
+ "16": [
47
+ "spacing/4"
48
+ ]
49
+ },
50
+ "radius": {
51
+ "8": [
52
+ "radius/md"
53
+ ],
54
+ "32": [
55
+ "radius/xl"
56
+ ]
57
+ }
58
+ }
59
+ },
60
+ "expected": {
61
+ "pass": true,
62
+ "failures": [],
63
+ "advisories": 1
64
+ }
65
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "covers": "an overridden fill inside an instance, unbound and token-valued, fails",
3
+ "snapshot": {
4
+ "fileKey": "FILE",
5
+ "screens": [
6
+ {
7
+ "id": "0:1",
8
+ "name": "Screen"
9
+ }
10
+ ],
11
+ "nodes": [
12
+ {
13
+ "id": "1:1",
14
+ "path": "Templates / Screen / Node",
15
+ "isRoot": false,
16
+ "inInstance": true,
17
+ "overridden": [
18
+ "fills"
19
+ ],
20
+ "paints": [
21
+ {
22
+ "kind": "fill",
23
+ "hex": "#111827",
24
+ "bound": false,
25
+ "styled": false
26
+ }
27
+ ],
28
+ "mixedPaints": false,
29
+ "gap": null,
30
+ "padding": [],
31
+ "radius": [],
32
+ "detached": null
33
+ }
34
+ ],
35
+ "tokens": {
36
+ "colours": {
37
+ "#ffffff": [
38
+ "color/bg/canvas"
39
+ ],
40
+ "#111827": [
41
+ "color/text/primary"
42
+ ]
43
+ },
44
+ "gap": {
45
+ "8": [
46
+ "spacing/2"
47
+ ],
48
+ "12": [
49
+ "spacing/3"
50
+ ],
51
+ "16": [
52
+ "spacing/4"
53
+ ]
54
+ },
55
+ "radius": {
56
+ "8": [
57
+ "radius/md"
58
+ ],
59
+ "32": [
60
+ "radius/xl"
61
+ ]
62
+ }
63
+ }
64
+ },
65
+ "expected": {
66
+ "pass": false,
67
+ "failures": [
68
+ "raw-colour"
69
+ ],
70
+ "advisories": 0
71
+ }
72
+ }
@@ -0,0 +1,74 @@
1
+ {
2
+ "covers": "an unbound padding side a token has fails",
3
+ "snapshot": {
4
+ "fileKey": "FILE",
5
+ "screens": [
6
+ {
7
+ "id": "0:1",
8
+ "name": "Screen"
9
+ }
10
+ ],
11
+ "nodes": [
12
+ {
13
+ "id": "1:1",
14
+ "path": "Templates / Screen / Node",
15
+ "isRoot": false,
16
+ "inInstance": false,
17
+ "overridden": [],
18
+ "paints": [],
19
+ "mixedPaints": false,
20
+ "gap": null,
21
+ "padding": [
22
+ {
23
+ "side": "left",
24
+ "value": 16,
25
+ "bound": false
26
+ },
27
+ {
28
+ "side": "top",
29
+ "value": 16,
30
+ "bound": true
31
+ }
32
+ ],
33
+ "radius": [],
34
+ "detached": null
35
+ }
36
+ ],
37
+ "tokens": {
38
+ "colours": {
39
+ "#ffffff": [
40
+ "color/bg/canvas"
41
+ ],
42
+ "#111827": [
43
+ "color/text/primary"
44
+ ]
45
+ },
46
+ "gap": {
47
+ "8": [
48
+ "spacing/2"
49
+ ],
50
+ "12": [
51
+ "spacing/3"
52
+ ],
53
+ "16": [
54
+ "spacing/4"
55
+ ]
56
+ },
57
+ "radius": {
58
+ "8": [
59
+ "radius/md"
60
+ ],
61
+ "32": [
62
+ "radius/xl"
63
+ ]
64
+ }
65
+ }
66
+ },
67
+ "expected": {
68
+ "pass": false,
69
+ "failures": [
70
+ "raw-dimension"
71
+ ],
72
+ "advisories": 0
73
+ }
74
+ }