@fias/arche-sdk 2.29.0 → 2.31.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 (38) hide show
  1. package/dist/bridge.d.ts +32 -6
  2. package/dist/bridge.d.ts.map +1 -1
  3. package/dist/bridge.js +73 -2
  4. package/dist/bridge.js.map +1 -1
  5. package/dist/bridge.test.js +71 -0
  6. package/dist/bridge.test.js.map +1 -1
  7. package/dist/entity-ops.d.ts +12 -0
  8. package/dist/entity-ops.d.ts.map +1 -1
  9. package/dist/entity-ops.js +13 -1
  10. package/dist/entity-ops.js.map +1 -1
  11. package/dist/generated/permissions.d.ts +1 -1
  12. package/dist/generated/permissions.d.ts.map +1 -1
  13. package/dist/generated/permissions.js +8 -0
  14. package/dist/generated/permissions.js.map +1 -1
  15. package/dist/generated/surfaces.d.ts +119 -5
  16. package/dist/generated/surfaces.d.ts.map +1 -1
  17. package/dist/generated/surfaces.js +14 -6
  18. package/dist/generated/surfaces.js.map +1 -1
  19. package/dist/hooks.d.ts +161 -18
  20. package/dist/hooks.d.ts.map +1 -1
  21. package/dist/hooks.js +682 -38
  22. package/dist/hooks.js.map +1 -1
  23. package/dist/hooks.test.js +613 -0
  24. package/dist/hooks.test.js.map +1 -1
  25. package/dist/index.d.ts +2 -2
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.js +8 -3
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +555 -21
  30. package/dist/types.d.ts +489 -13
  31. package/dist/types.d.ts.map +1 -1
  32. package/dist/use-fias-access.render.test.d.ts +12 -0
  33. package/dist/use-fias-access.render.test.d.ts.map +1 -0
  34. package/dist/use-fias-access.render.test.js +407 -0
  35. package/dist/use-fias-access.render.test.js.map +1 -0
  36. package/package.json +3 -2
  37. package/templates/default/AGENTS.md +360 -27
  38. package/templates/default/CLAUDE.md +360 -27
