@agent-native/core 0.161.23 → 0.163.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +27 -2
  4. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +18 -2
  5. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
  6. package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +27 -2
  7. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
  8. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +27 -2
  9. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
  10. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +27 -2
  11. package/corpus/templates/calendar/server/handlers/google-auth.ts +1 -8
  12. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  13. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  14. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +88 -35
  15. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +36 -1
  16. package/corpus/templates/clips/app/i18n/en-US.ts +4 -3
  17. package/corpus/templates/clips/app/lib/camera-composite.ts +15 -1
  18. package/corpus/templates/clips/changelog/2026-08-18-clips-no-longer-crashes-when-a-camera-bubble-starts-at-a-tin.md +6 -0
  19. package/corpus/templates/clips/desktop/src/app.tsx +30 -40
  20. package/corpus/templates/clips/desktop/src/components/MicOffConfirmation.tsx +47 -0
  21. package/corpus/templates/clips/desktop/src/styles.css +110 -0
  22. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +114 -7
  23. package/corpus/templates/clips/shared/recording-capture.ts +18 -13
  24. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
  25. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +27 -2
  26. package/corpus/templates/content/actions/create-content-database.ts +1 -1
  27. package/corpus/templates/content/actions/create-document.ts +1 -1
  28. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -5
  29. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +0 -3
  30. package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +0 -4
  31. package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +4 -1
  32. package/corpus/templates/content/app/hooks/use-notion.ts +17 -15
  33. package/corpus/templates/crm/.agents/skills/real-time-sync/SKILL.md +1 -1
  34. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +27 -2
  35. package/corpus/templates/design/.agents/skills/real-time-sync/SKILL.md +1 -1
  36. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +27 -2
  37. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +6 -1
  38. package/corpus/templates/dispatch/.agents/skills/real-time-sync/SKILL.md +1 -1
  39. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +27 -2
  40. package/corpus/templates/factory/.agents/skills/real-time-sync/SKILL.md +1 -1
  41. package/corpus/templates/factory/.agents/skills/sharing/SKILL.md +27 -2
  42. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
  43. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +27 -2
  44. package/corpus/templates/macros/.agents/skills/real-time-sync/SKILL.md +1 -1
  45. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +27 -2
  46. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
  47. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +27 -2
  48. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +18 -3
  49. package/corpus/templates/mail/server/handlers/google-auth.ts +1 -8
  50. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
  51. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +27 -2
  52. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +4 -0
  53. package/corpus/templates/slides/.agents/skills/real-time-sync/SKILL.md +1 -1
  54. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +27 -2
  55. package/corpus/templates/slides/actions/_await-fit-check.ts +30 -5
  56. package/corpus/templates/slides/actions/add-slide.ts +6 -2
  57. package/corpus/templates/slides/actions/create-deck.ts +2 -1
  58. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +3 -3
  59. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +3 -3
  60. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +3 -1
  61. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +1 -1
  62. package/corpus/templates/slides/app/components/editor/slide-object-interactions.ts +1 -1
  63. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -0
  64. package/corpus/templates/slides/app/pages/DeckEditor.tsx +35 -2
  65. package/corpus/templates/slides/app/routes/p.$id.tsx +3 -1
  66. package/corpus/templates/slides/changelog/2026-08-18-generated-slides-preserve-speaker-notes.md +6 -0
  67. package/corpus/templates/slides/server/handlers/assets.ts +23 -12
  68. package/corpus/templates/slides/server/handlers/design-system-decode-job-status.ts +15 -6
  69. package/corpus/templates/slides/server/handlers/design-system-upload-start.ts +15 -6
  70. package/corpus/templates/slides/server/handlers/index-design-system-sources.ts +34 -19
  71. package/corpus/templates/slides/server/handlers/request-auth-context.ts +1 -0
  72. package/corpus/templates/slides/server/handlers/share.ts +1 -1
  73. package/corpus/templates/slides/server/plugins/agent-chat.ts +1 -1
  74. package/corpus/templates/slides/server/routes/api/deck-agent-context.json.get.ts +1 -1
  75. package/corpus/templates/slides/server/routes/api/exports/[filename].get.ts +9 -3
  76. package/corpus/templates/slides/server/routes/api/exports/google-slides.post.ts +12 -5
  77. package/corpus/templates/slides/server/routes/api/exports/html.post.ts +9 -7
  78. package/corpus/templates/slides/server/routes/api/exports/pptx.post.ts +9 -7
  79. package/corpus/templates/slides/shared/api.ts +7 -1
  80. package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +1 -1
  81. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +27 -2
  82. package/dist/agent/engine/translate-anthropic.js +27 -2
  83. package/dist/agent/production-agent.d.ts +2 -2
  84. package/dist/agent/production-agent.js +16 -8
  85. package/dist/agent/thread-data-builder.d.ts +1 -0
  86. package/dist/agent/thread-data-builder.js +6 -4
  87. package/dist/agent/tool-approval-store.d.ts +10 -1
  88. package/dist/agent/tool-approval-store.js +19 -2
  89. package/dist/agent/types.d.ts +9 -0
  90. package/dist/app-config/schema.d.ts +1 -0
  91. package/dist/app-config/workspace.d.ts +1 -0
  92. package/dist/app-config/workspace.js +8 -0
  93. package/dist/chat-threads/schema.d.ts +2 -2
  94. package/dist/client/AssistantChat.js +14 -6
  95. package/dist/client/EnvironmentBadge.d.ts +15 -0
  96. package/dist/client/EnvironmentBadge.js +79 -0
  97. package/dist/client/MultiTabAssistantChat.js +73 -17
  98. package/dist/client/agent-chat-adapter.js +11 -1
  99. package/dist/client/api-path.js +14 -1
  100. package/dist/client/app-providers.js +2 -1
  101. package/dist/client/chat/tool-call-display.d.ts +12 -2
  102. package/dist/client/chat/tool-call-display.js +17 -7
  103. package/dist/client/chat/widgets/WorkspaceFileWidget.js +1 -1
  104. package/dist/client/chat/widgets/builtin-tool-renderers.d.ts +6 -0
  105. package/dist/client/chat/widgets/builtin-tool-renderers.js +19 -0
  106. package/dist/client/onboarding/FirstRunOnboarding.js +46 -32
  107. package/dist/client/onboarding/use-onboarding.d.ts +8 -1
  108. package/dist/client/onboarding/use-onboarding.js +22 -7
  109. package/dist/client/org/TeamPage.js +15 -2
  110. package/dist/client/org/hooks.d.ts +2 -0
  111. package/dist/client/org/hooks.js +12 -0
  112. package/dist/client/org/index.d.ts +2 -2
  113. package/dist/client/org/index.js +1 -1
  114. package/dist/client/session-replay.js +58 -15
  115. package/dist/client/settings/SettingsTabsPage.js +19 -6
  116. package/dist/client/sharing/ShareButton.js +43 -18
  117. package/dist/client/sharing/useShareButtonController.d.ts +12 -2
  118. package/dist/client/sharing/useShareButtonController.js +38 -8
  119. package/dist/client/sse-event-processor.d.ts +7 -0
  120. package/dist/client/sse-event-processor.js +4 -1
  121. package/dist/client/ui/index.d.ts +1 -0
  122. package/dist/client/ui/index.js +1 -0
  123. package/dist/client/use-action.js +26 -0
  124. package/dist/collab/struct-routes.d.ts +1 -1
  125. package/dist/config.d.ts +15 -0
  126. package/dist/config.js +53 -0
  127. package/dist/dashboard-storage/schema.d.ts +2 -2
  128. package/dist/data-programs/schema.d.ts +2 -2
  129. package/dist/extensions/fetch-tool.js +8 -2
  130. package/dist/extensions/schema.d.ts +2 -2
  131. package/dist/extensions/store.js +3 -0
  132. package/dist/localization/core-messages/ar-SA.js +1 -0
  133. package/dist/localization/core-messages/de-DE.js +1 -0
  134. package/dist/localization/core-messages/en-US.d.ts +1 -0
  135. package/dist/localization/core-messages/en-US.js +1 -0
  136. package/dist/localization/core-messages/es-ES.js +1 -0
  137. package/dist/localization/core-messages/fr-FR.js +1 -0
  138. package/dist/localization/core-messages/hi-IN.js +1 -0
  139. package/dist/localization/core-messages/ja-JP.js +1 -0
  140. package/dist/localization/core-messages/ko-KR.js +1 -0
  141. package/dist/localization/core-messages/pt-BR.js +1 -0
  142. package/dist/localization/core-messages/zh-CN.js +1 -0
  143. package/dist/localization/core-messages/zh-TW.js +1 -0
  144. package/dist/localization/core-messages.d.ts +1 -0
  145. package/dist/localization/default-messages.d.ts +4 -0
  146. package/dist/localization/default-messages.js +4 -0
  147. package/dist/mcp/embed-app.js +1 -1
  148. package/dist/notifications/routes.d.ts +1 -1
  149. package/dist/org/handlers.d.ts +6 -1
  150. package/dist/org/handlers.js +43 -0
  151. package/dist/org/index.d.ts +3 -3
  152. package/dist/org/index.js +2 -2
  153. package/dist/org/migrations.js +33 -0
  154. package/dist/org/plugin.js +10 -1
  155. package/dist/org/schema.d.ts +315 -0
  156. package/dist/org/schema.js +20 -1
  157. package/dist/org/types.d.ts +3 -0
  158. package/dist/org/workspace-app-access.d.ts +10 -0
  159. package/dist/org/workspace-app-access.js +179 -0
  160. package/dist/progress/routes.d.ts +1 -1
  161. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  162. package/dist/provider-api/actions/provider-api.d.ts +7 -7
  163. package/dist/provider-api/index.d.ts +10 -0
  164. package/dist/provider-api/index.js +45 -9
  165. package/dist/resources/handlers.d.ts +1 -1
  166. package/dist/secrets/routes.d.ts +9 -9
  167. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  168. package/dist/server/auth.js +24 -4
  169. package/dist/server/poll.js +13 -3
  170. package/dist/server/sentry.d.ts +1 -1
  171. package/dist/sharing/access.d.ts +2 -2
  172. package/dist/sharing/access.js +56 -3
  173. package/dist/sharing/actions/list-resource-shares.d.ts +2 -0
  174. package/dist/sharing/actions/list-resource-shares.js +26 -1
  175. package/dist/sharing/actions/share-resource.d.ts +1 -1
  176. package/dist/sharing/actions/share-resource.js +23 -4
  177. package/dist/sharing/actions/unshare-resource.d.ts +1 -1
  178. package/dist/sharing/actions/unshare-resource.js +1 -1
  179. package/dist/sharing/registry.d.ts +12 -0
  180. package/dist/sharing/schema.d.ts +4 -4
  181. package/dist/sharing/schema.js +2 -2
  182. package/dist/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  183. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  184. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  185. package/dist/templates/default/.agents/skills/sharing/SKILL.md +27 -2
  186. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  187. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
  188. package/dist/vite/client.d.ts +5 -0
  189. package/dist/vite/client.js +64 -2
  190. package/dist/workspace-connections/actions/list-workspace-user-groups.d.ts +9 -1
  191. package/dist/workspace-connections/actions/list-workspace-user-groups.js +9 -4
  192. package/dist/workspace-connections/groups.d.ts +2 -0
  193. package/dist/workspace-connections/groups.js +16 -5
  194. package/dist/workspace-files/actions.d.ts +1 -8
  195. package/dist/workspace-files/actions.js +5 -21
  196. package/dist/workspace-files/index.d.ts +1 -1
  197. package/dist/workspace-files/index.js +1 -1
  198. package/dist/workspace-files/store.d.ts +18 -0
  199. package/dist/workspace-files/store.js +24 -3
  200. package/package.json +3 -3
  201. package/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
  202. package/src/templates/chat/.agents/skills/sharing/SKILL.md +27 -2
  203. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
  204. package/src/templates/default/.agents/skills/sharing/SKILL.md +27 -2
  205. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
  206. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +27 -2
