sh3-core 0.16.0 → 0.17.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 (68) hide show
  1. package/dist/Sh3.svelte +2 -73
  2. package/dist/actions/ctx-actions.svelte.test.js +4 -4
  3. package/dist/api.d.ts +2 -0
  4. package/dist/api.js +1 -0
  5. package/dist/build.d.ts +27 -0
  6. package/dist/build.js +59 -1
  7. package/dist/build.test.d.ts +1 -0
  8. package/dist/build.test.js +31 -0
  9. package/dist/contributions/index.d.ts +1 -1
  10. package/dist/contributions/index.js +1 -1
  11. package/dist/contributions/registry.d.ts +17 -1
  12. package/dist/contributions/registry.js +50 -2
  13. package/dist/contributions/scope.test.d.ts +1 -0
  14. package/dist/contributions/scope.test.js +52 -0
  15. package/dist/contributions/types.d.ts +11 -3
  16. package/dist/createShell.js +7 -1
  17. package/dist/fields/address.d.ts +3 -0
  18. package/dist/fields/address.js +36 -0
  19. package/dist/fields/address.test.d.ts +1 -0
  20. package/dist/fields/address.test.js +34 -0
  21. package/dist/fields/decoration.d.ts +7 -0
  22. package/dist/fields/decoration.js +199 -0
  23. package/dist/fields/decoration.svelte.test.d.ts +1 -0
  24. package/dist/fields/decoration.svelte.test.js +177 -0
  25. package/dist/fields/dispatch.d.ts +22 -0
  26. package/dist/fields/dispatch.js +254 -0
  27. package/dist/fields/dispatch.test.d.ts +1 -0
  28. package/dist/fields/dispatch.test.js +175 -0
  29. package/dist/fields/types.d.ts +101 -0
  30. package/dist/fields/types.js +16 -0
  31. package/dist/fields/walker.svelte.test.d.ts +1 -0
  32. package/dist/fields/walker.svelte.test.js +138 -0
  33. package/dist/host.js +27 -2
  34. package/dist/host.svelte.test.d.ts +1 -0
  35. package/dist/host.svelte.test.js +92 -0
  36. package/dist/layout/slotHostPool.svelte.d.ts +8 -0
  37. package/dist/layout/slotHostPool.svelte.js +14 -1
  38. package/dist/overlays/OverlayRoots.svelte +86 -0
  39. package/dist/overlays/OverlayRoots.svelte.d.ts +3 -0
  40. package/dist/platform/tauri-backend.d.ts +3 -3
  41. package/dist/platform/tauri-backend.js +24 -3
  42. package/dist/projects/session-state.svelte.d.ts +3 -3
  43. package/dist/projects/session-state.svelte.js +5 -4
  44. package/dist/runtime/runVerb.js +2 -2
  45. package/dist/satellite/SatelliteShell.svelte +58 -11
  46. package/dist/satellite/SatelliteShell.svelte.test.d.ts +1 -0
  47. package/dist/satellite/SatelliteShell.svelte.test.js +61 -0
  48. package/dist/sh3Api/fields-walker.svelte.test.d.ts +1 -0
  49. package/dist/sh3Api/fields-walker.svelte.test.js +75 -0
  50. package/dist/sh3Api/headless.d.ts +9 -0
  51. package/dist/sh3Api/headless.js +163 -16
  52. package/dist/sh3Api/headless.svelte.test.js +9 -9
  53. package/dist/sh3core-shard/sh3coreShard.svelte.js +2 -2
  54. package/dist/shards/activate-fields.svelte.test.d.ts +1 -0
  55. package/dist/shards/activate-fields.svelte.test.js +121 -0
  56. package/dist/shards/activate-runtime.test.js +8 -8
  57. package/dist/shards/activate.svelte.js +29 -35
  58. package/dist/shards/types.d.ts +14 -75
  59. package/dist/shell-shard/ScrollbackView.svelte +55 -9
  60. package/dist/shell-shard/Terminal.svelte +1 -1
  61. package/dist/shell-shard/scrollback-stick.d.ts +9 -0
  62. package/dist/shell-shard/scrollback-stick.js +21 -0
  63. package/dist/shell-shard/scrollback-stick.test.d.ts +1 -0
  64. package/dist/shell-shard/scrollback-stick.test.js +25 -0
  65. package/dist/verbs/types.d.ts +56 -1
  66. package/dist/version.d.ts +1 -1
  67. package/dist/version.js +1 -1
  68. package/package.json +1 -1
