@ai-matrx/canvas 0.1.1 → 0.2.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.
- package/CHANGELOG.md +26 -0
- package/README.md +114 -4
- package/dist/controller-Dv6J6XZm.d.cts +576 -0
- package/dist/controller-Dv6J6XZm.d.ts +576 -0
- package/dist/index.cjs +743 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +118 -11
- package/dist/index.d.ts +118 -11
- package/dist/index.js +743 -104
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +1476 -406
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +242 -22
- package/dist/react.d.ts +242 -22
- package/dist/react.js +1486 -404
- package/dist/react.js.map +1 -1
- package/dist/styles.css +346 -51
- package/dist/tokens.css +58 -36
- package/package.json +1 -1
- package/dist/controller-oY84BN2c.d.cts +0 -345
- package/dist/controller-oY84BN2c.d.ts +0 -345
|
@@ -0,0 +1,576 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ai-matrx/canvas — core types.
|
|
3
|
+
*
|
|
4
|
+
* The canvas is ONE docked column on the right edge of an application. It holds
|
|
5
|
+
* panes; panes hold tabs; every tab is one CanvasItem. A CanvasItem is
|
|
6
|
+
* identified by `kind` + `key`, so opening the same thing twice always lands on
|
|
7
|
+
* the existing tab instead of creating a duplicate.
|
|
8
|
+
*
|
|
9
|
+
* Everything in CanvasState is plain JSON: it lives in the host's Redux store
|
|
10
|
+
* (or the package's own standalone store), it is persisted between sessions,
|
|
11
|
+
* and it can be inspected. No functions, no class instances, no React nodes.
|
|
12
|
+
*/
|
|
13
|
+
/** Any value that survives JSON.stringify → JSON.parse unchanged. */
|
|
14
|
+
type CanvasJson = string | number | boolean | null | readonly CanvasJson[] | {
|
|
15
|
+
readonly [key: string]: CanvasJson | undefined;
|
|
16
|
+
};
|
|
17
|
+
declare const itemIdBrand: unique symbol;
|
|
18
|
+
declare const paneIdBrand: unique symbol;
|
|
19
|
+
declare const splitIdBrand: unique symbol;
|
|
20
|
+
declare const windowIdBrand: unique symbol;
|
|
21
|
+
/** `${kind}::${key}` — the identity of one thing on the canvas. */
|
|
22
|
+
type CanvasItemId = string & {
|
|
23
|
+
readonly [itemIdBrand]: true;
|
|
24
|
+
};
|
|
25
|
+
type CanvasPaneId = string & {
|
|
26
|
+
readonly [paneIdBrand]: true;
|
|
27
|
+
};
|
|
28
|
+
type CanvasSplitId = string & {
|
|
29
|
+
readonly [splitIdBrand]: true;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* One application window that renders a canvas. Single-window hosts only ever
|
|
33
|
+
* see `CANVAS_MAIN_WINDOW`; a desktop host with one store in its main process
|
|
34
|
+
* gives every other window (a second app window, a popped-out tab) its own id.
|
|
35
|
+
*/
|
|
36
|
+
type CanvasWindowId = string & {
|
|
37
|
+
readonly [windowIdBrand]: true;
|
|
38
|
+
};
|
|
39
|
+
declare const CANVAS_MAIN_WINDOW: CanvasWindowId;
|
|
40
|
+
interface CanvasItem {
|
|
41
|
+
readonly id: CanvasItemId;
|
|
42
|
+
/** Registered kind id (see `registerCanvasKind`). */
|
|
43
|
+
readonly kind: string;
|
|
44
|
+
/** Stable key inside the kind — an artifact id, a conversation id, "default". */
|
|
45
|
+
readonly key: string;
|
|
46
|
+
/** Explicit tab title. `null` ⇒ the kind's `title(data)` or its label. */
|
|
47
|
+
readonly title: string | null;
|
|
48
|
+
readonly data: CanvasJson;
|
|
49
|
+
readonly openedAt: number;
|
|
50
|
+
readonly updatedAt: number;
|
|
51
|
+
}
|
|
52
|
+
interface CanvasPane {
|
|
53
|
+
readonly id: CanvasPaneId;
|
|
54
|
+
readonly itemIds: readonly CanvasItemId[];
|
|
55
|
+
readonly activeItemId: CanvasItemId | null;
|
|
56
|
+
}
|
|
57
|
+
/** "horizontal" = children side by side; "vertical" = children stacked. */
|
|
58
|
+
type CanvasOrientation = "horizontal" | "vertical";
|
|
59
|
+
type CanvasLayoutNode = {
|
|
60
|
+
readonly type: "pane";
|
|
61
|
+
readonly paneId: CanvasPaneId;
|
|
62
|
+
} | {
|
|
63
|
+
readonly type: "split";
|
|
64
|
+
readonly id: CanvasSplitId;
|
|
65
|
+
readonly orientation: CanvasOrientation;
|
|
66
|
+
readonly children: readonly CanvasLayoutNode[];
|
|
67
|
+
/** Fractions, one per child, summing to 1. */
|
|
68
|
+
readonly sizes: readonly number[];
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* What one window shows: its column's visibility, full screen, width and its
|
|
72
|
+
* own tree of panes. Panes and items are shared across windows (ids are
|
|
73
|
+
* global), so moving a tab between windows is an ordinary move.
|
|
74
|
+
*/
|
|
75
|
+
interface CanvasWindowFrame {
|
|
76
|
+
readonly isOpen: boolean;
|
|
77
|
+
/**
|
|
78
|
+
* "Expanded": the column fills everything the HOST gives it (the web: the
|
|
79
|
+
* whole window; the desktop: everything right of the sidebar). One flag —
|
|
80
|
+
* the host's placement decides what "everything" is.
|
|
81
|
+
*/
|
|
82
|
+
readonly isFullscreen: boolean;
|
|
83
|
+
/** Column width in CSS pixels; null until the person drags it — the host's layout rule decides. */
|
|
84
|
+
readonly width: number | null;
|
|
85
|
+
readonly layout: CanvasLayoutNode;
|
|
86
|
+
readonly focusedPaneId: CanvasPaneId;
|
|
87
|
+
}
|
|
88
|
+
/** A window other than the main one. */
|
|
89
|
+
interface CanvasExtraWindow extends CanvasWindowFrame {
|
|
90
|
+
readonly id: CanvasWindowId;
|
|
91
|
+
/**
|
|
92
|
+
* "window": another app window with its own column.
|
|
93
|
+
* "pop-out": a window holding tabs popped out of `home`; "Dock back" returns them there.
|
|
94
|
+
*/
|
|
95
|
+
readonly role: "window" | "pop-out";
|
|
96
|
+
/** Where this window's tabs go when it closes or a tab docks back. */
|
|
97
|
+
readonly home: CanvasWindowId;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* The whole canvas. The top-level frame fields ARE the main window's frame, so
|
|
101
|
+
* a single-window host reads `state.isOpen`, `state.layout`… exactly as before;
|
|
102
|
+
* every other window lives in `windows`.
|
|
103
|
+
*/
|
|
104
|
+
interface CanvasState extends CanvasWindowFrame {
|
|
105
|
+
readonly version: 1;
|
|
106
|
+
/** Windows other than the main one. Empty for single-window hosts. Never restored after a reload. */
|
|
107
|
+
readonly windows: {
|
|
108
|
+
readonly [windowId: string]: CanvasExtraWindow;
|
|
109
|
+
};
|
|
110
|
+
readonly panes: {
|
|
111
|
+
readonly [paneId: string]: CanvasPane;
|
|
112
|
+
};
|
|
113
|
+
readonly items: {
|
|
114
|
+
readonly [itemId: string]: CanvasItem;
|
|
115
|
+
};
|
|
116
|
+
/** Monotonic counter for minting pane/split ids — keeps the reducer pure. */
|
|
117
|
+
readonly seq: number;
|
|
118
|
+
/** True once a persisted snapshot was applied (or there was none). */
|
|
119
|
+
readonly hydrated: boolean;
|
|
120
|
+
}
|
|
121
|
+
/** Where a newly opened item goes. Existing items never move on open. */
|
|
122
|
+
type CanvasOpenTarget = "focused" | "split-right" | "split-down" | {
|
|
123
|
+
readonly paneId: CanvasPaneId;
|
|
124
|
+
};
|
|
125
|
+
interface CanvasOpenInput {
|
|
126
|
+
readonly kind: string;
|
|
127
|
+
/** Which window a NEW item opens in. Default: the controller's window (main). An existing item stays where it is. */
|
|
128
|
+
readonly windowId?: CanvasWindowId | undefined;
|
|
129
|
+
readonly key: string;
|
|
130
|
+
readonly title?: string | null | undefined;
|
|
131
|
+
readonly data?: CanvasJson | undefined;
|
|
132
|
+
readonly target?: CanvasOpenTarget | undefined;
|
|
133
|
+
/** Reveal the canvas when it is put away. Default true. */
|
|
134
|
+
readonly reveal?: boolean | undefined;
|
|
135
|
+
/** Make the item the pane's active tab. Default true. */
|
|
136
|
+
readonly activate?: boolean | undefined;
|
|
137
|
+
}
|
|
138
|
+
declare const CANVAS_MIN_WIDTH = 360;
|
|
139
|
+
declare const CANVAS_DEFAULT_WIDTH = 640;
|
|
140
|
+
declare const CANVAS_MIN_SPLIT_FRACTION = 0.12;
|
|
141
|
+
/** A rectangle in window CSS pixels (getBoundingClientRect coordinates). */
|
|
142
|
+
interface CanvasRect {
|
|
143
|
+
readonly x: number;
|
|
144
|
+
readonly y: number;
|
|
145
|
+
readonly width: number;
|
|
146
|
+
readonly height: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* THE canvas reducer. Plain Redux-compatible reducer + action creators with no
|
|
151
|
+
* Redux Toolkit dependency, so it mounts in a host's RTK store
|
|
152
|
+
* (`canvasHost: canvasReducer`), runs inside the package's own standalone
|
|
153
|
+
* store, or runs in a desktop main process behind IPC — identical behaviour.
|
|
154
|
+
*
|
|
155
|
+
* Identity law: an item is `kind::key`. Opening an item that is already on the
|
|
156
|
+
* canvas never duplicates it — it refreshes its data/title, activates its tab
|
|
157
|
+
* and focuses its pane, wherever that pane is (in whichever window).
|
|
158
|
+
*
|
|
159
|
+
* Windows: the top-level frame fields are the MAIN window's; every other window
|
|
160
|
+
* (a second app window, a popped-out tab) lives in `state.windows`. Panes and
|
|
161
|
+
* items are global, so a tab moves between windows like between panes.
|
|
162
|
+
*/
|
|
163
|
+
|
|
164
|
+
declare const P = "matrxCanvas/";
|
|
165
|
+
/** What happens to a closing window's tabs: back to its home window, or closed with it. */
|
|
166
|
+
type CanvasWindowCloseMode = "dock" | "close";
|
|
167
|
+
type CanvasAction = {
|
|
168
|
+
type: `${typeof P}open`;
|
|
169
|
+
payload: CanvasOpenInput & {
|
|
170
|
+
now: number;
|
|
171
|
+
splitShare?: number | undefined;
|
|
172
|
+
};
|
|
173
|
+
} | {
|
|
174
|
+
type: `${typeof P}update`;
|
|
175
|
+
payload: {
|
|
176
|
+
itemId: CanvasItemId;
|
|
177
|
+
data?: CanvasJson | undefined;
|
|
178
|
+
title?: string | null | undefined;
|
|
179
|
+
now: number;
|
|
180
|
+
};
|
|
181
|
+
} | {
|
|
182
|
+
type: `${typeof P}rekey`;
|
|
183
|
+
payload: {
|
|
184
|
+
itemId: CanvasItemId;
|
|
185
|
+
key: string;
|
|
186
|
+
};
|
|
187
|
+
} | {
|
|
188
|
+
type: `${typeof P}closeItem`;
|
|
189
|
+
payload: {
|
|
190
|
+
itemId: CanvasItemId;
|
|
191
|
+
};
|
|
192
|
+
} | {
|
|
193
|
+
type: `${typeof P}closeOthers`;
|
|
194
|
+
payload: {
|
|
195
|
+
itemId: CanvasItemId;
|
|
196
|
+
};
|
|
197
|
+
} | {
|
|
198
|
+
type: `${typeof P}activate`;
|
|
199
|
+
payload: {
|
|
200
|
+
itemId: CanvasItemId;
|
|
201
|
+
};
|
|
202
|
+
} | {
|
|
203
|
+
type: `${typeof P}focusPane`;
|
|
204
|
+
payload: {
|
|
205
|
+
paneId: CanvasPaneId;
|
|
206
|
+
};
|
|
207
|
+
} | {
|
|
208
|
+
type: `${typeof P}moveItem`;
|
|
209
|
+
payload: {
|
|
210
|
+
itemId: CanvasItemId;
|
|
211
|
+
toPaneId: CanvasPaneId;
|
|
212
|
+
index?: number | undefined;
|
|
213
|
+
};
|
|
214
|
+
} | {
|
|
215
|
+
type: `${typeof P}splitPane`;
|
|
216
|
+
payload: {
|
|
217
|
+
paneId: CanvasPaneId;
|
|
218
|
+
orientation: CanvasOrientation;
|
|
219
|
+
moveItemId?: CanvasItemId | undefined;
|
|
220
|
+
share?: number | undefined;
|
|
221
|
+
};
|
|
222
|
+
} | {
|
|
223
|
+
type: `${typeof P}closePane`;
|
|
224
|
+
payload: {
|
|
225
|
+
paneId: CanvasPaneId;
|
|
226
|
+
};
|
|
227
|
+
} | {
|
|
228
|
+
type: `${typeof P}resizeSplit`;
|
|
229
|
+
payload: {
|
|
230
|
+
splitId: CanvasSplitId;
|
|
231
|
+
sizes: readonly number[];
|
|
232
|
+
};
|
|
233
|
+
} | {
|
|
234
|
+
type: `${typeof P}setOpen`;
|
|
235
|
+
payload: {
|
|
236
|
+
open: boolean;
|
|
237
|
+
windowId?: CanvasWindowId | undefined;
|
|
238
|
+
};
|
|
239
|
+
} | {
|
|
240
|
+
type: `${typeof P}toggle`;
|
|
241
|
+
payload?: {
|
|
242
|
+
windowId?: CanvasWindowId | undefined;
|
|
243
|
+
} | undefined;
|
|
244
|
+
} | {
|
|
245
|
+
type: `${typeof P}setFullscreen`;
|
|
246
|
+
payload: {
|
|
247
|
+
fullscreen: boolean;
|
|
248
|
+
windowId?: CanvasWindowId | undefined;
|
|
249
|
+
};
|
|
250
|
+
} | {
|
|
251
|
+
type: `${typeof P}setWidth`;
|
|
252
|
+
payload: {
|
|
253
|
+
width: number;
|
|
254
|
+
windowId?: CanvasWindowId | undefined;
|
|
255
|
+
};
|
|
256
|
+
} | {
|
|
257
|
+
type: `${typeof P}popOut`;
|
|
258
|
+
payload: {
|
|
259
|
+
itemId: CanvasItemId;
|
|
260
|
+
windowId: CanvasWindowId;
|
|
261
|
+
};
|
|
262
|
+
} | {
|
|
263
|
+
type: `${typeof P}dockBack`;
|
|
264
|
+
payload: {
|
|
265
|
+
itemId: CanvasItemId;
|
|
266
|
+
};
|
|
267
|
+
} | {
|
|
268
|
+
type: `${typeof P}closeWindow`;
|
|
269
|
+
payload: {
|
|
270
|
+
windowId: CanvasWindowId;
|
|
271
|
+
mode: CanvasWindowCloseMode;
|
|
272
|
+
};
|
|
273
|
+
} | {
|
|
274
|
+
type: `${typeof P}hydrate`;
|
|
275
|
+
payload: {
|
|
276
|
+
snapshot: CanvasState | null;
|
|
277
|
+
};
|
|
278
|
+
} | {
|
|
279
|
+
type: `${typeof P}reset`;
|
|
280
|
+
};
|
|
281
|
+
declare const canvasActions: {
|
|
282
|
+
readonly open: (input: CanvasOpenInput, options?: {
|
|
283
|
+
splitShare?: number | undefined;
|
|
284
|
+
}) => CanvasAction;
|
|
285
|
+
readonly update: (itemId: CanvasItemId, patch: {
|
|
286
|
+
data?: CanvasJson | undefined;
|
|
287
|
+
title?: string | null | undefined;
|
|
288
|
+
}) => CanvasAction;
|
|
289
|
+
/** Gives an item a new identity in place (e.g. a draft that was just saved and now has an id). */
|
|
290
|
+
readonly rekey: (itemId: CanvasItemId, key: string) => CanvasAction;
|
|
291
|
+
readonly closeItem: (itemId: CanvasItemId) => CanvasAction;
|
|
292
|
+
readonly closeOthers: (itemId: CanvasItemId) => CanvasAction;
|
|
293
|
+
readonly activate: (itemId: CanvasItemId) => CanvasAction;
|
|
294
|
+
readonly focusPane: (paneId: CanvasPaneId) => CanvasAction;
|
|
295
|
+
readonly moveItem: (itemId: CanvasItemId, toPaneId: CanvasPaneId, index?: number) => CanvasAction;
|
|
296
|
+
/** `share` = the existing pane's fraction of the space (layout rules' stackSplit / sideSplit). */
|
|
297
|
+
readonly splitPane: (paneId: CanvasPaneId, orientation: CanvasOrientation, moveItemId?: CanvasItemId, share?: number) => CanvasAction;
|
|
298
|
+
readonly closePane: (paneId: CanvasPaneId) => CanvasAction;
|
|
299
|
+
readonly resizeSplit: (splitId: CanvasSplitId, sizes: readonly number[]) => CanvasAction;
|
|
300
|
+
readonly setOpen: (open: boolean, windowId?: CanvasWindowId) => CanvasAction;
|
|
301
|
+
readonly toggle: (windowId?: CanvasWindowId) => CanvasAction;
|
|
302
|
+
readonly setFullscreen: (fullscreen: boolean, windowId?: CanvasWindowId) => CanvasAction;
|
|
303
|
+
readonly setWidth: (width: number, windowId?: CanvasWindowId) => CanvasAction;
|
|
304
|
+
/** Moves a tab into its own pop-out window `windowId` (made by the host's popOut port). */
|
|
305
|
+
readonly popOut: (itemId: CanvasItemId, windowId: CanvasWindowId) => CanvasAction;
|
|
306
|
+
/** Returns a popped-out tab to the window it came from. */
|
|
307
|
+
readonly dockBack: (itemId: CanvasItemId) => CanvasAction;
|
|
308
|
+
/** A host window closed: its tabs dock home (default) or close with it. The main window never closes. */
|
|
309
|
+
readonly closeWindow: (windowId: CanvasWindowId, mode?: CanvasWindowCloseMode) => CanvasAction;
|
|
310
|
+
readonly hydrate: (snapshot: CanvasState | null) => CanvasAction;
|
|
311
|
+
readonly reset: () => CanvasAction;
|
|
312
|
+
};
|
|
313
|
+
declare function isCanvasAction(action: unknown): action is CanvasAction;
|
|
314
|
+
declare function createInitialCanvasState(): CanvasState;
|
|
315
|
+
/** Every window id, main first. */
|
|
316
|
+
declare function listCanvasWindowIds(state: CanvasState): CanvasWindowId[];
|
|
317
|
+
/** A window's frame, or null when that window does not exist. */
|
|
318
|
+
declare function canvasWindowFrame(state: CanvasState, windowId?: CanvasWindowId): CanvasWindowFrame | null;
|
|
319
|
+
/** The window whose layout holds this pane. */
|
|
320
|
+
declare function canvasWindowOfPane(state: CanvasState, paneId: CanvasPaneId): CanvasWindowId | null;
|
|
321
|
+
/** The window an item is in right now (null when it is not on the canvas). */
|
|
322
|
+
declare function canvasWindowOfItem(state: CanvasState, itemId: CanvasItemId): CanvasWindowId | null;
|
|
323
|
+
/**
|
|
324
|
+
* Every window but the main one is a session thing: its tabs come home to the
|
|
325
|
+
* main window's focused pane (nothing is lost) and the window goes away. Used
|
|
326
|
+
* for what is saved and for what is restored.
|
|
327
|
+
*/
|
|
328
|
+
declare function foldCanvasWindows(state: CanvasState): CanvasState;
|
|
329
|
+
/**
|
|
330
|
+
* Validates a persisted snapshot. Anything malformed is dropped back to the
|
|
331
|
+
* initial state rather than half-applied — a corrupt layout must never crash
|
|
332
|
+
* the shell. Extra windows are folded home; full screen never comes back.
|
|
333
|
+
*/
|
|
334
|
+
declare function sanitizeCanvasSnapshot(raw: unknown): CanvasState | null;
|
|
335
|
+
declare function canvasReducer(state: CanvasState | undefined, action: {
|
|
336
|
+
type: string;
|
|
337
|
+
}): CanvasState;
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* The store seam. The canvas never owns a second copy of its state: it reads
|
|
341
|
+
* and writes through a CanvasStoreBinding.
|
|
342
|
+
*
|
|
343
|
+
* - A host WITH Redux mounts `canvasReducer` in its root reducer and binds it:
|
|
344
|
+
* bindCanvasToReduxStore(store, (root) => root.canvasHost)
|
|
345
|
+
* - A host WITHOUT Redux (a Vite tool, an Electron window) calls
|
|
346
|
+
* createCanvasStore()
|
|
347
|
+
* which runs the very same reducer in a tiny standalone store.
|
|
348
|
+
*/
|
|
349
|
+
|
|
350
|
+
interface CanvasStoreBinding {
|
|
351
|
+
getState(): CanvasState;
|
|
352
|
+
dispatch(action: CanvasAction): void;
|
|
353
|
+
subscribe(listener: () => void): () => void;
|
|
354
|
+
/**
|
|
355
|
+
* "remote": the state lives in another process (createRemoteCanvasStore) —
|
|
356
|
+
* that process hydrates and saves it, so a controller over this binding
|
|
357
|
+
* never does. Absent means "local".
|
|
358
|
+
*/
|
|
359
|
+
readonly authority?: "local" | "remote";
|
|
360
|
+
}
|
|
361
|
+
declare function createCanvasStore(initial?: CanvasState): CanvasStoreBinding;
|
|
362
|
+
/** The minimum of a Redux store the canvas needs. */
|
|
363
|
+
interface ReduxStoreLike<TRoot> {
|
|
364
|
+
getState(): TRoot;
|
|
365
|
+
dispatch(action: CanvasAction): unknown;
|
|
366
|
+
subscribe(listener: () => void): () => void;
|
|
367
|
+
}
|
|
368
|
+
declare function bindCanvasToReduxStore<TRoot>(store: ReduxStoreLike<TRoot>, select: (root: TRoot) => CanvasState): CanvasStoreBinding;
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Remembering the canvas between sessions — a table stake, never optional.
|
|
372
|
+
*
|
|
373
|
+
* The default port writes to localStorage under a versioned key. Items whose
|
|
374
|
+
* kind opts out (`restore: false`, e.g. a live session that cannot come back)
|
|
375
|
+
* are dropped from the snapshot, and panes they leave empty are dropped too.
|
|
376
|
+
*/
|
|
377
|
+
|
|
378
|
+
interface CanvasPersistencePort {
|
|
379
|
+
load(): CanvasState | null | Promise<CanvasState | null>;
|
|
380
|
+
save(snapshot: CanvasState): void | Promise<void>;
|
|
381
|
+
}
|
|
382
|
+
declare const CANVAS_STORAGE_KEY = "ai-matrx.canvas.v1";
|
|
383
|
+
declare function createLocalStorageCanvasPersistence(key?: string, storage?: Pick<Storage, "getItem" | "setItem"> | null): CanvasPersistencePort;
|
|
384
|
+
/**
|
|
385
|
+
* Memory over any key-value store — an Electron main process's `state/kv`,
|
|
386
|
+
* electron-store, IndexedDB behind a promise, a test map. Values are written
|
|
387
|
+
* as plain JSON objects (not strings) so a structured store keeps them
|
|
388
|
+
* inspectable; a string-only store can wrap this with JSON.stringify.
|
|
389
|
+
*/
|
|
390
|
+
interface CanvasKeyValueStore {
|
|
391
|
+
get(key: string): unknown;
|
|
392
|
+
set(key: string, value: unknown): void | Promise<void>;
|
|
393
|
+
}
|
|
394
|
+
declare function createKeyValueCanvasPersistence(kv: CanvasKeyValueStore, key?: string): CanvasPersistencePort;
|
|
395
|
+
/**
|
|
396
|
+
* Builds the snapshot that is actually written: only restorable items, and
|
|
397
|
+
* every window but the main one folded home (other windows never come back).
|
|
398
|
+
*/
|
|
399
|
+
declare function toPersistableSnapshot(state: CanvasState, isRestorable: (kind: string) => boolean): CanvasState;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Layout rules — the host-tunable numbers behind the column's width and how its
|
|
403
|
+
* panes share space. Pure functions, so a desktop main process can place native
|
|
404
|
+
* views with exactly the numbers the renderer draws with.
|
|
405
|
+
*
|
|
406
|
+
* The canvas always shrinks FIRST: while the window narrows, the centre keeps
|
|
407
|
+
* `centreMinWidth` and the canvas gives way down to `minWidth`; only then does
|
|
408
|
+
* the centre squeeze. Side-by-side panes narrower than `paneMinWidth` stack
|
|
409
|
+
* instead of squeezing.
|
|
410
|
+
*/
|
|
411
|
+
interface CanvasLayoutRules {
|
|
412
|
+
/** Width before the person drags the edge: CSS px, or a fraction of the region the canvas shares. */
|
|
413
|
+
readonly defaultWidth: number | {
|
|
414
|
+
readonly fraction: number;
|
|
415
|
+
};
|
|
416
|
+
/** The narrowest the canvas column gets on a desktop-sized window. */
|
|
417
|
+
readonly minWidth: number;
|
|
418
|
+
/** Room the content beside the canvas always keeps (the canvas shrinks first). */
|
|
419
|
+
readonly centreMinWidth: number;
|
|
420
|
+
/** The FIRST pane's share when a pane splits down (stacked). 0.7 = the 70/30 divider. */
|
|
421
|
+
readonly stackSplit: number;
|
|
422
|
+
/** The first pane's share when a pane splits right (side by side). */
|
|
423
|
+
readonly sideSplit: number;
|
|
424
|
+
/** Side-by-side panes that would be narrower than this render stacked. 0 = never. */
|
|
425
|
+
readonly paneMinWidth: number;
|
|
426
|
+
}
|
|
427
|
+
/** The web app's numbers (the defaults). */
|
|
428
|
+
declare const CANVAS_WEB_LAYOUT_RULES: CanvasLayoutRules;
|
|
429
|
+
/** The desktop app's numbers (Claude desktop's proportions). */
|
|
430
|
+
declare const CANVAS_DESKTOP_LAYOUT_RULES: CanvasLayoutRules;
|
|
431
|
+
/** Fills missing fields from the web defaults and repairs nonsense values. */
|
|
432
|
+
declare function resolveCanvasLayoutRules(rules?: Partial<CanvasLayoutRules> | null): CanvasLayoutRules;
|
|
433
|
+
/** The width before the person chose one. `region` is the space the canvas shares (0 = unknown). */
|
|
434
|
+
declare function defaultCanvasWidth(rules: CanvasLayoutRules, region: number): number;
|
|
435
|
+
/**
|
|
436
|
+
* The column's width right now: the stored width (or the rule's default),
|
|
437
|
+
* fitted so the centre keeps `centreMinWidth` and the canvas never goes under
|
|
438
|
+
* `minWidth`. A width remembered on a wide monitor never crushes a laptop.
|
|
439
|
+
*/
|
|
440
|
+
declare function fitCanvasWidth(stored: number | null, region: number, rules: CanvasLayoutRules): number;
|
|
441
|
+
/** Clamp for a dragged width: the same bounds as fitting. */
|
|
442
|
+
declare function clampDraggedCanvasWidth(next: number, region: number, rules: CanvasLayoutRules): number;
|
|
443
|
+
/** Whether a side-by-side split this wide (px) with this many children renders stacked. */
|
|
444
|
+
declare function shouldStackSplit(width: number, children: number, rules: CanvasLayoutRules): boolean;
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* The canvas controller: the ONE imperative API every caller uses. It wraps a
|
|
448
|
+
* store binding, validates what callers hand it (so the reducer stays pure and
|
|
449
|
+
* trusting), and owns hydration, autosave, pop-outs and session lifecycles.
|
|
450
|
+
*
|
|
451
|
+
* A controller acts for ONE window by default (`controller.windowId`, main
|
|
452
|
+
* unless the host says otherwise); `forWindow(id)` gives the same canvas seen
|
|
453
|
+
* from another window. Nothing fails silently: a refused open, a pop-out the
|
|
454
|
+
* host cannot do, a throwing session hook — each is reported through the
|
|
455
|
+
* error sink.
|
|
456
|
+
*/
|
|
457
|
+
|
|
458
|
+
interface CanvasErrorReport {
|
|
459
|
+
readonly code: "non-json-data" | "unknown-kind" | "persistence-load" | "persistence-save" | "pop-out-unavailable" | "pop-out-refused" | "pop-out-failed" | "session-hook";
|
|
460
|
+
readonly message: string;
|
|
461
|
+
readonly detail?: unknown;
|
|
462
|
+
}
|
|
463
|
+
type CanvasErrorSink = (report: CanvasErrorReport) => void;
|
|
464
|
+
declare const consoleCanvasErrorSink: CanvasErrorSink;
|
|
465
|
+
/**
|
|
466
|
+
* The host's window abilities. Without `popOut` a pop-out is refused aloud and
|
|
467
|
+
* no "Pop out" entry is shown.
|
|
468
|
+
*/
|
|
469
|
+
interface CanvasWindowPorts {
|
|
470
|
+
/**
|
|
471
|
+
* Opens a window (web: a floating panel; desktop: a BrowserWindow) that will
|
|
472
|
+
* render the canvas for the returned window id, then the package moves the
|
|
473
|
+
* item there. Resolve null to refuse.
|
|
474
|
+
*/
|
|
475
|
+
readonly popOut?: ((item: CanvasItem) => Promise<{
|
|
476
|
+
readonly windowId: string;
|
|
477
|
+
} | null>) | undefined;
|
|
478
|
+
/** Called after a tab docked back out of `fromWindowId` (close that window when the state no longer lists it). */
|
|
479
|
+
readonly dockBack?: ((itemId: CanvasItemId, fromWindowId: CanvasWindowId) => void) | undefined;
|
|
480
|
+
}
|
|
481
|
+
/** Why a session-backed item attached or detached. */
|
|
482
|
+
type CanvasSessionAttachReason = "open" | "restore" | "arrive";
|
|
483
|
+
type CanvasSessionDetachReason = "close" | "leave" | "unmount";
|
|
484
|
+
interface CanvasSessionEvent<R extends string> {
|
|
485
|
+
readonly item: CanvasItem;
|
|
486
|
+
readonly sessionKey: string;
|
|
487
|
+
/** The window the item is in (attach) or was in (detach). */
|
|
488
|
+
readonly windowId: CanvasWindowId;
|
|
489
|
+
readonly reason: R;
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* A kind with `restore: "session"`: its tab is a view onto a process the host
|
|
493
|
+
* keeps (a terminal). Closing the tab DETACHES (the process lives on); a
|
|
494
|
+
* restore or a move REATTACHES. The package only calls these; the host owns the
|
|
495
|
+
* process side.
|
|
496
|
+
*/
|
|
497
|
+
interface CanvasSessionHooks {
|
|
498
|
+
readonly sessionKey: (item: CanvasItem) => string | null;
|
|
499
|
+
readonly onAttach?: ((event: CanvasSessionEvent<CanvasSessionAttachReason>) => void) | undefined;
|
|
500
|
+
readonly onDetach?: ((event: CanvasSessionEvent<CanvasSessionDetachReason>) => void) | undefined;
|
|
501
|
+
}
|
|
502
|
+
interface CanvasControllerOptions {
|
|
503
|
+
readonly store: CanvasStoreBinding;
|
|
504
|
+
readonly persistence?: CanvasPersistencePort | null | undefined;
|
|
505
|
+
/** Kinds that may not come back after a reload. Default: every kind restores. */
|
|
506
|
+
readonly isRestorable?: ((kind: string) => boolean) | undefined;
|
|
507
|
+
/** When given, opens of an unregistered kind are refused and reported. */
|
|
508
|
+
readonly isKnownKind?: ((kind: string) => boolean) | undefined;
|
|
509
|
+
readonly onError?: CanvasErrorSink | undefined;
|
|
510
|
+
readonly saveDelayMs?: number | undefined;
|
|
511
|
+
/**
|
|
512
|
+
* Whether a remembered "open" may be restored right now. A host returns
|
|
513
|
+
* false where an open canvas would take the whole screen (a phone).
|
|
514
|
+
*/
|
|
515
|
+
readonly mayRestoreOpen?: (() => boolean) | undefined;
|
|
516
|
+
/** The window this controller acts for by default. Default: the main window. */
|
|
517
|
+
readonly windowId?: string | undefined;
|
|
518
|
+
/** Width and split numbers (see CANVAS_WEB_LAYOUT_RULES / CANVAS_DESKTOP_LAYOUT_RULES). */
|
|
519
|
+
readonly layoutRules?: Partial<CanvasLayoutRules> | undefined;
|
|
520
|
+
/** Read on every use, so a provider can hand in its latest ports. */
|
|
521
|
+
readonly windowPorts?: (() => CanvasWindowPorts) | undefined;
|
|
522
|
+
/** Session hooks for a kind, or undefined when the kind is not session-backed. */
|
|
523
|
+
readonly sessions?: ((kind: string) => CanvasSessionHooks | undefined) | undefined;
|
|
524
|
+
}
|
|
525
|
+
interface CanvasController {
|
|
526
|
+
readonly store: CanvasStoreBinding;
|
|
527
|
+
/** The window this controller acts for by default. */
|
|
528
|
+
readonly windowId: CanvasWindowId;
|
|
529
|
+
readonly rules: CanvasLayoutRules;
|
|
530
|
+
/** The same canvas, acting for another window. */
|
|
531
|
+
forWindow(windowId: string): CanvasController;
|
|
532
|
+
getState(): CanvasState;
|
|
533
|
+
open(input: CanvasOpenInput): CanvasItemId | null;
|
|
534
|
+
update(itemId: CanvasItemId, patch: {
|
|
535
|
+
data?: CanvasJson | undefined;
|
|
536
|
+
title?: string | null | undefined;
|
|
537
|
+
}): boolean;
|
|
538
|
+
/** Gives an item a new identity in place (a draft that was saved and now has a durable id). */
|
|
539
|
+
rekey(itemId: CanvasItemId, key: string): CanvasItemId | null;
|
|
540
|
+
close(itemId: CanvasItemId): void;
|
|
541
|
+
closeOthers(itemId: CanvasItemId): void;
|
|
542
|
+
activate(itemId: CanvasItemId): void;
|
|
543
|
+
focusPane(paneId: CanvasPaneId): void;
|
|
544
|
+
moveItem(itemId: CanvasItemId, toPaneId: CanvasPaneId, index?: number): void;
|
|
545
|
+
/** Splits with the layout rules' share (stackSplit for "vertical", sideSplit for "horizontal"). */
|
|
546
|
+
splitPane(paneId: CanvasPaneId, orientation: CanvasOrientation, moveItemId?: CanvasItemId): void;
|
|
547
|
+
closePane(paneId: CanvasPaneId): void;
|
|
548
|
+
resizeSplit(splitId: CanvasSplitId, sizes: readonly number[]): void;
|
|
549
|
+
show(windowId?: CanvasWindowId): void;
|
|
550
|
+
hide(windowId?: CanvasWindowId): void;
|
|
551
|
+
toggle(windowId?: CanvasWindowId): void;
|
|
552
|
+
setFullscreen(fullscreen: boolean, windowId?: CanvasWindowId): void;
|
|
553
|
+
setWidth(width: number, windowId?: CanvasWindowId): void;
|
|
554
|
+
/** Moves a tab into its own window through the host's popOut port. Refused aloud without one. */
|
|
555
|
+
popOut(itemId: CanvasItemId): Promise<CanvasWindowId | null>;
|
|
556
|
+
/** Returns a popped-out tab to the window it came from. False when it is not popped out. */
|
|
557
|
+
dockBack(itemId: CanvasItemId): boolean;
|
|
558
|
+
/** A host window closed: its tabs dock home (default) or close with it. */
|
|
559
|
+
closeWindow(windowId: CanvasWindowId, mode?: CanvasWindowCloseMode): void;
|
|
560
|
+
/** Is this exact thing on the canvas right now (in any window)? */
|
|
561
|
+
has(kind: string, key: string): boolean;
|
|
562
|
+
/**
|
|
563
|
+
* Is a canvas column on screen (in this window, or in `windowId`)? A store
|
|
564
|
+
* can exist in a layout that shows no column (a kiosk, a meeting stage);
|
|
565
|
+
* opening there must be refused aloud.
|
|
566
|
+
*/
|
|
567
|
+
isPresented(windowId?: CanvasWindowId): boolean;
|
|
568
|
+
/** Called by a column on mount; returns the unmount callback. */
|
|
569
|
+
registerPresentation(windowId?: CanvasWindowId): () => void;
|
|
570
|
+
subscribePresentation(listener: () => void): () => void;
|
|
571
|
+
/** Loads the persisted snapshot, starts autosave and session tracking. Returns a disposer. */
|
|
572
|
+
start(): () => void;
|
|
573
|
+
}
|
|
574
|
+
declare function createCanvasController(options: CanvasControllerOptions): CanvasController;
|
|
575
|
+
|
|
576
|
+
export { foldCanvasWindows as $, type CanvasOrientation as A, type CanvasPersistencePort as B, type CanvasItemId as C, type CanvasRect as D, type CanvasSessionAttachReason as E, type CanvasSessionDetachReason as F, type CanvasSessionEvent as G, type CanvasSessionHooks as H, type CanvasSplitId as I, type CanvasWindowCloseMode as J, type CanvasWindowFrame as K, type CanvasWindowPorts as L, bindCanvasToReduxStore as M, canvasActions as N, canvasReducer as O, canvasWindowFrame as P, canvasWindowOfPane as Q, type ReduxStoreLike as R, clampDraggedCanvasWidth as S, consoleCanvasErrorSink as T, createCanvasController as U, createCanvasStore as V, createInitialCanvasState as W, createKeyValueCanvasPersistence as X, createLocalStorageCanvasPersistence as Y, defaultCanvasWidth as Z, fitCanvasWidth as _, type CanvasWindowId as a, isCanvasAction as a0, listCanvasWindowIds as a1, resolveCanvasLayoutRules as a2, sanitizeCanvasSnapshot as a3, shouldStackSplit as a4, toPersistableSnapshot as a5, type CanvasJson as b, type CanvasLayoutNode as c, type CanvasPaneId as d, type CanvasStoreBinding as e, type CanvasState as f, type CanvasAction as g, type CanvasController as h, type CanvasItem as i, type CanvasExtraWindow as j, type CanvasPane as k, canvasWindowOfItem as l, CANVAS_DEFAULT_WIDTH as m, CANVAS_DESKTOP_LAYOUT_RULES as n, CANVAS_MAIN_WINDOW as o, CANVAS_MIN_SPLIT_FRACTION as p, CANVAS_MIN_WIDTH as q, CANVAS_STORAGE_KEY as r, CANVAS_WEB_LAYOUT_RULES as s, type CanvasControllerOptions as t, type CanvasErrorReport as u, type CanvasErrorSink as v, type CanvasKeyValueStore as w, type CanvasLayoutRules as x, type CanvasOpenInput as y, type CanvasOpenTarget as z };
|