package/corpus/README.md CHANGED
@@ -31,4 +31,4 @@ rg -n "defineAction|useActionQuery" node_modules/@agent-native/core/corpus
31
31
 
32
32
  ## Generated Counts
33
33
 
34
- - template files: 8321
34
+ - template files: 8324
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
18
18
 
19
19
  This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
20
20
 
21
+ Workspace apps are the intentional organization-scoped exception: their default
22
+ visibility is `org`, configurable by an organization owner/admin. The creator is
23
+ stored as the app owner, and the creator or an organization owner/admin can
24
+ change the app's visibility or manage its share grants.
25
+
21
26
  ## Concepts
22
27
 
23
28
  ### Three visibility levels
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
26
31
  - **`org`** — owner + explicit grants + anyone in the same org (read-only).
27
32
  - **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
28
33
 
29
- Visibility is coarse. Explicit share grants are fine-grained (per user or per org).
34
+ Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
35
+ organization group, or per org).
30
36
 
31
37
  ### Roles on a share grant
32
38
 
@@ -101,6 +107,7 @@ registerShareableResource({
101
107
  // ...
102
108
  allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
103
109
  requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
110
+ supportsGroupShares: true, // enable reusable organization groups for this resource
104
111
  });
105
112
  ```
106
113
 
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
111
118
 
112
119
  Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
113
120
 
121
+ ### Reusable organization groups
122
+
123
+ The workspace organization model stores reusable groups in
124
+ `workspace_user_groups`. Organization owners/admins manage the groups and their
125
+ members through the existing group actions. Set `supportsGroupShares: true` on a
126
+ resource registration to enable `principalType: "group"` in the generic share
127
+ actions and the shared popover's autocomplete. Group ids are always checked
128
+ against the resource's organization, and membership is evaluated at access time
129
+ so group changes apply to existing shares.
130
+
131
+ Workspace apps opt into this flag and use the organization default visibility
132
+ setting. The app creator is the durable `owner_email`, while organization
133
+ owners/admins receive admin access through the registration's
134
+ `canManageAccess` callback.
135
+
114
136
  ## Filter list/read queries
115
137
 
116
138
  ```ts
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
197
219
  - Add `ShareAgentsSection` only when the resource has a real agent-readable
