sh3-core 0.20.2 → 0.21.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 (69) hide show
  1. package/dist/BrandSlot.svelte +2 -2
  2. package/dist/actions/ctx-actions.svelte.test.js +2 -2
  3. package/dist/api.d.ts +2 -2
  4. package/dist/api.js +1 -1
  5. package/dist/app/store/StoreView.svelte +26 -35
  6. package/dist/app/store/storeShard.svelte.js +35 -49
  7. package/dist/app/store/verbs.js +24 -55
  8. package/dist/artifact.d.ts +2 -0
  9. package/dist/boot/satellitePayload.d.ts +2 -0
  10. package/dist/boot/satellitePayload.test.js +19 -0
  11. package/dist/build.d.ts +7 -1
  12. package/dist/build.js +34 -9
  13. package/dist/build.test.js +27 -1
  14. package/dist/createShell.js +34 -9
  15. package/dist/documents/browse.d.ts +20 -0
  16. package/dist/documents/browse.js +35 -0
  17. package/dist/documents/browse.test.js +125 -0
  18. package/dist/documents/config.d.ts +0 -4
  19. package/dist/documents/config.js +0 -8
  20. package/dist/documents/http-backend.d.ts +5 -0
  21. package/dist/documents/http-backend.js +25 -0
  22. package/dist/documents/http-backend.test.js +66 -0
  23. package/dist/documents/index.d.ts +1 -1
  24. package/dist/documents/index.js +1 -1
  25. package/dist/documents/types.d.ts +11 -0
  26. package/dist/env/client.d.ts +6 -10
  27. package/dist/env/client.js +11 -21
  28. package/dist/env/index.d.ts +2 -1
  29. package/dist/env/index.js +1 -1
  30. package/dist/host-entry.d.ts +1 -1
  31. package/dist/host-entry.js +1 -1
  32. package/dist/host.d.ts +1 -1
  33. package/dist/host.js +1 -1
  34. package/dist/layout/slotHostPool.svelte.js +2 -2
  35. package/dist/overlays/FloatFrame.svelte +1 -0
  36. package/dist/projects/session-state.svelte.d.ts +3 -0
  37. package/dist/projects/session-state.svelte.js +25 -0
  38. package/dist/projects/session-state.test.js +43 -2
  39. package/dist/projects-shard/ProjectsSection.svelte +14 -18
  40. package/dist/registry/archive.d.ts +12 -0
  41. package/dist/registry/archive.js +80 -0
  42. package/dist/registry/archive.test.d.ts +1 -0
  43. package/dist/registry/archive.test.js +84 -0
  44. package/dist/registry/client.d.ts +9 -29
  45. package/dist/registry/client.js +14 -60
  46. package/dist/registry/client.test.js +31 -21
  47. package/dist/registry/index.d.ts +2 -2
  48. package/dist/registry/index.js +1 -1
  49. package/dist/registry/installer.d.ts +4 -4
  50. package/dist/registry/installer.js +74 -45
  51. package/dist/registry/schema.js +4 -27
  52. package/dist/registry/schema.test.d.ts +1 -0
  53. package/dist/registry/schema.test.js +41 -0
  54. package/dist/registry/types.d.ts +16 -41
  55. package/dist/runtime/runVerb-shell.test.js +2 -2
  56. package/dist/runtime/runVerb.test.js +2 -2
  57. package/dist/sh3core-shard/appActions.js +5 -2
  58. package/dist/shards/activate-browse.test.js +2 -2
  59. package/dist/shards/activate-contributions.test.js +2 -2
  60. package/dist/shards/activate-error-isolation.test.js +3 -3
  61. package/dist/shards/activate-on-key-revoked.test.js +2 -2
  62. package/dist/shards/activate-runtime.test.js +2 -2
  63. package/dist/shards/activate.svelte.js +4 -4
  64. package/dist/shards/ctx-fetch.test.js +4 -4
  65. package/dist/shell-shard/verbs/xfer.js +13 -27
  66. package/dist/shell-shard/verbs/xfer.test.js +36 -25
  67. package/dist/version.d.ts +1 -1
  68. package/dist/version.js +1 -1
  69. package/package.json +3 -2
