@ai-matrx/canvas 0.6.0 → 0.7.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 CanvasHistoryEntry, l as CanvasPane, m as canvasWindowOfItem } from './toggleKind-DhDmWExm.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_KEEP_ALIVE_DEPTH, s as CANVAS_LAUNCHER_KIND, t as CANVAS_MAIN_WINDOW, u as CANVAS_MIN_SPLIT_FRACTION, v as CANVAS_MIN_WIDTH, w as CANVAS_NARROW_WIDTH, x as CANVAS_PHONE_MAX_WIDTH, y as CANVAS_STORAGE_KEY, z as CANVAS_WEB_LAYOUT_RULES, A as CanvasControllerOptions, B as CanvasErrorReport, D as CanvasErrorSink, E as CanvasKeyValueStore, F as CanvasKindVisibility, G as CanvasLayoutRules, H as CanvasNavigateInput, I as CanvasOpenInput, J as CanvasOpenTarget, K as CanvasOrientation, L as CanvasPersistencePort, M as CanvasPreferredTarget, N as CanvasPresentation, O as CanvasPresentationOrientation, P as CanvasRect, Q as CanvasSessionAttachReason, R as CanvasSessionDetachReason, S as CanvasSessionEvent, T as CanvasSessionHooks, U as CanvasSplitId, V as CanvasWindowCloseMode, W as CanvasWindowFrame, X as CanvasWindowPorts, Y as ReduxStoreLike, Z as ToggleKindInput, _ as bindCanvasToReduxStore, $ as canvasActions, a0 as canvasOrientation, a1 as canvasPresentation, a2 as canvasReducer, a3 as canvasWindowFrame, a4 as canvasWindowOfPane, a5 as clampDraggedCanvasWidth, a6 as consoleCanvasErrorSink, a7 as createCanvasController, a8 as createCanvasStore, a9 as createInitialCanvasState, aa as createKeyValueCanvasPersistence, ab as createLocalStorageCanvasPersistence, ac as defaultCanvasWidth, ad as fitCanvasWidth, ae as foldCanvasWindows, af as isCanvasAction, ag as listCanvasWindowIds, ah as preferredFlowDirection, ai as resolveCanvasLayoutRules, aj as sanitizeCanvasSnapshot, ak as selectCanvasKindVisibility, al as shouldStackSplit, am as toPersistableSnapshot, an as toggleKind } from './toggleKind-DhDmWExm.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-CAI7pndj.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_KEEP_ALIVE_DEPTH, s as CANVAS_LAUNCHER_KIND, t as CANVAS_MAIN_WINDOW, u as CANVAS_MIN_SPLIT_FRACTION, v as CANVAS_MIN_WIDTH, w as CANVAS_NARROW_WIDTH, x as CANVAS_PANE_MIN_WIDTH, y as CANVAS_PHONE_MAX_WIDTH, z as CANVAS_STORAGE_KEY, A as CANVAS_WEB_LAYOUT_RULES, B as CanvasControllerOptions, D as CanvasErrorReport, E as CanvasErrorSink, F as CanvasKeyValueStore, G as CanvasKindVisibility, H as CanvasLayoutRules, I as CanvasNavigateInput, J as CanvasOpenInput, K as CanvasOpenTarget, L as CanvasOrientation, M as CanvasPersistencePort, N as CanvasPreferredTarget, O as CanvasPresentation, P as CanvasPresentationOrientation, Q as CanvasRect, R as CanvasSessionAttachReason, S as CanvasSessionDetachReason, T as CanvasSessionEvent, U as CanvasSessionHooks, V as CanvasSplitId, W as CanvasWindowCloseMode, X as CanvasWindowFrame, Y as CanvasWindowPorts, Z as ReduxStoreLike, _ as ToggleKindInput, $ as bindCanvasToReduxStore, a0 as canvasActions, a1 as canvasOrientation, a2 as canvasPresentation, a3 as canvasReducer, a4 as canvasWindowFrame, a5 as canvasWindowOfPane, a6 as clampDraggedCanvasWidth, a7 as consoleCanvasErrorSink, a8 as createCanvasController, a9 as createCanvasStore, aa as createInitialCanvasState, ab as createKeyValueCanvasPersistence, ac as createLocalStorageCanvasPersistence, ad as defaultCanvasWidth, ae as fitCanvasWidth, af as foldCanvasWindows, ag as isCanvasAction, ah as listCanvasWindowIds, ai as preferredFlowDirection, aj as resolveCanvasLayoutRules, ak as sanitizeCanvasSnapshot, al as selectCanvasKindVisibility, am as shouldStackSplit, an as toPersistableSnapshot, ao as toggleKind } from './toggleKind-CAI7pndj.cjs';
3
3
 
