deck-plugin-sdk 0.15.1 → 0.17.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deck-plugin-sdk",
3
- "version": "0.15.1",
3
+ "version": "0.17.0",
4
4
  "description": "Build a plugin for Deck, outside the Deck repository.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,7 +20,7 @@
20
20
  "README.md"
21
21
  ],
22
22
  "dependencies": {
23
- "esbuild": "^0.25.0"
23
+ "esbuild": "^0.28.0"
24
24
  },
25
25
  "peerDependencies": {
26
26
  "@types/react": "^19.0.0",
@@ -31,12 +31,22 @@
31
31
  "typescript": "^5.0.0"
32
32
  },
33
33
  "peerDependenciesMeta": {
34
- "framer-motion": { "optional": true },
35
- "monaco-editor": { "optional": true },
36
- "typescript": { "optional": true }
34
+ "framer-motion": {
35
+ "optional": true
36
+ },
37
+ "monaco-editor": {
38
+ "optional": true
39
+ },
40
+ "typescript": {
41
+ "optional": true
42
+ }
37
43
  },
38
44
  "engines": {
39
45
  "node": ">=20"
40
46
  },
41
- "keywords": ["deck", "plugin", "sdk"]
47
+ "keywords": [
48
+ "deck",
49
+ "plugin",
50
+ "sdk"
51
+ ]
42
52
  }
