sh3-core 0.16.1 → 0.17.2
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 +50 -108
- package/dist/__screenshots__/handheld.browser.test.ts/handheld-viewport-flip-e2e-viewport-override-flips-chrome-and-body-branches-1.png +0 -0
- package/dist/actions/ctx-actions.svelte.test.js +4 -4
- package/dist/actions/listActionsFromEntries.test.js +29 -0
- package/dist/actions/listActive.js +2 -0
- package/dist/actions/listeners.js +4 -0
- package/dist/actions/programmatic-dispatch.svelte.test.js +9 -2
- package/dist/actions/types.d.ts +8 -0
- package/dist/api.d.ts +6 -1
- package/dist/api.js +1 -0
- package/dist/chrome/CompactChrome.svelte +96 -0
- package/dist/chrome/CompactChrome.svelte.d.ts +3 -0
- package/dist/chrome/CompactChrome.svelte.test.d.ts +1 -0
- package/dist/chrome/CompactChrome.svelte.test.js +67 -0
- package/dist/chrome/MenuSheet.svelte +224 -0
- package/dist/chrome/MenuSheet.svelte.d.ts +7 -0
- package/dist/chrome/MenuSheet.svelte.test.d.ts +1 -0
- package/dist/chrome/MenuSheet.svelte.test.js +46 -0
- package/dist/contributions/index.d.ts +1 -1
- package/dist/contributions/index.js +1 -1
- package/dist/contributions/registry.d.ts +17 -1
- package/dist/contributions/registry.js +50 -2
- package/dist/contributions/scope.test.d.ts +1 -0
- package/dist/contributions/scope.test.js +52 -0
- package/dist/contributions/types.d.ts +11 -3
- package/dist/createShell.js +7 -1
- package/dist/fields/address.d.ts +3 -0
- package/dist/fields/address.js +36 -0
- package/dist/fields/address.test.d.ts +1 -0
- package/dist/fields/address.test.js +34 -0
- package/dist/fields/decoration.d.ts +7 -0
- package/dist/fields/decoration.js +199 -0
- package/dist/fields/decoration.svelte.test.d.ts +1 -0
- package/dist/fields/decoration.svelte.test.js +177 -0
- package/dist/fields/dispatch.d.ts +22 -0
- package/dist/fields/dispatch.js +254 -0
- package/dist/fields/dispatch.test.d.ts +1 -0
- package/dist/fields/dispatch.test.js +175 -0
- package/dist/fields/types.d.ts +101 -0
- package/dist/fields/types.js +16 -0
- package/dist/fields/walker.svelte.test.d.ts +1 -0
- package/dist/fields/walker.svelte.test.js +138 -0
- package/dist/handheld.browser.test.d.ts +1 -0
- package/dist/handheld.browser.test.js +90 -0
- package/dist/host.js +27 -2
- package/dist/host.svelte.test.d.ts +1 -0
- package/dist/host.svelte.test.js +92 -0
- package/dist/layout/LayoutRenderer.svelte +12 -1
- package/dist/layout/LayoutRenderer.svelte.d.ts +2 -1
- package/dist/layout/compact/CompactRenderer.svelte +53 -0
- package/dist/layout/compact/CompactRenderer.svelte.d.ts +3 -0
- package/dist/layout/compact/CompactRenderer.svelte.test.d.ts +1 -0
- package/dist/layout/compact/CompactRenderer.svelte.test.js +76 -0
- package/dist/layout/compact/derive.d.ts +3 -0
- package/dist/layout/compact/derive.js +155 -0
- package/dist/layout/compact/derive.test.d.ts +1 -0
- package/dist/layout/compact/derive.test.js +160 -0
- package/dist/layout/compact/drawerStore.svelte.d.ts +21 -0
- package/dist/layout/compact/drawerStore.svelte.js +75 -0
- package/dist/layout/compact/drawerStore.svelte.test.d.ts +1 -0
- package/dist/layout/compact/drawerStore.svelte.test.js +43 -0
- package/dist/layout/compact/resolveRole.d.ts +6 -0
- package/dist/layout/compact/resolveRole.js +13 -0
- package/dist/layout/compact/resolveRole.test.d.ts +1 -0
- package/dist/layout/compact/resolveRole.test.js +18 -0
- package/dist/layout/compact/types.d.ts +27 -0
- package/dist/layout/compact/types.js +15 -0
- package/dist/layout/presets.compactVariant.test.d.ts +1 -0
- package/dist/layout/presets.compactVariant.test.js +27 -0
- package/dist/layout/presets.d.ts +12 -0
- package/dist/layout/presets.js +16 -0
- package/dist/layout/slotHostPool.svelte.d.ts +8 -0
- package/dist/layout/slotHostPool.svelte.js +14 -1
- package/dist/layout/store.drawers.svelte.test.d.ts +1 -0
- package/dist/layout/store.drawers.svelte.test.js +49 -0
- package/dist/layout/store.schemaVersion.test.d.ts +1 -0
- package/dist/layout/store.schemaVersion.test.js +35 -0
- package/dist/layout/store.svelte.js +52 -2
- package/dist/layout/types.d.ts +43 -1
- package/dist/layout/types.js +1 -1
- package/dist/overlays/DrawerSurface.svelte +141 -0
- package/dist/overlays/DrawerSurface.svelte.d.ts +12 -0
- package/dist/overlays/DrawerSurface.svelte.test.d.ts +1 -0
- package/dist/overlays/DrawerSurface.svelte.test.js +67 -0
- package/dist/overlays/OverlayRoots.svelte +89 -0
- package/dist/overlays/OverlayRoots.svelte.d.ts +3 -0
- package/dist/overlays/types.d.ts +1 -1
- package/dist/platform/tauri-backend.d.ts +3 -3
- package/dist/platform/tauri-backend.js +24 -3
- package/dist/projects/session-state.svelte.d.ts +3 -3
- package/dist/projects/session-state.svelte.js +5 -4
- package/dist/runtime/runVerb.js +2 -2
- package/dist/satellite/SatelliteShell.svelte +58 -11
- package/dist/satellite/SatelliteShell.svelte.test.d.ts +1 -0
- package/dist/satellite/SatelliteShell.svelte.test.js +61 -0
- package/dist/sh3Api/fields-walker.svelte.test.d.ts +1 -0
- package/dist/sh3Api/fields-walker.svelte.test.js +75 -0
- package/dist/sh3Api/headless.d.ts +9 -0
- package/dist/sh3Api/headless.js +171 -16
- package/dist/sh3Api/headless.svelte.test.js +54 -10
- package/dist/sh3Runtime.svelte.d.ts +36 -0
- package/dist/sh3Runtime.svelte.js +33 -0
- package/dist/sh3core-shard/sh3coreShard.svelte.js +2 -2
- package/dist/shards/activate-fields.svelte.test.d.ts +1 -0
- package/dist/shards/activate-fields.svelte.test.js +121 -0
- package/dist/shards/activate-runtime.test.js +8 -8
- package/dist/shards/activate.svelte.js +29 -35
- package/dist/shards/types.d.ts +23 -76
- package/dist/shell-shard/ScrollbackView.svelte +55 -9
- package/dist/shell-shard/Terminal.svelte +1 -1
- package/dist/shell-shard/scrollback-stick.d.ts +9 -0
- package/dist/shell-shard/scrollback-stick.js +21 -0
- package/dist/shell-shard/scrollback-stick.test.d.ts +1 -0
- package/dist/shell-shard/scrollback-stick.test.js +25 -0
- package/dist/tokens.css +3 -2
- package/dist/verbs/types.d.ts +59 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/viewport/classify.d.ts +8 -0
- package/dist/viewport/classify.js +20 -0
- package/dist/viewport/classify.test.d.ts +1 -0
- package/dist/viewport/classify.test.js +32 -0
- package/dist/viewport/store.browser.test.d.ts +1 -0
- package/dist/viewport/store.browser.test.js +33 -0
- package/dist/viewport/store.svelte.d.ts +9 -0
- package/dist/viewport/store.svelte.js +71 -0
- package/dist/viewport/store.svelte.test.d.ts +1 -0
- package/dist/viewport/store.svelte.test.js +54 -0
- package/dist/viewport/types.d.ts +9 -0
- package/dist/viewport/types.js +6 -0
- package/package.json +1 -1
|
@@ -41,7 +41,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
41
41
|
});
|
|
42
42
|
await activateShard('host');
|
|
43
43
|
await activateShard('consumer');
|
|
44
|
-
const list = consumerCtx.listVerbs();
|
|
44
|
+
const list = consumerCtx.sh3.listVerbs();
|
|
45
45
|
const a = list.find((v) => v.name === 'host:a');
|
|
46
46
|
const b = list.find((v) => v.name === 'host:b');
|
|
47
47
|
expect(a).toEqual({ shardId: 'host', name: 'host:a', summary: 'first verb', programmatic: true, schema: undefined });
|
|
@@ -64,7 +64,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
64
64
|
});
|
|
65
65
|
await activateShard('host');
|
|
66
66
|
await activateShard('consumer');
|
|
67
|
-
const list = consumerCtx.listVerbs({ programmaticOnly: true });
|
|
67
|
+
const list = consumerCtx.sh3.listVerbs({ programmaticOnly: true });
|
|
68
68
|
expect(list.find((v) => v.name === 'host:a')).toBeDefined();
|
|
69
69
|
expect(list.find((v) => v.name === 'host:b')).toBeUndefined();
|
|
70
70
|
expect(list.every((v) => v.programmatic === true)).toBe(true);
|
|
@@ -87,7 +87,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
87
87
|
});
|
|
88
88
|
await activateShard('host');
|
|
89
89
|
await activateShard('consumer');
|
|
90
|
-
const out = await consumerCtx.runVerb('host', 'host:echo', ['hello']);
|
|
90
|
+
const out = await consumerCtx.sh3.runVerb('host', 'host:echo', ['hello']);
|
|
91
91
|
expect(out.scrollback).toHaveLength(1);
|
|
92
92
|
expect(out.scrollback[0]).toMatchObject({ kind: 'status', text: 'echo: hello' });
|
|
93
93
|
});
|
|
@@ -107,7 +107,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
107
107
|
});
|
|
108
108
|
await activateShard('host');
|
|
109
109
|
await activateShard('consumer');
|
|
110
|
-
await expect(consumerCtx.runVerb('host', 'host:plain', [])).rejects.toThrow('verb "host:plain" is not programmatic');
|
|
110
|
+
await expect(consumerCtx.sh3.runVerb('host', 'host:plain', [])).rejects.toThrow('verb "host:plain" is not programmatic');
|
|
111
111
|
});
|
|
112
112
|
it('runVerb populates ctx.structuredArgs when opts.structured is set', async () => {
|
|
113
113
|
let observed = undefined;
|
|
@@ -128,7 +128,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
128
128
|
});
|
|
129
129
|
await activateShard('host');
|
|
130
130
|
await activateShard('consumer');
|
|
131
|
-
await consumerCtx.runVerb('host', 'host:schemaCheck', [], { structured: { foo: 'bar' } });
|
|
131
|
+
await consumerCtx.sh3.runVerb('host', 'host:schemaCheck', [], { structured: { foo: 'bar' } });
|
|
132
132
|
expect(observed).toEqual({ foo: 'bar' });
|
|
133
133
|
});
|
|
134
134
|
it('runVerb rejects on unknown shardId', async () => {
|
|
@@ -140,7 +140,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
140
140
|
},
|
|
141
141
|
});
|
|
142
142
|
await activateShard('consumer');
|
|
143
|
-
await expect(consumerCtx.runVerb('missing', 'x', [])).rejects.toThrow('unknown shard: missing');
|
|
143
|
+
await expect(consumerCtx.sh3.runVerb('missing', 'x', [])).rejects.toThrow('unknown shard: missing');
|
|
144
144
|
});
|
|
145
145
|
it('runVerb rejects on unknown verb', async () => {
|
|
146
146
|
registerShard({
|
|
@@ -158,7 +158,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
158
158
|
});
|
|
159
159
|
await activateShard('host');
|
|
160
160
|
await activateShard('consumer');
|
|
161
|
-
await expect(consumerCtx.runVerb('host', 'host:absent', [])).rejects.toThrow('unknown verb: host:absent');
|
|
161
|
+
await expect(consumerCtx.sh3.runVerb('host', 'host:absent', [])).rejects.toThrow('unknown verb: host:absent');
|
|
162
162
|
});
|
|
163
163
|
it('verbs declaring schema.input expose it via listVerbs', async () => {
|
|
164
164
|
registerShard({
|
|
@@ -188,7 +188,7 @@ describe('ctx.listVerbs / ctx.runVerb (integration)', () => {
|
|
|
188
188
|
});
|
|
189
189
|
await activateShard('host');
|
|
190
190
|
await activateShard('consumer');
|
|
191
|
-
const list = consumerCtx.listVerbs();
|
|
191
|
+
const list = consumerCtx.sh3.listVerbs();
|
|
192
192
|
const typed = list.find((v) => v.name === 'host:typed');
|
|
193
193
|
expect(typed === null || typed === void 0 ? void 0 : typed.schema).toEqual({
|
|
194
194
|
input: {
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* stays in `registeredShards` — it's still known, just not running.
|
|
18
18
|
*/
|
|
19
19
|
import { sh3 } from '../sh3Runtime.svelte';
|
|
20
|
-
import { registerView, unregisterView, registerVerb as fwRegisterVerb, unregisterVerb as fwUnregisterVerb
|
|
21
|
-
import {
|
|
20
|
+
import { registerView, unregisterView, registerVerb as fwRegisterVerb, unregisterVerb as fwUnregisterVerb } from './registry';
|
|
21
|
+
import { makeSh3Api } from '../sh3Api/headless';
|
|
22
22
|
import { createDocumentHandle, getTenantId, getDocumentBackend } from '../documents';
|
|
23
23
|
import { fetchEnvState, putEnvState } from '../env/client';
|
|
24
24
|
import { isAdmin as checkIsAdmin } from '../auth/index';
|
|
@@ -30,11 +30,9 @@ import { createShardKeysApi } from '../keys/client';
|
|
|
30
30
|
import { PERMISSION_KEYS_MINT } from '../keys/types';
|
|
31
31
|
import { subscribe } from '../keys/revocation-bus.svelte';
|
|
32
32
|
import { register as contributionsRegister, list as contributionsList, listPoints as contributionsListPoints, onChange as contributionsOnChange, onAnyChange as contributionsOnAnyChange, } from '../contributions';
|
|
33
|
-
import { registerAction
|
|
33
|
+
import { registerAction } from '../actions/registry';
|
|
34
34
|
import { makeSelectionApi, clearSelectionForShard } from '../actions/selection.svelte';
|
|
35
|
-
import { openContextMenu as sh3OpenContextMenu, openPalette as sh3OpenPalette,
|
|
36
|
-
import { listActionsFromEntries } from '../actions/listActive';
|
|
37
|
-
import { getLiveDispatcherState } from '../actions/state.svelte';
|
|
35
|
+
import { openContextMenu as sh3OpenContextMenu, openPalette as sh3OpenPalette, } from '../actions/listeners';
|
|
38
36
|
/**
|
|
39
37
|
* Reactive registry of every shard known to the host. Keys are shard ids.
|
|
40
38
|
* Populated once at boot by the glob-discovery loop in main.ts (through
|
|
@@ -88,7 +86,7 @@ export function registerShard(shard) {
|
|
|
88
86
|
* @throws If the shard is not registered, if `shard.activate` throws, or if a manifest view has no factory after activation.
|
|
89
87
|
*/
|
|
90
88
|
export async function activateShard(id, opts) {
|
|
91
|
-
var _a, _b, _c, _d, _e, _f;
|
|
89
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
92
90
|
const shard = registeredShards.get(id);
|
|
93
91
|
if (!shard) {
|
|
94
92
|
throw new Error(`Cannot activate shard "${id}": not registered`);
|
|
@@ -109,8 +107,20 @@ export async function activateShard(id, opts) {
|
|
|
109
107
|
// global registry, and its disposer is pushed into entry.cleanupFns
|
|
110
108
|
// so deactivate auto-unregisters.
|
|
111
109
|
const contributions = {
|
|
112
|
-
register(pointId, descriptor) {
|
|
113
|
-
|
|
110
|
+
register(pointId, descriptor, contribOpts) {
|
|
111
|
+
var _a;
|
|
112
|
+
// Auto-wrap controllable-field descriptors with ownership metadata so
|
|
113
|
+
// fields/dispatch.ts can route addressed get/set without the user
|
|
114
|
+
// descriptor needing to know about the framework's wire format.
|
|
115
|
+
let stored = descriptor;
|
|
116
|
+
if (pointId === 'sh3.controllable-field') {
|
|
117
|
+
const owner = { shardId: id };
|
|
118
|
+
if (((_a = contribOpts === null || contribOpts === void 0 ? void 0 : contribOpts.scope) === null || _a === void 0 ? void 0 : _a.slotId) !== undefined) {
|
|
119
|
+
owner.slotId = contribOpts.scope.slotId;
|
|
120
|
+
}
|
|
121
|
+
stored = { owner, descriptor };
|
|
122
|
+
}
|
|
123
|
+
const dispose = contributionsRegister(pointId, stored, contribOpts);
|
|
114
124
|
entry.cleanupFns.push(async () => dispose());
|
|
115
125
|
return dispose;
|
|
116
126
|
},
|
|
@@ -203,29 +213,13 @@ export async function activateShard(id, opts) {
|
|
|
203
213
|
openContextMenu(opts) { sh3OpenContextMenu(opts); },
|
|
204
214
|
openPalette(opts) { sh3OpenPalette(opts); },
|
|
205
215
|
},
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
name: verb.name,
|
|
214
|
-
summary: verb.summary,
|
|
215
|
-
programmatic: verb.programmatic,
|
|
216
|
-
schema: verb.schema,
|
|
217
|
-
}));
|
|
218
|
-
},
|
|
219
|
-
runVerb(shardId, name, args, opts) {
|
|
220
|
-
return runVerbProgrammatic(shardId, name, args, opts);
|
|
221
|
-
},
|
|
222
|
-
listActions(opts) {
|
|
223
|
-
const all = listActionsFromEntries(listActionsFromRegistry(), getLiveDispatcherState());
|
|
224
|
-
return (opts === null || opts === void 0 ? void 0 : opts.activeOnly) ? all.filter((a) => a.active) : all;
|
|
225
|
-
},
|
|
226
|
-
runAction(actionId, opts) {
|
|
227
|
-
return dispatchActionProgrammatic(actionId, opts);
|
|
228
|
-
},
|
|
216
|
+
sh3: makeSh3Api({
|
|
217
|
+
callerKind: 'shard',
|
|
218
|
+
callerShardId: id,
|
|
219
|
+
zones: ((_e = shard.manifest.permissions) === null || _e === void 0 ? void 0 : _e.includes(PERMISSION_STATE_MANAGE))
|
|
220
|
+
? createZoneManager()
|
|
221
|
+
: undefined,
|
|
222
|
+
}),
|
|
229
223
|
};
|
|
230
224
|
entry.ctx = ctx;
|
|
231
225
|
// Wire onKeyRevoked hook: subscribe to the revocation bus for this shard.
|
|
@@ -271,7 +265,7 @@ export async function activateShard(id, opts) {
|
|
|
271
265
|
try {
|
|
272
266
|
void fn();
|
|
273
267
|
}
|
|
274
|
-
catch (
|
|
268
|
+
catch (_h) {
|
|
275
269
|
// intentionally swallowed: original error is what matters.
|
|
276
270
|
}
|
|
277
271
|
}
|
|
@@ -285,7 +279,7 @@ export async function activateShard(id, opts) {
|
|
|
285
279
|
erroredShards.set(id, {
|
|
286
280
|
id,
|
|
287
281
|
error: err,
|
|
288
|
-
phase: (
|
|
282
|
+
phase: (_f = opts === null || opts === void 0 ? void 0 : opts.phase) !== null && _f !== void 0 ? _f : 'launch',
|
|
289
283
|
timestamp: Date.now(),
|
|
290
284
|
});
|
|
291
285
|
console.error(`[sh3] Shard "${id}" failed to activate:`, err);
|
|
@@ -293,7 +287,7 @@ export async function activateShard(id, opts) {
|
|
|
293
287
|
}
|
|
294
288
|
// Activation succeeded — clear any prior error record for this shard.
|
|
295
289
|
erroredShards.delete(id);
|
|
296
|
-
void ((
|
|
290
|
+
void ((_g = shard.autostart) === null || _g === void 0 ? void 0 : _g.call(shard, ctx));
|
|
297
291
|
}
|
|
298
292
|
/**
|
|
299
293
|
* Deactivate an active shard. Calls `shard.deactivate`, flushes and disposes
|
package/dist/shards/types.d.ts
CHANGED
|
@@ -3,12 +3,12 @@ import type { ZoneSchema, ZoneManager } from '../state/types';
|
|
|
3
3
|
import type { DocumentHandle, DocumentHandleOptions } from '../documents/types';
|
|
4
4
|
import type { BrowseCapability } from '../documents/browse';
|
|
5
5
|
import type { EnvState } from '../env/types';
|
|
6
|
-
import type { Verb
|
|
7
|
-
import type {
|
|
6
|
+
import type { Verb } from '../verbs/types';
|
|
7
|
+
import type { Sh3Api } from '../verbs/types';
|
|
8
8
|
import type { ShardContextKeys } from '../keys/types';
|
|
9
9
|
import type { ContributionsApi } from '../contributions/types';
|
|
10
|
-
import type { ActionsApi
|
|
11
|
-
import type { TreeRootRef } from '../layout/types';
|
|
10
|
+
import type { ActionsApi } from '../actions/types';
|
|
11
|
+
import type { TreeRootRef, SlotRole } from '../layout/types';
|
|
12
12
|
export { PERMISSION_KEYS_MINT, type ShardContextKeys, type ApiKeyPublic, type MintOpts, ScopeEscalationError, ConsentDeniedError } from '../keys/types';
|
|
13
13
|
/**
|
|
14
14
|
* The object returned by `ViewFactory.mount`. The framework calls
|
|
@@ -38,6 +38,14 @@ export interface ViewHandle {
|
|
|
38
38
|
closable?: boolean | {
|
|
39
39
|
canClose(): Promise<boolean>;
|
|
40
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* View-level slot-role default. The compact renderer reads this when
|
|
43
|
+
* the containing slot's `role` is unset; slot-level always wins.
|
|
44
|
+
*
|
|
45
|
+
* Lets a view declare "I'm a sidebar by nature" without forcing the
|
|
46
|
+
* app author to know. See `layout/compact/resolveRole.ts`.
|
|
47
|
+
*/
|
|
48
|
+
defaultRole?: SlotRole;
|
|
41
49
|
}
|
|
42
50
|
/**
|
|
43
51
|
* Context passed to `ViewFactory.mount` so the view knows which layout
|
|
@@ -254,81 +262,20 @@ export interface ShardContext {
|
|
|
254
262
|
*/
|
|
255
263
|
actions: ActionsApi;
|
|
256
264
|
/**
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
* present) its `schema`. Order is undefined.
|
|
261
|
-
*
|
|
262
|
-
* Pass `{ programmaticOnly: true }` to restrict the result to verbs that
|
|
263
|
-
* have opted in via `programmatic: true` — i.e. the verbs that are
|
|
264
|
-
* actually invocable through `ctx.runVerb(...)`. AI-class shards
|
|
265
|
-
* typically want this filter so they only surface what they can call.
|
|
266
|
-
*
|
|
267
|
-
* No permission gate — verb names + summaries are already visible via
|
|
268
|
-
* the `help` verb. Diagnostic and AI-class shards (sh3-ai, sh3-diagnostic)
|
|
269
|
-
* use this to enumerate the host's action surface.
|
|
270
|
-
*/
|
|
271
|
-
listVerbs(opts?: {
|
|
272
|
-
programmaticOnly?: boolean;
|
|
273
|
-
}): Array<{
|
|
274
|
-
shardId: string;
|
|
275
|
-
name: string;
|
|
276
|
-
summary: string;
|
|
277
|
-
programmatic?: boolean;
|
|
278
|
-
schema?: VerbSchema;
|
|
279
|
-
}>;
|
|
280
|
-
/**
|
|
281
|
-
* Programmatically dispatch a verb by `(shardId, name)`. Resolves with
|
|
282
|
-
* `{ result, scrollback }` where `scrollback` is the array of entries
|
|
283
|
-
* the verb pushed during invocation. Rejects on:
|
|
284
|
-
* - unknown shardId,
|
|
285
|
-
* - unknown verb,
|
|
286
|
-
* - target verb not opted in via `programmatic: true`,
|
|
287
|
-
* - any error thrown by the verb's `run`.
|
|
265
|
+
* Cross-shard read+invoke façade. Same type exposed on `VerbContext.sh3`.
|
|
266
|
+
* Use for: enumerating verbs/actions/views, programmatic dispatch,
|
|
267
|
+
* controllable-field operations, decoration overlays.
|
|
288
268
|
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
signal?: AbortSignal;
|
|
295
|
-
structured?: unknown;
|
|
296
|
-
}): Promise<{
|
|
297
|
-
result: unknown;
|
|
298
|
-
scrollback: ScrollbackEntry[];
|
|
299
|
-
}>;
|
|
300
|
-
/**
|
|
301
|
-
* Read-only snapshot of every action registered across every shard.
|
|
302
|
-
* Returns one descriptor per action id; the `active` flag indicates
|
|
303
|
-
* whether `runAction(id)` would dispatch right now (scope live, not
|
|
304
|
-
* disabled, has a run handler).
|
|
305
|
-
*
|
|
306
|
-
* Pass `{ activeOnly: true }` to filter to currently-dispatchable
|
|
307
|
-
* actions. AI-class shards typically want this filter.
|
|
308
|
-
*
|
|
309
|
-
* No permission gate — actions are already enumerable through the
|
|
310
|
-
* keyboard / palette / context-menu surfaces.
|
|
311
|
-
*/
|
|
312
|
-
listActions(opts?: {
|
|
313
|
-
activeOnly?: boolean;
|
|
314
|
-
}): ActionDescriptor[];
|
|
315
|
-
/**
|
|
316
|
-
* Programmatically dispatch a registered action by id. Synthesizes the
|
|
317
|
-
* same `ActionDispatchContext` the keyboard/palette/context-menu paths
|
|
318
|
-
* use, with `invokedVia: 'programmatic'` and `appId / viewId / selection`
|
|
319
|
-
* sourced from current live state. Resolves after the action's `run`
|
|
320
|
-
* settles. Rejects on:
|
|
321
|
-
* - unknown action id,
|
|
322
|
-
* - action exists but is inactive (out-of-scope, disabled, submenu
|
|
323
|
-
* parent without `run`),
|
|
324
|
-
* - any error thrown by the action's `run`.
|
|
269
|
+
* In v0.17.0 this absorbs the four flat methods previously on `ShardContext`:
|
|
270
|
+
* - `ctx.listVerbs(opts?)` → `ctx.sh3.listVerbs(opts?)`
|
|
271
|
+
* - `ctx.runVerb(...)` → `ctx.sh3.runVerb(...)`
|
|
272
|
+
* - `ctx.listActions(opts?)` → `ctx.sh3.listActions(opts?)`
|
|
273
|
+
* - `ctx.runAction(id, ...)` → `ctx.sh3.runAction(id, ...)`
|
|
325
274
|
*
|
|
326
|
-
*
|
|
327
|
-
* `
|
|
275
|
+
* Ownership-bound registration (`registerView`, `registerVerb`, `actions`,
|
|
276
|
+
* `contributions`, `documents`) stays on `ShardContext` directly.
|
|
328
277
|
*/
|
|
329
|
-
|
|
330
|
-
signal?: AbortSignal;
|
|
331
|
-
}): Promise<void>;
|
|
278
|
+
sh3: Sh3Api;
|
|
332
279
|
}
|
|
333
280
|
/**
|
|
334
281
|
* A shard module. Shards are the fundamental unit of contribution in SH3.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Scrollback } from './scrollback.svelte';
|
|
3
|
+
import { isAtBottom } from './scrollback-stick';
|
|
3
4
|
import TextEntry from './entries/TextEntry.svelte';
|
|
4
5
|
import PromptEntry from './entries/PromptEntry.svelte';
|
|
5
6
|
import StatusEntry from './entries/StatusEntry.svelte';
|
|
@@ -14,20 +15,54 @@
|
|
|
14
15
|
|
|
15
16
|
let container: HTMLDivElement | null = $state(null);
|
|
16
17
|
let content: HTMLDivElement | null = $state(null);
|
|
17
|
-
let stuck = true;
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
// Stick-to-bottom is driven by user intent, not by inferring intent from
|
|
20
|
+
// scroll events. A scroll event tells us scrollTop changed but not WHY:
|
|
21
|
+
// it may be our programmatic snap, a browser scroll-anchor adjustment
|
|
22
|
+
// after a layout-affecting markdown re-render, or a real user wheel/touch.
|
|
23
|
+
// Conflating browser-induced shifts with user intent silently dropped the
|
|
24
|
+
// snap mid-stream. We now only surrender stick on actual input events
|
|
25
|
+
// (wheel / touchstart / keydown) and reacquire once the viewport reaches
|
|
26
|
+
// the bottom geometrically.
|
|
27
|
+
let userScrolling = false;
|
|
28
|
+
|
|
29
|
+
// scrollHeight − scrollTop − clientHeight can settle on small non-zero
|
|
20
30
|
// values from sub-pixel rounding even when visually at the bottom.
|
|
21
31
|
const STICK_THRESHOLD_PX = 4;
|
|
22
32
|
|
|
23
|
-
function isAtBottom(el: HTMLElement): boolean {
|
|
24
|
-
return el.scrollHeight - el.scrollTop - el.clientHeight <= STICK_THRESHOLD_PX;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
33
|
function handleScroll(): void {
|
|
28
|
-
if (container)
|
|
34
|
+
if (!container) return;
|
|
35
|
+
if (
|
|
36
|
+
isAtBottom({
|
|
37
|
+
scrollTop: container.scrollTop,
|
|
38
|
+
scrollHeight: container.scrollHeight,
|
|
39
|
+
clientHeight: container.clientHeight,
|
|
40
|
+
threshold: STICK_THRESHOLD_PX,
|
|
41
|
+
})
|
|
42
|
+
) {
|
|
43
|
+
userScrolling = false;
|
|
44
|
+
}
|
|
29
45
|
}
|
|
30
46
|
|
|
47
|
+
// Intent listeners are attached imperatively so wheel/touchstart can be
|
|
48
|
+
// passive (no main-thread block on scroll) and so the template stays a
|
|
49
|
+
// plain scroll container — putting touchstart/keydown attributes on the
|
|
50
|
+
// <div> tripped Svelte's a11y_no_static_element_interactions check.
|
|
51
|
+
$effect(() => {
|
|
52
|
+
if (!container) return;
|
|
53
|
+
const mark = () => {
|
|
54
|
+
userScrolling = true;
|
|
55
|
+
};
|
|
56
|
+
container.addEventListener('wheel', mark, { passive: true });
|
|
57
|
+
container.addEventListener('touchstart', mark, { passive: true });
|
|
58
|
+
container.addEventListener('keydown', mark);
|
|
59
|
+
return () => {
|
|
60
|
+
container?.removeEventListener('wheel', mark);
|
|
61
|
+
container?.removeEventListener('touchstart', mark);
|
|
62
|
+
container?.removeEventListener('keydown', mark);
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
31
66
|
// ResizeObserver on the inner content wrapper fires on any layout-affecting
|
|
32
67
|
// change regardless of source: text-chunk pushes, mutated rich-entry props
|
|
33
68
|
// (output.stream() / output.rich() handles), image or font load. A reactivity-
|
|
@@ -37,14 +72,20 @@
|
|
|
37
72
|
$effect(() => {
|
|
38
73
|
if (!content || !container) return;
|
|
39
74
|
const ro = new ResizeObserver(() => {
|
|
40
|
-
if (container &&
|
|
75
|
+
if (container && !userScrolling) {
|
|
76
|
+
container.scrollTop = container.scrollHeight;
|
|
77
|
+
}
|
|
41
78
|
});
|
|
42
79
|
ro.observe(content);
|
|
43
80
|
return () => ro.disconnect();
|
|
44
81
|
});
|
|
45
82
|
</script>
|
|
46
83
|
|
|
47
|
-
<div
|
|
84
|
+
<div
|
|
85
|
+
class="shell-scrollback"
|
|
86
|
+
bind:this={container}
|
|
87
|
+
onscroll={handleScroll}
|
|
88
|
+
>
|
|
48
89
|
<div class="content" bind:this={content}>
|
|
49
90
|
{#each scrollback.entries as entry (entry.id)}
|
|
50
91
|
{#if entry.kind === 'text'}
|
|
@@ -69,6 +110,11 @@
|
|
|
69
110
|
.shell-scrollback {
|
|
70
111
|
flex: 1 1 auto;
|
|
71
112
|
overflow-y: auto;
|
|
113
|
+
/* Disable browser scroll-anchor: when markdown re-renders shift content
|
|
114
|
+
above the viewport (a code fence completing, a heading appearing), the
|
|
115
|
+
browser would otherwise nudge scrollTop to keep the visual anchor put,
|
|
116
|
+
firing scroll events that have nothing to do with user intent. */
|
|
117
|
+
overflow-anchor: none;
|
|
72
118
|
background: var(--sh3-bg, #111);
|
|
73
119
|
color: var(--sh3-fg, #ddd);
|
|
74
120
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface IsAtBottomInput {
|
|
2
|
+
scrollTop: number;
|
|
3
|
+
scrollHeight: number;
|
|
4
|
+
clientHeight: number;
|
|
5
|
+
/** Tolerance for sub-pixel rounding (scrollHeight − scrollTop − clientHeight
|
|
6
|
+
* can settle on small non-zero values when visually at the bottom). */
|
|
7
|
+
threshold: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function isAtBottom(input: IsAtBottomInput): boolean;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Pure helper for ScrollbackView's autoscroll geometry.
|
|
3
|
+
*
|
|
4
|
+
* Stick-to-bottom is driven by USER INTENT (wheel / touchstart / keydown
|
|
5
|
+
* events on the scroll container), not by inferring intent from scroll
|
|
6
|
+
* events alone. A scroll event tells us scrollTop changed but not WHY:
|
|
7
|
+
* candidates include our own programmatic snap, a browser scroll-anchor
|
|
8
|
+
* adjustment after layout, and a real user wheel/touch. Treating the
|
|
9
|
+
* scroll-event-derived "we are no longer at the bottom" signal as a
|
|
10
|
+
* decision to drop autoscroll caused the snap to silently die mid-stream
|
|
11
|
+
* whenever markdown re-rendering shifted layout above the viewport.
|
|
12
|
+
*
|
|
13
|
+
* Intent tracking lives in the Svelte component because it is entirely a
|
|
14
|
+
* matter of DOM event wiring. This module only exposes the geometric
|
|
15
|
+
* "is the viewport currently at the bottom?" check, which the component
|
|
16
|
+
* uses to release `userScrolling` once the user lands back at the bottom.
|
|
17
|
+
*/
|
|
18
|
+
export function isAtBottom(input) {
|
|
19
|
+
const { scrollTop, scrollHeight, clientHeight, threshold } = input;
|
|
20
|
+
return scrollHeight - scrollTop - clientHeight <= threshold;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { isAtBottom } from './scrollback-stick';
|
|
3
|
+
describe('isAtBottom', () => {
|
|
4
|
+
const base = { scrollHeight: 1500, clientHeight: 500, threshold: 4 };
|
|
5
|
+
test('exact bottom', () => {
|
|
6
|
+
expect(isAtBottom(Object.assign(Object.assign({}, base), { scrollTop: 1000 }))).toBe(true);
|
|
7
|
+
});
|
|
8
|
+
test('within sub-pixel threshold', () => {
|
|
9
|
+
// Sub-pixel rounding can leave scrollHeight - scrollTop - clientHeight
|
|
10
|
+
// a few pixels above zero even when the viewport is visually flush.
|
|
11
|
+
expect(isAtBottom(Object.assign(Object.assign({}, base), { scrollTop: 996 }))).toBe(true);
|
|
12
|
+
});
|
|
13
|
+
test('beyond threshold', () => {
|
|
14
|
+
expect(isAtBottom(Object.assign(Object.assign({}, base), { scrollTop: 990 }))).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
test('top of scrollback', () => {
|
|
17
|
+
expect(isAtBottom(Object.assign(Object.assign({}, base), { scrollTop: 0 }))).toBe(false);
|
|
18
|
+
});
|
|
19
|
+
test('content shorter than viewport reads as at-bottom', () => {
|
|
20
|
+
// When scrollHeight ≤ clientHeight the delta is negative, so any
|
|
21
|
+
// non-negative threshold treats this as "at the bottom" — there is
|
|
22
|
+
// nowhere else to be.
|
|
23
|
+
expect(isAtBottom({ scrollTop: 0, scrollHeight: 200, clientHeight: 500, threshold: 4 })).toBe(true);
|
|
24
|
+
});
|
|
25
|
+
});
|
package/dist/tokens.css
CHANGED
|
@@ -79,8 +79,9 @@
|
|
|
79
79
|
* source of truth for the layer stack. No component outside the overlay
|
|
80
80
|
* layer managers is permitted to write a z-index.
|
|
81
81
|
*/
|
|
82
|
-
--sh3-z-layer-0: 0; /* docked layout (content area)
|
|
83
|
-
--sh3-z-layer-
|
|
82
|
+
--sh3-z-layer-0: 0; /* docked layout (content area) */
|
|
83
|
+
--sh3-z-layer-drawers: 50; /* compact-mode drawer surfaces */
|
|
84
|
+
--sh3-z-layer-1: 100; /* floating panels (deferred) */
|
|
84
85
|
--sh3-z-layer-2: 200; /* drag preview */
|
|
85
86
|
--sh3-z-layer-3: 300; /* popups, context menus */
|
|
86
87
|
--sh3-z-layer-4: 400; /* modals */
|
package/dist/verbs/types.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import type { Scrollback } from '../shell-shard/scrollback.svelte';
|
|
1
|
+
import type { Scrollback, ScrollbackEntry } from '../shell-shard/scrollback.svelte';
|
|
2
2
|
import type { SessionClient } from '../shell-shard/session-client.svelte';
|
|
3
3
|
import type { TenantFsClient } from '../shell-shard/tenant-fs-client';
|
|
4
4
|
import type { TreeRootRef } from '../layout/types';
|
|
5
5
|
import type { DispatchToTerminalResult } from '../shell-shard/dispatch-to-terminal';
|
|
6
|
+
import type { ActionDescriptor } from '../actions/types';
|
|
7
|
+
import type { FieldsApi } from '../fields/types';
|
|
6
8
|
export interface Sh3Api {
|
|
7
9
|
listApps(): Array<{
|
|
8
10
|
id: string;
|
|
@@ -100,6 +102,62 @@ export interface Sh3Api {
|
|
|
100
102
|
* its verb registry, gating, and history exactly like a typed submit.
|
|
101
103
|
*/
|
|
102
104
|
dispatchToTerminal(line: string): DispatchToTerminalResult;
|
|
105
|
+
/**
|
|
106
|
+
* Read-only snapshot of every verb registered across every active shard.
|
|
107
|
+
* Relocated here from ShardContext in v0.17.0. Pass
|
|
108
|
+
* { programmaticOnly: true } to restrict to verbs invocable through runVerb.
|
|
109
|
+
*/
|
|
110
|
+
listVerbs(opts?: {
|
|
111
|
+
programmaticOnly?: boolean;
|
|
112
|
+
}): Array<{
|
|
113
|
+
shardId: string;
|
|
114
|
+
name: string;
|
|
115
|
+
summary: string;
|
|
116
|
+
programmatic?: boolean;
|
|
117
|
+
schema?: VerbSchema;
|
|
118
|
+
}>;
|
|
119
|
+
/**
|
|
120
|
+
* Programmatically dispatch a verb. Resolves with { result, scrollback }.
|
|
121
|
+
* Rejects on unknown shard/verb, non-programmatic verb, or verb runtime error.
|
|
122
|
+
*/
|
|
123
|
+
runVerb(shardId: string, name: string, args: string[], opts?: {
|
|
124
|
+
signal?: AbortSignal;
|
|
125
|
+
structured?: unknown;
|
|
126
|
+
}): Promise<{
|
|
127
|
+
result: unknown;
|
|
128
|
+
scrollback: ScrollbackEntry[];
|
|
129
|
+
}>;
|
|
130
|
+
/**
|
|
131
|
+
* Read-only snapshot of every action registered across every shard.
|
|
132
|
+
* - `activeOnly`: filter to currently-dispatchable actions.
|
|
133
|
+
* - `submenuOf`: restrict to children of the named parent action id
|
|
134
|
+
* (mirrors the palette sub-drill filter).
|
|
135
|
+
*/
|
|
136
|
+
listActions(opts?: {
|
|
137
|
+
activeOnly?: boolean;
|
|
138
|
+
submenuOf?: string;
|
|
139
|
+
}): ActionDescriptor[];
|
|
140
|
+
/**
|
|
141
|
+
* Programmatically dispatch a registered action by id. Same semantics as
|
|
142
|
+
* the keyboard / palette / context-menu paths but with
|
|
143
|
+
* invokedVia: 'programmatic'.
|
|
144
|
+
*/
|
|
145
|
+
runAction(id: string, opts?: {
|
|
146
|
+
signal?: AbortSignal;
|
|
147
|
+
}): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Alias of listViewsInCurrentLayout. Kept short for ergonomics; the long
|
|
150
|
+
* name remains a deprecated alias for one minor cycle.
|
|
151
|
+
*/
|
|
152
|
+
listViews(): Array<{
|
|
153
|
+
slotId: string;
|
|
154
|
+
viewId: string;
|
|
155
|
+
label: string;
|
|
156
|
+
}>;
|
|
157
|
+
/**
|
|
158
|
+
* Controllable-field surface — see fields/types.ts:FieldsApi for shape.
|
|
159
|
+
*/
|
|
160
|
+
fields: FieldsApi;
|
|
103
161
|
}
|
|
104
162
|
export type { DispatchToTerminalResult } from '../shell-shard/dispatch-to-terminal';
|
|
105
163
|
export interface VerbContext {
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.17.2";
|
package/dist/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Auto-generated from package.json — do not edit manually. */
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.17.2';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* classify — derives the viewport class from multi-signal input.
|
|
3
|
+
*
|
|
4
|
+
* Why multi-signal: a 6.7" phone reports ~393 CSS px wide *with* coarse
|
|
5
|
+
* pointer + high DPR; a narrow desktop window reports the same width
|
|
6
|
+
* *with* fine pointer + DPR 1-2. CSS pixels alone undercount physical
|
|
7
|
+
* compactness on high-DPI mobile, so width is one signal among three.
|
|
8
|
+
*
|
|
9
|
+
* The thresholds (720, 1100) are not load-bearing — they're tunable in
|
|
10
|
+
* a single place. Adjust with a corresponding test row.
|
|
11
|
+
*/
|
|
12
|
+
export function classify(i) {
|
|
13
|
+
if (i.coarsePointer && i.noHover)
|
|
14
|
+
return 'compact';
|
|
15
|
+
if (i.width < 720)
|
|
16
|
+
return 'compact';
|
|
17
|
+
if (i.coarsePointer && i.dpr >= 2 && i.width < 1100)
|
|
18
|
+
return 'compact';
|
|
19
|
+
return 'desktop';
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|