@songsid/agend 2.1.1-beta.8 → 2.1.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.
@@ -1,8 +1,26 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { promisify } from "node:util";
3
3
  const exec = promisify(execFile);
4
+ export const DEFAULT_TMUX_LOGICAL_SIZE = Object.freeze({
5
+ columns: 120,
6
+ rows: 36,
7
+ });
8
+ export const LEGACY_TMUX_LOGICAL_SIZE = Object.freeze({
9
+ columns: 80,
10
+ rows: 24,
11
+ });
12
+ /** Resolve an effective config into the geometry that must be pinned in tmux. */
13
+ export function resolveTmuxLogicalSize(config) {
14
+ if (config?.enabled === false)
15
+ return { ...LEGACY_TMUX_LOGICAL_SIZE };
16
+ return {
17
+ columns: config?.columns ?? DEFAULT_TMUX_LOGICAL_SIZE.columns,
18
+ rows: config?.rows ?? DEFAULT_TMUX_LOGICAL_SIZE.rows,
19
+ };
20
+ }
4
21
  export class TmuxManager {
5
22
  sessionName;
23
+ logicalSize;
6
24
  windowId;
7
25
  // Socket isolation: null = use tmux default socket (backward compatible).
8
26
  // Set to a name to use `-L <name>` for custom AGEND_HOME isolation.
@@ -16,8 +34,9 @@ export class TmuxManager {
16
34
  return args;
17
35
  return ["-L", TmuxManager.socketName, ...args];
18
36
  }
19
- constructor(sessionName, windowId) {
37
+ constructor(sessionName, windowId, logicalSize = { ...DEFAULT_TMUX_LOGICAL_SIZE }) {
20
38
  this.sessionName = sessionName;
39
+ this.logicalSize = logicalSize;
21
40
  this.windowId = windowId;
22
41
  }
23
42
  // === Static session-level methods ===
@@ -84,8 +103,19 @@ export class TmuxManager {
84
103
  args.push("-P", "-F", "#{window_id}", command);
85
104
  const { stdout } = await exec("tmux", TmuxManager.tmuxArgs(args));
86
105
  this.windowId = stdout.trim();
87
- if (windowName) {
88
- await exec("tmux", TmuxManager.tmuxArgs(["set-window-option", "-t", `${this.sessionName}:${this.windowId}`, "allow-rename", "off"])).catch(() => { });
106
+ try {
107
+ // `window-size=manual` is essential: the shared control-mode client has
108
+ // no PTY and otherwise makes `window-size=latest` collapse windows back
109
+ // to its synthetic 80-column geometry.
110
+ await this.applyLogicalSize();
111
+ if (windowName) {
112
+ await exec("tmux", TmuxManager.tmuxArgs(["set-window-option", "-t", `${this.sessionName}:${this.windowId}`, "allow-rename", "off"])).catch(() => { });
113
+ }
114
+ }
115
+ catch (err) {
116
+ // Do not leave a live instance in an unpinned geometry if setup fails.
117
+ await this.killWindow();
118
+ throw err;
89
119
  }
90
120
  return this.windowId;
91
121
  }
@@ -93,11 +123,27 @@ export class TmuxManager {
93
123
  async respawnWindow(command, cwd) {
94
124
  if (!this.windowId)
95
125
  throw new Error("Cannot respawn tmux window without a window id");
126
+ // Apply before respawn so the new CLI sees the intended COLUMNS/LINES from
127
+ // its first frame. Reapplying is idempotent and repairs external drift.
128
+ await this.applyLogicalSize();
96
129
  await exec("tmux", TmuxManager.tmuxArgs([
97
130
  "respawn-window", "-k", "-t", `${this.sessionName}:${this.windowId}`,
98
131
  "-c", cwd, command,
99
132
  ]));
100
133
  }
134
+ async applyLogicalSize() {
135
+ if (!this.windowId)
136
+ throw new Error("Cannot size tmux window without a window id");
137
+ const target = `${this.sessionName}:${this.windowId}`;
138
+ await exec("tmux", TmuxManager.tmuxArgs([
139
+ "set-window-option", "-t", target, "window-size", "manual",
140
+ ]));
141
+ await exec("tmux", TmuxManager.tmuxArgs([
142
+ "resize-window", "-t", target,
143
+ "-x", String(this.logicalSize.columns),
144
+ "-y", String(this.logicalSize.rows),
145
+ ]));
146
+ }
101
147
  async killWindow() {
102
148
  if (!this.windowId)
103
149
  return;
@@ -159,6 +205,26 @@ export class TmuxManager {
159
205
  return null;
160
206
  }
161
207
  }
208
+ /** Return tmux's effective window geometry and sizing policy (diagnostics/tests). */
209
+ async getWindowGeometry() {
210
+ if (!this.windowId)
211
+ throw new Error("Cannot inspect tmux window without a window id");
212
+ const target = `${this.sessionName}:${this.windowId}`;
213
+ const { stdout } = await exec("tmux", TmuxManager.tmuxArgs([
214
+ "display-message", "-p", "-t", target,
215
+ "#{window_width} #{window_height}",
216
+ ]));
217
+ const { stdout: modeOutput } = await exec("tmux", TmuxManager.tmuxArgs([
218
+ "show-window-options", "-v", "-t", target, "window-size",
219
+ ]));
220
+ const [columnsRaw, rowsRaw] = stdout.trim().split(/\s+/);
221
+ const columns = Number.parseInt(columnsRaw, 10);
222
+ const rows = Number.parseInt(rowsRaw, 10);
223
+ if (!Number.isFinite(columns) || !Number.isFinite(rows)) {
224
+ throw new Error(`Invalid tmux window geometry: ${stdout.trim()}`);
225
+ }
226
+ return { columns, rows, mode: modeOutput.trim() };
227
+ }
162
228
  async sendKeys(text) {
163
229
  try {
164
230
  await exec("tmux", TmuxManager.tmuxArgs(["send-keys", "-l", "-t", `${this.sessionName}:${this.windowId}`, text]));
@@ -1 +1 @@
1
- {"version":3,"file":"tmux-manager.js","sourceRoot":"","sources":["../src/tmux-manager.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tmux-manager.js","sourceRoot":"","sources":["../src/tmux-manager.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -47,6 +47,17 @@ export interface HangDetectorConfig {
47
47
  enabled: boolean;
48
48
  timeout_minutes: number;
49
49
  }
50
+ /**
51
+ * Stable logical terminal geometry for an instance's tmux window.
52
+ *
53
+ * `enabled: false` is the compatibility escape hatch: the window is pinned to
54
+ * tmux's historical 80x24 geometry rather than inheriting the larger defaults.
55
+ */
56
+ export interface TerminalConfig {
57
+ enabled?: boolean;
58
+ columns?: number;
59
+ rows?: number;
60
+ }
50
61
  export interface DailySummaryConfig {
51
62
  enabled: boolean;
52
63
  hour: number;
@@ -130,6 +141,8 @@ export interface InstanceConfig {
130
141
  agent_mode?: "mcp" | "cli";
131
142
  /** Hang detector override for this instance. */
132
143
  hang_detector?: HangDetectorConfig;
144
+ /** Logical terminal size. Defaults to 120x36; set enabled=false for 80x24 compatibility. */
145
+ terminal?: TerminalConfig;
133
146
  /** Command to paste raw before each user message (e.g. "/chat load base.json") */
134
147
  pre_task_command?: string;
135
148
  }
package/dist/ui/view.html CHANGED
@@ -31,21 +31,35 @@
31
31
 
32
32
  /* Main */
33
33
  #main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
34
- #term { flex: 1; background: #000; overflow: auto; padding: 10px 12px; }
34
+ /* Padding kept at 5/6px: enough that glyphs don't touch the edge, small
35
+ enough that the terminal keeps essentially the whole main area. */
36
+ #term { flex: 1; background: #000; overflow: auto; scrollbar-gutter: stable; padding: 5px 6px; }
35
37
  #term pre { min-width: 100%; min-height: 100%; font-family: "SFMono-Regular", Menlo, Consolas, "Liberation Mono", monospace; line-height: 1.2; white-space: pre; color: #d0d0d0; }
36
38
 
37
- /* Card */
38
- #card { background: var(--panel); border-top: 1px solid var(--border); padding: 14px 18px; display: flex; gap: 16px; align-items: flex-start; }
39
- #avatarBox { width: 56px; height: 56px; flex: 0 0 56px; }
40
- .avatar-img { width: 56px; height: 56px; border-radius: 50%; object-fit: cover; display: block; }
41
- .avatar-ph { width: 56px; height: 56px; border-radius: 50%; background: #30363d; color: var(--dim); display: flex; align-items: center; justify-content: center; font-size: 24px; font-weight: 600; text-transform: uppercase; user-select: none; }
42
- #cardBody { flex: 1; min-width: 0; }
43
- #cardBody .row1 { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
44
- #cardBody .dname { font-size: 16px; font-weight: 600; }
45
- #cardBody .role { color: var(--accent); font-size: 13px; }
46
- #cardBody .meta { color: var(--dim); font-size: 12px; margin-top: 3px; }
47
- #cardBody .desc { font-size: 13px; margin-top: 6px; color: #adbac7; white-space: pre-wrap; }
48
- #editBtn { background: #21262d; color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 5px 12px; cursor: pointer; font-size: 13px; }
39
+ /* Card — a single compact bar by default so the terminal keeps the height.
40
+ `.expanded` restores the tall layout with avatar + description. */
41
+ #card { background: var(--panel); border-top: 1px solid var(--border); padding: 5px 10px; display: flex; gap: 10px; align-items: center; }
42
+ #avatarBox { width: 24px; height: 24px; flex: 0 0 24px; }
43
+ .avatar-img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; }
44
+ .avatar-ph { width: 100%; height: 100%; border-radius: 50%; background: #30363d; color: var(--dim); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 600; text-transform: uppercase; user-select: none; }
45
+ #cardBody { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 10px; overflow: hidden; }
46
+ #cardBody .row1 { display: flex; align-items: baseline; gap: 10px; flex: 0 0 auto; }
47
+ #cardBody .dname { font-size: 13px; font-weight: 600; white-space: nowrap; }
48
+ #cardBody .role { color: var(--accent); font-size: 12px; white-space: nowrap; }
49
+ #cardBody .meta { color: var(--dim); font-size: 11px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
50
+ #cardBody .desc { display: none; }
51
+ #cardToggle { background: none; border: none; color: var(--dim); cursor: pointer; font-size: 12px; padding: 2px 4px; flex: 0 0 auto; }
52
+ #cardToggle:hover { color: var(--fg); }
53
+
54
+ #card.expanded { align-items: flex-start; padding: 12px 14px; }
55
+ #card.expanded #avatarBox { width: 48px; height: 48px; flex: 0 0 48px; }
56
+ #card.expanded .avatar-ph { font-size: 20px; }
57
+ #card.expanded #cardBody { display: block; }
58
+ #card.expanded #cardBody .dname { font-size: 16px; }
59
+ #card.expanded #cardBody .role { font-size: 13px; }
60
+ #card.expanded #cardBody .meta { font-size: 12px; margin-top: 3px; white-space: normal; overflow: visible; }
61
+ #card.expanded #cardBody .desc { display: block; font-size: 13px; margin-top: 6px; color: #adbac7; white-space: pre-wrap; }
62
+ #editBtn { background: #21262d; color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 3px 10px; cursor: pointer; font-size: 12px; flex: 0 0 auto; }
49
63
  #editBtn:hover { border-color: var(--accent); }
50
64
 
51
65
  /* Edit form */
@@ -68,14 +82,18 @@
68
82
  .help-panel ul { margin: 8px 0 0 18px; }
69
83
  .help-panel li { font-size: 13px; margin: 6px 0; color: var(--fg); }
70
84
  .help-panel .close { position: absolute; top: 14px; right: 16px; cursor: pointer; color: var(--dim); font-size: 22px; line-height: 1; background: none; border: none; }
85
+
86
+ #densityBtn { font-size: 14px; font-weight: 600; letter-spacing: -.5px; }
71
87
  @media (max-width: 700px) {
72
88
  #sidebar { width: 180px; flex-basis: 180px; }
73
- #card { flex-wrap: wrap; padding: 10px 12px; }
89
+ /* The compact bar must stay one line; only the expanded form wraps. */
90
+ #card.expanded { flex-wrap: wrap; padding: 10px 12px; }
74
91
  #edit { flex-basis: 100%; }
75
92
  }
76
93
  </style>
77
94
  </head>
78
95
  <body>
96
+ <button class="help-btn" id="densityBtn" title="Font density" style="right:98px">⤢</button>
79
97
  <button class="help-btn" id="langBtn" title="Language" style="right:56px">🌐</button>
80
98
  <button class="help-btn" id="helpBtn" title="Help">ℹ️</button>
81
99
  <div class="help-backdrop" id="helpBackdrop"></div>
@@ -93,6 +111,7 @@
93
111
  <div class="meta" id="meta"></div>
94
112
  <div class="desc" id="desc"></div>
95
113
  </div>
114
+ <button id="cardToggle" title="Expand profile" aria-expanded="false">▲</button>
96
115
  <button id="editBtn" data-i18n="editProfile">✏️ Edit</button>
97
116
  <div id="edit">
98
117
  <input id="fDisplay" data-i18n-ph="displayName" placeholder="Display name">
@@ -126,13 +145,17 @@
126
145
  saveHint: "Viewing is open (read-only). Saving requires the web.token (~/.agend/web.token).", helpTitle: "📖 Help",
127
146
  help1: "Pick an instance on the left → its live terminal shows on the right",
128
147
  help2: "Click a group header ▼/▶ to collapse/expand", help3: "✏️ Edit changes display name, role, description",
129
- help4: "Drag to reorder (needs web.token permission)" },
148
+ help4: "Drag to reorder (needs web.token permission)",
149
+ help5: "Click ⤢ to cycle font density: Fit → Comfortable → Compact",
150
+ densityFit: "Fit", densityComfortable: "Comfortable", densityCompact: "Compact", densityLabel: "Font density" },
130
151
  "zh-TW": { viewTitle: "AgEnD · 檢視", editProfile: "✏️ 編輯", save: "儲存", cancel: "取消",
131
152
  displayName: "顯示名稱", role: "角色", description: "描述", tokenPh: "web.token(儲存時需要)",
132
153
  saveHint: "檢視為公開(唯讀)。儲存需要 web.token(~/.agend/web.token)。", helpTitle: "📖 使用說明",
133
154
  help1: "左側選 instance → 右側顯示即時 terminal 畫面",
134
155
  help2: "點 group header ▼/▶ 收合/展開", help3: "✏️ Edit 可修改顯示名稱、角色、描述",
135
- help4: "拖曳可調整順序(需 web.token 權限)" },
156
+ help4: "拖曳可調整順序(需 web.token 權限)",
157
+ help5: "點 ⤢ 循環切換字級密度:剛好填滿 → 舒適 → 精簡",
158
+ densityFit: "剛好填滿", densityComfortable: "舒適", densityCompact: "精簡", densityLabel: "字級密度" },
136
159
  };
137
160
  let lang = localStorage.getItem("agend_lang") || (navigator.language && navigator.language.toLowerCase().startsWith("zh") ? "zh-TW" : "en");
138
161
  if (!I18N[lang]) lang = "en";
@@ -142,16 +165,28 @@
142
165
  document.querySelectorAll("[data-i18n-ph]").forEach(e => { e.setAttribute("placeholder", T(e.getAttribute("data-i18n-ph"))); });
143
166
  const hc = q("helpContent");
144
167
  if (hc) { hc.innerHTML = ""; const h3 = document.createElement("h3"); h3.textContent = T("helpTitle"); hc.append(h3);
145
- const ul = document.createElement("ul"); for (const k of ["help1","help2","help3","help4"]) { const li = document.createElement("li"); li.textContent = T(k); ul.append(li); } hc.append(ul); }
168
+ const ul = document.createElement("ul"); for (const k of ["help1","help2","help3","help4","help5"]) { const li = document.createElement("li"); li.textContent = T(k); ul.append(li); } hc.append(ul); }
169
+ renderDensityBtn();
146
170
  }
147
171
 
148
172
  let roster = [];
149
173
  let rosterByName = new Map();
150
174
  let current = null;
151
175
  let poll = null;
152
- let fitted = false;
153
- let lastPaneText = "";
176
+ // Pane grid in cells, reported by /api/pane's X-Pane-Cols / X-Pane-Rows.
177
+ // This not the captured text — is the font-size input.
178
+ let paneCols = 0, paneRows = 0;
179
+ let lastFitKey = ""; // skip redundant recalcs
180
+ let appliedSize = 0; // last font-size actually written to #pre
154
181
  let resizeTimer = null;
182
+ // Font density — the manual escape hatch. Once the tmux grid is large
183
+ // (120x32), the auto-fit size is necessarily small, so the user needs a way
184
+ // to trade "everything visible" against "readable glyphs".
185
+ const DENSITY = { fit: 1, comfortable: 1.25, compact: 0.8 };
186
+ const DENSITY_GLYPH = { fit: "⤢", comfortable: "A+", compact: "A−" };
187
+ const DENSITY_ORDER = ["fit", "comfortable", "compact"];
188
+ let density = localStorage.getItem("agend_view_density");
189
+ if (!DENSITY[density]) density = "fit";
155
190
  const collapsed = new Set(); // group names the user has collapsed
156
191
  // Drag-sort state
157
192
  let sortGroups = new Map(); // groupName -> sort_index (persisted override)
@@ -463,38 +498,114 @@
463
498
  }
464
499
  const text = await r.text();
465
500
  if (target !== current) return;
466
- lastPaneText = text;
501
+ // Pane grid comes from tmux, so a sparse frame can't distort the fit.
502
+ const c = Number(r.headers.get("X-Pane-Cols")), rw = Number(r.headers.get("X-Pane-Rows"));
503
+ if (c >= 1 && rw >= 1 && (c !== paneCols || rw !== paneRows)) { paneCols = c; paneRows = rw; lastFitKey = ""; }
467
504
  q("pre").innerHTML = ansiToHtml(text);
468
- if (!fitted) { fitFont(text); fitted = true; }
505
+ fitFont();
469
506
  } catch { /* transient network error — keep last frame */ }
470
507
  }
471
508
 
472
- // Fit the captured terminal into both available dimensions. The old 16px
473
- // ceiling left an 80x24 pane occupying roughly one quarter of a 1080p view.
474
- function fitFont(text) {
475
- const visible = text.replace(/\s+$/, "");
476
- const lines = visible ? visible.split("\n") : [];
477
- const cols = lines.reduce((m, l) => Math.max(m, l.replace(/\x1b\[[0-9;]*m/g, "").length), 0);
478
- if (cols < 1) return;
479
- const termW = Math.max(1, q("term").clientWidth - 24);
480
- const termH = Math.max(1, q("term").clientHeight - 20);
481
- // Monospace glyph width ≈ .6em; line-height is fixed at 1.2.
482
- const widthFit = termW / (cols * 0.6);
483
- const heightFit = termH / (Math.max(1, lines.length) * 1.2);
484
- let size = Math.floor(Math.min(widthFit, heightFit));
485
- size = Math.max(8, Math.min(size, 48));
486
- q("pre").style.fontSize = size + "px";
509
+ // ── Font sizing ───────────────────────────────────────
510
+ // Measured advance width of one monospace cell as a fraction of font-size.
511
+ // The old hardcoded .6em was only an estimate and drifts per resolved font.
512
+ // The ratio is font-size independent, so measuring once is enough.
513
+ let cellRatio = 0;
514
+ function glyphRatio() {
515
+ if (cellRatio) return cellRatio;
516
+ const probe = document.createElement("span");
517
+ probe.style.cssText = "position:absolute;visibility:hidden;white-space:pre;left:-9999px;top:0;font-size:100px";
518
+ probe.style.fontFamily = getComputedStyle(q("pre")).fontFamily;
519
+ probe.textContent = "0".repeat(100);
520
+ document.body.appendChild(probe);
521
+ const w = probe.getBoundingClientRect().width;
522
+ probe.remove();
523
+ if (w > 0) cellRatio = w / 100 / 100; // ÷100 chars, ÷100px font-size
524
+ return cellRatio || 0.6; // headless / odd env → old estimate
525
+ }
526
+
527
+ // The removed 48px ceiling was what made an 80x24 pane render at ~33px on a
528
+ // 1080p screen — the "font is huge" complaint. A single desktop range is
529
+ // enough; /view is not targeting phones.
530
+ const MIN_PX = 12, MAX_PX = 22;
531
+
532
+ // Order: fit → clamp → user density → absolute readability bounds. When the
533
+ // clamped size can't show all `cols`, #term scrolls rather than shrinking
534
+ // below the floor.
535
+ function fitFont() {
536
+ if (paneCols < 1 || paneRows < 1) return; // no trustworthy size yet
537
+ const term = q("term"), pre = q("pre");
538
+ const cs = getComputedStyle(term);
539
+ const padX = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight);
540
+ const padY = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom);
541
+ const innerW = Math.max(1, term.clientWidth - (padX || 0));
542
+ const innerH = Math.max(1, term.clientHeight - (padY || 0));
543
+ const key = `${paneCols}x${paneRows}@${Math.round(innerW)}x${Math.round(innerH)}/${density}`;
544
+ if (key === lastFitKey) return;
545
+ const pcs = getComputedStyle(pre);
546
+ const lhRatio = parseFloat(pcs.lineHeight) / parseFloat(pcs.fontSize);
547
+ const lineH = Number.isFinite(lhRatio) && lhRatio > 0 ? lhRatio : 1.2;
548
+ const widthFit = innerW / (paneCols * glyphRatio());
549
+ const heightFit = innerH / (paneRows * lineH);
550
+ // 0.5% slack keeps an exact fit from rounding into a scrollbar (paired with
551
+ // scrollbar-gutter:stable, that's what stops resize→scrollbar→resize loops).
552
+ let size = Math.min(widthFit, heightFit) * 0.995;
553
+ size = Math.max(MIN_PX, Math.min(size, MAX_PX));
554
+ size *= DENSITY[density] || 1;
555
+ size = Math.max(8, Math.min(size, 32));
556
+ lastFitKey = key;
557
+ // Sub-pixel churn (a scrollbar appearing shrinks the box, which would shrink
558
+ // the font, which would hide the scrollbar again) resolves to a no-op here.
559
+ if (Math.abs(size - appliedSize) < 0.75) return;
560
+ appliedSize = size;
561
+ pre.style.fontSize = size.toFixed(2) + "px";
562
+ }
563
+
564
+ function scheduleFit() {
565
+ clearTimeout(resizeTimer);
566
+ resizeTimer = setTimeout(() => { lastFitKey = ""; fitFont(); }, 80);
567
+ }
568
+
569
+ function renderDensityBtn() {
570
+ const b = q("densityBtn");
571
+ if (!b) return;
572
+ b.textContent = DENSITY_GLYPH[density];
573
+ b.title = `${T("densityLabel")}: ${T("density" + density.charAt(0).toUpperCase() + density.slice(1))}`;
574
+ }
575
+
576
+ function cycleDensity() {
577
+ density = DENSITY_ORDER[(DENSITY_ORDER.indexOf(density) + 1) % DENSITY_ORDER.length];
578
+ localStorage.setItem("agend_view_density", density);
579
+ renderDensityBtn();
580
+ lastFitKey = "";
581
+ fitFont();
487
582
  }
488
583
 
489
584
  function select(name) {
490
585
  if (name === current) return;
491
- current = name; fitted = false;
586
+ current = name;
587
+ lastFitKey = ""; // force a refit once the new pane's size lands
492
588
  exitEdit();
493
589
  q("pre").textContent = ""; // blank the old instance's output immediately
494
590
  renderList(); renderCard(); refreshPane();
495
591
  }
496
592
 
497
593
  // ── Edit ──────────────────────────────────────────────
594
+ // The card is a one-line bar by default; expanding it (or editing) is what
595
+ // borrows height back from the terminal.
596
+ let cardExpanded = localStorage.getItem("agend_view_card_expanded") === "1";
597
+ function applyCardExpanded(on) {
598
+ q("card").classList.toggle("expanded", on);
599
+ const t = q("cardToggle");
600
+ t.textContent = on ? "▼" : "▲";
601
+ t.setAttribute("aria-expanded", on ? "true" : "false");
602
+ scheduleFit(); // the terminal just gained or lost height
603
+ }
604
+ function setCardExpanded(on, persist) {
605
+ if (persist) { cardExpanded = on; localStorage.setItem("agend_view_card_expanded", on ? "1" : "0"); }
606
+ applyCardExpanded(on);
607
+ }
608
+
498
609
  function enterEdit() {
499
610
  const it = roster.find(r => r.instance_name === current);
500
611
  if (!it) return;
@@ -503,14 +614,18 @@
503
614
  q("fDesc").value = it.description || "";
504
615
  q("fToken").value = localStorage.getItem("agend_web_token") || "";
505
616
  q("msg").textContent = "";
617
+ applyCardExpanded(true); // form needs the room; don't persist it
506
618
  q("cardBody").style.display = "none";
619
+ q("cardToggle").style.display = "none";
507
620
  q("editBtn").style.display = "none";
508
621
  q("edit").style.display = "flex";
509
622
  }
510
623
  function exitEdit() {
511
624
  q("edit").style.display = "none";
512
625
  q("cardBody").style.display = "";
626
+ q("cardToggle").style.display = "";
513
627
  q("editBtn").style.display = "";
628
+ applyCardExpanded(cardExpanded); // back to the user's own preference
514
629
  }
515
630
 
516
631
  async function save() {
@@ -542,19 +657,22 @@
542
657
  q("editBtn").onclick = enterEdit;
543
658
  q("cancelBtn").onclick = exitEdit;
544
659
  q("saveBtn").onclick = save;
660
+ q("cardToggle").onclick = () => setCardExpanded(!q("card").classList.contains("expanded"), true);
545
661
 
546
662
  const setHelp = (open) => { q("helpPanel").classList.toggle("open", open); q("helpBackdrop").classList.toggle("open", open); };
547
663
  q("helpBtn").onclick = () => setHelp(!q("helpPanel").classList.contains("open"));
548
664
  q("helpClose").onclick = () => setHelp(false);
549
665
  q("helpBackdrop").onclick = () => setHelp(false);
550
666
  q("langBtn").onclick = () => { lang = lang === "en" ? "zh-TW" : "en"; localStorage.setItem("agend_lang", lang); applyI18n(); };
551
- window.addEventListener("resize", () => {
552
- clearTimeout(resizeTimer);
553
- resizeTimer = setTimeout(() => {
554
- if (lastPaneText) fitFont(lastPaneText);
555
- }, 100);
556
- });
667
+ q("densityBtn").onclick = cycleDensity;
668
+ window.addEventListener("keydown", (e) => { if (e.key === "Escape") setHelp(false); });
669
+
670
+ window.addEventListener("resize", scheduleFit);
671
+ // Catches container changes the window resize event doesn't see — chiefly the
672
+ // profile card expanding or collapsing.
673
+ if (window.ResizeObserver) new ResizeObserver(scheduleFit).observe(q("term"));
557
674
  applyI18n();
675
+ applyCardExpanded(cardExpanded);
558
676
 
559
677
  // ── Boot ──────────────────────────────────────────────
560
678
  loadRoster().then(() => { refreshPane(); });
@@ -19,6 +19,13 @@ export interface ViewApiContext {
19
19
  getInstanceStatus(name: string): "running" | "paused" | "stopped" | "crashed";
20
20
  getUiStatus(): unknown;
21
21
  }
22
+ /** A pane's logical grid size in cells, as tmux sees it. */
23
+ export interface PaneSize {
24
+ cols: number;
25
+ rows: number;
26
+ }
27
+ /** Parse `display-message`'s `<cols>x<rows>` output. Returns null if unusable. */
28
+ export declare function parsePaneSize(out: string): PaneSize | null;
22
29
  /** True if the path belongs to the view feature (so the caller can skip the
23
30
  * global web-token gate and let this module do its own token checks). */
24
31
  export declare function isViewPath(path: string): boolean;
package/dist/view-api.js CHANGED
@@ -3,7 +3,8 @@
3
3
  * instance profiles. Separate from the operator Web UI (`/ui`):
4
4
  *
5
5
  * GET /view → static page (no token)
6
- * GET /api/pane/:instance → `tmux capture-pane -ep` output (ANSI text)
6
+ * GET /api/pane/:instance → `tmux capture-pane -ep` output (ANSI text),
7
+ * plus X-Pane-Cols / X-Pane-Rows response headers
7
8
  * GET /api/profiles → merged roster (live status + config + profile)
8
9
  * GET /api/profile/:instance → one profile row
9
10
  * POST /api/profile/:instance → upsert profile (web.token only)
@@ -107,21 +108,53 @@ function readBody(req, maxBytes) {
107
108
  req.on("error", reject);
108
109
  });
109
110
  }
