Skip to content

Release v0.22.0: native viewing and editing, Semantic Output, /track and input ownership - #444

Draft
raiseCatError wants to merge 209 commits into
masterfrom
release/v0.22.0
Draft

raiseCatError wants to merge 209 commits into
masterfrom
release/v0.22.0

Conversation

@raiseCatError

Copy link
Copy Markdown
Owner

Draft: do not merge. This is the release PR for v0.22.0. It follows the v0.20.0 flow (#392): one release branch, merged into master with a regular merge commit, and the tag created on that merge commit. It merges only after the gates below are met and the maintainer authorizes the release.

It supersedes v0.21.0. The maintainer chose to ship the latest stable version rather than every planned one. v0.21.0 (#429) is never tagged or published, and its work ships here. #429 is closed as superseded when this merges, never merged itself. The changelog has no [0.21.0] section; [0.22.0] covers everything since v0.20.0.

What it carries

Stage Record
v0.21 slices, stabilization and release-audit fixes, in dev #411–#428, dev = a8b383c2 (v0.21 ledger)
v0.22, merged here in dependency order with merge commits #435 → #433 → #437 → #442 → #438 → #441 → #434 → #439 → #440
Release integration the combined /view editor test (#434 with #439)
Release candidate metadata version 0.22.0, the [0.22.0] changelog section (candidate), README, ROADMAP and AGENTS status, the consolidated physical QA walkthrough, the v0.22.0 runbook, ledgers

master (6af5cbab) is an ancestor of this branch, so there are no conflicts. The branch does not contain release/v0.21.0's two commits; they held only metadata (version 0.21.0 and a [0.21.0] section), which this branch replaces.

Conflicts while integrating:

Apart from today's commits, the code equals the CI-green v0.22 candidate 567cf533. Those commits are:

Security review (hidden input, #426)

The candidate still had two ways for a password to run as a shell command. Both are fixed in #437 (6486d9c5). Details are in the v0.22 ledger and #437.

  1. A reply held after a hidden answer. For 1 s after NMSh answered a hidden question, hidden input seen by a look was treated as that same question. If the command then ended, the held typing ran as the next command. A second question with no prompt that gave up first looks identical.
    • Now that typing waits at the prompt, hidden, until the person chooses: Tab shows it, Esc or Ctrl+C discards it.
    • The 1 s window no longer decides whether anything runs.
  2. A reply queued before its question. A reply typed and entered before a question with no prompt existed was queued, and ran after a successful exit.
    • Now, when the command asks for hidden input, the entries queued while it ran wait for approval.
    • This was reproduced live on 567cf533 with real zsh and Bash; with the fix, the password is in neither NMSh's journal nor the shell's history file.

Regressions:

  • tests/inputOwnershipSecrets.test.ts: 10 tests; 6 fail without the fix.
  • tests/inputOwnershipSecretsLive.test.ts.

The remaining limits are listed in the changelog.

Gates (in order)

  1. Dispatched CI green on this branch's exact head. Fedora and both timing smoke jobs run only on dispatch and push.
  2. Physical QA: every row of docs/testing/v022-physical-qa.md recorded on the candidate build. One walkthrough covers v0.21's and v0.22's work. Not run yet.
  3. Final release commit: date the changelog section, record the QA results, and point README, ROADMAP, AGENTS, SECURITY, CONTRIBUTING and llms.txt at v0.22.0. Then dispatch CI on that exact head.
  4. Maintainer go-ahead, then merge, tag, GitHub release, Homebrew tap and website (docs/development/v022-release-runbook.md). Then close Release v0.21.0: native viewing and editing, Semantic Output and /track #429 as superseded and let the v0.22 PRs close as merged.

Package version: 0.22.0. There is no npm publication (private). The release has no uploaded assets: the source archive is GitHub's tag archive, which the Homebrew formula uses.

raiseCatError and others added 30 commits October 18, 2026 13:09
Reconciles the v0.21 candidate issues against dev at 6af5cba: what each slice
reuses and owns, the confirmed causes of #403, #408 and #388, the tests that
prove each slice, and what stays deferred. The ledger records progress, commits
and open risks so later sessions can resume.
…w redraws

#403: an interactive Fish on a fresh home starts its man-page completion
generator detached; it outlived test shells and raced home removal. Test
homes now mark that generation as done (Fish 3 and 4 locations), and the
runner no longer passes ambient XDG data, cache and state directories to
tests. A regression asserts no generator starts.

#408: newer shell knowledge resolved in-flight classifications as unknown,
so a stored queued command could keep the unclassified color. A superseded
lookup is now asked again against the new knowledge (bounded); stale answers
are still never cached. The highlight tests wait for the shell's startup
knowledge and for the lookups they started instead of one event-loop turn.

#388: the output parser ignored row movement, so every frame of a multi-row
progress display stayed in the transcript. Programs can now redraw their own
recent rows (cursor up/down, save/restore, partial erases), never above
NMSh's lines or more than a screen height; absolute positioning keeps the
previous behavior.
/view <path> reads a regular file relative to the shell's directory, /view N
completed output N (numbered as /copy), /view alone the latest output; block
Actions gain View output. Markdown files render (m shows the source), source
files get syntax roles and line numbers, diffs read as diffs. Find, go to
line, wrap or horizontal scroll, copy all.

Files are opened non-blocking and only regular files are read: at most 8 MiB
and 200,000 lines, binary and UTF-16 refused, invalid UTF-8 shown with a note.
Drawing is bounded to what is visible, and every control or format character
is drawn as a visible mark. Viewing never writes to the file or transcript.
/edit <path> (or e in /view) opens one small text file in a full-height
panel: typing, multi-line, caret by character/word/line/page, Shift
selection, Ctrl+Z/Ctrl+Y undo and redo (bounded, coalesced), Ctrl+F find,
Ctrl+C/Ctrl+X copy and cut, Ctrl+S save, Esc closes and asks about unsaved
changes. Ctrl+Y now decodes as redo (it was dropped before).

Only files that can be saved back exactly are opened: regular UTF-8 text up to
1 MiB, owned by this user, one hard link, writable file and folder, one kind
of line ending (BOM and final newline kept); a missing path in a writable
folder opens empty. Saving writes beside the original (exclusive create, its
mode and group), syncs, re-checks that the original is still the file opened
(device, inode, size, time, content hash) and renames over its real path; a
symlink stays a symlink. A changed, replaced or deleted original is a
conflict: Keep editing, Overwrite, or Reload. A new file never replaces one
that appeared meanwhile.
…#386)

A completed block whose output a recognizer is sure of gets one overview row
with the counts the tool printed and, when it listed failures or diagnostics,
a details row that opens them (verbatim lines, at most 20 plus a count).
Recognizers: Node's test runner (spec, TAP), Jest, Vitest, pytest, cargo
test, go test, TypeScript, Rust and GCC/Clang diagnostics (diagnostics only
from build commands or failed ones). Short passing output gets nothing.

Readings are derived per record, never stored: the transcript lines, raw
folding, find, selection and plain /copy are unchanged, and derived rows are
skipped by find and selection. Details start open when the raw output is
folded; a click on the details row or Actions -> Show or hide details toggles
them. /copy --semantic and --both copy the reading (raw once for an output
without one, said in the note). Settings -> Transcript -> Semantic Output.
A security review found patterns that could backtrack quadratically on
crafted output (trailing-whitespace stripping, the Jest summary, TAP
directives, GCC/TypeScript location prefixes), and recognition runs when a
block is first drawn. Lines over 2,048 characters are no longer read, trailing
whitespace is removed with trimEnd, and those formats are parsed with string
operations and end-anchored bounded patterns. A test runs eight adversarial
outputs (up to 2 MB each) and bounds each one's time; behavior is unchanged.
/track <command> (or --every 5s -- <command>) writes the command and a note
once, then draws a status row and the latest completed result under them,
replaced on every refresh with a stable height. Live rows are derived: never
stored, found, selected or copied, so nothing accumulates per refresh.
/track copy, refresh, pause, resume and stop; stopping leaves the last result
in the transcript once. /watch keeps its manager and behavior.

The shared scheduler takes draft PR #394's hardening as written (strict
cadence parsing; refusing substitutions, background jobs and heredocs;
UTF-8-safe bounded capture with a truncation flag; visible timeouts and spawn
errors; completion on stdio close; a stop grace period; at most 8 trackers),
and its tests. Samples now use the live shell's PATH. The derived-row flag
from Semantic Output is generalized for both.
…mings (#364, #365)

Bun's requested version comes from .bun-version or package.json
(packageManager, engines.bun) and the active one from its install location;
the Nix shell module reads IN_NIX_SHELL; the container module names Docker,
Podman, a dev container or Codespaces from marker files and host variables.
Nothing is run. IN_NIX_SHELL, REMOTE_CONTAINERS and CODESPACES join the shell's
allowlisted environment report. A Context Pack may now require 24
capabilities (as many as its modules), since the project pack needed a 17th.

/modules explain and /modules timings write plain-text reports from the
engine's snapshots: each configured module's surface, visibility reason and
sources' state, age and timing; or the slowest last collections. No value is
shown and nothing is collected to produce them.
Twelve variants with colors copied exactly from each project's MIT-licensed
source at a recorded revision (sources, revisions, licenses and role mapping
in docs/design/theme-families.md). Every role fill keeps 4.5:1 module text
contrast; light variants keep NMSh's text tiers. Existing ids and saved
choices are unchanged; Lavender stays the default.

Oxocarbon (grays computed at runtime in HSLuv), GitHub Primer (values from a
built primitives release), Material/Palenight and Monokai (licensing not
verified) are deferred with reasons. Theme Studio's Built-in list gains / to
filter by name, family or dark/light; Esc clears the filter before closing.
…on its own target

Review of #416/#420 found commands that /track (and /watch) started at once and
repeated every interval without asking:

- `echo x > ~/.zshrc 2>/dev/null`: the shared classifier excused every redirect in
  a command once `>/dev/null` appeared anywhere; `>|` was split as a pipe. Paste
  review labelled the same text read-only.
- `env rm -rf …`: `env` is read-only on its own but runs its operand.
- Anything classified network: `npm publish`, `npm login`, `npm audit fix`,
  `npm x`/`pnpm dlx`/`bun x`, `ssh host rm …`, `scp`, `rsync`, `curl -X POST`,
  `curl -o file`, plain `wget`.
- `git -c core.fsmonitor=… status`, `GIT_EXTERNAL_DIFF=… git diff`, `--ext-diff`
  (run arbitrary programs) and `git log --output=…` (writes files).

pasteGuard now judges each redirection's target (descriptor duplication and the
null/standard-stream devices are not files) and treats `>|` as a redirection.
repeatSafety.ts adds the repetition gate on top of the classifier: network
commands start at once only as read-only probes; env prefixes, wrappers and git
program options ask first; file-writing options, --fix/--write/snapshot updates,
--watch modes, scp/rsync and power control are refused. `2>&1` is no longer
mistaken for a background job. Network targets are remote unless they are this
computer.

Refs #393, #416, #420
… after wiring

- When a sample's shell exits, the close handler cancelled the KILL fallback even
  if other members of its process group were still running. A child that ignored
  TERM and closed its output survived `/track stop` (verified with a pid probe;
  the new test fails without the fix), and a stray process left by a finished
  sample piled up on every refresh. On close, anything still in the group now gets
  TERM, then KILL after the grace period; that timer keeps a closing window alive
  for at most the grace period so nothing is left behind.
- #420's refresh-start event now fires after the sample's handlers are attached,
  so a listener that throws cannot leave a child without them.
- A tracker whose directory disappeared said `spawn /bin/sh ENOENT`; it now names
  the directory and keeps trying at its interval.

Refs #393, #416, #420
…esult rows

- Above the composer each active tracker had its own row, repeating its block's
  status; eight trackers took eight rows. More than two now share one summary row
  (`4 trackers · 1 failing · /track opens the manager`).
- The state marks (◉ ■ ‖ ◌) ignored Safe glyphs; they now use ASCII there, in the
  block and the manager.
- Live rows split and sanitized the whole 200 kB result on every frame (1–3 ms per
  frame with eight large trackers); the first rows and line count are now computed
  once per result (~0.5 ms).
- The confirmation for an unknown /track command says "Track …?", not "Watch …?".
- Live PTY tests: four trackers at once (slow, failing, missing command, large
  output) with `refreshing…`, a resize and `/track stop all` ending every process;
  Bash and Fish trackers finding a PATH change made after startup.

Refs #393, #416, #420
…s samples

dash reads `cmd &>/dev/null` as `cmd &` followed by an empty redirection, so the
command would run detached. Descriptor duplication (`2>&1`, `>&2`) stays allowed.

Refs #393, #420
raiseCatError and others added 30 commits October 20, 2026 14:15
…bs in the session, /browser parsing and the Real browser setting
… wired into the app; lazy BrowserHost; marked dependency
…t change made while starting is reconciled; image tests follow the workspace
v0.22.0 final scope: multiline modes, images, text sizing, document workspace, /browser and the Vespyr Arcade

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant