@nimbalyst/extension-sdk 0.4.0 → 0.6.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 (48) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/__tests__/editorHostCapabilities.test.d.ts +2 -0
  3. package/dist/__tests__/editorHostCapabilities.test.d.ts.map +1 -0
  4. package/dist/__tests__/editorHostCapabilities.test.js +24 -0
  5. package/dist/__tests__/fileDirectoryTree.test.js +34 -0
  6. package/dist/__tests__/gitOperationLog.test.d.ts +2 -0
  7. package/dist/__tests__/gitOperationLog.test.d.ts.map +1 -0
  8. package/dist/__tests__/gitOperationLog.test.js +51 -0
  9. package/dist/__tests__/useCollaborativeEditor.offline.test.js +43 -1
  10. package/dist/agents/AgentProtocol.d.ts +20 -0
  11. package/dist/agents/AgentProtocol.d.ts.map +1 -1
  12. package/dist/agents/AgentProtocolHost.d.ts +1 -1
  13. package/dist/agents/AgentProtocolHost.d.ts.map +1 -1
  14. package/dist/createReadOnlyHost.d.ts +11 -1
  15. package/dist/createReadOnlyHost.d.ts.map +1 -1
  16. package/dist/createReadOnlyHost.js +3 -0
  17. package/dist/editorHostCapabilities.d.ts +31 -0
  18. package/dist/editorHostCapabilities.d.ts.map +1 -0
  19. package/dist/editorHostCapabilities.js +35 -0
  20. package/dist/externalsPlugin.d.ts.map +1 -1
  21. package/dist/externalsPlugin.js +1 -5
  22. package/dist/fileDirectoryTree.d.ts +9 -2
  23. package/dist/fileDirectoryTree.d.ts.map +1 -1
  24. package/dist/fileDirectoryTree.js +82 -14
  25. package/dist/gitOperationLog.d.ts +104 -0
  26. package/dist/gitOperationLog.d.ts.map +1 -0
  27. package/dist/gitOperationLog.js +88 -0
  28. package/dist/index.d.ts +3 -1
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +3 -0
  31. package/dist/types/collab.d.ts +31 -2
  32. package/dist/types/collab.d.ts.map +1 -1
  33. package/dist/types/comments.d.ts +104 -0
  34. package/dist/types/comments.d.ts.map +1 -0
  35. package/dist/types/comments.js +2 -0
  36. package/dist/types/editor.d.ts +201 -1
  37. package/dist/types/editor.d.ts.map +1 -1
  38. package/dist/types/extension.d.ts +49 -0
  39. package/dist/types/extension.d.ts.map +1 -1
  40. package/dist/types/index.d.ts +1 -0
  41. package/dist/types/index.d.ts.map +1 -1
  42. package/dist/types/index.js +1 -0
  43. package/dist/types/panel.d.ts +21 -0
  44. package/dist/types/panel.d.ts.map +1 -1
  45. package/dist/useCollaborativeEditor.d.ts +13 -8
  46. package/dist/useCollaborativeEditor.d.ts.map +1 -1
  47. package/dist/useCollaborativeEditor.js +92 -74
  48. package/package.json +14 -2
package/CHANGELOG.md CHANGED
@@ -8,6 +8,8 @@ The SDK is versioned independently of the Nimbalyst app. Each release declares i
8
8
 
9
9
  | SDK version | Minimum Nimbalyst app version |
10
10
  | --- | --- |
11
+ | 0.6.0 | 0.78.0 |
12
+ | 0.5.0 | 0.70.0 |
11
13
  | 0.4.0 | 0.70.0 |
12
14
  | 0.3.0 | 0.70.0 |
13
15
  | 0.2.2 | 0.58.5 |
@@ -18,6 +20,43 @@ The SDK is versioned independently of the Nimbalyst app. Each release declares i
18
20
 
19
21
  ## [Unreleased]
20
22
 