@@ -3,11 +3,11 @@ 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, VerbSchema } from '../verbs/types';
7
- import type { ScrollbackEntry } from '../shell-shard/scrollback.svelte';
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, ActionDescriptor } from '../actions/types';
10
+ import type { ActionsApi } from '../actions/types';
11
11
  import type { TreeRootRef } from '../layout/types';
12
12
  export { PERMISSION_KEYS_MINT, type ShardContextKeys, type ApiKeyPublic, type MintOpts, ScopeEscalationError, ConsentDeniedError } from '../keys/types';
13
13
  /**
@@ -254,81 +254,20 @@ export interface ShardContext {
254
254
  */
255
255
  actions: ActionsApi;
256
256
  /**
257
- * Read-only snapshot of every verb registered across every active shard.
258
- * Returned entries include the contributing `shardId`, the prefixed
259
- * `name`, the verb's `summary`, the `programmatic` flag, and (when
260
- * present) its `schema`. Order is undefined.
257
+ * Cross-shard read+invoke façade. Same type exposed on `VerbContext.sh3`.
258
+ * Use for: enumerating verbs/actions/views, programmatic dispatch,
259
+ * controllable-field operations, decoration overlays.
261
260
  *
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.
261
+ * In v0.17.0 this absorbs the four flat methods previously on `ShardContext`:
262
+ * - `ctx.listVerbs(opts?)` → `ctx.sh3.listVerbs(opts?)`
263
+ * - `ctx.runVerb(...)` → `ctx.sh3.runVerb(...)`
264
+ * - `ctx.listActions(opts?)` `ctx.sh3.listActions(opts?)`
265
+ * - `ctx.runAction(id, ...)` → `ctx.sh3.runAction(id, ...)`
266
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.
267
+ * Ownership-bound registration (`registerView`, `registerVerb`, `actions`,
268
+ * `contributions`, `documents`) stays on `ShardContext` directly.
270
269
  */
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`.
288
- *
289
- * Pass `opts.structured` to populate `ctx.structuredArgs` for verbs
290
- * that declare `schema.input`. Pass `opts.signal` for cooperative
291
- * cancellation (verbs must opt in to honor it).
292
- */
293
- runVerb(shardId: string, name: string, args: string[], opts?: {
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`.
325
- *
326
- * `opts.signal` is stored on the dispatch context for v1 parity with
327
- * `runVerb`; today's actions don't read it.
328
- */
329
- runAction(id: string, opts?: {
330
- signal?: AbortSignal;
331
- }): Promise<void>;
270
+ sh3: Sh3Api;
332
271
  }
333
272
  /**
334
273
  * 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
- // scrollHeight - scrollTop - clientHeight can settle on small non-zero
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) stuck = isAtBottom(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 && stuck) container.scrollTop = container.scrollHeight;
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 class="shell-scrollback" bind:this={container} onscroll={handleScroll}>
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
  }
@@ -248,7 +248,7 @@
248
248
  resolver,
249
249
  buffer: () => currentBuffer,
250
250
  session,
251
- shell: shellWithModes,
251
+ sh3: shellWithModes,
252
252
  fs,
253
253
  cwd: () => session.cwd,
254
254
  busy: acquireBusy,
@@ -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
+ });
@@ -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,59 @@ 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. Pass
132
+ * { activeOnly: true } to filter to currently-dispatchable actions.
133
+ */
134
+ listActions(opts?: {
135
+ activeOnly?: boolean;
136
+ }): ActionDescriptor[];
137
+ /**
138
+ * Programmatically dispatch a registered action by id. Same semantics as
139
+ * the keyboard / palette / context-menu paths but with
140
+ * invokedVia: 'programmatic'.
141
+ */
142
+ runAction(id: string, opts?: {
143
+ signal?: AbortSignal;
144
+ }): Promise<void>;
145
+ /**
146
+ * Alias of listViewsInCurrentLayout. Kept short for ergonomics; the long
147
+ * name remains a deprecated alias for one minor cycle.
148
+ */
149
+ listViews(): Array<{
150
+ slotId: string;
151
+ viewId: string;
152
+ label: string;
153
+ }>;
154
+ /**
155
+ * Controllable-field surface — see fields/types.ts:FieldsApi for shape.
156
+ */
157
+ fields: FieldsApi;
103
158
  }
104
159
  export type { DispatchToTerminalResult } from '../shell-shard/dispatch-to-terminal';
105
160
  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.16.0";
2
+ export declare const VERSION = "0.17.0";
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.16.0';
2
+ export const VERSION = '0.17.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh3-core",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"