@agent-native/core 0.136.4 → 0.137.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/corpus/README.md +1 -1
- package/corpus/templates/clips/.agents/skills/recording/SKILL.md +53 -0
- package/corpus/templates/clips/actions/import-loom-recording.ts +7 -0
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +24 -4
- package/corpus/templates/clips/app/components/import-menu.tsx +109 -0
- package/corpus/templates/clips/app/components/library/library-grid.tsx +8 -18
- package/corpus/templates/clips/app/components/library/library-layout.tsx +14 -50
- package/corpus/templates/clips/app/components/library/sort-menu.tsx +20 -6
- package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +10 -34
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +0 -2
- package/corpus/templates/clips/changelog/2026-08-03-restart-during-a-recording-now-immediately-starts-a-fresh-ta.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-04-cleaner-library-controls-with-an-icon-only-sort-button.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-04-upload-and-loom-imports-now-live-under-one-import-menu.md +6 -0
- package/corpus/templates/clips/desktop/src/app.tsx +81 -30
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +436 -158
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +14 -1
- package/corpus/templates/clips/server/plugins/auth.ts +9 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +13 -0
- package/corpus/templates/content/actions/_database-utils.ts +6 -0
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +273 -97
- package/corpus/templates/content/actions/change-content-database-source-role.ts +65 -29
- package/corpus/templates/content/actions/configure-document-property.ts +113 -32
- package/corpus/templates/content/actions/delete-content-database.ts +5 -7
- package/corpus/templates/content/actions/delete-document-property.ts +109 -62
- package/corpus/templates/content/actions/delete-document.ts +178 -36
- package/corpus/templates/content/actions/duplicate-database-item.ts +27 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +31 -1
- package/corpus/templates/content/actions/permanently-delete-document.ts +4 -6
- package/corpus/templates/content/actions/remove-database-items.ts +13 -0
- package/corpus/templates/content/actions/set-document-property.ts +69 -1
- package/corpus/templates/content/actions/upsert-database-item-by-key.ts +814 -0
- package/corpus/templates/content/parity/matrix.md +1 -1
- package/corpus/templates/content/parity/matrix.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +27 -0
- package/corpus/templates/content/server/plugins/db.ts +24 -0
- package/corpus/templates/design/app/components/design/BreakpointBar.tsx +122 -114
- package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
- package/corpus/templates/design/app/components/design/EditPanel.tsx +68 -34
- package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +78 -4
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +8 -0
- package/corpus/templates/design/app/components/design/QuestionFlow.tsx +5 -2
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +10 -9
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +31 -69
- package/corpus/templates/design/app/components/design/inspector/design-icons.tsx +80 -106
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +4 -0
- package/corpus/templates/design/app/hooks/use-editor-preferences.ts +75 -0
- package/corpus/templates/design/app/i18n-data.ts +8 -0
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +61 -0
- package/corpus/templates/design/app/pages/design-editor/editor-preferences.ts +98 -0
- package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +491 -0
- package/corpus/templates/design/app/pages/design-editor/paste-placement.ts +133 -0
- package/corpus/templates/design/app/pages/design-editor/png-export-render.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -12
- package/corpus/templates/design/changelog/2026-08-03-arrow-keys-now-reorder-a-layer-inside-an-auto-layout-frame.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-auto-layout-flow-icons-show-the-direction-at-a-glance-instea.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-auto-layout-icons-share-one-outlined-square-style-so-flow-ga.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-export-has-a-named-preview-section-you-can-expand-replacing-.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-export-preview-shows-the-selected-element-instead-of-the-who.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-inspector-sections-show-an-expand-indicator-so-it-is-clear-w.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-paste-lands-inside-a-selected-frame-instead-of-beside-it.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-add-breakpoint-button-sits-outside-the-breakpoint-select.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-keep-space-below-the-continu.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-agent-s-follow-up-questions-share-the-chat-panel-s-backg.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-export-panel-no-longer-stacks-a-duplicate-export-block-o.md +6 -0
- package/corpus/templates/design/changelog/2026-08-03-the-text-tool-uses-a-t-icon-matching-the-letter-it-inserts.md +6 -0
- package/corpus/templates/factory/.env.example +17 -0
- package/corpus/templates/factory/AGENTS.md +27 -17
- package/corpus/templates/factory/README.md +19 -9
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +90 -0
- package/corpus/templates/factory/actions/get-triage-config.ts +14 -3
- package/corpus/templates/factory/actions/govern-agent-native-pull-request.ts +386 -0
- package/corpus/templates/factory/actions/list-factory-automations.ts +53 -0
- package/corpus/templates/factory/actions/list-triage-items.ts +7 -2
- package/corpus/templates/factory/actions/poll-github-sources.ts +203 -0
- package/corpus/templates/factory/actions/poll-sentry-errors.ts +161 -0
- package/corpus/templates/factory/actions/poll-slack-channel.ts +4 -2
- package/corpus/templates/factory/actions/run-factory-automation.ts +42 -0
- package/corpus/templates/factory/actions/save-factory-automation.ts +69 -0
- package/corpus/templates/factory/actions/save-triage-config.ts +28 -2
- package/corpus/templates/factory/actions/start-builder-for-item.ts +411 -0
- package/corpus/templates/factory/app/components/factory/FactoryCanvas.tsx +14 -3
- package/corpus/templates/factory/app/components/ui/checkbox.tsx +1 -0
- package/corpus/templates/factory/app/hooks/use-navigation-state.ts +7 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +35 -1
- package/corpus/templates/factory/app/routes/factory.tsx +462 -9
- package/corpus/templates/factory/changelog/2026-08-04-added-organization-owned-factory-automations-for-slack-bug-t.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-04-automation-editor-controls-and-model-picker.md +6 -0
- package/corpus/templates/factory/netlify.toml +2 -2
- package/corpus/templates/factory/server/connectors/credentials.ts +6 -5
- package/corpus/templates/factory/server/connectors/slack.ts +133 -5
- package/corpus/templates/factory/server/db/schema.ts +6 -0
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +65 -0
- package/corpus/templates/factory/server/plugins/agent-chat.ts +29 -11
- package/corpus/templates/factory/server/plugins/auth.ts +1 -0
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +12 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +218 -67
- package/corpus/templates/factory/server/triage/builder-executor.ts +4 -0
- package/corpus/templates/factory/server/triage/contracts.ts +14 -1
- package/corpus/templates/factory/server/triage/github-client.ts +404 -0
- package/corpus/templates/factory/server/triage/metadata.ts +44 -0
- package/corpus/templates/factory/server/triage/pr-babysit.ts +5 -5
- package/corpus/templates/factory/server/triage/pr-policy.ts +144 -0
- package/corpus/templates/factory/server/triage/sentry-client.ts +180 -0
- package/corpus/templates/factory/server/triage/slack-client.ts +43 -0
- package/corpus/templates/factory/server/triage/slack-poller.ts +9 -2
- package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +27 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +14 -4
- package/corpus/templates/plan/.agents/skills/visual-plan/references/wireframe.md +7 -0
- package/corpus/templates/plan/.agents/skills/visual-recap/references/wireframe.md +7 -0
- package/corpus/templates/plan/actions/create-plan-design.ts +8 -6
- package/corpus/templates/plan/actions/create-prototype-plan.ts +8 -6
- package/corpus/templates/plan/actions/create-ui-plan.ts +7 -7
- package/corpus/templates/plan/actions/get-plan-blocks.ts +7 -1
- package/corpus/templates/plan/actions/validate-plan-input.ts +39 -0
- package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +167 -11
- package/corpus/templates/plan/app/i18n/en-US.ts +1 -0
- package/corpus/templates/plan/changelog/2026-08-04-canvas-zoom-controls-now-explain-the-command-ctrl-plus-scrol.md +6 -0
- package/corpus/templates/plan/changelog/2026-08-04-pinch-to-zoom-is-now-supported-on-touchscreens-in-visual-pla.md +6 -0
- package/corpus/templates/plan/shared/plan-content.ts +17 -2
- package/dist/cli/skills-content/canvas.d.ts +3 -3
- package/dist/cli/skills-content/canvas.js +16 -5
- package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
- package/dist/cli/skills-content/visual-plan-skill.js +27 -0
- package/dist/cli/skills-content/wireframe.d.ts +2 -2
- package/dist/cli/skills-content/wireframe.js +7 -0
- package/dist/client/AgentPanel.js +23 -8
- package/dist/client/api-path.d.ts +6 -0
- package/dist/client/api-path.js +55 -0
- package/dist/client/onboarding/FirstRunOnboarding.d.ts +3 -0
- package/dist/client/onboarding/FirstRunOnboarding.js +93 -0
- package/dist/client/onboarding/first-run-enabled.d.ts +2 -0
- package/dist/client/onboarding/first-run-enabled.js +9 -0
- package/dist/client/onboarding/index.d.ts +2 -1
- package/dist/client/onboarding/index.js +1 -0
- package/dist/client/onboarding/use-onboarding.d.ts +6 -1
- package/dist/client/onboarding/use-onboarding.js +32 -1
- package/dist/client/route-state.js +10 -0
- package/dist/client/settings/SecretsSection.js +32 -8
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +4 -4
- package/dist/connections/catalog.js +3 -3
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/localization/default-messages.d.ts +5 -0
- package/dist/localization/default-messages.js +5 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/observability/routes.d.ts +3 -3
- package/dist/onboarding/app-profile.d.ts +4 -0
- package/dist/onboarding/app-profile.js +378 -0
- package/dist/onboarding/index.d.ts +2 -1
- package/dist/onboarding/index.js +1 -0
- package/dist/onboarding/plugin.d.ts +2 -0
- package/dist/onboarding/plugin.js +21 -1
- package/dist/onboarding/types.d.ts +19 -0
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +9 -9
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/secrets/routes.d.ts +2 -2
- package/dist/secrets/routes.js +41 -17
- package/dist/server/auth.js +21 -1
- package/dist/server/better-auth-instance.d.ts +3 -2
- package/dist/server/better-auth-instance.js +9 -7
- package/dist/server/email-markdown.js +3 -8
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/first-run-onboarding.d.ts +3 -0
- package/dist/shared/first-run-onboarding.js +3 -0
- package/dist/triggers/index.d.ts +3 -0
- package/dist/triggers/index.js +5 -0
- package/docs/content/cross-app-sso.mdx +26 -0
- package/package.json +1 -1
|
@@ -4,34 +4,50 @@ interface IconProps {
|
|
|
4
4
|
className?: string;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
/** Text tool / text node. A serif T with end and foot serifs — the plain
|
|
8
|
+
* letterform reads as a letter, this reads as type. */
|
|
9
|
+
export function IconText({ className }: IconProps) {
|
|
10
|
+
return (
|
|
11
|
+
<svg
|
|
12
|
+
viewBox="0 0 24 24"
|
|
13
|
+
fill="none"
|
|
14
|
+
stroke="currentColor"
|
|
15
|
+
strokeWidth={1.5}
|
|
16
|
+
strokeLinecap="round"
|
|
17
|
+
strokeLinejoin="round"
|
|
18
|
+
className={cn("size-4", className)}
|
|
19
|
+
aria-hidden="true"
|
|
20
|
+
>
|
|
21
|
+
<path d="M3 4H21" />
|
|
22
|
+
<path d="M3 4V8" />
|
|
23
|
+
<path d="M21 4V8" />
|
|
24
|
+
<path d="M12 4V20" />
|
|
25
|
+
<path d="M7 20H17" />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Gap: two items with a measured space between them.
|
|
8
31
|
export function IconGap({ className }: IconProps) {
|
|
9
32
|
return (
|
|
10
33
|
<svg
|
|
11
34
|
viewBox="0 0 24 24"
|
|
12
35
|
fill="none"
|
|
13
36
|
stroke="currentColor"
|
|
14
|
-
strokeWidth={
|
|
37
|
+
strokeWidth={1.5}
|
|
15
38
|
strokeLinecap="round"
|
|
16
39
|
strokeLinejoin="round"
|
|
17
40
|
className={cn("size-4", className)}
|
|
18
41
|
aria-hidden="true"
|
|
19
42
|
>
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
<
|
|
23
|
-
<line x1="6" y1="17" x2="9" y2="17" />
|
|
24
|
-
{/* Center dot */}
|
|
25
|
-
<circle cx="12" cy="12" r="1" fill="currentColor" stroke="none" />
|
|
26
|
-
{/* Right bracket [ — vertical bar on left side, serifs pointing left */}
|
|
27
|
-
<line x1="15" y1="7" x2="18" y2="7" />
|
|
28
|
-
<line x1="18" y1="7" x2="18" y2="17" />
|
|
29
|
-
<line x1="18" y1="17" x2="15" y2="17" />
|
|
43
|
+
<rect x="2.5" y="5" width="6" height="14" rx="1" />
|
|
44
|
+
<rect x="15.5" y="5" width="6" height="14" rx="1" />
|
|
45
|
+
<path d="M12 7.5V16.5" />
|
|
30
46
|
</svg>
|
|
31
47
|
);
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
// Padding
|
|
50
|
+
// Padding: frame with the two insets for that axis marked.
|
|
35
51
|
export function IconPaddingHorizontal({ className }: IconProps) {
|
|
36
52
|
return (
|
|
37
53
|
<svg
|
|
@@ -44,33 +60,13 @@ export function IconPaddingHorizontal({ className }: IconProps) {
|
|
|
44
60
|
className={cn("size-4", className)}
|
|
45
61
|
aria-hidden="true"
|
|
46
62
|
>
|
|
47
|
-
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
<rect
|
|
51
|
-
x="3"
|
|
52
|
-
y="4"
|
|
53
|
-
width="4"
|
|
54
|
-
height="16"
|
|
55
|
-
rx="1.5"
|
|
56
|
-
fill="currentColor"
|
|
57
|
-
stroke="none"
|
|
58
|
-
/>
|
|
59
|
-
{/* Thick right edge fill */}
|
|
60
|
-
<rect
|
|
61
|
-
x="17"
|
|
62
|
-
y="4"
|
|
63
|
-
width="4"
|
|
64
|
-
height="16"
|
|
65
|
-
rx="1.5"
|
|
66
|
-
fill="currentColor"
|
|
67
|
-
stroke="none"
|
|
68
|
-
/>
|
|
63
|
+
<rect x="3" y="4" width="18" height="16" rx="1.5" />
|
|
64
|
+
<path d="M7.5 8V16" />
|
|
65
|
+
<path d="M16.5 8V16" />
|
|
69
66
|
</svg>
|
|
70
67
|
);
|
|
71
68
|
}
|
|
72
69
|
|
|
73
|
-
// Padding vertical: square with thick top and bottom edges
|
|
74
70
|
export function IconPaddingVertical({ className }: IconProps) {
|
|
75
71
|
return (
|
|
76
72
|
<svg
|
|
@@ -83,34 +79,19 @@ export function IconPaddingVertical({ className }: IconProps) {
|
|
|
83
79
|
className={cn("size-4", className)}
|
|
84
80
|
aria-hidden="true"
|
|
85
81
|
>
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
<rect
|
|
90
|
-
x="4"
|
|
91
|
-
y="3"
|
|
92
|
-
width="16"
|
|
93
|
-
height="4"
|
|
94
|
-
rx="1.5"
|
|
95
|
-
fill="currentColor"
|
|
96
|
-
stroke="none"
|
|
97
|
-
/>
|
|
98
|
-
{/* Thick bottom edge fill */}
|
|
99
|
-
<rect
|
|
100
|
-
x="4"
|
|
101
|
-
y="17"
|
|
102
|
-
width="16"
|
|
103
|
-
height="4"
|
|
104
|
-
rx="1.5"
|
|
105
|
-
fill="currentColor"
|
|
106
|
-
stroke="none"
|
|
107
|
-
/>
|
|
82
|
+
<rect x="4" y="3" width="16" height="18" rx="1.5" />
|
|
83
|
+
<path d="M8 7.5H16" />
|
|
84
|
+
<path d="M8 16.5H16" />
|
|
108
85
|
</svg>
|
|
109
86
|
);
|
|
110
87
|
}
|
|
111
88
|
|
|
112
|
-
|
|
113
|
-
|
|
89
|
+
/** Shared item square. Every flow glyph must stay on this one grid and stroke
|
|
90
|
+
* weight; a filled bar or container frame among them reads as a different
|
|
91
|
+
* control and stops the four segments comparing at a glance. */
|
|
92
|
+
const FLOW_ITEM = { width: 8, height: 8, rx: 1 } as const;
|
|
93
|
+
|
|
94
|
+
// Flow horizontal: two items side by side, arrow running left → right beneath.
|
|
114
95
|
export function IconFlowHorizontal({ className }: IconProps) {
|
|
115
96
|
return (
|
|
116
97
|
<svg
|
|
@@ -123,26 +104,15 @@ export function IconFlowHorizontal({ className }: IconProps) {
|
|
|
123
104
|
className={cn("size-4", className)}
|
|
124
105
|
aria-hidden="true"
|
|
125
106
|
>
|
|
126
|
-
|
|
127
|
-
<rect x="
|
|
128
|
-
|
|
129
|
-
<
|
|
130
|
-
x="4.5"
|
|
131
|
-
y="9"
|
|
132
|
-
width="4"
|
|
133
|
-
height="6"
|
|
134
|
-
rx="1"
|
|
135
|
-
fill="currentColor"
|
|
136
|
-
stroke="none"
|
|
137
|
-
/>
|
|
138
|
-
{/* Direction arrow — left to right */}
|
|
139
|
-
<path d="M11 12H18.5" />
|
|
140
|
-
<path d="M16.3 9.8 18.5 12 16.3 14.2" />
|
|
107
|
+
<rect x="3" y="3" {...FLOW_ITEM} />
|
|
108
|
+
<rect x="13" y="3" {...FLOW_ITEM} />
|
|
109
|
+
<path d="M3 17H19.4" />
|
|
110
|
+
<path d="M17 14.8 19.4 17 17 19.2" />
|
|
141
111
|
</svg>
|
|
142
112
|
);
|
|
143
113
|
}
|
|
144
114
|
|
|
145
|
-
// Flow vertical:
|
|
115
|
+
// Flow vertical: items stacked, arrow running top → bottom alongside.
|
|
146
116
|
export function IconFlowVertical({ className }: IconProps) {
|
|
147
117
|
return (
|
|
148
118
|
<svg
|
|
@@ -155,21 +125,29 @@ export function IconFlowVertical({ className }: IconProps) {
|
|
|
155
125
|
className={cn("size-4", className)}
|
|
156
126
|
aria-hidden="true"
|
|
157
127
|
>
|
|
158
|
-
|
|
159
|
-
<rect x="
|
|
160
|
-
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
128
|
+
<rect x="3" y="3" {...FLOW_ITEM} />
|
|
129
|
+
<rect x="3" y="13" {...FLOW_ITEM} />
|
|
130
|
+
<path d="M17 3V19.4" />
|
|
131
|
+
<path d="M14.8 17 17 19.4 19.2 17" />
|
|
132
|
+
</svg>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Flow normal: two items placed freely, no axis and no direction arrow.
|
|
137
|
+
export function IconFlowNormal({ className }: IconProps) {
|
|
138
|
+
return (
|
|
139
|
+
<svg
|
|
140
|
+
viewBox="0 0 24 24"
|
|
141
|
+
fill="none"
|
|
142
|
+
stroke="currentColor"
|
|
143
|
+
strokeWidth={1.5}
|
|
144
|
+
strokeLinecap="round"
|
|
145
|
+
strokeLinejoin="round"
|
|
146
|
+
className={cn("size-4", className)}
|
|
147
|
+
aria-hidden="true"
|
|
148
|
+
>
|
|
149
|
+
<rect x="3" y="3" {...FLOW_ITEM} />
|
|
150
|
+
<rect x="13" y="13" {...FLOW_ITEM} />
|
|
173
151
|
</svg>
|
|
174
152
|
);
|
|
175
153
|
}
|
|
@@ -199,7 +177,7 @@ export function IconFlowWrap({ className }: IconProps) {
|
|
|
199
177
|
);
|
|
200
178
|
}
|
|
201
179
|
|
|
202
|
-
// Flow grid:
|
|
180
|
+
// Flow grid: the same item square in a 2×2.
|
|
203
181
|
export function IconFlowGrid({ className }: IconProps) {
|
|
204
182
|
return (
|
|
205
183
|
<svg
|
|
@@ -212,14 +190,10 @@ export function IconFlowGrid({ className }: IconProps) {
|
|
|
212
190
|
className={cn("size-4", className)}
|
|
213
191
|
aria-hidden="true"
|
|
214
192
|
>
|
|
215
|
-
|
|
216
|
-
<rect x="
|
|
217
|
-
|
|
218
|
-
<rect x="13" y="
|
|
219
|
-
{/* Bottom-left */}
|
|
220
|
-
<rect x="2" y="13" width="9" height="9" rx="1.5" strokeWidth={1.5} />
|
|
221
|
-
{/* Bottom-right */}
|
|
222
|
-
<rect x="13" y="13" width="9" height="9" rx="1.5" strokeWidth={1.5} />
|
|
193
|
+
<rect x="3" y="3" {...FLOW_ITEM} />
|
|
194
|
+
<rect x="13" y="3" {...FLOW_ITEM} />
|
|
195
|
+
<rect x="3" y="13" {...FLOW_ITEM} />
|
|
196
|
+
<rect x="13" y="13" {...FLOW_ITEM} />
|
|
223
197
|
</svg>
|
|
224
198
|
);
|
|
225
199
|
}
|
|
@@ -580,7 +554,7 @@ export function IconSizingFixed({ className }: IconProps) {
|
|
|
580
554
|
viewBox="0 0 24 24"
|
|
581
555
|
fill="none"
|
|
582
556
|
stroke="currentColor"
|
|
583
|
-
strokeWidth={1.
|
|
557
|
+
strokeWidth={1.5}
|
|
584
558
|
strokeLinecap="round"
|
|
585
559
|
strokeLinejoin="round"
|
|
586
560
|
className={cn("size-3.5", className)}
|
|
@@ -603,7 +577,7 @@ export function IconSizingHug({ className }: IconProps) {
|
|
|
603
577
|
viewBox="0 0 24 24"
|
|
604
578
|
fill="none"
|
|
605
579
|
stroke="currentColor"
|
|
606
|
-
strokeWidth={1.
|
|
580
|
+
strokeWidth={1.5}
|
|
607
581
|
strokeLinecap="round"
|
|
608
582
|
strokeLinejoin="round"
|
|
609
583
|
className={cn("size-3.5", className)}
|
|
@@ -624,7 +598,7 @@ export function IconSizingFill({ className }: IconProps) {
|
|
|
624
598
|
viewBox="0 0 24 24"
|
|
625
599
|
fill="none"
|
|
626
600
|
stroke="currentColor"
|
|
627
|
-
strokeWidth={1.
|
|
601
|
+
strokeWidth={1.5}
|
|
628
602
|
strokeLinecap="round"
|
|
629
603
|
strokeLinejoin="round"
|
|
630
604
|
className={cn("size-3.5", className)}
|
|
@@ -649,7 +623,7 @@ export function IconSizingMin({ className }: IconProps) {
|
|
|
649
623
|
viewBox="0 0 24 24"
|
|
650
624
|
fill="none"
|
|
651
625
|
stroke="currentColor"
|
|
652
|
-
strokeWidth={1.
|
|
626
|
+
strokeWidth={1.5}
|
|
653
627
|
strokeLinecap="round"
|
|
654
628
|
strokeLinejoin="round"
|
|
655
629
|
className={cn("size-3.5", className)}
|
|
@@ -673,7 +647,7 @@ export function IconSizingMax({ className }: IconProps) {
|
|
|
673
647
|
viewBox="0 0 24 24"
|
|
674
648
|
fill="none"
|
|
675
649
|
stroke="currentColor"
|
|
676
|
-
strokeWidth={1.
|
|
650
|
+
strokeWidth={1.5}
|
|
677
651
|
strokeLinecap="round"
|
|
678
652
|
strokeLinejoin="round"
|
|
679
653
|
className={cn("size-3.5", className)}
|
|
@@ -697,7 +671,7 @@ export function IconSizingVariable({ className }: IconProps) {
|
|
|
697
671
|
viewBox="0 0 24 24"
|
|
698
672
|
fill="none"
|
|
699
673
|
stroke="currentColor"
|
|
700
|
-
strokeWidth={1.
|
|
674
|
+
strokeWidth={1.5}
|
|
701
675
|
strokeLinecap="round"
|
|
702
676
|
strokeLinejoin="round"
|
|
703
677
|
className={cn("size-3.5", className)}
|
|
@@ -196,6 +196,10 @@ export interface MultiScreenCanvasProps {
|
|
|
196
196
|
}) => void;
|
|
197
197
|
onCreateScreenFrame?: (geometry: FrameGeometry) => void;
|
|
198
198
|
onDeleteSelection?: (ids: string[]) => boolean | void;
|
|
199
|
+
/** Return false to keep the arrow key: the host's real selection is an
|
|
200
|
+
* element inside a screen, and this canvas still lists that screen in
|
|
201
|
+
* `selectedIds`. Same veto `onDeleteSelection` uses. */
|
|
202
|
+
onNudgeSelection?: (ids: string[]) => boolean | void;
|
|
199
203
|
onZoomChange?: (zoom: number) => void;
|
|
200
204
|
renderScreenContent?: (
|
|
201
205
|
screen: ScreenFile,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_EDITOR_PREFERENCES,
|
|
5
|
+
DESIGN_EDITOR_PREFERENCES_STORAGE_KEY,
|
|
6
|
+
parseEditorPreferences,
|
|
7
|
+
serializeEditorPreferences,
|
|
8
|
+
type DesignEditorPreferences,
|
|
9
|
+
} from "@/pages/design-editor/editor-preferences";
|
|
10
|
+
|
|
11
|
+
function readStoredPreferences(): DesignEditorPreferences {
|
|
12
|
+
if (typeof window === "undefined") return DEFAULT_EDITOR_PREFERENCES;
|
|
13
|
+
let raw: string | null;
|
|
14
|
+
try {
|
|
15
|
+
raw = window.localStorage.getItem(DESIGN_EDITOR_PREFERENCES_STORAGE_KEY);
|
|
16
|
+
} catch {
|
|
17
|
+
return DEFAULT_EDITOR_PREFERENCES;
|
|
18
|
+
}
|
|
19
|
+
const result = parseEditorPreferences(raw);
|
|
20
|
+
if (result.status === "invalid") {
|
|
21
|
+
console.warn(
|
|
22
|
+
`[design] discarding corrupt editor preferences: ${result.reason}`,
|
|
23
|
+
);
|
|
24
|
+
try {
|
|
25
|
+
window.localStorage.removeItem(DESIGN_EDITOR_PREFERENCES_STORAGE_KEY);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.warn("[design] could not clear editor preferences", error);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return result.preferences;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface UseEditorPreferences {
|
|
34
|
+
preferences: DesignEditorPreferences;
|
|
35
|
+
setPreferences: (next: DesignEditorPreferences) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Editor-chrome preferences (nudge amounts today) live in localStorage, not
|
|
39
|
+
* SQL: they are per-device input tuning, and a shared design opened by a
|
|
40
|
+
* collaborator must keep that collaborator's own nudge amounts. */
|
|
41
|
+
export function useEditorPreferences(): UseEditorPreferences {
|
|
42
|
+
const [preferences, setPreferencesState] = useState<DesignEditorPreferences>(
|
|
43
|
+
DEFAULT_EDITOR_PREFERENCES,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
setPreferencesState(readStoredPreferences());
|
|
48
|
+
}, []);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (typeof window === "undefined") return;
|
|
52
|
+
const onStorage = (event: StorageEvent) => {
|
|
53
|
+
if (event.key !== DESIGN_EDITOR_PREFERENCES_STORAGE_KEY) return;
|
|
54
|
+
setPreferencesState(readStoredPreferences());
|
|
55
|
+
};
|
|
56
|
+
window.addEventListener("storage", onStorage);
|
|
57
|
+
return () => window.removeEventListener("storage", onStorage);
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const setPreferences = useCallback((next: DesignEditorPreferences) => {
|
|
61
|
+
const serialized = serializeEditorPreferences(next);
|
|
62
|
+
setPreferencesState(parseEditorPreferences(serialized).preferences);
|
|
63
|
+
if (typeof window === "undefined") return;
|
|
64
|
+
try {
|
|
65
|
+
window.localStorage.setItem(
|
|
66
|
+
DESIGN_EDITOR_PREFERENCES_STORAGE_KEY,
|
|
67
|
+
serialized,
|
|
68
|
+
);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn("[design] could not persist editor preferences", error);
|
|
71
|
+
}
|
|
72
|
+
}, []);
|
|
73
|
+
|
|
74
|
+
return { preferences, setPreferences };
|
|
75
|
+
}
|
|
@@ -746,6 +746,14 @@ const enUS = {
|
|
|
746
746
|
close: "Close keyboard shortcuts",
|
|
747
747
|
codeContext: "Code",
|
|
748
748
|
screenContext: "Screen",
|
|
749
|
+
nudgeAmount: {
|
|
750
|
+
title: "Nudge amount",
|
|
751
|
+
small: "Small nudge",
|
|
752
|
+
big: "Big nudge",
|
|
753
|
+
unit: "px",
|
|
754
|
+
description:
|
|
755
|
+
"Arrow keys move by the small amount and Shift+arrow by the big one. Inside an auto layout frame, arrows reorder the layer instead of moving it.",
|
|
756
|
+
},
|
|
749
757
|
keys: {
|
|
750
758
|
or: "or",
|
|
751
759
|
command: "Command",
|
|
@@ -12,6 +12,10 @@ interface KeyboardShortcutLabels {
|
|
|
12
12
|
hideUiDescription: string;
|
|
13
13
|
undoDescription: string;
|
|
14
14
|
redoDescription: string;
|
|
15
|
+
nudgeAmountTitle: string;
|
|
16
|
+
nudgeAmountSmall: string;
|
|
17
|
+
nudgeAmountBig: string;
|
|
18
|
+
nudgeAmountDescription: string;
|
|
15
19
|
}
|
|
16
20
|
|
|
17
21
|
interface KeyboardKeyLabels {
|
|
@@ -50,6 +54,11 @@ export const keyboardShortcutLabels = {
|
|
|
50
54
|
hideUiDescription: "立即按下以快速隱藏面板並專注於工作",
|
|
51
55
|
undoDescription: "逐步回復最近的設計變更",
|
|
52
56
|
redoDescription: "還原剛才復原的設計變更",
|
|
57
|
+
nudgeAmountTitle: "微調距離",
|
|
58
|
+
nudgeAmountSmall: "小距離",
|
|
59
|
+
nudgeAmountBig: "大距離",
|
|
60
|
+
nudgeAmountDescription:
|
|
61
|
+
"方向鍵以小距離移動,Shift+方向鍵以大距離移動。在自動版面配置框架內,方向鍵改為調整圖層順序。",
|
|
53
62
|
},
|
|
54
63
|
"zh-CN": {
|
|
55
64
|
title: "键盘快捷键",
|
|
@@ -63,6 +72,11 @@ export const keyboardShortcutLabels = {
|
|
|
63
72
|
hideUiDescription: "立即按下以快速隐藏面板并专注于工作",
|
|
64
73
|
undoDescription: "逐步撤销最近的设计更改",
|
|
65
74
|
redoDescription: "恢复刚刚撤销的设计更改",
|
|
75
|
+
nudgeAmountTitle: "微调距离",
|
|
76
|
+
nudgeAmountSmall: "小距离",
|
|
77
|
+
nudgeAmountBig: "大距离",
|
|
78
|
+
nudgeAmountDescription:
|
|
79
|
+
"方向键以小距离移动,Shift+方向键以大距离移动。在自动布局框架内,方向键改为调整图层顺序。",
|
|
66
80
|
},
|
|
67
81
|
"es-ES": {
|
|
68
82
|
title: "Atajos de teclado",
|
|
@@ -77,6 +91,11 @@ export const keyboardShortcutLabels = {
|
|
|
77
91
|
"Púlsalo ahora para ocultar los paneles y concentrarte en tu trabajo",
|
|
78
92
|
undoDescription: "Retrocede por el cambio de diseño más reciente",
|
|
79
93
|
redoDescription: "Restaura el cambio de diseño que acabas de deshacer",
|
|
94
|
+
nudgeAmountTitle: "Cantidad de desplazamiento",
|
|
95
|
+
nudgeAmountSmall: "Desplazamiento pequeño",
|
|
96
|
+
nudgeAmountBig: "Desplazamiento grande",
|
|
97
|
+
nudgeAmountDescription:
|
|
98
|
+
"Las flechas mueven la cantidad pequeña y Mayús+flecha la grande. Dentro de un marco de autodiseño, las flechas reordenan la capa.",
|
|
80
99
|
},
|
|
81
100
|
"fr-FR": {
|
|
82
101
|
title: "Raccourcis clavier",
|
|
@@ -92,6 +111,11 @@ export const keyboardShortcutLabels = {
|
|
|
92
111
|
undoDescription: "Revenez sur votre dernière modification de design",
|
|
93
112
|
redoDescription:
|
|
94
113
|
"Restaurez la modification de design que vous venez d’annuler",
|
|
114
|
+
nudgeAmountTitle: "Valeur de déplacement",
|
|
115
|
+
nudgeAmountSmall: "Petit déplacement",
|
|
116
|
+
nudgeAmountBig: "Grand déplacement",
|
|
117
|
+
nudgeAmountDescription:
|
|
118
|
+
"Les flèches déplacent de la petite valeur et Maj+flèche de la grande. Dans un cadre en disposition automatique, les flèches réorganisent le calque.",
|
|
95
119
|
},
|
|
96
120
|
"de-DE": {
|
|
97
121
|
title: "Tastenkürzel",
|
|
@@ -107,6 +131,11 @@ export const keyboardShortcutLabels = {
|
|
|
107
131
|
undoDescription: "Mache die letzte Designänderung rückgängig",
|
|
108
132
|
redoDescription:
|
|
109
133
|
"Stelle die soeben rückgängig gemachte Designänderung wieder her",
|
|
134
|
+
nudgeAmountTitle: "Verschiebeschrittweite",
|
|
135
|
+
nudgeAmountSmall: "Kleiner Schritt",
|
|
136
|
+
nudgeAmountBig: "Großer Schritt",
|
|
137
|
+
nudgeAmountDescription:
|
|
138
|
+
"Pfeiltasten verschieben um den kleinen Wert, Umschalt+Pfeil um den großen. In einem Auto-Layout-Rahmen ordnen die Pfeiltasten die Ebene neu an.",
|
|
110
139
|
},
|
|
111
140
|
"ja-JP": {
|
|
112
141
|
title: "キーボードショートカット",
|
|
@@ -120,6 +149,11 @@ export const keyboardShortcutLabels = {
|
|
|
120
149
|
hideUiDescription: "今すぐ押してパネルを隠し、作業に集中できます",
|
|
121
150
|
undoDescription: "直前のデザイン変更を元に戻します",
|
|
122
151
|
redoDescription: "元に戻したデザイン変更を復元します",
|
|
152
|
+
nudgeAmountTitle: "移動量",
|
|
153
|
+
nudgeAmountSmall: "小さい移動量",
|
|
154
|
+
nudgeAmountBig: "大きい移動量",
|
|
155
|
+
nudgeAmountDescription:
|
|
156
|
+
"矢印キーは小さい移動量、Shift+矢印キーは大きい移動量で移動します。オートレイアウトフレーム内では、矢印キーはレイヤーの順序を変更します。",
|
|
123
157
|
},
|
|
124
158
|
"ko-KR": {
|
|
125
159
|
title: "키보드 단축키",
|
|
@@ -133,6 +167,11 @@ export const keyboardShortcutLabels = {
|
|
|
133
167
|
hideUiDescription: "지금 눌러 패널을 빠르게 숨기고 작업에 집중하세요",
|
|
134
168
|
undoDescription: "가장 최근 디자인 변경을 되돌립니다",
|
|
135
169
|
redoDescription: "방금 실행 취소한 디자인 변경을 복원합니다",
|
|
170
|
+
nudgeAmountTitle: "이동 간격",
|
|
171
|
+
nudgeAmountSmall: "작은 이동",
|
|
172
|
+
nudgeAmountBig: "큰 이동",
|
|
173
|
+
nudgeAmountDescription:
|
|
174
|
+
"화살표 키는 작은 간격으로, Shift+화살표 키는 큰 간격으로 이동합니다. 오토 레이아웃 프레임 안에서는 화살표 키가 레이어 순서를 변경합니다.",
|
|
136
175
|
},
|
|
137
176
|
"pt-BR": {
|
|
138
177
|
title: "Atalhos de teclado",
|
|
@@ -148,6 +187,11 @@ export const keyboardShortcutLabels = {
|
|
|
148
187
|
undoDescription: "Desfaça a alteração de design mais recente",
|
|
149
188
|
redoDescription:
|
|
150
189
|
"Restaure a alteração de design que você acabou de desfazer",
|
|
190
|
+
nudgeAmountTitle: "Distância de deslocamento",
|
|
191
|
+
nudgeAmountSmall: "Deslocamento pequeno",
|
|
192
|
+
nudgeAmountBig: "Deslocamento grande",
|
|
193
|
+
nudgeAmountDescription:
|
|
194
|
+
"As setas movem pela distância pequena e Shift+seta pela grande. Dentro de um frame com layout automático, as setas reordenam a camada.",
|
|
151
195
|
},
|
|
152
196
|
"hi-IN": {
|
|
153
197
|
title: "कीबोर्ड शॉर्टकट",
|
|
@@ -161,6 +205,11 @@ export const keyboardShortcutLabels = {
|
|
|
161
205
|
hideUiDescription: "पैनल तुरंत छिपाकर अपने काम पर ध्यान देने के लिए इसे अभी दबाएँ",
|
|
162
206
|
undoDescription: "सबसे हाल के डिज़ाइन बदलाव को वापस करें",
|
|
163
207
|
redoDescription: "अभी वापस किए गए डिज़ाइन बदलाव को फिर से लागू करें",
|
|
208
|
+
nudgeAmountTitle: "नज की दूरी",
|
|
209
|
+
nudgeAmountSmall: "छोटी नज",
|
|
210
|
+
nudgeAmountBig: "बड़ी नज",
|
|
211
|
+
nudgeAmountDescription:
|
|
212
|
+
"ऐरो कुंजियाँ छोटी दूरी से और Shift+ऐरो बड़ी दूरी से ले जाती हैं। ऑटो लेआउट फ़्रेम के अंदर ऐरो कुंजियाँ लेयर का क्रम बदलती हैं।",
|
|
164
213
|
},
|
|
165
214
|
"ar-SA": {
|
|
166
215
|
title: "اختصارات لوحة المفاتيح",
|
|
@@ -174,6 +223,11 @@ export const keyboardShortcutLabels = {
|
|
|
174
223
|
hideUiDescription: "اضغطه الآن لإخفاء اللوحات بسرعة والتركيز على عملك",
|
|
175
224
|
undoDescription: "تراجع عن أحدث تغيير في التصميم",
|
|
176
225
|
redoDescription: "استعد تغيير التصميم الذي تراجعت عنه للتو",
|
|
226
|
+
nudgeAmountTitle: "مقدار الإزاحة",
|
|
227
|
+
nudgeAmountSmall: "إزاحة صغيرة",
|
|
228
|
+
nudgeAmountBig: "إزاحة كبيرة",
|
|
229
|
+
nudgeAmountDescription:
|
|
230
|
+
"تحرّك مفاتيح الأسهم بالمقدار الصغير، وShift+سهم بالمقدار الكبير. داخل إطار التخطيط التلقائي، تعيد مفاتيح الأسهم ترتيب الطبقة.",
|
|
177
231
|
},
|
|
178
232
|
} satisfies Record<Exclude<LocaleCode, "en-US">, KeyboardShortcutLabels>;
|
|
179
233
|
|
|
@@ -483,6 +537,13 @@ export function attachLocalizedKeyboardShortcuts<
|
|
|
483
537
|
close: `${d.close}: ${labels.title}`,
|
|
484
538
|
codeContext: edit.sections.codeConfidence,
|
|
485
539
|
screenContext: layers.screens,
|
|
540
|
+
nudgeAmount: {
|
|
541
|
+
title: labels.nudgeAmountTitle,
|
|
542
|
+
small: labels.nudgeAmountSmall,
|
|
543
|
+
big: labels.nudgeAmountBig,
|
|
544
|
+
unit: "px",
|
|
545
|
+
description: labels.nudgeAmountDescription,
|
|
546
|
+
},
|
|
486
547
|
keys: keyLabels,
|
|
487
548
|
descriptions: {
|
|
488
549
|
toggleUi: labels.hideUiDescription,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { DEFAULT_NUDGE_AMOUNTS, type NudgeAmounts } from "./nudge-intent";
|
|
2
|
+
|
|
3
|
+
export const DESIGN_EDITOR_PREFERENCES_STORAGE_KEY =
|
|
4
|
+
"agent-native.design.editor-preferences";
|
|
5
|
+
|
|
6
|
+
export interface DesignEditorPreferences {
|
|
7
|
+
nudge: NudgeAmounts;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_EDITOR_PREFERENCES: DesignEditorPreferences = {
|
|
11
|
+
nudge: DEFAULT_NUDGE_AMOUNTS,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/** Figma allows 1-1000 for both nudge amounts and rejects 0 — a 0 nudge makes
|
|
15
|
+
* the arrow keys look broken rather than doing nothing on purpose. */
|
|
16
|
+
export const MIN_NUDGE_AMOUNT = 1;
|
|
17
|
+
export const MAX_NUDGE_AMOUNT = 1000;
|
|
18
|
+
|
|
19
|
+
export function normalizeNudgeAmount(value: unknown, fallback: number): number {
|
|
20
|
+
const parsed = typeof value === "string" ? Number(value) : value;
|
|
21
|
+
if (typeof parsed !== "number" || !Number.isFinite(parsed)) return fallback;
|
|
22
|
+
return Math.min(
|
|
23
|
+
MAX_NUDGE_AMOUNT,
|
|
24
|
+
Math.max(MIN_NUDGE_AMOUNT, Math.round(parsed)),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ParseEditorPreferencesResult =
|
|
29
|
+
| { status: "absent"; preferences: DesignEditorPreferences }
|
|
30
|
+
| { status: "ok"; preferences: DesignEditorPreferences }
|
|
31
|
+
| {
|
|
32
|
+
status: "invalid";
|
|
33
|
+
preferences: DesignEditorPreferences;
|
|
34
|
+
reason: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Never collapses "nothing stored yet" and "stored value is corrupt" into the
|
|
38
|
+
* same result: the caller must be able to tell a first run from a store it
|
|
39
|
+
* should overwrite rather than keep re-reading. */
|
|
40
|
+
export function parseEditorPreferences(
|
|
41
|
+
raw: string | null | undefined,
|
|
42
|
+
): ParseEditorPreferencesResult {
|
|
43
|
+
if (raw === null || raw === undefined || raw === "") {
|
|
44
|
+
return { status: "absent", preferences: DEFAULT_EDITOR_PREFERENCES };
|
|
45
|
+
}
|
|
46
|
+
let decoded: unknown;
|
|
47
|
+
try {
|
|
48
|
+
decoded = JSON.parse(raw);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
status: "invalid",
|
|
52
|
+
preferences: DEFAULT_EDITOR_PREFERENCES,
|
|
53
|
+
reason: String(error),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (typeof decoded !== "object" || decoded === null) {
|
|
57
|
+
return {
|
|
58
|
+
status: "invalid",
|
|
59
|
+
preferences: DEFAULT_EDITOR_PREFERENCES,
|
|
60
|
+
reason: "expected an object",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const nudge = (decoded as { nudge?: unknown }).nudge;
|
|
64
|
+
if (nudge !== undefined && (typeof nudge !== "object" || nudge === null)) {
|
|
65
|
+
return {
|
|
66
|
+
status: "invalid",
|
|
67
|
+
preferences: DEFAULT_EDITOR_PREFERENCES,
|
|
68
|
+
reason: "expected nudge to be an object",
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const source = (nudge ?? {}) as { small?: unknown; big?: unknown };
|
|
72
|
+
return {
|
|
73
|
+
status: "ok",
|
|
74
|
+
preferences: {
|
|
75
|
+
nudge: {
|
|
76
|
+
small: normalizeNudgeAmount(source.small, DEFAULT_NUDGE_AMOUNTS.small),
|
|
77
|
+
big: normalizeNudgeAmount(source.big, DEFAULT_NUDGE_AMOUNTS.big),
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function serializeEditorPreferences(
|
|
84
|
+
preferences: DesignEditorPreferences,
|
|
85
|
+
): string {
|
|
86
|
+
return JSON.stringify({
|
|
87
|
+
nudge: {
|
|
88
|
+
small: normalizeNudgeAmount(
|
|
89
|
+
preferences.nudge.small,
|
|
90
|
+
DEFAULT_NUDGE_AMOUNTS.small,
|
|
91
|
+
),
|
|
92
|
+
big: normalizeNudgeAmount(
|
|
93
|
+
preferences.nudge.big,
|
|
94
|
+
DEFAULT_NUDGE_AMOUNTS.big,
|
|
95
|
+
),
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
}
|