23
+ ## [0.6.0] - 2026-09-14
24
+
25
+ Requires Nimbalyst 0.78.0, available on the alpha channel at publication, for the complete host API including native screenshots.
26
+
27
+ ### Added
28
+
29
+ - `CollaborationCommentsService`, `CommentAnchor`, and codec-level `commentAnchors` let collaborative editors mount the platform comment experience and resolve structured anchors both live and headlessly.
30
+ - `host.registerViewport()` and `EditorViewport` let an editor expose proportional scroll position to hosts that carry the reader's place between related documents; `createReadOnlyHost()` can receive those registrations.
31
+ - `contributions.customEditors[].collaboration.documentType` lets a host identify a collaborative document type from the manifest before its extension bundle and codec are loaded.
32
+ - `screenshotService` exposes host-provided element and file screenshots, including unopened documents.
33
+ - `PanelHost.getWorkspaceFolders()` and `getPrimaryFolderPath()` expose multiple workspace roots; file-tree helpers accept those roots.
34
+ - `EditorHost.getAssetUrl()` and the `assetUrls` capability let media editors stream local files through a host-provided URL.
35
+ - The `agents`, `types/editor`, and `git-operation-log` entry points expose agent contracts, editor types, and Git operation selectors.
36
+ - `ProtocolSession.appliedModel` and `deliveredMcpServerCount` report the model and MCP servers actually used by an agent.
37
+
38
+ ### Changed
39
+
40
+ - `CollabCodec` documents file-form fallback for headless agent edits when no structured patch pair exists, while recommending structured edits for minimal concurrent deltas.
41
+
42
+ ### Fixed
43
+
44
+ - Agent host declarations use a Node-compatible relative import for consumers using Node16 or NodeNext module resolution.
45
+
46
+ ## [0.5.0]
47
+
48
+ Still runs on Nimbalyst 0.70.0: a host that declares no capabilities is treated as fully capable, so an extension written against this release keeps working on older desktop builds.
49
+
50
+ ### Added
51
+
52
+ - `host.capabilities` plus `editorHostSupports()` and `editorHostCapabilityGap()` let one editor bundle run in hosts that cannot honour every part of `EditorHost` — check a capability before calling instead of discovering the gap at runtime.
53
+ - `AiAgentProviderContribution` declares `supportsSlashCommands`, `supportsSkills`, and `compaction` instead of having the app infer them; all three default to off, so an unclaimed affordance is hidden rather than offered and silently broken.
54
+ - `AgentProtocol.compactSession()` is the optional real compaction entry point for a transport that has one.
55
+
56
+ ### Changed
57
+
58
+ - `host.filePath` is documented as an opaque document identifier — an absolute path on the desktop host, a synthetic URI on a host with no filesystem. Gate anything that touches disk on the `localFileSave` / `projectFileSystem` capabilities.
59
+
21
60
  ## [0.4.0]
22
61
 
23
62
  ### Added
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=editorHostCapabilities.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editorHostCapabilities.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/editorHostCapabilities.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,24 @@
1
+ // @vitest-environment node
2
+ import { describe, expect, it } from 'vitest';
3
+ import { editorHostCapabilityGap, editorHostSupports, } from '../editorHostCapabilities.js';
4
+ const browserCapabilities = {
5
+ environment: 'browser',
6
+ unavailable: [{ capability: 'localFileSave', reason: 'No file to write.' }],
7
+ supports: (capability) => capability !== 'localFileSave',
8
+ };
9
+ describe('editorHostSupports', () => {
10
+ it('treats a host that makes no claim as fully capable', () => {
11
+ // The Electron renderer predates this contract and implements every
12
+ // member. Answering false there would make every portable extension
13
+ // silently drop features on desktop.
14
+ expect(editorHostSupports({}, 'localFileSave')).toBe(true);
15
+ expect(editorHostCapabilityGap({}, 'localFileSave')).toBeNull();
16
+ });
17
+ it('defers to a host that does declare capabilities', () => {
18
+ const host = { capabilities: browserCapabilities };
19
+ expect(editorHostSupports(host, 'collaboration')).toBe(true);
20
+ expect(editorHostSupports(host, 'localFileSave')).toBe(false);
21
+ expect(editorHostCapabilityGap(host, 'localFileSave')).toBe('No file to write.');
22
+ expect(editorHostCapabilityGap(host, 'collaboration')).toBeNull();
23
+ });
24
+ });
@@ -56,4 +56,38 @@ describe('buildFileDirectoryTree', () => {
56
56
  expect(tree.fileCount).toBe(1);
57
57
  expect(tree.files).toHaveLength(1);
58
58
  });
