bmux_tabs_plugin_api 0.0.1-alpha.4

Typed public API of the bmux tabs plugin (BPDL-generated bindings)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
// Public, typed contract of the tabs plugin. Other plugins that
// depend on `bmux_tabs_plugin_api` import these interfaces rather
// than the tabs plugin implementation itself.
//
// Four interfaces:
// - `tabs-state` — read-only queries about panes and tabs.
// - `tabs-commands` — mutating operations on panes and tabs.
// - `tabs-events` — event stream of tab/pane lifecycle changes.
// - `tabs-list` — reactive state channel carrying the ordered
//   tab list for tab-bar rendering + positional navigation.

plugin bmux.tabs version 1;

capability TABS_READ = bmux.tabs.read;
capability TABS_WRITE = bmux.tabs.write;

@capability(TABS_WRITE)
interface tabs-placement-v1 {
    // Append a live tab to the destination arrangement before changing its
    // workspace attribute. Source references are retained, never destroyed.
    // Retrying an interrupted transfer is idempotent.
    command place-tab(context-id: uuid, workspace-id: uuid) -> result<unit, string>;
}

@capability(TABS_READ)
interface tabs-state {
    // A pane's current lifecycle status.
    variant pane-status {
        @default running,
        exited { exit-code: i32 },
    }

    // Snapshot of a single pane's state.
    record pane-state {
        id: uuid,
        session-id: uuid,
        focused: bool,
        zoomed: bool,
        name: string?,
        status: pane-status,
    }

    // Snapshot of a floating pane surface exposed through the tabs UX facade.
    record floating-pane-state {
        id: uuid,
        pane-id: uuid,
        session-id: uuid,
        anchor-pane-id: uuid?,
        context-id: uuid?,
        client-id: uuid?,
        x: u16,
        y: u16,
        w: u16,
        h: u16,
        z: i32,
        layer: string,
        scope: string,
        visible: bool,
    }

    // Snapshot of a tab. Tabs are a plugin-layered view over
    // sessions/contexts; the id is the underlying context id.
    record tab-entry {
        id: string,
        name: string,
        active: bool,
        workspace: string,
        workspace-id: uuid,
    }

    // Pane-level queries.
    query pane-state(id: uuid) -> pane-state?;
    query focused-pane(session: uuid) -> uuid?;
    query zoomed-pane(session: uuid) -> uuid?;
    query list-panes(session: uuid) -> list<pane-state>;
    query list-floating-panes(session: uuid?) -> list<floating-pane-state>;

    record active-tab-pane-set {
        tab-id: uuid,
        session-id: uuid,
        pane-ids: list<uuid>,
    }

    variant active-tab-pane-query-error {
        no-active-tab,
        no-selected-session,
        failed { reason: string },
    }

    // Tab-level queries.
    query list-tabs(session: string?) -> list<tab-entry>;
    query active-tab-panes()
        -> result<active-tab-pane-set, active-tab-pane-query-error>;
}

@capability(TABS_READ)
interface tabs-catalog-v1 {
    // Complete discovery in workspace order, then each workspace's tab arrangement.
    // Reading this catalog never initializes or rewrites arrangement storage.
    query list-tabs() -> result<list<tabs-state.tab-entry>, string>;
}

@capability(TABS_WRITE)
interface tabs-commands {
    // Errors a focus command can produce.
    variant focus-error {
        pane-not-found,
        focus-denied { reason: string },
    }

    // Errors a close command can produce.
    variant close-error {
        pane-not-found,
        close-denied { reason: string },
    }

    // Errors a tab-level command can produce.
    variant tab-error {
        not-found,
        denied { reason: string },
        failed { reason: string },
    }

    // Errors a pane mutation command (split/launch/resize/zoom/
    // restart) can produce.
    variant pane-mutation-error {
        pane-not-found,
        session-not-found,
        invalid-argument { reason: string },
        denied { reason: string },
        failed { reason: string },
    }

    // Acknowledgement returned by mutating tab commands. When a new
    // tab is created, `id` carries the new context id; other
    // commands may leave it empty.
    record tab-ack {
        ok: bool,
        id: string?,
    }

    // Acknowledgement returned by pane-mutation commands. `pane-id`
    // carries the freshly-created pane id for split/launch; other
    // commands leave it empty.
    record pane-ack {
        ok: bool,
        pane-id: uuid?,
    }

