@cruxy/cli 1.11.3 → 1.11.4

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.
@@ -359,15 +359,16 @@ export async function executeRun(promptParts, opts) {
359
359
  }) ?? undefined;
360
360
  // The TUI sidebar lists the same tree the `--resume` picker reads (P2).
361
361
  //
362
- // A FRESH SESSION IS NOT IN ITS OWN SIDEBAR UNTIL TURN 1 (#257). This used to
363
- // be populated after the log was opened precisely so the running session
364
- // listed itself; the meta line is buffered now, so there is nothing on disk
365
- // to list until something is said. That is the more honest reading the
366
- // sidebar and the `--resume` picker show the same tree, and a session with no
367
- // conversation in it is not one the picker would offer either. The active id
368
- // is still passed, so the row marks itself the moment it appears.
362
+ // A PROBE, NOT A LIST (cli#300). A fresh session is not on disk until its
363
+ // first turn the meta line is buffered (#257) — so a list read here could
364
+ // never contain the session the user is in, and with no later caller the
365
+ // sidebar stayed exactly as stale as that first read for the whole run. The
366
+ // renderer re-runs this probe after every turn, off its paint path, so the
367
+ // running session appears the moment it lands and the tree keeps following
368
+ // what this and any other cruxy write. The active id marks the row when it
369
+ // shows; the sidebar and the picker still read the same ten.
369
370
  if (renderer instanceof TuiRenderer) {
370
- renderer.setSessions(listSessions(primaryRoot, SIDEBAR_SESSIONS), sessionId);
371
+ renderer.attachSessions(() => listSessions(primaryRoot, SIDEBAR_SESSIONS), sessionId);
371
372
  }
372
373
  // ONE key reader for the whole TUI session (P5): the input loop and the
373
374
  // approval prompt's modal both lease it, so the two can never be live on
@@ -219,14 +219,21 @@ export class TuiRenderer {
219
219
  /**
220
220
  * Saved sessions shown in the sidebar (P2), and which one is running.
221
221
  *
222
- * Populated only by {@link setSessions}, never at construction. The running
223
- * session has to be listed, and its meta line does not exist until the
224
- * session log is open which happens after the renderer is built. A
225
- * constructor argument could therefore only ever carry the list from BEFORE
226
- * this run, i.e. a sidebar missing the very session the user is in.
222
+ * Populated by {@link setSessions} or by the probe {@link attachSessions}
223
+ * installs, never at construction: the list is read from disk, the renderer
224
+ * is built before the session log exists, and a constructor argument could
225
+ * only ever carry the tree from BEFORE this run.
226
+ *
227
+ * THE LIST IS RE-READ AFTER EVERY TURN (cli#300). It used to be read once at
228
+ * startup, and since a fresh session's meta line is buffered until its first
229
+ * turn (#257), that one read could never include the session the user was
230
+ * in — the row the active id exists to mark. Re-probing on `endTurn`, the
231
+ * same trigger the git cache and the views use, is what lets it appear.
227
232
  */
228
233
  sessions = [];
229
234
  activeSessionId;
235
+ /** Re-reads the session tree; installed by {@link attachSessions}. */
236
+ sessionsProbe;
230
237
  constructor(caps, out, opts = {}) {
231
238
  this.caps = caps;
232
239
  this.out = out;
@@ -573,9 +580,9 @@ export class TuiRenderer {
573
580
  }
574
581
  // ── app-owned surfaces ────────────────────────────────────────────────────
575
582
  /**
576
- * Replace the sidebar's session list (P2). Called once the session log is
577
- * open, so the running session is itself listed; a live refresh later (P3)
578
- * is the same call.
583
+ * Replace the sidebar's session list (P2) with one the caller already holds.
584
+ * The primitive {@link attachSessions} and {@link refreshSessions} both land
585
+ * on; a caller with no probe to install (tests, a static list) uses it alone.
579
586
  */
580
587
  setSessions(sessions, activeSessionId) {
581
588
  if (this.closed)
@@ -584,6 +591,46 @@ export class TuiRenderer {
584
591
  this.activeSessionId = activeSessionId;
585
592
  this.schedulePaint();
586
593
  }
594
+ /**
595
+ * Install the probe that reads the session tree, and read it once now
596
+ * (cli#300). From here on {@link endTurn} re-runs it, so the list follows the
597
+ * tree as this and other cruxys write to it — and so the running session
598
+ * shows up the moment its first turn puts it on disk.
599
+ *
600
+ * THE PROBE NEVER RUNS ON THE PAINT PATH. It reads one meta line per file
601
+ * for at most ten files, which is cheap, and cheap is not the discipline:
602
+ * `viewModel` reads `this.sessions` and nothing else, exactly as it reads
603
+ * the git cache's last answer rather than running `git status`. A paint
604
+ * that touched the disk would do so on every streaming frame.
605
+ */
606
+ attachSessions(probe, activeSessionId) {
607
+ if (this.closed)
608
+ return;
609
+ this.sessionsProbe = probe;
610
+ this.activeSessionId = activeSessionId;
611
+ this.refreshSessions();
612
+ }
613
+ /**
614
+ * Re-read the session tree through the installed probe, off the paint path,
615
+ * and repaint if it answered. A probe that throws leaves the last list
616
+ * standing: the sidebar is a status surface, and a transient read error must
617
+ * never blank it or take the shell down. No probe → nothing to do, and the
618
+ * list set by {@link setSessions} stays as it was.
619
+ */
620
+ refreshSessions() {
621
+ const probe = this.sessionsProbe;
622
+ if (probe === undefined || this.closed)
623
+ return;
624
+ let sessions;
625
+ try {
626
+ sessions = probe();
627
+ }
628
+ catch {
629
+ return;
630
+ }
631
+ this.sessions = sessions;
632
+ this.schedulePaint();
633
+ }
587
634
  /** Set the input line's rendered text (prompt + buffer + caret). */
588
635
  setInput(line) {
589
636
  if (this.closed)
@@ -865,6 +912,10 @@ export class TuiRenderer {
865
912
  this.refreshGit();
866
913
  this.refreshLimits();
867
914
  this.refreshViews();
915
+ // AFTER the turn's final paint, like the three above: the frame that
916
+ // closes the turn is composed from state already in hand, and the tree
917
+ // read lands in the next scheduled paint rather than delaying this one.
918
+ this.refreshSessions();
868
919
  }
869
920
  /**
870
921
  * REPAINT the headroom panel once the turn's reading lands (P9) — no longer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cruxy/cli",
3
- "version": "1.11.3",
3
+ "version": "1.11.4",
4
4
  "description": "an agentic coding CLI",
5
5
  "type": "module",
6
6
  "bin": {