thinkpool-pair 0.3.1 → 0.3.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.
Files changed (2) hide show
  1. package/bridge.mjs +30 -7
  2. package/package.json +1 -1
package/bridge.mjs CHANGED
@@ -135,7 +135,9 @@ const announce = () =>
135
135
  channel.send({ type: 'broadcast', event: 'bridge', payload: {
136
136
  v: 2, name, repo: repoLabel, branch,
137
137
  agents: installedAgents,
138
- terms: [...terms.entries()].map(([id, t]) => ({ id, cmd: t.cmd, alive: true })),
138
+ // cols/rows: the PTY's one true size web viewers render this grid and
139
+ // scale it to their own page instead of voting to reflow it.
140
+ terms: [...terms.entries()].map(([id, t]) => ({ id, cmd: t.cmd, alive: true, cols: t.term.cols, rows: t.term.rows })),
139
141
  } })
140
142
 
141
143
  // One flush timer batches every terminal's pending bytes (~35ms cadence).
@@ -149,12 +151,27 @@ const flushAll = () => {
149
151
  }
150
152
  const flushTimer = setInterval(flushAll, 35)
151
153
 
154
+ // ── shared-PTY geometry ───────────────────────────────────────────
155
+ // The room's view is the product: the shared PTY never goes below
156
+ // FLOOR_COLS×FLOOR_ROWS, no matter what the host does to their local
157
+ // window. Below the floor the HOST's rendering wraps oddly (the agent
158
+ // keeps drawing for the floor width) — the pair's view stays clean.
159
+ // Pin an exact size with TP_COLS/TP_ROWS (disables host-following).
160
+ const FLOOR_COLS = 100, FLOOR_ROWS = 28
161
+ const PIN_COLS = parseInt(process.env.TP_COLS, 10) || null
162
+ const PIN_ROWS = parseInt(process.env.TP_ROWS, 10) || null
163
+ const attachedDims = () => ({
164
+ cols: PIN_COLS || Math.max(process.stdout.columns || FLOOR_COLS, FLOOR_COLS),
165
+ rows: PIN_ROWS || Math.max(process.stdout.rows || FLOOR_ROWS, FLOOR_ROWS),
166
+ })
167
+
152
168
  function openTerm({ id, cmd, args = [], attached = false, cols, rows }) {
153
169
  if (terms.has(id)) return
170
+ const ad = attached ? attachedDims() : null
154
171
  const term = pty.spawn(cmd, args, {
155
172
  name: 'xterm-256color',
156
- cols: cols || (attached ? (process.stdout.columns || 100) : 100),
157
- rows: rows || (attached ? (process.stdout.rows || 30) : 30),
173
+ cols: cols || (attached ? ad.cols : 100),
174
+ rows: rows || (attached ? ad.rows : 30),
158
175
  cwd: process.cwd(), env: process.env,
159
176
  })
160
177
  const entry = { term, cmd, attached, scrollback: '', buf: '' }
@@ -192,10 +209,14 @@ process.stdin.on('data', d => {
192
209
  if (t) t.term.write(d.toString('utf8'))
193
210
  else if (d.includes(3)) shutdown() // Ctrl-C once detached/headless
194
211
  })
195
- // attached terminal follows the host's TTY size (web resize never touches it)
212
+ // attached terminal follows the host's TTY size but never below the
213
+ // floor, and never at all when pinned. Re-announce so viewers rescale.
196
214
  process.stdout.on('resize', () => {
197
215
  const t = attachedId && terms.get(attachedId)
198
- if (t) { try { t.term.resize(process.stdout.columns, process.stdout.rows) } catch { /* noop */ } }
216
+ if (!t) return
217
+ const { cols, rows } = attachedDims()
218
+ if (t.term.cols === cols && t.term.rows === rows) return
219
+ try { t.term.resize(cols, rows); announce() } catch { /* noop */ }
199
220
  })
200
221
 
201
222
  channel
@@ -205,10 +226,12 @@ channel
205
226
  if (t) t.term.write(payload.data)
206
227
  })
207
228
  .on('broadcast', { event: 'resize' }, ({ payload }) => {
208
- // headless terms are web-sized (last writer wins); the attached term is host-sized.
229
+ // headless terms are web-sized (last writer wins); the attached term is
230
+ // host-sized. Floor at 80 cols — one narrow phone viewer must not reflow
231
+ // the shared PTY into a 40-col column for everyone (2026-06-10 bug).
209
232
  if (!payload?.cols || !payload?.rows) return
210
233
  const t = terms.get(payload.term)
211
- if (t && !t.attached) { try { t.term.resize(payload.cols, payload.rows) } catch { /* noop */ } }
234
+ if (t && !t.attached) { try { t.term.resize(Math.max(payload.cols, 80), Math.max(payload.rows, 20)); announce() } catch { /* noop */ } }
212
235
  })
213
236
  .on('broadcast', { event: 'term-open' }, ({ payload }) => {
214
237
  if (!payload?.id || !payload?.cmd) return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thinkpool-pair",
3
- "version": "0.3.1",
3
+ "version": "0.3.4",
4
4
  "description": "Share a local coding-agent CLI (Claude Code, Codex, Gemini, Aider, …) into a ThinkPool Code room, live.",
5
5
  "type": "module",
6
6
  "bin": { "thinkpool-pair": "bridge.mjs" },