dirvi-lib 0.7.0 → 0.8.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 (72) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/tree-surfer/cursor.d.ts +52 -26
  5. package/dist/tree-surfer/cursor.d.ts.map +1 -1
  6. package/dist/tree-surfer/cursor.js +19 -44
  7. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts +2 -0
  8. package/dist/tree-surfer/fold-node/fold-node.arb.d.ts.map +1 -0
  9. package/dist/tree-surfer/fold-node/fold-node.arb.js +1 -0
  10. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts +3 -4
  11. package/dist/tree-surfer/fold-node/fold-node.impl.d.ts.map +1 -1
  12. package/dist/tree-surfer/fold-node/fold-node.impl.js +73 -67
  13. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts +3 -5
  14. package/dist/tree-surfer/fold-node/fold-node.schema.d.ts.map +1 -1
  15. package/dist/tree-surfer/fold-node/fold-node.schema.js +2 -9
  16. package/dist/tree-surfer/fold-node/fold-node.types.d.ts +169 -46
  17. package/dist/tree-surfer/fold-node/fold-node.types.d.ts.map +1 -1
  18. package/dist/tree-surfer/fold-node/tree-node.test.d.ts +2 -0
  19. package/dist/tree-surfer/fold-node/tree-node.test.d.ts.map +1 -0
  20. package/dist/tree-surfer/fold-node/tree-node.test.js +90 -0
  21. package/dist/tree-surfer/index.d.ts +3 -1
  22. package/dist/tree-surfer/index.d.ts.map +1 -1
  23. package/dist/tree-surfer/index.js +3 -1
  24. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts +5 -5
  25. package/dist/tree-surfer/nav-node/nav-node.impl.d.ts.map +1 -1
  26. package/dist/tree-surfer/nav-node/nav-node.impl.js +84 -99
  27. package/dist/tree-surfer/nav-node/nav-node.types.d.ts +62 -34
  28. package/dist/tree-surfer/nav-node/nav-node.types.d.ts.map +1 -1
  29. package/dist/tree-surfer/nav-node/nav-node.types.js +1 -3
  30. package/dist/tree-surfer/state/state.impl.d.ts +7 -0
  31. package/dist/tree-surfer/state/state.impl.d.ts.map +1 -0
  32. package/dist/tree-surfer/state/state.impl.js +89 -0
  33. package/dist/tree-surfer/state/state.schemas.d.ts +9 -0
  34. package/dist/tree-surfer/state/state.schemas.d.ts.map +1 -0
  35. package/dist/tree-surfer/state/state.schemas.js +8 -0
  36. package/dist/tree-surfer/state/state.types.d.ts +21 -0
  37. package/dist/tree-surfer/state/state.types.d.ts.map +1 -0
  38. package/dist/tree-surfer/state/state.types.js +1 -0
  39. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts +82 -17
  40. package/dist/tree-surfer/tree-node/tree-node.arb.d.ts.map +1 -1
  41. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts +2 -0
  42. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.d.ts.map +1 -0
  43. package/dist/tree-surfer/tree-node/tree-node.arb.debug.test.js +34 -0
  44. package/dist/tree-surfer/tree-node/tree-node.arb.js +164 -56
  45. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts +1 -1
  46. package/dist/tree-surfer/tree-node/tree-node.impl.d.ts.map +1 -1
  47. package/dist/tree-surfer/tree-node/tree-node.impl.js +41 -48
  48. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts +2 -2
  49. package/dist/tree-surfer/tree-node/tree-node.schemas.d.ts.map +1 -1
  50. package/dist/tree-surfer/tree-node/tree-node.schemas.js +2 -2
  51. package/dist/tree-surfer/tree-node/tree-node.test.js +144 -76
  52. package/dist/tree-surfer/tree-node/tree-node.types.d.ts +89 -107
  53. package/dist/tree-surfer/tree-node/tree-node.types.d.ts.map +1 -1
  54. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts +6 -2
  55. package/dist/tree-surfer-ui/application/intent-to-effect.d.ts.map +1 -1
  56. package/dist/tree-surfer-ui/application/intent-to-effect.js +1 -6
  57. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts +10 -2
  58. package/dist/tree-surfer-ui/application/user-input-to-intent.d.ts.map +1 -1
  59. package/dist/tree-surfer-ui/application/user-input-to-intent.js +12 -5
  60. package/dist/tree-surfer-ui/domain/effect.d.ts +9 -8
  61. package/dist/tree-surfer-ui/domain/effect.d.ts.map +1 -1
  62. package/dist/tree-surfer-ui/domain/index.d.ts +1 -1
  63. package/dist/tree-surfer-ui/domain/index.d.ts.map +1 -1
  64. package/dist/tree-surfer-ui/domain/index.js +1 -1
  65. package/dist/tree-surfer-ui/domain/{input-state.d.ts → input-mode-state.d.ts} +2 -2
  66. package/dist/tree-surfer-ui/domain/input-mode-state.d.ts.map +1 -0
  67. package/package.json +3 -2
  68. package/dist/tree-surfer/state.d.ts +0 -25
  69. package/dist/tree-surfer/state.d.ts.map +0 -1
  70. package/dist/tree-surfer/state.js +0 -118
  71. package/dist/tree-surfer-ui/domain/input-state.d.ts.map +0 -1
  72. /package/dist/tree-surfer-ui/domain/{input-state.js → input-mode-state.js} +0 -0
