Skip to content

Release v0.21.0: native viewing and editing, Semantic Output and /track - #429

Closed
raiseCatError wants to merge 72 commits into
masterfrom
release/v0.21.0
Closed

raiseCatError wants to merge 72 commits into
masterfrom
release/v0.21.0

Conversation

@raiseCatError

Copy link
Copy Markdown
Owner

Draft: do not merge. This is the release PR for v0.21.0, following 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.

What it carries

Stage Head Record
v0.21 slices and stabilization, merged into dev 433af8fd #411 → #424 (ledger: docs/development/v021-ledger.md)
Release candidate metadata d9e08393 0.21.0 changelog section (candidate), version 0.21.0, README/ROADMAP/AGENTS status
Release-audit fixes, pending review — #425 (/edit false conflict), #427 (test race), #428 (ledger, QA walkthrough, runbook, llms.txt)

master (6af5cbab) is an ancestor of dev, so this PR has no conflicts. Once #425, #427 and #428 are merged into dev, dev is merged into this branch with a regular merge commit (no force push).

Gates (in order)

  1. /edit: a new file in a symlinked folder saves again without a false conflict #425, Input ownership test: type the follow-up after the answered command completes #427, v0.21 release audit: integrated state, verification, QA walkthrough and runbook #428 reviewed and merged into dev, then merged into this branch.
  2. Dispatched CI green on this branch's exact head (Fedora and both timing smokes run only on dispatch and push), and local npm run verify:release on that head.
  3. Physical QA: every row of docs/testing/v021-physical-qa.md recorded on the candidate build (walkthrough in v0.21 release audit: integrated state, verification, QA walkthrough and runbook #428). Not run yet.
  4. Final release commit: date the changelog section, record the QA results, point README/ROADMAP/AGENTS/SECURITY/CONTRIBUTING/llms.txt at v0.21.0, and dispatch CI on that exact head.
  5. Maintainer go-ahead, then merge, tag, GitHub release, Homebrew tap, website (docs/development/v021-release-runbook.md).

Verification so far

Package version: 0.21.0. 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 16 commits October 18, 2026 19:41
Native /edit: a small, safe editor for one text file (#402)
Semantic Output, first phase: test and build readings in their blocks (#386)
/track: one live result of a finite command, refreshed in place (#393)
Native modules: Bun, Nix shell, container; /modules explain and timings (#364, #365)
Theme families: Ayu, Kanagawa, Everforest, Flexoki, Night Owl (#401)
v0.21 checkpoint: ledger, integration verification and release readiness
…ening

fix(v0.21): harden /track refresh and finalization lifecycle
/track review: repetition safety gate, process cleanup, bounded presentation
Flaky CI at the cause: task exit verdict, race-free private-root removal, stalled-file teardown
v0.21 bugs: wrapped-row redraws (#388), command colors after a slow zshrc, width in /modules explain (#365)
v0.21 ledger: stabilization findings, verification, ordered integration and release gates
…onflict

A path that did not exist yet kept its unresolved path as the file's real
path. After the first save, every later save compares realpath() of the path
as given with that real path, so in a folder reached through a symlink
(macOS /tmp and /var, or any linked project folder) the second save reported
"was replaced on disk since you opened it" and offered Overwrite or Reload.

The folder is now resolved when the file is opened, so the real path is the
one the file has once it exists. A file replaced after the first save is
still a conflict; the regression covers both, and fails on the old code.
…ompletes

The waiting-read test typed its follow-up command as soon as the answer's
output was on screen, and its "nothing was queued" check ran before that
follow-up had run. On a busy host the shell's prompt for the answered
command can arrive after the program's output; an Enter in that window
queues, as designed, so the check failed on the test's own follow-up
(seen on PR CI, Ubuntu, and locally under load). A diagnostic run recorded
the queued entry as `echo BACK-…` with queued: true and the queue empty
once it ran.

The test now waits for the answered command's journal record (zsh, Bash)
and for the shell to be back at its prompt (every shell), then waits for the
follow-up to complete before checking that the answer never ran as a
command and nothing is left queued. An answer that is itself queued still
fails the test, at the wait for the program's output (#426).
…nd runbook

The ledger records the merged state (#411–#424 on dev at 433af8f, each
merge commit), CI on that exact commit, the local gates, benchmarks, the
code review's findings (#425, #426, #427), the issue and PR disposition
(#403 and #394 closed) and the remaining release gates.

The physical QA checklist gains 3.9 (#425) and a walkthrough: three
sittings over the twelve passes, with what is unavailable on the QA Mac
and the coverage that exists instead. scripts/qa/v021-fixtures.sh builds
every fixture in one new folder, with a launcher that gives the QA build
its own configuration and session service.

The runbook follows the v0.20 release: release branch, merge into master,
tag on the merge commit, GitHub release, a manual tap PR, website sync and
the approved manual publish, then the post-release smoke and rollback.
The v0.20.0 release updated the other release pointers (README, ROADMAP,
AGENTS, SECURITY, CONTRIBUTING) but not this one.
The changelog's Unreleased work becomes a 0.21.0 section marked as an
unpublished release candidate, with an overview, compatibility (session
protocol unchanged; the background-task fix needs a new service), upgrade
notes and known limitations, and the two user-visible fixes from #422
that had no entry. package.json and package-lock.json are 0.21.0.

README, ROADMAP and AGENTS describe the candidate and its surfaces; the
latest published release pointers stay at v0.20.0 until it is published.
@raiseCatError

Copy link
Copy Markdown
Owner Author

Homebrew formula rehearsal on the candidate (d9e08393). This is a packaging check only, not a tap change or physical QA.

  • git archive --prefix=notMyShell-0.21.0/ tarball (standing in for GitHub's tag archive; the real tag archive's sha256 is computed at release, runbook step 4), then the formula's own steps: npm ci, then npm ci --offline with npm_config_nodedir, npm run build, npm prune --omit=dev --offline --ignore-scripts. All exit 0.
  • Installed only the formula's file list (bin, dist, node_modules, package.json, LICENSE, the two scripts, assets/completion, assets/understanding, assets/brand/nmsh-logo.png) into …/Cellar/nmsh/0.21.0/libexec.
  • The formula's three test blocks:
    • nmsh --version → notMyShell 0.21.0;
    • node-pty spawn → nmsh-pty-ok;
    • Homebrew update guard (with an install receipt as Homebrew writes) → homebrew-update-guard-ok.
  • Headless start of the installed keg (isolated config and session service): /view rendered a Markdown file, /track df -h went live, /track stop worked, and there were no ENOENT, Cannot find module or ERR_MODULE_NOT_FOUND errors. Nothing was left running.

The formula needs only its url and sha256 changed for v0.21.0.

@raiseCatError

Copy link
Copy Markdown
Owner Author

Pre-validation of the final candidate tree (local preview, never pushed). This branch with #427, #425 and #428 merged on top as regular merge commits gives commit a8033bbb, tree a06454b0. The release commit and the three PRs touch disjoint files, so merging them through dev produces the same code.

npm run verify:release on it (clean npm ci, macOS arm64, Node 26.10, T7 scratch, load 3–7): PASS, exit 0.

  • Canonical suite: 3,143 tests, 3,141 pass, 0 fail, 2 skipped; suggestion ranking 8/8.
  • typecheck:bench passes, and so does bench:smoke (every budget). p50: pty-ready 32 ms, service-ready 306 ms, cold context 53 ms, every-module render 51 ms, configured completion warm 11 ms, wrap-present-10000 64 ms, wrap-present-100000 636 ms. These are higher than the 433af8f run at load 4–5 (24 / 233 / 50 / 40 / 9 / 41 / 444 ms) because this run ended at load 6.5. They are inside the range the ledger records for this Mac (wrap-present-100000 484–1,161 ms) and well inside budget. The release run on the final head is the check.
  • git diff --check: pass.

Physical QA build: /Volumes/T7Tmp/ai-dev/worktrees/notMyShell/v021-qa at a8033bbb (notMyShell 0.21.0, build a8033bb). QA results recorded on it apply to the final candidate if git diff --stat a8033bbb <final head> -- . ':!docs' is empty. That is, nothing outside docs/ may change (#428 has one later wording commit, 6b3a06e8, that touches only the walkthrough text).

Input ownership test: type the follow-up after the answered command completes
/edit: a new file in a symlinked folder saves again without a false conflict
v0.21 release audit: integrated state, verification, QA walkthrough and runbook
chore(v0.21): sync audited dev fixes and QA runbook into release candidate
@raiseCatError

Copy link
Copy Markdown
Owner Author

Release state after the 2026-10-19 crash recovery: automated gates done on the exact head; physical QA not run.

Head: 86bb3a6c (#430 brought #425, #427 and #428 in from dev).

  • No v0.22 commit is in the branch.
  • git diff --check against master is clean, and the diff has no temporary, generated or secret files.

Gate 2, done

  • Dispatched CI on exactly 86bb3a6c: run 39215504453, all 13 jobs green, including Fedora and both timing smokes.
  • Local verify:release covers this head's code. 86bb3a6c differs from the pre-validated a8033bbb only in docs/testing/v021-physical-qa.md, which no test or script reads. That run (comment above) had 3,141 passed, 0 failed, 2 skipped, with every bench budget met.
  • I didn't repeat the full local run. This 8 GB Mac had just panicked from memory pressure, so CI carried the full matrix.

Known limitations to add. These came out of the v0.22 work. Each was checked on the release code, and each is also in v0.20.0, so none is a v0.21 regression.

Gate 3, physical QA: not run. The fixtures are ready: /Volumes/T7Tmp/ai-dev/q21/nmsh (QA build v021-qa, notMyShell 0.21.0, build a8033bb).

Gate 4, final release commit: drafted locally, not pushed. It holds the limitations above, the status pointers and the ledger evidence. It waits for the QA results and the publication date.

@raiseCatError

Copy link
Copy Markdown
Owner Author

Superseded by #444 (v0.22.0). The maintainer decided to ship the latest stable version instead of every planned one. v0.21.0 will not be tagged or published.

This PR stays open and unmerged until v0.22.0 is released. It is then closed as superseded, never merged for bookkeeping. The release/v0.21.0 branch is kept.

@raiseCatError

Copy link
Copy Markdown
Owner Author

v0.22.0 is published: https://github.com/raiseCatError/notMyShell/releases/tag/v0.22.0 (merged through #444 as ef57ff43; dev and master are at the same commit). This candidate's work is in that release; v0.21.0 was never tagged or published, and this PR was closed as superseded, not merged.

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