198
220
  link or prompt. Keep it collapsed by default and supply domain-specific
199
221
  content through the shared section shell.
222
+ - When `supportsGroupShares` is enabled, keep the existing people input as the
223
+ only entry point and include organization groups in its autocomplete. Do not
224
+ add a separate group-management surface to each resource.
200
225
  - App-specific tabs or controls may remain when they represent a real domain
201
226
  action (for example, an embed-code editor), but they should retain the same
202
227
  trigger, copy-row, access, and spacing language.
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
211
236
 
212
237
  | Action | Args | Purpose |
213
238
  | -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
214
- | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
239
+ | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
215
240
  | `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
216
241
  | `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
217
242
  | `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
@@ -1623,8 +1623,15 @@ function MetricRenderer({
1623
1623
 
1624
1624
  function numericTableValue(value: unknown): number | null {
1625
1625
  if (typeof value === "number") return Number.isFinite(value) ? value : null;
1626
- if (typeof value !== "string" || value.trim() === "") return null;
1627
- const numeric = Number(value);
1626
+ if (typeof value !== "string") return null;
1627
+ const trimmed = value.trim();
1628
+ if (trimmed === "") return null;
1629
+ // Strip display formatting (currency symbols, thousands separators,
1630
+ // percent signs, whitespace) so a formatted numeric column ("$1,234",
1631
+ // "12.5%") still parses as a number instead of falling back to text.
1632
+ const cleaned = trimmed.replace(/[$€£¥%,\s]/g, "");
1633
+ if (cleaned === "" || cleaned === "-" || cleaned === "+") return null;
1634
+ const numeric = Number(cleaned);
1628
1635
  return Number.isFinite(numeric) ? numeric : null;
1629
1636
  }
1630
1637
 
@@ -1651,6 +1658,15 @@ export function sortTableRows(
1651
1658
 
1652
1659
  const an = numericTableValue(av);
1653
1660
  const bn = numericTableValue(bv);
1661
+
1662
+ // A value that fails to parse as a number sorts after one that
1663
+ // does, regardless of column direction — same rule as null above.
1664
+ // Keeps a mostly-numeric column ("10", "9", "N/A") grouped by
1665
+ // magnitude instead of interleaving text lexically with numbers,
1666
+ // and never coerces the unparseable value to 0.
1667
+ if (an !== null && bn === null) return -1;
1668
+ if (an === null && bn !== null) return 1;
1669
+
1654
1670
  const comparison =
1655
1671
  an !== null && bn !== null
1656
1672
  ? an - bn
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
18
18
 
19
19
  This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
20
20
 
21
+ Workspace apps are the intentional organization-scoped exception: their default
22
+ visibility is `org`, configurable by an organization owner/admin. The creator is
23
+ stored as the app owner, and the creator or an organization owner/admin can
24
+ change the app's visibility or manage its share grants.
25
+
21
26
  ## Concepts
22
27
 
23
28
  ### Three visibility levels
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
26
31
  - **`org`** — owner + explicit grants + anyone in the same org (read-only).
27
32
  - **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
28
33
 
29
- Visibility is coarse. Explicit share grants are fine-grained (per user or per org).
34
+ Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
35
+ organization group, or per org).
30
36
 
31
37
  ### Roles on a share grant
32
38
 
@@ -101,6 +107,7 @@ registerShareableResource({
101
107
  // ...
102
108
  allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
103
109
  requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
110
+ supportsGroupShares: true, // enable reusable organization groups for this resource
104
111
  });
105
112
  ```
