@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
@@ -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`. |
@@ -203,7 +203,7 @@ export async function createContentDatabaseRecord(
203
203
  let orgId = getRequestOrgId() ?? null;
204
204
  let spaceId = options.spaceId ?? null;
205
205
  let inheritedShares: Array<{
206
- principalType: "user" | "org";
206
+ principalType: "user" | "group" | "org";
207
207
  principalId: string;
208
208
  role: ShareRole;
209
209
  }> = [];
@@ -172,7 +172,7 @@ export default defineAction({
172
172
  const db = getDb();
173
173
  let inheritedRole: "owner" | ShareRole = "owner";
174
174
  let inheritedShares: Array<{
175
- principalType: "user" | "org";
175
+ principalType: "user" | "group" | "org";
176
176
  principalId: string;
177
177
  role: ShareRole;
178
178
  }> = [];
@@ -597,9 +597,7 @@ function DocumentEditorBody({
597
597
  );
598
598
  // Polls Notion sync status to drive the conflict banner / sync bar and the
599
599
  // push-on-save path below (read via the query cache, not this return value).
600
- useDocumentSyncStatus(canEdit && !isLocalFileDocument ? documentId : null, {
601
- autoSync,
602
- });
600
+ useDocumentSyncStatus(canEdit && !isLocalFileDocument ? documentId : null);
603
601
  const pushDocumentToNotion = usePushDocumentToNotion(documentId);
604
602
  const [localTitle, setLocalTitle] = useState("");
605
603
  const [localContent, setLocalContent] = useState("");
@@ -1088,7 +1086,7 @@ function DocumentEditorBody({
1088
1086
  // Pulls remain driven by the polling refetch in useDocumentSyncStatus.
1089
1087
  if (autoSync) {
1090
1088
  const status = queryClient.getQueryData<DocumentSyncStatus>(
1091
- documentSyncStatusQueryKey(documentId, { autoSync }),
1089
+ documentSyncStatusQueryKey(documentId),
1092
1090
  );
1093
1091
  if (status?.pageId && !status.hasConflict) {
1094
1092
  try {
@@ -1100,7 +1098,7 @@ function DocumentEditorBody({
1100
1098
  flushOpenEditor: false,
1101
1099
  });
1102
1100
  queryClient.setQueryData(
1103
- documentSyncStatusQueryKey(documentId, { autoSync }),
1101
+ documentSyncStatusQueryKey(documentId),
1104
1102
  next,
1105
1103
  );
1106
1104
  } catch {
@@ -510,9 +510,6 @@ export function DocumentToolbar({
510
510
  const { data: connection } = useNotionConnection();
511
511
  const { data: syncStatus } = useDocumentSyncStatus(
512
512
  canEdit && !isLocalFileDocument ? documentId : null,
513
- {
514
- autoSync,
515
- },
516
513
  );
517
514
  const linkDocument = useLinkDocumentToNotion(documentId);
518
515
  const unlinkDocument = useUnlinkDocumentFromNotion(documentId);
@@ -4,7 +4,6 @@ import { useState } from "react";
4
4
  import { toast } from "sonner";
5
5
 
6
6
  import { Button } from "@/components/ui/button";
7
- import { useLocalStorage } from "@/hooks/use-local-storage";
8
7
  import {
9
8
  useDocumentSyncStatus,
10
9
  useResolveDocumentSyncConflict,
@@ -20,11 +19,8 @@ export function NotionConflictBanner({
20
19
  canEdit = true,
21
20
  }: NotionConflictBannerProps) {
22
21
  const t = useT();
23
- // Share autoSync state (and React Query cache) with DocumentToolbar.
24
- const [autoSync] = useLocalStorage(`notion-auto-sync:${documentId}`, false);
25
22
  const { data: syncStatus } = useDocumentSyncStatus(
26
23
  canEdit ? documentId : null,
27
- { autoSync },
28
24
  );
29
25
  const resolveConflict = useResolveDocumentSyncConflict(documentId);
30
26
  const [direction, setDirection] = useState<"pull" | "push" | null>(null);
@@ -15,6 +15,7 @@ import { toast } from "sonner";
15
15
  import { Button } from "@/components/ui/button";
16
16
  import { Input } from "@/components/ui/input";
17
17
  import {
18
+ invalidateDocumentQueries,
18
19
  useDisconnectNotion,
19
20
  useDocumentSyncStatus,
20
21
  useLinkDocumentToNotion,
@@ -51,7 +52,9 @@ export function NotionSyncBar({ documentId }: NotionSyncBarProps) {
51
52
  lastSyncedRef.current &&
52
53
  lastSyncedRef.current !== syncStatus.lastSyncedAt
53
54
  ) {
54
- queryClient.invalidateQueries({ queryKey: ["action"] });
55
+ // Bare ["action"] refetches every mounted query app-wide (sidebar tree,
56
+ // comments, database views, search, connection status) on each sync tick.
57
+ invalidateDocumentQueries(queryClient, documentId);
55
58
  }
56
59
  lastSyncedRef.current = syncStatus.lastSyncedAt;
57
60
  }, [syncStatus?.lastSyncedAt, queryClient, documentId]);
@@ -16,6 +16,8 @@ import type {
16
16
  import { useQuery, useQueryClient } from "@tanstack/react-query";
17
17
  import { useEffect, useRef } from "react";
18
18
 
19
+ import { useLocalStorage } from "@/hooks/use-local-storage";
20
+
19
21
  import { documentQueryFilter } from "./use-documents";
20
22
 
21
23
  // The server signs a `redirect` query param into the OAuth `state` and the
@@ -64,20 +66,16 @@ export function invalidateDocumentQueries(
64
66
  queryClient.invalidateQueries({
65
67
  queryKey: documentSyncStatusQueryKey(documentId),
66
68
  });
67
- queryClient.invalidateQueries({
68
- queryKey: documentSyncStatusQueryKey(documentId, { autoSync: true }),
69
- });
70
69
  }
71
70
 
72
- export function documentSyncStatusQueryKey(
73
- documentId: string,
74
- options?: { autoSync?: boolean },
75
- ) {
76
- const normalizedDocumentId = documentId.trim();
71
+ // `autoSync` only decides how often to refetch, so keying on it split one
72
+ // document's status across two cache entries and two independent poll loops —
73
+ // the toolbar polling at 2s while the sync bar polled the same action at 30s.
74
+ export function documentSyncStatusQueryKey(documentId: string) {
77
75
  return [
78
76
  "action",
79
77
  "refresh-notion-sync-status",
80
- { documentId: normalizedDocumentId, autoSync: !!options?.autoSync },
78
+ { documentId: documentId.trim() },
81
79
  ] as const;
82
80
  }
83
81
 
@@ -119,17 +117,21 @@ export function documentSyncRefetchIntervalMs(
119
117
  return autoSync ? 2_000 : 30_000;
120
118
  }
121
119
 
122
- export function useDocumentSyncStatus(
123
- documentId: string | null,
124
- options?: { autoSync?: boolean },
125
- ) {
120
+ export function useDocumentSyncStatus(documentId: string | null) {
126
121
  const queryClient = useQueryClient();
127
122
  const lastObservedSyncedAtRef = useRef<string | null>(null);
128
123
  const normalizedDocumentId = documentId?.trim() || null;
129
- const autoSync = !!options?.autoSync;
124
+ // `autoSync` changes what the server does, so every observer of the shared
125
+ // query key has to agree on it. Read the same per-document toggle the
126
+ // toolbar writes instead of taking it per mount, where one component could
127
+ // suppress or enable another's auto-sync through the shared query function.
128
+ const [autoSync] = useLocalStorage(
129
+ `notion-auto-sync:${normalizedDocumentId ?? ""}`,
130
+ false,
131
+ );
130
132
  const query = useQuery<DocumentSyncStatus>({
131
133
  queryKey: normalizedDocumentId
132
- ? documentSyncStatusQueryKey(normalizedDocumentId, options)
134
+ ? documentSyncStatusQueryKey(normalizedDocumentId)
133
135
  : ["action", "refresh-notion-sync-status", null],
134
136
  queryFn: () => {
135
137
  if (!normalizedDocumentId) throw new Error("documentId is required");
@@ -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`. |
@@ -4099,7 +4099,12 @@ export function DesignCanvas({
4099
4099
  if (externalPreviewUrl) return false;
4100
4100
  return replacePreviewContent(
4101
4101
  getEmbeddedFrameDocumentContent({
4102
- content: nextContent,
4102
+ // The initial srcdoc is normalized through withLocalRuntimes below.
4103
+ // Keep the in-place overview replacement on that same local runtime
4104
+ // path, otherwise a saved CDN script is reintroduced after the
4105
+ // iframe has mounted and can leave the design unstyled when the
4106
+ // browser cannot reach the provider.
4107
+ content: withLocalRuntimes(nextContent),
4103
4108
  embeddedFrameBackground,
4104
4109
  transparentBackground,
4105
4110
  contentOffsetX: embeddedFrame?.contentOffsetX ?? 0,
@@ -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