sh3-core 0.19.1 → 0.19.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Sh3.svelte +3 -1
- package/dist/actions/menuBarModel.js +8 -0
- package/dist/actions/menuBarModel.test.js +61 -0
- package/dist/app/admin/ApiKeysView.svelte +6 -5
- package/dist/app/store/PermissionConfirmModal.svelte +23 -0
- package/dist/app/store/PermissionConfirmModal.svelte.d.ts +1 -0
- package/dist/app/store/StoreView.svelte +6 -1
- package/dist/chrome/CompactChrome.svelte.test.js +7 -4
- package/dist/env/client.d.ts +5 -4
- package/dist/env/client.js +11 -17
- package/dist/env/serverUrl.d.ts +2 -0
- package/dist/env/serverUrl.js +8 -0
- package/dist/gestures/index.d.ts +17 -0
- package/dist/gestures/index.js +27 -0
- package/dist/keys/client.js +6 -7
- package/dist/keys/revocation-bus.svelte.js +11 -1
- package/dist/layout/compact/CarouselTabs.svelte +150 -14
- package/dist/layout/compact/CarouselTabs.svelte.test.js +222 -2
- package/dist/layout/compact/CompactRenderer.svelte +1 -1
- package/dist/layout/compact/CompactRenderer.svelte.test.js +5 -3
- package/dist/layout/compact/derive.js +7 -16
- package/dist/layout/compact/derive.test.js +30 -9
- package/dist/layout/drag.svelte.js +16 -3
- package/dist/layout/inspection.d.ts +20 -9
- package/dist/layout/inspection.js +66 -11
- package/dist/layout/inspection.svelte.test.d.ts +1 -0
- package/dist/layout/inspection.svelte.test.js +114 -0
- package/dist/layout/store.schemaVersion.test.js +2 -2
- package/dist/layout/types.d.ts +11 -8
- package/dist/layout/types.js +1 -1
- package/dist/layout/types.test.js +2 -2
- package/dist/overlays/FloatFrame.svelte +93 -22
- package/dist/primitives/ResizableSplitter.svelte +42 -8
- package/dist/registry/checkFetch.d.ts +6 -0
- package/dist/registry/checkFetch.js +23 -0
- package/dist/sh3/views/KeysAndPeers.svelte +4 -3
- package/dist/shards/activate-runtime.test.js +99 -1
- package/dist/shards/activate.svelte.js +12 -3
- package/dist/shards/registry.d.ts +8 -1
- package/dist/shards/registry.js +13 -2
- package/dist/shards/registry.test.js +25 -4
- package/dist/shards/types.d.ts +14 -1
- package/dist/shell-shard/ScrollbackView.svelte +145 -67
- package/dist/shell-shard/ScrollbackView.svelte.test.d.ts +1 -0
- package/dist/shell-shard/ScrollbackView.svelte.test.js +182 -0
- package/dist/shell-shard/dispatch-gating.test.js +38 -2
- package/dist/shell-shard/dispatch.js +9 -1
- package/dist/shell-shard/registry-resolve.test.js +50 -0
- package/dist/shell-shard/registry.d.ts +2 -1
- package/dist/shell-shard/registry.js +12 -2
- package/dist/shell-shard/verbs/help.js +5 -4
- package/dist/shell-shard/verbs/help.svelte.test.js +5 -2
- package/dist/verbs/types.d.ts +10 -5
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/Sh3.svelte
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import CompactRenderer from './layout/compact/CompactRenderer.svelte';
|
|
31
31
|
import { sh3 } from './sh3Runtime.svelte';
|
|
32
32
|
import { claim, revoke } from './gestures/pointerClaim';
|
|
33
|
+
import { EDGE_PX } from './gestures';
|
|
33
34
|
|
|
34
35
|
let contentEl: HTMLElement | undefined = $state();
|
|
35
36
|
|
|
@@ -70,7 +71,8 @@
|
|
|
70
71
|
// Register left/right edge zones as priority:'edge' pointer claims so that
|
|
71
72
|
// any shard with a priority:'normal' claim automatically beats the shell's
|
|
72
73
|
// edge-swipe gesture (carousel navigation, future side-panel reveals).
|
|
73
|
-
|
|
74
|
+
// EDGE_PX is shared with carousel/swipe sites so the gutter width stays
|
|
75
|
+
// consistent across the framework.
|
|
74
76
|
$effect(() => {
|
|
75
77
|
const el = contentEl;
|
|
76
78
|
if (!el) return;
|
|
@@ -62,6 +62,12 @@ export function resolveMenuItems(entries, state, containerId) {
|
|
|
62
62
|
const winning = innermostActiveScope(entry.action.scope, state, entry.ownerShardId);
|
|
63
63
|
if (!winning)
|
|
64
64
|
continue;
|
|
65
|
+
// Menu surface only: when the action is active via the 'app' tier,
|
|
66
|
+
// require the owner shard to be in the active app's requiredShards.
|
|
67
|
+
// Dispatcher / palette / context menu / hotkey paths keep autostart
|
|
68
|
+
// activation. See issue #32 and the design spec dated 2026-05-12.
|
|
69
|
+
if (winning === 'app' && !state.activeAppRequiredShards.has(entry.ownerShardId))
|
|
70
|
+
continue;
|
|
65
71
|
seen.add(entry.action.id);
|
|
66
72
|
out.push({
|
|
67
73
|
id: entry.action.id,
|
|
@@ -95,6 +101,8 @@ export function resolveSubmenuItems(entries, state, parentId) {
|
|
|
95
101
|
const winning = innermostActiveScope(entry.action.scope, state, entry.ownerShardId);
|
|
96
102
|
if (!winning)
|
|
97
103
|
continue;
|
|
104
|
+
if (winning === 'app' && !state.activeAppRequiredShards.has(entry.ownerShardId))
|
|
105
|
+
continue;
|
|
98
106
|
seen.add(entry.action.id);
|
|
99
107
|
out.push({
|
|
100
108
|
id: entry.action.id,
|
|
@@ -156,3 +156,64 @@ describe('resolveSubmenuItems', () => {
|
|
|
156
156
|
expect(resolveSubmenuItems([], stateWithApp, 'nope')).toEqual([]);
|
|
157
157
|
});
|
|
158
158
|
});
|
|
159
|
+
describe("resolveMenuItems — required-shard menu filter (issue #32)", () => {
|
|
160
|
+
it("omits scope:'app' actions when owner shard is autostart-only (not required by active app)", () => {
|
|
161
|
+
const state = mkState({
|
|
162
|
+
activeAppId: 'other-app',
|
|
163
|
+
activeAppRequiredShards: new Set(['other-shard']),
|
|
164
|
+
autostartShards: new Set(['guml.core']),
|
|
165
|
+
});
|
|
166
|
+
const entries = [
|
|
167
|
+
mkEntry({ id: 'guml.project.new', scope: 'app', menuItem: 'file', label: 'New Project…' }, 'guml.core'),
|
|
168
|
+
];
|
|
169
|
+
expect(resolveMenuItems(entries, state, 'file')).toEqual([]);
|
|
170
|
+
});
|
|
171
|
+
it("includes scope:'app' actions when owner shard IS in active app's requiredShards", () => {
|
|
172
|
+
const state = mkState({
|
|
173
|
+
activeAppId: 'guml-ide',
|
|
174
|
+
activeAppRequiredShards: new Set(['guml.core']),
|
|
175
|
+
autostartShards: new Set(['guml.core']),
|
|
176
|
+
});
|
|
177
|
+
const entries = [
|
|
178
|
+
mkEntry({ id: 'guml.project.new', scope: 'app', menuItem: 'file', label: 'New Project…' }, 'guml.core'),
|
|
179
|
+
];
|
|
180
|
+
expect(resolveMenuItems(entries, state, 'file').map((i) => i.id))
|
|
181
|
+
.toEqual(['guml.project.new']);
|
|
182
|
+
});
|
|
183
|
+
it('still includes the action when a more-specific tier wins (view:editor over app)', () => {
|
|
184
|
+
const state = mkState({
|
|
185
|
+
activeAppId: 'other-app',
|
|
186
|
+
activeAppRequiredShards: new Set(['other-shard']),
|
|
187
|
+
autostartShards: new Set(['guml.core']),
|
|
188
|
+
mountedViewIds: new Set(['editor']),
|
|
189
|
+
});
|
|
190
|
+
const entries = [
|
|
191
|
+
mkEntry({ id: 'fmt', scope: ['view:editor', 'app'], menuItem: 'file', label: 'Format' }, 'guml.core'),
|
|
192
|
+
];
|
|
193
|
+
expect(resolveMenuItems(entries, state, 'file').map((i) => i.id)).toEqual(['fmt']);
|
|
194
|
+
});
|
|
195
|
+
it("omits scope:['home','app'] action in an app that does not require the autostart owner", () => {
|
|
196
|
+
const state = mkState({
|
|
197
|
+
activeAppId: 'other-app',
|
|
198
|
+
activeAppRequiredShards: new Set(['other-shard']),
|
|
199
|
+
autostartShards: new Set(['guml.core']),
|
|
200
|
+
});
|
|
201
|
+
const entries = [
|
|
202
|
+
mkEntry({ id: 'g.global', scope: ['home', 'app'], menuItem: 'file', label: 'Global' }, 'guml.core'),
|
|
203
|
+
];
|
|
204
|
+
expect(resolveMenuItems(entries, state, 'file')).toEqual([]);
|
|
205
|
+
});
|
|
206
|
+
it('drops submenu children whose owner shard is autostart-only', () => {
|
|
207
|
+
const state = mkState({
|
|
208
|
+
activeAppId: 'other-app',
|
|
209
|
+
activeAppRequiredShards: new Set(['other-shard']),
|
|
210
|
+
autostartShards: new Set(['guml.core']),
|
|
211
|
+
});
|
|
212
|
+
const entries = [
|
|
213
|
+
mkEntry({ id: 'g.parent', scope: 'app', menuItem: 'file', label: 'GUML', submenu: true }, 'guml.core'),
|
|
214
|
+
mkEntry({ id: 'g.parent.a', scope: 'app', label: 'A', submenuOf: 'g.parent' }, 'guml.core'),
|
|
215
|
+
];
|
|
216
|
+
expect(resolveMenuItems(entries, state, 'file')).toEqual([]);
|
|
217
|
+
expect(resolveSubmenuItems(entries, state, 'g.parent')).toEqual([]);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Admin API Keys view — list, create, reveal, revoke API keys.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { apiFetch } from '../../transport/apiFetch';
|
|
7
|
+
import { getEnvServerUrl } from '../../env/serverUrl';
|
|
8
|
+
|
|
6
9
|
interface ApiKeyPublic {
|
|
7
10
|
id: string;
|
|
8
11
|
label: string;
|
|
@@ -29,7 +32,7 @@
|
|
|
29
32
|
loading = true;
|
|
30
33
|
error = null;
|
|
31
34
|
try {
|
|
32
|
-
const res = await
|
|
35
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/admin/keys`);
|
|
33
36
|
if (!res.ok) throw new Error('Failed to fetch keys');
|
|
34
37
|
keys = await res.json();
|
|
35
38
|
} catch (err) {
|
|
@@ -42,10 +45,9 @@
|
|
|
42
45
|
async function createKey() {
|
|
43
46
|
createError = null;
|
|
44
47
|
try {
|
|
45
|
-
const res = await
|
|
48
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/admin/keys`, {
|
|
46
49
|
method: 'POST',
|
|
47
50
|
headers: { 'Content-Type': 'application/json' },
|
|
48
|
-
credentials: 'include',
|
|
49
51
|
body: JSON.stringify({ label: newLabel }),
|
|
50
52
|
});
|
|
51
53
|
if (!res.ok) {
|
|
@@ -66,9 +68,8 @@
|
|
|
66
68
|
async function revokeKey(id: string) {
|
|
67
69
|
confirmingId = null;
|
|
68
70
|
try {
|
|
69
|
-
await
|
|
71
|
+
await apiFetch(`${getEnvServerUrl()}/api/admin/keys/${id}`, {
|
|
70
72
|
method: 'DELETE',
|
|
71
|
-
credentials: 'include',
|
|
72
73
|
});
|
|
73
74
|
if (justCreated?.id === id) justCreated = null;
|
|
74
75
|
await fetchKeys();
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
permissions?: string[];
|
|
22
22
|
added?: string[];
|
|
23
23
|
removed?: string[];
|
|
24
|
+
warnings?: string[];
|
|
24
25
|
onConfirm: () => void;
|
|
25
26
|
onCancel: () => void;
|
|
26
27
|
}
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
permissions = [],
|
|
33
34
|
added = [],
|
|
34
35
|
removed = [],
|
|
36
|
+
warnings = [],
|
|
35
37
|
onConfirm,
|
|
36
38
|
onCancel,
|
|
37
39
|
}: Props = $props();
|
|
@@ -111,6 +113,17 @@
|
|
|
111
113
|
</ul>
|
|
112
114
|
{/if}
|
|
113
115
|
{/if}
|
|
116
|
+
{#if warnings.length > 0}
|
|
117
|
+
<p class="perm-modal-intro perm-modal-warn-heading">Potential compatibility issues:</p>
|
|
118
|
+
<ul class="perm-modal-list perm-modal-warnings">
|
|
119
|
+
{#each warnings as msg (msg)}
|
|
120
|
+
<li class="perm-modal-item perm-modal-warn-item">
|
|
121
|
+
<div class="perm-modal-item-title">⚠ Compatibility warning</div>
|
|
122
|
+
<div class="perm-modal-item-desc">{msg}</div>
|
|
123
|
+
</li>
|
|
124
|
+
{/each}
|
|
125
|
+
</ul>
|
|
126
|
+
{/if}
|
|
114
127
|
</div>
|
|
115
128
|
|
|
116
129
|
<footer class="perm-modal-footer">
|
|
@@ -203,6 +216,16 @@
|
|
|
203
216
|
.perm-modal-removed .perm-modal-item {
|
|
204
217
|
opacity: 0.75;
|
|
205
218
|
}
|
|
219
|
+
.perm-modal-warn-heading {
|
|
220
|
+
margin-top: 12px;
|
|
221
|
+
}
|
|
222
|
+
.perm-modal-warn-item {
|
|
223
|
+
border-color: color-mix(in srgb, var(--sh3-warning, #ff9800) 60%, var(--sh3-border, #444));
|
|
224
|
+
background: color-mix(in srgb, var(--sh3-warning, #ff9800) 8%, var(--sh3-input-bg, #2a2a2a));
|
|
225
|
+
}
|
|
226
|
+
.perm-modal-warn-item .perm-modal-item-title {
|
|
227
|
+
color: var(--sh3-warning, #ff9800);
|
|
228
|
+
}
|
|
206
229
|
.perm-modal-footer {
|
|
207
230
|
padding: 12px 20px;
|
|
208
231
|
border-top: 1px solid var(--sh3-border, #444);
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { loadBundleModule, type LoadedBundle } from '../../registry/loader';
|
|
13
13
|
import { extractBundlePermissions } from '../../registry/permission-descriptions';
|
|
14
14
|
import { serverInstallPackage } from '../../env/client';
|
|
15
|
+
import { checkBundleFetch } from '../../registry/checkFetch';
|
|
15
16
|
import { contract } from '../../contract';
|
|
16
17
|
import type { ResolvedPackage } from '../../registry/client';
|
|
17
18
|
import type { InstalledPackage } from '../../registry/types';
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
bundle: ArrayBuffer;
|
|
39
40
|
meta: ReturnType<typeof buildPackageMeta>;
|
|
40
41
|
serverBundle: ArrayBuffer | undefined;
|
|
42
|
+
warnings: string[];
|
|
41
43
|
}>(null);
|
|
42
44
|
|
|
43
45
|
let updateModal = $state<null | {
|
|
@@ -186,7 +188,9 @@
|
|
|
186
188
|
|
|
187
189
|
// 4. Show the confirmation modal. The actual install happens in
|
|
188
190
|
// confirmInstall() once the user clicks Install.
|
|
189
|
-
|
|
191
|
+
const bundleText = new TextDecoder().decode(new Uint8Array(bundle));
|
|
192
|
+
const warnings = checkBundleFetch(bundleText);
|
|
193
|
+
installModal = { pkg, permissions, loaded, bundle, meta, serverBundle, warnings };
|
|
190
194
|
} catch (err) {
|
|
191
195
|
installError = err instanceof Error ? err.message : String(err);
|
|
192
196
|
const next = new Set(installingIds);
|
|
@@ -400,6 +404,7 @@
|
|
|
400
404
|
author: installModal.pkg.entry.author.name,
|
|
401
405
|
}}
|
|
402
406
|
permissions={installModal.permissions}
|
|
407
|
+
warnings={installModal.warnings}
|
|
403
408
|
onConfirm={confirmInstall}
|
|
404
409
|
onCancel={cancelInstall}
|
|
405
410
|
/>
|
|
@@ -125,9 +125,10 @@ describe('CompactChrome — breadcrumb', () => {
|
|
|
125
125
|
initialLayout: {
|
|
126
126
|
type: 'tabs',
|
|
127
127
|
activeTab: 1,
|
|
128
|
+
role: 'body',
|
|
128
129
|
tabs: [
|
|
129
|
-
{ slotId: 's0', viewId: null, label: 'First'
|
|
130
|
-
{ slotId: 's1', viewId: null, label: 'Second'
|
|
130
|
+
{ slotId: 's0', viewId: null, label: 'First' },
|
|
131
|
+
{ slotId: 's1', viewId: null, label: 'Second' },
|
|
131
132
|
],
|
|
132
133
|
},
|
|
133
134
|
};
|
|
@@ -152,12 +153,14 @@ describe('CompactChrome — breadcrumb', () => {
|
|
|
152
153
|
{
|
|
153
154
|
type: 'tabs',
|
|
154
155
|
activeTab: 0,
|
|
155
|
-
|
|
156
|
+
role: 'body',
|
|
157
|
+
tabs: [{ slotId: 'top0', viewId: null, label: 'TopActive' }],
|
|
156
158
|
},
|
|
157
159
|
{
|
|
158
160
|
type: 'tabs',
|
|
159
161
|
activeTab: 0,
|
|
160
|
-
|
|
162
|
+
role: 'body',
|
|
163
|
+
tabs: [{ slotId: 'bot0', viewId: null, label: 'BottomActive' }],
|
|
161
164
|
},
|
|
162
165
|
],
|
|
163
166
|
},
|
package/dist/env/client.d.ts
CHANGED
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
* Env state client — fetches and updates per-shard environment state
|
|
3
3
|
* from the server.
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
|
-
export declare function __setEnvServerUrl(url: string): void;
|
|
7
|
-
/** Return the configured server URL. */
|
|
8
|
-
export declare function getEnvServerUrl(): string;
|
|
5
|
+
export { getEnvServerUrl, __setEnvServerUrl } from './serverUrl';
|
|
9
6
|
/**
|
|
10
7
|
* Fetch env state for a shard from the server.
|
|
11
8
|
* Returns an empty object if the server has no stored state.
|
|
@@ -24,6 +21,10 @@ export interface ServerInstallResult {
|
|
|
24
21
|
missing?: Array<{
|
|
25
22
|
id: string;
|
|
26
23
|
}>;
|
|
24
|
+
warnings?: Array<{
|
|
25
|
+
level: 'warn';
|
|
26
|
+
message: string;
|
|
27
|
+
}>;
|
|
27
28
|
}
|
|
28
29
|
/**
|
|
29
30
|
* Install a package on the server via multipart upload.
|
package/dist/env/client.js
CHANGED
|
@@ -4,22 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { getAuthHeader, isAdmin } from '../auth/index';
|
|
6
6
|
import { apiFetch } from '../transport/apiFetch';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/** Configure the server URL for env state operations. */
|
|
10
|
-
export function __setEnvServerUrl(url) {
|
|
11
|
-
serverUrl = url;
|
|
12
|
-
}
|
|
13
|
-
/** Return the configured server URL. */
|
|
14
|
-
export function getEnvServerUrl() {
|
|
15
|
-
return serverUrl;
|
|
16
|
-
}
|
|
7
|
+
import { getEnvServerUrl } from './serverUrl';
|
|
8
|
+
export { getEnvServerUrl, __setEnvServerUrl } from './serverUrl';
|
|
17
9
|
/**
|
|
18
10
|
* Fetch env state for a shard from the server.
|
|
19
11
|
* Returns an empty object if the server has no stored state.
|
|
20
12
|
*/
|
|
21
13
|
export async function fetchEnvState(shardId) {
|
|
22
|
-
const res = await apiFetch(`${
|
|
14
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/env-state/${encodeURIComponent(shardId)}`, {
|
|
23
15
|
credentials: 'omit',
|
|
24
16
|
});
|
|
25
17
|
if (!res.ok) {
|
|
@@ -41,7 +33,7 @@ export async function putEnvState(shardId, state) {
|
|
|
41
33
|
const headers = { 'Content-Type': 'application/json' };
|
|
42
34
|
if (auth)
|
|
43
35
|
headers['Authorization'] = auth;
|
|
44
|
-
const res = await apiFetch(`${
|
|
36
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/env-state/${encodeURIComponent(shardId)}`, {
|
|
45
37
|
method: 'PUT',
|
|
46
38
|
headers,
|
|
47
39
|
body: JSON.stringify(state),
|
|
@@ -65,6 +57,7 @@ export async function putEnvState(shardId, state) {
|
|
|
65
57
|
* back server-side.
|
|
66
58
|
*/
|
|
67
59
|
export async function serverInstallPackage(manifest, clientBundle, serverBundle) {
|
|
60
|
+
var _a;
|
|
68
61
|
if (!isAdmin())
|
|
69
62
|
throw new Error('Cannot install: not elevated to admin');
|
|
70
63
|
const auth = getAuthHeader();
|
|
@@ -77,7 +70,7 @@ export async function serverInstallPackage(manifest, clientBundle, serverBundle)
|
|
|
77
70
|
const headers = {};
|
|
78
71
|
if (auth)
|
|
79
72
|
headers['Authorization'] = auth;
|
|
80
|
-
const res = await apiFetch(`${
|
|
73
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/packages/install`, {
|
|
81
74
|
method: 'POST',
|
|
82
75
|
headers,
|
|
83
76
|
body: form,
|
|
@@ -88,7 +81,7 @@ export async function serverInstallPackage(manifest, clientBundle, serverBundle)
|
|
|
88
81
|
try {
|
|
89
82
|
body = await res.json();
|
|
90
83
|
}
|
|
91
|
-
catch ( /* non-JSON */
|
|
84
|
+
catch ( /* non-JSON */_b) { /* non-JSON */ }
|
|
92
85
|
return {
|
|
93
86
|
ok: false,
|
|
94
87
|
error: typeof body.error === 'string' ? body.error : `HTTP ${res.status}`,
|
|
@@ -96,7 +89,8 @@ export async function serverInstallPackage(manifest, clientBundle, serverBundle)
|
|
|
96
89
|
missing: Array.isArray(body.missing) ? body.missing : undefined,
|
|
97
90
|
};
|
|
98
91
|
}
|
|
99
|
-
|
|
92
|
+
const body = await res.json();
|
|
93
|
+
return { ok: true, warnings: (_a = body.warnings) !== null && _a !== void 0 ? _a : [] };
|
|
100
94
|
}
|
|
101
95
|
/**
|
|
102
96
|
* Uninstall a package from the server.
|
|
@@ -109,7 +103,7 @@ export async function serverUninstallPackage(id) {
|
|
|
109
103
|
const headers = { 'Content-Type': 'application/json' };
|
|
110
104
|
if (auth)
|
|
111
105
|
headers['Authorization'] = auth;
|
|
112
|
-
const res = await apiFetch(`${
|
|
106
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/packages/uninstall`, {
|
|
113
107
|
method: 'POST',
|
|
114
108
|
headers,
|
|
115
109
|
body: JSON.stringify({ id }),
|
|
@@ -124,7 +118,7 @@ export async function serverUninstallPackage(id) {
|
|
|
124
118
|
* Fetch the list of packages installed on the server.
|
|
125
119
|
*/
|
|
126
120
|
export async function fetchServerPackages() {
|
|
127
|
-
const res = await apiFetch(`${
|
|
121
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/packages`, { credentials: 'omit' });
|
|
128
122
|
if (!res.ok)
|
|
129
123
|
return [];
|
|
130
124
|
return await res.json();
|
package/dist/gestures/index.d.ts
CHANGED
|
@@ -4,3 +4,20 @@ export type { GestureRegistry } from './gestureRegistry';
|
|
|
4
4
|
export type { GestureHandle, GestureOptions, GestureType, Axis, ClaimPriority, ClaimEntry, PanEvent, ScrollEvent, ButtonEvent, } from './types';
|
|
5
5
|
/** Internal utility — used by framework gesture sites. Not re-exported via api.ts. */
|
|
6
6
|
export declare function ancestorCount(el: Element): number;
|
|
7
|
+
/**
|
|
8
|
+
* Width of the reserved gutter at each side edge, in CSS pixels.
|
|
9
|
+
* Pointer-downs that land within this strip of either side of a swipe-aware
|
|
10
|
+
* surface (carousel, future side drawers) initiate the gesture unconditionally
|
|
11
|
+
* — content-specific bailouts (editable target, native horizontal scroll) are
|
|
12
|
+
* suppressed. Outside the gutter, those bailouts still apply so taps on
|
|
13
|
+
* inputs and horizontally-scrollable regions behave normally.
|
|
14
|
+
*/
|
|
15
|
+
export declare const EDGE_PX = 24;
|
|
16
|
+
/**
|
|
17
|
+
* Always-on diagnostic for premature gesture-end paths (claim stolen, foreign
|
|
18
|
+
* pointercancel, foreign pointerup, etc.). The log only fires on anomalies, so
|
|
19
|
+
* a healthy drag is silent in production. Include `pointerType` so the user
|
|
20
|
+
* can tell touch from mouse from pen at a glance — the touch-only auto-release
|
|
21
|
+
* bug class doesn't reproduce with a mouse.
|
|
22
|
+
*/
|
|
23
|
+
export declare function logGesture(label: string, ev: PointerEvent | null, extra?: Record<string, unknown>): void;
|
package/dist/gestures/index.js
CHANGED
|
@@ -10,3 +10,30 @@ export function ancestorCount(el) {
|
|
|
10
10
|
}
|
|
11
11
|
return n;
|
|
12
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Width of the reserved gutter at each side edge, in CSS pixels.
|
|
15
|
+
* Pointer-downs that land within this strip of either side of a swipe-aware
|
|
16
|
+
* surface (carousel, future side drawers) initiate the gesture unconditionally
|
|
17
|
+
* — content-specific bailouts (editable target, native horizontal scroll) are
|
|
18
|
+
* suppressed. Outside the gutter, those bailouts still apply so taps on
|
|
19
|
+
* inputs and horizontally-scrollable regions behave normally.
|
|
20
|
+
*/
|
|
21
|
+
export const EDGE_PX = 24;
|
|
22
|
+
/**
|
|
23
|
+
* Always-on diagnostic for premature gesture-end paths (claim stolen, foreign
|
|
24
|
+
* pointercancel, foreign pointerup, etc.). The log only fires on anomalies, so
|
|
25
|
+
* a healthy drag is silent in production. Include `pointerType` so the user
|
|
26
|
+
* can tell touch from mouse from pen at a glance — the touch-only auto-release
|
|
27
|
+
* bug class doesn't reproduce with a mouse.
|
|
28
|
+
*/
|
|
29
|
+
export function logGesture(label, ev, extra) {
|
|
30
|
+
var _a, _b;
|
|
31
|
+
if (typeof console === 'undefined')
|
|
32
|
+
return;
|
|
33
|
+
const tgt = ev === null || ev === void 0 ? void 0 : ev.target;
|
|
34
|
+
const tag = (_a = tgt === null || tgt === void 0 ? void 0 : tgt.tagName) !== null && _a !== void 0 ? _a : '-';
|
|
35
|
+
const raw = (_b = tgt === null || tgt === void 0 ? void 0 : tgt.className) !== null && _b !== void 0 ? _b : '';
|
|
36
|
+
const cls = typeof raw === 'string' ? raw : '';
|
|
37
|
+
// eslint-disable-next-line no-console
|
|
38
|
+
console.log('[sh3:gesture]', label, Object.assign({ pointerId: ev === null || ev === void 0 ? void 0 : ev.pointerId, pointerType: ev === null || ev === void 0 ? void 0 : ev.pointerType, type: ev === null || ev === void 0 ? void 0 : ev.type, target: cls ? `${tag}.${cls}` : tag }, extra));
|
|
39
|
+
}
|
package/dist/keys/client.js
CHANGED
|
@@ -9,6 +9,8 @@
|
|
|
9
9
|
import { ConsentDeniedError, ScopeEscalationError } from './types';
|
|
10
10
|
import { requestConsent } from './consent.svelte';
|
|
11
11
|
import { emit } from './revocation-bus.svelte';
|
|
12
|
+
import { apiFetch } from '../transport/apiFetch';
|
|
13
|
+
import { getEnvServerUrl } from '../env/serverUrl';
|
|
12
14
|
export function createShardKeysApi(params) {
|
|
13
15
|
const { shardId, shardPermissions } = params;
|
|
14
16
|
const assertScopesSubset = (scopes) => {
|
|
@@ -24,18 +26,16 @@ export function createShardKeysApi(params) {
|
|
|
24
26
|
const approved = await requestConsent(shardId, opts);
|
|
25
27
|
if (!approved)
|
|
26
28
|
throw new ConsentDeniedError();
|
|
27
|
-
const ticketRes = await
|
|
29
|
+
const ticketRes = await apiFetch(`${getEnvServerUrl()}/api/keys/consent`, {
|
|
28
30
|
method: 'POST',
|
|
29
|
-
credentials: 'include',
|
|
30
31
|
headers: { 'content-type': 'application/json' },
|
|
31
32
|
body: JSON.stringify(Object.assign({ shardId }, opts)),
|
|
32
33
|
});
|
|
33
34
|
if (!ticketRes.ok)
|
|
34
35
|
throw new Error(`Consent ticket failed: ${ticketRes.status}`);
|
|
35
36
|
const { ticket } = await ticketRes.json();
|
|
36
|
-
const mintRes = await
|
|
37
|
+
const mintRes = await apiFetch(`${getEnvServerUrl()}/api/keys`, {
|
|
37
38
|
method: 'POST',
|
|
38
|
-
credentials: 'include',
|
|
39
39
|
headers: { 'content-type': 'application/json' },
|
|
40
40
|
body: JSON.stringify({ ticket }),
|
|
41
41
|
});
|
|
@@ -44,16 +44,15 @@ export function createShardKeysApi(params) {
|
|
|
44
44
|
return mintRes.json();
|
|
45
45
|
},
|
|
46
46
|
async list() {
|
|
47
|
-
const res = await
|
|
47
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/keys`);
|
|
48
48
|
if (!res.ok)
|
|
49
49
|
throw new Error(`List failed: ${res.status}`);
|
|
50
50
|
const all = (await res.json());
|
|
51
51
|
return all.filter((k) => k.mintedByShardId === shardId);
|
|
52
52
|
},
|
|
53
53
|
async revoke(id) {
|
|
54
|
-
const res = await
|
|
54
|
+
const res = await apiFetch(`${getEnvServerUrl()}/api/keys/${encodeURIComponent(id)}`, {
|
|
55
55
|
method: 'DELETE',
|
|
56
|
-
credentials: 'include',
|
|
57
56
|
});
|
|
58
57
|
if (!res.ok && res.status !== 404)
|
|
59
58
|
throw new Error(`Revoke failed: ${res.status}`);
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
* The bus is populated by a server-sent events stream on /api/keys/events
|
|
7
7
|
* (wired by the sh3 runtime at boot) and/or by local revoke() calls.
|
|
8
8
|
*/
|
|
9
|
+
import { getEnvServerUrl } from '../env/serverUrl';
|
|
10
|
+
import { getAuthToken } from '../transport/authToken';
|
|
9
11
|
const handlersByShard = new Map();
|
|
10
12
|
/**
|
|
11
13
|
* Recently-emitted (shardId → Set<keyId>) with per-entry TTL timers.
|
|
@@ -78,7 +80,15 @@ export function emit(shardId, keyId) {
|
|
|
78
80
|
export function startServerSideStream() {
|
|
79
81
|
if (typeof EventSource === 'undefined')
|
|
80
82
|
return () => { };
|
|
81
|
-
|
|
83
|
+
// EventSource cannot send custom headers, so cross-origin auth (Tauri remote)
|
|
84
|
+
// is handled by passing the session token as a query param. Same-origin
|
|
85
|
+
// builds fall back to cookies via withCredentials.
|
|
86
|
+
const base = getEnvServerUrl();
|
|
87
|
+
const token = getAuthToken();
|
|
88
|
+
const url = token
|
|
89
|
+
? `${base}/api/keys/events?token=${encodeURIComponent(token)}`
|
|
90
|
+
: `${base}/api/keys/events`;
|
|
91
|
+
const es = new EventSource(url, token ? {} : { withCredentials: true });
|
|
82
92
|
es.onmessage = (msg) => {
|
|
83
93
|
try {
|
|
84
94
|
const ev = JSON.parse(msg.data);
|