59
+ describe('multi-root workspaces', () => {
60
+ const ROOTS = ['/repo', '/other/collab'];
61
+ it('leaves primary-root paths bare and names the attached root', () => {
62
+ expect(getWorkspaceRelativeFilePath('/repo/src/index.ts', ROOTS)).toBe('src/index.ts');
63
+ expect(getWorkspaceRelativeFilePath('/other/collab/src/index.ts', ROOTS))
64
+ .toBe('collab/src/index.ts');
65
+ });
66
+ it('keeps same-named files in different roots in separate groups', () => {
67
+ const tree = buildTree(['/repo/src/index.ts', '/other/collab/src/index.ts'], ROOTS);
68
+ // `collab/src` is the collapsed single-child chain; the point is that the
69
+ // two `src` directories do not merge into one group.
70
+ expect(getFileDirectoryPaths(tree)).toEqual(['src', 'collab/src']);
71
+ expect(tree.subdirectories.get('src')?.fileCount).toBe(1);
72
+ expect(tree.subdirectories.get('collab')?.fileCount).toBe(1);
73
+ });
74
+ it('prefers the deepest root for a root nested inside another root', () => {
75
+ expect(getWorkspaceRelativeFilePath('/repo/vendor/lib/a.ts', ['/repo', '/repo/vendor/lib']))
76
+ .toBe('lib/a.ts');
77
+ });
78
+ it('falls back to the absolute path for a file in no root', () => {
79
+ expect(getWorkspaceRelativeFilePath('/elsewhere/a.ts', ROOTS)).toBe('/elsewhere/a.ts');
80
+ });
81
+ it('disambiguates attached roots that share a basename', () => {
82
+ // `/app/api` and `/infra/api` are an ordinary pair, and a bare basename
83
+ // would collapse both repos into one `api` group in the tree.
84
+ const roots = ['/repo', '/app/api', '/infra/api'];
85
+ expect(getWorkspaceRelativeFilePath('/app/api/src/a.ts', roots)).toBe('app/api/src/a.ts');
86
+ expect(getWorkspaceRelativeFilePath('/infra/api/src/a.ts', roots)).toBe('infra/api/src/a.ts');
87
+ });
88
+ it('matches single-root behaviour when given one root', () => {
89
+ expect(getWorkspaceRelativeFilePath('/repo/src/index.ts', ['/repo']))
90
+ .toBe(getWorkspaceRelativeFilePath('/repo/src/index.ts', '/repo'));
91
+ });
92
+ });
59
93
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=gitOperationLog.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gitOperationLog.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/gitOperationLog.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { mergeGitOperationEntries, normalizeGitOperationEntry, selectLatestRunningGitOperation, selectLatestTerminalGitOperation, selectRunningGitOperations, } from '../gitOperationLog';
3
+ function entry(overrides) {
4
+ return {
5
+ timestamp: 1000,
6
+ updatedAt: 1000,
7
+ command: 'git status',
8
+ executable: 'git',
9
+ args: ['status'],
10
+ cwd: '/repo',
11
+ status: 'running',
12
+ output: '',
13
+ stdout: '',
14
+ stderr: '',
15
+ ...overrides,
16
+ };
17
+ }
18
+ describe('git operation log selectors', () => {
19
+ it('keeps a live terminal event that landed while hydration was in flight', () => {
20
+ // The `git:operation-log:get` response is older than upserts that arrived
21
+ // after it was requested. Applying it blindly rewinds a finished push back
22
+ // to running, which is exactly the stale indicator this ordering prevents.
23
+ const hydrated = [entry({ id: 'a', status: 'running', updatedAt: 1000 })];
24
+ const live = [entry({ id: 'a', status: 'success', updatedAt: 2000 })];
25
+ expect(mergeGitOperationEntries(hydrated, live)[0].status).toBe('success');
26
+ expect(mergeGitOperationEntries(live, hydrated)[0].status).toBe('success');
27
+ });
28
+ it('names the newest running command and counts the rest', () => {
29
+ const entries = [
30
+ entry({ id: 'push', timestamp: 1000, command: 'git push' }),
31
+ entry({ id: 'fetch', timestamp: 3000, command: 'git fetch origin' }),
32
+ entry({ id: 'done', timestamp: 2000, status: 'success', updatedAt: 5000 }),
33
+ ];
34
+ expect(selectLatestRunningGitOperation(entries)?.id).toBe('fetch');
35
+ expect(selectRunningGitOperations(entries).map((e) => e.id)).toEqual(['push', 'fetch']);
36
+ expect(selectLatestTerminalGitOperation(entries)?.id).toBe('done');
37
+ });
38
+ it('reads a journal written before source metadata existed as app-owned direct git', () => {
39
+ const legacy = entry({ id: 'legacy' });
40
+ delete legacy.source;
41
+ delete legacy.executor;
42
+ const normalized = normalizeGitOperationEntry(legacy);
43
+ expect(normalized.source).toBe('nimbalyst');
44
+ expect(normalized.executor).toBe('git');
45
+ });
46
+ it('does not let an agent entry inherit the app-owned default', () => {
47
+ const agent = entry({ id: 'agent', source: 'agent', executor: 'shell', sessionId: 's1' });
48
+ expect(normalizeGitOperationEntry(agent).source).toBe('agent');
49
+ expect(normalizeGitOperationEntry(agent).executor).toBe('shell');
50
+ });
51
+ });
@@ -1,4 +1,4 @@
1
- import { renderHook, waitFor } from '@testing-library/react';
1
+ import { act, renderHook, waitFor } from '@testing-library/react';
2
2
  import { Awareness } from 'y-protocols/awareness';