106
113
 
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
111
118
 
112
119
  Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
113
120
 
121
+ ### Reusable organization groups
122
+
123
+ The workspace organization model stores reusable groups in
124
+ `workspace_user_groups`. Organization owners/admins manage the groups and their
125
+ members through the existing group actions. Set `supportsGroupShares: true` on a
126
+ resource registration to enable `principalType: "group"` in the generic share
127
+ actions and the shared popover's autocomplete. Group ids are always checked
128
+ against the resource's organization, and membership is evaluated at access time
129
+ so group changes apply to existing shares.
130
+
131
+ Workspace apps opt into this flag and use the organization default visibility
132
+ setting. The app creator is the durable `owner_email`, while organization
133
+ owners/admins receive admin access through the registration's
134
+ `canManageAccess` callback.
135
+
114
136
  ## Filter list/read queries
115
137
 
116
138
  ```ts
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
197
219
  - Add `ShareAgentsSection` only when the resource has a real agent-readable
198
220
  link or prompt. Keep it collapsed by default and supply domain-specific
199
221
  content through the shared section shell.
222
+ - When `supportsGroupShares` is enabled, keep the existing people input as the
223
+ only entry point and include organization groups in its autocomplete. Do not
224
+ add a separate group-management surface to each resource.
200
225
  - App-specific tabs or controls may remain when they represent a real domain
201
226
  action (for example, an embed-code editor), but they should retain the same
202
227
  trigger, copy-row, access, and spacing language.
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
211
236
 
212
237
  | Action | Args | Purpose |
213
238
  | -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
214
- | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
239
+ | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
215
240
  | `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
