@ai-matrx/canvas 0.3.0 → 0.5.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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CanvasItemId, a as CanvasWindowId, b as CanvasJson, c as CanvasLayoutNode, d as CanvasPaneId, e as CanvasStoreBinding, f as CanvasState, g as CanvasAction, h as CanvasController, i as CanvasItem, j as CanvasExtraWindow, k as CanvasPane, l as canvasWindowOfItem } from './toggleKind-A9v45oSb.cjs';
2
- export { m as CANVAS_DEFAULT_KEY, n as CANVAS_DEFAULT_WIDTH, o as CANVAS_DESKTOP_LAYOUT_RULES, p as CANVAS_MAIN_WINDOW, q as CANVAS_MIN_SPLIT_FRACTION, r as CANVAS_MIN_WIDTH, s as CANVAS_STORAGE_KEY, t as CANVAS_WEB_LAYOUT_RULES, u as CanvasControllerOptions, v as CanvasErrorReport, w as CanvasErrorSink, x as CanvasKeyValueStore, y as CanvasKindVisibility, z as CanvasLayoutRules, A as CanvasOpenInput, B as CanvasOpenTarget, D as CanvasOrientation, E as CanvasPersistencePort, F as CanvasRect, G as CanvasSessionAttachReason, H as CanvasSessionDetachReason, I as CanvasSessionEvent, J as CanvasSessionHooks, K as CanvasSplitId, L as CanvasWindowCloseMode, M as CanvasWindowFrame, N as CanvasWindowPorts, R as ReduxStoreLike, T as ToggleKindInput, O as bindCanvasToReduxStore, P as canvasActions, Q as canvasReducer, S as canvasWindowFrame, U as canvasWindowOfPane, V as clampDraggedCanvasWidth, W as consoleCanvasErrorSink, X as createCanvasController, Y as createCanvasStore, Z as createInitialCanvasState, _ as createKeyValueCanvasPersistence, $ as createLocalStorageCanvasPersistence, a0 as defaultCanvasWidth, a1 as fitCanvasWidth, a2 as foldCanvasWindows, a3 as isCanvasAction, a4 as listCanvasWindowIds, a5 as resolveCanvasLayoutRules, a6 as sanitizeCanvasSnapshot, a7 as selectCanvasKindVisibility, a8 as shouldStackSplit, a9 as toPersistableSnapshot, aa as toggleKind } from './toggleKind-A9v45oSb.cjs';
1
+ import { C as CanvasItemId, a as CanvasWindowId, b as CanvasJson, c as CanvasLayoutNode, d as CanvasPaneId, e as CanvasStoreBinding, f as CanvasState, g as CanvasAction, h as CanvasController, i as CanvasItem, j as CanvasExtraWindow, k as CanvasHistoryEntry, l as CanvasPane, m as canvasWindowOfItem } from './toggleKind-BD78LnMB.cjs';
2
+ export { n as CANVAS_DEFAULT_KEY, o as CANVAS_DEFAULT_WIDTH, p as CANVAS_DESKTOP_LAYOUT_RULES, q as CANVAS_HISTORY_LIMIT, r as CANVAS_LAUNCHER_KIND, s as CANVAS_MAIN_WINDOW, t as CANVAS_MIN_SPLIT_FRACTION, u as CANVAS_MIN_WIDTH, v as CANVAS_NARROW_WIDTH, w as CANVAS_STORAGE_KEY, x as CANVAS_WEB_LAYOUT_RULES, y as CanvasControllerOptions, z as CanvasErrorReport, A as CanvasErrorSink, B as CanvasKeyValueStore, D as CanvasKindVisibility, E as CanvasLayoutRules, F as CanvasNavigateInput, G as CanvasOpenInput, H as CanvasOpenTarget, I as CanvasOrientation, J as CanvasPersistencePort, K as CanvasPreferredTarget, L as CanvasPresentation, M as CanvasPresentationOrientation, N as CanvasRect, O as CanvasSessionAttachReason, P as CanvasSessionDetachReason, Q as CanvasSessionEvent, R as CanvasSessionHooks, S as CanvasSplitId, T as CanvasWindowCloseMode, U as CanvasWindowFrame, V as CanvasWindowPorts, W as ReduxStoreLike, X as ToggleKindInput, Y as bindCanvasToReduxStore, Z as canvasActions, _ as canvasOrientation, $ as canvasPresentation, a0 as canvasReducer, a1 as canvasWindowFrame, a2 as canvasWindowOfPane, a3 as clampDraggedCanvasWidth, a4 as consoleCanvasErrorSink, a5 as createCanvasController, a6 as createCanvasStore, a7 as createInitialCanvasState, a8 as createKeyValueCanvasPersistence, a9 as createLocalStorageCanvasPersistence, aa as defaultCanvasWidth, ab as fitCanvasWidth, ac as foldCanvasWindows, ad as isCanvasAction, ae as listCanvasWindowIds, af as preferredFlowDirection, ag as resolveCanvasLayoutRules, ah as sanitizeCanvasSnapshot, ai as selectCanvasKindVisibility, aj as shouldStackSplit, ak as toPersistableSnapshot, al as toggleKind } from './toggleKind-BD78LnMB.cjs';
3
3
 
