@sigrun/tui 0.11.4 → 0.11.5

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/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## [0.11.5] - 2026-11-08
2
+
3
+ - Version-locked bump with the 0.11.5 core line (no changes).
4
+
1
5
  ## [0.11.4] - 2026-11-08
2
6
 
3
7
  - Version-locked bump with the 0.11.4 core line (no changes).
package/dist/ui/app.js CHANGED
@@ -166,6 +166,12 @@ export class TuiApp {
166
166
  * nothing and swallow keys). Exported for the launcher's mount. */
167
167
  mainContent;
168
168
  #activeId;
169
+ /** Deleted-conversations Task 4: whether the ACTIVE conversation's
170
+ * RAW list row carries deleted:true. Re-checked on every sidebar
171
+ * refresh (sidebar.rows drops deleted rows, so the pane reads the
172
+ * raw listConversations() response via the sidebar). Drives the
173
+ * pane's dim line and the composer gate. */
174
+ #activeDeleted = false;
169
175
  #unwatch;
170
176
  /** Monotonic watch generation: a stale watch's frames must never land. */
171
177
  #watchGen = 0;
@@ -564,6 +570,15 @@ export class TuiApp {
564
570
  */
565
571
  async refresh() {
566
572
  await this.sidebar.refresh();
573
+ // Deleted-conversations Task 4: the deleted state comes from the
574
+ // RAW list response (the sidebar drops deleted rows from its
575
+ // display rows but keeps the raw list). Re-checked on EVERY
576
+ // sidebar refresh, so a /delete, /restore, or another client's
577
+ // change lands on the next poll.
578
+ this.#activeDeleted =
579
+ this.#activeId !== undefined &&
580
+ this.sidebar.rawConversations.find((row) => row.id === this.#activeId)?.deleted === true;
581
+ this.pane.setDeleted(this.#activeDeleted);
567
582
  // Notices Task 8: the count rides the boot refresh too (quiet —
568
583
  // the chrome keeps its last list on a fetch failure).
569
584
  void this.notices.refresh().catch(() => { });
@@ -595,6 +610,13 @@ export class TuiApp {
595
610
  // Busy state is per-frame: unknown for the new conversation until
596
611
  // its first frame arrives.
597
612
  this.#busy = false;
613
+ // Deleted-conversations Task 4: the swap re-checks the deleted
614
+ // state from the raw rows the last sidebar refresh left (a
615
+ // refresh may be mid-flight; the next one re-checks again).
616
+ this.#activeDeleted =
617
+ this.sidebar.rawConversations.find((row) => row.id === id)
618
+ ?.deleted === true;
619
+ this.pane.setDeleted(this.#activeDeleted);
598
620
  this.sidebar.setActive(id);
599
621
  // Notices are per-conversation context (fix wave F4): an error
600
622
  // from the OLD conversation must never render in the new one.
@@ -832,6 +854,13 @@ export class TuiApp {
832
854
  const id = this.#activeId;
833
855
  if (id === undefined)
834
856
  return;
857
+ // Deleted-conversations Task 4: a deleted conversation is
858
+ // read-only — the server 409s any submission; the client keeps
859
+ // the draft and says so instead of firing the doomed POST.
860
+ if (this.#activeDeleted) {
861
+ this.pane.showError("this conversation is deleted · /restore to restore it");
862
+ return;
863
+ }
835
864
  this.editor.setText("");
836
865
  const whenBusy = this.#busy ? "steer" : undefined;
837
866
  this.#client.submit(id, text, whenBusy).catch((error) => {
@@ -926,6 +955,20 @@ export class TuiApp {
926
955
  await this.sidebar.refresh();
927
956
  return;
928
957
  }
958
+ case "delete":
959
+ case "restore": {
960
+ const id = this.#activeId;
961
+ if (id === undefined) {
962
+ this.pane.showError(`/${parsed.name} needs an active conversation`);
963
+ return;
964
+ }
965
+ await this.#client.patchMeta(id, { deleted: parsed.name === "delete" });
966
+ // Re-read the RAW list row so the pane's dim line lands
967
+ // at once (the next poll would catch up, but the user
968
+ // just acted — same rule as the archive refresh above).
969
+ await this.refresh();
970
+ return;
971
+ }
929
972
  case "model":
930
973
  case "thinking": {
931
974
  const id = this.#activeId;
@@ -134,6 +134,10 @@ export declare class ConversationPane implements Component {
134
134
  surfaceStateFor(entryId: number): SurfaceState | undefined;
135
135
  /** Append an `[error]` line (a failed composer action's surface). */
136
136
  showError(text: string): void;
137
+ /** Deleted-conversations Task 4: show (or clear) the dim deleted
138
+ * line. The app drives it from the raw list rows, never the view
139
+ * stream (which carries no meta). */
140
+ setDeleted(deleted: boolean): void;
137
141
  /** Append a `[notice]` line (an outcome, e.g. an abort that settled). */
138
142
  showNotice(text: string): void;
139
143
  /**
@@ -169,6 +169,10 @@ export class ConversationPane {
169
169
  /** The notices' mounted components, aligned with `#notices`. */
170
170
  #noticeComponents = [];
171
171
  #turns = [];
172
+ /** Deleted-conversations Task 4: the app sets this from the RAW list
173
+ * row (re-checked per sidebar refresh and on conversation swap);
174
+ * the dim line renders under the last turn until restored. */
175
+ #deleted = false;
172
176
  /** The latest view's `run ended:` line (stream state — recomputed on
173
177
  * every `showView`, unlike the composer-owned notices). */
174
178
  #runFailureLine;
@@ -322,6 +326,15 @@ export class ConversationPane {
322
326
  showError(text) {
323
327
  this.#appendNotice({ kind: "error", text });
324
328
  }
329
+ /** Deleted-conversations Task 4: show (or clear) the dim deleted
330
+ * line. The app drives it from the raw list rows, never the view
331
+ * stream (which carries no meta). */
332
+ setDeleted(deleted) {
333
+ if (deleted === this.#deleted)
334
+ return;
335
+ this.#deleted = deleted;
336
+ this.#rebuild();
337
+ }
325
338
  /** Append a `[notice]` line (an outcome, e.g. an abort that settled). */
326
339
  showNotice(text) {
327
340
  this.#appendNotice({ kind: "notice", text });
@@ -433,6 +446,12 @@ export class ConversationPane {
433
446
  if (this.#runFailureLine !== undefined) {
434
447
  this.#container.addChild(new Text(this.#theme.dim(this.#runFailureLine), 0, 0));
435
448
  }
449
+ // The deleted dim line (deleted-conversations Task 4) rides under
450
+ // the transcript, above the composer notices — verbatim copy from
451
+ // the plan's Global Constraints.
452
+ if (this.#deleted) {
453
+ this.#container.addChild(new Text(this.#theme.dim("this conversation is deleted · /restore to restore it"), 0, 0));
454
+ }
436
455
  for (const notice of this.#notices) {
437
456
  const component = noticeComponent(notice);
438
457
  this.#noticeComponents.push(component);
@@ -4,8 +4,11 @@
4
4
  * indent two spaces + `↳ `), names are single-line via B1's
5
5
  * `displayName` + pi-tui's `truncateToWidth`, ages right-align via B1's
6
6
  * `formatAge`, and the active row highlights accent-fg + bold. Archived
7
- * conversations stay hidden (the tree's `includeArchived: false`; the
8
- * server has no delete, archive is the hide), and quiet job runs stay
7
+ * conversations stay hidden (the tree's `includeArchived: false`),
8
+ * deleted conversations stay hidden too (the tree's default deleted
9
+ * filter; {@link rawConversations} keeps the RAW list response so the
10
+ * app can still read the active conversation's deleted marker), and
11
+ * quiet job runs stay
9
12
  * hidden (`includeJobRuns: false`; failed runs remain visible with their
10
13
  * `displayName` prefix). `+ New conversation` is always the last row.
11
14
  *
@@ -24,7 +27,7 @@
24
27
  */
25
28
  import { type Component } from "@earendil-works/pi-tui";
26
29
  import { type TuiTheme } from "../theme.js";
27
- import type { SigrunClient } from "../api.js";
30
+ import type { ConversationSummary, SigrunClient } from "../api.js";
28
31
  /** The sidebar selection: a conversation id, the `+ new` action, or an
29
32
  * extension VIEW (UI-blocks U3 Task 5 — the app opens it in the main
30
33
  * pane). */
@@ -64,6 +67,10 @@ export declare class Sidebar implements Component {
64
67
  get rows(): readonly SidebarRow[];
65
68
  /** The cursor's row index (0-based over {@link rows}). */
66
69
  get cursor(): number;
70
+ /** The RAW list rows from the last refresh (deleted rows included).
71
+ * The app reads the active conversation's deleted marker here —
72
+ * `rows` above never carries deleted rows. */
73
+ get rawConversations(): readonly ConversationSummary[];
67
74
  /**
68
75
  * Re-fetch the listing and rebuild the rows from the tree (summary
69
76
  * fields only — no per-row meta fetch). The cursor follows its
@@ -4,8 +4,11 @@
4
4
  * indent two spaces + `↳ `), names are single-line via B1's
5
5
  * `displayName` + pi-tui's `truncateToWidth`, ages right-align via B1's
6
6
  * `formatAge`, and the active row highlights accent-fg + bold. Archived
7
- * conversations stay hidden (the tree's `includeArchived: false`; the
8
- * server has no delete, archive is the hide), and quiet job runs stay
7
+ * conversations stay hidden (the tree's `includeArchived: false`),
8
+ * deleted conversations stay hidden too (the tree's default deleted
9
+ * filter; {@link rawConversations} keeps the RAW list response so the
10
+ * app can still read the active conversation's deleted marker), and
11
+ * quiet job runs stay
9
12
  * hidden (`includeJobRuns: false`; failed runs remain visible with their
10
13
  * `displayName` prefix). `+ New conversation` is always the last row.
11
14
  *
@@ -36,6 +39,11 @@ export class Sidebar {
36
39
  #rowComponents = [];
37
40
  /** Conversation rows only (the action row is {@link #newRow}). */
38
41
  #rows = [];
42
+ /** The RAW list response from the last refresh (deleted-conversations
43
+ * Task 4): the app's dim-line check reads it because the display
44
+ * rows below DROP deleted conversations (the tree's default filter).
45
+ * The listing is the same one request — no extra fetch. */
46
+ #rawConversations = [];
39
47
  /** The extension VIEW rows (U3 Task 5): one per declared view surface,
40
48
  * rendered under the VIEWS group header. */
41
49
  #views = [];
@@ -71,6 +79,12 @@ export class Sidebar {
71
79
  get cursor() {
72
80
  return this.#cursor;
73
81
  }
82
+ /** The RAW list rows from the last refresh (deleted rows included).
83
+ * The app reads the active conversation's deleted marker here —
84
+ * `rows` above never carries deleted rows. */
85
+ get rawConversations() {
86
+ return this.#rawConversations;
87
+ }
74
88
  /**
75
89
  * Re-fetch the listing and rebuild the rows from the tree (summary
76
90
  * fields only — no per-row meta fetch). The cursor follows its
@@ -78,6 +92,7 @@ export class Sidebar {
78
92
  */
79
93
  async refresh() {
80
94
  const list = await this.#client.listConversations();
95
+ this.#rawConversations = list.conversations;
81
96
  const previousId = this.#display[this.#cursor]?.id;
82
97
  const tree = buildConversationTree(list.conversations, {
83
98
  includeArchived: false,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sigrun/tui",
3
- "version": "0.11.4",
4
- "description": "sigrun terminal client (pi-tui based) over the \u00a75 API \u2014 spawned by the sigrun CLI, or standalone against a server URL",
3
+ "version": "0.11.5",
4
+ "description": "sigrun terminal client (pi-tui based) over the §5 API — spawned by the sigrun CLI, or standalone against a server URL",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "sigrun-tui": "dist/launch.js"
@@ -21,7 +21,7 @@
21
21
  },
22
22
  "dependencies": {
23
23
  "@earendil-works/pi-tui": "^1.3.0",
24
- "@sigrun/shared": "0.11.4"
24
+ "@sigrun/shared": "0.11.5"
25
25
  },
26
26
  "devDependencies": {
27
27
  "typescript": "^7.0.2",