3
3
  import { describe, expect, it, vi } from 'vitest';
4
4
  import * as Y from 'yjs';
@@ -31,4 +31,46 @@ describe('useCollaborativeEditor offline replica binding', () => {
31
31
  awareness.destroy();
32
32
  yDoc.destroy();
33
33
  });
34
+ it('does not start a second binding while the first async bind is unresolved', async () => {
35
+ const yDoc = new Y.Doc();
36
+ yDoc.getMap('nodes').set('node_root', 'persisted');
37
+ const awareness = new Awareness(yDoc);
38
+ let notifyStatusChange = () => { };
39
+ let finishBind = () => { };
40
+ const bindReady = new Promise((resolve) => {
41
+ finishBind = resolve;
42
+ });
43
+ const destroy = vi.fn();
44
+ const bind = vi.fn(async () => {
45
+ await bindReady;
46
+ return { destroy };
47
+ });
48
+ const host = {
49
+ collaboration: {
50
+ yDoc,
51
+ awareness,
52
+ user: { id: 'offline-user', name: 'Offline User', color: '#123456' },
53
+ getStatus: () => 'disconnected',
54
+ onStatusChange: (callback) => {
55
+ notifyStatusChange = callback;
56
+ return () => { };
57
+ },
58
+ loadInitialContent: vi.fn(async () => 'must not seed offline'),
59
+ },
60
+ };
61
+ const { result, unmount } = renderHook(() => useCollaborativeEditor(host, {
62
+ isEmpty: doc => doc.getMap('nodes').size === 0,
63
+ initializeFromContent: vi.fn(),
64
+ createBinding: bind,
65
+ }));
66
+ await waitFor(() => expect(bind).toHaveBeenCalledTimes(1));
67
+ act(() => notifyStatusChange());
68
+ expect(bind).toHaveBeenCalledTimes(1);
69
+ finishBind();
70
+ await waitFor(() => expect(result.current.binding).not.toBeNull());
71
+ unmount();
72
+ expect(destroy).toHaveBeenCalledTimes(1);
73
+ awareness.destroy();
74
+ yDoc.destroy();
75
+ });
34
76
  });
@@ -129,6 +129,14 @@ export interface ProtocolSession {
129
129
  platform: string;
130
130
  /** Platform-specific session data (for internal use) */
131
131
  raw?: RawProtocolSession;
132
+ /** Number of host-supplied MCP servers this protocol actually delivered. */
133
+ deliveredMcpServerCount?: number;
134
+ /**
135
+ * Model the agent is actually running, when the protocol can observe it.
136
+ * Absent means "the protocol has no say"; the host then reports the model it
137
+ * asked for. Never report a requested model a protocol knows was not applied.
138
+ */
139
+ appliedModel?: string;
132
140
  }
133
141
  /**
134
142
  * Event types emitted during message streaming
@@ -229,5 +237,17 @@ export interface AgentProtocol {
229
237
  * @param session - Session to clean up
230
238
  */
231
239
  cleanupSession(session: ProtocolSession): void;
240
+ /**
241
+ * Compact the session's context in-place, if the transport supports it.
242
+ *
243
+ * Optional: a transport that cannot compact simply omits this, and callers
244
+ * must check for it before offering the action. Do NOT emulate compaction by
245
+ * sending a `/compact` string as a user turn -- for transports without a real
246
+ * compaction RPC that reaches the model as literal prompt text and silently
247
+ * does nothing (#1252).
248
+ *
249
+ * @param session - Session whose context should be compacted
250
+ */
251
+ compactSession?(session: ProtocolSession): Promise<void>;
232
252
  }
233
253
  //# sourceMappingURL=AgentProtocol.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AgentProtocol.d.ts","sourceRoot":"","sources":["../../src/agents/AgentProtocol.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AgentProtocol.d.ts","sourceRoot":"","sources":["../../src/agents/AgentProtocol.ts"],"names":[],"mappings":"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"}
@@ -39,7 +39,7 @@
39
39
  * See `docs/EXTENSION_ARCHITECTURE.md` and the Phase 4 SDK design doc
40
40
  * (Section 4.3) for the full host contract.
41
41
  */
