sh3-core 0.17.0 → 0.19.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 (154) hide show
  1. package/dist/Sh3.svelte +107 -39
  2. package/dist/__screenshots__/handheld.browser.test.ts/handheld-viewport-flip-e2e-viewport-override-flips-chrome-and-body-branches-1.png +0 -0
  3. package/dist/actions/CommandPalette.svelte +1 -2
  4. package/dist/actions/listActionsFromEntries.test.js +29 -0
  5. package/dist/actions/listActive.js +2 -0
  6. package/dist/actions/listeners.js +16 -1
  7. package/dist/actions/programmatic-dispatch.svelte.test.js +9 -2
  8. package/dist/actions/types.d.ts +8 -0
  9. package/dist/api.d.ts +8 -1
  10. package/dist/app/store/storeShard.svelte.js +1 -21
  11. package/dist/app/store/version.d.ts +11 -0
  12. package/dist/app/store/version.js +39 -0
  13. package/dist/app/store/version.test.d.ts +1 -0
  14. package/dist/app/store/version.test.js +44 -0
  15. package/dist/apps/lifecycle.d.ts +6 -0
  16. package/dist/apps/lifecycle.js +5 -2
  17. package/dist/apps/lifecycle.test.js +30 -0
  18. package/dist/apps/types.d.ts +12 -0
  19. package/dist/assets/iconIds.generated.d.ts +1 -1
  20. package/dist/assets/iconIds.generated.js +5 -0
  21. package/dist/assets/icons.svg +31 -0
  22. package/dist/auth/auth.svelte.js +18 -8
  23. package/dist/auth/types.d.ts +6 -0
  24. package/dist/chrome/CompactChrome.svelte +130 -0
  25. package/dist/chrome/CompactChrome.svelte.d.ts +3 -0
  26. package/dist/chrome/CompactChrome.svelte.test.d.ts +1 -0
  27. package/dist/chrome/CompactChrome.svelte.test.js +174 -0
  28. package/dist/chrome/MenuSheet.svelte +224 -0
  29. package/dist/chrome/MenuSheet.svelte.d.ts +7 -0
  30. package/dist/chrome/MenuSheet.svelte.test.d.ts +1 -0
  31. package/dist/chrome/MenuSheet.svelte.test.js +46 -0
  32. package/dist/createShell.d.ts +9 -0
  33. package/dist/createShell.js +20 -7
  34. package/dist/createShell.remoteAuth.test.d.ts +1 -0
  35. package/dist/createShell.remoteAuth.test.js +71 -0
  36. package/dist/documents/http-backend.js +12 -11
  37. package/dist/env/client.js +11 -5
  38. package/dist/files/types.d.ts +106 -0
  39. package/dist/files/types.js +1 -0
  40. package/dist/gestures/gestureRegistry.d.ts +6 -0
  41. package/dist/gestures/gestureRegistry.js +190 -0
  42. package/dist/gestures/gestureRegistry.test.d.ts +1 -0
  43. package/dist/gestures/gestureRegistry.test.js +119 -0
  44. package/dist/gestures/index.d.ts +6 -0
  45. package/dist/gestures/index.js +12 -0
  46. package/dist/gestures/pointerClaim.d.ts +7 -0
  47. package/dist/gestures/pointerClaim.js +36 -0
  48. package/dist/gestures/pointerClaim.test.d.ts +1 -0
  49. package/dist/gestures/pointerClaim.test.js +64 -0
  50. package/dist/gestures/types.d.ts +83 -0
  51. package/dist/gestures/types.js +1 -0
  52. package/dist/handheld.browser.test.d.ts +1 -0
  53. package/dist/handheld.browser.test.js +90 -0
  54. package/dist/host-entry.d.ts +1 -0
  55. package/dist/host-entry.js +1 -0
  56. package/dist/layout/LayoutRenderer.browser.test.js +15 -3
  57. package/dist/layout/LayoutRenderer.svelte +27 -3
  58. package/dist/layout/LayoutRenderer.svelte.d.ts +4 -1
  59. package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-3-splitter-drag-updates-split-sizes-when-the-splitter-handle-is-dragged-1.png +0 -0
  60. package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-5-splitter-collapse-toggle-toggles-collapsed-i--on-double-click-1.png +0 -0
  61. package/dist/layout/__screenshots__/LayoutRenderer.browser.test.ts/LayoutRenderer-browser---E-6-fixed-slots-hides-the-collapse-widget-on-a-fixed-pane-but-keeps-it-on-panes-with-a-non-fixed-neighbor-1.png +0 -0
  62. package/dist/layout/compact/CarouselTabs.svelte +361 -0
  63. package/dist/layout/compact/CarouselTabs.svelte.d.ts +10 -0
  64. package/dist/layout/compact/CarouselTabs.svelte.test.d.ts +1 -0
  65. package/dist/layout/compact/CarouselTabs.svelte.test.js +300 -0
  66. package/dist/layout/compact/CompactRenderer.svelte +53 -0
  67. package/dist/layout/compact/CompactRenderer.svelte.d.ts +3 -0
  68. package/dist/layout/compact/CompactRenderer.svelte.test.d.ts +1 -0
  69. package/dist/layout/compact/CompactRenderer.svelte.test.js +125 -0
  70. package/dist/layout/compact/derive.d.ts +3 -0
  71. package/dist/layout/compact/derive.js +157 -0
  72. package/dist/layout/compact/derive.test.d.ts +1 -0
  73. package/dist/layout/compact/derive.test.js +197 -0
  74. package/dist/layout/compact/drawerStore.svelte.d.ts +21 -0
  75. package/dist/layout/compact/drawerStore.svelte.js +75 -0
  76. package/dist/layout/compact/drawerStore.svelte.test.d.ts +1 -0
  77. package/dist/layout/compact/drawerStore.svelte.test.js +43 -0
  78. package/dist/layout/compact/enrichCarousels.d.ts +8 -0
  79. package/dist/layout/compact/enrichCarousels.js +44 -0
  80. package/dist/layout/compact/enrichCarousels.test.d.ts +1 -0
  81. package/dist/layout/compact/enrichCarousels.test.js +88 -0
  82. package/dist/layout/compact/resolveRole.d.ts +6 -0
  83. package/dist/layout/compact/resolveRole.js +13 -0
  84. package/dist/layout/compact/resolveRole.test.d.ts +1 -0
  85. package/dist/layout/compact/resolveRole.test.js +18 -0
  86. package/dist/layout/compact/types.d.ts +30 -0
  87. package/dist/layout/compact/types.js +15 -0
  88. package/dist/layout/drag.svelte.js +13 -0
  89. package/dist/layout/presets.compactVariant.test.d.ts +1 -0
  90. package/dist/layout/presets.compactVariant.test.js +27 -0
  91. package/dist/layout/presets.d.ts +12 -0
  92. package/dist/layout/presets.js +16 -0
  93. package/dist/layout/store.drawers.svelte.test.d.ts +1 -0
  94. package/dist/layout/store.drawers.svelte.test.js +49 -0
  95. package/dist/layout/store.schemaVersion.test.d.ts +1 -0
  96. package/dist/layout/store.schemaVersion.test.js +35 -0
  97. package/dist/layout/store.svelte.js +52 -2
  98. package/dist/layout/types.d.ts +51 -1
  99. package/dist/layout/types.js +1 -1
  100. package/dist/layout/types.test.d.ts +1 -0
  101. package/dist/layout/types.test.js +26 -0
  102. package/dist/overlays/DrawerSurface.svelte +141 -0
  103. package/dist/overlays/DrawerSurface.svelte.d.ts +12 -0
  104. package/dist/overlays/DrawerSurface.svelte.test.d.ts +1 -0
  105. package/dist/overlays/DrawerSurface.svelte.test.js +67 -0
  106. package/dist/overlays/ModalFrame.svelte +3 -1
  107. package/dist/overlays/ModalFrame.svelte.d.ts +1 -0
  108. package/dist/overlays/OverlayRoots.svelte +12 -9
  109. package/dist/overlays/floatDismiss.js +5 -0
  110. package/dist/overlays/focusTrap.d.ts +11 -1
  111. package/dist/overlays/focusTrap.js +11 -9
  112. package/dist/overlays/modal.js +1 -0
  113. package/dist/overlays/popup.js +4 -0
  114. package/dist/overlays/types.d.ts +10 -1
  115. package/dist/primitives/Button.svelte +18 -0
  116. package/dist/primitives/Button.svelte.d.ts +6 -0
  117. package/dist/primitives/ResizableSplitter.svelte +71 -11
  118. package/dist/primitives/ResizableSplitter.svelte.d.ts +8 -0
  119. package/dist/primitives/ResizableSplitter.svelte.test.d.ts +1 -0
  120. package/dist/primitives/ResizableSplitter.svelte.test.js +74 -0
  121. package/dist/server-shard/types.d.ts +2 -1
  122. package/dist/sh3Api/headless.js +9 -1
  123. package/dist/sh3Api/headless.svelte.test.js +45 -1
  124. package/dist/sh3Runtime.svelte.d.ts +36 -0
  125. package/dist/sh3Runtime.svelte.js +33 -0
  126. package/dist/shards/activate.svelte.js +10 -0
  127. package/dist/shards/ctx-fetch.test.d.ts +1 -0
  128. package/dist/shards/ctx-fetch.test.js +66 -0
  129. package/dist/shards/types.d.ts +22 -1
  130. package/dist/tokens.css +3 -2
  131. package/dist/transport/apiFetch.d.ts +1 -0
  132. package/dist/transport/apiFetch.js +65 -0
  133. package/dist/transport/apiFetch.test.d.ts +1 -0
  134. package/dist/transport/apiFetch.test.js +37 -0
  135. package/dist/transport/authToken.d.ts +2 -0
  136. package/dist/transport/authToken.js +53 -0
  137. package/dist/transport/authToken.test.d.ts +1 -0
  138. package/dist/transport/authToken.test.js +33 -0
  139. package/dist/verbs/types.d.ts +5 -2
  140. package/dist/version.d.ts +1 -1
  141. package/dist/version.js +1 -1
  142. package/dist/viewport/classify.d.ts +8 -0
  143. package/dist/viewport/classify.js +20 -0
  144. package/dist/viewport/classify.test.d.ts +1 -0
  145. package/dist/viewport/classify.test.js +32 -0
  146. package/dist/viewport/store.browser.test.d.ts +1 -0
  147. package/dist/viewport/store.browser.test.js +33 -0
  148. package/dist/viewport/store.svelte.d.ts +9 -0
  149. package/dist/viewport/store.svelte.js +71 -0
  150. package/dist/viewport/store.svelte.test.d.ts +1 -0
  151. package/dist/viewport/store.svelte.test.js +54 -0
  152. package/dist/viewport/types.d.ts +9 -0
  153. package/dist/viewport/types.js +6 -0
  154. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,46 @@