@@ -1,117 +1,185 @@
1
- import { describe, expect, it } from 'vitest';
1
+ import { describe, expect, it, vi } from 'vitest';
2
2
  import * as fc from 'fast-check';
3
3
  import { createTreeNodeApi } from './tree-node.impl.js';
4
- import { forestAndPathAndExpectedArb, newBranchesOrNullArb, nodesArrayAndBranchPathArb, } from './tree-node.arb.js';
5
- const stringNodeApi = createTreeNodeApi();
6
- describe('tree node API', () => {
7
- it('getAtPath returns the node at the generated path', () => {
8
- fc.assert(fc.property(forestAndPathAndExpectedArb, ({ entries, path, expected }) => {
9
- expect(stringNodeApi.getAtPath(entries, path, (node) => node)).toBe(expected);
4
+ import { newBranchesOrNullArb, nodesArrayAndBranchPathArb, nodeAndPathAndExpectedArb, } from './tree-node.arb.js';
5
+ const api = createTreeNodeApi();
6
+ describe('getChildById', () => {
7
+ it('UT: returns the matching direct child', () => {
8
+ const child = { id: 'child' };
9
+ const root = {
10
+ id: 'root',
11
+ children: [child],
12
+ };
13
+ expect(api.getChildById(root, 'child')).toBe(child);
14
+ });
15
+ it('UT: returns undefined for a missing child', () => {
16
+ const root = {
17
+ id: 'root',
18
+ children: [],
19
+ };
20
+ expect(api.getChildById(root, 'missing')).toBeUndefined();
21
+ });
22
+ });
23
+ describe('getAtPath', () => {
24
+ // Using `selector = vi.fn...` here is too heavy
25
+ it('applies selector to the node at a valid path', () => {
26
+ fc.assert(fc.property(nodeAndPathAndExpectedArb, ({ root, path, expected }) => {
27
+ expect(api.getAtPath(root, path, (node) => node)).toBe(expected);
28
+ expect(api.getAtPath(root, path, (node) => node.id)).toBe(expected.id);
10
29
  }));
11
30
  });
12
- it('getAtPath applies the selector', () => {
13
- const entries = [
14
- {
15
- id: 'root',
16
- children: [{ id: 'child' }],
17
- },
18
- ];
19
- expect(stringNodeApi.getAtPath(entries, ['root', 'child'], (node) => node.id)).toBe('child');
31
+ it('UT: calls the selector once with the resolved node', () => {
32
+ const child = { id: 'child' };
33
+ const root = {
34
+ id: 'root',
35
+ children: [child],
36
+ };
37
+ const selector = vi.fn((node) => node.id);
38
+ expect(api.getAtPath(root, ['child'], selector)).toBe('child');
39
+ expect(selector).toHaveBeenCalledOnce();
40
+ expect(selector).toHaveBeenCalledWith(child);
20
41
  });
21
- it('getAtPath returns undefined for an empty path', () => {
22
- const entries = [{ id: 'root' }];
23
- expect(stringNodeApi.getAtPath(entries, [], (node) => node)).toBeUndefined();
42
+ it('UT: returns undefined for an invalid root path without calling the selector', () => {
43
+ const selector = vi.fn((node) => node.id);
44
+ const result = api.getAtPath([{ id: 'root' }], ['missing'], selector);
45
+ expect(result).toBeUndefined();
46
+ expect(selector).not.toHaveBeenCalled();
24
47
  });
25
- it('getAtPath returns undefined through a closed branch', () => {
26
- const entries = [
27
- {
28
- id: 'root',
29
- children: null,
30
- },
31
- ];
32
- expect(stringNodeApi.getAtPath(entries, ['root', 'child'], (node) => node)).toBeUndefined();
48
+ it('UT: returns undefined when a child is missing from an open branch', () => {
49
+ const root = {
50
+ id: 'root',
51
+ children: [{ id: 'child' }],
52
+ };
53
+ const selector = vi.fn((node) => node.id);
54
+ const result = api.getAtPath(root, ['root', 'missing'], selector);
55
+ expect(result).toBeUndefined();
56
+ expect(selector).not.toHaveBeenCalled();
33
57
  });
34
- it('getAtPath returns undefined when the selector returns undefined', () => {
35
- const entries = [{ id: 'root' }];
36
- expect(stringNodeApi.getAtPath(entries, ['root'], () => undefined)).toBeUndefined();
58
+ it('UT: resolves the root for an empty path', () => {
59
+ const root = { id: 'root' };
60
+ const selector = vi.fn((node) => node.id);
61
+ expect(api.getAtPath(root, [], selector)).toBe('root');
62
+ expect(selector).toHaveBeenCalledOnce();
63
+ expect(selector).toHaveBeenCalledWith(root);
37
64
  });
38
- it('getChildById returns the matching child', () => {
39
- const child = { id: 'child' };
65
+ it('UT: returns undefined through a closed branch without calling the selector', () => {
40
66
  const root = {
41
67
  id: 'root',
42
- children: [child],
68
+ children: null,
43
69
  };
44
- expect(stringNodeApi.isOpenBranch(root)).toBe(true);
45
- if (!stringNodeApi.isOpenBranch(root)) {
46
- return;
47
- }
48
- expect(stringNodeApi.getChildById(root, 'child')).toBe(child);
49
- expect(stringNodeApi.getChildById(root, 'missing')).toBeUndefined();
70
+ const selector = vi.fn((node) => node.id);
71
+ const result = api.getAtPath(root, ['child'], selector);
72
+ expect(result).toBeUndefined();
73
+ expect(selector).not.toHaveBeenCalled();
74
+ });
75
+ it('UT: returns undefined when the selector returns undefined', () => {
76
+ const selector = vi.fn((_node) => undefined);
77
+ const result = api.getAtPath({ id: 'root' }, [], selector);
78
+ expect(result).toBeUndefined();
79
+ expect(selector).toHaveBeenCalledOnce();
80
+ expect(selector).toHaveBeenCalledWith({ id: 'root' });
50
81
  });
51
- it('modifyAtPath replaces the generated node', () => {
52
- fc.assert(fc.property(forestAndPathAndExpectedArb, ({ entries, path, expected }) => {
82
+ });
83
+ describe('modifyAtPath', () => {
84
+ it('replaces the generated node', () => {
85
+ fc.assert(fc.property(nodeAndPathAndExpectedArb, ({ root, path, expected }) => {
53
86
  const replacement = {
54
87
  id: expected.id,
55
88
  };
56
- const updatedForest = stringNodeApi.modifyAtPath(entries, path, () => replacement);
57
- expect(updatedForest).not.toBeUndefined();
58
- if (updatedForest === undefined) {
89
+ const updatedRoot = api.modifyAtPath(root, path, () => replacement);
90
+ expect(updatedRoot).not.toBeUndefined();
91
+ if (updatedRoot === undefined) {
59
92
  return;
60
93
  }
61
- expect(stringNodeApi.getAtPath(updatedForest, path, (node) => node)).toBe(replacement);
62
- expect(stringNodeApi.getAtPath(entries, path, (node) => node)).toBe(expected);
94
+ expect(api.getAtPath(updatedRoot, path, (node) => node)).toBe(replacement);
95
+ expect(api.getAtPath(root, path, (node) => node)).toBe(expected);
63
96
  }));
64
- });
65
- it('modifyAtPath replaces children at a branch path', () => {
66
- fc.assert(fc.property(nodesArrayAndBranchPathArb, newBranchesOrNullArb, ({ entries, path, expected }, newBranches) => {
67
- expect(stringNodeApi.isBranch(expected)).toBe(true);
68
- const updatedForest = stringNodeApi.modifyAtPath(entries, path, (node) => ({
97
+ }, 12000);
98
+ it('replaces children at a branch path', () => {
99
+ fc.assert(fc.property(nodesArrayAndBranchPathArb, newBranchesOrNullArb, ({ root, path, expected }, newBranches) => {
100
+ expect(api.isBranch(expected)).toBe(true);
101
+ const updatedRoot = api.modifyAtPath(root, path, (node) => ({
69
102
  ...node,
70
103
  children: newBranches,
71
104
  }));
72
- expect(updatedForest).not.toBeUndefined();
73
- if (updatedForest === undefined) {
105
+ expect(updatedRoot).not.toBeUndefined();
106
+ if (updatedRoot === undefined) {
74
107
  return;
75
108
  }
76
- const updatedNode = stringNodeApi.getAtPath(updatedForest, path, (node) => node);
109
+ const updatedNode = api.getAtPath(updatedRoot, path, (node) => node);
77
110
  expect(updatedNode).not.toBeUndefined();
78
111
  expect(updatedNode).not.toBe(expected);
79
112
  if (updatedNode === undefined ||
80
- !stringNodeApi.isBranch(updatedNode) ||
81
- (!stringNodeApi.isOpenBranch(updatedNode) && newBranches !== null)) {
113
+ !api.isBranch(updatedNode) ||
114
+ (!api.isOpenBranch(updatedNode) && newBranches !== null)) {
82
115
  return;
83
116
  }
84
- if (stringNodeApi.isOpenBranch(updatedNode)) {
85
- expect([...stringNodeApi.getChildren(updatedNode)]).toStrictEqual(newBranches);
117
+ if (api.isOpenBranch(updatedNode)) {
118
+ expect([...api.getChildren(updatedNode)]).toStrictEqual(newBranches);
86
119
  }
87
- expect(stringNodeApi.getAtPath(entries, path, (node) => node)).toBe(expected);
120
+ expect(api.getAtPath(root, path, (node) => node)).toBe(expected);
88
121
  }));
89
122
  });
90
- it('modifyAtPath returns undefined when the modifier aborts', () => {
91
- const entries = [{ id: 'root' }];
92
- expect(stringNodeApi.modifyAtPath(entries, ['root'], () => undefined)).toBeUndefined();
123
+ it('UT: calls the modifier once with the resolved node', () => {
124
+ const child = { id: 'child' };
125
+ const root = {
126
+ id: 'root',
127
+ children: [child],
128
+ };
129
+ const replacement = { id: 'replacement' };
130
+ const modifier = vi.fn(() => replacement);
131
+ const updatedRoot = api.modifyAtPath(root, ['child'], modifier);
132
+ expect(updatedRoot).toBeDefined();
133
+ expect(modifier).toHaveBeenCalledOnce();
134
+ expect(modifier).toHaveBeenCalledWith(child);
135
+ expect(api.getAtPath(updatedRoot, ['replacement'], (node) => node)).toBe(replacement);
136
+ expect(api.getAtPath(updatedRoot, ['child'], (node) => node)).toBeUndefined();
93
137
  });
94
- it('modifyAtPath returns undefined for an empty path', () => {
95
- const entries = [{ id: 'root' }];
96
- expect(stringNodeApi.modifyAtPath(entries, [], (node) => node)).toBeUndefined();
138
+ it('UT: modifies the root for an empty path', () => {
139
+ const root = { id: 'root' };
140
+ const replacement = { id: 'replacement' };
141
+ const modifier = vi.fn(() => replacement);
142
+ const updatedRoot = api.modifyAtPath(root, [], modifier);
143
+ expect(updatedRoot).toBe(replacement);
144
+ expect(modifier).toHaveBeenCalledOnce();
145
+ expect(modifier).toHaveBeenCalledWith(root);
97
146
  });
98
- it('modifyAtPath returns undefined for an invalid path', () => {
99
- const entries = [
100
- {
147
+ it.each([
148
+ {
149
+ name: 'a missing child',
150
+ root: {
101
151
  id: 'root',
102
152
  children: [],
103
153
  },
104
- ];
105
- expect(stringNodeApi.modifyAtPath(entries, ['missing'], (node) => node)).toBeUndefined();
106
- expect(stringNodeApi.modifyAtPath(entries, ['root', 'missing'], (node) => node)).toBeUndefined();
107
- });
108
- it('modifyAtPath cannot traverse a closed branch', () => {
109
- const entries = [
110
- {
154
+ path: ['missing'],
155
+ },
156
+ {
157
+ name: 'a path through a leaf',
158
+ root: {
159
+ id: 'root',
160
+ },
161
+ path: ['child'],
162
+ },
163
+ {
164
+ name: 'a path through a closed branch',
165
+ root: {
111
166
  id: 'root',
112
167
  children: null,
113
168
  },
114
- ];
115
- expect(stringNodeApi.modifyAtPath(entries, ['root', 'child'], (node) => node)).toBeUndefined();
169
+ path: ['child'],
170
+ },
171
+ ])('UT: returns undefined and does not call the modifier', ({ root, path }) => {
172
+ const modifier = vi.fn((node) => node);
173
+ const result = api.modifyAtPath(root, path, modifier);
174
+ expect(result).toBeUndefined();
175
+ expect(modifier).not.toHaveBeenCalled();
176
+ });
177
+ it('UT: returns undefined when the modifier aborts', () => {
178
+ const root = { id: 'root' };
179
+ const modifier = vi.fn(() => undefined);
180
+ const result = api.modifyAtPath(root, [], modifier);
181
+ expect(result).toBeUndefined();
182
+ expect(modifier).toHaveBeenCalledOnce();
183
+ expect(modifier).toHaveBeenCalledWith(root);
116
184
  });
117
185
  });
@@ -1,13 +1,14 @@
1
1
  /**
2
2
  * A serializable primitive suitable for use as a stable tree ID.
3
3
  *
4
- * The runtime schema guarantees that numeric values are finite. TypeScript
5
- * cannot represent the exclusion of `NaN` and infinities from `number`, so
6
- * values from untrusted sources should be validated with
7
- * `serializableKeySchema` before being used as IDs.
4
+ * @remarks
5
+ *
6
+ * Numeric IDs must be finite. TypeScript cannot represent the exclusion of
7
+ * `NaN` and infinities from `number`, so values from untrusted sources should
8
+ * be validated with `serializableKeySchema`.
8
9
  *
9
10
  * For application-specific IDs, define a narrower branded schema and derive
10
- * its type with `z.output`.
11
+ * its type with `z.output`:
11
12
  *
12
13
  * @example
13
14
  *
@@ -27,146 +28,127 @@
27
28
  */
28
29
  export type SerializableKey = string | number;
29
30
  /**
30
- * A node with no children.
31
+ * A node in a tree.
32
+ *
33
+ * @remarks
34
+ *
35
+ * A node's `id` identifies it among its siblings.
36
+ *
37
+ * Nodes are discriminated by the shape of their `children` property:
38
+ *
39
+ * - a leaf has no children and does not have a child collection;
40
+ * - an open branch has a loaded array of child nodes;
41
+ * - a closed branch has children that have not been loaded yet and uses
42
+ * `children: null`.
43
+ *
44
+ * `Node` is the concrete application-specific node type. It may contain
45
+ * additional properties, provided that it preserves the leaf-or-branch shape
46
+ * described by this type.
47
+ *
48
+ * The recursive `Node` parameter allows application-specific properties
49
+ * to be available on every descendant.
50
+ *
51
+ * @typeParam Id - The type of node IDs.
52
+ * @typeParam Node - The application-specific recursive node type.
53
+ *
54
+ * @example
55
+ *
56
+ * ```ts
57
+ * type DirectoryEntry = TreeNode<string, DirectoryEntry> & {
58
+ * kind: 'file' | 'directory';
59
+ * };
60
+ * ```
31
61
  */
62
+ export type TreeNode<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = LeafTreeNode<Id> | BranchTreeNode<Id, Node>;
63
+ /** A node with no children. */
32
64
  export type LeafTreeNode<Id extends SerializableKey> = {
33
65
  id: Id;
34
66
  children?: never;
35
67
  };
36
- /**
37
- * A branch whose children have not been loaded.
38
- */
39
- export type ClosedBranchTreeNode<Id extends SerializableKey> = {
68
+ /** A tree node that has children, either loaded or unloaded. */
69
+ export type BranchTreeNode<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = OpenBranchTreeNode<Id, Node> | ClosedBranchTreeNode<Id>;
70
+ /** A branch whose direct children have been loaded. */
71
+ export type OpenBranchTreeNode<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = {
40
72
  id: Id;
41
- children: null;
73
+ children: Node[];
42
74
  };
43
- /**
44
- * A branch whose children have been loaded.
45
- *
46
- * TODO Later: Add a dictionary by ID for faster lookup.
47
- * Then children will just be an array for keeping order.
48
- */
49
- export type OpenBranchTreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = {
75
+ /** A branch whose direct children have not been loaded. */
76
+ export type ClosedBranchTreeNode<Id extends SerializableKey> = {
50
77
  id: Id;
51
- children: ChildNode[];
78
+ children: null;
52
79
  };
53
80
  /**
54
- * A tree node that has children, either loaded or unloaded.
55
- */
56
- export type BranchTreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = OpenBranchTreeNode<Id, ChildNode> | ClosedBranchTreeNode<Id>;
57
- /**
58
- * A node in a tree.
59
- *
60
- * `ChildNode` is the concrete application-specific node type. It may contain
61
- * additional properties, provided that it has the shape of either a leaf or a
62
- * branch.
63
- */
64
- export type TreeNode<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = LeafTreeNode<Id> | BranchTreeNode<Id, ChildNode>;
65
- /**
66
- * Selects a value from a node reached by a path.
67
- *
68
- * The selector is called only when the complete path exists and identifies
69
- * a node. Returning `undefined` from the selector is allowed, but is
70
- * indistinguishable from an unsuccessful lookup in the return value.
71
- */
72
- export type TreeNodeSelector<ChildNode, Result> = (node: ChildNode) => Result;
73
- /**
74
- * Produces a replacement for a node reached by a path.
81
+ * Operations for inspecting and immutably updating a tree rooted at a node.
75
82
  *
76
- * Returning `undefined` aborts the modification and causes `modifyAtPath`
77
- * to return `undefined`.
78
- */
79
- export type TreeNodeModifier<ChildNode> = (node: ChildNode) => ChildNode | undefined;
80
- /**
81
- * Fundamental operations for inspecting and immutably updating a tree forest.
83
+ * @remarks
82
84
  *
83
- * A forest is represented by an array of root-level nodes. A path is an array
84
- * of IDs beginning at a root node and continuing through its descendants.
85
+ * A path is an array of IDs relative to the supplied root node.
85
86
  *
86
87
  * For example, given tree with IDs:
87
88
  *
88
89
  * root
89
90
  * └── child
90
91
  *
91
- * the path to `child` is `['root', 'child']`.
92
+ * the path to `root` is `[]`, and the path to `child` is `['child']`.
92
93
  *
93
- * Path traversal and child lookup are performed by this API so that callers
94
- * do not depend on the internal child-storage representation or need to
95
- * iterate over children themselves.
94
+ * Paths cannot pass through leaves or closed branches. A closed branch may be
95
+ * the final node in a path. An empty path identifies the root node.
96
96
  *
97
- * An empty path does not identify a node and is invalid for both lookup and
98
- * modification operations.
97
+ * @typeParam Id - The type of node IDs.
98
+ * @typeParam Node - The application-specific recursive node type.
99
99
  */
100
- export type TreeNodeApi<Id extends SerializableKey, ChildNode extends TreeNode<Id, ChildNode>> = {
101
- isLeaf(node: ChildNode): node is ChildNode & LeafTreeNode<Id>;
102
- isBranch(node: ChildNode): node is ChildNode & BranchTreeNode<Id, ChildNode>;
103
- isClosedBranch(node: BranchTreeNode<Id, ChildNode>): node is ClosedBranchTreeNode<Id>;
104
- isOpenBranch(node: BranchTreeNode<Id, ChildNode>): node is OpenBranchTreeNode<Id, ChildNode>;
100
+ export type TreeNodeApi<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = {
101
+ isLeaf(node: Node): node is Node & LeafTreeNode<Id>;
102
+ isBranch(node: Node): node is Node & BranchTreeNode<Id, Node>;
103
+ isClosedBranch(node: BranchTreeNode<Id, Node>): node is ClosedBranchTreeNode<Id>;
104
+ isOpenBranch(node: BranchTreeNode<Id, Node>): node is OpenBranchTreeNode<Id, Node>;
105
105
  /**
106
- * Returns the loaded children of an open branch.
106
+ * Returns the loaded direct children of an open branch.
107
107
  *
108
- * The returned iterable preserves the branch's sibling order. The method
109
- * can only be called after narrowing the branch with `isOpenBranch`.
108
+ * The children are returned in sibling order. The returned nodes are the
109
+ * existing nodes and are not cloned.
110
110
  *
111
- * The returned children are the existing child nodes; they are not cloned.
111
+ * @param node - An open branch.
112
+ * @returns The branch's loaded children.
112
113
  */
113
- getChildren(node: OpenBranchTreeNode<Id, ChildNode>): Iterable<ChildNode>;
114
+ getChildren(node: OpenBranchTreeNode<Id, Node>): Iterable<Node>;
114
115
  /**
115
- * Returns the child with the supplied ID.
116
+ * Finds a direct child by ID.
116
117
  *
117
- * Returns `undefined` when the branch has no child with that ID.
118
- * The returned child is the existing node; it is not cloned.
118
+ * @param node - An open branch.
119
+ * @param id - The ID of the child to find.
120
+ * @returns The existing child, or `undefined` when no matching child exists.
119
121
  */
120
- getChildById(node: OpenBranchTreeNode<Id, ChildNode>, id: Id): ChildNode | undefined;
122
+ getChildById(node: OpenBranchTreeNode<Id, Node>, id: Id): Node | undefined;
121
123
  /**
122
- * Applies `selector` to the node at `path`.
123
- *
124
- * The path must:
125
- *
126
- * - be non-empty;
127
- * - begin at one of the supplied forest entries;
128
- * - contain only IDs that identify existing entries;
129
- * - not traverse through a leaf; and
130
- * - not traverse through a closed branch.
131
- *
132
- * A branch at the end of the path is valid even when it is closed.
124
+ * Applies a selector to the node at a path relative to `root`.
133
125
  *
134
126
  * The selector is called only after the complete path has been resolved.
135
- * This method does not clone the tree or the selected node.
127
+ * The selected node is not cloned.
136
128
  *
137
- * Returns `undefined` when the path cannot be resolved, or when the
138
- * selector returns `undefined`.
129
+ * @param root - The node searching through.
130
+ * @param path - Path relative to root, with empty selecting the root.
131
+ * @param selector - The function to apply to the resolved node.
132
+ * @returns The selector result, or `undefined` when the path cannot be
133
+ * resolved or the selector returns `undefined`.
139
134
  */
140
- getAtPath<Result>(entries: ChildNode[], path: Id[], selector: TreeNodeSelector<ChildNode, Result>): Result | undefined;
135
+ getAtPath<Result>(root: Node, path: readonly Id[], selector: (node: Node) => Result): Result | undefined;
141
136
  /**
142
- * Immutably modifies the node at `path`.
137
+ * Immutably replaces the node at a path relative to `root`.
143
138
  *
144
139
  * The modifier is called only after the complete path has been resolved.
145
- * Its return value replaces the target node. Returning `undefined` aborts
146
- * the modification.
147
- *
148
- * The operation creates new arrays and ancestor nodes along the path.
149
- * Nodes and arrays unrelated to the path retain their original object
150
- * identity. The input forest and its nodes (as objects) are never modified.
151
- *
152
- * The path must:
153
- *
154
- * - be non-empty;
155
- * - begin at one of the supplied forest entries;
156
- * - contain only IDs that identify existing entries;
157
- * - not traverse through a leaf; and
158
- * - not traverse through a closed branch.
159
- *
160
- * A leaf or branch, including a closed branch, may be the final target.
140
+ * Returning `undefined` aborts the modification.
161
141
  *
162
- * Returns `undefined` when:
142
+ * New arrays and ancestor nodes are created along the modified path.
143
+ * Unrelated nodes and arrays retain their original object identity. The
144
+ * input array is never modified.
163
145
  *
164
- * - `path` is empty;
165
- * - the path does not exist;
166
- * - an intermediate node is a leaf;
167
- * - an intermediate branch is closed; or
168
- * - the modifier returns `undefined`.
146
+ * @param root - The root node to modify.
147
+ * @param path - A path relative to root. An empty path selects the root.
148
+ * @param modifier - Produces the replacement node.
149
+ * @returns A new root, or `undefined` when the path cannot be resolved or
150
+ * the modifier returns `undefined`.
169
151
  */
170
- modifyAtPath(entries: ChildNode[], path: Id[], modifier: TreeNodeModifier<ChildNode>): ChildNode[] | undefined;
152
+ modifyAtPath(root: Node, path: readonly Id[], modifier: (node: Node) => Node | undefined): Node | undefined;
171
153
  };
172
154
  //# sourceMappingURL=tree-node.types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tree-node.types.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer/tree-node/tree-node.types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,MAAM,QAAQ,CAClB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B,YAAY,CAAC,EAAE,CAAC,GAAG,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;AAEhD,+BAA+B;AAC/B,MAAM,MAAM,YAAY,CAAC,EAAE,SAAS,eAAe,IAAI;IACrD,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,cAAc,CACxB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,oBAAoB,CAAC,EAAE,CAAC,CAAC;AAK5D,uDAAuD;AACvD,MAAM,MAAM,kBAAkB,CAC5B,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B;IACF,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,IAAI,EAAE,CAAC;CAClB,CAAC;AAEF,2DAA2D;AAC3D,MAAM,MAAM,oBAAoB,CAAC,EAAE,SAAS,eAAe,IAAI;IAC7D,EAAE,EAAE,EAAE,CAAC;IACP,QAAQ,EAAE,IAAI,CAAC;CAChB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,MAAM,WAAW,CACrB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B;IACF,MAAM,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;IACpD,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,IAAI,IAAI,GAAG,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC9D,cAAc,CACZ,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,GAC7B,IAAI,IAAI,oBAAoB,CAAC,EAAE,CAAC,CAAC;IACpC,YAAY,CACV,IAAI,EAAE,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,GAC7B,IAAI,IAAI,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAExC;;;;;;;;OAQG;IACH,WAAW,CAAC,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAEhE;;;;;;OAMG;IACH,YAAY,CAAC,IAAI,EAAE,kBAAkB,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,EAAE,EAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IAE3E;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,MAAM,EACd,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,MAAM,GAC/B,MAAM,GAAG,SAAS,CAAC;IAEtB;;;;;;;;;;;;;;;OAeG;IACH,YAAY,CACV,IAAI,EAAE,IAAI,EACV,IAAI,EAAE,SAAS,EAAE,EAAE,EACnB,QAAQ,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,SAAS,GACzC,IAAI,GAAG,SAAS,CAAC;CACrB,CAAC"}
@@ -1,6 +1,10 @@
1
1
  import { SerializableKey, TreeNode, TreeNodeApi } from '../../tree-surfer/index.js';
2
2
  import { Effect, Intent } from '../domain/index.js';
3
3
  import { CursorApi, State, StateApi } from '../../tree-surfer/index.js';
4
- export type IntentToEffect<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>> = (intent: Intent, state: State<Id, BufferNode>) => Effect<Id, BufferNode> | undefined;
5
- export declare function createIntentToEffect<Id extends SerializableKey, BufferNode extends TreeNode<Id, BufferNode>>(stateApi: StateApi<Id, BufferNode>, cursorApi: CursorApi<Id>, treeNodeApi: TreeNodeApi<Id, BufferNode>): IntentToEffect<Id, BufferNode>;
4
+ /**
5
+ * By design "effect = intent + state", so it awaits based on the intent and
6
+ * the current state whether an effect should be executed.
7
+ */
8
+ export type IntentToEffect<Id extends SerializableKey, Node extends TreeNode<Id, Node>> = (intent: Intent, state: State<Id, Node>) => Effect<Id, Node> | undefined;
9
+ export declare function createIntentToEffect<Id extends SerializableKey, Node extends TreeNode<Id, Node>>(stateApi: StateApi<Id, Node>, cursorApi: CursorApi<Id>, treeNodeApi: TreeNodeApi<Id, Node>): IntentToEffect<Id, Node>;
6
10
  //# sourceMappingURL=intent-to-effect.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"intent-to-effect.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/intent-to-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,MAAM,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAIxE,MAAM,MAAM,cAAc,CACxB,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,IACzC,CACF,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,UAAU,CAAC,KACzB,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC;AAGxC,wBAAgB,oBAAoB,CAClC,EAAE,SAAS,eAAe,EAC1B,UAAU,SAAS,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAE3C,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,UAAU,CAAC,EAClC,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,EACxB,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,UAAU,CAAC,GACvC,cAAc,CAAC,EAAE,EAAE,UAAU,CAAC,CAkEhC"}
1
+ {"version":3,"file":"intent-to-effect.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/intent-to-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,QAAQ,EACR,WAAW,EACZ,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAAE,MAAM,EAAgB,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAGxE;;;GAGG;AACH,MAAM,MAAM,cAAc,CACxB,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,IAC7B,CACF,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,KAAK,CAAC,EAAE,EAAE,IAAI,CAAC,KACnB,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,GAAG,SAAS,CAAC;AAElC,wBAAgB,oBAAoB,CAClC,EAAE,SAAS,eAAe,EAC1B,IAAI,SAAS,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAE/B,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC,EAC5B,SAAS,EAAE,SAAS,CAAC,EAAE,CAAC,EACxB,WAAW,EAAE,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,GACjC,cAAc,CAAC,EAAE,EAAE,IAAI,CAAC,CAmE1B"}
@@ -1,5 +1,4 @@
1
1
  import { parseCommand } from './parse-command.js';
2
- // Factory method, since Apis are needed to derive intent
3
2
  export function createIntentToEffect(stateApi, cursorApi, treeNodeApi) {
4
3
  return (intent, state) => {
5
4
  switch (intent.intentType) {
@@ -73,10 +72,6 @@ function normalBufferToEffectResult(updatedNormalBuffer) {
73
72
  normalBuffer: updatedNormalBuffer,
74
73
  },
75
74
  };
76
- case 'za':
77
- return dispatchAction({
78
- effectActionType: 'toggleFold',
79
- });
80
75
  case 'zc':
81
76
  return dispatchAction({
82
77
  effectActionType: 'fold',
@@ -120,7 +115,7 @@ function normalInteractRightToEffect(state, stateApi, cursorApi, treeNodeApi) {
120
115
  return;
121
116
  }
122
117
  function normalInteractLeftToEffect(state) {
123
- if (state.cursor.parentPath.length === 0) {
118
+ if (state.cursor.length === 0) {
124
119
  return undefined;
125
120
  }
126
121
  return dispatchAction({
@@ -1,3 +1,11 @@
1
- import { UserInput, InputState, Intent } from '../domain/index.js';
2
- export declare function userInputToIntent(userInput: UserInput, inputState: InputState): Intent | undefined;
1
+ import { UserInput, InputModeState, Intent } from '../domain/index.js';
2
+ /**
3
+ * By design "intent = input + mode + modeState", so it interprets the key
4
+ * inputs depending on the mode and input so far. No context in terms of UI.
5
+ *
6
+ * @param userInput - the key user input.
7
+ * @param inputModeState - the current mode and the user inputs so far.
8
+ * @returns The user's intent.
9
+ */
10
+ export declare function userInputToIntent(userInput: UserInput, inputModeState: InputModeState): Intent | undefined;
3
11
  //# sourceMappingURL=user-input-to-intent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"user-input-to-intent.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/user-input-to-intent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAGnE,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,UAAU,GACrB,MAAM,GAAG,SAAS,CAQpB"}
1
+ {"version":3,"file":"user-input-to-intent.d.ts","sourceRoot":"","sources":["../../../src/tree-surfer-ui/application/user-input-to-intent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAEvE;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,SAAS,EACpB,cAAc,EAAE,cAAc,GAC7B,MAAM,GAAG,SAAS,CAQpB"}
@@ -1,10 +1,17 @@
1
- // Derive the intent from the user input and the current input state.
2
- export function userInputToIntent(userInput, inputState) {
3
- switch (inputState.inputMode) {
1
+ /**
2
+ * By design "intent = input + mode + modeState", so it interprets the key
3
+ * inputs depending on the mode and input so far. No context in terms of UI.
4
+ *
5
+ * @param userInput - the key user input.
6
+ * @param inputModeState - the current mode and the user inputs so far.
7
+ * @returns The user's intent.
8
+ */
9
+ export function userInputToIntent(userInput, inputModeState) {
10
+ switch (inputModeState.inputMode) {
4
11
  case 'normal':
5
- return normalUserInputToIntent(userInput, inputState.normalBuffer);
12
+ return normalUserInputToIntent(userInput, inputModeState.normalBuffer);
6
13
  case 'command':
7
- return commandUserInputToIntent(userInput, inputState.commandLine);
14
+ return commandUserInputToIntent(userInput, inputModeState.commandLine);
8
15
  }
9
16
  }
10
17
  function normalUserInputToIntent(userInput, normalBuffer) {