42
- import type { MCPServerConfig } from './index';
42
+ import type { MCPServerConfig } from './index.js';
43
43
  /**
44
44
  * Permission posture the host has set for the current turn.
45
45
  *
@@ -1 +1 @@
1
- {"version":3,"file":"AgentProtocolHost.d.ts","sourceRoot":"","sources":["../../src/agents/AgentProtocolHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAErD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAGhC,uDAAuD;IACvD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAExC;;;OAGG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAIjC,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,wDAAwD;IACxD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAElC,yDAAyD;IACzD,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IAExC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAEhD;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAI/C;;;;;;;;;;;OAWG;IACH,MAAM,CACJ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,GAAG,QAAQ,EAC7B,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;;;OAQG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAE3D;;;;;;;OAOG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAErD;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErE;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D"}
1
+ {"version":3,"file":"AgentProtocolHost.d.ts","sourceRoot":"","sources":["../../src/agents/AgentProtocolHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAErD;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAGhC,uDAAuD;IACvD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B;;;OAGG;IACH,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IAExC;;;OAGG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IAIjC,iDAAiD;IACjD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAE3B,wDAAwD;IACxD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAE/B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAElC,yDAAyD;IACzD,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IAExC;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAEhD;;;;OAIG;IACH,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAI/C;;;;;;;;;;;OAWG;IACH,MAAM,CACJ,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,OAAO,GAAG,QAAQ,EAC7B,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACjC,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB;;;;;;;;OAQG;IACH,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAE3D;;;;;;;OAOG;IACH,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAErD;;;;OAIG;IACH,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAErE;;;;;;;;;;;OAWG;IACH,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7D"}
@@ -21,7 +21,7 @@
21
21
  * host.setTheme('light');
22
22
  * ```
23
23
  */
