pi-mono-all 1.0.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.
- package/CHANGELOG.md +13 -0
- package/LICENCE.md +7 -0
- package/node_modules/pi-common/package.json +22 -0
- package/node_modules/pi-common/src/auth-config.ts +290 -0
- package/node_modules/pi-common/src/auth.ts +63 -0
- package/node_modules/pi-common/src/cache.ts +60 -0
- package/node_modules/pi-common/src/errors.ts +47 -0
- package/node_modules/pi-common/src/http-client.ts +118 -0
- package/node_modules/pi-common/src/index.ts +7 -0
- package/node_modules/pi-common/src/rate-limiter.ts +32 -0
- package/node_modules/pi-common/src/tool-result.ts +27 -0
- package/node_modules/pi-mono-ask-user-question/CHANGELOG.md +185 -0
- package/node_modules/pi-mono-ask-user-question/README.md +226 -0
- package/node_modules/pi-mono-ask-user-question/index.ts +923 -0
- package/node_modules/pi-mono-ask-user-question/package.json +29 -0
- package/node_modules/pi-mono-auto-fix/CHANGELOG.md +59 -0
- package/node_modules/pi-mono-auto-fix/README.md +77 -0
- package/node_modules/pi-mono-auto-fix/index.ts +488 -0
- package/node_modules/pi-mono-auto-fix/package.json +23 -0
- package/node_modules/pi-mono-btw/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-btw/README.md +24 -0
- package/node_modules/pi-mono-btw/index.ts +499 -0
- package/node_modules/pi-mono-btw/package.json +29 -0
- package/node_modules/pi-mono-clear/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-clear/README.md +40 -0
- package/node_modules/pi-mono-clear/index.ts +45 -0
- package/node_modules/pi-mono-clear/package.json +29 -0
- package/node_modules/pi-mono-context/CHANGELOG.md +12 -0
- package/node_modules/pi-mono-context/README.md +74 -0
- package/node_modules/pi-mono-context/index.ts +641 -0
- package/node_modules/pi-mono-context/package.json +29 -0
- package/node_modules/pi-mono-context-guard/CHANGELOG.md +195 -0
- package/node_modules/pi-mono-context-guard/README.md +81 -0
- package/node_modules/pi-mono-context-guard/index.ts +212 -0
- package/node_modules/pi-mono-context-guard/package.json +23 -0
- package/node_modules/pi-mono-figma/CHANGELOG.md +59 -0
- package/node_modules/pi-mono-figma/README.md +236 -0
- package/node_modules/pi-mono-figma/__tests__/code-connect.test.ts +32 -0
- package/node_modules/pi-mono-figma/__tests__/figma-assets.test.ts +38 -0
- package/node_modules/pi-mono-figma/__tests__/figma-component-hints.test.ts +23 -0
- package/node_modules/pi-mono-figma/__tests__/figma-implementation-layout.test.ts +47 -0
- package/node_modules/pi-mono-figma/__tests__/figma-search.test.ts +51 -0
- package/node_modules/pi-mono-figma/__tests__/figma-summarizer.test.ts +65 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/complex-auto-layout.json +115 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/component-instance.json +50 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/hidden-and-vectors.json +28 -0
- package/node_modules/pi-mono-figma/__tests__/fixtures/variables-and-styles.json +40 -0
- package/node_modules/pi-mono-figma/docs/live-selection-bridge.md +16 -0
- package/node_modules/pi-mono-figma/index.ts +6 -0
- package/node_modules/pi-mono-figma/package.json +33 -0
- package/node_modules/pi-mono-figma/skills/figma/SKILL.md +143 -0
- package/node_modules/pi-mono-figma/src/code-connect.ts +110 -0
- package/node_modules/pi-mono-figma/src/figma-assets.ts +146 -0
- package/node_modules/pi-mono-figma/src/figma-cache.ts +6 -0
- package/node_modules/pi-mono-figma/src/figma-client.ts +471 -0
- package/node_modules/pi-mono-figma/src/figma-component-hints.ts +87 -0
- package/node_modules/pi-mono-figma/src/figma-implementation.ts +264 -0
- package/node_modules/pi-mono-figma/src/figma-schemas.ts +139 -0
- package/node_modules/pi-mono-figma/src/figma-search.ts +195 -0
- package/node_modules/pi-mono-figma/src/figma-summarizer.ts +673 -0
- package/node_modules/pi-mono-figma/src/figma-tokens.ts +57 -0
- package/node_modules/pi-mono-figma/src/figma-tools.ts +352 -0
- package/node_modules/pi-mono-linear/CHANGELOG.md +44 -0
- package/node_modules/pi-mono-linear/README.md +159 -0
- package/node_modules/pi-mono-linear/index.ts +6 -0
- package/node_modules/pi-mono-linear/package.json +30 -0
- package/node_modules/pi-mono-linear/skills/linear/SKILL.md +107 -0
- package/node_modules/pi-mono-linear/src/linear-client.ts +339 -0
- package/node_modules/pi-mono-linear/src/linear-queries.ts +101 -0
- package/node_modules/pi-mono-linear/src/linear-schemas.ts +90 -0
- package/node_modules/pi-mono-linear/src/linear-tools.ts +362 -0
- package/node_modules/pi-mono-loop/CHANGELOG.md +163 -0
- package/node_modules/pi-mono-loop/README.md +54 -0
- package/node_modules/pi-mono-loop/index.ts +291 -0
- package/node_modules/pi-mono-loop/package.json +26 -0
- package/node_modules/pi-mono-multi-edit/CHANGELOG.md +232 -0
- package/node_modules/pi-mono-multi-edit/README.md +244 -0
- package/node_modules/pi-mono-multi-edit/__tests__/classic.test.ts +277 -0
- package/node_modules/pi-mono-multi-edit/__tests__/diff.test.ts +77 -0
- package/node_modules/pi-mono-multi-edit/__tests__/patch.test.ts +287 -0
- package/node_modules/pi-mono-multi-edit/benchmark-edits.ts +966 -0
- package/node_modules/pi-mono-multi-edit/classic.ts +435 -0
- package/node_modules/pi-mono-multi-edit/diff.ts +143 -0
- package/node_modules/pi-mono-multi-edit/index.ts +266 -0
- package/node_modules/pi-mono-multi-edit/package.json +37 -0
- package/node_modules/pi-mono-multi-edit/patch.ts +463 -0
- package/node_modules/pi-mono-multi-edit/types.ts +53 -0
- package/node_modules/pi-mono-multi-edit/workspace.ts +85 -0
- package/node_modules/pi-mono-review/CHANGELOG.md +190 -0
- package/node_modules/pi-mono-review/README.md +30 -0
- package/node_modules/pi-mono-review/common.ts +930 -0
- package/node_modules/pi-mono-review/index.ts +8 -0
- package/node_modules/pi-mono-review/package.json +29 -0
- package/node_modules/pi-mono-review/review-tui.ts +194 -0
- package/node_modules/pi-mono-review/review.ts +119 -0
- package/node_modules/pi-mono-review/reviewer.ts +339 -0
- package/node_modules/pi-mono-sentinel/CHANGELOG.md +158 -0
- package/node_modules/pi-mono-sentinel/README.md +87 -0
- package/node_modules/pi-mono-sentinel/__tests__/output-scanner.test.ts +109 -0
- package/node_modules/pi-mono-sentinel/__tests__/permissions.test.ts +202 -0
- package/node_modules/pi-mono-sentinel/__tests__/whitelist.test.ts +59 -0
- package/node_modules/pi-mono-sentinel/guards/execution-tracker.ts +281 -0
- package/node_modules/pi-mono-sentinel/guards/output-scanner.ts +232 -0
- package/node_modules/pi-mono-sentinel/guards/permission-gate.ts +170 -0
- package/node_modules/pi-mono-sentinel/index.ts +43 -0
- package/node_modules/pi-mono-sentinel/package.json +26 -0
- package/node_modules/pi-mono-sentinel/patterns/permissions.ts +175 -0
- package/node_modules/pi-mono-sentinel/patterns/read-targets.ts +104 -0
- package/node_modules/pi-mono-sentinel/patterns/secrets.ts +143 -0
- package/node_modules/pi-mono-sentinel/session.ts +95 -0
- package/node_modules/pi-mono-sentinel/specs/2026/04/sentinel/001-permission-gate.md +145 -0
- package/node_modules/pi-mono-sentinel/types.ts +39 -0
- package/node_modules/pi-mono-sentinel/whitelist.ts +86 -0
- package/node_modules/pi-mono-simplify/CHANGELOG.md +163 -0
- package/node_modules/pi-mono-simplify/README.md +56 -0
- package/node_modules/pi-mono-simplify/index.ts +78 -0
- package/node_modules/pi-mono-simplify/package.json +29 -0
- package/node_modules/pi-mono-status-line/CHANGELOG.md +180 -0
- package/node_modules/pi-mono-status-line/README.md +96 -0
- package/node_modules/pi-mono-status-line/basic.ts +89 -0
- package/node_modules/pi-mono-status-line/expert.ts +689 -0
- package/node_modules/pi-mono-status-line/index.ts +54 -0
- package/node_modules/pi-mono-status-line/package.json +29 -0
- package/node_modules/pi-mono-team-mode/CHANGELOG.md +278 -0
- package/node_modules/pi-mono-team-mode/README.md +246 -0
- package/node_modules/pi-mono-team-mode/__tests__/agent-manager-transient.test.ts +75 -0
- package/node_modules/pi-mono-team-mode/__tests__/delegation-manager.test.ts +118 -0
- package/node_modules/pi-mono-team-mode/__tests__/formatters.test.ts +104 -0
- package/node_modules/pi-mono-team-mode/__tests__/model-config.test.ts +272 -0
- package/node_modules/pi-mono-team-mode/__tests__/notification-box.test.ts +34 -0
- package/node_modules/pi-mono-team-mode/__tests__/parallel-utils.test.ts +32 -0
- package/node_modules/pi-mono-team-mode/__tests__/pi-stream-parser.test.ts +64 -0
- package/node_modules/pi-mono-team-mode/__tests__/prompts.test.ts +106 -0
- package/node_modules/pi-mono-team-mode/__tests__/store.test.ts +164 -0
- package/node_modules/pi-mono-team-mode/__tests__/tasks.test.ts +267 -0
- package/node_modules/pi-mono-team-mode/__tests__/teammate-specs.test.ts +114 -0
- package/node_modules/pi-mono-team-mode/__tests__/widget.test.ts +41 -0
- package/node_modules/pi-mono-team-mode/__tests__/worktree.test.ts +78 -0
- package/node_modules/pi-mono-team-mode/core/chain-utils.ts +90 -0
- package/node_modules/pi-mono-team-mode/core/fs-utils.ts +44 -0
- package/node_modules/pi-mono-team-mode/core/model-config.ts +432 -0
- package/node_modules/pi-mono-team-mode/core/parallel-utils.ts +48 -0
- package/node_modules/pi-mono-team-mode/core/prompts.ts +158 -0
- package/node_modules/pi-mono-team-mode/core/store.ts +156 -0
- package/node_modules/pi-mono-team-mode/core/tasks.ts +99 -0
- package/node_modules/pi-mono-team-mode/core/teammate-specs.ts +124 -0
- package/node_modules/pi-mono-team-mode/core/types.ts +160 -0
- package/node_modules/pi-mono-team-mode/index.ts +825 -0
- package/node_modules/pi-mono-team-mode/managers/agent-manager.ts +654 -0
- package/node_modules/pi-mono-team-mode/managers/delegation-manager.ts +211 -0
- package/node_modules/pi-mono-team-mode/managers/task-manager.ts +238 -0
- package/node_modules/pi-mono-team-mode/managers/team-manager.ts +59 -0
- package/node_modules/pi-mono-team-mode/package.json +33 -0
- package/node_modules/pi-mono-team-mode/runtime/pi-stream-parser.ts +194 -0
- package/node_modules/pi-mono-team-mode/runtime/subprocess.ts +183 -0
- package/node_modules/pi-mono-team-mode/runtime/transient-session.ts +196 -0
- package/node_modules/pi-mono-team-mode/runtime/worktree.ts +90 -0
- package/node_modules/pi-mono-team-mode/ui/formatters.ts +149 -0
- package/node_modules/pi-mono-team-mode/ui/notification-box.ts +55 -0
- package/node_modules/pi-mono-team-mode/ui/widget.ts +94 -0
- package/package.json +76 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# pi-mono-figma
|
|
2
|
+
|
|
3
|
+
A pi extension and skill package that exposes native Figma tools for design exploration and design-to-code workflows. The default tools return compact, LLM-ready design context instead of raw Figma JSON.
|
|
4
|
+
|
|
5
|
+
## Benefits over Figma MCP
|
|
6
|
+
|
|
7
|
+
This package is Pi-native and uses Figma's REST API directly, with tools designed specifically for LLM-friendly design exploration and design-to-code workflows.
|
|
8
|
+
|
|
9
|
+
- **No hosted Figma MCP quota path:** the extension calls Figma's REST API directly instead of using a hosted Claude/Figma connector quota path. It is still subject to Figma API limits, and the client smooths calls with a fixed 1s limiter plus a 5-minute TTL cache.
|
|
10
|
+
- **Better LLM-shaped output:** `figma_get_node_summary`, `figma_explain_node`, and `figma_get_implementation_context` avoid raw Figma JSON by default. They cap depth, skip hidden nodes, vector internals, and component internals unless requested, and return `metadata.nextSteps` when follow-up inspection would help.
|
|
11
|
+
- **Design-to-code specialization:** `figma_get_implementation_context` extracts fields, buttons, layout measurements, typography, colors, spacing, CSS/flex/grid hints, responsive guidance, accessibility hints, design tokens, assets, and optional framework starter snippets instead of simply relaying generic server output.
|
|
12
|
+
- **Safer local auth UX:** `figma_configure_auth` uses masked local prompting and stores the token in Pi auth storage. The model never sees the token.
|
|
13
|
+
- **Good raw escape hatches:** raw `figma_get_file` and `figma_get_nodes` tools are available for debugging, while tool descriptions steer agents toward processed tools first.
|
|
14
|
+
- **Local asset handling:** `figma_render_nodes` can download rendered images to an OS temp directory by default, while `figma_extract_assets` returns a manifest for SVG icons, node renders, and image fills with node paths, hashes, byte sizes, and suggested names. Persistent project directories are used only when `outputDir` is explicitly provided.
|
|
15
|
+
- **Broader inspection surface:** styles, variables, components, component sets, component search, metadata, text extraction, rendering, summaries, explanations, and implementation context are exposed as separate native tools.
|
|
16
|
+
|
|
17
|
+
## Tools
|
|
18
|
+
|
|
19
|
+
### Processed, LLM-ready tools (preferred)
|
|
20
|
+
|
|
21
|
+
- `figma_parse_url` — parse a Figma URL into `fileKey` and `nodeId`.
|
|
22
|
+
- `figma_find_nodes_by_name` — search layer/node names in a file or subtree and return compact path-aware matches.
|
|
23
|
+
- `figma_find_nodes_by_text` — search visible text in a file or subtree and return matches with nearest parent context.
|
|
24
|
+
- `figma_render_nodes` — render node image URLs and optionally download assets locally. Downloads use an OS temp directory by default unless the user explicitly provides `outputDir`.
|
|
25
|
+
- `figma_get_node_summary` — fetch a compact structured summary of a node: name, type, size, layout, spacing, visual style, visible text, component properties, and shallow child hierarchy.
|
|
26
|
+
- `figma_extract_text` — return visible text nodes only.
|
|
27
|
+
- `figma_explain_node` — explain a node in Markdown using summary, visible text, hierarchy, and optional rendered asset.
|
|
28
|
+
- `figma_get_implementation_context` — return coding-ready design context: purpose, sections, fields/buttons, measurements, typography, colors, spacing, CSS layout, responsive hints, accessibility hints, design token resolution, assets, component hierarchy, and optional snippets.
|
|
29
|
+
- `figma_extract_assets` — extract SVG/icon exports, node renders, and image fills into a node-path manifest with hashes and local paths.
|
|
30
|
+
- `figma_find_code_connect_mapping` — scan the current repo for Code Connect files, `figma.connect(...)`, Figma URLs/node IDs, and component key references.
|
|
31
|
+
- `figma_get_component_implementation_hints` — combine summary, implementation context, variants/properties, tokens, assets, accessibility, optional Code Connect matches, and starter snippets.
|
|
32
|
+
- `figma_get_design_context` — fetch compact file context. With `nodeId`, returns target node summary plus location/sibling context; without `nodeId`, returns canvases and top-level frames only.
|
|
33
|
+
- `figma_get_node_metadata` — fetch compact spatial/layout metadata for one or more nodes.
|
|
34
|
+
- `figma_get_styles` — fetch named styles.
|
|
35
|
+
- `figma_get_variables` — fetch local variables/collections for design tokens.
|
|
36
|
+
- `figma_get_components` — fetch component metadata.
|
|
37
|
+
- `figma_get_component_sets` — fetch component set metadata.
|
|
38
|
+
- `figma_search_components` — search components by name or description.
|
|
39
|
+
- `figma_configure_auth` — securely prompt for and store a Figma token without exposing it to the model.
|
|
40
|
+
|
|
41
|
+
### Raw escape hatches
|
|
42
|
+
|
|
43
|
+
- `figma_get_file` — fetch raw Figma file JSON. Use only when raw Figma JSON is explicitly needed or when debugging the extension.
|
|
44
|
+
- `figma_get_nodes` — fetch raw node JSON. Use only when raw Figma JSON is explicitly needed or when debugging the extension.
|
|
45
|
+
|
|
46
|
+
The package also bundles the `figma` skill under `skills/figma/SKILL.md`.
|
|
47
|
+
|
|
48
|
+
## Recommended workflows
|
|
49
|
+
|
|
50
|
+
### Explaining a component
|
|
51
|
+
|
|
52
|
+
```text
|
|
53
|
+
figma_parse_url
|
|
54
|
+
figma_render_nodes
|
|
55
|
+
figma_explain_node
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Implementing a design
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
figma_parse_url
|
|
62
|
+
figma_find_nodes_by_name or figma_find_nodes_by_text when the URL does not include the exact target node
|
|
63
|
+
figma_render_nodes
|
|
64
|
+
figma_get_implementation_context with framework/styling when useful
|
|
65
|
+
figma_get_node_summary for specific subnodes if needed
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Example implementation-context options:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
{
|
|
72
|
+
framework: "react",
|
|
73
|
+
styling: "styled-components",
|
|
74
|
+
resolveTokens: true,
|
|
75
|
+
includeCodeSnippets: true
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Finding a frame or layer before implementation
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
figma_get_design_context
|
|
83
|
+
figma_find_nodes_by_name query="Checkout" nodeId=<top-level-frame-if-known>
|
|
84
|
+
figma_find_nodes_by_text query="Submit" nodeId=<candidate-frame>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Extracting assets for a frame
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
figma_extract_assets assetTypes=["svgIcons", "nodeRenders", "imageFills"]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Use returned `nodePath`, `suggestedName`, `sha256`, and `bytes` to map downloaded files back to Figma layers and avoid duplicate assets.
|
|
94
|
+
Omit `outputDir` unless the user asked for files to be saved in a persistent project location; the default is an OS temp directory.
|
|
95
|
+
|
|
96
|
+
### Finding local Code Connect mappings
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
figma_find_code_connect_mapping fileKey=<fileKey> nodeId=<nodeId>
|
|
100
|
+
figma_get_component_implementation_hints includeCodeConnect=true framework="react"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Use Code Connect matches only as local implementation hints; no Figma write access is used.
|
|
104
|
+
|
|
105
|
+
### Debugging raw Figma data
|
|
106
|
+
|
|
107
|
+
```text
|
|
108
|
+
figma_get_nodes
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Live selection boundary
|
|
112
|
+
|
|
113
|
+
This package is REST/API-based and read-only. It can inspect files, nodes, styles, variables, renders, and local repository mappings when you provide a file key/node ID, but it does **not** currently know the live selection in an open Figma desktop/browser session.
|
|
114
|
+
|
|
115
|
+
True Dev Mode-style live selection parity would require a separate local bridge or Figma plugin that captures the current selection and exposes selected file/node IDs to Pi. See [`docs/live-selection-bridge.md`](docs/live-selection-bridge.md) for a future architecture sketch.
|
|
116
|
+
|
|
117
|
+
## Processed node options
|
|
118
|
+
|
|
119
|
+
Processed tools fetch nodes with compact depth limits and summarize safely:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
{
|
|
123
|
+
depth?: number; // default 2, capped at 4
|
|
124
|
+
includeHidden?: boolean; // default false
|
|
125
|
+
includeVectors?: boolean; // default false
|
|
126
|
+
includeComponentInternals?: boolean; // default false
|
|
127
|
+
framework?: "react" | "html" | "vue" | "angular" | "react-native";
|
|
128
|
+
styling?: "css" | "css-modules" | "styled-components" | "tailwind" | "inline";
|
|
129
|
+
resolveTokens?: boolean; // implementation context, default true
|
|
130
|
+
includeCodeSnippets?: boolean; // implementation context, default false
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Defaults intentionally avoid hidden layers, vector internals, and huge component instance trees. Increase depth or include internals only for a focused child node.
|
|
135
|
+
|
|
136
|
+
## Output limits and truncation
|
|
137
|
+
|
|
138
|
+
Processed tools default to compact responses (~20k chars unless overridden by `maxResponseChars`) and cap common large arrays:
|
|
139
|
+
|
|
140
|
+
- visible text: 200 entries
|
|
141
|
+
- children: 100 entries
|
|
142
|
+
- depth: 4 max
|
|
143
|
+
|
|
144
|
+
When data is capped, responses include `metadata.truncated: true`, `truncatedReasons`, and `nextSteps` such as inspecting a child node or increasing depth.
|
|
145
|
+
|
|
146
|
+
## Authentication
|
|
147
|
+
|
|
148
|
+
The extension looks for a Figma token in this order:
|
|
149
|
+
|
|
150
|
+
1. in-memory token override created by `/figma-auth --force` or `figma_configure_auth`
|
|
151
|
+
2. `FIGMA_TOKEN` environment variable
|
|
152
|
+
3. `~/.pi/agent/auth.json` at `.figma.token`
|
|
153
|
+
|
|
154
|
+
If no token is found, or if Figma rejects the token as invalid/expired, the native tools can prompt you with a masked local dialog and store the replacement token without returning it to the model.
|
|
155
|
+
|
|
156
|
+
Do **not** paste tokens into an LLM chat.
|
|
157
|
+
|
|
158
|
+
### Recommended: native auth command
|
|
159
|
+
|
|
160
|
+
Run this in pi:
|
|
161
|
+
|
|
162
|
+
```text
|
|
163
|
+
/figma-auth --force
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
The command shows the Figma token URL and required scopes, prompts for the token with masked input, and writes it to `~/.pi/agent/auth.json` at `.figma.token`.
|
|
167
|
+
|
|
168
|
+
The same flow is available to the agent through the `figma_configure_auth` tool. That tool returns only metadata such as `stored: true`; it never returns the token.
|
|
169
|
+
|
|
170
|
+
### Option A: environment variable
|
|
171
|
+
|
|
172
|
+
For the current shell session:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
export FIGMA_TOKEN="figd_xxx"
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
To persist it, add that export to your shell profile (`~/.zshrc`, `~/.bashrc`, etc.) or your preferred secrets manager.
|
|
179
|
+
|
|
180
|
+
### Option B: pi auth file
|
|
181
|
+
|
|
182
|
+
Create or update `~/.pi/agent/auth.json`:
|
|
183
|
+
|
|
184
|
+
```json
|
|
185
|
+
{
|
|
186
|
+
"figma": {
|
|
187
|
+
"token": "figd_xxx"
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Recommended file permissions:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
mkdir -p ~/.pi/agent
|
|
196
|
+
chmod 700 ~/.pi/agent
|
|
197
|
+
chmod 600 ~/.pi/agent/auth.json
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
If the file already contains other credentials, merge the `figma.token` entry instead of overwriting the file.
|
|
201
|
+
|
|
202
|
+
## Creating a Figma token
|
|
203
|
+
|
|
204
|
+
1. Open Figma.
|
|
205
|
+
2. Go to **Settings → Security → Personal access tokens**.
|
|
206
|
+
3. Click **Generate new token**.
|
|
207
|
+
4. Name it something recognizable, for example `pi-agent`.
|
|
208
|
+
5. Enable the required scopes/permissions below.
|
|
209
|
+
6. Copy the token once and store it via `FIGMA_TOKEN` or `~/.pi/agent/auth.json`.
|
|
210
|
+
|
|
211
|
+
## Required scopes / permissions
|
|
212
|
+
|
|
213
|
+
This extension is read-only. It needs permission to read file content and render nodes.
|
|
214
|
+
|
|
215
|
+
Enable:
|
|
216
|
+
|
|
217
|
+
- **File content** / read access to files
|
|
218
|
+
|
|
219
|
+
Also make sure the token has access to the specific Figma file, project, or team you want to inspect. If your Figma organization supports resource scoping, grant access only to the minimum projects/files needed.
|
|
220
|
+
|
|
221
|
+
No write/admin scopes are required.
|
|
222
|
+
|
|
223
|
+
## Troubleshooting
|
|
224
|
+
|
|
225
|
+
### `Token expired`
|
|
226
|
+
|
|
227
|
+
Generate a new Figma personal access token and update `FIGMA_TOKEN` or `~/.pi/agent/auth.json`.
|
|
228
|
+
|
|
229
|
+
### Missing or inaccessible file
|
|
230
|
+
|
|
231
|
+
Confirm that:
|
|
232
|
+
|
|
233
|
+
- the file key is correct,
|
|
234
|
+
- the token has file-content read permission,
|
|
235
|
+
- the token owner can access the file in Figma,
|
|
236
|
+
- the node ID uses either URL format (`27399-4245`) or API format (`27399:4245`).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import assert from "node:assert/strict";
|
|
6
|
+
import { findCodeConnectMapping } from "../src/code-connect.js";
|
|
7
|
+
|
|
8
|
+
test("findCodeConnectMapping discovers figma.connect, URLs, and node IDs", async () => {
|
|
9
|
+
const root = await mkdtemp(join(tmpdir(), "figma-code-connect-"));
|
|
10
|
+
await writeFile(join(root, "Button.figma.tsx"), "figma.connect(Button, 'https://www.figma.com/design/FILE123/Name?node-id=1-2')\n");
|
|
11
|
+
await writeFile(join(root, "README.md"), "component key COMPONENT123\n");
|
|
12
|
+
const result = await findCodeConnectMapping({ cwd: root, fileKey: "FILE123", nodeId: "1:2", componentKey: "COMPONENT123" });
|
|
13
|
+
assert.ok(result.matches.some((match) => match.kind === "figma-connect"));
|
|
14
|
+
assert.ok(result.matches.some((match) => match.kind === "figma-file-reference"));
|
|
15
|
+
assert.ok(result.matches.some((match) => match.kind === "component-key-reference"));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("findCodeConnectMapping ignores node_modules and enforces caps", async () => {
|
|
19
|
+
const root = await mkdtemp(join(tmpdir(), "figma-code-connect-"));
|
|
20
|
+
await mkdir(join(root, "node_modules"));
|
|
21
|
+
await writeFile(join(root, "node_modules", "Ignored.ts"), "figma.connect(Ignored)\n");
|
|
22
|
+
await writeFile(join(root, "One.ts"), "figma.connect(One)\nfigma.connect(Two)\n");
|
|
23
|
+
const result = await findCodeConnectMapping({ cwd: root, fileKey: "FILE123", maxMatches: 1 });
|
|
24
|
+
assert.equal(result.matches.length, 1);
|
|
25
|
+
assert.equal(result.matches[0]?.path, "One.ts");
|
|
26
|
+
assert.equal(result.metadata.truncated, true);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("findCodeConnectMapping rejects rootDir outside cwd", async () => {
|
|
30
|
+
const root = await mkdtemp(join(tmpdir(), "figma-code-connect-"));
|
|
31
|
+
await assert.rejects(() => findCodeConnectMapping({ cwd: root, rootDir: "/", fileKey: "FILE123" }), /rootDir/);
|
|
32
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { collectAssetCandidates, formatFromPath, safeFilename, sha256 } from "../src/figma-assets.js";
|
|
4
|
+
|
|
5
|
+
const assetTree = {
|
|
6
|
+
id: "10:1",
|
|
7
|
+
name: "Hero Card",
|
|
8
|
+
type: "FRAME",
|
|
9
|
+
absoluteBoundingBox: { width: 320, height: 200 },
|
|
10
|
+
fills: [{ type: "IMAGE", imageRef: "abc123", scaleMode: "FILL" }],
|
|
11
|
+
children: [
|
|
12
|
+
{ id: "10:2", name: "Close icon", type: "VECTOR", absoluteBoundingBox: { width: 16, height: 16 } },
|
|
13
|
+
{ id: "10:3", name: "Logo Mark", type: "FRAME", absoluteBoundingBox: { width: 32, height: 32 } },
|
|
14
|
+
{ id: "10:4", name: "Hidden asset", type: "VECTOR", visible: false, absoluteBoundingBox: { width: 16, height: 16 } },
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
test("collectAssetCandidates detects icons, node renders, image fills, and paths", () => {
|
|
19
|
+
const result = collectAssetCandidates(assetTree, { assetTypes: ["svgIcons", "nodeRenders", "imageFills"] });
|
|
20
|
+
assert.ok(result.assets.some((asset) => asset.kind === "svgIcon" && asset.nodePath === "Hero Card > Close icon"));
|
|
21
|
+
assert.ok(result.assets.some((asset) => asset.kind === "nodeRender" && asset.nodePath === "Hero Card"));
|
|
22
|
+
assert.ok(result.assets.some((asset) => asset.kind === "imageFill" && asset.imageRef === "abc123"));
|
|
23
|
+
assert.equal(result.assets.some((asset) => asset.nodeName === "Hidden asset"), false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("collectAssetCandidates supports hidden nodes and caps results", () => {
|
|
27
|
+
const result = collectAssetCandidates(assetTree, { includeHidden: true, maxAssets: 1 });
|
|
28
|
+
assert.equal(result.assets.length, 1);
|
|
29
|
+
assert.equal(result.metadata.truncated, true);
|
|
30
|
+
assert.ok(result.metadata.truncatedReasons.some((reason) => reason.includes("maxAssets")));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("asset helper utilities normalize names, hashes, and formats", () => {
|
|
34
|
+
assert.equal(safeFilename("Close Icon / Primary"), "close-icon-primary");
|
|
35
|
+
assert.equal(sha256("abc"), "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
|
|
36
|
+
assert.equal(formatFromPath("/tmp/icon.svg"), "svg");
|
|
37
|
+
assert.equal(formatFromPath("/tmp/photo.jpeg"), "jpg");
|
|
38
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { buildComponentImplementationHints } from "../src/figma-component-hints.js";
|
|
6
|
+
import { getImplementationContext, summarizeNode } from "../src/figma-summarizer.js";
|
|
7
|
+
|
|
8
|
+
async function fixture(name: string): Promise<unknown> {
|
|
9
|
+
return JSON.parse(await readFile(join(import.meta.dirname, "fixtures", name), "utf8"));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
test("buildComponentImplementationHints combines summary, variants, accessibility, tokens, and snippets", async () => {
|
|
13
|
+
const node = await fixture("component-instance.json");
|
|
14
|
+
const summary = summarizeNode(node, { depth: 3 });
|
|
15
|
+
const context = getImplementationContext(node, { framework: "react", includeCodeSnippets: true });
|
|
16
|
+
const hints = buildComponentImplementationHints(summary, context, { framework: "react", includeSnippet: true, includeCodeConnect: true }, { rootDir: "/repo", matches: [], metadata: { truncated: false, truncatedReasons: [], nextSteps: [] } });
|
|
17
|
+
assert.equal(hints.componentName, "SettingsModal");
|
|
18
|
+
assert.ok(hints.suggestedProps.some((prop) => prop.name === "children"));
|
|
19
|
+
assert.ok(hints.statesAndVariants.some((variant) => variant.name === "State"));
|
|
20
|
+
assert.ok(hints.accessibilityRequirements.some((hint) => hint.role === "dialog" || hint.role === "button"));
|
|
21
|
+
assert.match(String(hints.frameworkHints?.snippet), /export function SettingsModal/);
|
|
22
|
+
assert.ok(hints.metadata.nextSteps.some((step) => step.includes("Code Connect")));
|
|
23
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { getImplementationContext } from "../src/figma-summarizer.js";
|
|
6
|
+
import { buildCssLayoutHints, buildResponsiveHints } from "../src/figma-implementation.js";
|
|
7
|
+
|
|
8
|
+
async function fixture(name: string): Promise<unknown> {
|
|
9
|
+
return JSON.parse(await readFile(join(import.meta.dirname, "fixtures", name), "utf8"));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
test("buildCssLayoutHints maps auto-layout to CSS flex and grid hints", async () => {
|
|
13
|
+
const node = await fixture("complex-auto-layout.json");
|
|
14
|
+
const hints = buildCssLayoutHints(node);
|
|
15
|
+
assert.deepEqual((hints.css as Record<string, unknown>).display, "flex");
|
|
16
|
+
assert.equal((hints.css as Record<string, unknown>).flexDirection, "column");
|
|
17
|
+
assert.equal((hints.css as Record<string, unknown>).gap, "16px");
|
|
18
|
+
assert.equal((hints.css as Record<string, unknown>).padding, "20px 24px 20px 24px");
|
|
19
|
+
assert.ok(Array.isArray((hints.css as Record<string, unknown>).layoutGrids));
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("buildResponsiveHints recommends fill, hug, fixed, and wrap behavior", async () => {
|
|
23
|
+
const node = await fixture("complex-auto-layout.json");
|
|
24
|
+
const hints = buildResponsiveHints(node);
|
|
25
|
+
assert.ok(hints.some((hint) => String(hint.name) === "Header Row" && (hint.recommendations as string[]).some((rec) => rec.includes("width: 100%"))));
|
|
26
|
+
assert.ok(hints.some((hint) => String(hint.name) === "Dashboard Card" && (hint.recommendations as string[]).some((rec) => rec.includes("Fixed width"))));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("implementation context includes layout, responsive, accessibility, tokens, and snippets", async () => {
|
|
30
|
+
const node = await fixture("variables-and-styles.json");
|
|
31
|
+
const context = getImplementationContext(node, {
|
|
32
|
+
framework: "react",
|
|
33
|
+
styling: "styled-components",
|
|
34
|
+
includeCodeSnippets: true,
|
|
35
|
+
tokenMap: {
|
|
36
|
+
styles: { "S:primary-fill": { name: "Color/Primary", type: "FILL" }, "S:text-button": { name: "Typography/Button", type: "TEXT" } },
|
|
37
|
+
variables: { "VariableID:color-primary": { name: "color.primary" }, "VariableID:text-on-primary": { name: "color.onPrimary" }, "VariableID:radius-md": { name: "radius.md" } },
|
|
38
|
+
collections: {},
|
|
39
|
+
warnings: [],
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
assert.ok(context.cssLayout);
|
|
43
|
+
assert.ok(context.accessibility?.some((hint) => hint.role === "button"));
|
|
44
|
+
assert.ok((context.designTokens?.resolved as Array<Record<string, unknown>>).some((token) => token.name === "Color/Primary"));
|
|
45
|
+
assert.equal(context.frameworkHints?.framework, "react");
|
|
46
|
+
assert.match(String(context.frameworkHints?.snippet), /styled\.section/);
|
|
47
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { findNodesByName, findNodesByText } from "../src/figma-search.js";
|
|
6
|
+
|
|
7
|
+
async function fixture(name: string): Promise<unknown> {
|
|
8
|
+
return JSON.parse(await readFile(join(import.meta.dirname, "fixtures", name), "utf8"));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
test("findNodesByName supports partial, exact, and case-sensitive matching", async () => {
|
|
12
|
+
const node = await fixture("complex-auto-layout.json");
|
|
13
|
+
assert.deepEqual(findNodesByName(node, { query: "button" }).matches.map((match) => match.name), ["Save button", "Button label"]);
|
|
14
|
+
assert.equal(findNodesByName(node, { query: "save button", exact: true }).matches.length, 1);
|
|
15
|
+
assert.equal(findNodesByName(node, { query: "save button", exact: true, caseSensitive: true }).matches.length, 0);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("findNodesByText returns path and parent context", async () => {
|
|
19
|
+
const node = await fixture("complex-auto-layout.json");
|
|
20
|
+
const result = findNodesByText(node, { query: "Water risk" });
|
|
21
|
+
assert.equal(result.matches[0]?.text, "Water risk summary");
|
|
22
|
+
assert.equal(result.matches[0]?.parent?.name, "Header Row");
|
|
23
|
+
assert.match(result.matches[0]?.path ?? "", /Dashboard Card > Header Row > Title/);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("findNodesByName respects hidden and vector filters", async () => {
|
|
27
|
+
const node = await fixture("hidden-and-vectors.json");
|
|
28
|
+
assert.equal(findNodesByName(node, { query: "Hidden" }).matches.length, 0);
|
|
29
|
+
assert.equal(findNodesByName(node, { query: "Hidden", includeHidden: true }).matches.length, 1);
|
|
30
|
+
assert.equal(findNodesByName(node, { query: "icon" }).matches.length, 0);
|
|
31
|
+
assert.equal(findNodesByName(node, { query: "icon", includeVectors: true }).matches.length, 1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test("findNodesByText searches collapsed instance text but not vector internals", async () => {
|
|
35
|
+
const node = await fixture("component-instance.json");
|
|
36
|
+
const result = findNodesByText(node, { query: "Continue" });
|
|
37
|
+
assert.equal(result.matches.length, 1);
|
|
38
|
+
assert.equal(result.matches[0]?.parent?.name, "Primary CTA instance");
|
|
39
|
+
assert.ok(result.metadata.nextSteps.some((step) => step.includes("includeComponentInternals")));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("findNodesByName enforces depth and result caps", async () => {
|
|
43
|
+
const node = await fixture("complex-auto-layout.json");
|
|
44
|
+
const depthLimited = findNodesByName(node, { query: "Title", depth: 1 });
|
|
45
|
+
assert.equal(depthLimited.matches.length, 0);
|
|
46
|
+
assert.ok(depthLimited.metadata.truncatedReasons.some((reason) => reason.includes("depth limit")));
|
|
47
|
+
|
|
48
|
+
const capped = findNodesByName(node, { query: "", maxResults: 1 });
|
|
49
|
+
assert.equal(capped.matches.length, 1);
|
|
50
|
+
assert.ok(capped.metadata.truncatedReasons.some((reason) => reason.includes("maxResults")));
|
|
51
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import test from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { extractVisibleText, getImplementationContext, summarizeNode } from "../src/figma-summarizer.js";
|
|
6
|
+
|
|
7
|
+
async function fixture(name: string): Promise<unknown> {
|
|
8
|
+
const path = join(import.meta.dirname, "fixtures", name);
|
|
9
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
test("summarizeNode excludes hidden nodes and vector internals by default", async () => {
|
|
13
|
+
const node = await fixture("hidden-and-vectors.json");
|
|
14
|
+
const summary = summarizeNode(node, { depth: 3 });
|
|
15
|
+
assert.deepEqual(summary.visibleText, ["Visible copy"]);
|
|
16
|
+
assert.equal(summary.children?.some((child) => child.name === "Hidden text"), false);
|
|
17
|
+
assert.equal(summary.children?.some((child) => child.name === "Search icon"), false);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("summarizeNode can include hidden nodes and vectors when requested", async () => {
|
|
21
|
+
const node = await fixture("hidden-and-vectors.json");
|
|
22
|
+
const summary = summarizeNode(node, { depth: 3, includeHidden: true, includeVectors: true });
|
|
23
|
+
assert.deepEqual(summary.visibleText, ["Visible copy", "Hidden copy"]);
|
|
24
|
+
assert.ok(summary.children?.some((child) => child.name === "Hidden text"));
|
|
25
|
+
assert.ok(summary.children?.some((child) => child.name === "Search icon"));
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("component instances collapse structural internals while retaining useful text", async () => {
|
|
29
|
+
const node = await fixture("component-instance.json");
|
|
30
|
+
const summary = summarizeNode(node, { depth: 2 });
|
|
31
|
+
const instance = summary.children?.find((child) => child.type === "INSTANCE");
|
|
32
|
+
assert.equal(instance?.children, undefined);
|
|
33
|
+
assert.deepEqual(instance?.text, ["Continue"]);
|
|
34
|
+
assert.ok(summary.metadata?.truncatedReasons.some((reason) => reason.includes("Collapsed component instance")));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("extractVisibleText returns capped text metadata", async () => {
|
|
38
|
+
const node = await fixture("complex-auto-layout.json");
|
|
39
|
+
const result = extractVisibleText(node, { maxVisibleText: 1 });
|
|
40
|
+
assert.deepEqual(result.texts, ["Water risk summary"]);
|
|
41
|
+
assert.equal(result.metadata.truncated, true);
|
|
42
|
+
assert.ok(result.metadata.nextSteps.includes("Use figma_extract_text on a narrower child node to see more text."));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("getImplementationContext deterministically extracts typography colors spacing and controls", async () => {
|
|
46
|
+
const node = await fixture("complex-auto-layout.json");
|
|
47
|
+
const context = getImplementationContext(node, { depth: 3 });
|
|
48
|
+
assert.match(context.purpose, /Water risk summary/);
|
|
49
|
+
assert.equal(context.sections.length, 2);
|
|
50
|
+
assert.ok(context.buttons.some((button) => button.name === "Save button"));
|
|
51
|
+
assert.ok(context.typography.some((entry) => entry.fontFamily === "Inter" && entry.fontSize === 18));
|
|
52
|
+
assert.ok(context.colors.some((entry) => entry.hex === "#ffffff"));
|
|
53
|
+
assert.ok(context.spacing.some((entry) => entry.itemSpacing === 16));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("depth and children caps produce truncation metadata", async () => {
|
|
57
|
+
const node = await fixture("complex-auto-layout.json");
|
|
58
|
+
const depthLimited = summarizeNode(node, { depth: 1 });
|
|
59
|
+
assert.equal(depthLimited.metadata?.truncated, true);
|
|
60
|
+
assert.ok(depthLimited.metadata?.nextSteps.some((step) => step.includes("depth 2")));
|
|
61
|
+
|
|
62
|
+
const childLimited = summarizeNode(node, { depth: 2, maxChildren: 1 });
|
|
63
|
+
assert.equal(childLimited.children?.length, 1);
|
|
64
|
+
assert.ok(childLimited.metadata?.truncatedReasons.some((reason) => reason.includes("Capped children")));
|
|
65
|
+
});
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "1:1",
|
|
3
|
+
"name": "Dashboard Card",
|
|
4
|
+
"type": "FRAME",
|
|
5
|
+
"visible": true,
|
|
6
|
+
"absoluteBoundingBox": { "width": 360, "height": 240 },
|
|
7
|
+
"layoutMode": "VERTICAL",
|
|
8
|
+
"primaryAxisAlignItems": "MIN",
|
|
9
|
+
"counterAxisAlignItems": "STRETCH",
|
|
10
|
+
"layoutWrap": "NO_WRAP",
|
|
11
|
+
"layoutSizingHorizontal": "FIXED",
|
|
12
|
+
"layoutSizingVertical": "HUG",
|
|
13
|
+
"itemSpacing": 16,
|
|
14
|
+
"paddingLeft": 24,
|
|
15
|
+
"paddingRight": 24,
|
|
16
|
+
"paddingTop": 20,
|
|
17
|
+
"paddingBottom": 20,
|
|
18
|
+
"layoutGrids": [
|
|
19
|
+
{ "pattern": "COLUMNS", "count": 12, "gutterSize": 16, "sectionSize": 56 }
|
|
20
|
+
],
|
|
21
|
+
"fills": [
|
|
22
|
+
{ "type": "SOLID", "color": { "r": 1, "g": 1, "b": 1 }, "opacity": 1 }
|
|
23
|
+
],
|
|
24
|
+
"strokes": [
|
|
25
|
+
{
|
|
26
|
+
"type": "SOLID",
|
|
27
|
+
"color": { "r": 0.8, "g": 0.84, "b": 0.9 },
|
|
28
|
+
"opacity": 1
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"cornerRadius": 12,
|
|
32
|
+
"children": [
|
|
33
|
+
{
|
|
34
|
+
"id": "1:2",
|
|
35
|
+
"name": "Header Row",
|
|
36
|
+
"type": "FRAME",
|
|
37
|
+
"absoluteBoundingBox": { "width": 312, "height": 40 },
|
|
38
|
+
"layoutMode": "HORIZONTAL",
|
|
39
|
+
"primaryAxisAlignItems": "SPACE_BETWEEN",
|
|
40
|
+
"counterAxisAlignItems": "CENTER",
|
|
41
|
+
"layoutSizingHorizontal": "FILL",
|
|
42
|
+
"layoutSizingVertical": "HUG",
|
|
43
|
+
"itemSpacing": 8,
|
|
44
|
+
"children": [
|
|
45
|
+
{
|
|
46
|
+
"id": "1:3",
|
|
47
|
+
"name": "Title",
|
|
48
|
+
"type": "TEXT",
|
|
49
|
+
"absoluteBoundingBox": { "width": 140, "height": 24 },
|
|
50
|
+
"characters": "Water risk summary",
|
|
51
|
+
"style": {
|
|
52
|
+
"fontFamily": "Inter",
|
|
53
|
+
"fontSize": 18,
|
|
54
|
+
"fontWeight": 700,
|
|
55
|
+
"lineHeightPx": 24
|
|
56
|
+
},
|
|
57
|
+
"fills": [
|
|
58
|
+
{
|
|
59
|
+
"type": "SOLID",
|
|
60
|
+
"color": { "r": 0.05, "g": 0.1, "b": 0.18 },
|
|
61
|
+
"opacity": 1
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "1:4",
|
|
67
|
+
"name": "More icon",
|
|
68
|
+
"type": "VECTOR",
|
|
69
|
+
"absoluteBoundingBox": { "width": 20, "height": 20 },
|
|
70
|
+
"fills": [
|
|
71
|
+
{
|
|
72
|
+
"type": "SOLID",
|
|
73
|
+
"color": { "r": 0.3, "g": 0.35, "b": 0.44 },
|
|
74
|
+
"opacity": 1
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"id": "1:5",
|
|
82
|
+
"name": "Save button",
|
|
83
|
+
"type": "FRAME",
|
|
84
|
+
"absoluteBoundingBox": { "width": 120, "height": 40 },
|
|
85
|
+
"layoutMode": "HORIZONTAL",
|
|
86
|
+
"primaryAxisAlignItems": "CENTER",
|
|
87
|
+
"counterAxisAlignItems": "CENTER",
|
|
88
|
+
"itemSpacing": 8,
|
|
89
|
+
"fills": [
|
|
90
|
+
{
|
|
91
|
+
"type": "SOLID",
|
|
92
|
+
"color": { "r": 0.05, "g": 0.42, "b": 0.95 },
|
|
93
|
+
"opacity": 1
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"children": [
|
|
97
|
+
{
|
|
98
|
+
"id": "1:6",
|
|
99
|
+
"name": "Button label",
|
|
100
|
+
"type": "TEXT",
|
|
101
|
+
"characters": "Save",
|
|
102
|
+
"absoluteBoundingBox": { "width": 34, "height": 20 },
|
|
103
|
+
"style": { "fontFamily": "Inter", "fontSize": 14, "fontWeight": 600 },
|
|
104
|
+
"fills": [
|
|
105
|
+
{
|
|
106
|
+
"type": "SOLID",
|
|
107
|
+
"color": { "r": 1, "g": 1, "b": 1 },
|
|
108
|
+
"opacity": 1
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
}
|
|
114
|
+
]
|
|
115
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "2:1",
|
|
3
|
+
"name": "Settings modal",
|
|
4
|
+
"type": "FRAME",
|
|
5
|
+
"absoluteBoundingBox": { "width": 480, "height": 360 },
|
|
6
|
+
"children": [
|
|
7
|
+
{
|
|
8
|
+
"id": "2:2",
|
|
9
|
+
"name": "Primary CTA instance",
|
|
10
|
+
"type": "INSTANCE",
|
|
11
|
+
"componentId": "99:1",
|
|
12
|
+
"componentSetId": "99:0",
|
|
13
|
+
"componentProperties": {
|
|
14
|
+
"State": { "type": "VARIANT", "value": "Default" },
|
|
15
|
+
"Disabled": { "type": "BOOLEAN", "value": false }
|
|
16
|
+
},
|
|
17
|
+
"absoluteBoundingBox": { "width": 180, "height": 44 },
|
|
18
|
+
"children": [
|
|
19
|
+
{
|
|
20
|
+
"id": "2:3",
|
|
21
|
+
"name": "Hidden icon",
|
|
22
|
+
"type": "VECTOR",
|
|
23
|
+
"absoluteBoundingBox": { "width": 16, "height": 16 }
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "2:4",
|
|
27
|
+
"name": "Label",
|
|
28
|
+
"type": "TEXT",
|
|
29
|
+
"characters": "Continue",
|
|
30
|
+
"absoluteBoundingBox": { "width": 70, "height": 20 },
|
|
31
|
+
"style": { "fontFamily": "Inter", "fontSize": 14, "fontWeight": 600 }
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "2:5",
|
|
37
|
+
"name": "Email input field",
|
|
38
|
+
"type": "FRAME",
|
|
39
|
+
"absoluteBoundingBox": { "width": 300, "height": 48 },
|
|
40
|
+
"children": [
|
|
41
|
+
{
|
|
42
|
+
"id": "2:6",
|
|
43
|
+
"name": "Placeholder",
|
|
44
|
+
"type": "TEXT",
|
|
45
|
+
"characters": "Email address"
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
}
|