4
4
  declare function canvasItemId(kind: string, key: string): CanvasItemId;
5
5
  declare function parseCanvasItemId(id: CanvasItemId): {
@@ -142,5 +142,9 @@ declare function selectCanvasWindows(s: CanvasState): CanvasState["windows"];
142
142
  declare const selectCanvasExtraWindow: (s: CanvasState, windowId: CanvasWindowId) => CanvasExtraWindow | undefined;
143
143
  /** The window an item is in, or null when it is not on the canvas. */
144
144
  declare const selectCanvasWindowOfItem: typeof canvasWindowOfItem;
145
+ /** What a tab showed before, oldest first. Stable per state. */
146
+ declare function selectCanvasItemHistory(s: CanvasState, itemId: CanvasItemId): readonly CanvasHistoryEntry[];
147
+ /** Whether a tab offers Back (it navigated in place). */
148
+ declare const selectCanvasCanGoBack: (s: CanvasState, itemId: CanvasItemId) => boolean;
145
149
 
146
- export { CANVAS_SHORTCUTS, CanvasAction, type CanvasCommandId, CanvasController, CanvasExtraWindow, CanvasItem, CanvasItemId, CanvasJson, type CanvasKeyInput, CanvasLayoutNode, CanvasPane, CanvasPaneId, type CanvasShortcut, type CanvasShortcutWhen, CanvasState, CanvasStoreBinding, CanvasWindowId, type RemoteCanvasStore, type RemoteCanvasStoreOptions, canvasItemId, canvasShortcutHint, canvasWindowId, canvasWindowOfItem, createRemoteCanvasStore, findNonJson, isCanvasJson, listPaneIds, matchesCanvasAccelerator, normalizeSizes, parseCanvasItemId, runCanvasCommand, selectCanvasActiveItem, selectCanvasExtraWindow, selectCanvasFocusedPaneId, selectCanvasIsFullscreen, selectCanvasIsHydrated, selectCanvasIsOpen, selectCanvasItem, selectCanvasItemCount, selectCanvasLayout, selectCanvasPane, selectCanvasPaneCount, selectCanvasWidth, selectCanvasWindowItemCount, selectCanvasWindowOfItem, selectCanvasWindows, shareStructure };
150
+ export { CANVAS_SHORTCUTS, CanvasAction, type CanvasCommandId, CanvasController, CanvasExtraWindow, CanvasHistoryEntry, CanvasItem, CanvasItemId, CanvasJson, type CanvasKeyInput, CanvasLayoutNode, CanvasPane, CanvasPaneId, type CanvasShortcut, type CanvasShortcutWhen, CanvasState, CanvasStoreBinding, CanvasWindowId, type RemoteCanvasStore, type RemoteCanvasStoreOptions, canvasItemId, canvasShortcutHint, canvasWindowId, canvasWindowOfItem, createRemoteCanvasStore, findNonJson, isCanvasJson, listPaneIds, matchesCanvasAccelerator, normalizeSizes, parseCanvasItemId, runCanvasCommand, selectCanvasActiveItem, selectCanvasCanGoBack, selectCanvasExtraWindow, selectCanvasFocusedPaneId, selectCanvasIsFullscreen, selectCanvasIsHydrated, selectCanvasIsOpen, selectCanvasItem, selectCanvasItemCount, selectCanvasItemHistory, selectCanvasLayout, selectCanvasPane, selectCanvasPaneCount, selectCanvasWidth, selectCanvasWindowItemCount, selectCanvasWindowOfItem, selectCanvasWindows, shareStructure };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { C as CanvasItemId, a as CanvasWindowId, b as CanvasJson, c as CanvasLayoutNode, d as CanvasPaneId, e as CanvasStoreBinding, f as CanvasState, g as CanvasAction, h as CanvasController, i as CanvasItem, j as CanvasExtraWindow, k as CanvasPane, l as canvasWindowOfItem } from './toggleKind-A9v45oSb.js';
2
- export { m as CANVAS_DEFAULT_KEY, n as CANVAS_DEFAULT_WIDTH, o as CANVAS_DESKTOP_LAYOUT_RULES, p as CANVAS_MAIN_WINDOW, q as CANVAS_MIN_SPLIT_FRACTION, r as CANVAS_MIN_WIDTH, s as CANVAS_STORAGE_KEY, t as CANVAS_WEB_LAYOUT_RULES, u as CanvasControllerOptions, v as CanvasErrorReport, w as CanvasErrorSink, x as CanvasKeyValueStore, y as CanvasKindVisibility, z as CanvasLayoutRules, A as CanvasOpenInput, B as CanvasOpenTarget, D as CanvasOrientation, E as CanvasPersistencePort, F as CanvasRect, G as CanvasSessionAttachReason, H as CanvasSessionDetachReason, I as CanvasSessionEvent, J as CanvasSessionHooks, K as CanvasSplitId, L as CanvasWindowCloseMode, M as CanvasWindowFrame, N as CanvasWindowPorts, R as ReduxStoreLike, T as ToggleKindInput, O as bindCanvasToReduxStore, P as canvasActions, Q as canvasReducer, S as canvasWindowFrame, U as canvasWindowOfPane, V as clampDraggedCanvasWidth, W as consoleCanvasErrorSink, X as createCanvasController, Y as createCanvasStore, Z as createInitialCanvasState, _ as createKeyValueCanvasPersistence, $ as createLocalStorageCanvasPersistence, a0 as defaultCanvasWidth, a1 as fitCanvasWidth, a2 as foldCanvasWindows, a3 as isCanvasAction, a4 as listCanvasWindowIds, a5 as resolveCanvasLayoutRules, a6 as sanitizeCanvasSnapshot, a7 as selectCanvasKindVisibility, a8 as shouldStackSplit, a9 as toPersistableSnapshot, aa as toggleKind } from './toggleKind-A9v45oSb.js';
1
+ import { C as CanvasItemId, a as CanvasWindowId, b as CanvasJson, c as CanvasLayoutNode, d as CanvasPaneId, e as CanvasStoreBinding, f as CanvasState, g as CanvasAction, h as CanvasController, i as CanvasItem, j as CanvasExtraWindow, k as CanvasHistoryEntry, l as CanvasPane, m as canvasWindowOfItem } from './toggleKind-BD78LnMB.js';
2
+ export { n as CANVAS_DEFAULT_KEY, o as CANVAS_DEFAULT_WIDTH, p as CANVAS_DESKTOP_LAYOUT_RULES, q as CANVAS_HISTORY_LIMIT, r as CANVAS_LAUNCHER_KIND, s as CANVAS_MAIN_WINDOW, t as CANVAS_MIN_SPLIT_FRACTION, u as CANVAS_MIN_WIDTH, v as CANVAS_NARROW_WIDTH, w as CANVAS_STORAGE_KEY, x as CANVAS_WEB_LAYOUT_RULES, y as CanvasControllerOptions, z as CanvasErrorReport, A as CanvasErrorSink, B as CanvasKeyValueStore, D as CanvasKindVisibility, E as CanvasLayoutRules, F as CanvasNavigateInput, G as CanvasOpenInput, H as CanvasOpenTarget, I as CanvasOrientation, J as CanvasPersistencePort, K as CanvasPreferredTarget, L as CanvasPresentation, M as CanvasPresentationOrientation, N as CanvasRect, O as CanvasSessionAttachReason, P as CanvasSessionDetachReason, Q as CanvasSessionEvent, R as CanvasSessionHooks, S as CanvasSplitId, T as CanvasWindowCloseMode, U as CanvasWindowFrame, V as CanvasWindowPorts, W as ReduxStoreLike, X as ToggleKindInput, Y as bindCanvasToReduxStore, Z as canvasActions, _ as canvasOrientation, $ as canvasPresentation, a0 as canvasReducer, a1 as canvasWindowFrame, a2 as canvasWindowOfPane, a3 as clampDraggedCanvasWidth, a4 as consoleCanvasErrorSink, a5 as createCanvasController, a6 as createCanvasStore, a7 as createInitialCanvasState, a8 as createKeyValueCanvasPersistence, a9 as createLocalStorageCanvasPersistence, aa as defaultCanvasWidth, ab as fitCanvasWidth, ac as foldCanvasWindows, ad as isCanvasAction, ae as listCanvasWindowIds, af as preferredFlowDirection, ag as resolveCanvasLayoutRules, ah as sanitizeCanvasSnapshot, ai as selectCanvasKindVisibility, aj as shouldStackSplit, ak as toPersistableSnapshot, al as toggleKind } from './toggleKind-BD78LnMB.js';
3
3
 
4
4
  declare function canvasItemId(kind: string, key: string): CanvasItemId;
5
5
  declare function parseCanvasItemId(id: CanvasItemId): {
@@ -142,5 +142,9 @@ declare function selectCanvasWindows(s: CanvasState): CanvasState["windows"];
142
142
  declare const selectCanvasExtraWindow: (s: CanvasState, windowId: CanvasWindowId) => CanvasExtraWindow | undefined;
143
143
  /** The window an item is in, or null when it is not on the canvas. */
144
144
  declare const selectCanvasWindowOfItem: typeof canvasWindowOfItem;
145
+ /** What a tab showed before, oldest first. Stable per state. */
146
+ declare function selectCanvasItemHistory(s: CanvasState, itemId: CanvasItemId): readonly CanvasHistoryEntry[];
147
+ /** Whether a tab offers Back (it navigated in place). */
148
+ declare const selectCanvasCanGoBack: (s: CanvasState, itemId: CanvasItemId) => boolean;
145
149
 
146
- export { CANVAS_SHORTCUTS, CanvasAction, type CanvasCommandId, CanvasController, CanvasExtraWindow, CanvasItem, CanvasItemId, CanvasJson, type CanvasKeyInput, CanvasLayoutNode, CanvasPane, CanvasPaneId, type CanvasShortcut, type CanvasShortcutWhen, CanvasState, CanvasStoreBinding, CanvasWindowId, type RemoteCanvasStore, type RemoteCanvasStoreOptions, canvasItemId, canvasShortcutHint, canvasWindowId, canvasWindowOfItem, createRemoteCanvasStore, findNonJson, isCanvasJson, listPaneIds, matchesCanvasAccelerator, normalizeSizes, parseCanvasItemId, runCanvasCommand, selectCanvasActiveItem, selectCanvasExtraWindow, selectCanvasFocusedPaneId, selectCanvasIsFullscreen, selectCanvasIsHydrated, selectCanvasIsOpen, selectCanvasItem, selectCanvasItemCount, selectCanvasLayout, selectCanvasPane, selectCanvasPaneCount, selectCanvasWidth, selectCanvasWindowItemCount, selectCanvasWindowOfItem, selectCanvasWindows, shareStructure };
150
+ export { CANVAS_SHORTCUTS, CanvasAction, type CanvasCommandId, CanvasController, CanvasExtraWindow, CanvasHistoryEntry, CanvasItem, CanvasItemId, CanvasJson, type CanvasKeyInput, CanvasLayoutNode, CanvasPane, CanvasPaneId, type CanvasShortcut, type CanvasShortcutWhen, CanvasState, CanvasStoreBinding, CanvasWindowId, type RemoteCanvasStore, type RemoteCanvasStoreOptions, canvasItemId, canvasShortcutHint, canvasWindowId, canvasWindowOfItem, createRemoteCanvasStore, findNonJson, isCanvasJson, listPaneIds, matchesCanvasAccelerator, normalizeSizes, parseCanvasItemId, runCanvasCommand, selectCanvasActiveItem, selectCanvasCanGoBack, selectCanvasExtraWindow, selectCanvasFocusedPaneId, selectCanvasIsFullscreen, selectCanvasIsHydrated, selectCanvasIsOpen, selectCanvasItem, selectCanvasItemCount, selectCanvasItemHistory, selectCanvasLayout, selectCanvasPane, selectCanvasPaneCount, selectCanvasWidth, selectCanvasWindowItemCount, selectCanvasWindowOfItem, selectCanvasWindows, shareStructure };
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  // src/core/types.ts
2
2
  var CANVAS_MAIN_WINDOW = "main";
3
+ var CANVAS_LAUNCHER_KIND = "canvas.launcher";
4
+ var CANVAS_HISTORY_LIMIT = 50;
3
5
  var CANVAS_MIN_WIDTH = 360;
4
6
  var CANVAS_DEFAULT_WIDTH = 640;
5
7
  var CANVAS_MIN_SPLIT_FRACTION = 0.12;
@@ -165,6 +167,10 @@ var canvasActions = {
165
167
  }),
166
168
  /** Gives an item a new identity in place (e.g. a draft that was just saved and now has an id). */
167
169
  rekey: (itemId, key) => ({ type: `${P}rekey`, payload: { itemId, key } }),
170
+ /** Replaces a tab's content with another item in place, remembering what it showed (Back returns). */
171
+ navigate: (itemId, to) => ({ type: `${P}navigate`, payload: { itemId, to, now: Date.now() } }),
172
+ /** Returns a tab to what it showed before its last navigate. */
173
+ back: (itemId) => ({ type: `${P}back`, payload: { itemId, now: Date.now() } }),
168
174
  closeItem: (itemId) => ({ type: `${P}closeItem`, payload: { itemId } }),
169
175
  closeOthers: (itemId) => ({ type: `${P}closeOthers`, payload: { itemId } }),
170
176
  activate: (itemId) => ({ type: `${P}activate`, payload: { itemId } }),
@@ -361,6 +367,63 @@ function sendHome(state, itemId, home, activate) {
361
367
  next = insertInto(next, homeFrame.focusedPaneId, itemId, activate);
362
368
  return activate ? withFrame(next, target, { focusedPaneId: homeFrame.focusedPaneId }) : next;
363
369
  }
370
+ function newItem(kind, key, title, data, now, history) {
371
+ const id = canvasItemId(kind, key);
372
+ return {
373
+ id,
374
+ kind,
375
+ key,
376
+ title,
377
+ data,
378
+ openedAt: now,
379
+ updatedAt: now,
380
+ instance: `${now.toString(36)}:${id}`,
381
+ ...history && history.length > 0 ? { history: history.slice(-CANVAS_HISTORY_LIMIT) } : {}
382
+ };
383
+ }
384
+ function historyEntry(item) {
385
+ return { kind: item.kind, key: item.key, title: item.title, data: item.data };
386
+ }
387
+ function replaceInPlace(state, current, next) {
388
+ const pane = paneOf(state, current.id);
389
+ if (!pane) return state;
390
+ const items = { ...state.items, [next.id]: next };
391
+ delete items[current.id];
392
+ const withItems = { ...state, items };
393
+ const replaced = withPane(withItems, {
394
+ ...pane,
395
+ itemIds: pane.itemIds.map((id) => id === current.id ? next.id : id),
396
+ activeItemId: pane.activeItemId === current.id ? next.id : pane.activeItemId
397
+ });
398
+ return focus(replaced, pane.id);
399
+ }
400
+ function focusExisting(state, itemId) {
401
+ const pane = paneOf(state, itemId);
402
+ if (!pane) return state;
403
+ const next = focus(withPane(state, { ...pane, activeItemId: itemId }), pane.id);
404
+ const windowId = canvasWindowOfPane(next, pane.id);
405
+ return windowId ? withFrame(next, windowId, { isOpen: true }) : next;
406
+ }
407
+ function sanitizeHistory(raw) {
408
+ if (!Array.isArray(raw)) return [];
409
+ const out = [];
410
+ for (const entry of raw) {
411
+ if (!entry || typeof entry !== "object") continue;
412
+ const e = entry;
413
+ if (typeof e.kind !== "string" || typeof e.key !== "string") continue;
414
+ out.push({ kind: e.kind, key: e.key, title: typeof e.title === "string" ? e.title : null, data: e.data ?? null });
415
+ }
416
+ return out.slice(-CANVAS_HISTORY_LIMIT);
417
+ }
418
+ function sanitizeItem(item) {
419
+ const history = sanitizeHistory(item.history);
420
+ const { history: _history, instance, ...rest } = item;
421
+ return {
422
+ ...rest,
423
+ ...typeof instance === "string" && instance ? { instance } : {},
424
+ ...history.length > 0 ? { history } : {}
425
+ };
426
+ }
364
427
  function clampWidth(width) {
365
428
  return Number.isFinite(width) ? Math.max(CANVAS_MIN_WIDTH, Math.round(width)) : null;
366
429
  }
@@ -408,7 +471,7 @@ function sanitizeCanvasSnapshot(raw) {
408
471
  const items = {};
409
472
  for (const [id, item] of Object.entries(s.items)) {
410
473
  if (item && typeof item.kind === "string" && typeof item.key === "string" && canvasItemId(item.kind, item.key) === id) {
411
- items[id] = item;
474
+ items[id] = sanitizeItem(item);
412
475
  }
413
476
  }
414
477
  const placed = /* @__PURE__ */ new Set();
@@ -482,7 +545,7 @@ function canvasReducer(state = createInitialCanvasState(), action) {
482
545
  next = insertInto(next, frame2.focusedPaneId, itemId, true);
483
546
  }
484
547
  } else {
485
- const item = { id: itemId, kind, key, title: title ?? null, data: data ?? null, openedAt: now, updatedAt: now };
548
+ const item = newItem(kind, key, title ?? null, data ?? null, now);
486
549
  next = ensureWindow({ ...next, items: { ...next.items, [itemId]: item } }, revealIn);
487
550
  const frame2 = canvasWindowFrame(next, revealIn) ?? next;
488
551
  let paneId = frame2.focusedPaneId;
@@ -531,6 +594,35 @@ function canvasReducer(state = createInitialCanvasState(), action) {
531
594
  }
532
595
  return { ...state, items, panes };
533
596
  }
597
+ case `${P}navigate`: {
598
+ const { itemId, to, now } = action.payload;
599
+ const current = state.items[itemId];
600
+ if (!current || !paneOf(state, itemId)) return state;
601
+ const targetId = canvasItemId(to.kind, to.key);
602
+ if (targetId === itemId) {
603
+ const refresh = { type: `${P}update`, payload: { itemId, data: to.data, title: to.title, now } };
604
+ return canvasReducer(state, refresh);
605
+ }
606
+ if (state.items[targetId]) {
607
+ const focused = focusExisting(state, targetId);
608
+ return current.kind === CANVAS_LAUNCHER_KIND ? canvasReducer(focused, canvasActions.closeItem(itemId)) : focused;
609
+ }
610
+ const history = [...current.history ?? [], historyEntry(current)];
611
+ return replaceInPlace(state, current, newItem(to.kind, to.key, to.title ?? null, to.data ?? null, now, history));
612
+ }
613
+ case `${P}back`: {
614
+ const { itemId, now } = action.payload;
615
+ const current = state.items[itemId];
616
+ const history = current?.history ?? [];
617
+ const entry = history[history.length - 1];
618
+ if (!current || !entry || !paneOf(state, itemId)) return state;
619
+ const targetId = canvasItemId(entry.kind, entry.key);
620
+ if (targetId === itemId) return state;
621
+ if (state.items[targetId]) {
622
+ return focusExisting(canvasReducer(state, canvasActions.closeItem(itemId)), targetId);
623
+ }
624
+ return replaceInPlace(state, current, newItem(entry.kind, entry.key, entry.title, entry.data, now, history.slice(0, -1)));
625
+ }
534
626
  case `${P}closeItem`: {
535
627
  const { itemId } = action.payload;
536
628
  if (!state.items[itemId]) return state;
@@ -861,7 +953,8 @@ var CANVAS_WEB_LAYOUT_RULES = {
861
953
  defaultWidth: CANVAS_DEFAULT_WIDTH,
862
954
  minWidth: CANVAS_MIN_WIDTH,
863
955
  centreMinWidth: 420,
864
- stackSplit: 0.5,
956
+ // Split down 70/30 like the desktop: what opens below (notifications, a feed) is the smaller pane.
957
+ stackSplit: 0.7,
865
958
  sideSplit: 0.5,
866
959
  paneMinWidth: 0
867
960
  };
@@ -994,7 +1087,8 @@ function createCanvasController(options) {
994
1087
  presentationListeners: /* @__PURE__ */ new Set(),
995
1088
  views: /* @__PURE__ */ new Map(),
996
1089
  started: /* @__PURE__ */ new Map(),
997
- sessionStop: null
1090
+ sessionStop: null,
1091
+ launcherSeq: 0
998
1092
  };
999
1093
  return view(shared, canvasWindowId(options.windowId ?? CANVAS_MAIN_WINDOW));
1000
1094
  }
@@ -1009,6 +1103,36 @@ function view(shared, windowId) {
1009
1103
  for (const listener of [...shared.presentationListeners]) listener();
1010
1104
  };
1011
1105
  const shareFor = (orientation) => orientation === "vertical" ? rules.stackSplit : rules.sideSplit;
1106
+ const acceptable = (input) => {
1107
+ if (input.kind !== CANVAS_LAUNCHER_KIND && options.isKnownKind && !options.isKnownKind(input.kind)) {
1108
+ onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
1109
+ return false;
1110
+ }
1111
+ if (input.data !== void 0) {
1112
+ const bad = findNonJson(input.data);
1113
+ if (bad) {
1114
+ onError({
1115
+ code: "non-json-data",
1116
+ message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
1117
+ detail: { kind: input.kind, key: input.key }
1118
+ });
1119
+ return false;
1120
+ }
1121
+ }
1122
+ return true;
1123
+ };
1124
+ const preferredTargetFor = (input) => {
1125
+ const preferred = options.preferredTarget?.(input.kind);
1126
+ if (!preferred || preferred === "focused") return "focused";
1127
+ const state = store.getState();
1128
+ if (canvasItemId(input.kind, input.key) in state.items) return "focused";
1129
+ const frame2 = canvasWindowFrame(state, input.windowId ?? windowId);
1130
+ if (!frame2?.isOpen) return "focused";
1131
+ const panes = listPaneIds(frame2.layout);
1132
+ const holding = panes.find((paneId) => state.panes[paneId]?.itemIds.some((id) => state.items[id]?.kind === input.kind));
1133
+ if (holding) return { paneId: holding };
1134
+ return state.panes[frame2.focusedPaneId]?.activeItemId ? preferred : "focused";
1135
+ };
1012
1136
  const controller = {
1013
1137
  store,
1014
1138
  windowId,
@@ -1016,24 +1140,11 @@ function view(shared, windowId) {
1016
1140
  forWindow: (id) => view(shared, canvasWindowId(id)),
1017
1141
  getState: () => store.getState(),
1018
1142
  open(input) {
1019
- if (options.isKnownKind && !options.isKnownKind(input.kind)) {
1020
- onError({ code: "unknown-kind", message: `No canvas kind "${input.kind}" is registered.`, detail: input });
1021
- return null;
1022
- }
1023
- if (input.data !== void 0) {
1024
- const bad = findNonJson(input.data);
1025
- if (bad) {
1026
- onError({
1027
- code: "non-json-data",
1028
- message: `Canvas data for "${input.kind}" must be plain JSON; ${bad} is not.`,
1029
- detail: { kind: input.kind, key: input.key }
1030
- });
1031
- return null;
1032
- }
1033
- }
1034
- const target = input.target ?? "focused";
1143
+ if (!acceptable(input)) return null;
1144
+ const target = input.target ?? preferredTargetFor(input);
1035
1145
  const splitShare = target === "split-down" ? rules.stackSplit : target === "split-right" ? rules.sideSplit : void 0;
1036
- const resolved = input.windowId || windowId === CANVAS_MAIN_WINDOW ? input : { ...input, windowId };
1146
+ const withTarget = input.target === target ? input : { ...input, target };
1147
+ const resolved = withTarget.windowId || windowId === CANVAS_MAIN_WINDOW ? withTarget : { ...withTarget, windowId };
1037
1148
  dispatch(canvasActions.open(resolved, splitShare === void 0 ? void 0 : { splitShare }));
1038
1149
  return canvasItemId(input.kind, input.key);
1039
1150
  },
@@ -1049,6 +1160,34 @@ function view(shared, windowId) {
1049
1160
  dispatch(canvasActions.update(itemId, patch));
1050
1161
  return true;
1051
1162
  },
1163
+ navigate(itemId, to) {
1164
+ if (!store.getState().items[itemId] || !acceptable(to)) return null;
1165
+ dispatch(canvasActions.navigate(itemId, to));
1166
+ const shown = canvasItemId(to.kind, to.key);
1167
+ return shown in store.getState().items ? shown : null;
1168
+ },
1169
+ back(itemId) {
1170
+ const history = store.getState().items[itemId]?.history ?? [];
1171
+ const entry = history[history.length - 1];
1172
+ if (!entry) return null;
1173
+ dispatch(canvasActions.back(itemId));
1174
+ return canvasItemId(entry.kind, entry.key);
1175
+ },
1176
+ newTab(paneId) {
1177
+ const state = store.getState();
1178
+ const frame2 = canvasWindowFrame(state, windowId);
1179
+ const target = paneId && state.panes[paneId] ? paneId : frame2?.focusedPaneId ?? state.focusedPaneId;
1180
+ const existing = state.panes[target]?.itemIds.find((id) => state.items[id]?.kind === CANVAS_LAUNCHER_KIND);
1181
+ if (existing) {
1182
+ dispatch(canvasActions.activate(existing));
1183
+ dispatch(canvasActions.setOpen(true, canvasWindowOfItem(store.getState(), existing) ?? windowId));
1184
+ return existing;
1185
+ }
1186
+ shared.launcherSeq += 1;
1187
+ const key = `${Date.now().toString(36)}-${shared.launcherSeq}`;
1188
+ dispatch(canvasActions.open({ kind: CANVAS_LAUNCHER_KIND, key, target: { paneId: target }, ...windowId === CANVAS_MAIN_WINDOW ? {} : { windowId } }));
1189
+ return canvasItemId(CANVAS_LAUNCHER_KIND, key);
1190
+ },
1052
1191
  rekey(itemId, key) {
1053
1192
  const item = store.getState().items[itemId];
1054
1193
  if (!item) return null;
@@ -1312,6 +1451,41 @@ function selectCanvasWindows(s) {
1312
1451
  }
1313
1452
  var selectCanvasExtraWindow = (s, windowId) => s.windows?.[windowId];
1314
1453
  var selectCanvasWindowOfItem = canvasWindowOfItem;
1454
+ var NO_HISTORY = Object.freeze([]);
1455
+ function selectCanvasItemHistory(s, itemId) {
1456
+ return s.items[itemId]?.history ?? NO_HISTORY;
1457
+ }
1458
+ var selectCanvasCanGoBack = (s, itemId) => (s.items[itemId]?.history?.length ?? 0) > 0;
1459
+
1460
+ // src/core/presentation.ts
1461
+ var CANVAS_NARROW_WIDTH = 480;
1462
+ var SQUARE_BAND = 1.2;
1463
+ function canvasOrientation(width, height) {
1464
+ if (!(width > 0) || !(height > 0)) return "square";
1465
+ const ratio = width / height;
1466
+ if (ratio >= SQUARE_BAND) return "landscape";
1467
+ if (ratio <= 1 / SQUARE_BAND) return "portrait";
1468
+ return "square";
1469
+ }
1470
+ function canvasPresentation(input) {
1471
+ const width = Math.max(0, Math.round(input.width));
1472
+ const height = Math.max(0, Math.round(input.height));
1473
+ return {
1474
+ host: "canvas",
1475
+ width,
1476
+ height,
1477
+ orientation: canvasOrientation(width, height),
1478
+ isNarrow: width > 0 && width < CANVAS_NARROW_WIDTH,
1479
+ isFullscreen: input.isFullscreen,
1480
+ paneCount: Math.max(1, input.paneCount)
1481
+ };
1482
+ }
1483
+ function preferredFlowDirection(presentation) {
1484
+ if (!presentation) return "horizontal";
1485
+ if (presentation.orientation === "portrait") return "vertical";
1486
+ if (presentation.orientation === "landscape") return "horizontal";
1487
+ return presentation.isNarrow ? "vertical" : "horizontal";
1488
+ }
1315
1489
 
1316
1490
  // src/core/toggleKind.ts
1317
1491
  var CANVAS_DEFAULT_KEY = "default";
@@ -1354,15 +1528,20 @@ export {
1354
1528
  CANVAS_DEFAULT_KEY,
1355
1529
  CANVAS_DEFAULT_WIDTH,
1356
1530
  CANVAS_DESKTOP_LAYOUT_RULES,
1531
+ CANVAS_HISTORY_LIMIT,
1532
+ CANVAS_LAUNCHER_KIND,
1357
1533
  CANVAS_MAIN_WINDOW,
1358
1534
  CANVAS_MIN_SPLIT_FRACTION,
1359
1535
  CANVAS_MIN_WIDTH,
1536
+ CANVAS_NARROW_WIDTH,
1360
1537
  CANVAS_SHORTCUTS,
1361
1538
  CANVAS_STORAGE_KEY,
1362
1539
  CANVAS_WEB_LAYOUT_RULES,
1363
1540
  bindCanvasToReduxStore,
1364
1541
  canvasActions,
1365
1542
  canvasItemId,
1543
+ canvasOrientation,
1544
+ canvasPresentation,
1366
1545
  canvasReducer,
1367
1546
  canvasShortcutHint,
1368
1547
  canvasWindowFrame,
@@ -1388,10 +1567,12 @@ export {
1388
1567
  matchesCanvasAccelerator,
1389
1568
  normalizeSizes,
1390
1569
  parseCanvasItemId,
1570
+ preferredFlowDirection,
1391
1571
  resolveCanvasLayoutRules,
1392
1572
  runCanvasCommand,
1393
1573
  sanitizeCanvasSnapshot,
1394
1574
  selectCanvasActiveItem,
1575
+ selectCanvasCanGoBack,
1395
1576
  selectCanvasExtraWindow,
1396
1577
  selectCanvasFocusedPaneId,
1397
1578
  selectCanvasIsFullscreen,
@@ -1399,6 +1580,7 @@ export {
1399
1580
  selectCanvasIsOpen,
1400
1581
  selectCanvasItem,
1401
1582
  selectCanvasItemCount,
1583
+ selectCanvasItemHistory,
1402
1584
  selectCanvasKindVisibility,
1403
1585
  selectCanvasLayout,
1404
1586
  selectCanvasPane,