1
+ /*
2
+ * DOM smoke for MenuSheet — verifies open/closed rendering. The
3
+ * container/item resolution is exercised via the same model functions
4
+ * MenuBar uses; their unit tests cover the resolution semantics so
5
+ * this test only asserts the wrapper structure.
6
+ */
7
+ import { describe, it, expect, afterEach } from 'vitest';
8
+ import { mount, unmount, flushSync } from 'svelte';
9
+ import MenuSheet from './MenuSheet.svelte';
10
+ const MenuSheetAny = MenuSheet;
11
+ let mounted = null;
12
+ let host = null;
13
+ afterEach(() => {
14
+ if (mounted) {
15
+ unmount(mounted);
16
+ mounted = null;
17
+ }
18
+ if (host) {
19
+ host.remove();
20
+ host = null;
21
+ }
22
+ });
23
+ describe('MenuSheet (dom)', () => {
24
+ it('renders nothing when closed', () => {
25
+ host = document.createElement('div');
26
+ document.body.appendChild(host);
27
+ mounted = mount(MenuSheetAny, {
28
+ target: host,
29
+ props: { open: false, onClose: () => { } },
30
+ });
31
+ flushSync();
32
+ expect(host.querySelector('[data-sh3-region="menu-sheet"]')).toBeNull();
33
+ });
34
+ it('renders a sheet with a Cancel button when open', () => {
35
+ host = document.createElement('div');
36
+ document.body.appendChild(host);
37
+ mounted = mount(MenuSheetAny, {
38
+ target: host,
39
+ props: { open: true, onClose: () => { } },
40
+ });
41
+ flushSync();
42
+ const sheet = host.querySelector('[data-sh3-region="menu-sheet"]');
43
+ expect(sheet).not.toBeNull();
44
+ expect(sheet.querySelector('.cancel').textContent).toContain('Cancel');
45
+ });
46
+ });
@@ -22,5 +22,14 @@ export interface Sh3Config {
22
22
  target?: string | HTMLElement;
23
23
  /** Server base URL ('' for same-origin) */
24
24
  serverUrl?: string;
25
+ /**
26
+ * When true, override the local-owner short-circuit and run the
27
+ * full server-driven auth flow (boot config fetch, SignInWall when
28
+ * required) against `serverUrl`. Used by Tauri clients connecting
29
+ * to a remote sh3-server (e.g. Android), where Tauri presence makes
30
+ * `platform.localOwner` true even though authentication is server-
31
+ * gated.
32
+ */
33
+ remoteAuth?: boolean;
25
34
  }
