@crossworks/client-types 0.232.351 → 0.232.354
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/dto/access.ts +13 -0
- package/src/dto/client.ts +5 -0
- package/src/dto/member.ts +9 -0
- package/src/dto/recall.ts +10 -0
- package/src/dto/review.ts +18 -0
- package/src/index.ts +25 -0
- package/src/tree.ts +337 -0
- package/src/types/recall.ts +195 -16
package/package.json
CHANGED
package/src/dto/access.ts
CHANGED
|
@@ -74,6 +74,19 @@ export type AccessNodeView = {
|
|
|
74
74
|
/** Old live links above this item (see ClientOldLinkAbove); only for an
|
|
75
75
|
* item at client. */
|
|
76
76
|
oldLinksAbove?: ClientOldLinkAbove[];
|
|
77
|
+
/** The shared folder it takes its share from (folder sharing); null when
|
|
78
|
+
* none. Absent from brains before folder sharing. */
|
|
79
|
+
sharedVia?: AccessSharedVia | null;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/** The nearest shared folder holding an item (folder sharing, phase 4). The
|
|
83
|
+
* item is read at least at `level`, whatever its own level says, so the
|
|
84
|
+
* control offers nothing above it: moving it out of the folder hides it. */
|
|
85
|
+
export type AccessSharedVia = {
|
|
86
|
+
folderId: string;
|
|
87
|
+
/** Folder names from the kind's top level down to the shared folder. */
|
|
88
|
+
trail: string[];
|
|
89
|
+
level: 'team' | 'client';
|
|
77
90
|
};
|
|
78
91
|
|
|
79
92
|
/** One item that went down with the item that embeds it. */
|
package/src/dto/client.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { ClientItemKind, MemberItemKind } from '../member-kinds';
|
|
9
9
|
import type { MemberItemPill, MemberSpaceItemRow } from './member';
|
|
10
10
|
import type { NodeComment } from './rows';
|
|
11
|
+
import type { TreeKind } from '../tree';
|
|
11
12
|
|
|
12
13
|
/** GET /api/client/shell: who is signed in and the brain's brand. */
|
|
13
14
|
export type ClientShell = {
|
|
@@ -29,6 +30,10 @@ export type ClientShell = {
|
|
|
29
30
|
fontProseSize: string | null;
|
|
30
31
|
logoVersion: string | null;
|
|
31
32
|
logoDarkVersion: string | null;
|
|
33
|
+
/** The kinds this login browses as a read-only folder tree (GET
|
|
34
|
+
* /api/{member,client}/tree/:kind). Absent from brains before folder
|
|
35
|
+
* sharing: the client keeps its flat lists there. */
|
|
36
|
+
treeKinds?: TreeKind[];
|
|
32
37
|
};
|
|
33
38
|
|
|
34
39
|
/** One item in "Shared with you": an item at client level. */
|
package/src/dto/member.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { AccessLevel, MemberItemAuthor } from './access';
|
|
8
8
|
import type { MemberItemKind } from '../member-kinds';
|
|
9
|
+
import type { TreeKind } from '../tree';
|
|
9
10
|
export type { MemberItemFilter } from '../member-kinds';
|
|
10
11
|
|
|
11
12
|
/** GET /api/member/shell */
|
|
@@ -30,6 +31,10 @@ export type MemberShell = {
|
|
|
30
31
|
fontProseSize: string | null;
|
|
31
32
|
logoVersion: string | null;
|
|
32
33
|
logoDarkVersion: string | null;
|
|
34
|
+
/** The kinds this login browses as a read-only folder tree (GET
|
|
35
|
+
* /api/{member,client}/tree/:kind). Absent from brains before folder
|
|
36
|
+
* sharing: the client keeps its flat lists there. */
|
|
37
|
+
treeKinds?: TreeKind[];
|
|
33
38
|
};
|
|
34
39
|
|
|
35
40
|
export type MemberLibraryKind = MemberItemKind;
|
|
@@ -219,6 +224,10 @@ export type MemberAcceptedRow = {
|
|
|
219
224
|
icon: string | null;
|
|
220
225
|
/** The level the admin chose: at team or lower it is in the Library too. */
|
|
221
226
|
audience: AccessLevel;
|
|
227
|
+
/** The share it takes from a folder holding it: in the Library at that
|
|
228
|
+
* level too, whatever `audience` says. Absent from brains before folder
|
|
229
|
+
* sharing. */
|
|
230
|
+
inherited?: 'team' | 'client' | null;
|
|
222
231
|
acceptedAt: string | null;
|
|
223
232
|
updatedAt: string;
|
|
224
233
|
};
|
package/src/dto/recall.ts
CHANGED
|
@@ -11,11 +11,21 @@
|
|
|
11
11
|
// ── Recall (memory maps) ─────────────────────────────────────────────────────
|
|
12
12
|
|
|
13
13
|
export type {
|
|
14
|
+
RecallActorKind,
|
|
15
|
+
RecallCardDetailDTO,
|
|
16
|
+
RecallCardWriteDTO,
|
|
14
17
|
RecallLintIssueDTO,
|
|
15
18
|
RecallLintSeverity,
|
|
19
|
+
RecallMapCreateDTO,
|
|
20
|
+
RecallMapCreateResultDTO,
|
|
16
21
|
RecallMapDetailDTO,
|
|
22
|
+
RecallMapPatchDTO,
|
|
17
23
|
RecallMapSummaryDTO,
|
|
18
24
|
RecallNodeDTO,
|
|
19
25
|
RecallOptionDTO,
|
|
20
26
|
RecallPageStateDTO,
|
|
27
|
+
RecallRevisionDTO,
|
|
28
|
+
RecallWarningDTO,
|
|
29
|
+
RecallWriteErrorDTO,
|
|
30
|
+
RecallWriteResultDTO,
|
|
21
31
|
} from '../types/recall';
|
package/src/dto/review.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* the wire, so an older brain or an older client keeps working.
|
|
6
6
|
*/
|
|
7
7
|
import type { AccessItemView, AccessLevel } from './access';
|
|
8
|
+
import type { TreeCrumb, TreeKind } from '../tree';
|
|
8
9
|
|
|
9
10
|
/** Who wrote a reviewable item: a member or a client login. */
|
|
10
11
|
export type ReviewAuthorRole = 'member' | 'client';
|
|
@@ -29,6 +30,19 @@ export type AcceptPreview = {
|
|
|
29
30
|
* CURRENT level. The client shows the ones above the chosen level: they
|
|
30
31
|
* go DOWN with it and need a tick. */
|
|
31
32
|
closure?: AccessItemView[];
|
|
33
|
+
/** Where it lands by default (folder plan phase 5): the brain folder the
|
|
34
|
+
* author filed it in (`folderId` null = the kind's top level), and the
|
|
35
|
+
* author's own folders that become brain folders below it. Absent for a
|
|
36
|
+
* page (no tree yet) and from brains before the tree. */
|
|
37
|
+
place?: AcceptPlace;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type AcceptPlace = {
|
|
41
|
+
kind: TreeKind;
|
|
42
|
+
folderId: string | null;
|
|
43
|
+
/** The folder's crumbs, top-down, itself included. */
|
|
44
|
+
crumbs: TreeCrumb[];
|
|
45
|
+
creates: string[];
|
|
32
46
|
};
|
|
33
47
|
|
|
34
48
|
/** POST /api/team-admin/submissions/:id/accept and
|
|
@@ -37,6 +51,10 @@ export type AcceptRequest = {
|
|
|
37
51
|
audience?: AccessLevel;
|
|
38
52
|
parentPageId?: string | null;
|
|
39
53
|
folderPath?: string | null;
|
|
54
|
+
/** Where the item lands (folder plan phase 5): left out, in place; null,
|
|
55
|
+
* the kind's top level; an id, a brain folder of its kind (the author's
|
|
56
|
+
* own folders still go below it). The rest of the bundle lands in place. */
|
|
57
|
+
folderId?: string | null;
|
|
40
58
|
/** A client's item at client or public: the admin confirmed the level. */
|
|
41
59
|
lowerConfirmed?: boolean;
|
|
42
60
|
/** Ids of closure items the admin ticked (with `lowerConfirmed: true`).
|
package/src/index.ts
CHANGED
|
@@ -40,6 +40,7 @@ export type {
|
|
|
40
40
|
AccessLoweredView,
|
|
41
41
|
AccessNodeUpdate,
|
|
42
42
|
AccessNodeView,
|
|
43
|
+
AccessSharedVia,
|
|
43
44
|
ClientOldLinkAbove,
|
|
44
45
|
ClientReport,
|
|
45
46
|
ClientReportAck,
|
|
@@ -52,6 +53,7 @@ export type {
|
|
|
52
53
|
} from './dto/access';
|
|
53
54
|
export type {
|
|
54
55
|
AcceptConfirmLevelRefusal,
|
|
56
|
+
AcceptPlace,
|
|
55
57
|
AcceptPreview,
|
|
56
58
|
AcceptRequest,
|
|
57
59
|
ReviewAuthorRole,
|
|
@@ -171,3 +173,26 @@ export type {
|
|
|
171
173
|
AppOpenStat,
|
|
172
174
|
AppTint,
|
|
173
175
|
} from './app-nav';
|
|
176
|
+
|
|
177
|
+
// Types only at the root; the runtime constants (kinds, specs, limits) are the
|
|
178
|
+
// `@mantle/client-types/tree` subpath.
|
|
179
|
+
export type {
|
|
180
|
+
ClientTreeFolder,
|
|
181
|
+
ClientTreeFolderPage,
|
|
182
|
+
ClientTreeItem,
|
|
183
|
+
ClientTreeSearchResult,
|
|
184
|
+
TreeCrumb,
|
|
185
|
+
TreeFolder,
|
|
186
|
+
TreeFolderPage,
|
|
187
|
+
TreeItem,
|
|
188
|
+
TreeItemState,
|
|
189
|
+
TreeKind,
|
|
190
|
+
TreeKindSpec,
|
|
191
|
+
TreeMarkList,
|
|
192
|
+
TreeMarkView,
|
|
193
|
+
TreeSearchResult,
|
|
194
|
+
TreeShareLevel,
|
|
195
|
+
TreeSort,
|
|
196
|
+
TreeVisibilityChange,
|
|
197
|
+
TreeVisibilityRefusal,
|
|
198
|
+
} from './tree';
|
package/src/tree.ts
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The item tree: one navigation for every workspace kind (docs/folder-tree.md).
|
|
3
|
+
*
|
|
4
|
+
* A kind's items live under the kind's root (`files`, `notes`, ...). Folders
|
|
5
|
+
* are `branch` rows under that root and nest at most TREE_MAX_DEPTH deep; an
|
|
6
|
+
* item's location is its folder's path, and an item at the root is unsorted.
|
|
7
|
+
* Items are never parents. Every reader (admin, member, client) browses the
|
|
8
|
+
* same tree, pruned by the brain to what that reader may see.
|
|
9
|
+
*
|
|
10
|
+
* Pure (no runtime deps) so the brain and every client share the kinds, the
|
|
11
|
+
* limits and the wire shapes. A runtime constant module, so it is the
|
|
12
|
+
* `@mantle/client-types/tree` subpath.
|
|
13
|
+
*/
|
|
14
|
+
import type { AccessLevel } from './dto/access';
|
|
15
|
+
import type { AppTint } from './app-nav';
|
|
16
|
+
|
|
17
|
+
/** The kinds the tree serves, in navigation order. */
|
|
18
|
+
export const TREE_KINDS = [
|
|
19
|
+
'files',
|
|
20
|
+
'notes',
|
|
21
|
+
'pages',
|
|
22
|
+
'draw',
|
|
23
|
+
'tables',
|
|
24
|
+
'formulas',
|
|
25
|
+
'apps',
|
|
26
|
+
'tasks',
|
|
27
|
+
'events',
|
|
28
|
+
'contacts',
|
|
29
|
+
'secrets',
|
|
30
|
+
// Recall v2's maps (its own content type, not pages). Appended last so no
|
|
31
|
+
// earlier kind's index moves. Not served until the Recall screen ships.
|
|
32
|
+
'recall',
|
|
33
|
+
] as const;
|
|
34
|
+
export type TreeKind = (typeof TREE_KINDS)[number];
|
|
35
|
+
|
|
36
|
+
export function isTreeKind(v: unknown): v is TreeKind {
|
|
37
|
+
return typeof v === 'string' && (TREE_KINDS as readonly string[]).includes(v);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Folders nest at most this deep below a kind's root: folder › subfolder ›
|
|
41
|
+
* sub-subfolder. Items sit in any of them. */
|
|
42
|
+
export const TREE_MAX_DEPTH = 3;
|
|
43
|
+
/** Items per folder page; the client asks for the next page as it scrolls. */
|
|
44
|
+
export const TREE_PAGE_SIZE = 50;
|
|
45
|
+
export const TREE_PAGE_MAX = 100;
|
|
46
|
+
export const TREE_FOLDER_NAME_MAX = 60;
|
|
47
|
+
export const TREE_SEARCH_MAX = 200;
|
|
48
|
+
|
|
49
|
+
/** The orders a folder's items can be listed in. Folders always keep their
|
|
50
|
+
* own manual order (rank, then name). */
|
|
51
|
+
export const TREE_SORTS = ['name', 'updated', 'start', 'due'] as const;
|
|
52
|
+
export type TreeSort = (typeof TREE_SORTS)[number];
|
|
53
|
+
|
|
54
|
+
export function isTreeSort(v: unknown): v is TreeSort {
|
|
55
|
+
return typeof v === 'string' && (TREE_SORTS as readonly string[]).includes(v);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Who a shared folder shares its contents with. Public is never a folder
|
|
59
|
+
* share: an item goes public through its own link. */
|
|
60
|
+
export const TREE_SHARE_LEVELS = ['team', 'client'] as const;
|
|
61
|
+
export type TreeShareLevel = (typeof TREE_SHARE_LEVELS)[number];
|
|
62
|
+
|
|
63
|
+
/** Everything that differs between kinds, in one place. The brain adds its
|
|
64
|
+
* own hooks (the files disk mirror); a client reads the rest. */
|
|
65
|
+
export type TreeKindSpec = {
|
|
66
|
+
kind: TreeKind;
|
|
67
|
+
/** The ltree root label of the kind (the `*_ROOT_LABEL` constants). */
|
|
68
|
+
root: string;
|
|
69
|
+
/** `nodes.type` of the kind's items. */
|
|
70
|
+
nodeType: string;
|
|
71
|
+
/** Whether a folder of this kind may be shared below admin. The kinds that
|
|
72
|
+
* are admin-only forever (the type ceiling) never are. */
|
|
73
|
+
shareable: boolean;
|
|
74
|
+
/** The item orders offered, the first being the default. */
|
|
75
|
+
sorts: readonly TreeSort[];
|
|
76
|
+
/** Which levels a folder of this kind may be shared at. Absent means both
|
|
77
|
+
* of TREE_SHARE_LEVELS. Recall is team-only: a client agent matching the
|
|
78
|
+
* owner's prompts is the case the Recall v1 plan deferred. Enforced where
|
|
79
|
+
* folders are shared (phase 4). */
|
|
80
|
+
shareLevels?: readonly TreeShareLevel[];
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
export const TREE_KIND_SPECS: Readonly<Record<TreeKind, TreeKindSpec>> = {
|
|
84
|
+
files: {
|
|
85
|
+
kind: 'files',
|
|
86
|
+
root: 'files',
|
|
87
|
+
nodeType: 'file',
|
|
88
|
+
shareable: true,
|
|
89
|
+
sorts: ['name', 'updated'],
|
|
90
|
+
},
|
|
91
|
+
notes: {
|
|
92
|
+
kind: 'notes',
|
|
93
|
+
root: 'notes',
|
|
94
|
+
nodeType: 'note',
|
|
95
|
+
shareable: true,
|
|
96
|
+
sorts: ['updated', 'name'],
|
|
97
|
+
},
|
|
98
|
+
pages: {
|
|
99
|
+
kind: 'pages',
|
|
100
|
+
root: 'pages',
|
|
101
|
+
nodeType: 'page',
|
|
102
|
+
shareable: true,
|
|
103
|
+
sorts: ['updated', 'name'],
|
|
104
|
+
},
|
|
105
|
+
draw: {
|
|
106
|
+
kind: 'draw',
|
|
107
|
+
root: 'draw',
|
|
108
|
+
nodeType: 'draw',
|
|
109
|
+
shareable: true,
|
|
110
|
+
sorts: ['updated', 'name'],
|
|
111
|
+
},
|
|
112
|
+
tables: {
|
|
113
|
+
kind: 'tables',
|
|
114
|
+
root: 'tables',
|
|
115
|
+
nodeType: 'table',
|
|
116
|
+
shareable: true,
|
|
117
|
+
sorts: ['updated', 'name'],
|
|
118
|
+
},
|
|
119
|
+
formulas: {
|
|
120
|
+
kind: 'formulas',
|
|
121
|
+
root: 'formulas',
|
|
122
|
+
nodeType: 'formula',
|
|
123
|
+
shareable: true,
|
|
124
|
+
sorts: ['name', 'updated'],
|
|
125
|
+
},
|
|
126
|
+
apps: {
|
|
127
|
+
kind: 'apps',
|
|
128
|
+
root: 'apps',
|
|
129
|
+
nodeType: 'app',
|
|
130
|
+
shareable: true,
|
|
131
|
+
sorts: ['name', 'updated'],
|
|
132
|
+
},
|
|
133
|
+
tasks: {
|
|
134
|
+
kind: 'tasks',
|
|
135
|
+
root: 'tasks',
|
|
136
|
+
nodeType: 'task',
|
|
137
|
+
shareable: false,
|
|
138
|
+
sorts: ['due', 'updated', 'name'],
|
|
139
|
+
},
|
|
140
|
+
events: {
|
|
141
|
+
kind: 'events',
|
|
142
|
+
root: 'events',
|
|
143
|
+
nodeType: 'event',
|
|
144
|
+
shareable: false,
|
|
145
|
+
sorts: ['start', 'name'],
|
|
146
|
+
},
|
|
147
|
+
contacts: {
|
|
148
|
+
kind: 'contacts',
|
|
149
|
+
root: 'contacts',
|
|
150
|
+
nodeType: 'contact',
|
|
151
|
+
shareable: false,
|
|
152
|
+
sorts: ['name', 'updated'],
|
|
153
|
+
},
|
|
154
|
+
secrets: {
|
|
155
|
+
kind: 'secrets',
|
|
156
|
+
root: 'secrets',
|
|
157
|
+
nodeType: 'secret',
|
|
158
|
+
shareable: false,
|
|
159
|
+
sorts: ['name', 'updated'],
|
|
160
|
+
},
|
|
161
|
+
// The tree lists maps only; a map's cards are recall_nodes rows, never
|
|
162
|
+
// nodes, so they never appear as items.
|
|
163
|
+
recall: {
|
|
164
|
+
kind: 'recall',
|
|
165
|
+
root: 'recall',
|
|
166
|
+
nodeType: 'recall',
|
|
167
|
+
shareable: true,
|
|
168
|
+
shareLevels: ['team'],
|
|
169
|
+
sorts: ['name', 'updated'],
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** One folder as the tree shows it. Counts are what THIS reader can see. */
|
|
174
|
+
export type TreeFolder = {
|
|
175
|
+
id: string;
|
|
176
|
+
/** The folder's ltree path: its identity in agent tools and on disk. */
|
|
177
|
+
path: string;
|
|
178
|
+
name: string;
|
|
179
|
+
/** An emoji or `lucide:<name>` (the app-nav icon vocabulary); null = the
|
|
180
|
+
* default folder glyph. */
|
|
181
|
+
icon: string | null;
|
|
182
|
+
color: AppTint | null;
|
|
183
|
+
/** 1..TREE_MAX_DEPTH below the kind's root. */
|
|
184
|
+
depth: number;
|
|
185
|
+
/** The parent folder; null at the top level. */
|
|
186
|
+
parentId: string | null;
|
|
187
|
+
/** The folder's own share (it shares everything below it); null = none. */
|
|
188
|
+
share: TreeShareLevel | null;
|
|
189
|
+
/** The share of the nearest shared folder above it; null = none. */
|
|
190
|
+
inherited?: TreeShareLevel | null;
|
|
191
|
+
/** Made by Mantle (Auto-filed): the name is locked and it cannot be shared. */
|
|
192
|
+
system: boolean;
|
|
193
|
+
folderCount: number;
|
|
194
|
+
itemCount: number;
|
|
195
|
+
/** A member's tree only: the member's own private folder (folder plan
|
|
196
|
+
* phase 5). Only they see it; they may rename, move and delete it. */
|
|
197
|
+
own?: boolean;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
/** An item's review state, when it has one (the one-list pill vocabulary). */
|
|
201
|
+
export type TreeItemState = 'private' | 'draft' | 'submitted' | 'returned' | 'with-admin';
|
|
202
|
+
|
|
203
|
+
/** Kind-specific facts a row draws: a task's done box and due date, an
|
|
204
|
+
* event's start. Only the kinds that have them send them. */
|
|
205
|
+
export type TreeItemMeta = {
|
|
206
|
+
/** A task marked done. */
|
|
207
|
+
done?: boolean;
|
|
208
|
+
/** A task's due instant (ISO), when it has one. */
|
|
209
|
+
due?: string | null;
|
|
210
|
+
/** An event's start (ISO). */
|
|
211
|
+
start?: string | null;
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** One item as the tree shows it: a title and what the status slot needs.
|
|
215
|
+
* Summaries, descriptions and tags live in the item's own view. */
|
|
216
|
+
export type TreeItem = {
|
|
217
|
+
id: string;
|
|
218
|
+
title: string;
|
|
219
|
+
/** The item's own icon when it has one (emoji or lucide key). */
|
|
220
|
+
icon: string | null;
|
|
221
|
+
color: AppTint | null;
|
|
222
|
+
/** A short kind-specific token for the status slot: a file's extension, a
|
|
223
|
+
* secret's kind, an event's start. Null when the kind has none. */
|
|
224
|
+
subtype: string | null;
|
|
225
|
+
/** Who can read it: its own level, or its folder's share when that is more
|
|
226
|
+
* open. */
|
|
227
|
+
level: AccessLevel;
|
|
228
|
+
/** The share it takes from the nearest shared folder holding it (what
|
|
229
|
+
* "Shared via" names); null = none. `level` already counts it. */
|
|
230
|
+
inherited?: TreeShareLevel | null;
|
|
231
|
+
state: TreeItemState | null;
|
|
232
|
+
updatedAt: string;
|
|
233
|
+
meta?: TreeItemMeta;
|
|
234
|
+
/** A member's tree only: a draft rather than a brain item (folder plan
|
|
235
|
+
* phase 5). `own` is the member's own; `team` a teammate's draft shared
|
|
236
|
+
* with the team. Absent on a brain item. */
|
|
237
|
+
source?: 'own' | 'team';
|
|
238
|
+
/** A teammate's draft: who wrote it. */
|
|
239
|
+
author?: string;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
export type TreeCrumb = { id: string; name: string };
|
|
243
|
+
|
|
244
|
+
/** GET /api/tree/:kind?folder= — one folder's direct subfolders (all, in
|
|
245
|
+
* order) and one page of its items. `folder` null is the kind's root. */
|
|
246
|
+
export type TreeFolderPage = {
|
|
247
|
+
kind: TreeKind;
|
|
248
|
+
folder: TreeFolder | null;
|
|
249
|
+
/** Top-down, excluding `folder` itself. */
|
|
250
|
+
crumbs: TreeCrumb[];
|
|
251
|
+
folders: TreeFolder[];
|
|
252
|
+
items: TreeItem[];
|
|
253
|
+
sort: TreeSort;
|
|
254
|
+
/** Pass back as `cursor` for the next page of items; null at the end. */
|
|
255
|
+
nextCursor: string | null;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/** Narrowing for GET /api/tree/:kind/search (`level=`, `tag=`), the tree's
|
|
259
|
+
* filter menu. Both optional; together they both apply. A filtered search
|
|
260
|
+
* lists items only, no folders. */
|
|
261
|
+
export type TreeFilter = {
|
|
262
|
+
/** Only items read at this level (their own, or their folder's share). */
|
|
263
|
+
level?: AccessLevel;
|
|
264
|
+
/** Only items carrying this tag. */
|
|
265
|
+
tag?: string;
|
|
266
|
+
};
|
|
267
|
+
export const TREE_TAG_MAX = 100;
|
|
268
|
+
|
|
269
|
+
/** GET /api/tree/:kind/tags — the tags on a kind's items, most used first,
|
|
270
|
+
* for the filter menu. At most TREE_TAGS_LIST_MAX. */
|
|
271
|
+
export type TreeTagList = { kind: TreeKind; tags: Array<{ tag: string; count: number }> };
|
|
272
|
+
export const TREE_TAGS_LIST_MAX = 40;
|
|
273
|
+
|
|
274
|
+
/** GET /api/tree/:kind/search?q= — matching folders first, then items, each
|
|
275
|
+
* with the crumbs of where it lives. */
|
|
276
|
+
export type TreeSearchResult = {
|
|
277
|
+
kind: TreeKind;
|
|
278
|
+
folders: Array<TreeFolder & { crumbs: TreeCrumb[] }>;
|
|
279
|
+
items: Array<TreeItem & { crumbs: TreeCrumb[] }>;
|
|
280
|
+
nextCursor: string | null;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/** The flat views beside the tree: this login's pins, recent and most used
|
|
284
|
+
* items of a kind. */
|
|
285
|
+
export const TREE_MARK_VIEWS = ['pinned', 'recent', 'used'] as const;
|
|
286
|
+
export type TreeMarkView = (typeof TREE_MARK_VIEWS)[number];
|
|
287
|
+
export const TREE_PINS_MAX = 12;
|
|
288
|
+
|
|
289
|
+
export type TreeMarkList = {
|
|
290
|
+
kind: TreeKind;
|
|
291
|
+
view: TreeMarkView;
|
|
292
|
+
items: Array<TreeItem & { crumbs: TreeCrumb[] }>;
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
/** A write that would change who can see something is refused with this
|
|
296
|
+
* (409) until it is repeated with `confirm: true`. */
|
|
297
|
+
export type TreeVisibilityChange = {
|
|
298
|
+
id: string;
|
|
299
|
+
title: string;
|
|
300
|
+
from: AccessLevel;
|
|
301
|
+
to: AccessLevel;
|
|
302
|
+
};
|
|
303
|
+
export type TreeVisibilityRefusal = {
|
|
304
|
+
error: 'visibility';
|
|
305
|
+
/** The first changes (at most TREE_VISIBILITY_LIST_MAX). */
|
|
306
|
+
changes: TreeVisibilityChange[];
|
|
307
|
+
/** How many items change in all. */
|
|
308
|
+
total: number;
|
|
309
|
+
};
|
|
310
|
+
export const TREE_VISIBILITY_LIST_MAX = 100;
|
|
311
|
+
|
|
312
|
+
// ── The member and client trees ──────────────────────────────────────────
|
|
313
|
+
// A member (team) or client login browses the kinds its Library holds as a
|
|
314
|
+
// read-only tree: GET /api/member/tree/:kind and /api/client/tree/:kind (with
|
|
315
|
+
// /search), the kinds its shell lists in `treeKinds`. A member receives the
|
|
316
|
+
// shapes above (levels are staff information); a client receives these,
|
|
317
|
+
// which carry no level, share or system flag (clients see the brand, not the
|
|
318
|
+
// staff view). Folders show where the reader reads something below them or
|
|
319
|
+
// where a folder's share covers the reader; counts are the reader's.
|
|
320
|
+
|
|
321
|
+
/** A folder as a client login's tree shows it. */
|
|
322
|
+
export type ClientTreeFolder = Omit<TreeFolder, 'share' | 'inherited' | 'system' | 'own'>;
|
|
323
|
+
/** An item as a client login's tree shows it. */
|
|
324
|
+
export type ClientTreeItem = Omit<TreeItem, 'level' | 'inherited' | 'state' | 'source' | 'author'>;
|
|
325
|
+
|
|
326
|
+
export type ClientTreeFolderPage = Omit<TreeFolderPage, 'folder' | 'folders' | 'items'> & {
|
|
327
|
+
folder: ClientTreeFolder | null;
|
|
328
|
+
folders: ClientTreeFolder[];
|
|
329
|
+
items: ClientTreeItem[];
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
export type ClientTreeSearchResult = {
|
|
333
|
+
kind: TreeKind;
|
|
334
|
+
folders: Array<ClientTreeFolder & { crumbs: TreeCrumb[] }>;
|
|
335
|
+
items: Array<ClientTreeItem & { crumbs: TreeCrumb[] }>;
|
|
336
|
+
nextCursor: string | null;
|
|
337
|
+
};
|
package/src/types/recall.ts
CHANGED
|
@@ -1,65 +1,133 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Wire shapes for the owner Recall API (
|
|
3
|
-
*
|
|
4
|
-
* tasks 073b322d / 91c93428; docs/recall.md in the mantle repo).
|
|
2
|
+
* Wire shapes for the owner Recall API (roadmap tasks 073b322d / 91c93428;
|
|
3
|
+
* docs/recall.md in the mantle repo). Dates are ISO strings.
|
|
5
4
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* ── Two kinds of map ───────────────────────────────────────────────────────
|
|
6
|
+
* v1 maps are COMPILED from a page tree: the rows are a build artifact, and a
|
|
7
|
+
* card's `id` is its source page's node id, so every row is a click-through to
|
|
8
|
+
* the page editor. v2 maps are NATIVE: the map is one `recall` item in the
|
|
9
|
+
* tree, its cards are rows written directly, and a card's `id` is its own —
|
|
10
|
+
* NOT a page. A client must not link a card to the page editor unless the map
|
|
11
|
+
* is page-built, which `nodeId === null` tells it.
|
|
12
|
+
*
|
|
13
|
+
* Plan: "PLAN: Recall v2, its own content type" (dev brain, task 5d6ce06a).
|
|
14
|
+
* Everything v2 adds here is additive, so a client written against the v1
|
|
15
|
+
* shapes keeps compiling and keeps working against a v1 map.
|
|
16
|
+
*
|
|
17
|
+
* Whether a brain can serve the native path at all is `features.recallV2` from
|
|
18
|
+
* `GET /api/shell`, absent on an older brain. Branch the SCREEN on that, not
|
|
19
|
+
* on the shapes below.
|
|
9
20
|
*/
|
|
10
21
|
|
|
11
22
|
export type RecallLintSeverity = 'error' | 'warning';
|
|
12
23
|
|
|
13
|
-
/** One issue from a map's last compile report
|
|
24
|
+
/** One issue from a map's last compile report (v1), or one warning from a
|
|
25
|
+
* native write (v2). */
|
|
14
26
|
export interface RecallLintIssueDTO {
|
|
15
27
|
severity: RecallLintSeverity;
|
|
16
28
|
code: string;
|
|
17
29
|
message: string;
|
|
18
|
-
/**
|
|
30
|
+
/** v1 only: the page the issue is about — the page-editor click-through. */
|
|
19
31
|
pageId?: string;
|
|
32
|
+
/** v2: the card the warning is about, by slug. A native map has no pages,
|
|
33
|
+
* and a write that fails a CHECK is refused rather than reported here —
|
|
34
|
+
* so on a native map these are only ever warnings (an orphan card, an
|
|
35
|
+
* entry card with no options yet). */
|
|
36
|
+
cardSlug?: string;
|
|
20
37
|
}
|
|
21
38
|
|
|
22
39
|
/** One map in the catalog (`GET /api/recall/maps`). Unlike the agent-facing
|
|
23
40
|
* `recall_index`, the owner catalog includes never-compiled maps
|
|
24
41
|
* (`nodeCount` 0) — a failed compile is exactly what the owner must see. */
|
|
25
42
|
export interface RecallMapSummaryDTO {
|
|
26
|
-
/**
|
|
43
|
+
/** v1: the map root page's node id — a map IS its root page. v2: the map's
|
|
44
|
+
* own id, which equals `nodeId`. Either way this is what the other Recall
|
|
45
|
+
* routes take as `:id`. */
|
|
27
46
|
id: string;
|
|
28
47
|
slug: string;
|
|
29
48
|
title: string;
|
|
30
49
|
/** The catalog line: when an agent should enter this map. */
|
|
31
50
|
enterWhen: string;
|
|
32
|
-
/**
|
|
51
|
+
/** Card count; 0 = a v1 map that never compiled clean. */
|
|
33
52
|
nodeCount: number;
|
|
34
|
-
/**
|
|
53
|
+
/** v1 only: false = the served rows are one rev behind the pages, and
|
|
54
|
+
* `report` says why. Always true for a native map, which cannot be stale:
|
|
55
|
+
* its rows are the source, and a write that fails its checks is refused. */
|
|
35
56
|
lastCompileOk: boolean;
|
|
57
|
+
/** v2: the map's `recall` item in the tree. NULL means this map is still
|
|
58
|
+
* page-built, which is also the flag for "link its cards to the page
|
|
59
|
+
* editor". */
|
|
60
|
+
nodeId: string | null;
|
|
61
|
+
/** v2: where the owner filed it, as display crumbs ("Mantle / Fleet");
|
|
62
|
+
* null when unsorted or page-built. Derived from the item's folder, so it
|
|
63
|
+
* is a label to show, never something to write back. */
|
|
64
|
+
folder: string | null;
|
|
65
|
+
/** v2: false while a map an AGENT created waits for the owner to publish
|
|
66
|
+
* it. An unpublished map is invisible to every agent-facing tool, so the
|
|
67
|
+
* owner UI is the only place it can be seen — show it as a draft. */
|
|
68
|
+
published: boolean;
|
|
69
|
+
/** v2: optimistic-concurrency token. Send it back on a write; a stale one
|
|
70
|
+
* is refused rather than silently overwriting someone else's edit. */
|
|
71
|
+
version: number;
|
|
36
72
|
updatedAt: string;
|
|
37
73
|
}
|
|
38
74
|
|
|
39
|
-
/** One routing edge
|
|
75
|
+
/** One routing edge: an affordance ("use when …"), never a command. */
|
|
40
76
|
export interface RecallOptionDTO {
|
|
41
77
|
label: string;
|
|
42
78
|
useWhen: string;
|
|
43
|
-
/** Slug of the target
|
|
79
|
+
/** Slug of the target card, within this map unless `targetMap` is set. */
|
|
44
80
|
targetSlug: string;
|
|
81
|
+
/** v2: the target card's id. Draw the graph from this, not from the slug —
|
|
82
|
+
* it survives a rename. Absent on a v1-compiled row. */
|
|
83
|
+
targetId?: string;
|
|
84
|
+
/** v2: a CROSS-MAP option. The slug of another published map, whose entry
|
|
85
|
+
* card this option leads to. Absent for an ordinary same-map edge. */
|
|
86
|
+
targetMap?: string;
|
|
45
87
|
}
|
|
46
88
|
|
|
47
89
|
export interface RecallNodeDTO {
|
|
48
|
-
/**
|
|
90
|
+
/** v1: the source page's node id, the page-editor click-through target.
|
|
91
|
+
* v2: the card's own id. It is NOT a page — see the file header. */
|
|
49
92
|
id: string;
|
|
50
93
|
slug: string;
|
|
51
94
|
kind: 'index' | 'knowledge' | 'prompt';
|
|
52
95
|
title: string;
|
|
53
96
|
/** Prompts: the matcher line; empty elsewhere. */
|
|
54
97
|
useWhen: string;
|
|
55
|
-
/**
|
|
98
|
+
/** Body size in characters (the budget is enforced on the write). The body
|
|
99
|
+
* itself is not in the list shape: a 100-card map would put most of a
|
|
100
|
+
* megabyte on the wire to render a sidebar. Fetch one card to edit it. */
|
|
56
101
|
bodyChars: number;
|
|
57
102
|
options: RecallOptionDTO[];
|
|
58
|
-
/** `pages.version` this row was compiled from
|
|
103
|
+
/** v1: the `pages.version` this row was compiled from. v2: the map version
|
|
104
|
+
* the card was written at. */
|
|
59
105
|
sourceVersion: number;
|
|
106
|
+
/** v2: the card's order in the editor. The list arrives sorted by it. */
|
|
107
|
+
rank: number;
|
|
108
|
+
/** v2: an AGENT asked for this card to be a prompt and the owner has not
|
|
109
|
+
* confirmed. Until they do it is not embedded and never matches, so show
|
|
110
|
+
* it as a request rather than as a prompt. */
|
|
111
|
+
promptPending: boolean;
|
|
60
112
|
updatedAt: string;
|
|
61
113
|
}
|
|
62
114
|
|
|
115
|
+
/** One card with its body — `GET /api/recall/maps/:id/cards/:cardId`, what
|
|
116
|
+
* the editor opens. Separate from the list shape on purpose (see
|
|
117
|
+
* `bodyChars`). */
|
|
118
|
+
export interface RecallCardDetailDTO extends RecallNodeDTO {
|
|
119
|
+
/** The markdown the owner edits, at most RECALL_BODY_CHAR_BUDGET characters
|
|
120
|
+
* (`@mantle/content-core/recall-compile`). */
|
|
121
|
+
bodyMd: string;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The caps a card and a map are held to are NOT redefined here: they already
|
|
125
|
+
* live in `@mantle/content-core/recall-compile`, which clients also consume —
|
|
126
|
+
* `RECALL_BODY_CHAR_BUDGET` (6000 characters of body) and
|
|
127
|
+
* `RECALL_MAX_MAP_NODES` (100 cards). The editor's counter and the brain's
|
|
128
|
+
* write check must read the same constant, or the counter promises room the
|
|
129
|
+
* write refuses. */
|
|
130
|
+
|
|
63
131
|
/** `GET /api/recall/maps/:id` — the whole compiled map, index node first,
|
|
64
132
|
* plus the last lint report (null when the last compile was clean). */
|
|
65
133
|
export interface RecallMapDetailDTO extends RecallMapSummaryDTO {
|
|
@@ -77,3 +145,114 @@ export interface RecallPageStateDTO {
|
|
|
77
145
|
node: { slug: string; kind: RecallNodeDTO['kind'] } | null;
|
|
78
146
|
report: RecallLintIssueDTO[] | null;
|
|
79
147
|
}
|
|
148
|
+
|
|
149
|
+
// ── v2: the write side ───────────────────────────────────────────────────────
|
|
150
|
+
// The owner routes the Recall editor calls. Every write carries the map
|
|
151
|
+
// `version` it was made against; a stale one is refused rather than silently
|
|
152
|
+
// overwriting another editor's (or an agent's) change. Nothing here exists on
|
|
153
|
+
// a v1 map: the page editor is its authoring surface.
|
|
154
|
+
|
|
155
|
+
/** Who made a change. An `agent` row is why the revision log exists: v2 serves
|
|
156
|
+
* an agent's card edit immediately, with no compile step to hold it back. */
|
|
157
|
+
export type RecallActorKind = 'owner' | 'agent';
|
|
158
|
+
|
|
159
|
+
/** `POST /api/recall/maps` — a new map, with its entry card. Created
|
|
160
|
+
* unpublished when an agent asks; the owner publishes it. */
|
|
161
|
+
export interface RecallMapCreateDTO {
|
|
162
|
+
title: string;
|
|
163
|
+
/** The catalog line: when an agent should enter this map. */
|
|
164
|
+
enterWhen: string;
|
|
165
|
+
/** Folder crumbs to file it under ("Mantle / Fleet"), or omit for unsorted.
|
|
166
|
+
* Folders must already exist; this does not create them. */
|
|
167
|
+
folder?: string;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** `POST /api/recall/maps` answers 201 with this. An owner-created map is
|
|
171
|
+
* published at once; only an agent's starts as a draft. */
|
|
172
|
+
export interface RecallMapCreateResultDTO {
|
|
173
|
+
mapId: string;
|
|
174
|
+
slug: string;
|
|
175
|
+
version: number;
|
|
176
|
+
published: boolean;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The body of every refused Recall write. `error` is a sentence written to be
|
|
180
|
+
* shown as is: what failed and what to do. `code` is stable and absent only on
|
|
181
|
+
* a malformed request (a body that failed validation). A stale `version` is
|
|
182
|
+
* 409 with code `version_stale`; any code ending `_not_found` is 404; every
|
|
183
|
+
* other refusal is 400. */
|
|
184
|
+
export interface RecallWriteErrorDTO {
|
|
185
|
+
error: string;
|
|
186
|
+
code?: string;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** `PATCH /api/recall/maps/:id` — only the fields present are changed.
|
|
190
|
+
* Renaming does NOT change the slug: agents and skills remember slugs. */
|
|
191
|
+
export interface RecallMapPatchDTO {
|
|
192
|
+
title?: string;
|
|
193
|
+
enterWhen?: string;
|
|
194
|
+
/** An explicit slug change. The old slug is kept and keeps resolving. */
|
|
195
|
+
slug?: string;
|
|
196
|
+
/** Publish (or unpublish) the map. Publishing is what makes it visible to
|
|
197
|
+
* every agent-facing tool. */
|
|
198
|
+
published?: boolean;
|
|
199
|
+
version: number;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** `PUT /api/recall/maps/:id/cards/:cardId` — create or replace one card.
|
|
203
|
+
* `options` replaces the card's whole list, so read-modify-write it. */
|
|
204
|
+
export interface RecallCardWriteDTO {
|
|
205
|
+
title: string;
|
|
206
|
+
/** Markdown, at most RECALL_BODY_CHAR_BUDGET characters. */
|
|
207
|
+
bodyMd: string;
|
|
208
|
+
/** The matcher line. Required when `prompt` is true: a prompt without one
|
|
209
|
+
* cannot be matched by meaning, which is the only thing prompts are for. */
|
|
210
|
+
useWhen?: string;
|
|
211
|
+
/** Ask for this card to be a prompt. From the OWNER this makes it one; from
|
|
212
|
+
* an agent it only records the request (`promptPending`). */
|
|
213
|
+
prompt?: boolean;
|
|
214
|
+
options?: RecallOptionDTO[];
|
|
215
|
+
/** Slug of the card to place this one after, for a new card. */
|
|
216
|
+
after?: string;
|
|
217
|
+
version: number;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/** One advisory warning from a native write. Never blocks: an orphan card is
|
|
221
|
+
* a normal intermediate state while a map is being built. No `severity`,
|
|
222
|
+
* because a native write has only one kind of issue it reports rather than
|
|
223
|
+
* refuses. Codes today: `orphan_card`, `entry_without_options`. */
|
|
224
|
+
export interface RecallWarningDTO {
|
|
225
|
+
code: string;
|
|
226
|
+
message: string;
|
|
227
|
+
/** The card the warning is about, when it is about one. */
|
|
228
|
+
cardSlug?: string;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** What a write answers with. */
|
|
232
|
+
export interface RecallWriteResultDTO {
|
|
233
|
+
/** The map's new version. Carry it into the next write. */
|
|
234
|
+
version: number;
|
|
235
|
+
/** The slug of the card written, absent for a map-level write. The body is
|
|
236
|
+
* not echoed back: GET the card if the editor needs it again. */
|
|
237
|
+
cardSlug?: string;
|
|
238
|
+
warnings: RecallWarningDTO[];
|
|
239
|
+
/** Cards whose options pointed at a card this write deleted, and so had
|
|
240
|
+
* that option removed in the same transaction. */
|
|
241
|
+
optionsDropped?: { cardSlug: string; label: string }[];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** One entry in a map's revision log (`GET /api/recall/maps/:id/revisions`),
|
|
245
|
+
* newest first. Restore one with `POST /api/recall/revisions/:id/restore`. */
|
|
246
|
+
export interface RecallRevisionDTO {
|
|
247
|
+
id: string;
|
|
248
|
+
/** The card this touched; null for a map-level change. */
|
|
249
|
+
cardId: string | null;
|
|
250
|
+
/** The card's slug at the time, for display when the card is gone. */
|
|
251
|
+
cardSlug: string | null;
|
|
252
|
+
actorKind: RecallActorKind;
|
|
253
|
+
/** The agent's slug or the admin's display name, when known. */
|
|
254
|
+
actorName: string | null;
|
|
255
|
+
/** One line on what changed ("body edited", "card added", "published"). */
|
|
256
|
+
summary: string;
|
|
257
|
+
createdAt: string;
|
|
258
|
+
}
|