@modelstatus/cli 0.1.54 → 0.1.56

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": "@modelstatus/cli",
3
- "version": "0.1.54",
3
+ "version": "0.1.56",
4
4
  "description": "Track which AI models you use, where, and never get surprised by a retirement. Free offline model-health for any repo (mm status), browser sign-in for cloud inventory + alerts.",
5
5
  "keywords": [
6
6
  "llm",
package/src/tui/app.js CHANGED
@@ -88,22 +88,19 @@ function useTermDims() {
88
88
  const [dims, setDims] = React.useState({ cols: (stdout && stdout.columns) || 80, rows: (stdout && stdout.rows) || 24 });
89
89
  React.useEffect(() => {
90
90
  if (!stdout) return undefined;
91
- // Functional update that returns the SAME object when nothing changed, so a
92
- // re-measure that finds identical dims doesn't trigger a needless re-render.
91
+ // Re-measure ONLY on a genuine resize event NEVER poll with timers.
92
+ // ROOT CAUSE of the Warp clip: process.stdout.rows changes a beat AFTER the
93
+ // alt-screen transition settles, and a re-render with those changed dims
94
+ // SCROLLS the window's top (the header) off-screen in Warp. The dims read at
95
+ // mount render correctly; a "more accurate" later value is actually worse. So
96
+ // we keep the mount-time dims and only react to real resizes (functional
97
+ // update returns the SAME object when nothing changed → no needless render).
93
98
  const on = () => setDims((prev) => {
94
99
  const cols = stdout.columns || 80, rows = stdout.rows || 24;
95
100
  return prev.cols === cols && prev.rows === rows ? prev : { cols, rows };
96
101
  });
97
102
  stdout.on("resize", on);
98
- // Re-measure a few beats after mount. When the TUI REMOUNTS right after the
99
- // game leaves the alternate screen, some terminals (notably Warp's block
100
- // model) re-flow the viewport a moment LATER and report stdout.{columns,rows}
101
- // stale at mount-time — so the first frame is too SHORT and Ink falls back to
102
- // cursor-up diffing, which drifts/scrolls the window off the top (and Ctrl-L
103
- // can't fix it because it redraws at the same wrong height). These one-shot
104
- // re-reads pick up the settled size and snap the window back to full screen.
105
- const timers = [60, 250, 600].map((ms) => setTimeout(on, ms));
106
- return () => { stdout.off("resize", on); timers.forEach(clearTimeout); };
103
+ return () => { stdout.off("resize", on); };
107
104
  }, [stdout]);
108
105
  const fw = Number(process.env.MM_TUI_WIDTH);
109
106
  const fh = Number(process.env.MM_TUI_HEIGHT);
@@ -310,16 +307,10 @@ export const appController = {
310
307
  remount(next = {}) {
311
308
  const opts = { ...(this._opts || {}), ...next };
312
309
  this._opts = opts;
313
- // RECYCLE the alt screen: leave (?1049l) then re-enter (?1049h) + clear + home.
314
- // We're still in the alt screen the game kept, but block-model terminals (Warp)
315
- // drop their clean full-screen presentation during the game round-trip and the
316
- // host's block header bleeds back over the top rows. A plain clear can't undo
317
- // that. Leaving + re-entering reproduces the EXACT main→alt transition that
318
- // runApp does on first launch (which renders perfectly), so Warp re-enters
319
- // full-screen mode and the window paints flush from the top again. The two
320
- // sequences are written together with no render between them, so there's no
321
- // visible flash of the host screen.
322
- try { process.stdout.write("\x1b[?1049l\x1b[?1049h\x1b[2J\x1b[H"); } catch { /* ignore */ }
310
+ // Clear + home so the fresh tree paints from the top. The alt-screen recycle
311
+ // (forcing Warp back into full-screen) + the settle pause happen in launch.js
312
+ // right before this, so by now the terminal reports its stable full height.
313
+ try { process.stdout.write("\x1b[2J\x1b[H"); } catch { /* ignore */ }
323
314
  this._instance = render(h(Bootstrap, opts));
324
315
  return this._instance;
325
316
  },
@@ -81,9 +81,14 @@ export async function playGameInTui({ dir, width, height, initialView = "scan",
81
81
  // TUI comes back shifted down / not full-height until the next resize. Reset
82
82
  // the scroll region (\x1b[r), clear the screen, and home the cursor so the
83
83
  // remounted tree fills the whole terminal from the top (same as Ctrl-L).
84
- // The game kept the TUI's alt screen intact (inTui keepAlt), so we never
85
- // left itjust let the game's restore() flush, then remount. remount() clears
86
- // + homes inside the alt screen so the fresh tree paints a clean full screen.
84
+ // RECYCLE Warp's alt screen (leave + re-enter) to force it back into clean
85
+ // full-screen modeWarp drops that presentation during the game round-trip
86
+ // and its block header otherwise bleeds over the top rows. Then remount RIGHT
87
+ // AWAY (just a tick to flush the write): this reads the dims at mount the same
88
+ // way a FRESH launch does — those render perfectly. Do NOT wait for Warp to
89
+ // "settle" its reported height; the settled value is the one that clips (see
90
+ // useTermDims — we no longer poll for it).
91
+ try { process.stdout.write("\x1b[?1049l\x1b[?1049h\x1b[2J\x1b[H"); } catch { /* ignore */ }
87
92
  await new Promise((r) => setImmediate(r));
88
93
  appController.remount({ initialView, fresh: false });
89
94
  } catch (e) {