26
35
  export declare function createShell(config?: Sh3Config): Promise<void>;
@@ -10,8 +10,9 @@ import { mount, unmount } from 'svelte';
10
10
  import { Sh3 } from './index';
11
11
  import { registerShard, registerApp, bootstrap, bootstrapSatellite, __setBackend, setLocalOwner, } from './host';
12
12
  import { resolvePlatform } from './platform/index';
13
+ import { apiFetch } from './transport/apiFetch';
13
14
  import { hydrateTokenOverrides } from './theme';
14
- import { __setEnvServerUrl } from './env/index';
15
+ import { __setEnvServerUrl, getEnvServerUrl } from './env/index';
15
16
  import { __setActiveScope } from './documents/config';
16
17
  import { initFromBoot } from './auth/index';
17
18
  import SignInWall from './auth/SignInWall.svelte';
@@ -83,11 +84,13 @@ export async function createShell(config) {
83
84
  mount(SatelliteShell, { target, props: { payload: satellite.payload } });
84
85
  return;
85
86
  }
86
- // 3. Fetch boot config (skip for local-owner platforms like Tauri/dev)
87
+ // 3. Fetch boot config (skip for purely-local owners; remoteAuth
88
+ // forces it for cross-origin Tauri clients).
87
89
  let bootConfig = null;