4
4
  declare function canvasItemId(kind: string, key: string): CanvasItemId;
5
5
  declare function parseCanvasItemId(id: CanvasItemId): {
@@ -73,7 +73,7 @@ declare function shareStructure<T>(prev: T, next: T): T;
73
73
  * through `runCanvasCommand`, so both paths do exactly the same thing.
74
74
  */
75
75
 
76
- type CanvasCommandId = "canvas.toggle" | "canvas.toggleFullscreen" | "canvas.exitFullscreen" | "canvas.closeTab" | "canvas.nextTab" | "canvas.previousTab" | "canvas.splitRight" | "canvas.splitDown";
76
+ type CanvasCommandId = "canvas.toggle" | "canvas.toggleFullscreen" | "canvas.exitFullscreen" | "canvas.closeTab" | "canvas.nextTab" | "canvas.previousTab" | "canvas.splitRight" | "canvas.splitDown" | "canvas.back" | "canvas.forward";
77
77
  /**
78
78
  * When a shortcut applies:
79
79
  * - "presented": a canvas column is on screen in this window;
@@ -146,5 +146,14 @@ declare const selectCanvasWindowOfItem: typeof canvasWindowOfItem;
146
146
  declare function selectCanvasItemHistory(s: CanvasState, itemId: CanvasItemId): readonly CanvasHistoryEntry[];
147
147
  /** Whether a tab offers Back (it navigated in place). */
148
148
  declare const selectCanvasCanGoBack: (s: CanvasState, itemId: CanvasItemId) => boolean;
149
+ /** Whether a tab offers Forward (it went Back and has not navigated elsewhere since). */
150
+ declare const selectCanvasCanGoForward: (s: CanvasState, itemId: CanvasItemId) => boolean;
151
+ /**
152
+ * The ONE pane a phone presents: the focused pane when it holds a tab, else
153
+ * the first pane that does (an empty pane is never presented over tabs that
154
+ * exist — the header and the body always describe the same pane), else the
155
+ * focused one.
156
+ */
157
+ declare function selectCanvasPresentedPaneId(s: CanvasState, windowId?: CanvasWindowId): CanvasPaneId | null;
149
158
 
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 };
159
+ 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, selectCanvasCanGoForward, selectCanvasExtraWindow, selectCanvasFocusedPaneId, selectCanvasIsFullscreen, selectCanvasIsHydrated, selectCanvasIsOpen, selectCanvasItem, selectCanvasItemCount, selectCanvasItemHistory, selectCanvasLayout, selectCanvasPane, selectCanvasPaneCount, selectCanvasPresentedPaneId, 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 CanvasHistoryEntry, l as CanvasPane, m as canvasWindowOfItem } from './toggleKind-DhDmWExm.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_KEEP_ALIVE_DEPTH, s as CANVAS_LAUNCHER_KIND, t as CANVAS_MAIN_WINDOW, u as CANVAS_MIN_SPLIT_FRACTION, v as CANVAS_MIN_WIDTH, w as CANVAS_NARROW_WIDTH, x as CANVAS_PHONE_MAX_WIDTH, y as CANVAS_STORAGE_KEY, z as CANVAS_WEB_LAYOUT_RULES, A as CanvasControllerOptions, B as CanvasErrorReport, D as CanvasErrorSink, E as CanvasKeyValueStore, F as CanvasKindVisibility, G as CanvasLayoutRules, H as CanvasNavigateInput, I as CanvasOpenInput, J as CanvasOpenTarget, K as CanvasOrientation, L as CanvasPersistencePort, M as CanvasPreferredTarget, N as CanvasPresentation, O as CanvasPresentationOrientation, P as CanvasRect, Q as CanvasSessionAttachReason, R as CanvasSessionDetachReason, S as CanvasSessionEvent, T as CanvasSessionHooks, U as CanvasSplitId, V as CanvasWindowCloseMode, W as CanvasWindowFrame, X as CanvasWindowPorts, Y as ReduxStoreLike, Z as ToggleKindInput, _ as bindCanvasToReduxStore, $ as canvasActions, a0 as canvasOrientation, a1 as canvasPresentation, a2 as canvasReducer, a3 as canvasWindowFrame, a4 as canvasWindowOfPane, a5 as clampDraggedCanvasWidth, a6 as consoleCanvasErrorSink, a7 as createCanvasController, a8 as createCanvasStore, a9 as createInitialCanvasState, aa as createKeyValueCanvasPersistence, ab as createLocalStorageCanvasPersistence, ac as defaultCanvasWidth, ad as fitCanvasWidth, ae as foldCanvasWindows, af as isCanvasAction, ag as listCanvasWindowIds, ah as preferredFlowDirection, ai as resolveCanvasLayoutRules, aj as sanitizeCanvasSnapshot, ak as selectCanvasKindVisibility, al as shouldStackSplit, am as toPersistableSnapshot, an as toggleKind } from './toggleKind-DhDmWExm.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-CAI7pndj.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_KEEP_ALIVE_DEPTH, s as CANVAS_LAUNCHER_KIND, t as CANVAS_MAIN_WINDOW, u as CANVAS_MIN_SPLIT_FRACTION, v as CANVAS_MIN_WIDTH, w as CANVAS_NARROW_WIDTH, x as CANVAS_PANE_MIN_WIDTH, y as CANVAS_PHONE_MAX_WIDTH, z as CANVAS_STORAGE_KEY, A as CANVAS_WEB_LAYOUT_RULES, B as CanvasControllerOptions, D as CanvasErrorReport, E as CanvasErrorSink, F as CanvasKeyValueStore, G as CanvasKindVisibility, H as CanvasLayoutRules, I as CanvasNavigateInput, J as CanvasOpenInput, K as CanvasOpenTarget, L as CanvasOrientation, M as CanvasPersistencePort, N as CanvasPreferredTarget, O as CanvasPresentation, P as CanvasPresentationOrientation, Q as CanvasRect, R as CanvasSessionAttachReason, S as CanvasSessionDetachReason, T as CanvasSessionEvent, U as CanvasSessionHooks, V as CanvasSplitId, W as CanvasWindowCloseMode, X as CanvasWindowFrame, Y as CanvasWindowPorts, Z as ReduxStoreLike, _ as ToggleKindInput, $ as bindCanvasToReduxStore, a0 as canvasActions, a1 as canvasOrientation, a2 as canvasPresentation, a3 as canvasReducer, a4 as canvasWindowFrame, a5 as canvasWindowOfPane, a6 as clampDraggedCanvasWidth, a7 as consoleCanvasErrorSink, a8 as createCanvasController, a9 as createCanvasStore, aa as createInitialCanvasState, ab as createKeyValueCanvasPersistence, ac as createLocalStorageCanvasPersistence, ad as defaultCanvasWidth, ae as fitCanvasWidth, af as foldCanvasWindows, ag as isCanvasAction, ah as listCanvasWindowIds, ai as preferredFlowDirection, aj as resolveCanvasLayoutRules, ak as sanitizeCanvasSnapshot, al as selectCanvasKindVisibility, am as shouldStackSplit, an as toPersistableSnapshot, ao as toggleKind } from './toggleKind-CAI7pndj.js';
3
3
 