110
- /** Capture a pane's current contents (with ANSI escapes) for an instance. */
111
- async function capturePane(ctx, name) {
111
+ /** tmux target for an instance's window, or null if it has no window yet. */
112
+ function paneTarget(ctx, name) {
112
113
  const widFile = join(ctx.dataDir, "instances", name, "window-id");
113
114
  if (!existsSync(widFile))
114
- return "";
115
+ return null;
115
116
  const wid = readFileSync(widFile, "utf-8").trim();
116
117
  if (!wid)
117
- return "";
118
+ return null;
119
+ return `${getTmuxSession()}:${wid}`;
120
+ }
121
+ function tmuxArgs(rest) {
118
122
  const socket = getTmuxSocketName();
119
- const args = [
120
- ...(socket ? ["-L", socket] : []),
121
- "capture-pane", "-p", "-e", "-t", `${getTmuxSession()}:${wid}`,
122
- ];
123
- const { stdout } = await execFileP("tmux", args, { maxBuffer: 8 * 1024 * 1024 });
124
- return stdout;
123
+ return socket ? ["-L", socket, ...rest] : rest;
124
+ }
125
+ /**
126
+ * Capture a pane's contents (with ANSI escapes) **and** its logical grid size.
127
+ *
128
+ * The size matters as much as the text: the web view sizes its font from
129
+ * `cols`/`rows` rather than from the captured text's longest line, because a
130
+ * sparse frame (a freshly-started CLI printing three short lines) would
131
+ * otherwise fit to ~3 columns and blow the font up to the 48px ceiling.
132
+ */
133
+ async function capturePane(ctx, name) {
134
+ const target = paneTarget(ctx, name);
135
+ if (!target)
136
+ return { text: "", size: null };
137
+ const [text, size] = await Promise.all([
138
+ execFileP("tmux", tmuxArgs(["capture-pane", "-p", "-e", "-t", target]), { maxBuffer: 8 * 1024 * 1024 })
139
+ .then(r => r.stdout),
140
+ // display-message resolves the window's *active* pane, matching capture-pane.
141
+ execFileP("tmux", tmuxArgs(["display-message", "-p", "-t", target, "#{pane_width}x#{pane_height}"]))
142
+ .then(r => parsePaneSize(r.stdout))
143
+ // A missing size must not fail the whole capture — the client falls back
144
+ // to its last known size (or skips refitting) when the headers are absent.
145
+ .catch(() => null),
146
+ ]);
147
+ return { text, size };
148
+ }
149
+ /** Parse `display-message`'s `<cols>x<rows>` output. Returns null if unusable. */
150
+ export function parsePaneSize(out) {
151
+ const m = /^(\d+)x(\d+)$/.exec(out.trim());
152
+ if (!m)
153
+ return null;
154
+ const cols = Number(m[1]), rows = Number(m[2]);
155
+ if (!Number.isFinite(cols) || !Number.isFinite(rows) || cols < 1 || rows < 1)
156
+ return null;
157
+ return { cols, rows };
125
158
  }
126
159
  /** True if the path belongs to the view feature (so the caller can skip the
127
160
  * global web-token gate and let this module do its own token checks). */
@@ -158,6 +191,9 @@ export function handleViewRequest(req, res, url, ctx) {
158
191
  return true;
159
192
  }
160
193
  // ── GET /api/pane/:instance ──
194
+ // Body is the raw ANSI capture; the pane's cell grid rides along in
195
+ // X-Pane-Cols / X-Pane-Rows so the client can size its font from the pane's
196
+ // real dimensions instead of guessing from the captured text.
161
197
  if (method === "GET" && path.startsWith("/api/pane/")) {
162
198
  const name = decodeURIComponent(path.slice("/api/pane/".length));
163
199
  if (!knownInstance(ctx, name)) {
@@ -165,7 +201,15 @@ export function handleViewRequest(req, res, url, ctx) {
165
201
  return true;
166
202
  }
167
203
  capturePane(ctx, name)
168
- .then(text => { res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" }); res.end(text); })
204
+ .then(({ text, size }) => {
205
+ const headers = { "Content-Type": "text/plain; charset=utf-8" };
206
+ if (size) {
207
+ headers["X-Pane-Cols"] = String(size.cols);
208
+ headers["X-Pane-Rows"] = String(size.rows);
209
+ }
210
+ res.writeHead(200, headers);
211
+ res.end(text);
212
+ })
169
213
  .catch(err => { ctx.logger.debug({ err, name }, "capture-pane failed"); res.writeHead(200, { "Content-Type": "text/plain" }); res.end(""); });
170
214
  return true;
171
215
  }