24
- import type { EditorHost } from './types/editor.js';
24
+ import type { EditorHost, EditorViewport } from './types/editor.js';
25
25
  export interface ReadOnlyHostOptions {
26
26
  /** Current theme name (e.g., 'dark', 'light') */
27
27
  theme: string;
@@ -34,6 +34,16 @@ export interface ReadOnlyHostOptions {
34
34
  * When true, extensions may suppress chrome that doesn't fit inline contexts.
35
35
  */
36
36
  embedded?: boolean;
37
+ /**
38
+ * Receives the editor's scroll viewport when it publishes one.
39
+ *
40
+ * Supplied only by a surface that shows several documents in sequence and
41
+ * wants to carry the reader's place between them -- the feedback detail
42
+ * popover comparing design alternatives. Absent, `registerViewport` is
43
+ * absent too, so an extension that checks before registering gets a straight
44
+ * answer rather than a silent no-op.
45
+ */
46
+ onViewportRegistered?(viewport: EditorViewport | null): void;
37
47
  }
38
48
  export interface ReadOnlyHost extends EditorHost {
39
49
  /** Update the theme (call from viewer chrome's theme toggle) */
@@ -1 +1 @@
1
- {"version":3,"file":"createReadOnlyHost.d.ts","sourceRoot":"","sources":["../src/createReadOnlyHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAiC,MAAM,mBAAmB,CAAC;AAEnF,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IAEd,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IAEjB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,mBAAmB,GACxB,YAAY,CA8Dd"}
1
+ {"version":3,"file":"createReadOnlyHost.d.ts","sourceRoot":"","sources":["../src/createReadOnlyHost.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAEV,UAAU,EAEV,cAAc,EACf,MAAM,mBAAmB,CAAC;AAE3B,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IAEd,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IAEjB,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;;;;;;OAQG;IACH,oBAAoB,CAAC,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,GAAG,IAAI,CAAC;CAC9D;AAED,MAAM,WAAW,YAAa,SAAQ,UAAU;IAC9C,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,mBAAmB,GACxB,YAAY,CAiEd"}
@@ -60,6 +60,9 @@ export function createReadOnlyHost(content, opts) {
60
60
  openHistory: () => { },
61
61
  registerMenuItems: (_items) => { },
62
62
  registerEditorAPI: () => { },
63
+ ...(opts.onViewportRegistered
64
+ ? { registerViewport: opts.onViewportRegistered }
65
+ : {}),
63
66
  setEditorContext: (_context) => { },
64
67
  setEditorContextItems: () => { },
65
68
  // -- Storage (in-memory, non-persistent) --
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Reading `EditorHost.capabilities`.
3
+ *
4
+ * One function so the "host makes no claim" case is decided once instead of in
5
+ * every extension. See {@link EditorHostCapabilities}.
6
+ */
7
+ import type { EditorHost, EditorHostCapability } from './types/editor.js';
8
+ /**
9
+ * Whether `host` really provides `capability`.
10
+ *
11
+ * A host with no `capabilities` block is treated as fully capable. That is not
12
+ * an optimistic guess: the only such host is the Electron renderer, which
13
+ * predates this contract and does implement every member. Any host that cannot
14
+ * -- the browser collaborative host, and anything after it -- is required to
15
+ * populate `capabilities`, so "absent" and "incapable" never overlap.
16
+ *
17
+ * ```ts
18
+ * if (editorHostSupports(host, 'localFileSave')) {
19
+ * await host.saveContent(serialize());
20
+ * }
21
+ * ```
22
+ */
23
+ export declare function editorHostSupports(host: Pick<EditorHost, 'capabilities'>, capability: EditorHostCapability): boolean;
24
+ /**
25
+ * The host's stated reason for withholding `capability`, or null when it is
26
+ * available (or the host makes no claim). For diagnostics and degraded-mode
27
+ * copy -- never branch on the string itself, branch on
28
+ * {@link editorHostSupports}.
29
+ */
30
+ export declare function editorHostCapabilityGap(host: Pick<EditorHost, 'capabilities'>, capability: EditorHostCapability): string | null;
31
+ //# sourceMappingURL=editorHostCapabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"editorHostCapabilities.d.ts","sourceRoot":"","sources":["../src/editorHostCapabilities.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EACtC,UAAU,EAAE,oBAAoB,GAC/B,OAAO,CAET;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EACtC,UAAU,EAAE,oBAAoB,GAC/B,MAAM,GAAG,IAAI,CAIf"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Reading `EditorHost.capabilities`.
3
+ *
4
+ * One function so the "host makes no claim" case is decided once instead of in
5
+ * every extension. See {@link EditorHostCapabilities}.
6
+ */
7
+ /**
8
+ * Whether `host` really provides `capability`.
9
+ *
10
+ * A host with no `capabilities` block is treated as fully capable. That is not
11
+ * an optimistic guess: the only such host is the Electron renderer, which
12
+ * predates this contract and does implement every member. Any host that cannot
13
+ * -- the browser collaborative host, and anything after it -- is required to
14
+ * populate `capabilities`, so "absent" and "incapable" never overlap.
15
+ *
16
+ * ```ts
17
+ * if (editorHostSupports(host, 'localFileSave')) {
18
+ * await host.saveContent(serialize());
19
+ * }
20
+ * ```
21
+ */
22
+ export function editorHostSupports(host, capability) {
23
+ return host.capabilities ? host.capabilities.supports(capability) : true;
24
+ }
25
+ /**
26
+ * The host's stated reason for withholding `capability`, or null when it is
27
+ * available (or the host makes no claim). For diagnostics and degraded-mode
28
+ * copy -- never branch on the string itself, branch on
29
+ * {@link editorHostSupports}.
30
+ */
31
+ export function editorHostCapabilityGap(host, capability) {
32
+ const gap = host.capabilities?.unavailable
33
+ .find((entry) => entry.capability === capability);
34
+ return gap ? gap.reason : null;
35
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"externalsPlugin.d.ts","sourceRoot":"","sources":["../src/externalsPlugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAqDnC;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAyDjD"}
1
+ {"version":3,"file":"externalsPlugin.d.ts","sourceRoot":"","sources":["../src/externalsPlugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAoDnC;;;;;;;;;GASG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAyDjD"}
@@ -45,7 +45,6 @@ const EXTERNALS_MAP = {
45
45
  '@nimbalyst/editor-context': '@nimbalyst/editor-context',
46
46
  '@nimbalyst/runtime/ui/icons/MaterialSymbol': '@nimbalyst/runtime/ui/icons/MaterialSymbol',
47
47
  '@nimbalyst/screenshot-service': '@nimbalyst/screenshot-service',
48
- '@nimbalyst/datamodel-platform-service': '@nimbalyst/datamodel-platform-service',
49
48
  // Shared libraries
50
49
  'pdfjs-dist': 'pdfjs-dist',
51
50
  virtua: 'virtua',
@@ -230,7 +229,7 @@ function generateCommonExports(externalKey) {
230
229
  }
231
230
  else if (externalKey === '@nimbalyst/runtime') {
232
231
  exports = [
233
- 'MaterialSymbol', 'useDocumentPath', 'useEditorLifecycle',
232
+ 'MaterialSymbol', 'useDocumentPath', 'useEditorLifecycle', 'screenshotService',
234
233
  'useCollaborativeEditor', 'COLLAB_INIT_ORIGIN',
235
234
  'setTranscriptMarkdownContributions', 'clearTranscriptMarkdownContributions',
236
235
  'MarkdownEditor', 'MonacoEditor', 'MonacoCodeEditor',
@@ -241,9 +240,6 @@ function generateCommonExports(externalKey) {
241
240
  else if (externalKey === '@nimbalyst/screenshot-service') {
242
241
  exports = ['screenshotService'];
243
242
  }
244
- else if (externalKey === '@nimbalyst/datamodel-platform-service') {
245
- exports = ['DataModelPlatformServiceImpl', 'getInstance'];
246
- }
247
243
  else if (externalKey === 'pdfjs-dist') {
248
244
  exports = ['getDocument', 'GlobalWorkerOptions', 'version'];
249
245
  }
@@ -11,9 +11,16 @@ export declare function getFilePathBasename(filePath: string): string;
11
11
  /**
12
12
  * Return a forward-slash path relative to the workspace when the file is inside it.
13
13
  * Windows drive paths are compared case-insensitively to match filesystem behavior.
14
+ *
15
+ * A multi-root workspace passes every root, primary first. The primary root
16
+ * still yields a bare relative path, so a single-folder workspace is unchanged;
17
+ * a file in an attached root is prefixed with that root's heading, which is
18
+ * what groups it under its own heading in the directory tree instead of
19
+ * spelling out an absolute path. Deepest matching root wins, so a root checked
20
+ * out inside another root keeps its own prefix.
14
21
  */
15
- export declare function getWorkspaceRelativeFilePath(filePath: string, workspacePath?: string): string;
22
+ export declare function getWorkspaceRelativeFilePath(filePath: string, workspacePath?: string | string[]): string;
16
23
  /** Build a collapsed directory tree for arbitrary file records. */
17
- export declare function buildFileDirectoryTree<T>(files: T[], getFilePath: (file: T) => string, workspacePath?: string): FileDirectoryNode<T>;
24
+ export declare function buildFileDirectoryTree<T>(files: T[], getFilePath: (file: T) => string, workspacePath?: string | string[]): FileDirectoryNode<T>;
18
25
  export declare function getFileDirectoryPaths<T>(node: FileDirectoryNode<T>): string[];
19
26
  //# sourceMappingURL=fileDirectoryTree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fileDirectoryTree.d.ts","sourceRoot":"","sources":["../src/fileDirectoryTree.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG5D;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAkB7F;AA6BD,mEAAmE;AACnE,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,KAAK,EAAE,CAAC,EAAE,EACV,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EAChC,aAAa,CAAC,EAAE,MAAM,GACrB,iBAAiB,CAAC,CAAC,CAAC,CAwCtB;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAM7E"}
1
+ {"version":3,"file":"fileDirectoryTree.d.ts","sourceRoot":"","sources":["../src/fileDirectoryTree.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,CAAC;IAClD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,qFAAqF;AACrF,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1D;AAED,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG5D;AAwDD;;;;;;;;;;GAUG;AACH,wBAAgB,4BAA4B,CAC1C,QAAQ,EAAE,MAAM,EAChB,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAChC,MAAM,CAwBR;AA6BD,mEAAmE;AACnE,wBAAgB,sBAAsB,CAAC,CAAC,EACtC,KAAK,EAAE,CAAC,EAAE,EACV,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,EAChC,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAChC,iBAAiB,CAAC,CAAC,CAAC,CAwCtB;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,MAAM,EAAE,CAM7E"}
@@ -6,27 +6,95 @@ export function getFilePathBasename(filePath) {
6
6
  const normalizedPath = normalizeFilePath(filePath).replace(/\/+$/, '');
7
7
  return normalizedPath.slice(normalizedPath.lastIndexOf('/') + 1);
8
8
  }
9
+ /** Relative to one root, or null when the file is not inside it. */
10
+ function relativeToRoot(normalizedFile, rootPath) {
11
+ const normalizedRoot = normalizeFilePath(rootPath).replace(/\/$/, '');
12
+ if (!normalizedRoot)
13
+ return null;
14
+ const windowsDrivePath = /^[A-Za-z]:\//.test(normalizedFile)
15
+ && /^[A-Za-z]:\//.test(normalizedRoot);
16
+ const comparableFile = windowsDrivePath ? normalizedFile.toLowerCase() : normalizedFile;
17
+ const comparableRoot = windowsDrivePath ? normalizedRoot.toLowerCase() : normalizedRoot;
18
+ if (comparableFile === comparableRoot)
19
+ return '';
20
+ if (comparableFile.startsWith(`${comparableRoot}/`)) {
21
+ return normalizedFile.slice(normalizedRoot.length + 1);
22
+ }
23
+ return null;
24
+ }
25
+ /**
26
+ * Heading for each attached root, unique within the root list.
27
+ *
28
+ * Two roots sharing a basename is ordinary -- `app/api` and `infra/api`, or two
29
+ * checkouts of the same repo -- and a bare basename would collapse both into
30
+ * one directory group. Colliding roots grow parent segments until they differ;
31
+ * everything else stays the bare folder name. The primary root has no heading,
32
+ * so it never competes.
33
+ */
34
+ function attachedRootLabels(roots) {
35
+ const segmentsPerRoot = roots.map((rootPath) => normalizeFilePath(rootPath).replace(/\/+$/, '').split('/').filter(Boolean));
36
+ const labels = roots.map((_, index) => segmentsPerRoot[index].slice(-1).join('/'));
37
+ // Grow every still-colliding label by one more parent segment per pass. A
38
+ // root can only run out of segments once it is the filesystem root, at which
39
+ // point its label is already unique.
40
+ for (let depth = 2; depth <= 16; depth++) {
41
+ const counts = new Map();
42
+ for (let index = 1; index < labels.length; index++) {
43
+ counts.set(labels[index], (counts.get(labels[index]) ?? 0) + 1);
44
+ }
45
+ if ([...counts.values()].every((count) => count < 2))
46
+ break;
47
+ let grew = false;
48
+ for (let index = 1; index < labels.length; index++) {
49
+ if ((counts.get(labels[index]) ?? 0) < 2)
50
+ continue;
51
+ if (segmentsPerRoot[index].length < depth)
52
+ continue;
53
+ labels[index] = segmentsPerRoot[index].slice(-depth).join('/');
54
+ grew = true;
55
+ }
56
+ if (!grew)
57
+ break;
58
+ }
59
+ return labels;
60
+ }
9
61
  /**
10
62
  * Return a forward-slash path relative to the workspace when the file is inside it.
11
63
  * Windows drive paths are compared case-insensitively to match filesystem behavior.
64
+ *
65
+ * A multi-root workspace passes every root, primary first. The primary root
66
+ * still yields a bare relative path, so a single-folder workspace is unchanged;
67
+ * a file in an attached root is prefixed with that root's heading, which is
68
+ * what groups it under its own heading in the directory tree instead of
69
+ * spelling out an absolute path. Deepest matching root wins, so a root checked
70
+ * out inside another root keeps its own prefix.
12
71
  */
13
72
  export function getWorkspaceRelativeFilePath(filePath, workspacePath) {
14
73
  const normalizedFile = normalizeFilePath(filePath);
15
- if (!workspacePath)
74
+ const roots = typeof workspacePath === 'string' ? [workspacePath] : workspacePath;
75
+ if (!roots || roots.length === 0)
16
76
  return normalizedFile;
17
- const normalizedWorkspace = normalizeFilePath(workspacePath).replace(/\/$/, '');
18
- const windowsDrivePath = /^[A-Za-z]:\//.test(normalizedFile)
19
- && /^[A-Za-z]:\//.test(normalizedWorkspace);
20
- const comparableFile = windowsDrivePath ? normalizedFile.toLowerCase() : normalizedFile;
21
- const comparableWorkspace = windowsDrivePath
22
- ? normalizedWorkspace.toLowerCase()
23
- : normalizedWorkspace;
24
- if (comparableFile === comparableWorkspace)
25
- return '';
26
- if (comparableFile.startsWith(`${comparableWorkspace}/`)) {
27
- return normalizedFile.slice(normalizedWorkspace.length + 1);
28
- }
29
- return normalizedFile;
77
+ let bestIndex = -1;
78
+ let bestRelative = null;
79
+ let bestRootLength = -1;
80
+ roots.forEach((rootPath, index) => {
81
+ const relative = relativeToRoot(normalizedFile, rootPath);
82
+ if (relative === null)
83
+ return;
84
+ const rootLength = normalizeFilePath(rootPath).replace(/\/$/, '').length;
85
+ if (rootLength <= bestRootLength)
86
+ return;
87
+ bestIndex = index;
88
+ bestRelative = relative;
89
+ bestRootLength = rootLength;
90
+ });
91
+ if (bestRelative === null)
92
+ return normalizedFile;
93
+ if (bestIndex === 0)
94
+ return bestRelative;
95
+ // Attached root: name it, so two files called `src/index.ts` in two repos do
96
+ // not collapse into one directory group.
97
+ return `${attachedRootLabels(roots)[bestIndex]}/${bestRelative}`;
30
98
  }
31
99
  function collapseDirectoryTree(node) {
32
100
  node.subdirectories.forEach((subdirectory, key) => {