4
4
  declare function canvasItemId(kind: string, key: string): CanvasItemId;
5
5
  declare function parseCanvasItemId(id: CanvasItemId): {
@@ -73,7 +73,7 @@ declare function shareStructure<T>(prev: T, next: T): T;
73
73
  * through `runCanvasCommand`, so both paths do exactly the same thing.
74
74
  */
75
75
 
76
- type CanvasCommandId = "canvas.toggle" | "canvas.toggleFullscreen" | "canvas.exitFullscreen" | "canvas.closeTab" | "canvas.nextTab" | "canvas.previousTab" | "canvas.splitRight" | "canvas.splitDown";
76
+ type CanvasCommandId = "canvas.toggle" | "canvas.toggleFullscreen" | "canvas.exitFullscreen" | "canvas.closeTab" | "canvas.nextTab" | "canvas.previousTab" | "canvas.splitRight" | "canvas.splitDown" | "canvas.back" | "canvas.forward";
77
77
  /**
78
78
  * When a shortcut applies:
79
79
  * - "presented": a canvas column is on screen in this window;
@@ -146,5 +146,14 @@ declare const selectCanvasWindowOfItem: typeof canvasWindowOfItem;
146
146
  declare function selectCanvasItemHistory(s: CanvasState, itemId: CanvasItemId): readonly CanvasHistoryEntry[];
147
147
  /** Whether a tab offers Back (it navigated in place). */
148
148
  declare const selectCanvasCanGoBack: (s: CanvasState, itemId: CanvasItemId) => boolean;
149
+ /** Whether a tab offers Forward (it went Back and has not navigated elsewhere since). */
150
+ declare const selectCanvasCanGoForward: (s: CanvasState, itemId: CanvasItemId) => boolean;
151
+ /**
152
+ * The ONE pane a phone presents: the focused pane when it holds a tab, else
153
+ * the first pane that does (an empty pane is never presented over tabs that
154
+ * exist — the header and the body always describe the same pane), else the
155
+ * focused one.
156
+ */
157
+ declare function selectCanvasPresentedPaneId(s: CanvasState, windowId?: CanvasWindowId): CanvasPaneId | null;
149
158
 
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 };
159
+ 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, selectCanvasCanGoForward, selectCanvasExtraWindow, selectCanvasFocusedPaneId, selectCanvasIsFullscreen, selectCanvasIsHydrated, selectCanvasIsOpen, selectCanvasItem, selectCanvasItemCount, selectCanvasItemHistory, selectCanvasLayout, selectCanvasPane, selectCanvasPaneCount, selectCanvasPresentedPaneId, selectCanvasWidth, selectCanvasWindowItemCount, selectCanvasWindowOfItem, selectCanvasWindows, shareStructure };
package/dist/index.js CHANGED
@@ -172,6 +172,8 @@ var canvasActions = {
172
172
  navigate: (itemId, to) => ({ type: `${P}navigate`, payload: { itemId, to, now: Date.now() } }),
173
173
  /** Returns a tab to what it showed before its last navigate. */
174
174
  back: (itemId) => ({ type: `${P}back`, payload: { itemId, now: Date.now() } }),
175
+ /** Re-does the last Back (the step Back left), with that step's live body. */
176
+ forward: (itemId) => ({ type: `${P}forward`, payload: { itemId, now: Date.now() } }),
175
177
  closeItem: (itemId) => ({ type: `${P}closeItem`, payload: { itemId } }),
176
178
  closeOthers: (itemId) => ({ type: `${P}closeOthers`, payload: { itemId } }),
177
179
  activate: (itemId) => ({ type: `${P}activate`, payload: { itemId } }),
@@ -372,6 +374,7 @@ function newItem(kind, key, title, data, now, steps) {
372
374
  const id = canvasItemId(kind, key);
373
375
  const history = steps?.history ?? [];
374
376
  const kept = steps?.kept ?? [];
377
+ const forward = steps?.forward ?? [];
375
378
  return {
376
379
  id,
377
380
  kind,
@@ -383,7 +386,8 @@ function newItem(kind, key, title, data, now, steps) {
383
386
  // A reused instance is the SAME live body coming back (Back, or returning to a kept step).
384
387
  instance: steps?.instance ?? `${now.toString(36)}:${id}`,
385
388
  ...history.length > 0 ? { history: history.slice(-CANVAS_HISTORY_LIMIT) } : {},
386
- ...kept.length > 0 ? { kept: kept.slice(-CANVAS_KEEP_ALIVE_DEPTH) } : {}
389
+ ...kept.length > 0 ? { kept: kept.slice(-CANVAS_KEEP_ALIVE_DEPTH) } : {},
390
+ ...forward.length > 0 ? { forward: forward.slice(-CANVAS_HISTORY_LIMIT) } : {}
387
391
  };
388
392
  }
389
393
  function historyEntry(item) {
@@ -429,12 +433,14 @@ function sanitizeHistory(raw) {
429
433
  function sanitizeItem(item) {
430
434
  const history = sanitizeHistory(item.history);
431
435
  const kept = sanitizeHistory(item.kept).slice(-CANVAS_KEEP_ALIVE_DEPTH);
432
- const { history: _history, kept: _kept, instance, ...rest } = item;
436
+ const forward = sanitizeHistory(item.forward);
437
+ const { history: _history, kept: _kept, forward: _forward, instance, ...rest } = item;
433
438
  return {
434
439
  ...rest,
435
440
  ...typeof instance === "string" && instance ? { instance } : {},
436
441
  ...history.length > 0 ? { history } : {},
437
- ...kept.length > 0 ? { kept } : {}
442
+ ...kept.length > 0 ? { kept } : {},
443
+ ...forward.length > 0 ? { forward } : {}
438
444
  };
439
445
  }
440
446
  function clampWidth(width) {
@@ -625,6 +631,9 @@ function canvasReducer(state = createInitialCanvasState(), action) {
625
631
  const keptIndex = keptAll.findIndex((entry) => canvasItemId(entry.kind, entry.key) === targetId);
626
632
  const revived = keptIndex >= 0 ? keptAll[keptIndex] : void 0;
627
633
  const kept = keptAll.filter((_, i) => i !== keptIndex);
634
+ const forwardAll = current.forward ?? [];
635
+ const nextForward = forwardAll[forwardAll.length - 1];
636
+ const forward = nextForward && canvasItemId(nextForward.kind, nextForward.key) === targetId ? forwardAll.slice(0, -1) : [];
628
637
  return replaceInPlace(
629
638
  state,
630
639
  current,
@@ -634,7 +643,7 @@ function canvasReducer(state = createInitialCanvasState(), action) {
634
643
  to.title !== void 0 ? to.title : revived?.title ?? null,
635
644
  to.data !== void 0 ? to.data : revived?.data ?? null,
636
645
  now,
637
- { history, kept, instance: revived?.instance }
646
+ { history, kept, forward, instance: revived?.instance }
638
647
  )
639
648
  );
640
649
  }
@@ -650,10 +659,36 @@ function canvasReducer(state = createInitialCanvasState(), action) {
650
659
  return focusExisting(canvasReducer(state, canvasActions.closeItem(itemId)), targetId);
651
660
  }
652
661
  const kept = [...(current.kept ?? []).filter((k) => canvasItemId(k.kind, k.key) !== itemId), historyEntry(current)];
662
+ const forward = [...current.forward ?? [], historyEntry(current)];
653
663
  return replaceInPlace(
654
664
  state,
655
665
  current,
656
- newItem(entry.kind, entry.key, entry.title, entry.data, now, { history: history.slice(0, -1), kept, instance: entry.instance })
666
+ newItem(entry.kind, entry.key, entry.title, entry.data, now, { history: history.slice(0, -1), kept, forward, instance: entry.instance })
667
+ );
668
+ }
669
+ case `${P}forward`: {
670
+ const { itemId, now } = action.payload;
671
+ const current = state.items[itemId];
672
+ const forwardAll = current?.forward ?? [];
673
+ const entry = forwardAll[forwardAll.length - 1];
674
+ if (!current || !entry || !paneOf(state, itemId)) return state;
675
+ const targetId = canvasItemId(entry.kind, entry.key);
676
+ if (targetId === itemId) return state;
677
+ if (state.items[targetId]) {
678
+ return focusExisting(canvasReducer(state, canvasActions.closeItem(itemId)), targetId);
679
+ }
680
+ const keptAll = current.kept ?? [];
681
+ const revived = keptAll.find((k) => canvasItemId(k.kind, k.key) === targetId);
682
+ const kept = keptAll.filter((k) => canvasItemId(k.kind, k.key) !== targetId);
683
+ return replaceInPlace(
684
+ state,
685
+ current,
686
+ newItem(entry.kind, entry.key, entry.title, entry.data, now, {
687
+ history: [...current.history ?? [], historyEntry(current)],
688
+ kept,
689
+ forward: forwardAll.slice(0, -1),
690
+ instance: revived?.instance ?? entry.instance
691
+ })
657
692
  );
658
693
  }
659
694
  case `${P}closeItem`: {
@@ -982,6 +1017,7 @@ function toPersistableSnapshot(state, isRestorable) {
982
1017
  }
983
1018
 
984
1019
  // src/core/rules.ts
1020
+ var CANVAS_PANE_MIN_WIDTH = 280;
985
1021
  var CANVAS_WEB_LAYOUT_RULES = {
986
1022
  defaultWidth: CANVAS_DEFAULT_WIDTH,
987
1023
  minWidth: CANVAS_MIN_WIDTH,
@@ -989,7 +1025,9 @@ var CANVAS_WEB_LAYOUT_RULES = {
989
1025
  // Split down 70/30 like the desktop: what opens below (notifications, a feed) is the smaller pane.
990
1026
  stackSplit: 0.7,
991
1027
  sideSplit: 0.5,
992
- paneMinWidth: 0
1028
+ // A pane narrower than this cannot show a header (Back, a readable tab, "+", the menu, close) and a
1029
+ // body side by side with another: a split right that would go under it stacks instead.
1030
+ paneMinWidth: CANVAS_PANE_MIN_WIDTH
993
1031
  };
994
1032
  var CANVAS_DESKTOP_LAYOUT_RULES = {
995
1033
  defaultWidth: { fraction: 0.36 },
@@ -1036,6 +1074,9 @@ var CANVAS_SHORTCUTS = [
1036
1074
  { id: "canvas.toggleFullscreen", accelerator: "CmdOrCtrl+Shift+Enter", when: "canvas-focus", label: "Expand canvas", web: true },
1037
1075
  { id: "canvas.splitRight", accelerator: "CmdOrCtrl+Alt+\\", when: "canvas-focus", label: "Split right", web: true },
1038
1076
  { id: "canvas.splitDown", accelerator: "CmdOrCtrl+Alt+Shift+\\", when: "canvas-focus", label: "Split down", web: true },
1077
+ // Browsers own ⌘[ / ⌘] for their own history: data for a desktop host (and the header's Back / Forward).
1078
+ { id: "canvas.back", accelerator: "CmdOrCtrl+[", when: "canvas-focus", label: "Back", web: false },
1079
+ { id: "canvas.forward", accelerator: "CmdOrCtrl+]", when: "canvas-focus", label: "Forward", web: false },
1039
1080
  { id: "canvas.closeTab", accelerator: "CmdOrCtrl+W", when: "canvas-focus", label: "Close tab", web: false },
1040
1081
  { id: "canvas.nextTab", accelerator: "Ctrl+Tab", when: "canvas-focus", label: "Next tab", web: false },
1041
1082
  { id: "canvas.previousTab", accelerator: "Ctrl+Shift+Tab", when: "canvas-focus", label: "Previous tab", web: false }
@@ -1085,11 +1126,17 @@ function runCanvasCommand(controller, id, windowId = controller.windowId) {
1085
1126
  if (!frame2?.isOpen || !pane) return false;
1086
1127
  controller.splitPane(pane.id, id === "canvas.splitRight" ? "horizontal" : "vertical", pane.itemIds.length > 1 ? pane.activeItemId ?? void 0 : void 0);
1087
1128
  return true;
1129
+ case "canvas.back":
1130
+ if (!frame2?.isOpen || !pane?.activeItemId) return false;
1131
+ return controller.back(pane.activeItemId) !== null;
1132
+ case "canvas.forward":
1133
+ if (!frame2?.isOpen || !pane?.activeItemId) return false;
1134
+ return controller.forward(pane.activeItemId) !== null;
1088
1135
  default:
1089
1136
  return false;
1090
1137
  }
1091
1138
  }
1092
- var CODE_FOR = { "\\": "Backslash", Tab: "Tab", Escape: "Escape", Enter: "Enter" };
1139
+ var CODE_FOR = { "\\": "Backslash", "[": "BracketLeft", "]": "BracketRight", Tab: "Tab", Escape: "Escape", Enter: "Enter" };
1093
1140
  function matchesCanvasAccelerator(input, accelerator, isMac) {
1094
1141
  const parts = accelerator.split("+");
1095
1142
  const key = parts.pop() ?? "";
@@ -1206,6 +1253,13 @@ function view(shared, windowId) {
1206
1253
  dispatch(canvasActions.back(itemId));
1207
1254
  return canvasItemId(entry.kind, entry.key);
1208
1255
  },
1256
+ forward(itemId) {
1257
+ const forward = store.getState().items[itemId]?.forward ?? [];
1258
+ const entry = forward[forward.length - 1];
1259
+ if (!entry) return null;
1260
+ dispatch(canvasActions.forward(itemId));
1261
+ return canvasItemId(entry.kind, entry.key);
1262
+ },
1209
1263
  newTab(paneId) {
1210
1264
  const state = store.getState();
1211
1265
  const frame2 = canvasWindowFrame(state, windowId);
@@ -1489,6 +1543,16 @@ function selectCanvasItemHistory(s, itemId) {
1489
1543
  return s.items[itemId]?.history ?? NO_HISTORY;
1490
1544
  }
1491
1545
  var selectCanvasCanGoBack = (s, itemId) => (s.items[itemId]?.history?.length ?? 0) > 0;
1546
+ var selectCanvasCanGoForward = (s, itemId) => (s.items[itemId]?.forward?.length ?? 0) > 0;
1547
+ function selectCanvasPresentedPaneId(s, windowId) {
1548
+ const f = frame(s, windowId);
1549
+ if (!f) return null;
1550
+ const ids = listPaneIds(f.layout);
1551
+ const holds = (id) => (s.panes[id]?.itemIds.length ?? 0) > 0;
1552
+ const focused = ids.find((id) => id === f.focusedPaneId);
1553
+ if (focused && holds(focused)) return focused;
1554
+ return ids.find(holds) ?? focused ?? ids[0] ?? null;
1555
+ }
1492
1556
 
1493
1557
  // src/core/presentation.ts
1494
1558
  var CANVAS_PHONE_MAX_WIDTH = 767;
@@ -1538,7 +1602,9 @@ function toggleKind(controller, input) {
1538
1602
  const before = selectCanvasKindVisibility(controller.getState(), input.kind, key);
1539
1603
  if (before === "visible") {
1540
1604
  if ((input.whenVisible ?? "close") === "close") {
1605
+ const windowId = canvasWindowOfItem(controller.getState(), itemId);
1541
1606
  controller.close(itemId);
1607
+ if (windowId && selectCanvasWindowItemCount(controller.getState(), windowId) === 0) controller.hide(windowId);
1542
1608
  } else {
1543
1609
  controller.hide(canvasWindowOfItem(controller.getState(), itemId) ?? void 0);
1544
1610
  }
@@ -1569,6 +1635,7 @@ export {
1569
1635
  CANVAS_MIN_SPLIT_FRACTION,
1570
1636
  CANVAS_MIN_WIDTH,
1571
1637
  CANVAS_NARROW_WIDTH,
1638
+ CANVAS_PANE_MIN_WIDTH,
1572
1639
  CANVAS_PHONE_MAX_WIDTH,
1573
1640
  CANVAS_SHORTCUTS,
1574
1641
  CANVAS_STORAGE_KEY,
@@ -1609,6 +1676,7 @@ export {
1609
1676
  sanitizeCanvasSnapshot,
1610
1677
  selectCanvasActiveItem,
1611
1678
  selectCanvasCanGoBack,
1679
+ selectCanvasCanGoForward,
1612
1680
  selectCanvasExtraWindow,
1613
1681
  selectCanvasFocusedPaneId,
1614
1682
  selectCanvasIsFullscreen,
@@ -1621,6 +1689,7 @@ export {
1621
1689
  selectCanvasLayout,
1622
1690
  selectCanvasPane,
1623
1691
  selectCanvasPaneCount,
1692
+ selectCanvasPresentedPaneId,
1624
1693
  selectCanvasWidth,
1625
1694
  selectCanvasWindowItemCount,
1626
1695
  selectCanvasWindowOfItem,