Repository navigation
Status Strip 2.0, module manager UX, and Ghostty physical-QA fixes - #328
Merged
Merged
Conversation
…rub whole control sequences - Project and session reads this session's own identity (launch/reattach lookup, /rename result, notice refresh) instead of the notice-only label cache, so /rename QA shows at once without a cd or notices enabled. - Submitting a passthrough command now marks the screen foreign, and leaving passthrough marks it again, so a title a program sets as it exits (Vim's "Thanks for flying Vim") is overwritten on return. - Titles strip complete OSC/DCS/APC/CSI/C1 sequences and bidi controls rather than leaving "]0;PWNED" debris; injection protection is unchanged.
…king history Nested in tmux, NMSh resolved baseline capabilities and never requested mouse reporting, so the outer terminal's alternate-scroll turned the wheel into Up/Down keys and the composer cycled history. tmux forwards SGR wheel reports to a pane that enables 1000/1002/1006 whether its own mouse option is on or off, so tmux now gets button+SGR reporting (no motion, Shift for native selection). Outer-host hints stay hidden; screen and zellij stay baseline. Passthrough programs keep receiving the reports unchanged.
… provider-aware surfaces
- /modules opens the same module manager /prompt embeds and saves each change
as it is made ("Saved."); Esc only closes. Inside /prompt the manager says it
edits the /prompt draft, marks unsaved module changes, and A saves module
settings alone without saving the rest of the draft.
- / starts a local search in Modules, Catalog and Packs over already-loaded
labels, ids, categories, descriptions and pack metadata; Esc clears it before
the panel's own Escape; "No matches" is explicit; nothing is collected.
- Footers only advertise controls that can act; Space with nothing
recommendable and arrows with no other option say why instead of no-oping.
- Details wrap at narrow widths (label over value below 60 columns), the
prompt preview is dropped first, and long footers wrap at control boundaries.
- Under Starship, Powerlevel10k, Oh My Posh or Prompt None, surfaces that cannot
show modules are marked with the reason; routing and settings are kept and
the Status Strip keeps working.
- A shared tab-cycling helper gives Tab/Shift+Tab with wrap-around to every
visual tab bar (/prompt views, Modules, Settings, Tools, tmux, Theme Studio)
without taking Tab from text fields.
- /modules is in slash commands, the palette, /help and Ask.
NMSh enables kitty keyboard disambiguation where supported; the protocol's functional-key table encodes unmodified F1 as CSI P. Legacy SS3 P and CSI 11~ still open the palette. Physical QA (Ghostty 1.3.1, macOS with the top row as media keys) has not yet confirmed which bytes the host sends.
… facts - Top or Bottom edge of the NMSh pane through the screen plan (Bottom adds the last pane row and moves nothing; inside tmux it sits above tmux's own bar). - Independent Left, Center and Right groups; routed modules carry a strip-only stripZone (absent = Right, where they were). Deterministic fitting in display cells: compact forms, then lowest priority, the center before the edges, Keep Awake narrowing last; hidden below 30 columns or 8 rows. - Plain (dot, bar or space separator) or Powerline via the prompt's existing block geometry; Safe glyphs, NO_COLOR and every color depth stay valid. - Clock, CPU, RAM, battery and uptime come from the Context Engine (system.time/memory/battery plus new bounded system.cpu and system.uptime), demanded only while the row shows; the strip's own stats poller is removed and rendering does no I/O. Hostile module text loses whole escape sequences. - /strip becomes the Status Strip Studio over the canonical settings rows, with module groups/order, presets (Minimal, Developer, System, Custom) previewed before applying and undoable, and a preview from the real renderer. - Built-in modules other than the exit status may now route to the strip. - Old settings migrate unchanged: Off stays Off, Top stays Top. - Docs, /help, Ask and CHANGELOG (Unreleased) updated.
This was referenced Oct 7, 2026
The sandbox environment had PATH '' but no TMUX_TMPDIR, and resolveCommand falls back to standard tool directories, so where tmux is installed the test's reloadTmux ran 'tmux source-file' against the person's default tmux server and its result depended on whether that server was running. The sandbox now carries its own TMUX_TMPDIR, the missing-fragment case is asserted on its own, and a new test reloads into an isolated, test-owned tmux server and kills only that server. Product behavior is unchanged.
…itles safeContextText (prompt, modules, path, Status Strip, /prompt and /modules disclosure), the fact cache boundary, historical prompt blocks and the welcome screen each replaced control bytes with U+FFFD and kept the rest of the sequence, so a directory named evil ESC]0;PWNED BEL rendered as evil�]0;PWNED. They now share the terminal-title scrubber: OSC/DCS/APC to BEL or ST, CSI, C1 forms and unterminated sequences are removed whole, remaining controls and bidi formatting are dropped, and ordinary Unicode and punctuation are untouched. Regression covers the real prompt, strip, history and welcome rendering.
…lace Every frame was wrapped in hide/show (?25l … ?25h). With animated Chroma (Breathe, rules on), idle NMSh repaints about ten times a second; measured in real tmux through a byte-logging relay, that was 51 hide/show pairs in 5 s idle, independent of tmux mouse mode and the Status Strip. Ghostty applies each frame atomically, but through tmux the pairs restart the blink and can be drawn as a flicker. A frame whose caret stays visible at the same position now repaints its rows and restores the position without touching visibility (0 pairs in the same measurement, rows still repainted). Moving, hiding, showing, resize, panels and passthrough return keep their explicit visibility writes.
…in DEC 2026 tmux answers the 2026 probe, so NMSh wrapped every frame in its own synchronized-output block. Measured with tmux 3.7 and Ghostty's terminfo (idle, animated Chroma), tmux then left the visible outer cursor at intermediate frame positions after its own sync block in 24 of 25 frames (e.g. 28,99 / 30,44 / 30,90). Ghostty's surface-wide cursor shader animates every such move, so the trail bumped around while the logical caret was still. tmux already draws to the real terminal in synchronized updates; without the application block the cursor is left away from the caret in 4 of 24 frames, all at column 1 of the divider rows where tmux draws a line outside sync on its own. The tmux profile records hostSynchronizes; the probe keeps synchronized output off for it. Other hosts are unchanged. NMSh's own trail engine was verified not to retarget on idle repaints (103 idle frames, caret unchanged), and a test now pins that.
Measured from Ghostty's side of tmux 3.7 (your animated appearance): idle, typing, Left/Right, multiline and resize all expose redraw positions to the outer terminal, and every one is a row change to column 1 of a neighbouring row, lasting ~0 ms; genuine composer moves stay on the caret's row. Ghostty's shader inputs are only the current and previous cursor and the change time, with no history, so a shader cannot tell a parked redraw position from a real move, and one shader serves every Ghostty window. New cursor setting ghosttyTrail (/cursor → Across rows): Tmux-safe, the default, draws no smear, sparks or ripple for moves that change row; Full trails every move. Fire on the caret is unchanged. At launch NMSh rewrites only its own managed shader when it is out of date and says why and how to reload Ghostty. Only documented Ghostty uniforms are used.
…cross rows" This reverts commit 7d2d506.
…ls jump inside tmux A host-native trail (NMSh's Ghostty shader) follows the terminal's real cursor, which tmux moves while it draws, so it can jump, even at a plain shell prompt. A shader cannot tell that from a real move, so NMSh does not try to filter it. - Renderer Portable now leaves the managed Ghostty shader off: NMSh draws the trail from its own caret and the host no longer draws a second one. Auto and Native are unchanged. - Inside tmux the managed files stay in step with /cursor even though the outer terminal is hidden from NMSh. - /cursor's Renderer row says this once, only inside tmux.
…ng it raw Physical QA: the one-detached-session startup screen showed only its heading and /tmp/evil. The session service reported the hostile cwd without its BEL, and the picker wrote it raw, so the unterminated OSC (ESC]0;PWNED) swallowed every following line, including the controls, and could inject terminal sequences. Session-reported text (cwd via tildePath, running command, program title) now goes through the shared scrubber in the startup screens, /resume and the live-session list. Reproduced and verified in a real PTY with an isolated session service at 100 and 40 columns.
…unch Physical QA: tmux new -s qa typed in the composer gave a blank tmux. The tmux server inherited the environment of NMSh's zsh, including the exported private ZDOTDIR, so every pane's zsh loaded NMSh's bootstrap (prompt blanked every cycle, ZLE and echo off) and showed nothing. Once startup is over the bootstrap now gives children the person's own ZDOTDIR back (or unsets it) and stops exporting POWERLEVEL9K_DISABLE_PROMPT; NMSh's own shell has already read its files, so nothing changes for it. Any nested zsh benefits the same way. Multiplexer client invocations (tmux with no command, new, new-session, attach, attach-session; screen; zellij) now get the terminal from launch instead of after their first output, as multiplexer-interop.md follow-up 6 planned; tmux ls, screen -ls and other listing/control commands stay in the transcript. Real-PTY regression: tmux launched from the composer on a private socket gets a 100x30 client, a working pane shell (blank before this fix), raw keys and its own drawing; detach returns to NMSh; attach -t works the same way.
…anscript text Output from the first ownership evidence (alternate screen, an input mode, a kitty keyboard push) is the program's, even in the same read as earlier output, so agy's logo no longer stays behind after it exits. The transcript parser no longer prints the bytes of sequences it does not draw (ESC ( B printed a stray B; CSI > 4;2 m became ESC[NaN;2m), keeps zero colour components, and abandons a CSI interrupted by ESC. Every handoff drops half-decoded input first.
raiseCatError
force-pushed
the
feature/status-strip-2
branch
from
October 7, 2026 23:37
a2862b6 to
747a8a6
Compare
…decoder The transcript parser keeps at most 8 KiB of one unfinished sequence. Past it OSC/DCS/SOS/PM/APC are discarded to their terminator (BEL or ST for OSC, ST only for the rest), CSI and nF escapes are skipped through their final byte, and none of it becomes text; C1 characters are dropped. NMSh's shell markers are bounded at 1 MiB and the key decoder holds at most 256 bytes of a partial key. Chunked adversarial tests assert the retained size after every read.
This was referenced Oct 8, 2026
Open
This was referenced Oct 8, 2026
…top leaking the cache redirect The physical blank tmux came from a session service started before the ZDOTDIR fix: the service writes every shell's bootstrap and outlives frontends, so a restarted window still got the old one, and the tmux server it started kept NMSh's private ZDOTDIR. A window now says when its service is an older build. The bootstrap also gives children back the person's own XDG_CACHE_HOME (the redirect only kept instant prompts out of startup).
…e terminal agy asks about synchronized output, grapheme clustering and colours in its first write, before it enters the alternate screen. Those bytes reached only NMSh's transcript parser, agy got no answers and redrew without synchronized output, so its cursor visibly jumped. Queries a running command writes while NMSh still owns the screen now go to the host, and the host's replies go back to the program instead of the key decoder. Cursor position reports excluded.
Node's raw mode keeps OPOST and sets ONLCR on NMSh's terminal, so every bare LF NMSh forwarded became CR LF. Raw-mode TUIs use a bare LF to move down a row in the same column: through NMSh, agy's inline interface overwrote its prompt marker and suggestion prefixes and its cursor jumped on each redraw (measured in Ghostty with a recording PTY proxy: 69 bare LFs written, none arrived). Output processing is now off while a program owns the terminal and back on when NMSh reclaims it. The terminal-answer router also no longer holds two-byte Alt+key prefixes; only unmistakable answer prefixes wait.
raiseCatError
added a commit
that referenced
this pull request
Oct 8, 2026
…rubber The mods panel cycles tabs with the shared PanelShell helpers, so Tab has one rule across panels. Agent and mod display text uses the shared terminal control scrubber line by line, so unterminated, C1, DCS and APC sequences leave no debris (the local scrubber kept ]0;... from an unterminated OSC).
raiseCatError
added a commit
that referenced
this pull request
Oct 8, 2026
This was referenced Oct 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Physical-QA fixes from the Ghostty 1.3.1 pass (#321), the module manager UX pass, and Status Strip 2.0 on the existing Context Engine. Version stays 0.17.0; no tag, release or Homebrew change.
QA defects fixed
/rename QA. The title read a label cache that only filled while notices were polling. It now reads the session's own identity (one lookup at launch/reattach, the/renameresult, notice refreshes), so renames and resets show up at once.evil�]0;PWNED. A bounded scrubber now removes whole OSC/DCS/APC (BEL/ST/C1, unterminated), CSI/C1 CSI, two-character escapes, controls and bidi characters, so the title isevil. Injection protection is unchanged.mouseoption is on or off (verified against real tmux in both modes), so NMSh now requests 1000/1002/1006 in tmux (no motion, Shift for native selection). Passthrough programs still get the reports unchanged.fnStateunset), which likely explains the physical result. NMSh also now decodes the kitty-protocolCSI Pform. Ghostty's actual F1 bytes were not observed.Module manager
/modules(direct; saves each change as it's made),/prompt→ Modules (labelled as part of the draft,Asaves module settings only),/local search on all three tabs, Tab/Shift+Tab through a shared helper in every visual tab bar, footers that only offer controls that can act, details that wrap at narrow widths with the preview dropped first, and unavailable surfaces marked with the reason under Starship/P10k/Oh My Posh/None (settings kept).Status Strip 2.0
Top/Bottom edge through ScreenPlan; Left/Center/Right groups with a strip-only
stripZone; deterministic width fitting in display cells; Plain (dot/bar/space) or Powerline using the existing block geometry;/stripStudio with presets previewed before applying, plusUundo; built-ins other than the exit status may route to the strip. Clock/CPU/RAM/battery/uptime are Context Engine facts (new boundedsystem.cpuandsystem.uptime) demanded only while the row shows. The oldLocalStatspoller is removed and rendering does no I/O. Old configs migrate unchanged (Off stays Off, Top stays Top).Validation
npm run verify:releasepasses exceptthemeBridge.test.ts › applyThemeBridge…, which fails identically on clean master because it assumes no running tmux server. It's environment-dependent and not touched here.typecheck:bench,bench:smoke(all budgets met),test:node22andgit diff --checkpass.