    // Acknowledgement returned by `zoom-pane`. Carries the pane that
    // was toggled plus the post-toggle zoom state so callers can
    // render an accurate status without a follow-up query.
    record pane-zoom-ack {
        pane-id: uuid,
        zoomed: bool,
    }

    // Direction in which a pane split/focus action moves.
    enum pane-direction {
        @default horizontal,
        vertical,
        left,
        right,
        up,
        down,
    }

    // Directional intent for resizing a pane. Increase/decrease grow or
    // shrink the focused pane automatically; physical directions move
    // the nearest boundary on that side.
    enum pane-resize-direction {
        @default increase,
        decrease,
        left,
        right,
        up,
        down,
    }

    // Directional intent for moving a floating pane.
    enum floating-pane-move-direction {
        @default right,
        left,
        up,
        down,
    }

    // Placement for moving a tab relative to another tab.
    enum tab-move-placement {
        @default before,
        after,
    }

    // Selector identifying a session or pane. Exactly one of `id`,
    // `name`, or `index` should be populated. `id` wins when multiple
    // are set. `index` is only meaningful for pane selectors scoped
    // to a session.
    record selector {
        id: uuid?,
        name: string?,
        index: u32?,
    }

    // Pane-level commands.
    command focus-pane(id: uuid) -> result<unit, focus-error>;
    command close-pane(id: uuid) -> result<unit, close-error>;
    // Selector-targeted variants. These accept the full `selector`
    // record (id / name / index / active-by-empty) so callers that
    // address panes by position within a session don't have to resolve
    // an id first.
    command focus-pane-by-selector(session: selector?, target: selector)
        -> result<pane-ack, pane-mutation-error>;
    command close-pane-by-selector(session: selector?, target: selector)
        -> result<pane-ack, pane-mutation-error>;
    // Close the currently-active pane in the targeted session.
    // Equivalent to `close-pane-by-selector` with an empty target, but
    // gives consumers a dedicated name that documents the "close active
    // pane" semantic.
    command close-active-pane(session: selector?)
        -> result<pane-ack, pane-mutation-error>;
    // Focus the next/previous pane in the layout relative to the
    // currently-active pane. The direction is interpreted by the host
    // today; once the tabs plugin owns focus ordering internally,
    // this shifts to a plugin-local lookup.
    command focus-pane-in-direction(session: selector?, direction: pane-direction)
        -> result<pane-ack, pane-mutation-error>;

    // Split / launch / resize / zoom / restart.
    // Each targets a session (by uuid or name) and an existing pane
    // (likewise), plus operation-specific extras.
    command split-pane(
        session: selector?,
        target: selector?,
        direction: pane-direction,
        ratio-pct: u32?,
    ) -> result<pane-ack, pane-mutation-error>;

    command launch-pane(
        session: selector?,
        target: selector?,
        direction: pane-direction,
        name: string?,
        program: string,
        args: list<string>,
    ) -> result<pane-ack, pane-mutation-error>;

    command resize-pane(
        session: selector?,
        target: selector?,
        direction: pane-resize-direction,
        cells: u16,
    ) -> result<pane-ack, pane-mutation-error>;

    command zoom-pane(
        session: selector?,
    ) -> result<pane-zoom-ack, pane-mutation-error>;

    command restart-pane(
        session: selector?,
        target: selector?,
    ) -> result<pane-ack, pane-mutation-error>;

    command create-floating-pane(
        session: selector?,
        target: selector?,
        x: u16?,
        y: u16?,
        w: u16?,
        h: u16?,
        z: i32?,
        layer: string?,
        scope: string?,
        program: string?,
        args: list<string>,
    ) -> result<pane-ack, pane-mutation-error>;

    command move-floating-pane(
        session: selector?,
        target: selector,
        x: u16,
        y: u16,
    ) -> result<pane-ack, pane-mutation-error>;

    command resize-floating-pane(
        session: selector?,
        target: selector,
        w: u16,
        h: u16,
    ) -> result<pane-ack, pane-mutation-error>;

    command move-active-floating-pane(
        session: selector?,
        direction: floating-pane-move-direction,
        cells: u16,
    ) -> result<pane-ack, pane-mutation-error>;