88
- if (!platform.localOwner) {
90
+ const useServerAuth = !platform.localOwner || (config === null || config === void 0 ? void 0 : config.remoteAuth) === true;
91
+ if (useServerAuth) {
89
92
  try {
90
- const res = await fetch(`${sUrl}/api/boot`, { credentials: 'include' });
93
+ const res = await apiFetch(`${sUrl}/api/boot`);
91
94
  if (res.ok) {
92
95
  bootConfig = await res.json();
93
96
  }
@@ -97,7 +100,7 @@ export async function createShell(config) {
97
100
  }
98
101
  }
99
102
  // 4. Auth decision point
100
- if (platform.localOwner) {
103
+ if (platform.localOwner && !(config === null || config === void 0 ? void 0 : config.remoteAuth)) {
101
104
  // Local-owner (Tauri/dev): no auth, no sign-in, scope is 'local'.
102
105
  // setLocalOwner() already called above — admin is assumed.
103
106
  __setActiveScope('local');
@@ -110,7 +113,7 @@ export async function createShell(config) {
110
113
  if (!session && auth.required && !auth.guestAllowed) {
111
114
  await showSignInWall(target, bootConfig);
112
115
  // After successful sign-in, re-fetch boot config
113
- const res = await fetch(`${sUrl}/api/boot`, { credentials: 'include' });
116
+ const res = await apiFetch(`${sUrl}/api/boot`);
114
117
  if (res.ok) {
115
118
  bootConfig = await res.json();
116
119
  initFromBoot(sUrl, bootConfig);
@@ -150,7 +153,17 @@ async function loadDiscoveredPackages(packages) {
150
153
  return;
151
154
  for (const pkg of packages) {
152
155
  try {
153
- const res = await fetch(pkg.bundleUrl);
156
+ // Server returns server-relative paths like `/packages/<id>/client.js`.
157
+ // In a cross-origin Tauri client these resolve against the webview
158
+ // origin (`tauri://localhost`) instead of the configured server, so
159
+ // we get the SPA's index.html back and the loader chokes on `<`.
160
+ // Resolve against the active serverUrl and route through apiFetch
161
+ // for the cross-origin-safe transport + bearer header.
162
+ const isAbsolute = pkg.bundleUrl.startsWith('http://') || pkg.bundleUrl.startsWith('https://');
163
+ const base = getEnvServerUrl();
164
+ const sep = pkg.bundleUrl.startsWith('/') ? '' : '/';
165
+ const url = isAbsolute ? pkg.bundleUrl : `${base}${sep}${pkg.bundleUrl}`;
166
+ const res = await apiFetch(url);
154
167
  if (!res.ok) {
155
168
  console.warn(`[sh3] Failed to fetch discovered package "${pkg.id}": HTTP ${res.status}`);
156
169
  continue;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,71 @@
1
+ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest';
2
+ describe('createShell remoteAuth flag', () => {
3
+ let originalFetch;
4
+ beforeEach(() => {
5
+ originalFetch = globalThis.fetch;
6
+ vi.resetModules();
7
+ });
8
+ afterEach(() => {
9
+ globalThis.fetch = originalFetch;
10
+ vi.doUnmock('./platform/index');
11
+ vi.doUnmock('./host');
12
+ });
13
+ function shortCircuitAfterBoot() {
14
+ // Mock bootstrap so createShell throws right after the boot-config
15
+ // fetch we want to observe — and never reaches mount(Sh3, ...).
16
+ vi.doMock('./host', async () => {
17
+ const actual = await vi.importActual('./host');
18
+ return Object.assign(Object.assign({}, actual), { bootstrap: async () => {
19
+ throw new Error('test-skip-bootstrap');
20
+ } });
21
+ });
22
+ }
23
+ it('fetches /api/boot when remoteAuth is true even if localOwner is detected', async () => {
24
+ const calls = [];
25
+ globalThis.fetch = vi.fn(async (input) => {
26
+ calls.push(String(input));
27
+ return new Response(JSON.stringify({
28
+ version: '0.18.0',
29
+ tenantId: 't1',
30
+ auth: { required: false, guestAllowed: true, selfRegistration: false },
31
+ session: { token: 'tok', userId: 'u1', role: 'user', expiresAt: Number.MAX_SAFE_INTEGER },
32
+ user: { id: 'u1', username: 'u', displayName: 'U', role: 'user', createdAt: '', updatedAt: '' },
33
+ }), { status: 200, headers: { 'Content-Type': 'application/json' } });
34
+ });
35
+ vi.doMock('./platform/index', () => ({
36
+ resolvePlatform: async () => ({ backends: null, localOwner: true }),
37
+ }));
38
+ shortCircuitAfterBoot();
39
+ document.body.innerHTML = '<div id="app"></div>';
40
+ const { createShell } = await import('./createShell');
41
+ // The full boot pipeline (mount, bootstrap, etc.) may throw in this
42
+ // test env — we only care that the boot-config fetch was attempted.
43
+ try {
44
+ await createShell({ serverUrl: 'https://remote.example.com', remoteAuth: true });
45
+ }
46
+ catch (_a) {
47
+ // ignore — assertion below is the contract.
48
+ }
49
+ expect(calls.some(u => u === 'https://remote.example.com/api/boot')).toBe(true);
50
+ });
51
+ it('keeps the legacy localOwner short-circuit when remoteAuth is absent', async () => {
52
+ const calls = [];
53
+ globalThis.fetch = vi.fn(async (input) => {
54
+ calls.push(String(input));
55
+ return new Response('ok');
56
+ });
57
+ vi.doMock('./platform/index', () => ({
58
+ resolvePlatform: async () => ({ backends: null, localOwner: true }),
59
+ }));
60
+ shortCircuitAfterBoot();
61
+ document.body.innerHTML = '<div id="app"></div>';
62
+ const { createShell } = await import('./createShell');
63
+ try {
64
+ await createShell({ serverUrl: '' });
65
+ }
66
+ catch (_a) {
67
+ // ignore — assertion below is the contract.
68
+ }
69
+ expect(calls.some(u => u.endsWith('/api/boot'))).toBe(false);
70
+ });
71
+ });
@@ -20,6 +20,7 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
20
20
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
21
21
  };
22
22
  var _HttpDocumentBackend_instances, _HttpDocumentBackend_baseUrl, _HttpDocumentBackend_apiKey, _HttpDocumentBackend_authHeaders;
23
+ import { apiFetch } from '../transport/apiFetch';
23
24
  export class HttpDocumentBackend {
24
25
  /**
25
26
  * @param baseUrl - The server origin (e.g. 'http://localhost:3000' or window.location.origin).
@@ -36,7 +37,7 @@ export class HttpDocumentBackend {
36
37
  async read(tenantId, shardId, path) {
37
38
  var _a;
38
39
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/${shardId}/${path}`;
39
- const res = await fetch(url, { credentials: 'include' });
40
+ const res = await apiFetch(url, { credentials: 'include' });
40
41
  if (res.status === 404)
41
42
  return null;
42
43
  if (!res.ok)
@@ -50,45 +51,45 @@ export class HttpDocumentBackend {
50
51
  async write(tenantId, shardId, path, content) {
51
52
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/${shardId}/${path}`;
52
53
  const headers = Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpDocumentBackend_instances, "m", _HttpDocumentBackend_authHeaders).call(this)), { 'Content-Type': typeof content === 'string' ? 'text/plain' : 'application/octet-stream' });
53
- const res = await fetch(url, { method: 'PUT', headers, body: content, credentials: 'include' });
54
+ const res = await apiFetch(url, { method: 'PUT', headers, body: content, credentials: 'include' });
54
55
  if (!res.ok)
55
56
  throw new Error(`Document write failed: ${res.status}`);
56
57
  }
57
58
  async delete(tenantId, shardId, path) {
58
59
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/${shardId}/${path}`;
59
- const res = await fetch(url, { method: 'DELETE', headers: __classPrivateFieldGet(this, _HttpDocumentBackend_instances, "m", _HttpDocumentBackend_authHeaders).call(this), credentials: 'include' });
60
+ const res = await apiFetch(url, { method: 'DELETE', headers: __classPrivateFieldGet(this, _HttpDocumentBackend_instances, "m", _HttpDocumentBackend_authHeaders).call(this), credentials: 'include' });
60
61
  if (!res.ok)
61
62
  throw new Error(`Document delete failed: ${res.status}`);
62
63
  }
63
64
  async list(tenantId, shardId) {
64
65
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/${shardId}`;
65
- const res = await fetch(url, { credentials: 'include' });
66
+ const res = await apiFetch(url, { credentials: 'include' });
66
67
  if (!res.ok)
67
68
  throw new Error(`Document list failed: ${res.status}`);
68
69
  return res.json();
69
70
  }
70
71
  async exists(tenantId, shardId, path) {
71
72
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/${shardId}/${path}`;
72
- const res = await fetch(url, { method: 'HEAD', credentials: 'include' });
73
+ const res = await apiFetch(url, { method: 'HEAD', credentials: 'include' });
73
74
  return res.ok;
74
75
  }
75
76
  async listAllShards(tenantId) {
76
77
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/_shards`;
77
- const res = await fetch(url, { credentials: 'include' });
78
+ const res = await apiFetch(url, { credentials: 'include' });
78
79
  if (!res.ok)
79
80
  throw new Error(`listAllShards failed: ${res.status}`);
80
81
  return res.json();
81
82
  }
82
83
  async listAllDocuments(tenantId) {
83
84
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/_all`;
84
- const res = await fetch(url, { credentials: 'include' });
85
+ const res = await apiFetch(url, { credentials: 'include' });
85
86
  if (!res.ok)
86
87
  throw new Error(`listAllDocuments failed: ${res.status}`);
87
88
  return res.json();
88
89
  }
89
90
  async readMeta(tenantId, shardId, path) {
90
91
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/${shardId}/${path}?meta=1`;
91
- const res = await fetch(url, { credentials: 'include' });
92
+ const res = await apiFetch(url, { credentials: 'include' });
92
93
  if (!res.ok)
93
94
  throw new Error(`readMeta failed: ${res.status}`);
94
95
  const body = await res.json();
@@ -101,7 +102,7 @@ export class HttpDocumentBackend {
101
102
  const body = typeof choice === 'string'
102
103
  ? { choice }
103
104
  : { choice: choice.origin };
104
- const res = await fetch(url, {
105
+ const res = await apiFetch(url, {
105
106
  method: 'POST',
106
107
  credentials: 'include',
107
108
  headers: Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpDocumentBackend_instances, "m", _HttpDocumentBackend_authHeaders).call(this)), { 'Content-Type': 'application/json' }),
@@ -112,7 +113,7 @@ export class HttpDocumentBackend {
112
113
  }
113
114
  async readBranch(tenantId, shardId, path, origin) {
114
115
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/${shardId}/${path}/branch?origin=${encodeURIComponent(origin)}`;
115
- const res = await fetch(url, { credentials: 'include' });
116
+ const res = await apiFetch(url, { credentials: 'include' });
116
117
  if (res.status === 404)
117
118
  return null;
118
119
  if (!res.ok)
@@ -122,7 +123,7 @@ export class HttpDocumentBackend {
122
123
  async rename(tenantId, shardId, oldPath, newPath) {
123
124
  const url = `${__classPrivateFieldGet(this, _HttpDocumentBackend_baseUrl, "f")}/api/docs/${tenantId}/${shardId}/${oldPath}/rename`;
124
125
  const headers = Object.assign(Object.assign({}, __classPrivateFieldGet(this, _HttpDocumentBackend_instances, "m", _HttpDocumentBackend_authHeaders).call(this)), { 'Content-Type': 'application/json' });
125
- const res = await fetch(url, {
126
+ const res = await apiFetch(url, {
126
127
  method: 'POST',
127
128
  headers,
128
129
  body: JSON.stringify({ to: newPath }),
@@ -3,6 +3,7 @@
3
3
  * from the server.
4
4
  */
5
5
  import { getAuthHeader, isAdmin } from '../auth/index';
6
+ import { apiFetch } from '../transport/apiFetch';
6
7
  /** Server base URL, set once during configuration. */
7
8
  let serverUrl = '';
8
9
  /** Configure the server URL for env state operations. */
@@ -18,7 +19,9 @@ export function getEnvServerUrl() {
18
19
  * Returns an empty object if the server has no stored state.
19
20
  */
20
21
  export async function fetchEnvState(shardId) {
21
- const res = await fetch(`${serverUrl}/api/env-state/${encodeURIComponent(shardId)}`);
22
+ const res = await apiFetch(`${serverUrl}/api/env-state/${encodeURIComponent(shardId)}`, {
23
+ credentials: 'omit',
24
+ });
22
25
  if (!res.ok) {
23
26
  console.warn(`[sh3] Failed to fetch env state for "${shardId}": HTTP ${res.status}`);
24
27
  return {};
@@ -38,10 +41,11 @@ export async function putEnvState(shardId, state) {
38
41
  const headers = { 'Content-Type': 'application/json' };
39
42
  if (auth)
40
43
  headers['Authorization'] = auth;
41
- const res = await fetch(`${serverUrl}/api/env-state/${encodeURIComponent(shardId)}`, {
44
+ const res = await apiFetch(`${serverUrl}/api/env-state/${encodeURIComponent(shardId)}`, {
42
45
  method: 'PUT',
43
46
  headers,
44
47
  body: JSON.stringify(state),
48
+ credentials: 'omit',
45
49
  });
46
50
  if (!res.ok) {
47
51
  const body = await res.json().catch(() => ({}));
@@ -73,10 +77,11 @@ export async function serverInstallPackage(manifest, clientBundle, serverBundle)
73
77
  const headers = {};
74
78
  if (auth)
75
79
  headers['Authorization'] = auth;
76
- const res = await fetch(`${serverUrl}/api/packages/install`, {
80
+ const res = await apiFetch(`${serverUrl}/api/packages/install`, {
77
81
  method: 'POST',
78
82
  headers,
79
83
  body: form,
84
+ credentials: 'omit',
80
85
  });
81
86
  if (!res.ok) {
82
87
  let body = {};
@@ -104,10 +109,11 @@ export async function serverUninstallPackage(id) {
104
109
  const headers = { 'Content-Type': 'application/json' };
105
110
  if (auth)
106
111
  headers['Authorization'] = auth;
107
- const res = await fetch(`${serverUrl}/api/packages/uninstall`, {
112
+ const res = await apiFetch(`${serverUrl}/api/packages/uninstall`, {
108
113
  method: 'POST',
109
114
  headers,
110
115
  body: JSON.stringify({ id }),
116
+ credentials: 'omit',
111
117
  });
112
118
  if (!res.ok) {
113
119
  const body = await res.json().catch(() => ({}));
@@ -118,7 +124,7 @@ export async function serverUninstallPackage(id) {
118
124
  * Fetch the list of packages installed on the server.
119
125
  */
120
126
  export async function fetchServerPackages() {
121
- const res = await fetch(`${serverUrl}/api/packages`);
127
+ const res = await apiFetch(`${serverUrl}/api/packages`, { credentials: 'omit' });
122
128
  if (!res.ok)
123
129
  return [];
124
130
  return await res.json();
@@ -0,0 +1,106 @@
1
+ /**
2
+ * A reference to a document passed to file handler callbacks and app launch args.
3
+ */
4
+ export interface FileRef {
5
+ path: string;
6
+ tenantId: string;
7
+ /** True when the file was identified as binary content. */
8
+ binary: boolean;
9
+ }
10
+ /** A single header pattern rule. */
11
+ export type FileHandlerPattern = {
12
+ type: 'startsWith';
13
+ value: string;
14
+ } | {
15
+ type: 'includes';
16
+ value: string;
17
+ } | {
18
+ type: 'regex';
19
+ pattern: string;
20
+ flags?: string;
21
+ };
22
+ /**
23
+ * Optional header-based disambiguation used when multiple handlers share
24
+ * an extension. The dispatcher reads the file head and tests these rules
25
+ * to narrow the candidate set.
26
+ */
27
+ export interface FileHandlerHeader {
28
+ /**
29
+ * Max bytes/chars to read from the file head.
30
+ * @default 256
31
+ */
32
+ readBytes?: number;
33
+ /**
34
+ * Declarative patterns tested in order — first match wins.
35
+ * Evaluated before `predicate`.
36
+ */
37
+ patterns?: FileHandlerPattern[];
38
+ /**
39
+ * Escape-hatch for logic that cannot be expressed as patterns.
40
+ * Receives `string` for text files, `Uint8Array` for binary.
41
+ */
42
+ predicate?: (header: string | Uint8Array) => boolean;
43
+ }
44
+ /** File matching rules for a handler. */
45
+ export interface FileHandlerMatch {
46
+ /**
47
+ * File extensions this handler claims, e.g. `['.svg', '.svgz']`.
48
+ * Compared case-insensitively by the dispatcher.
49
+ */
50
+ extensions: string[];
51
+ /**
52
+ * When true, this handler accepts binary files.
53
+ * @default false
54
+ */
55
+ binary?: boolean;
56
+ /** Optional header rules for disambiguation when extensions collide. */
57
+ header?: FileHandlerHeader;
58
+ }
59
+ /**
60
+ * How the dispatcher should open the matched file.
61
+ *
62
+ * - `view` — the shard's callback is called; the shard mounts the view
63
+ * itself via its own `ctx` closure. Placement is the dispatcher's concern.
64
+ * - `app` — the dispatcher calls `launchApp(appId, { args: { file } })`.
65
+ * The app reads `ctx.args.file` in `activate()`.
66
+ */
67
+ export type FileHandlerOpen = {
68
+ type: 'view';
69
+ /**
70
+ * Called by the dispatcher with the resolved file. The shard closes
71
+ * over its own ShardContext — it wires the file to internal state and
72
+ * mounts its view using its existing shell access.
73
+ */
74
+ open: (file: FileRef) => void | Promise<void>;
75
+ } | {
76
+ type: 'app';
77
+ /** Id of the app to launch. Must be registered. */
78
+ appId: string;
79
+ };
80
+ /**
81
+ * Descriptor registered at contribution point `sh3.file-handler`.
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * ctx.contributions.register('sh3.file-handler', {
86
+ * label: 'SVG Viewer',
87
+ * match: { extensions: ['.svg'] },
88
+ * open: {
89
+ * type: 'view',
90
+ * open: (file) => { pendingFile.set(file); shell.openViewInCurrentLayout(viewId); },
91
+ * },
92
+ * } satisfies FileHandlerDescriptor);
93
+ * ```
94
+ */
95
+ export interface FileHandlerDescriptor {
96
+ /** Human-readable name shown in "open with" UI. */
97
+ label: string;
98
+ match: FileHandlerMatch;
99
+ open: FileHandlerOpen;
100
+ /**
101
+ * Tie-breaking priority. Higher wins when multiple handlers match after
102
+ * extension + header filtering.
103
+ * @default 0
104
+ */
105
+ priority?: number;
106
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,6 @@
1
+ import type { GestureOptions, GestureHandle } from './types';
2
+ export interface GestureRegistry {
3
+ register(opts: GestureOptions): GestureHandle;
4
+ destroy(): void;
5
+ }
6
+ export declare function createGestureRegistry(defaultContainer: HTMLElement | null): GestureRegistry;