216
241
  | `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
217
242
  | `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
18
18
 
19
19
  This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
20
20
 
21
+ Workspace apps are the intentional organization-scoped exception: their default
22
+ visibility is `org`, configurable by an organization owner/admin. The creator is
23
+ stored as the app owner, and the creator or an organization owner/admin can
24
+ change the app's visibility or manage its share grants.
25
+
21
26
  ## Concepts
22
27
 
23
28
  ### Three visibility levels
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
26
31
  - **`org`** — owner + explicit grants + anyone in the same org (read-only).
27
32
  - **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
28
33
 
29
- Visibility is coarse. Explicit share grants are fine-grained (per user or per org).
34
+ Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
35
+ organization group, or per org).
30
36
 
31
37
  ### Roles on a share grant
32
38
 
@@ -101,6 +107,7 @@ registerShareableResource({
101
107
  // ...
102
108
  allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
103
109
  requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
110
+ supportsGroupShares: true, // enable reusable organization groups for this resource
104
111
  });
105
112
  ```
106
113
 
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
111
118
 
112
119
  Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
113
120
 
121
+ ### Reusable organization groups
122
+
123
+ The workspace organization model stores reusable groups in
124
+ `workspace_user_groups`. Organization owners/admins manage the groups and their
125
+ members through the existing group actions. Set `supportsGroupShares: true` on a
126
+ resource registration to enable `principalType: "group"` in the generic share
127
+ actions and the shared popover's autocomplete. Group ids are always checked
128
+ against the resource's organization, and membership is evaluated at access time
129
+ so group changes apply to existing shares.
130
+
131
+ Workspace apps opt into this flag and use the organization default visibility
132
+ setting. The app creator is the durable `owner_email`, while organization
133
+ owners/admins receive admin access through the registration's
134
+ `canManageAccess` callback.
135
+
114
136
  ## Filter list/read queries
115
137
 
116
138
  ```ts
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
197
219
  - Add `ShareAgentsSection` only when the resource has a real agent-readable
198
220
  link or prompt. Keep it collapsed by default and supply domain-specific
199
221
  content through the shared section shell.
222
+ - When `supportsGroupShares` is enabled, keep the existing people input as the
223
+ only entry point and include organization groups in its autocomplete. Do not
224
+ add a separate group-management surface to each resource.
200
225
  - App-specific tabs or controls may remain when they represent a real domain
201
226
  action (for example, an embed-code editor), but they should retain the same
202
227
  trigger, copy-row, access, and spacing language.
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
211
236
 
212
237
  | Action | Args | Purpose |
213
238
  | -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
214
- | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
239
+ | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
215
240
  | `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
216
241
  | `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
217
242
  | `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
18
18
 
19
19
  This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
20
20
 
21
+ Workspace apps are the intentional organization-scoped exception: their default
22
+ visibility is `org`, configurable by an organization owner/admin. The creator is
23
+ stored as the app owner, and the creator or an organization owner/admin can
24
+ change the app's visibility or manage its share grants.
25
+
21
26
  ## Concepts
22
27
 
23
28
  ### Three visibility levels
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
26
31
  - **`org`** — owner + explicit grants + anyone in the same org (read-only).
27
32
  - **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
28
33
 
29
- Visibility is coarse. Explicit share grants are fine-grained (per user or per org).
34
+ Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
35
+ organization group, or per org).
30
36
 
31
37
  ### Roles on a share grant
32
38
 
@@ -101,6 +107,7 @@ registerShareableResource({
101
107
  // ...
102
108
  allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
103
109
  requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
110
+ supportsGroupShares: true, // enable reusable organization groups for this resource
104
111
  });
105
112
  ```
106
113
 
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
111
118
 
112
119
  Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
113
120
 
121
+ ### Reusable organization groups
122
+
123
+ The workspace organization model stores reusable groups in
124
+ `workspace_user_groups`. Organization owners/admins manage the groups and their
125
+ members through the existing group actions. Set `supportsGroupShares: true` on a
126
+ resource registration to enable `principalType: "group"` in the generic share
127
+ actions and the shared popover's autocomplete. Group ids are always checked
128
+ against the resource's organization, and membership is evaluated at access time
129
+ so group changes apply to existing shares.
130
+
131
+ Workspace apps opt into this flag and use the organization default visibility
132
+ setting. The app creator is the durable `owner_email`, while organization
133
+ owners/admins receive admin access through the registration's
134
+ `canManageAccess` callback.
135
+
114
136
  ## Filter list/read queries