    command resize-active-floating-pane(
        session: selector?,
        direction: pane-resize-direction,
        cells: u16,
    ) -> result<pane-ack, pane-mutation-error>;

    command focus-floating-pane(session: selector?, target: selector)
        -> result<pane-ack, pane-mutation-error>;
    command raise-floating-pane(session: selector?, target: selector)
        -> result<pane-ack, pane-mutation-error>;
    command lower-floating-pane(session: selector?, target: selector)
        -> result<pane-ack, pane-mutation-error>;
    command close-floating-pane(session: selector?, target: selector)
        -> result<pane-ack, pane-mutation-error>;
    command focus-next-floating-pane(session: selector?)
        -> result<pane-ack, pane-mutation-error>;
    command raise-active-floating-pane(session: selector?)
        -> result<pane-ack, pane-mutation-error>;
    command lower-active-floating-pane(session: selector?)
        -> result<pane-ack, pane-mutation-error>;
    command close-active-floating-pane(session: selector?)
        -> result<pane-ack, pane-mutation-error>;

    command set-floating-pane-z(
        session: selector?,
        target: selector,
        z: i32,
    ) -> result<pane-ack, pane-mutation-error>;

    command set-floating-pane-layer(
        session: selector?,
        target: selector,
        layer: string,
    ) -> result<pane-ack, pane-mutation-error>;

    // Tab-level commands.
    command new-tab(name: string?) -> result<tab-ack, tab-error>;
    command rename-tab(name: string) -> result<tab-ack, tab-error>;
    // Rename a specific tab by id, independent of which tab is current.
    command rename-tab-by-id(id: uuid, name: string)
        -> result<tab-ack, tab-error>;
    command kill-tab(target: string, force-local: bool) -> result<tab-ack, tab-error>;
    command kill-all-tabs(force-local: bool) -> result<tab-ack, tab-error>;
    command switch-tab(target: string) -> result<tab-ack, tab-error>;
    command move-tab(source: uuid, target: uuid, placement: tab-move-placement)
        -> result<tab-ack, tab-error>;
}

interface tabs-events {
    // A single event describing a pane-level state change.
    variant pane-event {
        // A pane gained focus.
        focused { pane-id: uuid },
        // A pane lost focus.
        unfocused { pane-id: uuid },
        // A pane was zoomed to fill its session viewport.
        zoomed { pane-id: uuid },
        // A zoomed pane was restored.
        unzoomed { pane-id: uuid },
        // A new pane was opened.
        opened { pane-id: uuid, session-id: uuid },
        // A pane was closed.
        closed { pane-id: uuid },
        // The pane's lifecycle status changed (e.g., running -> exited).
        status-changed { pane-id: uuid },
    }

    events pane-event;
}

// Reactive state channel carrying the ordered list of tabs in the
// order consumers should render them (and navigate through). The
// tabs plugin owns the authoritative tab order (persisted in its
// plugin-local storage under `tabs.order`) and publishes a fresh
// snapshot on every mutation: new-tab, rename-tab,
// switch-tab, move-tab, kill-tab, kill-all-tabs,
// goto-tab, close-current-tab, next/prev/last tab.
//
// Subscribers (notably the attach tab bar) consume this via
// `EventBus::subscribe_state::<TabListSnapshot>` to observe the
// current ordered list synchronously plus a live-update receiver.
// This lets the tab bar render the exact order the plugin maintains
// and lets next/prev navigation walk the same order without a
// round-trip query per frame.
//
// Payload semantics:
// - `tabs` is the ordered list of live tabs; consumers render
//   positional indices (`1:`, `2:`, …) from this order.
// - `active` on each entry is `true` for the current active tab
//   (as resolved by the plugin's effective-current-context logic) and
//   `false` otherwise.
// - `revision` is a monotonic counter advancing on every publish;
//   consumers can use it to deduplicate or order updates.
// Client-local selection, published by attach rather than the shared catalog.
interface tabs-local-view {
    record tab-selection {
        context-id: uuid?,
    }

    @state events tab-selection;
}

interface tabs-list {
    record tab-list-snapshot {
        tabs: list<tab-list-entry>,
        revision: u64,
    }

    record tab-list-entry {
        id: uuid,
        name: string,
        active: bool,
        workspace: string,
        workspace-id: uuid,
    }

    @state events tab-list-snapshot;
}