package/dist/index.mjs CHANGED
@@ -276,8 +276,14 @@ var FiasBridge = class {
276
276
  this.holder = null;
277
277
  this.holderListeners = /* @__PURE__ */ new Set();
278
278
  this.currentPath = "";
279
+ // Host-chrome state (useFiasChrome). `null` until init, and from a host
280
+ // older than the chrome lane — `hasChrome` tells the two apart.
281
+ this.chrome = null;
282
+ this.hasChrome = false;
283
+ this.chromeListeners = /* @__PURE__ */ new Set();
279
284
  this.hostOrigin = "";
280
285
  this.themeListeners = /* @__PURE__ */ new Set();
286
+ this.accessListeners = /* @__PURE__ */ new Set();
281
287
  this.navigationListeners = /* @__PURE__ */ new Set();
282
288
  this.stepNavigationListeners = /* @__PURE__ */ new Set();
283
289
  // Preview-state preservation (preview mode only). Keyed so multiple
@@ -331,6 +337,8 @@ var FiasBridge = class {
331
337
  this.textModels = initMsg.payload.textModels ?? null;
332
338
  this.holder = initMsg.payload.holder ?? null;
333
339
  this.currentPath = initMsg.payload.currentPath;
340
+ this.hasChrome = "chrome" in initMsg.payload;
341
+ this.chrome = initMsg.payload.chrome ?? null;
334
342
  this.initialized = true;
335
343
  this.initResolve?.();
336
344
  for (const listener of this.navigationListeners) {
@@ -339,6 +347,9 @@ var FiasBridge = class {
339
347
  for (const listener of this.holderListeners) {
340
348
  listener(this.holder);
341
349
  }
350
+ for (const listener of this.chromeListeners) {
351
+ listener(this.chrome);
352
+ }
342
353
  return;
343
354
  }
344
355
  if (this.hostOrigin && event.origin !== this.hostOrigin) return;
@@ -349,6 +360,10 @@ var FiasBridge = class {
349
360
  }
350
361
  return;
351
362
  }
363
+ if (data.type === "access_update") {
364
+ for (const listener of this.accessListeners) listener();
365
+ return;
366
+ }
352
367
  if (data.type === "text_models_update") {
353
368
  if (Array.isArray(data.payload)) {
354
369
  this.textModels = data.payload;
@@ -365,6 +380,16 @@ var FiasBridge = class {
365
380
  }
366
381
  return;
367
382
  }
383
+ if (data.type === "chrome_update") {
384
+ if (data.payload && typeof data.payload === "object") {
385
+ this.hasChrome = true;
386
+ this.chrome = data.payload;
387
+ for (const listener of this.chromeListeners) {
388
+ listener(this.chrome);
389
+ }
390
+ }
391
+ return;
392
+ }
368
393
  if (data.type === "step_navigate") {
369
394
  const { nodeId } = data.payload;
370
395
  if (typeof nodeId === "string") {
@@ -592,6 +617,37 @@ var FiasBridge = class {
592
617
  getCurrentPath() {
593
618
  return this.currentPath;
594
619
  }
620
+ /**
621
+ * The host-chrome state, or null before init / from a host without the
622
+ * chrome lane (`hasChromeSupport` tells the two apart). See `useFiasChrome`.
623
+ */
624
+ getChrome() {
625
+ return this.chrome;
626
+ }
627
+ /** True once a host has delivered chrome state (init or chrome_update). */
628
+ hasChromeSupport() {
629
+ return this.hasChrome;
630
+ }
631
+ /** Called on init and on every chrome_update. */
632
+ onChromeUpdate(listener) {
633
+ this.chromeListeners.add(listener);
634
+ return () => this.chromeListeners.delete(listener);
635
+ }
636
+ /**
637
+ * Set host-chrome state for this arche (fire-and-forget; the host echoes the
638
+ * result as chrome_update). Every present key must be a boolean — the host
639
+ * drops the whole message otherwise. See `useFiasChrome`.
640
+ */
641
+ setChrome(patch) {
642
+ this.send("chrome_set", patch);
643
+ }
644
+ /**
645
+ * Leave the arche for a host destination — a closed enum, never a URL.
646
+ * See `useFiasChrome`.
647
+ */
648
+ navigateHost(target) {
649
+ this.send("chrome_navigate", { target });
650
+ }
595
651
  /**
596
652
  * The live text-model catalog, or null until the host has delivered it (or
597
653
  * when the plugin lacks `entities:invoke`). See `useTextModels`.
@@ -620,6 +676,14 @@ var FiasBridge = class {
620
676
  this.themeListeners.add(listener);
621
677
  return () => this.themeListeners.delete(listener);
622
678
  }
679
+ /**
680
+ * The host says the person's own access may have changed (ADR-034) — after
681
+ * its access sheet changed it. Carries nothing: ask `my_access` again.
682
+ */
683
+ onAccessUpdate(listener) {
684
+ this.accessListeners.add(listener);
685
+ return () => this.accessListeners.delete(listener);
686
+ }
623
687
  onNavigationUpdate(listener) {
624
688
  this.navigationListeners.add(listener);
625
689
  return () => this.navigationListeners.delete(listener);
@@ -743,6 +807,7 @@ var FiasBridge = class {
743
807
  }
744
808
  this.pendingRequests.clear();
745
809
  this.themeListeners.clear();
810
+ this.accessListeners.clear();
746
811
  this.textModelListeners.clear();
747
812
  this.holderListeners.clear();
748
813
  this.navigationListeners.clear();
@@ -777,7 +842,7 @@ var FiasBridge = class {
777
842
  /**
778
843
  * Listens for the terminal ending of a logical realtime subscription.
779
844
  * Reasons are a closed union — `authorization_revoked` and
780
- * `collection_deleted` are definitive; the rest may be worth a fresh
845
+ * `collection_deleted` (the resource is gone) are definitive; the rest may be worth a fresh
781
846
  * subscribe once conditions change.
782
847
  */
783
848
  onSubscriptionEnded(subscriptionId, listener) {
@@ -1088,6 +1153,7 @@ var PLUGIN_WORKSPACES_ENTITY_ID = "ent_intg_plugin_workspaces_00000";
1088
1153
  var PLUGIN_STORE_ENTITY_ID = "ent_intg_plugin_store_0000000000";
1089
1154
  var VAULT_USER_DOCUMENTS_ENTITY_ID = "ent_intg_vault_documents_0000000";
1090
1155
  var VAULT_ARCHE_DOCUMENTS_ENTITY_ID = "ent_intg_vault_arche_docs_000000";
1156
+ var VAULT_CREATIVE_ENTITY_ID = "ent_intg_vault_creative_00000000";
1091
1157
  var COMMUNITY_ASSETS_ENTITY_ID = "ent_intg_community_assets_000000";
1092
1158
  var DOCUMENT_SIGN_ENTITY_ID = "ent_intg_document_sign_000000000";
1093
1159
  var COMPANY_EMAIL_SEND_ENTITY_ID = "ent_intg_company_email_send_0000";
@@ -1096,6 +1162,9 @@ var CONNECTIONS_CONNECT_ENTITY_ID = "ent_intg_connections_connect_000";
1096
1162
  var X_ENTITY_ID = "ent_intg_twitter_000000000000000";
1097
1163
  var LINKEDIN_ENTITY_ID = "ent_intg_linkedin_00000000000000";
1098
1164
  var ACTION_CONFIRMATION_ENTITY_ID = "ent_intg_action_confirmation_000";
1165
+ var APP_ACCESS_DIALOG_ENTITY_ID = "ent_intg_app_access_dialog_00000";
1166
+ var CREATIVE_PICKER_ENTITY_ID = "ent_intg_creative_picker_0000000";
1167
+ var SEND_TO_OPENER_ENTITY_ID = "ent_intg_send_to_opener_00000000";
1099
1168
  var SCHEDULED_INVOCATIONS_ENTITY_ID = "ent_intg_scheduled_invocations00";
1100
1169
  var CONFIRM_TIMEOUT_MS = 10 * 60 * 1e3;
1101
1170
  var APPROVED_CALL_TIMEOUT_MS = 2 * 60 * 1e3;
@@ -1573,6 +1642,128 @@ function usePersistentState(key, initialValue) {
1573
1642
  );
1574
1643
  return [value, setValue];
1575
1644
  }
1645
+ var INERT_CHROME = {
1646
+ focusMode: false,
1647
+ focusModeAvailable: false,
1648
+ sidebarAvailable: false,
1649
+ sidebarCollapsed: false,
1650
+ hostExpandedBar: true,
1651
+ hostExpandButton: true,
1652
+ opener: null
1653
+ };
1654
+ function useFiasChrome() {
1655
+ const bridge = useBridge();
1656
+ const [chrome, setChrome] = useState2(bridge.getChrome());
1657
+ const [supported, setSupported] = useState2(bridge.hasChromeSupport());
1658
+ useEffect2(() => {
1659
+ let cancelled = false;
1660
+ bridge.waitForInit().then(() => {
1661
+ if (cancelled) return;
1662
+ setChrome(bridge.getChrome());
1663
+ setSupported(bridge.hasChromeSupport());
1664
+ });
1665
+ const unsubscribe = bridge.onChromeUpdate((next) => {
1666
+ setChrome(next);
1667
+ setSupported(bridge.hasChromeSupport());
1668
+ });
1669
+ return () => {
1670
+ cancelled = true;
1671
+ unsubscribe();
1672
+ };
1673
+ }, [bridge]);
1674
+ useEffect2(() => {
1675
+ bridge.setChrome({ hostExpandButton: false });
1676
+ }, [bridge]);
1677
+ const setFocusMode = useCallback((on) => bridge.setChrome({ focusMode: on }), [bridge]);
1678
+ const setSidebarCollapsed = useCallback(
1679
+ (collapsed) => bridge.setChrome({ sidebarCollapsed: collapsed }),
1680
+ [bridge]
1681
+ );
1682
+ const setHostExpandedBar = useCallback(
1683
+ (shown) => bridge.setChrome({ hostExpandedBar: shown }),
1684
+ [bridge]
1685
+ );
1686
+ const goToFiasHome = useCallback(() => bridge.navigateHost("home"), [bridge]);
1687
+ const goToWorktop = useCallback(() => bridge.navigateHost("worktop"), [bridge]);
1688
+ const openAssistant = useCallback(() => bridge.openAssistant(), [bridge]);
1689
+ return useMemo2(
1690
+ () => ({
1691
+ ...chrome ?? INERT_CHROME,
1692
+ opener: chrome?.opener ?? null,
1693
+ supported: supported && chrome !== null,
1694
+ setFocusMode,
1695
+ setSidebarCollapsed,
1696
+ setHostExpandedBar,
1697
+ goToFiasHome,
1698
+ goToWorktop,
1699
+ openAssistant
1700
+ }),
1701
+ [
1702
+ chrome,
1703
+ supported,
1704
+ setFocusMode,
1705
+ setSidebarCollapsed,
1706
+ setHostExpandedBar,
1707
+ goToFiasHome,
1708
+ goToWorktop,
1709
+ openAssistant
1710
+ ]
1711
+ );
1712
+ }
1713
+ var SEND_TO_OPENER_TIMEOUT_MS = 30 * 60 * 1e3;
1714
+ function useOpener() {
1715
+ const bridge = useBridge();
1716
+ const [opener, setOpener] = useState2(bridge.getChrome()?.opener ?? null);
1717
+ useEffect2(() => {
1718
+ let cancelled = false;
1719
+ bridge.waitForInit().then(() => {
1720
+ if (!cancelled) setOpener(bridge.getChrome()?.opener ?? null);
1721
+ });
1722
+ const unsubscribe = bridge.onChromeUpdate((next) => setOpener(next?.opener ?? null));
1723
+ return () => {
1724
+ cancelled = true;
1725
+ unsubscribe();
1726
+ };
1727
+ }, [bridge]);
1728
+ const returnTo = useCallback(() => {
1729
+ if (bridge.getChrome()?.opener) bridge.navigateHost("opener");
1730
+ }, [bridge]);
1731
+ const send = useCallback(
1732
+ async (file) => {
1733
+ if (!bridge.getChrome()?.opener) {
1734
+ throw new FiasBridgeError(
1735
+ "Nothing opened this arche, so there is nowhere to send",
1736
+ "NO_OPENER"
1737
+ );
1738
+ }
1739
+ let answer;
1740
+ try {
1741
+ answer = await bridge.request(
1742
+ "client_entity_invoke",
1743
+ {
1744
+ entityId: SEND_TO_OPENER_ENTITY_ID,
1745
+ input: { fileId: file.fileId, ...file.name ? { name: file.name } : {} }
1746
+ },
1747
+ SEND_TO_OPENER_TIMEOUT_MS
1748
+ );
1749
+ } catch (err) {
1750
+ if (err instanceof FiasBridgeError && err.code === "BRIDGE_ERROR" && err.message.includes("no client-side executor")) {
1751
+ throw new FiasBridgeError(
1752
+ "Sending to another arche is not available here",
1753
+ "SEND_UNAVAILABLE"
1754
+ );
1755
+ }
1756
+ throw err;
1757
+ }
1758
+ if (answer && "error" in answer) {
1759
+ throw new FiasBridgeError(answer.error.message, answer.error.code);
1760
+ }
1761
+ return answer;
1762
+ },
1763
+ [bridge]
1764
+ );
1765
+ return useMemo2(() => ({ opener, returnTo, send }), [opener, returnTo, send]);
1766
+ }
1576
1767
  function useStepNavigation(initialStep, options) {
1577
1768
  const bridge = useBridge();
1578
1769
  const persistKey = options?.persistKey;
@@ -1674,15 +1865,34 @@ function useFiasDataStore() {
1674
1865
  "put",
1675
1866
  {
1676
1867
  collection,
1677
- key,
1868
+ // No key: the server makes one (collections with audiences, ADR-034).
1869
+ ...key !== null ? { key } : {},
1678
1870
  data,
1679
- workspaceId: options?.workspaceId
1871
+ workspaceId: options?.workspaceId,
1872
+ ...options?.audience !== void 0 ? { audience: options.audience } : {}
1680
1873
  }
1681
1874
  );
1682
- return { version: res?.version };
1875
+ return {
1876
+ version: res?.version,
1877
+ ...res?.key !== void 0 ? { key: res.key } : {},
1878
+ ...res?.audience !== void 0 ? { audience: res.audience } : {}
1879
+ };
1683
1880
  },
1684
1881
  [bridge]
1685
1882
  );
1883
+ const create = useCallback(
1884
+ async (collection, data, options) => {
1885
+ const result = await put(collection, null, data, options);
1886
+ if (result.key === void 0) {
1887
+ throw new FiasBridgeError(
1888
+ "This Fias does not support creating documents in audiences yet",
1889
+ "AUDIENCES_UNAVAILABLE"
1890
+ );
1891
+ }
1892
+ return { ...result, key: result.key };
1893
+ },
1894
+ [put]
1895
+ );
1686
1896
  const get = useCallback(
1687
1897
  async (collection, key, options) => {
1688
1898
  const res = await invokeEntityOp(
@@ -1692,7 +1902,8 @@ function useFiasDataStore() {
1692
1902
  {
1693
1903
  collection,
1694
1904
  key,
1695
- workspaceId: options?.workspaceId
1905
+ workspaceId: options?.workspaceId,
1906
+ ...options?.audience !== void 0 ? { audience: options.audience } : {}
1696
1907
  }
1697
1908
  );
1698
1909
  return res.data;
@@ -1704,7 +1915,8 @@ function useFiasDataStore() {
1704
1915
  const res = await invokeEntityOp(bridge, PLUGIN_DATASTORE_ENTITY_ID, "get", {
1705
1916
  collection,
1706
1917
  key,
1707
- workspaceId: options?.workspaceId
1918
+ workspaceId: options?.workspaceId,
1919
+ ...options?.audience !== void 0 ? { audience: options.audience } : {}
1708
1920
  });
1709
1921
  if (res.data === null || res.data === void 0) return null;
1710
1922
  return {
@@ -1712,7 +1924,8 @@ function useFiasDataStore() {
1712
1924
  data: res.data,
1713
1925
  updatedAt: res.updatedAt ?? "",
1714
1926
  ...res.version !== void 0 ? { version: res.version } : {},
1715
- ...res.authoredByCollaborator !== void 0 ? { authoredByCollaborator: res.authoredByCollaborator } : {}
1927
+ ...res.authoredByCollaborator !== void 0 ? { authoredByCollaborator: res.authoredByCollaborator } : {},
1928
+ ...res.audience !== void 0 ? { audience: res.audience } : {}
1716
1929
  };
1717
1930
  },
1718
1931
  [bridge]
@@ -1729,7 +1942,8 @@ function useFiasDataStore() {
1729
1942
  // answered WORKSPACE_REQUIRED, pointing the author at the collection
1730
1943
  // rather than at the call. Take the scope arg when it carries one and
1731
1944
  // otherwise leave whatever the spread put there.
1732
- ...scope?.workspaceId !== void 0 ? { workspaceId: scope.workspaceId } : {}
1945
+ ...scope?.workspaceId !== void 0 ? { workspaceId: scope.workspaceId } : {},
1946
+ ...scope?.audience !== void 0 ? { audience: scope.audience } : {}
1733
1947
  });
1734
1948
  },
1735
1949
  [bridge]
@@ -1745,7 +1959,8 @@ function useFiasDataStore() {
1745
1959
  query: queryText,
1746
1960
  ...options,
1747
1961
  // See `query` above — never blank a caller-supplied workspaceId.
1748
- ...scope?.workspaceId !== void 0 ? { workspaceId: scope.workspaceId } : {}
1962
+ ...scope?.workspaceId !== void 0 ? { workspaceId: scope.workspaceId } : {},
1963
+ ...scope?.audience !== void 0 ? { audience: scope.audience } : {}
1749
1964
  }
1750
1965
  );
1751
1966
  return res.matches;
@@ -1783,6 +1998,7 @@ function useFiasDataStore() {
1783
1998
  listCollections,
1784
1999
  deleteCollection,
1785
2000
  put,
2001
+ create,
1786
2002
  get,
1787
2003
  getDocument,
1788
2004
  query,
@@ -1795,6 +2011,7 @@ function useFiasDataStore() {
1795
2011
  listCollections,
1796
2012
  deleteCollection,
1797
2013
  put,
2014
+ create,
1798
2015
  get,
1799
2016
  getDocument,
1800
2017
  query,
@@ -1928,6 +2145,213 @@ function useFiasWorkspaces() {
1928
2145
  ]
1929
2146
  );
1930
2147
  }
2148
+ var NO_WORKSPACE_ACCESS_CODES = /* @__PURE__ */ new Set([
2149
+ "NOT_A_MEMBER",
2150
+ "WORKSPACE_NOT_FOUND"
2151
+ ]);
2152
+ function useFiasAccess(workspaceId) {
2153
+ const bridge = useBridge();
2154
+ const known = workspaceId ? peekSharedAccess(bridge, workspaceId) : void 0;
2155
+ const [settled, setSettled] = useState2(known?.settled ?? null);
2156
+ const [fetching, setFetching] = useState2(known ? known.fetching : !!workspaceId);
2157
+ useEffect2(() => {
2158
+ if (!workspaceId) return;
2159
+ const shared = sharedAccessFor(bridge, workspaceId);
2160
+ const sync = () => {
2161
+ setSettled(shared.settled);
2162
+ setFetching(shared.fetching);
2163
+ };
2164
+ const unsubscribe = subscribeSharedAccess(shared, sync);
2165
+ askSharedAccess(shared, "ensure");
2166
+ sync();
2167
+ return unsubscribe;
2168
+ }, [bridge, workspaceId]);
2169
+ const refetch = useCallback(() => {
2170
+ if (workspaceId) askSharedAccess(sharedAccessFor(bridge, workspaceId), "again");
2171
+ }, [bridge, workspaceId]);
2172
+ const openAccessDialog = useCallback(async () => {
2173
+ let answer;
2174
+ try {
2175
+ answer = await bridge.request(
2176
+ "client_entity_invoke",
2177
+ { entityId: APP_ACCESS_DIALOG_ENTITY_ID, input: {} },
2178
+ ACCESS_SHEET_TIMEOUT_MS
2179
+ );
2180
+ } catch (err) {
2181
+ if (err instanceof FiasBridgeError && err.code === "BRIDGE_ERROR" && err.message.includes("no client-side executor")) {
2182
+ throw new FiasBridgeError("Managing access is not available here", "ACCESS_UNAVAILABLE");
2183
+ }
2184
+ throw err;
2185
+ }
2186
+ if (answer && "error" in answer) {
2187
+ throw new FiasBridgeError(answer.error.message, answer.error.code);
2188
+ }
2189
+ const changed = answer?.changed === true;
2190
+ if (changed) refetch();
2191
+ return { changed };
2192
+ }, [bridge, refetch]);
2193
+ const own = workspaceId && settled?.workspaceId === workspaceId ? settled : null;
2194
+ const current = known ? known.settled : own;
2195
+ const access = current?.access ?? null;
2196
+ const error = current?.error ?? null;
2197
+ const isLoading = !!workspaceId && (current === null || (known ? known.fetching : fetching));
2198
+ const can = useCallback(
2199
+ (permission) => access?.mode === "roles" && access.permissions.includes(permission),
2200
+ [access]
2201
+ );
2202
+ const canReadAudience = useCallback(
2203
+ (audience) => access?.mode === "roles" && (access.audiences?.readable.includes(audience) ?? false),
2204
+ [access]
2205
+ );
2206
+ const canDiscloseTo = useCallback(
2207
+ (audience) => access?.mode === "roles" && (access.audiences?.disclosable.includes(audience) ?? false),
2208
+ [access]
2209
+ );
2210
+ return useMemo2(
2211
+ () => ({
2212
+ access,
2213
+ isLoading,
2214
+ error,
2215
+ refetch,
2216
+ can,
2217
+ canReadAudience,
2218
+ canDiscloseTo,
2219
+ openAccessDialog
2220
+ }),
2221
+ [access, isLoading, error, refetch, can, canReadAudience, canDiscloseTo, openAccessDialog]
2222
+ );
2223
+ }
2224
+ var lastAccessAnswers = /* @__PURE__ */ new Map();
2225
+ var FOCUS_REFETCH_MIN_MS = 3e4;
2226
+ var ACCESS_SHEET_TIMEOUT_MS = 30 * 60 * 1e3;
2227
+ var sharedAccessByBridge = /* @__PURE__ */ new WeakMap();
2228
+ function peekSharedAccess(bridge, workspaceId) {
2229
+ return sharedAccessByBridge.get(bridge)?.get(workspaceId);
2230
+ }
2231
+ function sharedAccessFor(bridge, workspaceId) {
2232
+ let byWorkspace = sharedAccessByBridge.get(bridge);
2233
+ if (!byWorkspace) {
2234
+ byWorkspace = /* @__PURE__ */ new Map();
2235
+ sharedAccessByBridge.set(bridge, byWorkspace);
2236
+ }
2237
+ let shared = byWorkspace.get(workspaceId);
2238
+ if (!shared) {
2239
+ shared = {
2240
+ bridge,
2241
+ workspaceId,
2242
+ settled: null,
2243
+ fetching: false,
2244
+ answered: false,
2245
+ answeredAt: 0,
2246
+ inFlight: null,
2247
+ scheduled: false,
2248
+ again: false,
2249
+ listeners: /* @__PURE__ */ new Set(),
2250
+ stopListening: null
2251
+ };
2252
+ byWorkspace.set(workspaceId, shared);
2253
+ }
2254
+ return shared;
2255
+ }
2256
+ function releaseSharedAccess(shared) {
2257
+ if (shared.listeners.size > 0 || shared.inFlight || shared.scheduled) return;
2258
+ const byWorkspace = sharedAccessByBridge.get(shared.bridge);
2259
+ if (byWorkspace?.get(shared.workspaceId) === shared) byWorkspace.delete(shared.workspaceId);
2260
+ }
2261
+ function notifySharedAccess(shared) {
2262
+ for (const listener of Array.from(shared.listeners)) listener();
2263
+ }
2264
+ function subscribeSharedAccess(shared, listener) {
2265
+ shared.listeners.add(listener);
2266
+ if (!shared.stopListening) {
2267
+ const unsubscribe = shared.bridge.onAccessUpdate(() => askSharedAccess(shared, "again"));
2268
+ const onFocus = () => {
2269
+ if (Date.now() - shared.answeredAt >= FOCUS_REFETCH_MIN_MS) {
2270
+ askSharedAccess(shared, "join");
2271
+ }
2272
+ };
2273
+ const win = typeof window === "undefined" ? null : window;
2274
+ win?.addEventListener("focus", onFocus);
2275
+ shared.stopListening = () => {
2276
+ unsubscribe();
2277
+ win?.removeEventListener("focus", onFocus);
2278
+ };
2279
+ }
2280
+ return () => {
2281
+ shared.listeners.delete(listener);
2282
+ if (shared.listeners.size === 0 && shared.stopListening) {
2283
+ shared.stopListening();
2284
+ shared.stopListening = null;
2285
+ }
2286
+ releaseSharedAccess(shared);
2287
+ };
2288
+ }
2289
+ function askSharedAccess(shared, demand) {
2290
+ if (shared.inFlight) {
2291
+ if (demand === "again") shared.again = true;
2292
+ return;
2293
+ }
2294
+ if (shared.scheduled) return;
2295
+ if (demand === "ensure" && shared.answered) return;
2296
+ shared.scheduled = true;
2297
+ void Promise.resolve().then(() => {
2298
+ shared.scheduled = false;
2299
+ startSharedAccess(shared);
2300
+ });
2301
+ }
2302
+ function settleSharedAccess(shared, access) {
2303
+ const answer = JSON.stringify(access);
2304
+ const before = lastAccessAnswers.get(shared.workspaceId);
2305
+ const noAccess = access.mode === "tier" && access.tier === null;
2306
+ if (noAccess || before !== void 0 && before !== answer) clearSdkReadCaches();
2307
+ lastAccessAnswers.set(shared.workspaceId, answer);
2308
+ shared.answered = true;
2309
+ shared.answeredAt = Date.now();
2310
+ shared.settled = { workspaceId: shared.workspaceId, access, error: null };
2311
+ }
2312
+ function startSharedAccess(shared) {
2313
+ if (shared.listeners.size === 0) {
2314
+ releaseSharedAccess(shared);
2315
+ return;
2316
+ }
2317
+ const { bridge, workspaceId } = shared;
2318
+ const background = shared.answered;
2319
+ if (!background) {
2320
+ shared.fetching = true;
2321
+ notifySharedAccess(shared);
2322
+ }
2323
+ shared.inFlight = (async () => {
2324
+ try {
2325
+ const res = await invokeEntityOp(
2326
+ bridge,
2327
+ PLUGIN_WORKSPACES_ENTITY_ID,
2328
+ "my_access",
2329
+ { workspaceId }
2330
+ );
2331
+ if (shared.listeners.size > 0) settleSharedAccess(shared, res.access);
2332
+ } catch (err) {
2333
+ if (shared.listeners.size === 0) {
2334
+ } else if (err instanceof FiasBridgeError && NO_WORKSPACE_ACCESS_CODES.has(err.code)) {
2335
+ settleSharedAccess(shared, { mode: "tier", workspaceId, tier: null, expiresAt: null });
2336
+ } else if (!background) {
2337
+ shared.settled = {
2338
+ workspaceId,
2339
+ access: null,
2340
+ error: err instanceof Error ? err : new Error(String(err))
2341
+ };
2342
+ }
2343
+ } finally {
2344
+ shared.fetching = false;
2345
+ shared.inFlight = null;
2346
+ if (shared.again) {
2347
+ shared.again = false;
2348
+ askSharedAccess(shared, "again");
2349
+ }
2350
+ notifySharedAccess(shared);
2351
+ releaseSharedAccess(shared);
2352
+ }
2353
+ })();
2354
+ }
1931
2355
  function useFiasStore() {
1932
2356
  const bridge = useBridge();
1933
2357
  const [products, setProducts] = useState2([]);
@@ -2013,6 +2437,12 @@ function useFiasStore() {
2013
2437
  var DOWNLOAD_URL_REFRESH_BUFFER_MS = 6e4;
2014
2438
  var downloadUrlCache = /* @__PURE__ */ new Map();
2015
2439
  var downloadUrlInflight = /* @__PURE__ */ new Map();
2440
+ function clearSdkReadCaches() {
2441
+ downloadUrlGeneration += 1;
2442
+ downloadUrlCache.clear();
2443
+ downloadUrlInflight.clear();
2444
+ }
2445
+ var downloadUrlGeneration = 0;
2016
2446
  async function fetchVaultDocumentDownloadUrl(documentId) {
2017
2447
  const now = Date.now();
2018
2448
  const cached = downloadUrlCache.get(documentId);
@@ -2022,6 +2452,7 @@ async function fetchVaultDocumentDownloadUrl(documentId) {
2022
2452
  const inflight = downloadUrlInflight.get(documentId);
2023
2453
  if (inflight) return inflight;
2024
2454
  const bridge = getBridge();
2455
+ const generation = downloadUrlGeneration;
2025
2456
  const promise = bridge.request(
2026
2457
  "vault_documents_download_url",
2027
2458
  { documentId }
@@ -2031,14 +2462,16 @@ async function fetchVaultDocumentDownloadUrl(documentId) {
2031
2462
  expiresAt: new Date(result.expiresAt).getTime(),
2032
2463
  contentType: result.contentType
2033
2464
  };
2034
- downloadUrlCache.set(documentId, {
2035
- url: normalized.url,
2036
- expiresAt: normalized.expiresAt,
2037
- contentType: normalized.contentType
2038
- });
2465
+ if (generation === downloadUrlGeneration) {
2466
+ downloadUrlCache.set(documentId, {
2467
+ url: normalized.url,
2468
+ expiresAt: normalized.expiresAt,
2469
+ contentType: normalized.contentType
2470
+ });
2471
+ }
2039
2472
  return normalized;
2040
2473
  }).finally(() => {
2041
- downloadUrlInflight.delete(documentId);
2474
+ if (downloadUrlInflight.get(documentId) === promise) downloadUrlInflight.delete(documentId);
2042
2475
  });
2043
2476
  downloadUrlInflight.set(documentId, promise);
2044
2477
  return promise;
@@ -2799,13 +3232,22 @@ function useFiasPreviewState(key, getState, onRestore) {
2799
3232
  );
2800
3233
  }, [bridge, key]);
2801
3234
  }
2802
- function useDataSubscription(collection, onInvalidate, options) {
3235
+ function stableStringify(value) {
3236
+ return JSON.stringify(
3237
+ value,
3238
+ (_key, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(
3239
+ Object.keys(v).sort().map((k) => [k, v[k]])
3240
+ ) : v
3241
+ );
3242
+ }
3243
+ function useEntitySubscription(entityId, payload, onInvalidate, options) {
2803
3244
  const bridge = useBridge();
2804
3245
  const [state, setState] = useState2({ status: "subscribing" });
2805
3246
  const onInvalidateRef = useRef(onInvalidate);
2806
3247
  onInvalidateRef.current = onInvalidate;
2807
3248
  const enabled = options?.enabled ?? true;
2808
- const workspaceId = options?.workspaceId;
3249
+ const minRefetchIntervalMs = Math.max(0, options?.minRefetchIntervalMs ?? 0);
3250
+ const payloadKey = stableStringify(payload);
2809
3251
  useEffect2(() => {
2810
3252
  if (!enabled) {
2811
3253
  setState({ status: "ended", endedReason: "unsubscribed" });
@@ -2817,12 +3259,27 @@ function useDataSubscription(collection, onInvalidate, options) {
2817
3259
  let unsubscribeEnded = null;
2818
3260
  let refetching = false;
2819
3261
  let trailing = false;
3262
+ let lastRunAt = -Infinity;
3263
+ let intervalTimer = null;
2820
3264
  const runInvalidate = async () => {
2821
- if (refetching) {
3265
+ if (refetching || intervalTimer) {
3266
+ trailing = true;
3267
+ return;
3268
+ }
3269
+ const wait = lastRunAt + minRefetchIntervalMs - Date.now();
3270
+ if (wait > 0) {
2822
3271
  trailing = true;
3272
+ intervalTimer = setTimeout(() => {
3273
+ intervalTimer = null;
3274
+ if (trailing && !cancelled) {
3275
+ trailing = false;
3276
+ void runInvalidate();
3277
+ }
3278
+ }, wait);
2823
3279
  return;
2824
3280
  }
2825
3281
  refetching = true;
3282
+ lastRunAt = Date.now();
2826
3283
  try {
2827
3284
  await onInvalidateRef.current();
2828
3285
  } catch {
@@ -2837,8 +3294,8 @@ function useDataSubscription(collection, onInvalidate, options) {
2837
3294
  void (async () => {
2838
3295
  try {
2839
3296
  const res = await bridge.request("entity_invoke", {
2840
- entityId: PLUGIN_DATASTORE_ENTITY_ID,
2841
- payload: { op: "subscribe", collection, workspaceId }
3297
+ entityId,
3298
+ payload: JSON.parse(payloadKey)
2842
3299
  });
2843
3300
  const subscription = res?.output;
2844
3301
  if (!subscription || subscription.kind !== "subscription" || typeof subscription.subscriptionId !== "string") {
@@ -2862,13 +3319,28 @@ function useDataSubscription(collection, onInvalidate, options) {
2862
3319
  })();
2863
3320
  return () => {
2864
3321
  cancelled = true;
3322
+ if (intervalTimer) clearTimeout(intervalTimer);
2865
3323
  unsubscribeEvent?.();
2866
3324
  unsubscribeEnded?.();
2867
3325
  if (subscriptionId) bridge.releaseSubscription(subscriptionId);
2868
3326
  };
2869
- }, [bridge, collection, workspaceId, enabled]);
3327
+ }, [bridge, entityId, payloadKey, enabled, minRefetchIntervalMs]);
2870
3328
  return state;
2871
3329
  }
3330
+ function useDataSubscription(collection, onInvalidate, options) {
3331
+ const audience = options?.audience;
3332
+ return useEntitySubscription(
3333
+ PLUGIN_DATASTORE_ENTITY_ID,
3334
+ {
3335
+ op: "subscribe",
3336
+ collection,
3337
+ workspaceId: options?.workspaceId,
3338
+ ...audience !== void 0 ? { audience } : {}
3339
+ },
3340
+ onInvalidate,
3341
+ { enabled: options?.enabled }
3342
+ );
3343
+ }
2872
3344
  function useVaultUserDocuments() {
2873
3345
  const bridge = useBridge();
2874
3346
  const pick = useCallback(
@@ -3068,6 +3540,51 @@ async function fetchCommunityAssetUrls(bridge, assetIds) {
3068
3540
  }
3069
3541
  return hits;
3070
3542
  }
3543
+ var CREATIVE_PICKER_TIMEOUT_MS = 30 * 60 * 1e3;
3544
+ function useCreativeLibrary() {
3545
+ const bridge = useBridge();
3546
+ return useMemo2(() => {
3547
+ const item = async (op, args) => (await invokeEntityOp(bridge, VAULT_CREATIVE_ENTITY_ID, op, args)).item;
3548
+ return {
3549
+ list: async (options = {}) => (await invokeEntityOp(
3550
+ bridge,
3551
+ VAULT_CREATIVE_ENTITY_ID,
3552
+ "list_items",
3553
+ { ...options }
3554
+ )).items,
3555
+ get: (itemId, version) => item("get_item", { itemId, ...version !== void 0 ? { version } : {} }),
3556
+ create: (fields) => item("create_item", { referenceDocumentIds: [], ...fields }),
3557
+ update: (itemId, fields) => item("update_item", { itemId, ...fields }),
3558
+ remove: (itemId) => item("delete_item", { itemId }),
3559
+ restore: (itemId) => item("restore_item", { itemId }),
3560
+ pick: async (options = {}) => {
3561
+ let answer;
3562
+ try {
3563
+ answer = await bridge.request(
3564
+ "client_entity_invoke",
3565
+ {
3566
+ entityId: CREATIVE_PICKER_ENTITY_ID,
3567
+ input: options.kind ? { kind: options.kind } : {}
3568
+ },
3569
+ CREATIVE_PICKER_TIMEOUT_MS
3570
+ );
3571
+ } catch (err) {
3572
+ if (err instanceof FiasBridgeError && err.code === "BRIDGE_ERROR" && err.message.includes("no client-side executor")) {
3573
+ throw new FiasBridgeError(
3574
+ "The Creative Library picker is not available here",
3575
+ "PICKER_UNAVAILABLE"
3576
+ );
3577
+ }
3578
+ throw err;
3579
+ }
3580
+ if (answer && "error" in answer) {
3581
+ throw new FiasBridgeError(answer.error.message, answer.error.code);
3582
+ }
3583
+ return answer;
3584
+ }
3585
+ };
3586
+ }, [bridge]);
3587
+ }
3071
3588
  var CONNECT_TIMEOUT_MS = 10 * 60 * 1e3;
3072
3589
  function useFiasConnections() {
3073
3590
  const bridge = useBridge();
@@ -3422,6 +3939,8 @@ var AUDIO_VOICE_DESIGN_SURFACE_KEY = "audio.voice-design";
3422
3939
  var useElevenLabsVoiceDesign = () => useSurface(
3423
3940
  AUDIO_VOICE_DESIGN_SURFACE_KEY
3424
3941
  );
3942
+ var IMAGE_CREATE_ASSET_SURFACE_KEY = "image.create-asset";
3943
+ var useCreateAsset = () => useSurface(IMAGE_CREATE_ASSET_SURFACE_KEY);
3425
3944
  var IMAGE_SMART_CONTROL_SURFACE_KEY = "image.smart-control";
3426
3945
  var useSmartControl = () => useSurface(IMAGE_SMART_CONTROL_SURFACE_KEY);
3427
3946
  var IMAGE_SMART_ERASE_SURFACE_KEY = "image.smart-erase";
@@ -3478,7 +3997,9 @@ var VALID_PLUGIN_PERMISSIONS = [
3478
3997
  "entities:image_generate",
3479
3998
  "entities:invoke",
3480
3999
  "entities:web_search",
4000
+ "litcraft:hub",
3481
4001
  "navigation:open_arche",
4002
+ "opener:send",
3482
4003
  "sandbox:vendored-libraries",
3483
4004
  "schedule:invoke",
3484
4005
  "storage:local",
@@ -3487,6 +4008,8 @@ var VALID_PLUGIN_PERMISSIONS = [
3487
4008
  "theme:read",
3488
4009
  "user:profile:read",
3489
4010
  "vault:arche-saves:write",
4011
+ "vault:creative:read",
4012
+ "vault:creative:write",
3490
4013
  "vault:documents:read",
3491
4014
  "vault:documents:write",
3492
4015
  "vault:user-documents:edit",
@@ -3515,7 +4038,9 @@ var PLUGIN_PERMISSION_DESCRIPTIONS = {
3515
4038
  "entities:image_generate": "Generate images with platform AI \u2014 costs credits.",
3516
4039
  "entities:invoke": "Use platform AI (text) on your behalf \u2014 costs credits.",
3517
4040
  "entities:web_search": "Search the live web on your behalf \u2014 costs credits.",
4041
+ "litcraft:hub": "See your LitCraft profile, stats and inventory, and the LitCraft community leaderboards, play time and activity. It can hide your name on leaderboards, but only you can show it again.",
3518
4042
  "navigation:open_arche": "Open other arches for you (navigation only).",
4043
+ "opener:send": "Send pictures it made to the arche you came from, like your Fias Forge \u2014 you confirm each one.",
3519
4044
  "sandbox:vendored-libraries": "Load platform-hosted libraries (like the PDF renderer) from the trusted CDN.",
3520
4045
  "schedule:invoke": "Schedule an action for later \u2014 like a post at 9 AM tomorrow. You approve exactly what will be sent and when, and can see or cancel what it scheduled.",
3521
4046
  "storage:local": "Keep its working files on this device, so a refresh or crash does not lose your work. Nothing leaves your device; cleared when you sign out or when someone else signs in here.",
@@ -3524,6 +4049,8 @@ var PLUGIN_PERMISSION_DESCRIPTIONS = {
3524
4049
  "theme:read": "Match your platform theme.",
3525
4050
  "user:profile:read": "See your display name and avatar.",
3526
4051
  "vault:arche-saves:write": "Saves files to your Arche Saves automatically.",
4052
+ "vault:creative:read": "See and use your styles and characters, including their reference pictures \u2014 in a team workspace only after its owner or admin approves it there.",
4053
+ "vault:creative:write": "Create and change your styles and characters. Earlier versions are kept.",
3527
4054
  "vault:documents:read": "Read files this arche itself created, in its own folders of your Vault.",
3528
4055
  "vault:documents:write": "Create and manage its own files, in its own folder of your Vault.",
3529
4056
  "vault:user-documents:edit": "Save changes to files of yours that you let it edit \u2014 you choose each file, earlier versions are kept unless you turned version history off, and you can remove access any time in Vault \u2192 Arche Access.",
@@ -6092,6 +6619,7 @@ export {
6092
6619
  FiasLocalStoreError,
6093
6620
  FiasProvider,
6094
6621
  GRAMMAR_CHECK_ENTITY_ID,
6622
+ IMAGE_CREATE_ASSET_SURFACE_KEY,
6095
6623
  IMAGE_SMART_CONTROL_SURFACE_KEY,
6096
6624
  IMAGE_SMART_ERASE_SURFACE_KEY,
6097
6625
  IMAGE_SMART_IMAGE_TO_IMAGE_SURFACE_KEY,
@@ -6156,6 +6684,8 @@ export {
6156
6684
  useClientEntity,
6157
6685
  useCommunityAssets,
6158
6686
  useCompanyEmail,
6687
+ useCreateAsset,
6688
+ useCreativeLibrary,
6159
6689
  useDataSubscription,
6160
6690
  useDocumentSign,
6161
6691
  useElevenLabsAudioIsolation,
@@ -6167,7 +6697,10 @@ export {
6167
6697
  useElevenLabsVoiceChanger,
6168
6698
  useElevenLabsVoiceDesign,
6169
6699
  useEntityInvocation,
6700
+ useEntitySubscription,
6170
6701
  useFiasAIActions,
6702
+ useFiasAccess,
6703
+ useFiasChrome,
6171
6704
  useFiasConnections,
6172
6705
  useFiasContent,
6173
6706
  useFiasDataStore,
@@ -6191,6 +6724,7 @@ export {
6191
6724
  useIntegrationWebSearch,
6192
6725
  useLocalStore,
6193
6726
  useLyriaMusic,
6727
+ useOpener,
6194
6728
  usePersistentState,
6195
6729
  useSmartControl,
6196
6730
  useSmartErase,