@neosh/api 0.3.0 → 0.4.1

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": "@neosh/api",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "The neosh plugin API. Types are generated from the Rust side and drift-checked in CI.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -5,6 +5,7 @@ import type { Capability } from "./Capability";
5
5
  import type { CursorMotion } from "./CursorMotion";
6
6
  import type { CursorShape } from "./CursorShape";
7
7
  import type { DiffTarget } from "./DiffTarget";
8
+ import type { Direction } from "./Direction";
8
9
  import type { DriverKind } from "./DriverKind";
9
10
  import type { ExtmarkId } from "./ExtmarkId";
10
11
  import type { ExtmarkOpts } from "./ExtmarkOpts";
@@ -25,17 +26,20 @@ import type { NodeId } from "./NodeId";
25
26
  import type { NoticeKind } from "./NoticeKind";
26
27
  import type { OptionSpec } from "./OptionSpec";
27
28
  import type { OptionValue } from "./OptionValue";
29
+ import type { PaneId } from "./PaneId";
28
30
  import type { PermissionMode } from "./PermissionMode";
29
31
  import type { ProjectKey } from "./ProjectKey";
30
32
  import type { ProviderEvent } from "./ProviderEvent";
31
33
  import type { QuotaSnapshot } from "./QuotaSnapshot";
32
34
  import type { Rect } from "./Rect";
35
+ import type { ScrollAmount } from "./ScrollAmount";
33
36
  import type { SelectShape } from "./SelectShape";
34
37
  import type { SessionId } from "./SessionId";
35
38
  import type { StatusSegment } from "./StatusSegment";
36
39
  import type { StreamId } from "./StreamId";
37
40
  import type { SurfaceCell } from "./SurfaceCell";
38
41
  import type { SurfaceId } from "./SurfaceId";
42
+ import type { TabId } from "./TabId";
39
43
  import type { TextEdit } from "./TextEdit";
40
44
  import type { ToolDef } from "./ToolDef";
41
45
  import type { UsageResolution } from "./UsageResolution";
@@ -55,6 +59,7 @@ export type ApiCall =
55
59
  */
56
60
  kind?: string | null;
57
61
  }
62
+ | { "call": "buf_delete"; buf: BufferId }
58
63
  | { "call": "buf_line_count"; buf: BufferId }