115
137
 
116
138
  ```ts
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
197
219
  - Add `ShareAgentsSection` only when the resource has a real agent-readable
198
220
  link or prompt. Keep it collapsed by default and supply domain-specific
199
221
  content through the shared section shell.
222
+ - When `supportsGroupShares` is enabled, keep the existing people input as the
223
+ only entry point and include organization groups in its autocomplete. Do not
224
+ add a separate group-management surface to each resource.
200
225
  - App-specific tabs or controls may remain when they represent a real domain
201
226
  action (for example, an embed-code editor), but they should retain the same
202
227
  trigger, copy-row, access, and spacing language.
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
211
236
 
212
237
  | Action | Args | Purpose |
213
238
  | -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
214
- | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
239
+ | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
215
240
  | `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
216
241
  | `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
217
242
  | `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
@@ -163,23 +163,16 @@ function googleOAuthErrorPayload(
163
163
  message: string;
164
164
  code?: string;
165
165
  accountId?: string;
166
- existingOwner?: string;
167
- attemptedOwner?: string;
168
166
  } {
169
167
  if (
170
168
  error instanceof OAuthAccountOwnedByOtherUserError ||
171
169
  error?.name === "OAuthAccountOwnedByOtherUserError"
172
170
  ) {
173
171
  const account = error.accountId || "This Google account";
174
- const existingOwner = error.existingOwner || undefined;
175
- const attemptedOwner = error.attemptedOwner || undefined;
176
- const message = `${account} is connected to another login. Sign out, then sign in with ${account}.`;
177
172
  return {
178
- message,
173
+ message: `${account} is already connected to another login. Sign out, then sign in with the login that originally connected it.`,
179
174
  code: "account_owner_mismatch",
180
175
  accountId: error.accountId,
181
- existingOwner,
182
- attemptedOwner,
183
176
  };
184
177
  }
185
178
 
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
50
50
 
51
51
  For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
52
52
 
53
- 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
53
+ 4. **Fallback** polling calls `/_agent-native/poll?since=N`. It polls every 2 seconds only while an agent run is in flight; otherwise it idles at 60 seconds whether or not SSE is connected (`SSE_FALLBACK_INTERVAL_MS` and `IDLE_POLL_INTERVAL_MS`, both 60 s). A hidden tab polls no faster than every 10 seconds (`HIDDEN_POLL_INTERVAL_MS`). Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
54
54
 
55
55
  5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
56
56
 
@@ -18,6 +18,11 @@ Any resource a user **creates** (dashboards, documents, forms, decks, compositio
18
18
 
19
19
  This is the framework-level primitive. Every ownable resource gets it for free — same API, same UI, same skill.
20
20
 
21
+ Workspace apps are the intentional organization-scoped exception: their default
22
+ visibility is `org`, configurable by an organization owner/admin. The creator is
23
+ stored as the app owner, and the creator or an organization owner/admin can
24
+ change the app's visibility or manage its share grants.
25
+
21
26
  ## Concepts
22
27
 
23
28
  ### Three visibility levels
@@ -26,7 +31,8 @@ This is the framework-level primitive. Every ownable resource gets it for free
26
31
  - **`org`** — owner + explicit grants + anyone in the same org (read-only).
27
32
  - **`public`** — owner + explicit grants + **anyone with the link** (read-only). Public docs do NOT appear in other users' list/sidebar/search results — `accessFilter` omits them by default. They're reachable by id (`resolveAccess` admits them) so direct links and SSR routes like `/p/:id` keep working. If a list endpoint legitimately needs cross-user public discovery (a template gallery, etc.), pass `accessFilter(table, shares, ctx, minRole, { includePublic: true })`.
28
33
 
29
- Visibility is coarse. Explicit share grants are fine-grained (per user or per org).
34
+ Visibility is coarse. Explicit share grants are fine-grained (per user, reusable
35
+ organization group, or per org).
30
36
 
31
37
  ### Roles on a share grant
32
38
 
@@ -101,6 +107,7 @@ registerShareableResource({
101
107
  // ...
102
108
  allowPublic: false, // hides "Public" in the share dialog and rejects it server-side
103
109
  requireOrgMemberForUserShares: true, // user shares must target an org member or pending invitee
110
+ supportsGroupShares: true, // enable reusable organization groups for this resource
104
111
  });
105
112
  ```
106
113
 
@@ -111,6 +118,21 @@ Use both for resources that execute code or expose privileged data with the *vie
111
118
 
112
119
  Defaults match historical behaviour: `allowPublic: true`, `requireOrgMemberForUserShares: false`. Resources that don't set the flags work as before.
113
120
 
121
+ ### Reusable organization groups
122
+
123
+ The workspace organization model stores reusable groups in
124
+ `workspace_user_groups`. Organization owners/admins manage the groups and their
125
+ members through the existing group actions. Set `supportsGroupShares: true` on a
126
+ resource registration to enable `principalType: "group"` in the generic share
127
+ actions and the shared popover's autocomplete. Group ids are always checked
128
+ against the resource's organization, and membership is evaluated at access time
129
+ so group changes apply to existing shares.
130
+
131
+ Workspace apps opt into this flag and use the organization default visibility
132
+ setting. The app creator is the durable `owner_email`, while organization
133
+ owners/admins receive admin access through the registration's
134
+ `canManageAccess` callback.
135
+
114
136
  ## Filter list/read queries
115
137
 
116
138
  ```ts
@@ -197,6 +219,9 @@ All app share popovers should use the same compact surface contract:
197
219
  - Add `ShareAgentsSection` only when the resource has a real agent-readable
198
220
  link or prompt. Keep it collapsed by default and supply domain-specific
199
221
  content through the shared section shell.
222
+ - When `supportsGroupShares` is enabled, keep the existing people input as the
223
+ only entry point and include organization groups in its autocomplete. Do not
224
+ add a separate group-management surface to each resource.
200
225
  - App-specific tabs or controls may remain when they represent a real domain
201
226
  action (for example, an embed-code editor), but they should retain the same
202
227
  trigger, copy-row, access, and spacing language.
@@ -211,7 +236,7 @@ The framework auto-mounts these actions in every template — no per-template bo
211
236
 
212
237
  | Action | Args | Purpose |
213
238
  | -------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------- |
214
- | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
239
+ | `share-resource` | `resourceType, resourceId, principalType, principalId, role, notify?, resourceUrl?, message?` | Grant a user, group, or org access. `notify` defaults to true for individual user shares; `resourceUrl` can provide the direct app link and `message` an optional short note for the notification email. |
215
240
  | `unshare-resource` | `resourceType, resourceId, principalType, principalId` | Revoke access. |
216
241
  | `list-resource-shares` | `resourceType, resourceId` | Current visibility + all share grants. |
217
242
  | `set-resource-visibility` | `resourceType, resourceId, visibility` | Change to `private` / `org` / `public`. |
@@ -2,6 +2,7 @@ import { agentNativePath } from "@agent-native/core/client/api-path";
2
2
  import { useT } from "@agent-native/core/client/i18n";
3
3
  import { isSelectableAudioInputDevice } from "@shared/media-device-selection";
4
4
  import {
5
+ IconArrowLeft,
5
6
  IconBrowser,
6
7
  IconCamera,
7
8
  IconChevronDown,
@@ -14,16 +15,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
14
15
 
15
16
  import { CaptureInstallInlineLink } from "@/components/capture-install-options";
16
17
  import { ImportMenu } from "@/components/import-menu";
17
- import {
18
- AlertDialog,
19
- AlertDialogAction,
20
- AlertDialogCancel,
21
- AlertDialogContent,
22
- AlertDialogDescription,
23
- AlertDialogFooter,
24
- AlertDialogHeader,
25
- AlertDialogTitle,
26
- } from "@/components/ui/alert-dialog";
27
18
  import { Button } from "@/components/ui/button";
28
19
  import {
29
20
  Collapsible,
@@ -124,6 +115,69 @@ type SurfaceOption = {
124
115
 
125
116
  const REQUEST_MIC_ACCESS_VALUE = "__clips_request_microphone_access__";
126
117
 
118
+ function MicOffConfirmation({
119
+ onBack,
120
+ onUnmute,
121
+ onContinue,
122
+ }: {
123
+ onBack: () => void;
124
+ onUnmute: () => void;
125
+ onContinue: () => void;
126
+ }) {
127
+ const t = useT();
128
+
129
+ return (
130
+ <div className="fixed inset-0 z-50 flex min-h-screen flex-col overflow-y-auto bg-background text-foreground">
131
+ <header className="flex shrink-0 items-center px-6 py-5 sm:px-10">
132
+ <button
133
+ type="button"
134
+ onClick={onBack}
135
+ className="inline-flex items-center gap-2 rounded-lg px-1 py-1 text-lg font-semibold transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
136
+ >
137
+ <IconArrowLeft className="h-5 w-5 rtl:-scale-x-100" />
138
+ {t("recordingPage.back")}
139
+ </button>
140
+ </header>
141
+
142
+ <main className="flex flex-1 flex-col items-center justify-center px-6 py-12 text-center sm:py-16">
143
+ <div
144
+ className="relative flex h-28 w-28 items-center justify-center rounded-full bg-destructive/10 text-destructive"
145
+ aria-hidden="true"
146
+ >
147
+ <IconMicrophone className="h-12 w-12" strokeWidth={1.75} />
148
+ <span className="absolute h-1 w-20 rotate-45 rounded-full bg-destructive" />
149
+ </div>
150
+ <h1 className="mt-10 text-3xl font-semibold tracking-tight sm:text-4xl">
151
+ {t("preRecord.micOffConfirmTitle")}
152
+ </h1>
153
+ <p className="mt-4 max-w-md text-base leading-7 text-muted-foreground sm:text-lg">
154
+ {t("preRecord.micOffConfirmDescription")}
155
+ </p>
156
+ </main>
157
+
158
+ <footer className="flex shrink-0 justify-center border-t border-border bg-muted/20 px-6 py-6 sm:px-10">
159
+ <div className="flex w-full max-w-md gap-4">
160
+ <Button
161
+ type="button"
162
+ variant="outline"
163
+ onClick={onUnmute}
164
+ className="h-12 flex-1 rounded-xl text-base font-semibold"
165
+ >
166
+ {t("preRecord.unmuteMicrophone")}
167
+ </Button>
168
+ <Button
169
+ type="button"
170
+ onClick={onContinue}
171
+ className="h-12 flex-1 rounded-xl text-base font-semibold"
172
+ >
173
+ {t("preRecord.startWithoutMic")}
174
+ </Button>
175
+ </div>
176
+ </footer>
177
+ </div>
178
+ );
179
+ }
180
+
127
181
  function isPseudoMediaDeviceId(value: string | null | undefined): boolean {
128
182
  const id = value?.trim().toLowerCase();
129
183
  return !id || id === "default" || id === "communications";
@@ -465,6 +519,20 @@ export function PreRecordPanel({
465
519
  onStart(buildStartOpts());
466
520
  }, [micId, buildStartOpts, onStart]);
467
521
 
522
+ const handleMicWarningBack = useCallback(() => {
523
+ setMicWarningOpen(false);
524
+ }, []);
525
+
526
+ const handleMicWarningUnmute = useCallback(() => {
527
+ chooseMic("default");
528
+ setMicWarningOpen(false);
529
+ }, [chooseMic]);
530
+
531
+ const handleMicWarningContinue = useCallback(() => {
532
+ setMicWarningOpen(false);
533
+ onStart(buildStartOpts());
534
+ }, [buildStartOpts, onStart]);
535
+
468
536
  const selectedCameraLabel = useMemo(() => {
469
537
  if (!needsCamera) return null;
470
538
  if (cameraId === "default") return t("preRecord.defaultCamera");
@@ -619,6 +687,16 @@ export function PreRecordPanel({
619
687
  return null;
620
688
  }, [audioEnabled, cameraTest.status, micTest.status, needsCamera, t]);
621
689
 
690
+ if (micWarningOpen) {
691
+ return (
692
+ <MicOffConfirmation
693
+ onBack={handleMicWarningBack}
694
+ onUnmute={handleMicWarningUnmute}
695
+ onContinue={handleMicWarningContinue}
696
+ />
697
+ );
698
+ }
699
+
622
700
  return (
623
701
  <div className="mx-auto w-full max-w-lg overflow-hidden rounded-2xl border border-border bg-card shadow-lg">
624
702
  <div className="p-6">
@@ -965,31 +1043,6 @@ export function PreRecordPanel({
965
1043
  </>
966
1044
  )}
967
1045
  </div>
968
-
969
- <AlertDialog open={micWarningOpen} onOpenChange={setMicWarningOpen}>
970
- <AlertDialogContent>
971
- <AlertDialogHeader>
972
- <AlertDialogTitle>
973
- {t("preRecord.micOffConfirmTitle")}
974
- </AlertDialogTitle>
975
- <AlertDialogDescription>
976
- {t("preRecord.micOffConfirmDescription")}
977
- </AlertDialogDescription>
978
- </AlertDialogHeader>
979
- <AlertDialogFooter>
980
- <AlertDialogCancel>{t("common.cancel")}</AlertDialogCancel>
981
- <AlertDialogAction
982
- onClick={(event) => {
983
- event.preventDefault();
984
- setMicWarningOpen(false);
985
- onStart(buildStartOpts());
986
- }}
987
- >
988
- {t("preRecord.startWithoutMic")}
989
- </AlertDialogAction>
990
- </AlertDialogFooter>
991
- </AlertDialogContent>
992
- </AlertDialog>
993
1046
  </div>
994
1047
  );
995
1048
  }