@@ -3,7 +3,8 @@ export const xferVerb = {
3
3
  name: 'xfer',
4
4
  summary: [
5
5
  'Transfer docs across scopes. Usage: xfer [-R] [-C] <src> <dst>',
6
- ' Scopes: @me | @project-<slug> (e.g. @project-acme:notes/draft.md)',
6
+ ' Scopes: @me | @project-<slug> (e.g. @me:notes/draft.md, @project-acme:notes/draft.md)',
7
+ ' Either side may be @me or @project-<slug>; bare paths resolve to the active scope.',
7
8
  ' -R recursive (src is a folder prefix)',
8
9
  ' -C copy only, do not delete source',
9
10
  ].join('\n'),
@@ -14,12 +15,10 @@ export const xferVerb = {
14
15
  ctx.scrollback.push({ kind: 'status', text: 'xfer: document capability not available', level: 'error', ts });
15
16
  return;
16
17
  }
17
- const scope = ctx.sh3.getActiveScope();
18
- if (!scope.isProject) {
19
- ctx.scrollback.push({ kind: 'status', text: 'xfer: only available when a project scope is active', level: 'error', ts });
18
+ if (!ctx.docs.transferBetweenScopes) {
19
+ ctx.scrollback.push({ kind: 'status', text: 'xfer: write permission not granted', level: 'error', ts });
20
20
  return;
21
21
  }
22
- // Parse flags
23
22
  let recursive = false;
24
23
  let copy = false;
25
24
  const positional = [];
@@ -38,16 +37,13 @@ export const xferVerb = {
38
37
  ctx.scrollback.push({ kind: 'status', text: 'usage: xfer [-R] [-C] <src> <dst>', level: 'error', ts });
39
38
  return;
40
39
  }
41
- if (!ctx.docs.transferToScope) {
42
- ctx.scrollback.push({ kind: 'status', text: 'xfer: write permission not granted', level: 'error', ts });
43
- return;
44
- }
45
40
  const srcParsed = parseScopePath(positional[0]);
46
41
  const dstParsed = parseScopePath(positional[1]);
47
42
  if (!srcParsed || !dstParsed) {
48
43
  ctx.scrollback.push({ kind: 'status', text: 'xfer: invalid path', level: 'error', ts });
49
44
  return;
50
45
  }
46
+ const scope = ctx.sh3.getActiveScope();
51
47
  let srcTenant;
52
48
  let dstTenant;
53
49
  try {
@@ -58,33 +54,23 @@ export const xferVerb = {
58
54
  ctx.scrollback.push({ kind: 'status', text: `xfer: ${e.message}`, level: 'error', ts });
59
55
  return;
60
56
  }
61
- // transferToScope always reads from the active tenant; reject if src doesn't match.
62
- if (srcTenant !== scope.id) {
63
- ctx.scrollback.push({
64
- kind: 'status',
65
- text: 'xfer: source must be the active project scope in v1 — switch to the source scope first',
66
- level: 'error',
67
- ts,
68
- });
69
- return;
70
- }
71
- const opts = { delete: !copy, targetShardId: dstParsed.shardId };
57
+ const moveOpts = { delete: !copy };
72
58
  if (!recursive) {
73
59
  if (!srcParsed.path) {
74
60
  ctx.scrollback.push({ kind: 'status', text: 'xfer: path required (use -R for folder recursion)', level: 'error', ts });
75
61
  return;
76
62
  }
77
- await ctx.docs.transferToScope(srcParsed.shardId, srcParsed.path, dstTenant, opts);
63
+ if (srcTenant === dstTenant && srcParsed.shardId === dstParsed.shardId && srcParsed.path === dstParsed.path) {
64
+ ctx.scrollback.push({ kind: 'status', text: 'xfer: source and destination are the same', level: 'error', ts });
65
+ return;
66
+ }
67
+ await ctx.docs.transferBetweenScopes(srcTenant, srcParsed.shardId, srcParsed.path, dstTenant, dstParsed.shardId, dstParsed.path, moveOpts);
78
68
  const verb = copy ? 'copied' : 'moved';
79
69
  ctx.scrollback.push({ kind: 'status', text: `xfer: ${verb} ${positional[0]} → ${positional[1]}`, level: 'info', ts });
80
70
  return;
81
71
  }
82
- // Recursive: list all docs in srcTenant matching the prefix
83
- // transferToScope uses getTenantId() (active scope) — to read from srcTenant
84
- // we rely on the src scope being the active tenant or the capability seeing it.
85
- // For v1 we use listDocuments (active tenant) and filter by shard + prefix.
86
72
  const prefix = srcParsed.path;
87
- const allDocs = await ctx.docs.listDocuments();
73
+ const allDocs = await ctx.docs.listDocumentsIn(srcTenant);
88
74
  const matching = allDocs.filter((d) => d.shardId === srcParsed.shardId && (!prefix || d.path.startsWith(prefix)));
89
75
  if (matching.length === 0) {
90
76
  ctx.scrollback.push({ kind: 'status', text: `xfer: no documents found under ${positional[0]}`, level: 'info', ts });
@@ -92,7 +78,7 @@ export const xferVerb = {
92
78
  }
93
79
  let count = 0;
94
80
  for (const doc of matching) {
95
- await ctx.docs.transferToScope(doc.shardId, doc.path, dstTenant, opts);
81
+ await ctx.docs.transferBetweenScopes(srcTenant, doc.shardId, doc.path, dstTenant, dstParsed.shardId, doc.path, moveOpts);
96
82
  count++;
97
83
  }
98
84
  const verb = copy ? 'copied' : 'moved';
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect, vi } from 'vitest';
2
2
  import { xferVerb } from './xfer';
3
3
  function makeDocs(overrides = {}) {
4
- return Object.assign({ listDocuments: vi.fn(async () => []), listShards: vi.fn(async () => []), watchDocuments: vi.fn(() => () => { }), transferToScope: vi.fn(async () => { }) }, overrides);
4
+ return Object.assign({ listDocuments: vi.fn(async () => []), listShards: vi.fn(async () => []), watchDocuments: vi.fn(() => () => { }), transferToScope: vi.fn(async () => { }), listDocumentsIn: vi.fn(async () => []), transferBetweenScopes: vi.fn(async () => { }) }, overrides);
5
5
  }
6
6
  function makeSh3(scope) {
7
7
  return {
@@ -32,13 +32,15 @@ describe('xfer verb', () => {
32
32
  const err = pushed.find((e) => e.kind === 'status' && e.level === 'error');
33
33
  expect(err).toBeDefined();
34
34
  });
35
- it('emits error when active scope is not a project', async () => {
35
+ it('works from personal (non-project) scope', async () => {
36
+ const transferBetweenScopes = vi.fn(async () => { });
37
+ const docs = makeDocs({ transferBetweenScopes });
36
38
  const sh3 = makeSh3(personalScope);
37
- const { ctx, pushed } = makeCtx(makeDocs(), sh3);
38
- await xferVerb.run(ctx, ['notes/draft.md', '@me:notes/draft.md']);
39
- const err = pushed.find((e) => e.kind === 'status' && e.level === 'error');
40
- expect(err).toBeDefined();
41
- expect(err.text).toMatch(/project/i);
39
+ const { ctx, pushed } = makeCtx(docs, sh3);
40
+ await xferVerb.run(ctx, ['@me:notes/draft.md', '@project-proj-abc:notes/draft.md']);
41
+ expect(transferBetweenScopes).toHaveBeenCalledWith('user-me', 'notes', 'draft.md', 'proj-abc', 'notes', 'draft.md', expect.objectContaining({ delete: true }));
42
+ const ok = pushed.find((e) => e.kind === 'status' && e.level === 'info');
43
+ expect(ok).toBeDefined();
42
44
  });
43
45
  it('emits usage error when fewer than two args', async () => {
44
46
  const sh3 = makeSh3(projectScope);
@@ -47,50 +49,59 @@ describe('xfer verb', () => {
47
49
  const err = pushed.find((e) => e.kind === 'status' && e.level === 'error');
48
50
  expect(err.text).toMatch(/usage/i);
49
51
  });
50
- it('moves (default) a doc from project to personal scope', async () => {
51
- const transferToScope = vi.fn(async () => { });
52
- const docs = makeDocs({ transferToScope });
52
+ it('moves a doc from project to personal scope', async () => {
53
+ const transferBetweenScopes = vi.fn(async () => { });
54
+ const docs = makeDocs({ transferBetweenScopes });
53
55
  const sh3 = makeSh3(projectScope);
54
56
  const { ctx, pushed } = makeCtx(docs, sh3);
55
57
  await xferVerb.run(ctx, ['@project-proj-abc:notes/draft.md', '@me:notes/draft.md']);
56
- expect(transferToScope).toHaveBeenCalledWith('notes', 'draft.md', 'user-me', expect.objectContaining({ delete: true }));
58
+ expect(transferBetweenScopes).toHaveBeenCalledWith('proj-abc', 'notes', 'draft.md', 'user-me', 'notes', 'draft.md', expect.objectContaining({ delete: true }));
57
59
  const ok = pushed.find((e) => e.kind === 'status' && e.level === 'info');
58
60
  expect(ok).toBeDefined();
59
61
  });
60
62
  it('-C flag copies without deleting source', async () => {
61
- const transferToScope = vi.fn(async () => { });
62
- const docs = makeDocs({ transferToScope });
63
+ const transferBetweenScopes = vi.fn(async () => { });
64
+ const docs = makeDocs({ transferBetweenScopes });
63
65
  const sh3 = makeSh3(projectScope);
64
66
  const { ctx } = makeCtx(docs, sh3);
65
67
  await xferVerb.run(ctx, ['-C', '@project-proj-abc:notes/draft.md', '@me:notes/draft.md']);
66
- expect(transferToScope).toHaveBeenCalledWith('notes', 'draft.md', 'user-me', expect.objectContaining({ delete: false }));
68
+ expect(transferBetweenScopes).toHaveBeenCalledWith('proj-abc', 'notes', 'draft.md', 'user-me', 'notes', 'draft.md', expect.objectContaining({ delete: false }));
67
69
  });
68
- it('rejects when src scope is not the active project (v1 limitation)', async () => {
69
- const transferToScope = vi.fn(async () => { });
70
- const docs = makeDocs({ transferToScope });
70
+ it('allows @me as source when project scope is active', async () => {
71
+ const transferBetweenScopes = vi.fn(async () => { });
72
+ const docs = makeDocs({ transferBetweenScopes });
71
73
  const sh3 = makeSh3(projectScope);
72
74
  const { ctx, pushed } = makeCtx(docs, sh3);
73
- // @me src while project is active → src tenant differs from active
74
75
  await xferVerb.run(ctx, ['@me:notes/draft.md', '@project-proj-abc:notes/draft.md']);
75
- expect(transferToScope).not.toHaveBeenCalled();
76
+ expect(transferBetweenScopes).toHaveBeenCalledWith('user-me', 'notes', 'draft.md', 'proj-abc', 'notes', 'draft.md', expect.objectContaining({ delete: true }));
77
+ const ok = pushed.find((e) => e.kind === 'status' && e.level === 'info');
78
+ expect(ok).toBeDefined();
79
+ });
80
+ it('emits error when source and destination are identical', async () => {
81
+ const transferBetweenScopes = vi.fn(async () => { });
82
+ const docs = makeDocs({ transferBetweenScopes });
83
+ const sh3 = makeSh3(projectScope);
84
+ const { ctx, pushed } = makeCtx(docs, sh3);
85
+ await xferVerb.run(ctx, ['@project-proj-abc:notes/draft.md', '@project-proj-abc:notes/draft.md']);
86
+ expect(transferBetweenScopes).not.toHaveBeenCalled();
76
87
  const err = pushed.find((e) => e.kind === 'status' && e.level === 'error');
77
- expect(err).toBeDefined();
88
+ expect(err.text).toMatch(/same/i);
78
89
  });
79
90
  it('-R flag recurses over all docs matching src prefix', async () => {
80
- const transferToScope = vi.fn(async () => { });
91
+ const transferBetweenScopes = vi.fn(async () => { });
81
92
  const allDocs = [
82
93
  { shardId: 'notes', path: 'ideas/a.md', size: 1, lastModified: 0 },
83
94
  { shardId: 'notes', path: 'ideas/b.md', size: 1, lastModified: 0 },
84
95
  { shardId: 'notes', path: 'other.md', size: 1, lastModified: 0 },
85
96
  ];
86
97
  const docs = makeDocs({
87
- transferToScope,
88
- listDocuments: vi.fn(async () => allDocs),
98
+ transferBetweenScopes,
99
+ listDocumentsIn: vi.fn(async () => allDocs),
89
100
  });
90
101
  const sh3 = makeSh3(projectScope);
91
102
  const { ctx } = makeCtx(docs, sh3);
92
103
  await xferVerb.run(ctx, ['-R', '@project-proj-abc:notes/ideas', '@me:notes/ideas']);
93
- // Only the two docs under ideas/ should be transferred
94
- expect(transferToScope).toHaveBeenCalledTimes(2);
104
+ expect(transferBetweenScopes).toHaveBeenCalledTimes(2);
105
+ expect(transferBetweenScopes).toHaveBeenCalledWith('proj-abc', 'notes', 'ideas/a.md', 'user-me', 'notes', 'ideas/a.md', expect.objectContaining({ delete: true }));
95
106
  });
96
107
  });
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.20.2";
2
+ export declare const VERSION = "0.21.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.20.2';
2
+ export const VERSION = '0.21.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sh3-core",
3
- "version": "0.20.2",
3
+ "version": "0.21.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -39,7 +39,8 @@
39
39
  "test:watch": "vitest"
40
40
  },
41
41
  "dependencies": {
42
- "esm-env": "^1.1.0"
42
+ "esm-env": "^1.1.0",
43
+ "fflate": "^0.8.3"
43
44
  },
44
45
  "peerDependencies": {
45
46
  "svelte": "^5.0.0",