59
64
  | {
60
65
  "call": "buf_get_lines";
@@ -109,7 +114,40 @@ export type ApiCall =
109
114
  | { "call": "win_set_cursor"; win: WindowId; row: number; col: number }
110
115
  | { "call": "win_get_viewport"; win: WindowId }
111
116
  | { "call": "win_scroll_to"; win: WindowId; top_line: number | null }
117
+ | { "call": "win_scroll"; win: WindowId; amount: ScrollAmount }
112
118
  | { "call": "win_list" }
119
+ | {
120
+ "call": "pane_split";
121
+ pane: PaneId;
122
+ /**
123
+ * Which side of `pane` the new one takes. `Right` and `Down` put it after `pane` in the
124
+ * split; `Left` and `Up` put it before.
125
+ */
126
+ dir: Direction;
127
+ }
128
+ | { "call": "pane_close"; pane: PaneId }
129
+ | { "call": "pane_focus"; pane: PaneId }
130
+ | { "call": "pane_focus_dir"; view?: ViewId | null; dir: Direction }
131
+ | { "call": "pane_resize"; pane: PaneId; dir: Direction; delta: number }
132
+ | { "call": "pane_swap"; pane: PaneId; with: PaneId }
133
+ | { "call": "pane_move_edge"; pane: PaneId; dir: Direction }
134
+ | { "call": "pane_equalize"; view?: ViewId | null }
135
+ | {
136
+ "call": "tab_new";
137
+ view?: ViewId | null;
138
+ title?: string | null;
139
+ /**
140
+ * Whether to go to it. A tab opened by a key is; a tab opened by an orchestrator putting
141
+ * work somewhere is not, for the same reason `SessionNew { activate: false }` exists.
142
+ */
143
+ activate: boolean;
144
+ }
145
+ | { "call": "tab_close"; tab: TabId }
146
+ | { "call": "tab_select"; tab: TabId }
147
+ | { "call": "tab_step"; view?: ViewId | null; delta: number }
148
+ | { "call": "tab_move"; tab: TabId; to: number }
149
+ | { "call": "tab_rename"; tab: TabId; title: string | null }
150
+ | { "call": "tab_list"; view?: ViewId | null }
113
151
  | {
114
152
  "call": "win_motion";
115
153
  win: WindowId;
@@ -370,6 +408,7 @@ export type ApiCall =
370
408
  | { "call": "swarm_reconnect"; node: NodeId }
371
409
  | { "call": "swarm_disconnect"; node: NodeId }
372
410
  | { "call": "swarm_strangers" }
411
+ | { "call": "swarm_browse"; node: NodeId; prefix: string }
373
412
  | { "call": "quota_list" }
374
413
  | { "call": "quota_refresh"; instance?: InstanceId | null }
375
414
  | { "call": "quota_report"; snapshot: QuotaSnapshot }
@@ -441,12 +480,37 @@ export type ApiCall =
441
480
  | { "call": "git_stage"; paths: Array<string> }
442
481
  | { "call": "git_unstage"; paths: Array<string> }
443
482
  | { "call": "git_commit"; message: string }
483
+ | {
484
+ "call": "git_fetch";
485
+ /**
486
+ * The repository to fetch in. The conversation's own when absent.
487
+ */
488
+ cwd?: string | null;
489
+ }
444
490
  | {
445
491
  "call": "git_pull";
446
492
  /**
447
493
  * The repository to pull in. The conversation's own when absent.
448
494
  */
449
495
  cwd?: string | null;
496
+ /**
497
+ * Replay this branch's own commits on top of what arrived, rather than merging.
498
+ *
499
+ * A flag rather than a call of its own because it is one argument to one command, and the
500
+ * caller that needs it is the one that has just been told the branch diverged — at which
501
+ * point "rebase or merge" is the question being answered, and two entry points for the two
502
+ * answers would be two things to keep in step for no gain.
503
+ *
504
+ * It rewrites local commits, which is why nothing sets it without asking first.
505
+ */
506
+ rebase: boolean;
507
+ }
508
+ | {
509
+ "call": "forge_pulls";
510
+ /**
511
+ * The repository to ask about. The conversation's own when absent.
512
+ */
513
+ cwd?: string | null;
450
514
  }
451
515
  | {
452
516
  "call": "git_add_worktree";
@@ -470,6 +534,32 @@ export type ApiCall =
470
534
  */
471
535
  cwd?: string | null;
472
536
  }
537
+ | {
538
+ "call": "git_clone";
539
+ /**
540
+ * Anything `git clone` takes: an `https://` or `git@` URL, or a local path.
541
+ */
542
+ url: string;
543
+ /**
544
+ * Where the working tree lands, in full. The caller resolved the root and the name, so
545
+ * that a picker can *show* the destination on the row before anything is written.
546
+ */
547
+ path: string;
548
+ }
549
+ | {
550
+ "call": "git_move_worktree";
551
+ path: string;
552
+ /**
553
+ * Where it lands, in full. The leaf must not exist; the parent is created.
554
+ */
555
+ dest: string;
556
+ /**
557
+ * The repository it belongs to. `git worktree move` runs from a checkout, and the
558
+ * conversation the caller is in may be standing in the one being moved — the same reason
559
+ * [`Self::GitRemoveWorktree`] takes one.
560
+ */
561
+ cwd?: string | null;
562
+ }
473
563
  | {
474
564
  "call": "gen_complete";
475
565
  prompt: string;
@@ -478,6 +568,30 @@ export type ApiCall =
478
568
  * Ask for JSON and parse it host-side, tolerating the code fences models wrap it in.
479
569
  */
480
570
  json: boolean;
571
+ /**
572
+ * The one key the answer is *about*, when the whole answer is one value.
573
+ *
574
+ * A prompt that says "return `{"branch": …}`" is answered with the object most of the
575
+ * time and with a bare `fix/composer-paste` the rest of it — the model did the work and
576
+ * skipped the envelope, and a caller that only accepts the envelope throws a correct
577
+ * answer away. Measured on this workspace's own history it was two runs in twenty-two:
578
+ * twice a branch was never named, silently, and both times the name was sitting in the
579
+ * reply.
580
+ *
581
+ * So: extraction first, exactly as before, and this is what a *bare* answer means. Named
582
+ * rather than inferred, because only the caller knows which key one value belongs under
583
+ * — and absent, nothing changes, which is what every existing caller wants.
584
+ *
585
+ * Only for a one-value answer. A commit message is a subject *and* a body, and guessing
586
+ * which half a bare paragraph is would be inventing the other one.
587
+ *
588
+ * And only for a value a *wrong* answer is recognisable in. A branch name is checkable
589
+ * and one `git branch -m` from being fixed; a thread title is any short line, which is
590
+ * also what a refusal and a driver's own error message look like — there the envelope is
591
+ * the only evidence the question was answered rather than commented on, and it is worth
592
+ * the one reply in ten that arrives without it.
593
+ */
594
+ field?: string | null;
481
595
  /**
482
596
  * Defaults to `gen.model` if set, else the session's own selection.
483
597
  */
@@ -20,10 +20,12 @@ import type { ModelSelection } from "./ModelSelection";
20
20
  import type { NamespaceId } from "./NamespaceId";
21
21
  import type { NodeInfo } from "./NodeInfo";
22
22
  import type { OptionEntry } from "./OptionEntry";
23
+ import type { PaneId } from "./PaneId";
23
24
  import type { PermissionDecision } from "./PermissionDecision";
24
25
  import type { PermissionMode } from "./PermissionMode";
25
26
  import type { PluginInfo } from "./PluginInfo";
26
27
  import type { PointInfo } from "./PointInfo";
28
+ import type { PullRequest } from "./PullRequest";
27
29
  import type { QuestionAnswer } from "./QuestionAnswer";
28
30
  import type { QuotaSample } from "./QuotaSample";
29
31
  import type { QuotaSnapshot } from "./QuotaSnapshot";
@@ -34,6 +36,8 @@ import type { SurfaceId } from "./SurfaceId";
34
36
  import type { SwarmAgent } from "./SwarmAgent";
35
37
  import type { SwarmNode } from "./SwarmNode";
36
38
  import type { SwarmStranger } from "./SwarmStranger";
39
+ import type { TabId } from "./TabId";
40
+ import type { TabInfo } from "./TabInfo";
37
41
  import type { ToolDef } from "./ToolDef";
38
42
  import type { UpdateOutcome } from "./UpdateOutcome";
39
43
  import type { UpdateStatus } from "./UpdateStatus";
@@ -56,6 +60,9 @@ export type ApiOk =
56
60
  | { "ok": "views"; views: Array<ViewInfo> }
57
61
  | { "ok": "ns"; ns: NamespaceId }
58
62
  | { "ok": "mark"; id: ExtmarkId }
63
+ | { "ok": "pane"; pane: PaneId | null }
64
+ | { "ok": "tab"; tab: TabId }
65
+ | { "ok": "tabs"; tabs: Array<TabInfo>; active: TabId }
59
66
  | { "ok": "surface"; surface: SurfaceId }
60
67
  | { "ok": "lines"; lines: Array<string> }
61
68
  | { "ok": "count"; n: number }
@@ -76,7 +83,7 @@ export type ApiOk =
76
83
  | { "ok": "focused_win"; win: WindowId | null }
77
84
  | { "ok": "option"; entry: OptionEntry | null }
78
85
  | { "ok": "options"; options: Array<OptionEntry> }
79
- | { "ok": "paths"; paths: Array<string> }
86
+ | { "ok": "paths"; paths: Array<string>; denied?: string | null }
80
87
  | { "ok": "viewport"; viewport: Viewport | null }
81
88
  | { "ok": "sessions"; sessions: Array<SessionInfo> }
82
89
  | { "ok": "credentials"; credentials: Array<CredentialInfo> }
@@ -86,6 +93,7 @@ export type ApiOk =
86
93
  | { "ok": "status"; status: RepoStatus }
87
94
  | { "ok": "branches"; branches: Array<BranchInfo> }
88
95
  | { "ok": "worktrees"; worktrees: Array<WorktreeInfo> }
96
+ | { "ok": "pulls"; pulls: Array<PullRequest> }
89
97
  | { "ok": "commits"; commits: Array<CommitInfo> }
90
98
  | { "ok": "commit"; commit: CommitInfo }
91
99
  | { "ok": "text"; text: string }
@@ -107,6 +115,7 @@ export type ApiOk =
107
115
  | { "ok": "swarm_nodes"; nodes: Array<SwarmNode> }
108
116
  | { "ok": "swarm_agents"; agents: Array<SwarmAgent> }
109
117
  | { "ok": "swarm_strangers"; strangers: Array<SwarmStranger> }
118
+ | { "ok": "swarm_commanded"; session?: SessionId | null }
110
119
  | { "ok": "quotas"; quotas: Array<QuotaSnapshot> }
111
120
  | { "ok": "quota_history"; samples: Array<QuotaSample> }
112
121
  | { "ok": "usage_history"; history: UsageHistory }
@@ -70,4 +70,6 @@ export type AscpMessage =
70
70
  session?: SessionId | null;
71
71
  }
72
72
  | { "type": "refused"; id: string; refusal: Refusal }
73
+ | { "type": "browse"; id: string; prefix: string }
74
+ | { "type": "browsed"; id: string; paths: Array<string> }
73
75
  | { "type": "goodbye"; message?: string | null };
@@ -9,6 +9,11 @@
9
9
  export type AuthRef =
10
10
  | { "kind": "env"; var: string }
11
11
  | { "kind": "command"; argv: Array<string> }
12
- | { "kind": "cli"; program: string; login?: string | null }
12
+ | {
13
+ "kind": "cli";
14
+ program: string;
15
+ login?: string | null;
16
+ retired?: string | null;
17
+ }
13
18
  | { "kind": "inherited" }
14
19
  | { "kind": "none" };
@@ -0,0 +1,14 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * What the forge's checks say, folded to the one thing a row has room for.
5
+ *
6
+ * Folded here rather than on the wire as a list, because the question a project row answers is
7
+ * "is anything wrong" and the answer is one glyph. The counts travel beside it for whoever wants
8
+ * to say `✗2`; the full list of check names is a panel's job and a different call.
9
+ *
10
+ * `Pending` and `None` are deliberately different. Nothing has run yet is a state that resolves on
11
+ * its own in a few minutes; there are no checks configured at all is permanent, and drawing a
12
+ * spinner over it for ever is the failure this distinction avoids.
13
+ */
14
+ export type ChecksState = "none" | "pending" | "passing" | "failing";
@@ -9,6 +9,7 @@
9
9
  export type CredentialSource =
10
10
  | { "kind": "plan"; via: string }
11
11
  | { "kind": "plan_missing"; program: string; hint?: string | null }
12
+ | { "kind": "plan_retired"; program: string; note: string }
12
13
  | { "kind": "env"; var: string }
13
14
  | { "kind": "keychain" }
14
15
  | { "kind": "session" }
@@ -0,0 +1,6 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * Which way to go, for moving between panes and for growing one.
5
+ */
6
+ export type Direction = "left" | "right" | "up" | "down";
@@ -1,9 +1,15 @@
1
1
  // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
2
 
3
3
  /**
4
- * Where a non-floating window sits.
4
+ * Which edge of a rectangle a non-floating window sits against.
5
5
  *
6
- * Deliberately *not* an arbitrary split tree: agent UIs do not need one, and a split tree creates
7
- * layout ambiguity that every plugin then has to reason about.
6
+ * The rectangle is the screen, or when the window names a [`PaneId`] — one pane of the main
7
+ * region. That is the only difference between the two, and it is what let splitting arrive without
8
+ * a second geometry vocabulary: a composer docked `Bottom` in a pane is the same call as a
9
+ * composer docked `Bottom` on the screen, asked about a smaller rectangle.
10
+ *
11
+ * Still not an arbitrary split tree *here*: a window says which edge of which rectangle it wants,
12
+ * and the tree that makes those rectangles is [`PaneNode`], one level up. Keeping the two apart is
13
+ * what stops every plugin from having to reason about nesting to place a status line.
8
14
  */
9
- export type Dock = "left" | "right" | "bottom" | "main";
15
+ export type Dock = "left" | "right" | "top" | "bottom" | "main";
@@ -23,6 +23,17 @@ export type FloatConfig = {
23
23
  */
24
24
  border_hl?: string | null;
25
25
  title?: string | null;
26
+ /**
27
+ * A strip on the bottom border: what the keys here do.
28
+ *
29
+ * On the border rather than in the buffer, because a key strip written as a row of content is
30
+ * one that scrolls away exactly when it is wanted, and is the first thing clipped on a terminal
31
+ * too short for the panel. Both of those happened: the picker's strip is the row that says how
32
+ * to get out, and on a sixteen-row screen it was the row that did not fit.
33
+ *
34
+ * Clipped to the border's width like the title, so it is for a legend and not for prose.
35
+ */
36
+ footer?: string | null;
26
37
  /**
27
38
  * Close automatically when focus moves elsewhere. The right default for pickers and hovers.
28
39
  */
@@ -60,4 +71,26 @@ export type FloatConfig = {
60
71
  * characters through to the composer would be typing into a field you cannot see.
61
72
  */
62
73
  modal: boolean;
74
+ /**
75
+ * This panel is a place you can move in when it does not fit.
76
+ *
77
+ * A float asks for a height and the frontend gives it what there is — so on a short terminal
78
+ * every row past the bottom edge is content that exists, is drawn nowhere, and has no key
79
+ * pointed at it. `height: Max { n: 30 }` on a twenty-row screen is not thirty rows of panel, it
80
+ * is ten rows of panel and twenty rows nobody can reach.
81
+ *
82
+ * Setting this adds one scope to the keymap chain while this float has focus —
83
+ * `BufKind { name: "neosh.scroll" }` — which is where the workspace binds the reader's motions:
84
+ * `j`/`k`, `<C-d>`/`<C-u>`, `<C-f>`/`<C-b>`, `gg`/`G`, the arrows and the paging keys. A scope
85
+ * rather than a capture, so all three of the usual rules hold: `^Z` lists the keys, `init.ts`
86
+ * moves them, and a panel that wants `j` for something of its own binds it on *its* kind, which
87
+ * is nearer and wins. It sits below the panel's own kind and above `Global`, so it is a default
88
+ * that never takes a key away from the thing it is scrolling.
89
+ *
90
+ * Off by default, and not for want of ambition: a picker whose filter takes printable
91
+ * characters through a capture would have `j` and `G` resolved out from under it by a scope, so
92
+ * typing a model's name would scroll instead of filtering. A panel that already moves in itself
93
+ * says so by staying quiet here.
94
+ */
95
+ scroll: boolean;
63
96
  };
@@ -27,4 +27,20 @@ export type ModelInfo = {
27
27
  * numbers are already columns.
28
28
  */
29
29
  tagline?: string | null;
30
+ /**
31
+ * Why this one cannot be chosen here, in a sentence, or nothing when it can.
32
+ *
33
+ * A model a driver lists but will not run — the vendor CLI on this machine is too old for it,
34
+ * most often. Said rather than filtered out, for the reason a missing provider is listed
35
+ * rather than dropped: nothing there and not allowed are the same empty list, and only one of
36
+ * them is fixed by doing something. A model that silently vanishes from the picker is a
37
+ * question with nowhere to ask it — you go looking for the release notes, or for our bug
38
+ * tracker, and the answer was one `claude update` away the whole time.
39
+ *
40
+ * The sentence carries the fix, not just the diagnosis: "needs claude 2.1.251 — run `claude
41
+ * update`" is actionable and "unsupported model" is not. Anything reading a catalogue to
42
+ * *choose* — the startup default, `model.upgrade`, `model.line` — skips these; only a picker
43
+ * draws them, and draws them disabled.
44
+ */
45
+ unavailable?: string | null;
30
46
  };
@@ -23,6 +23,17 @@ export type NodeCapabilities = {
23
23
  * Whether [`AscpMessage::Subscribe`] will produce anything.
24
24
  */
25
25
  streams: boolean;
26
+ /**
27
+ * Whether [`AscpMessage::Browse`] will be answered.
28
+ *
29
+ * Not a permission — it follows `accepts_commands`, for the reason written on `Browse` — but a
30
+ * *compatibility* flag, and that is why it is here rather than derived. A node built before
31
+ * `Browse` existed cannot skip a message it has never heard of: the frame parses or the
32
+ * connection fails, so a new node that sent one on spec would drop an old peer's link every
33
+ * time somebody opened a directory picker. It defaults to `false`, which is exactly what an
34
+ * older node's handshake decodes to, so "does not say" and "cannot" are the same answer.
35
+ */
36
+ browse: boolean;
26
37
  /**
27
38
  * The checkouts this node has, for starting something on it.
28
39
  */
@@ -0,0 +1,18 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ import type { PaneNode } from "./PaneNode";
3
+
4
+ /**
5
+ * One child of a split, and how much of the axis it gets.
6
+ */
7
+ export type PaneChild = {
8
+ node: PaneNode;
9
+ /**
10
+ * Share of the parent's axis, relative to its siblings.
11
+ *
12
+ * A weight rather than cells, because the terminal is resized by somebody dragging its corner
13
+ * and a layout stored in cells has to be recomputed — badly, and with rounding that
14
+ * accumulates — every time that happens. Weights survive a resize by meaning the same thing at
15
+ * every size. [`WEIGHT`] is what an even split is made of.
16
+ */
17
+ weight: number;
18
+ };
@@ -0,0 +1,15 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * One rectangle of the main region, and everything docked inside it.
5
+ *
6
+ * A pane is *where* rather than *what*: it owns no buffer and draws nothing. A chat pane is a
7
+ * transcript window docked `Main` in it plus a composer docked `Bottom` in it; a pane showing
8
+ * one thing is one window docked `Main`. That is the whole of why splitting did not need a
9
+ * second dock vocabulary — [`Dock`](crate::Dock) already answers "where inside this rectangle",
10
+ * and a pane is just a smaller rectangle to ask it about.
11
+ *
12
+ * Panes live in a tree per tab ([`PaneNode`](crate::PaneNode)). The tree says how the main
13
+ * region is divided; the frontend turns it into rectangles, exactly as it does for docks.
14
+ */
15
+ export type PaneId = number;
@@ -0,0 +1,17 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ import type { PaneChild } from "./PaneChild";
3
+ import type { PaneId } from "./PaneId";
4
+ import type { SplitDir } from "./SplitDir";
5
+
6
+ /**
7
+ * How the main region of one tab is divided.
8
+ *
9
+ * A tree rather than a list because a split of a split is the thing people actually build — a chat
10
+ * on the left, and on the right a chat above a terminal — and a flat list of rectangles cannot say
11
+ * that without every consumer re-deriving the nesting from coordinates.
12
+ */
13
+ export type PaneNode = { "kind": "leaf"; pane: PaneId } | {
14
+ "kind": "split";
15
+ dir: SplitDir;
16
+ children: Array<PaneChild>;
17
+ };
@@ -63,6 +63,7 @@ export type PluginEvent =
63
63
  | { "type": "composer_changed"; text: string }
64
64
  | { "type": "event"; name: string; data?: unknown; from: string }
65
65
  | { "type": "var_changed"; scope: VarScope; key: string; value?: unknown }
66
+ | { "type": "project_moved"; from: string; to: string }
66
67
  | { "type": "highlight_changed"; names: Array<string> }
67
68
  | { "type": "contributions_changed"; point: string }
68
69
  | { "type": "swarm_changed" }
@@ -0,0 +1,30 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ import type { ChecksState } from "./ChecksState";
3
+ import type { PullState } from "./PullState";
4
+
5
+ /**
6
+ * One pull request, as much of it as a row can use.
7
+ *
8
+ * **Keyed by `branch`**, which is what makes one call answer for every worktree at once: a
9
+ * repository's pull requests come back together and each checkout finds its own by the branch it
10
+ * has out. Asking per branch would be one network round trip per row in the panel.
11
+ */
12
+ export type PullRequest = {
13
+ number: number;
14
+ title: string;
15
+ /**
16
+ * For opening it. The forge's own URL, so nothing here has to know how to build one.
17
+ */
18
+ url: string;
19
+ /**
20
+ * The head branch. What a checkout matches itself against.
21
+ */
22
+ branch: string;
23
+ state: PullState;
24
+ checks: ChecksState;
25
+ /**
26
+ * How many checks have failed, for a row that wants to say `✗2` rather than `✗`.
27
+ */
28
+ checks_failed: number;
29
+ checks_total: number;
30
+ };
@@ -0,0 +1,11 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * Where a pull request has got to.
5
+ *
6
+ * Four states because that is what a person means by "what happened to it": one is a review you
7
+ * are waiting for, one is a review you have not asked for yet, one is done, and one is not going
8
+ * to happen. Draft is its own state rather than a flag on `Open` because on a row it is the whole
9
+ * difference between "somebody should look at this" and "I am still writing it".
10
+ */
11
+ export type PullState = "open" | "draft" | "merged" | "closed";
@@ -14,6 +14,24 @@ export type RemoteProject = {
14
14
  cwd: string;
15
15
  /**
16
16
  * Whether this node has a conversation open in it now.
17
+ *
18
+ * Meant something for exactly as long as this list was derived from live conversations: every
19
+ * project on it had one, so every one of them said `true` and a board drawing the flag drew
20
+ * one colour. A node now advertises the places it *works in* — which is the list its own panel
21
+ * shows, and includes the project you cleared out this morning — so the two states exist and
22
+ * the flag is worth reading.
17
23
  */
18
24
  active: boolean;
25
+ /**
26
+ * How many conversations are open in it there.
27
+ */
28
+ sessions: number;
29
+ /**
30
+ * How many of those are mid-turn.
31
+ *
32
+ * Separate from `sessions` because they are different questions and a board answers both in
33
+ * one row: `3 conversations · 1 working` is what the sidebar says about a local project, and a
34
+ * remote one should not have to say less.
35
+ */
36
+ running: number;
19
37
  };
@@ -0,0 +1,16 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * How far to move a window's scroll. See [`crate::ApiCall::WinScroll`].
5
+ *
6
+ * Verbs rather than a row number, for the reason [`CursorMotion`] is: only the core knows how many
7
+ * buffer rows the frontend last drew, so "half a screen" is a question the caller cannot answer and
8
+ * a caller that tried would answer it with a height. Signed, so one variant covers both directions
9
+ * and a binding for `<C-d>` is the binding for `<C-u>` with the sign flipped.
10
+ */
11
+ export type ScrollAmount =
12
+ | { "kind": "lines"; n: number }
13
+ | { "kind": "half"; n: number }
14
+ | { "kind": "page"; n: number }
15
+ | { "kind": "top" }
16
+ | { "kind": "bottom" };
@@ -0,0 +1,12 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * Which way a split lays its children out.
5
+ *
6
+ * Named for what it does to the screen rather than for the key that made it, because those two
7
+ * disagree in every editor that has ever shipped one: Vim's `:vsplit` produces panes side by side,
8
+ * and a `Vertical` that means "stacked" to the layout code and "side by side" to the user is a
9
+ * bug waiting for its first off-by-one. `Row` is a row of panes; `Column` is a column of them.
10
+ * The *commands* say `right` and `down`, which is the one description nobody can misread.
11
+ */
12
+ export type SplitDir = "row" | "column";
@@ -0,0 +1,10 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+
3
+ /**
4
+ * One tab of one view: a title, and a tree of panes.
5
+ *
6
+ * Per view rather than per workspace, for the same reason a window is: what the agent produced
7
+ * is the workspace's, and where you are looking is yours. Two terminals attached to one
8
+ * workspace have their own tabs over the same conversations.
9
+ */
10
+ export type TabId = number;
@@ -0,0 +1,27 @@
1
+ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
2
+ import type { PaneId } from "./PaneId";
3
+ import type { PaneNode } from "./PaneNode";
4
+ import type { TabId } from "./TabId";
5
+
6
+ /**
7
+ * One tab of one view.
8
+ */
9
+ export type TabInfo = {
10
+ id: TabId;
11
+ /**
12
+ * What the tab bar calls it.
13
+ *
14
+ * `None` is "name it after what is in it" — the conversation's title, the terminal's command —
15
+ * which is what a tab nobody has renamed should say, and it has to stay `None` to keep saying
16
+ * it: a title resolved once and stored is a tab still called `main` three conversations later.
17
+ */
18
+ title?: string | null;
19
+ root: PaneNode;
20
+ /**
21
+ * Which pane in this tab has the keyboard when the tab does.
22
+ *
23
+ * Per tab rather than per view, so that leaving a tab and coming back puts you where you were
24
+ * rather than in whichever pane happens to be first.
25
+ */
26
+ active_pane: PaneId;
27
+ };
@@ -8,6 +8,8 @@ import type { NoticeKind } from "./NoticeKind";
8
8
  import type { Rect } from "./Rect";
9
9
  import type { SurfaceCell } from "./SurfaceCell";
10
10
  import type { SurfaceId } from "./SurfaceId";
11
+ import type { TabId } from "./TabId";
12
+ import type { TabInfo } from "./TabInfo";
11
13
  import type { WindowId } from "./WindowId";
12
14
  import type { WindowLayout } from "./WindowLayout";
13
15
 
@@ -45,7 +47,9 @@ export type UiEvent =
45
47
  | { "type": "surface_claimed"; surface: SurfaceId; win: WindowId; rect: Rect }
46
48
  | { "type": "surface_cells"; surface: SurfaceId; cells: Array<SurfaceCell> }
47
49
  | { "type": "surface_released"; surface: SurfaceId }
50
+ | { "type": "panes_changed"; tabs: Array<TabInfo>; active: TabId }
48
51
  | { "type": "focus_changed"; win: WindowId | null }
52
+ | { "type": "home_changed"; win: WindowId | null }
49
53
  | {
50
54
  "type": "message";
51
55
  level: MessageLevel;