@agent-native/core 0.90.5 → 0.90.8
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/README.md +34 -7
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/docs/content/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
- package/corpus/core/docs/content/toolkit-agent-ux.mdx +1 -1
- package/corpus/core/docs/content/toolkit-app-adapters.mdx +1 -1
- package/corpus/core/docs/content/toolkit-collaboration-ui.mdx +1 -1
- package/corpus/core/docs/content/toolkit-collaboration.mdx +1 -1
- package/corpus/core/docs/content/toolkit-command-navigation.mdx +2 -2
- package/corpus/core/docs/content/toolkit-comments-review.mdx +1 -1
- package/corpus/core/docs/content/toolkit-history.mdx +1 -1
- package/corpus/core/docs/content/toolkit-observability.mdx +1 -1
- package/corpus/core/docs/content/toolkit-org-team.mdx +1 -1
- package/corpus/core/docs/content/toolkit-resources.mdx +1 -1
- package/corpus/core/docs/content/toolkit-settings.mdx +1 -1
- package/corpus/core/docs/content/toolkit-setup-connections.mdx +1 -1
- package/corpus/core/docs/content/toolkit-sharing-ui.mdx +1 -1
- package/corpus/core/docs/content/toolkit-sharing.mdx +1 -1
- package/corpus/core/docs/content/toolkit-shell-hooks.mdx +1 -1
- package/corpus/core/docs/content/toolkit-ui.mdx +1 -1
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/use-db-sync.ts +99 -1
- package/corpus/core/src/collab/client.ts +49 -4
- package/corpus/core/src/deploy/build.ts +156 -0
- package/corpus/core/src/server/core-routes-plugin.ts +137 -8
- package/corpus/templates/clips/changelog/2026-07-07-faster-local-whisper-dictation-start.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +311 -31
- package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +2 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +9 -0
- package/corpus/templates/design/actions/read-source-file.ts +28 -0
- package/corpus/templates/design/actions/update-file.ts +76 -7
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +181 -1
- package/corpus/templates/design/app/components/design/EditPanel.tsx +4 -0
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +387 -25
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1110 -88
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +368 -16
- package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +24 -0
- package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +27 -6
- package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +18 -0
- package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +5 -4
- package/corpus/templates/design/app/components/design/multi-screen/handle-hit-zones.ts +124 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +23 -0
- package/corpus/templates/design/app/i18n/zh-TW.ts +1 -0
- package/corpus/templates/design/app/i18n-data.ts +19 -0
- package/corpus/templates/design/changelog/2026-07-07-dragging-a-free-floating-element-into-a-layout-column-or-row.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-dragging-a-repeated-list-item-like-a-to-do-card-generated-fr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-dragging-an-element-onto-a-button-or-chip-s-middle-no-longer.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-elements-dropped-into-another-board-shape-now-stay-exactly-w.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-drag-reordering-elements-within-a-screen-no-longer-sil.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-dragging-an-element-from-a-screen-onto-the-canvas-now-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-dragging-elements-between-screens-into-ai-generated-co.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-moving-elements-between-screens-no-longer-bakes-intern.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-the-cross-screen-drop-indicator-now-points-at-the-corr.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-fixed-the-zoom-menu-no-longer-leaves-the-canvas-unresponsive.md +6 -0
- package/corpus/templates/design/changelog/2026-07-07-text-drawn-on-the-canvas-is-now-readable-white-instead-of-in.md +6 -0
- package/corpus/templates/design/server/source-workspace.ts +27 -3
- package/corpus/templates/design/shared/board-file.ts +359 -0
- package/corpus/templates/design/shared/breakpoint-media.ts +21 -3
- package/corpus/templates/design/shared/code-layer.ts +145 -10
- package/dist/client/use-db-sync.d.ts +18 -0
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +88 -1
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +43 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/deploy/build.d.ts +2 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +96 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/server/core-routes-plugin.d.ts +10 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +103 -9
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/docs/content/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/de-DE/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/es-ES/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +1 -1
- package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +1 -1
- package/docs/content/toolkit-agent-ux.mdx +1 -1
- package/docs/content/toolkit-app-adapters.mdx +1 -1
- package/docs/content/toolkit-collaboration-ui.mdx +1 -1
- package/docs/content/toolkit-collaboration.mdx +1 -1
- package/docs/content/toolkit-command-navigation.mdx +2 -2
- package/docs/content/toolkit-comments-review.mdx +1 -1
- package/docs/content/toolkit-history.mdx +1 -1
- package/docs/content/toolkit-observability.mdx +1 -1
- package/docs/content/toolkit-org-team.mdx +1 -1
- package/docs/content/toolkit-resources.mdx +1 -1
- package/docs/content/toolkit-settings.mdx +1 -1
- package/docs/content/toolkit-setup-connections.mdx +1 -1
- package/docs/content/toolkit-sharing-ui.mdx +1 -1
- package/docs/content/toolkit-sharing.mdx +1 -1
- package/docs/content/toolkit-shell-hooks.mdx +1 -1
- package/docs/content/toolkit-ui.mdx +1 -1
- package/package.json +1 -1
|
@@ -13,7 +13,7 @@ that operators use to understand what happened.
|
|
|
13
13
|
<Screen
|
|
14
14
|
surface="desktop"
|
|
15
15
|
html={
|
|
16
|
-
"<div style='min-height:
|
|
16
|
+
"<div style='min-height:540px;box-sizing:border-box;padding:24px;display:grid;grid-template-columns:1fr 300px;gap:16px'><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><h2 style='margin:0'>Observability</h2><div style='display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:10px'><div class='wf-box'><small class='wf-muted'>Runs</small><br/><strong style='font-size:20px'>482</strong></div><div class='wf-box'><small class='wf-muted'>Spend</small><br/><strong style='font-size:20px'>$42</strong></div><div class='wf-box'><small class='wf-muted'>Errors</small><br/><strong style='font-size:20px'>3</strong></div></div><div class='wf-box' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Runs per day</small><div style='display:flex;align-items:flex-end;gap:6px;height:150px'><div style='flex:1;height:40%;background:var(--wf-accent-soft);border-radius:3px'></div><div style='flex:1;height:60%;background:var(--wf-accent-soft);border-radius:3px'></div><div style='flex:1;height:50%;background:var(--wf-accent-soft);border-radius:3px'></div><div style='flex:1;height:75%;background:var(--wf-accent-soft);border-radius:3px'></div><div style='flex:1;height:65%;background:var(--wf-accent-soft);border-radius:3px'></div><div style='flex:1;height:90%;background:var(--wf-accent);border-radius:3px'></div><div style='flex:1;height:80%;background:var(--wf-accent-soft);border-radius:3px'></div></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><strong>Diagnostics</strong><div class='wf-box' style='display:flex;align-items:center;gap:8px'><span data-icon='x'></span><div style='flex:1'>Run failed: missing key</div></div><div class='wf-box' style='display:flex;align-items:center;gap:8px'><span data-icon='bell'></span><div style='flex:1'>Feedback: thumbs down</div></div><div class='wf-box' style='display:flex;align-items:center;gap:8px'><span data-icon='calendar'></span><div style='flex:1'>Slow tool: provider-api</div></div><div style='flex:1'></div><button class='primary'>Open run</button></aside></div>"
|
|
17
17
|
}
|
|
18
18
|
/>
|
|
19
19
|
</WireframeBlock>
|
|
@@ -14,7 +14,7 @@ connections, service tokens, and app switching.
|
|
|
14
14
|
<Screen
|
|
15
15
|
surface="desktop"
|
|
16
16
|
html={
|
|
17
|
-
"<div style='min-height:540px;box-sizing:border-box;padding:
|
|
17
|
+
"<div style='min-height:540px;box-sizing:border-box;padding:26px;display:grid;grid-template-columns:210px 1fr;gap:18px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Organization</small><div class='wf-box' style='display:flex;align-items:center;gap:8px;padding:8px 10px'><div style='width:24px;height:24px;border-radius:7px;background:var(--wf-accent-soft);display:flex;align-items:center;justify-content:center;font-size:11px;flex:none'>A</div><strong style='flex:1'>Acme</strong><span data-icon='chevronDown'></span></div><div style='display:flex;flex-direction:column;gap:4px;margin-top:4px'><div style='padding:6px 9px;border-radius:8px;background:var(--wf-accent-soft)'>Analytics</div><div style='padding:6px 9px;border-radius:8px'>Content</div><div style='padding:6px 9px;border-radius:8px'>Slides</div></div></aside><main class='wf-card' style='display:flex;flex-direction:column;gap:12px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Team</h2><span class='wf-pill'>8 members</span><div style='flex:1'></div><button class='primary'><span data-icon='plus'></span> Invite</button></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Steve Ray</strong><br/><small class='wf-muted'>steve@acme.com</small></div><span class='wf-pill'>Owner</span></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Maya Chen</strong><br/><small class='wf-muted'>maya@acme.com</small></div><span class='wf-pill'>Admin <span data-icon='chevronDown'></span></span></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Kai Wong</strong><br/><small class='wf-muted'>kai@acme.com</small></div><span class='wf-pill'>Member <span data-icon='chevronDown'></span></span></div><hr/><div style='display:flex;align-items:center;gap:8px'><span data-icon='mail'></span><div style='flex:1'>Anyone with an acme.com email can join</div></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='user'></span><div style='flex:1'>Pending invites</div><span class='wf-pill'>3</span></div></main></div>"
|
|
18
18
|
}
|
|
19
19
|
/>
|
|
20
20
|
</WireframeBlock>
|
|
@@ -14,7 +14,7 @@ for repo-backed content.
|
|
|
14
14
|
<Screen
|
|
15
15
|
surface="desktop"
|
|
16
16
|
html={
|
|
17
|
-
"<div style='min-height:
|
|
17
|
+
"<div style='min-height:540px;box-sizing:border-box;padding:24px;display:grid;grid-template-columns:230px 1fr 220px;gap:14px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:4px'><div style='display:flex;align-items:center;gap:8px;margin-bottom:6px'><strong style='flex:1'>Resources</strong><span data-icon='search'></span><span data-icon='plus'></span></div><div style='display:flex;align-items:center;gap:8px;padding:6px 8px'><span data-icon='chevronDown'></span> docs</div><div style='display:flex;align-items:center;gap:8px;padding:6px 8px 6px 22px;border-radius:8px;background:var(--wf-accent-soft)'><span data-icon='edit'></span> Q3 forecast.md</div><div style='display:flex;align-items:center;gap:8px;padding:6px 8px 6px 22px'><span data-icon='edit'></span> roadmap.md</div><div style='display:flex;align-items:center;gap:8px;padding:6px 8px'><span data-icon='chevronRight'></span> blog</div><div style='display:flex;align-items:center;gap:8px;padding:6px 8px'><span data-icon='chevronRight'></span> assets</div></aside><main class='wf-card' style='display:flex;flex-direction:column;gap:12px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Q3 forecast.md</h2><div style='flex:1'></div><button class='primary'>Save</button></div><div style='display:flex;flex-direction:column;gap:8px'><div style='height:11px;width:60%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:92%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:85%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:95%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:70%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:88%;background:var(--wf-line);border-radius:4px'></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><strong>Attachments</strong><div style='border:1.6px dashed var(--wf-line);border-radius:var(--wf-radius);padding:16px;text-align:center'><span data-icon='plus'></span><br/><small class='wf-muted'>Drop files</small></div><div class='wf-box' style='display:flex;align-items:center;gap:8px'><span data-icon='search'></span><span class='wf-muted'>Search resources</span></div><div style='flex:1'></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='check'></span><small>Local file mode on</small></div></aside></div>"
|
|
18
18
|
}
|
|
19
19
|
/>
|
|
20
20
|
</WireframeBlock>
|
|
@@ -14,7 +14,7 @@ page; framework tabs can be mounted inside it.
|
|
|
14
14
|
<Screen
|
|
15
15
|
surface="desktop"
|
|
16
16
|
html={
|
|
17
|
-
"<div style='min-height:
|
|
17
|
+
"<div style='min-height:520px;box-sizing:border-box;padding:26px;display:grid;grid-template-columns:200px 1fr;gap:18px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:6px'><strong style='margin-bottom:4px'>Settings</strong><div style='padding:7px 9px;border-radius:8px'>General</div><div style='padding:7px 9px;border-radius:8px;background:var(--wf-accent-soft);display:flex;align-items:center;gap:8px'><span data-icon='settings'></span> AI and models</div><div style='padding:7px 9px;border-radius:8px'>Connections</div><div style='padding:7px 9px;border-radius:8px'>Team</div><div style='padding:7px 9px;border-radius:8px'>Developer</div></aside><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><h2 style='margin:0'>AI and models</h2><div style='display:flex;flex-direction:column;gap:6px'><small class='wf-muted'>Default model</small><div class='wf-box' style='display:flex;align-items:center;gap:8px'><div style='flex:1'>GPT-5</div><span data-icon='chevronDown'></span></div></div><div style='display:flex;flex-direction:column;gap:8px'><small class='wf-muted'>Provider keys</small><div class='wf-box' style='display:flex;align-items:center;gap:10px'><span data-icon='check'></span><div style='flex:1'>OpenAI</div><span class='wf-pill'>Connected</span></div><div class='wf-box' style='display:flex;align-items:center;gap:10px'><span data-icon='lock'></span><div style='flex:1'>Anthropic</div><button>Add key</button></div><div class='wf-box' style='display:flex;align-items:center;gap:10px'><span data-icon='check'></span><div style='flex:1'>Builder</div><span class='wf-pill'>Connected</span></div></div><div style='flex:1'></div><div style='display:flex;gap:10px'><button>Open agent settings</button><div style='flex:1'></div><button class='primary'>Save changes</button></div></main></div>"
|
|
18
18
|
}
|
|
19
19
|
/>
|
|
20
20
|
</WireframeBlock>
|
|
@@ -14,7 +14,7 @@ what is missing and link users to the exact fix.
|
|
|
14
14
|
<Screen
|
|
15
15
|
surface="desktop"
|
|
16
16
|
html={
|
|
17
|
-
"<div style='min-height:
|
|
17
|
+
"<div style='min-height:520px;box-sizing:border-box;padding:26px;display:grid;grid-template-columns:1fr 300px;gap:18px'><main class='wf-card' style='display:flex;flex-direction:column;gap:12px'><h2 style='margin:0'>Finish setup</h2><small class='wf-muted'>2 of 4 steps complete</small><div class='wf-box' style='display:flex;align-items:center;gap:10px'><span data-icon='check'></span><div style='flex:1'>Connect Builder</div><span class='wf-pill'>Done</span></div><div class='wf-box' style='display:flex;align-items:center;gap:10px;border-color:var(--wf-accent)'><span data-icon='lock'></span><div style='flex:1'><strong>Add OpenAI key</strong><br/><small class='wf-muted'>Needed for the agent to run</small></div><button class='primary'>Fix</button></div><div class='wf-box' style='display:flex;align-items:center;gap:10px'><span data-icon='plus'></span><div style='flex:1'>Connect Slack</div><span class='wf-muted'>Optional</span></div><div class='wf-box' style='display:flex;align-items:center;gap:10px'><span data-icon='check'></span><div style='flex:1'>Enable GitHub OAuth</div><span class='wf-pill'>Ready</span></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><strong>Provider readiness</strong><div style='display:flex;align-items:center;gap:8px'><span data-icon='check'></span><div style='flex:1'>Builder</div><span class='wf-pill'>Connected</span></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='x'></span><div style='flex:1'>LLM key</div><span class='wf-pill'>Missing</span></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='check'></span><div style='flex:1'>GitHub</div><span class='wf-pill'>OAuth ready</span></div><div style='flex:1'></div><button class='primary'>Open settings</button></aside></div>"
|
|
18
18
|
}
|
|
19
19
|
/>
|
|
20
20
|
</WireframeBlock>
|
|
@@ -13,7 +13,7 @@ mutations; core and app wrappers inject data and callbacks.
|
|
|
13
13
|
<Screen
|
|
14
14
|
surface="desktop"
|
|
15
15
|
html={
|
|
16
|
-
"<div style='min-height:480px;box-sizing:border-box;padding:
|
|
16
|
+
"<div style='min-height:480px;box-sizing:border-box;padding:26px;display:grid;grid-template-columns:1fr 300px;gap:16px'><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Roadmap</h2><span class='wf-pill accent'>Org visible</span><div style='flex:1'></div><button><span data-icon='user'></span> Share</button></div><small class='wf-muted'>Visibility badges</small><div style='display:flex;gap:6px'><span class='wf-pill'><span data-icon='lock'></span> Private</span><span class='wf-pill'>Shared</span><span class='wf-pill accent'>Org</span><span class='wf-pill'>Public</span></div><div style='display:flex;flex-direction:column;gap:8px'><div style='height:11px;width:88%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:72%;background:var(--wf-line);border-radius:4px'></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:10px'><strong>Share</strong><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Maya Chen</strong><br/><small class='wf-muted'>maya@acme.com</small></div><span class='wf-pill'>Editor <span data-icon='chevronDown'></span></span></div><div class='wf-card' style='display:flex;flex-direction:column;gap:2px;padding:6px'><div style='padding:6px 8px;border-radius:6px;background:var(--wf-accent-soft);display:flex;align-items:center;gap:8px'><span data-icon='check'></span> Editor</div><div style='padding:6px 8px'>Viewer</div><div style='padding:6px 8px'>Remove</div></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='lock'></span><small class='wf-muted'>Public links disabled by policy</small></div></aside></div>"
|
|
17
17
|
}
|
|
18
18
|
/>
|
|
19
19
|
</WireframeBlock>
|
|
@@ -14,7 +14,7 @@ links.
|
|
|
14
14
|
<Screen
|
|
15
15
|
surface="desktop"
|
|
16
16
|
html={
|
|
17
|
-
"<div style='min-height:540px;box-sizing:border-box;padding:
|
|
17
|
+
"<div style='min-height:540px;box-sizing:border-box;padding:26px;display:grid;grid-template-columns:1fr 320px;gap:18px'><main class='wf-card' style='display:flex;flex-direction:column;gap:14px'><div style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Q3 Forecast</h2><span class='wf-pill accent'>Org visible</span><div style='flex:1'></div><button><span data-icon='user'></span> Share</button></div><div class='wf-box' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Revenue by month</small><div style='display:flex;align-items:flex-end;gap:10px;height:120px'><div style='flex:1;height:50%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:75%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:60%;background:var(--wf-accent-soft);border-radius:4px'></div><div style='flex:1;height:92%;background:var(--wf-accent);border-radius:4px'></div></div></div><div style='display:flex;flex-direction:column;gap:8px'><div style='height:11px;width:70%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:90%;background:var(--wf-line);border-radius:4px'></div></div></main><aside class='wf-card' style='display:flex;flex-direction:column;gap:12px'><strong>Share Q3 Forecast</strong><div style='display:flex;align-items:center;gap:8px'><div class='wf-box' style='flex:1;display:flex;align-items:center;gap:8px;padding:8px 10px'><span data-icon='mail'></span><span class='wf-muted'>Add people or teams</span></div><button class='primary'>Invite</button></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Steve Ray</strong><br/><small class='wf-muted'>steve@acme.com</small></div><small class='wf-muted'>Owner</small></div><div style='display:flex;align-items:center;gap:10px'><div style='width:28px;height:28px;border-radius:999px;background:var(--wf-accent-soft);flex:none'></div><div style='flex:1;min-width:0'><strong>Maya Chen</strong><br/><small class='wf-muted'>maya@acme.com</small></div><span class='wf-pill'>Editor <span data-icon='chevronDown'></span></span></div><hr/><div style='display:flex;align-items:center;gap:8px'><span data-icon='user'></span><div style='flex:1'>Anyone at Acme</div><span class='wf-pill'>Can view <span data-icon='chevronDown'></span></span></div><div style='display:flex;align-items:center;gap:8px'><span data-icon='lock'></span><div style='flex:1'>Public link</div><span class='wf-muted'>Off</span></div></aside></div>"
|
|
18
18
|
}
|
|
19
19
|
/>
|
|
20
20
|
</WireframeBlock>
|
|
@@ -11,7 +11,7 @@ Toolkit includes small shell helpers and hooks that are useful across apps:
|
|
|
11
11
|
<Screen
|
|
12
12
|
surface="desktop"
|
|
13
13
|
html={
|
|
14
|
-
"<div style='min-height:
|
|
14
|
+
"<div style='min-height:460px;box-sizing:border-box;padding:24px;display:grid;grid-template-columns:200px 1fr;gap:16px'><aside class='wf-card' style='display:flex;flex-direction:column;gap:6px'><strong style='margin-bottom:4px'>Analytics</strong><div style='padding:6px 9px;border-radius:8px;background:var(--wf-accent-soft)'>Dashboards</div><div style='padding:6px 9px;border-radius:8px'>Reports</div><div style='padding:6px 9px;border-radius:8px'>Settings</div></aside><main style='position:relative;display:flex;flex-direction:column;gap:14px'><div class='wf-card' style='display:flex;align-items:center;gap:10px'><h2 style='margin:0'>Dashboards</h2><div style='flex:1'></div><button><span data-icon='send'></span> Export</button><button class='primary'><span data-icon='plus'></span> Create</button></div><div class='wf-card' style='flex:1;display:flex;flex-direction:column;gap:8px'><div style='height:11px;width:70%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:90%;background:var(--wf-line);border-radius:4px'></div><div style='height:11px;width:80%;background:var(--wf-line);border-radius:4px'></div></div><div class='wf-card' style='display:flex;align-items:center;gap:8px;border-color:var(--wf-accent);position:absolute;right:0;bottom:0;width:230px'><span data-icon='check'></span><div style='flex:1'>Saved changes</div><span data-icon='x'></span></div></main></div>"
|
|
15
15
|
}
|
|
16
16
|
/>
|
|
17
17
|
</WireframeBlock>
|
|
@@ -12,7 +12,7 @@ shared shadcn-style building blocks used by templates and reusable kits.
|
|
|
12
12
|
<Screen
|
|
13
13
|
surface="desktop"
|
|
14
14
|
html={
|
|
15
|
-
"<div style='min-height:
|
|
15
|
+
"<div style='min-height:460px;box-sizing:border-box;padding:26px;display:flex;flex-direction:column;gap:16px'><h2 style='margin:0'>UI primitives</h2><div style='display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px'><div class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Buttons</small><div style='display:flex;gap:8px'><button>Cancel</button><button class='primary'>Save</button></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Input</small><div class='wf-box' style='display:flex;align-items:center;gap:8px'><span data-icon='search'></span><span class='wf-muted'>Search...</span></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Select</small><div class='wf-box' style='display:flex;align-items:center;gap:8px'><div style='flex:1'>Editor</div><span data-icon='chevronDown'></span></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:10px'><small class='wf-muted'>Badges</small><div style='display:flex;gap:6px'><span class='wf-pill accent'>Active</span><span class='wf-pill'>Draft</span></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:8px'><small class='wf-muted'>Dialog</small><strong>Delete file?</strong><small class='wf-muted'>This cannot be undone.</small><div style='display:flex;gap:8px;justify-content:flex-end'><button>Cancel</button><button class='primary'>Delete</button></div></div><div class='wf-card' style='display:flex;flex-direction:column;gap:6px'><small class='wf-muted'>Menu</small><div style='display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px;background:var(--wf-accent-soft)'><span data-icon='edit'></span> Rename</div><div style='display:flex;align-items:center;gap:8px;padding:6px 8px'><span data-icon='user'></span> Share</div></div></div></div>"
|
|
16
16
|
}
|
|
17
17
|
/>
|
|
18
18
|
</WireframeBlock>
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.90.
|
|
3
|
+
"version": "0.90.8",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -22,6 +22,13 @@ interface QueryClient {
|
|
|
22
22
|
const POLL_ABORT_MIN_MS = 10_000;
|
|
23
23
|
const SSE_FALLBACK_INTERVAL_MS = 15_000;
|
|
24
24
|
const POLL_AUTH_FAILURE_COOLDOWN_MS = 60_000;
|
|
25
|
+
/**
|
|
26
|
+
* Max cadence for SSE/poll-driven query invalidation in `useDbSync`. Events
|
|
27
|
+
* that arrive within this window of the first one in a burst are merged into
|
|
28
|
+
* a single `invalidateForEvents` call instead of one call per event — see the
|
|
29
|
+
* `queueInvalidateBatch` comment at the call site.
|
|
30
|
+
*/
|
|
31
|
+
const INVALIDATE_COALESCE_MS = 250;
|
|
25
32
|
|
|
26
33
|
class HttpStatusError extends Error {
|
|
27
34
|
status: number;
|
|
@@ -94,6 +101,46 @@ function isAuthFailure(error: unknown): boolean {
|
|
|
94
101
|
);
|
|
95
102
|
}
|
|
96
103
|
|
|
104
|
+
/**
|
|
105
|
+
* App-state keys that drive immediate UI navigation/interaction and must
|
|
106
|
+
* never sit behind the invalidation coalesce window (see
|
|
107
|
+
* `isInteractionCriticalSyncEvent`).
|
|
108
|
+
*/
|
|
109
|
+
const INTERACTION_CRITICAL_APP_STATE_KEYS = [
|
|
110
|
+
"navigate",
|
|
111
|
+
"show-questions",
|
|
112
|
+
"__set_url__",
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* True for sync events that drive immediate, agent-initiated UI navigation
|
|
117
|
+
* or interaction rather than passive data invalidation — app-state writes in
|
|
118
|
+
* general (they back `["app-state"]` queries directly), and specifically the
|
|
119
|
+
* `navigate` / `show-questions` / `__set_url__` app-state keys that
|
|
120
|
+
* `invalidateForEvents` special-cases into their own query keys below.
|
|
121
|
+
*
|
|
122
|
+
* `useDbSync` batches ordinary invalidation-driving events (action/collab/db
|
|
123
|
+
* change events) into one flush per `INVALIDATE_COALESCE_MS` so a chatty doc
|
|
124
|
+
* doesn't refetch on every keystroke. That trade-off is wrong for these
|
|
125
|
+
* events: agent-driven navigation, `set-url`, and guided-questions prompts
|
|
126
|
+
* must land as close to instantly as possible, so any batch containing one
|
|
127
|
+
* of these bypasses the coalesce window and flushes immediately instead.
|
|
128
|
+
*
|
|
129
|
+
* Exported as a small pure predicate so this classification is unit-testable
|
|
130
|
+
* independent of the transport/timer plumbing around it.
|
|
131
|
+
*/
|
|
132
|
+
export function isInteractionCriticalSyncEvent(event: SyncEvent): boolean {
|
|
133
|
+
return (
|
|
134
|
+
event.source === "app-state" &&
|
|
135
|
+
(event.key === "*" ||
|
|
136
|
+
INTERACTION_CRITICAL_APP_STATE_KEYS.some(
|
|
137
|
+
(key) =>
|
|
138
|
+
event.key === key ||
|
|
139
|
+
(typeof event.key === "string" && event.key.startsWith(`${key}:`)),
|
|
140
|
+
))
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
97
144
|
async function fetchPollJson<T>(
|
|
98
145
|
pollUrl: string,
|
|
99
146
|
since: number,
|
|
@@ -615,6 +662,51 @@ export function useDbSync(
|
|
|
615
662
|
// processed by THIS subscriber so stale poll re-deliveries are ignored.
|
|
616
663
|
let subscriberVersion = 0;
|
|
617
664
|
|
|
665
|
+
// Coalesce bursts of SSE-driven invalidation into at most one flush per
|
|
666
|
+
// INVALIDATE_COALESCE_MS. A chatty doc (many small agent edits, several
|
|
667
|
+
// peers editing at once) can otherwise deliver a handful of `action`/
|
|
668
|
+
// `collab` events within a few hundred ms, each independently calling
|
|
669
|
+
// `queryClient.invalidateQueries` and firing `onEvent` — every one of
|
|
670
|
+
// those touches whatever query subscribers are mounted (e.g. a
|
|
671
|
+
// full-page editor) even though the end state only needs to be
|
|
672
|
+
// refreshed once. Version bookkeeping stays synchronous (below) so
|
|
673
|
+
// freshness filtering for the NEXT batch is unaffected by the delay.
|
|
674
|
+
//
|
|
675
|
+
// This coalesce window is wrong for interaction-critical events (agent
|
|
676
|
+
// navigation, `set-url`, guided questions — see
|
|
677
|
+
// `isInteractionCriticalSyncEvent`): those must reach the UI immediately,
|
|
678
|
+
// not up to INVALIDATE_COALESCE_MS late. So a fresh batch containing one
|
|
679
|
+
// of those flushes synchronously (queued + new events together,
|
|
680
|
+
// canceling any pending timer) instead of joining the coalesce window.
|
|
681
|
+
// Pure invalidation bursts with no interaction-critical members keep the
|
|
682
|
+
// coalesced behavior.
|
|
683
|
+
let pendingInvalidateEvents: SyncEvent[] = [];
|
|
684
|
+
let invalidateTimer: ReturnType<typeof setTimeout> | null = null;
|
|
685
|
+
|
|
686
|
+
function flushInvalidateBatch() {
|
|
687
|
+
if (invalidateTimer) {
|
|
688
|
+
clearTimeout(invalidateTimer);
|
|
689
|
+
invalidateTimer = null;
|
|
690
|
+
}
|
|
691
|
+
if (pendingInvalidateEvents.length === 0) return;
|
|
692
|
+
const batch = pendingInvalidateEvents;
|
|
693
|
+
pendingInvalidateEvents = [];
|
|
694
|
+
invalidateForEvents(batch);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function queueInvalidateBatch(events: SyncEvent[]) {
|
|
698
|
+
pendingInvalidateEvents.push(...events);
|
|
699
|
+
if (events.some(isInteractionCriticalSyncEvent)) {
|
|
700
|
+
flushInvalidateBatch();
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
if (invalidateTimer) return;
|
|
704
|
+
invalidateTimer = setTimeout(
|
|
705
|
+
flushInvalidateBatch,
|
|
706
|
+
INVALIDATE_COALESCE_MS,
|
|
707
|
+
);
|
|
708
|
+
}
|
|
709
|
+
|
|
618
710
|
function hasAppStateEvent(events: SyncEvent[], key: string): boolean {
|
|
619
711
|
return events.some(
|
|
620
712
|
(event) =>
|
|
@@ -722,7 +814,7 @@ export function useDbSync(
|
|
|
722
814
|
});
|
|
723
815
|
|
|
724
816
|
if (freshEvents.length > 0) {
|
|
725
|
-
|
|
817
|
+
queueInvalidateBatch(freshEvents);
|
|
726
818
|
}
|
|
727
819
|
|
|
728
820
|
const maxEventVersion = freshEvents.reduce(
|
|
@@ -746,6 +838,12 @@ export function useDbSync(
|
|
|
746
838
|
});
|
|
747
839
|
|
|
748
840
|
return () => {
|
|
841
|
+
if (invalidateTimer) {
|
|
842
|
+
clearTimeout(invalidateTimer);
|
|
843
|
+
// Flush synchronously on unmount so a pending batch isn't silently
|
|
844
|
+
// dropped (e.g. a route change right after an agent edit lands).
|
|
845
|
+
flushInvalidateBatch();
|
|
846
|
+
}
|
|
749
847
|
transport.remove(id);
|
|
750
848
|
// If the registry still holds this transport, and the transport is now
|
|
751
849
|
// empty, evict it so the next mount gets a fresh instance rather than a
|
|
@@ -94,6 +94,31 @@ function isDocumentHidden(): boolean {
|
|
|
94
94
|
);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Content equality for deduped active-user lists. `dedupeCollabUsersByEmail`
|
|
99
|
+
* always allocates a fresh array (and fresh per-user objects), so callers
|
|
100
|
+
* that only want to know "did the actual user set change" need this instead
|
|
101
|
+
* of an identity check. Order-sensitive: the dedupe helper iterates awareness
|
|
102
|
+
* states in a stable Map insertion order, so a reordering here does reflect
|
|
103
|
+
* a real change (a client joined/left and re-joined).
|
|
104
|
+
*/
|
|
105
|
+
function collabUsersEqual(a: CollabUser[], b: CollabUser[]): boolean {
|
|
106
|
+
if (a === b) return true;
|
|
107
|
+
if (a.length !== b.length) return false;
|
|
108
|
+
for (let i = 0; i < a.length; i++) {
|
|
109
|
+
const left = a[i]!;
|
|
110
|
+
const right = b[i]!;
|
|
111
|
+
if (
|
|
112
|
+
left.email !== right.email ||
|
|
113
|
+
left.name !== right.name ||
|
|
114
|
+
left.color !== right.color
|
|
115
|
+
) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
|
|
97
122
|
/**
|
|
98
123
|
* Leader election for applying authoritative external snapshots into a shared
|
|
99
124
|
* collaborative document.
|
|
@@ -566,10 +591,30 @@ class CollabDocConnection {
|
|
|
566
591
|
}
|
|
567
592
|
}
|
|
568
593
|
});
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
594
|
+
const nextActiveUsers = dedupeCollabUsersByEmail(users);
|
|
595
|
+
// Awareness "change" fires for every remote broadcast — including cursor
|
|
596
|
+
// jiggles, re-published-but-unchanged heartbeat state, and edits to
|
|
597
|
+
// presence fields (recentEdits, selection) that don't affect who's
|
|
598
|
+
// active. dedupeCollabUsersByEmail always returns a fresh array, so
|
|
599
|
+
// without this comparison every one of those events would hand
|
|
600
|
+
// `activeUsers` a new identity and force every consumer (e.g. a
|
|
601
|
+
// full-page editor keying a useMemo/effect off it) to re-render even
|
|
602
|
+
// though the actual user list is unchanged. Reuse the previous
|
|
603
|
+
// reference when the deduped content is the same, matching the
|
|
604
|
+
// stable-ref discipline `usePresence`'s shallowEqualOthers already
|
|
605
|
+
// applies to `others`.
|
|
606
|
+
const activeUsers = collabUsersEqual(
|
|
607
|
+
this.snapshot.activeUsers,
|
|
608
|
+
nextActiveUsers,
|
|
609
|
+
)
|
|
610
|
+
? this.snapshot.activeUsers
|
|
611
|
+
: nextActiveUsers;
|
|
612
|
+
if (
|
|
613
|
+
activeUsers !== this.snapshot.activeUsers ||
|
|
614
|
+
hasAgent !== this.snapshot.agentPresent
|
|
615
|
+
) {
|
|
616
|
+
this.setSnapshot({ activeUsers, agentPresent: hasAgent });
|
|
617
|
+
}
|
|
573
618
|
|
|
574
619
|
// Fast awareness push: whenever awareness changes (e.g. cursor moves,
|
|
575
620
|
// setPresence() calls), schedule a throttled POST so peers receive updates
|
|
@@ -2427,6 +2427,157 @@ export const config = {
|
|
|
2427
2427
|
);
|
|
2428
2428
|
}
|
|
2429
2429
|
|
|
2430
|
+
export function assertSingleTemplateNetlifyBuildOutput(
|
|
2431
|
+
projectCwd: string,
|
|
2432
|
+
): void {
|
|
2433
|
+
const failures: string[] = [];
|
|
2434
|
+
const publishDir = path.join(projectCwd, "dist");
|
|
2435
|
+
const redirectsPath = path.join(publishDir, "_redirects");
|
|
2436
|
+
const internalDir = path.join(projectCwd, ".netlify", "functions-internal");
|
|
2437
|
+
const serverDir = path.join(internalDir, "server");
|
|
2438
|
+
const serverEntryPath = path.join(serverDir, "server.mjs");
|
|
2439
|
+
const serverMainPath = path.join(serverDir, "main.mjs");
|
|
2440
|
+
|
|
2441
|
+
if (!fs.existsSync(publishDir)) {
|
|
2442
|
+
failures.push("missing publish directory: dist");
|
|
2443
|
+
} else if (!fs.existsSync(redirectsPath)) {
|
|
2444
|
+
failures.push("missing Netlify fallback redirects file: dist/_redirects");
|
|
2445
|
+
} else {
|
|
2446
|
+
const redirects = fs.readFileSync(redirectsPath, "utf-8");
|
|
2447
|
+
if (
|
|
2448
|
+
!redirects
|
|
2449
|
+
.split(/\r?\n/)
|
|
2450
|
+
.some(
|
|
2451
|
+
(line) =>
|
|
2452
|
+
line.trim().replace(/\s+/g, " ") ===
|
|
2453
|
+
"/* /.netlify/functions/server 200",
|
|
2454
|
+
)
|
|
2455
|
+
) {
|
|
2456
|
+
failures.push(
|
|
2457
|
+
'dist/_redirects is missing "/* /.netlify/functions/server 200"',
|
|
2458
|
+
);
|
|
2459
|
+
}
|
|
2460
|
+
}
|
|
2461
|
+
|
|
2462
|
+
if (!fs.existsSync(serverDir)) {
|
|
2463
|
+
failures.push(
|
|
2464
|
+
"missing scanned Netlify server function: .netlify/functions-internal/server",
|
|
2465
|
+
);
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
if (!fs.existsSync(serverMainPath)) {
|
|
2469
|
+
failures.push(
|
|
2470
|
+
"missing Netlify server bundle: .netlify/functions-internal/server/main.mjs",
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
if (!fs.existsSync(serverEntryPath)) {
|
|
2475
|
+
failures.push(
|
|
2476
|
+
"missing Netlify server entry: .netlify/functions-internal/server/server.mjs",
|
|
2477
|
+
);
|
|
2478
|
+
} else {
|
|
2479
|
+
const serverEntry = fs.readFileSync(serverEntryPath, "utf-8");
|
|
2480
|
+
if (!/\bpath\s*:\s*["']\/\*["']/.test(serverEntry)) {
|
|
2481
|
+
failures.push(
|
|
2482
|
+
'Netlify server entry is missing the "/*" catch-all function path',
|
|
2483
|
+
);
|
|
2484
|
+
}
|
|
2485
|
+
if (!serverEntry.includes('"/.netlify/*"')) {
|
|
2486
|
+
failures.push(
|
|
2487
|
+
'Netlify server catch-all is missing the "/.netlify/*" exclusion',
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
if (!serverEntry.includes("./main.mjs")) {
|
|
2491
|
+
failures.push(
|
|
2492
|
+
"Netlify server entry does not reference the generated main.mjs bundle",
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
if (isDurableBackgroundDeployEnabled()) {
|
|
2498
|
+
const backgroundDir = path.join(
|
|
2499
|
+
internalDir,
|
|
2500
|
+
AGENT_BACKGROUND_FUNCTION_NAME,
|
|
2501
|
+
);
|
|
2502
|
+
const backgroundEntryPath = path.join(
|
|
2503
|
+
backgroundDir,
|
|
2504
|
+
`${AGENT_BACKGROUND_FUNCTION_NAME}.mjs`,
|
|
2505
|
+
);
|
|
2506
|
+
if (!fs.existsSync(backgroundEntryPath)) {
|
|
2507
|
+
failures.push(
|
|
2508
|
+
`durable background is enabled but ${path.relative(
|
|
2509
|
+
projectCwd,
|
|
2510
|
+
backgroundEntryPath,
|
|
2511
|
+
)} was not emitted`,
|
|
2512
|
+
);
|
|
2513
|
+
} else {
|
|
2514
|
+
const backgroundEntry = fs.readFileSync(backgroundEntryPath, "utf-8");
|
|
2515
|
+
if (!/\bbackground\s*:\s*true\b/.test(backgroundEntry)) {
|
|
2516
|
+
failures.push(
|
|
2517
|
+
`durable background entry ${path.relative(
|
|
2518
|
+
projectCwd,
|
|
2519
|
+
backgroundEntryPath,
|
|
2520
|
+
)} is missing background: true`,
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
if (/^\s*path\s*:/m.test(backgroundEntry)) {
|
|
2524
|
+
failures.push(
|
|
2525
|
+
`durable background entry ${path.relative(
|
|
2526
|
+
projectCwd,
|
|
2527
|
+
backgroundEntryPath,
|
|
2528
|
+
)} must not declare a custom path`,
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
|
|
2534
|
+
if (failures.length > 0) {
|
|
2535
|
+
throw new Error(
|
|
2536
|
+
"[deploy] Netlify deploy guard failed; refusing to publish an output " +
|
|
2537
|
+
"that would likely serve Netlify 404s:\n" +
|
|
2538
|
+
failures.map((failure) => `- ${failure}`).join("\n"),
|
|
2539
|
+
);
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
console.log(
|
|
2543
|
+
"[deploy] Netlify deploy guard passed: publish dir and catch-all server function are present.",
|
|
2544
|
+
);
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
export function writeSingleTemplateNetlifyRedirects(projectCwd: string): void {
|
|
2548
|
+
const publishDir = path.join(projectCwd, "dist");
|
|
2549
|
+
if (!fs.existsSync(publishDir)) return;
|
|
2550
|
+
|
|
2551
|
+
const redirectsPath = path.join(publishDir, "_redirects");
|
|
2552
|
+
const existing = fs.existsSync(redirectsPath)
|
|
2553
|
+
? fs.readFileSync(redirectsPath, "utf-8").trimEnd()
|
|
2554
|
+
: "";
|
|
2555
|
+
const lines = existing ? existing.split(/\r?\n/) : [];
|
|
2556
|
+
|
|
2557
|
+
function hasRedirect(line: string): boolean {
|
|
2558
|
+
return lines.some(
|
|
2559
|
+
(existingLine) => existingLine.trim().replace(/\s+/g, " ") === line,
|
|
2560
|
+
);
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
const fallbackRules = [
|
|
2564
|
+
"# Generated by agent-native build for Netlify single-template deploys",
|
|
2565
|
+
"# Static files are served first; dynamic routes fall through to the server function.",
|
|
2566
|
+
"/* /.netlify/functions/server 200",
|
|
2567
|
+
];
|
|
2568
|
+
|
|
2569
|
+
for (const line of fallbackRules) {
|
|
2570
|
+
if (line.startsWith("#") || !hasRedirect(line)) {
|
|
2571
|
+
lines.push(line);
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
fs.writeFileSync(redirectsPath, lines.join("\n") + "\n");
|
|
2576
|
+
console.log(
|
|
2577
|
+
"[deploy] Wrote Netlify fallback redirect /* -> /.netlify/functions/server.",
|
|
2578
|
+
);
|
|
2579
|
+
}
|
|
2580
|
+
|
|
2430
2581
|
function copyInstalledLibsqlNativePackages(serverDir: string | undefined) {
|
|
2431
2582
|
if (!serverDir || !fs.existsSync(serverDir)) return;
|
|
2432
2583
|
const nodeModulesRoots = nodeModulesAncestors(cwd);
|
|
@@ -2976,6 +3127,11 @@ export default bundle;
|
|
|
2976
3127
|
}
|
|
2977
3128
|
}
|
|
2978
3129
|
|
|
3130
|
+
if (preset === "netlify") {
|
|
3131
|
+
writeSingleTemplateNetlifyRedirects(cwd);
|
|
3132
|
+
assertSingleTemplateNetlifyBuildOutput(cwd);
|
|
3133
|
+
}
|
|
3134
|
+
|
|
2979
3135
|
// Resolve remaining bare npm imports by bundling them into _libs/.
|
|
2980
3136
|
// Nitro sometimes leaves small packages as externals even with noExternals.
|
|
2981
3137
|
if (preset.startsWith("cloudflare") || preset.startsWith("deno")) {
|