package/shim/bridge.js CHANGED
@@ -12,6 +12,8 @@ const b = () => {
12
12
  export const askAssistant = (...a) => b().askAssistant(...a);
13
13
  export const calendarCached = (...a) => b().calendarCached(...a);
14
14
  export const calendarRefresh = (...a) => b().calendarRefresh(...a);
15
+ export const claudeMcpEntry = (...a) => b().claudeMcpEntry(...a);
16
+ export const claudeMcpStatus = (...a) => b().claudeMcpStatus(...a);
15
17
  export const claudePlanLimits = (...a) => b().claudePlanLimits(...a);
16
18
  export const claudeUsage = (...a) => b().claudeUsage(...a);
17
19
  export const clearWallpaper = (...a) => b().clearWallpaper(...a);
@@ -48,12 +50,18 @@ export const fsWrite = (...a) => b().fsWrite(...a);
48
50
  export const fsWriteBytes = (...a) => b().fsWriteBytes(...a);
49
51
  export const getAutostart = (...a) => b().getAutostart(...a);
50
52
  export const getVaultRoot = (...a) => b().getVaultRoot(...a);
53
+ export const ghPrLineComment = (...a) => b().ghPrLineComment(...a);
54
+ export const ghPrThreads = (...a) => b().ghPrThreads(...a);
55
+ export const ghRun = (...a) => b().ghRun(...a);
56
+ export const ghThreadReply = (...a) => b().ghThreadReply(...a);
57
+ export const ghThreadResolve = (...a) => b().ghThreadResolve(...a);
51
58
  export const gitAbortOp = (...a) => b().gitAbortOp(...a);
52
59
  export const gitAiCommitMsg = (...a) => b().gitAiCommitMsg(...a);
53
60
  export const gitBlameLine = (...a) => b().gitBlameLine(...a);
54
61
  export const gitBranchAt = (...a) => b().gitBranchAt(...a);
55
62
  export const gitBranchDelete = (...a) => b().gitBranchDelete(...a);
56
63
  export const gitBranchDeleteRemote = (...a) => b().gitBranchDeleteRemote(...a);
64
+ export const gitBranchRename = (...a) => b().gitBranchRename(...a);
57
65
  export const gitBranches = (...a) => b().gitBranches(...a);
58
66
  export const gitCheckout = (...a) => b().gitCheckout(...a);
59
67
  export const gitCheckoutDetach = (...a) => b().gitCheckoutDetach(...a);
@@ -63,22 +71,32 @@ export const gitCherryPick = (...a) => b().gitCherryPick(...a);
63
71
  export const gitCommit = (...a) => b().gitCommit(...a);
64
72
  export const gitCommitAmend = (...a) => b().gitCommitAmend(...a);
65
73
  export const gitCommitFiles = (...a) => b().gitCommitFiles(...a);
74
+ export const gitContinueOp = (...a) => b().gitContinueOp(...a);
66
75
  export const gitDiscard = (...a) => b().gitDiscard(...a);
67
76
  export const gitFetch = (...a) => b().gitFetch(...a);
77
+ export const gitFetchPr = (...a) => b().gitFetchPr(...a);
68
78
  export const gitFileBytes = (...a) => b().gitFileBytes(...a);
69
79
  export const gitFileDiff = (...a) => b().gitFileDiff(...a);
80
+ export const gitHead = (...a) => b().gitHead(...a);
70
81
  export const gitHeadFile = (...a) => b().gitHeadFile(...a);
71
82
  export const gitIndexFile = (...a) => b().gitIndexFile(...a);
72
83
  export const gitInfo = (...a) => b().gitInfo(...a);
73
84
  export const gitLogGraph = (...a) => b().gitLogGraph(...a);
74
85
  export const gitMerge = (...a) => b().gitMerge(...a);
86
+ export const gitMergeAs = (...a) => b().gitMergeAs(...a);
87
+ export const gitMergeBase = (...a) => b().gitMergeBase(...a);
75
88
  export const gitPendingOp = (...a) => b().gitPendingOp(...a);
76
89
  export const gitPull = (...a) => b().gitPull(...a);
77
90
  export const gitPullRebase = (...a) => b().gitPullRebase(...a);
78
91
  export const gitPush = (...a) => b().gitPush(...a);
79
92
  export const gitPushBranch = (...a) => b().gitPushBranch(...a);
93
+ export const gitRangeFiles = (...a) => b().gitRangeFiles(...a);
94
+ export const gitRangeSubjects = (...a) => b().gitRangeSubjects(...a);
95
+ export const gitRebase = (...a) => b().gitRebase(...a);
80
96
  export const gitRefs = (...a) => b().gitRefs(...a);
97
+ export const gitRemoteUrl = (...a) => b().gitRemoteUrl(...a);
81
98
  export const gitRemotes = (...a) => b().gitRemotes(...a);
99
+ export const gitRepos = (...a) => b().gitRepos(...a);
82
100
  export const gitResetTo = (...a) => b().gitResetTo(...a);
83
101
  export const gitRevert = (...a) => b().gitRevert(...a);
84
102
  export const gitShowFile = (...a) => b().gitShowFile(...a);
@@ -107,6 +125,7 @@ export const historyDelete = (...a) => b().historyDelete(...a);
107
125
  export const httpSend = (...a) => b().httpSend(...a);
108
126
  export const importIconPack = (...a) => b().importIconPack(...a);
109
127
  export const installClaudeHooks = (...a) => b().installClaudeHooks(...a);
128
+ export const installClaudeMcp = (...a) => b().installClaudeMcp(...a);
110
129
  export const installFromRegistry = (...a) => b().installFromRegistry(...a);
111
130
  export const installPlugin = (...a) => b().installPlugin(...a);
112
131
  export const isFullscreen = (...a) => b().isFullscreen(...a);
@@ -151,6 +170,7 @@ export const readMusicTaste = (...a) => b().readMusicTaste(...a);
151
170
  export const readTermLayout = (...a) => b().readTermLayout(...a);
152
171
  export const readTrackMeta = (...a) => b().readTrackMeta(...a);
153
172
  export const removeClaudeHooks = (...a) => b().removeClaudeHooks(...a);
173
+ export const removeClaudeMcp = (...a) => b().removeClaudeMcp(...a);
154
174
  export const restartApp = (...a) => b().restartApp(...a);
155
175
  export const revealInFolder = (...a) => b().revealInFolder(...a);
156
176
  export const runScriptNow = (...a) => b().runScriptNow(...a);
package/shim/terminals.js CHANGED
@@ -12,6 +12,7 @@ const t = () => {
12
12
 
13
13
  export const ZONE_BOARD = (...a) => t().ZONE_BOARD(...a);
14
14
  export const activeWorkspaceCwd = (...a) => t().activeWorkspaceCwd(...a);
15
+ export const addTabToZone = (...a) => t().addTabToZone(...a);
15
16
  export const applyDtCommand = (...a) => t().applyDtCommand(...a);
16
17
  export const applyTerminalLook = (...a) => t().applyTerminalLook(...a);
17
18
  export const attach = (...a) => t().attach(...a);
@@ -33,6 +34,7 @@ export const moveLeaf = (...a) => t().moveLeaf(...a);
33
34
  export const moveWorkspace = (...a) => t().moveWorkspace(...a);
34
35
  export const moveZone = (...a) => t().moveZone(...a);
35
36
  export const mutateLayout = (...a) => t().mutateLayout(...a);
37
+ export const nextActiveAfterClose = (...a) => t().nextActiveAfterClose(...a);
36
38
  export const nextZoneSpot = (...a) => t().nextZoneSpot(...a);
37
39
  export const openTabInWorkspace = (...a) => t().openTabInWorkspace(...a);
38
40
  export const paneCwd = (...a) => t().paneCwd(...a);
@@ -48,6 +50,7 @@ export const restartPane = (...a) => t().restartPane(...a);
48
50
  export const sendInput = (...a) => t().sendInput(...a);
49
51
  export const setLeafLabel = (...a) => t().setLeafLabel(...a);
50
52
  export const setLeafView = (...a) => t().setLeafView(...a);
53
+ export const showActiveTab = (...a) => t().showActiveTab(...a);
51
54
  export const splitLeaf = (...a) => t().splitLeaf(...a);
52
55
  export const suspendWorkspace = (...a) => t().suspendWorkspace(...a);
53
56
  export const switchWorkspace = (...a) => t().switchWorkspace(...a);
@@ -67,4 +70,6 @@ export const workspaceByCwd = (...a) => t().workspaceByCwd(...a);
67
70
  export const workspaceShellCount = (...a) => t().workspaceShellCount(...a);
68
71
  export const zoneAt = (...a) => t().zoneAt(...a);
69
72
  export const zoneBoardFull = (...a) => t().zoneBoardFull(...a);
73
+ export const zoneOf = (...a) => t().zoneOf(...a);
70
74
  export const zoneToward = (...a) => t().zoneToward(...a);
75
+ export const zones = (...a) => t().zones(...a);
@@ -4,5 +4,5 @@
4
4
  "description": "One line on what this is for, shown under its name in Settings.",
5
5
  "icon": "Sparkles",
6
6
  "version": "0.1.0",
7
- "minDeck": "0.15.0"
7
+ "minDeck": "0.17.0"
8
8
  }
@@ -506,9 +506,25 @@ export declare const fsDelete: (path: string) => Promise<void>;
506
506
  export declare const fsCopy: (src: string, dstDir: string) => Promise<string>;
507
507
  export declare const fsReveal: (path: string) => Promise<void>;
508
508
  export declare const installClaudeHooks: () => Promise<void>;
509
+ /**
510
+ * Deck's MCP server, which lets an agent drive Deck's terminals and HTTP client.
511
+ *
512
+ * The server ships with Deck as a single bundled file, so there is nothing to clone or install.
513
+ * Registering it adds one entry to ~/.claude.json — a file that belongs to Claude Code, so Deck
514
+ * backs it up first, merges rather than replaces, and refuses outright if it cannot parse it.
515
+ *
516
+ * `claudeMcpStatus` is "missing", "current", or "stale" — stale meaning an entry exists but
517
+ * points somewhere else, which is what a reinstall to a different folder leaves behind.
518
+ */
519
+ export declare const claudeMcpStatus: () => Promise<string>;
520
+ export declare const claudeMcpEntry: () => Promise<string>;
521
+ export declare const installClaudeMcp: () => Promise<string>;
522
+ export declare const removeClaudeMcp: () => Promise<void>;
509
523
  export declare const removeClaudeHooks: () => Promise<void>;
510
524
  export declare const notifyOs: (title: string, body: string) => Promise<void>;
511
525
  export declare const gitStatus: (dir: string) => Promise<GitStatusRow[]>;
526
+ /** The repos a folder holds: the one it is inside first, then any below it. Each a repo's top level. */
527
+ export declare const gitRepos: (dir: string) => Promise<string[]>;
512
528
  export interface BlameLine {
513
529
  hash: string;
514
530
  author: string;
@@ -591,9 +607,37 @@ export declare const gitUndoCommit: (dir: string) => Promise<string>;
591
607
  export declare const gitBranchAt: (dir: string, name: string, rev: string) => Promise<string>;
592
608
  export declare const gitRevert: (dir: string, rev: string) => Promise<string>;
593
609
  export declare const gitCherryPick: (dir: string, rev: string) => Promise<string>;
594
- export declare const gitResetTo: (dir: string, rev: string, mode: "soft" | "mixed" | "hard") => Promise<string>;
610
+ export declare const gitResetTo: (dir: string, rev: string, mode: "soft" | "mixed" | "hard" | "keep") => Promise<string>;
595
611
  export declare const gitTag: (dir: string, name: string, rev: string, message?: string) => Promise<string>;
596
612
  export declare const gitMerge: (dir: string, rev: string, noFf?: boolean) => Promise<string>;
613
+ /** Rebase the current branch onto `onto`; a conflict leaves it pending (gitPendingOp → "rebase"). */
614
+ export declare const gitRebase: (dir: string, onto: string) => Promise<string>;
615
+ /** HEAD's commit and branch ("" when detached) — what an undo returns to. */
616
+ export declare const gitHead: (dir: string) => Promise<{
617
+ hash: string;
618
+ branch: string;
619
+ }>;
620
+ /** Merge `rev` into the current branch: a merge commit always, fast-forward when possible, or squashed into one commit. */
621
+ export declare const gitMergeAs: (dir: string, rev: string, style: "no-ff" | "ff" | "squash") => Promise<string>;
622
+ /** Finish a merge, rebase, revert or cherry-pick (what gitPendingOp reported) once its conflicts are staged. */
623
+ export declare const gitContinueOp: (dir: string, op: string) => Promise<string>;
624
+ /** Subjects of the commits on `head` that `base` lacks, oldest first. */
625
+ export declare const gitRangeSubjects: (dir: string, base: string, head: string) => Promise<string[]>;
626
+ export declare const gitRemoteUrl: (dir: string, name: string) => Promise<string>;
627
+ /** The GitHub CLI, `gh pr …` or `gh repo view …` only. Rejects with "gh-missing" when gh is not installed. */
628
+ export declare const ghRun: (dir: string, args: string[]) => Promise<string>;
629
+ /** Pull request N's review threads, as GitHub's GraphQL JSON (parsed by prs.ts parseThreads). */
630
+ export declare const ghPrThreads: (dir: string, number: number) => Promise<string>;
631
+ /** Comment on one line of a PR's diff: side "RIGHT" is the new version, "LEFT" the old. */
632
+ export declare const ghPrLineComment: (dir: string, number: number, commit: string, path: string, line: number, side: "LEFT" | "RIGHT", body: string) => Promise<string>;
633
+ export declare const ghThreadReply: (dir: string, thread: string, body: string) => Promise<string>;
634
+ export declare const ghThreadResolve: (dir: string, thread: string, resolve: boolean) => Promise<string>;
635
+ /** The files that differ between two revisions. */
636
+ export declare const gitRangeFiles: (dir: string, from: string, to: string) => Promise<CommitFile[]>;
637
+ export declare const gitMergeBase: (dir: string, a: string, b: string) => Promise<string>;
638
+ /** Fetch pull request N's head from `remote`, so its commits can be diffed here. */
639
+ export declare const gitFetchPr: (dir: string, remote: string, number: number, base: string) => Promise<string>;
640
+ export declare const gitBranchRename: (dir: string, from: string, to: string) => Promise<string>;
597
641
  export declare const gitPendingOp: (dir: string) => Promise<string>;
598
642
  export declare const gitAbortOp: (dir: string, op: string) => Promise<string>;
599
643
  export declare const gitBranchDelete: (dir: string, name: string, force?: boolean) => Promise<string>;
@@ -0,0 +1,90 @@
1
+ // GENERATED by sdk/gen-types.mjs from Deck's source — do not edit.
2
+ /**
3
+ * Where a binding applies.
4
+ *
5
+ * Not decoration. App.tsx renders exactly one module and keys it by id, so navigating UNMOUNTS
6
+ * the previous one: Alt+Z and Ctrl+Alt+Space are window listeners today, but they only exist
7
+ * while Workspaces is on screen. A registry that ignored that would silently make every binding
8
+ * global, which is a behaviour change nobody asked for.
9
+ *
10
+ * It is also what lets two bindings share a chord honestly — the editor's save and a terminal
11
+ * action can both be Ctrl+S if the two surfaces never have the keyboard at the same time.
12
+ */
13
+ export type Scope = "app" | "workspaces" | "editor" | "http" | "terminal" | `plugin:${string}`;
14
+ /** Which thing owns the chord. Absent means a plain window listener. */
15
+ export type Surface = "monaco" | "xterm" | "codemirror";
16
+ export interface KeyBinding {
17
+ /** Stable and namespaced — "app:palette", "plugin:db:run". The id is what an override keys on,
18
+ * so renaming one silently drops a user's binding. Treat these as permanent. */
19
+ id: string;
20
+ /** What the settings panel shows. */
21
+ title: string;
22
+ scope: Scope;
23
+ /** Canonical form, e.g. "ctrl+shift+p". Never a display string. */
24
+ defaultChord: string;
25
+ surface?: Surface;
26
+ }
27
+ /**
28
+ * A key event as its canonical chord, or "" when the event is a bare modifier.
29
+ *
30
+ * A modifier alone is not a chord — holding Ctrl fires keydown with key "Control", and treating
31
+ * that as a binding would make every chord fire its own prefix on the way down.
32
+ */
33
+ export declare function parseChord(e: Pick<KeyboardEvent, "key" | "code" | "ctrlKey" | "altKey" | "shiftKey" | "metaKey">): string;
34
+ /** Put a chord's parts in the canonical order, so a hand-edited config file still matches. */
35
+ export declare function normalizeChord(chord: string): string;
36
+ /** A chord as a person reads it: "ctrl+shift+p" -> "Ctrl+Shift+P". */
37
+ export declare function formatChord(chord: string): string;
38
+ /**
39
+ * What the user changed, and nothing else.
40
+ *
41
+ * A sparse map, never a full snapshot of the table. Deck already stores exceptions this way for
42
+ * the footer's order and for which plugins are off, and for the same reason: a binding added in
43
+ * a later version has to appear for someone who has customised something else, rather than being
44
+ * masked by a stale copy of the old table. An empty string means "unbound", which is different
45
+ * from absent (= use the default).
46
+ */
47
+ export type Overrides = Record<string, string>;
48
+ /** The chord in force for every binding: its default, with any override applied. */
49
+ export declare function resolve(bindings: readonly KeyBinding[], overrides: Overrides): Map<string, string>;
50
+ /** The reverse: which binding owns a chord in a given scope. Keyed "scope\u0000chord". */
51
+ export declare function chordIndex(bindings: readonly KeyBinding[], overrides: Overrides): Map<string, string>;
52
+ /**
53
+ * The binding already holding `chord` in `scope`, or null.
54
+ *
55
+ * Scope-aware on purpose: the same chord in two scopes that never have the keyboard at once is
56
+ * not a clash, and refusing it would be a lie. Only a collision within one scope blocks a save.
57
+ */
58
+ export declare function conflict(bindings: readonly KeyBinding[], overrides: Overrides, id: string, chord: string): KeyBinding | null;
59
+ /**
60
+ * Whether `scope` is live given the scopes currently active.
61
+ *
62
+ * "app" is always live. Everything else has to be in the set, which is what stops a Workspaces
63
+ * chord firing while the Editor has the screen.
64
+ */
65
+ export declare function scopeActive(scope: Scope, active: ReadonlySet<Scope>): boolean;
66
+ /** The binding to run for an event, or null. Scope-aware, so the caller need not filter. */
67
+ export declare function match(bindings: readonly KeyBinding[], overrides: Overrides, e: Parameters<typeof parseChord>[0], active: ReadonlySet<Scope>): KeyBinding | null;
68
+ /**
69
+ * A chord as Monaco's keycode bitmask, or null when Monaco has no code for it.
70
+ *
71
+ * Monaco owns its own keybindings, so a chord bound to something it hosts has to be handed over
72
+ * in its terms. Kept here beside the parsing rather than in the editor, so there is still one
73
+ * place that knows what a chord is.
74
+ *
75
+ * `mods` and `codes` are passed in rather than imported: this file must not pull in
76
+ * monaco-editor, which is megabytes and would land in every check that touches keys.ts.
77
+ */
78
+ export declare function toMonaco(chord: string, mods: {
79
+ CtrlCmd: number;
80
+ Alt: number;
81
+ Shift: number;
82
+ WinCtrl: number;
83
+ }, codes: Record<string, number>): number | null;
84
+ /**
85
+ * Deck's built-in bindings.
86
+ *
87
+ * Plugin bindings are not here — they arrive at load time through the `keybindings` export and
88
+ * are concatenated on, so a disabled plugin's chords leave the table with it.
89
+ */
90
+ export declare const DEFAULT_BINDINGS: readonly KeyBinding[];
@@ -0,0 +1,45 @@
1
+ // GENERATED by sdk/gen-types.mjs from Deck's source — do not edit.
2
+ import { type KeyBinding, type Overrides, type Scope } from "./keys";
3
+ export declare function setPluginBindings(list: readonly KeyBinding[]): void;
4
+ /** Every binding in force: Deck's own, plus whatever the loaded plugins contribute. */
5
+ export declare function allBindings(): readonly KeyBinding[];
6
+ /** Read the saved overrides. Safe to call repeatedly; only the first does any work. */
7
+ export declare function loadOverrides(): Promise<void>;
8
+ export declare function getOverrides(): Overrides;
9
+ /**
10
+ * Rebind one command, or clear it with "".
11
+ *
12
+ * The whole map is written, but only the entries that differ from the defaults are IN the map —
13
+ * see the note on Overrides in keys.ts. Setting a binding back to its default removes its entry
14
+ * rather than recording it, so the file stays a list of exceptions.
15
+ */
16
+ export declare function setOverride(id: string, chord: string): Promise<void>;
17
+ /** Drop every override, returning the whole table to its defaults. */
18
+ export declare function resetOverrides(): Promise<void>;
19
+ /** Subscribe for React. */
20
+ export declare function useKeybindings(): number;
21
+ /** The chord in force for a binding, for a label or a tooltip. */
22
+ export declare function chordFor(id: string): string;
23
+ /** Register one action. Returns a disposer; call it on unmount. */
24
+ export declare function onKey(id: string, run: () => void): () => void;
25
+ /** Register several at once — what a module with a dozen chords wants. */
26
+ export declare function onKeys(map: Record<string, () => void>): () => void;
27
+ /** Claim a scope. Returns a disposer. */
28
+ export declare function pushScope(scope: Scope): () => void;
29
+ export declare function activeScopes(): ReadonlySet<Scope>;
30
+ /**
31
+ * Run whatever this event is bound to, and say whether anything did.
32
+ *
33
+ * The caller decides about preventDefault — this cannot, because some bindings want the default
34
+ * suppressed (the palette) and some do not (a chord whose action declines to act, like Ctrl+7
35
+ * with only three projects open).
36
+ */
37
+ export declare function dispatch(e: KeyboardEvent): boolean;
38
+ /**
39
+ * Whether a chord belongs to something OTHER than a focused terminal.
40
+ *
41
+ * xterm's key handler asks this to decide what to swallow, so the swallow list is derived from
42
+ * the table rather than written out a second time. The hand-maintained copy it replaces had
43
+ * already drifted: Ctrl+Alt+N and Ctrl+` were missing from it, so both reached the shell.
44
+ */
45
+ export declare function claimedOutsideTerminal(e: KeyboardEvent): boolean;
@@ -37,6 +37,12 @@ export interface LayoutTab {
37
37
  root: LayoutNode;
38
38
  x?: number;
39
39
  y?: number;
40
+ /**
41
+ * Behind another tab in the same zone. Tabs sharing a cell are one zone, and the one without
42
+ * this flag is the one it shows — so a zone reopens on the tab you left it on. Absent on every
43
+ * tab that has a cell to itself, which is every tab written before zones held several.
44
+ */
45
+ hidden?: boolean;
40
46
  }
41
47
  export interface LayoutWorkspace {
42
48
  id: string;
@@ -63,18 +69,31 @@ export declare function switchWorkspace(wsId: string, tabId?: string): void;
63
69
  * placement below has to agree with what the overview can draw.
64
70
  */
65
71
  export declare const ZONE_BOARD = 3;
66
- /**
67
- * Where a zone sits on the canvas: its stored coordinates, else its place in the list.
68
- *
69
- * The fallback is what makes zones work on a layout that predates them — the tabs you already
70
- * have become the first row, left to right, without rewriting anything on disk.
71
- */
72
+ /** Where a tab sits on the canvas — which is to say, which zone it belongs to. */
72
73
  export declare function zoneAt(tabs: readonly LayoutTab[], id: string, cols?: number): {
73
74
  x: number;
74
75
  y: number;
75
76
  };
77
+ /** One zone: a cell, the tabs in it in list order, and the one it shows. */
78
+ export interface Zone {
79
+ x: number;
80
+ y: number;
81
+ tabs: LayoutTab[];
82
+ front: LayoutTab;
83
+ }
76
84
  /**
77
- * The zone to move to from `id` going `dir`, or null when there is none that way.
85
+ * The tabs grouped into zones, one per occupied cell, in the order each cell first appears.
86
+ *
87
+ * The front tab is the one not marked `hidden` — the last one shown there, which is what makes a
88
+ * zone open on the tab you left it on. A cell whose tabs are ALL hidden (its front was closed
89
+ * from outside, by an agent) shows its first, rather than nothing.
90
+ */
91
+ export declare function zones(tabs: readonly LayoutTab[], cols?: number): Zone[];
92
+ /** The zone holding a tab. */
93
+ export declare function zoneOf(tabs: readonly LayoutTab[], id: string): Zone | undefined;
94
+ /**
95
+ * The zone to move to from `id` going `dir`, as the tab it would show, or null when there is
96
+ * none that way.
78
97
  *
79
98
  * Nearest in a straight line: same row for left/right, same column for up/down, smallest step.
80
99
  * No wrapping — running out of canvas should feel like an edge, not teleport you across it.
@@ -83,17 +102,37 @@ export declare function zoneToward(tabs: readonly LayoutTab[], id: string, dir:
83
102
  /**
84
103
  * Put a zone at a spot on the canvas, swapping with whatever is already there.
85
104
  *
105
+ * Every tab in the zone moves, since the zone IS the tabs sharing a cell — moving only the one
106
+ * named would split it in two.
107
+ *
86
108
  * Swap rather than push-and-shuffle: a zone you did not touch should not move because another
87
109
  * one arrived next to it, and with sparse coordinates there is no row to shuffle along anyway.
88
- *
89
- * Every tab gets explicit coordinates on the way out, including ones that were still relying on
90
- * the index fallback — otherwise moving one zone would silently shift the unplaced ones, since
91
- * their position depends on where they sit in the list.
92
110
  */
93
111
  export declare function moveZone(tabs: readonly LayoutTab[], id: string, to: {
94
112
  x: number;
95
113
  y: number;
96
114
  }): LayoutTab[];
115
+ /**
116
+ * Add a tab to the zone holding `besideId`, as the tab it shows.
117
+ *
118
+ * The others there are marked hidden here rather than left to the active-tab normaliser, so the
119
+ * result is right even for a zone that is not the active one.
120
+ */
121
+ export declare function addTabToZone(tabs: readonly LayoutTab[], besideId: string, tab: LayoutTab): LayoutTab[];
122
+ /**
123
+ * Make a workspace's active tab the one its zone shows.
124
+ *
125
+ * Run on every layout write (mutateLayout), so whatever made a tab active — a click in the
126
+ * strip, the compass, an agent notification jumping to a pane in a tab behind another — the
127
+ * zone ends up showing it, and remembers it next time. One place rather than every caller that
128
+ * sets activeTab, of which there are many and will be more.
129
+ */
130
+ export declare function showActiveTab(w: LayoutWorkspace): LayoutWorkspace;
131
+ /**
132
+ * Which tab becomes active when `closing` goes: another tab in the same zone if it has one, so
133
+ * closing a tab does not also throw you out of the zone; otherwise the first zone left.
134
+ */
135
+ export declare function nextActiveAfterClose(tabs: readonly LayoutTab[], closing: string): string | undefined;
97
136
  /** The first free spot on the top row, for a zone created without a position of its own. */
98
137
  export declare function nextZoneSpot(tabs: readonly LayoutTab[], cols?: number, rows?: number): {
99
138
  x: number;
@@ -102,6 +141,8 @@ export declare function nextZoneSpot(tabs: readonly LayoutTab[], cols?: number,
102
141
  /**
103
142
  * Is there nowhere left to put a zone?
104
143
  *
144
+ * Counts zones, not tabs: a zone can hold several tabs and still take one cell.
145
+ *
105
146
  * The board is a fixed 3x3 — deliberately, so the overview and the Ctrl+Alt compass are always
106
147
  * the same nine cells. A tenth zone has no spot: it drew off the edge of the overview and the
107
148
  * compass could not reach it, so it existed but could not be seen or switched to.