changelog
0.43.1 — 2026-10-08
Release recovery for the unpublished 0.43.0 candidate. Includes its Sovel syntax preview, built-in light theme and LSP file-rename changes below; the existing 0.43.0 source tag is preserved.
Fixed
- Worker cancellation under backpressure: synchronize cancellation and session-stop wakeups with the scheduler's wait lock. Previously a producer could miss the wakeup between checking its cancellation flag and parking, leaving a full stream queue blocked indefinitely. Cancellation callbacks still run outside that lock. The Loom regression now exposes the native token's scheduling gaps and checks callback reentry as well.
- Release assurance configuration: update the LSP queue's Loom fixture for file-rename requests and notifications so the concurrency gate compiles with the same request and queue types as the shipped client.
0.43.0 — 2026-10-08
Added
- Semantic file rename through the LSP (0054 §12): applying a reviewed
:fs rename/movenow asks every ready server advertisingworkspace/willRenameFilesfor preparation edits before the mutation runs. Returned edits apply straight to open buffers through the revision-checked gateway; the reviewed steps then execute, andworkspace/didRenameFilesreports only the receipts that committed. Servers without the capability (or with no open documents) change nothing;:cancel-changewhile waiting abandons the stage untouched. - Sovel — experimental syntax highlighting (0070):
.sovfiles open with a dedicated Tree-sitter presentation grammar (ABI 15, statically linked, pinned regeneration recipe undercrates/strop-syntax/grammars/sovel/). Modules, contextual/dependency/ effect clauses, generics, traits, regions, tasks, attributes, macro quotes/splices andextern "C"blocks highlight through the current theme; effect spellings stay contextual (an ordinaryreadidentifier is not an effect). Preview only: no Sovel server, formatter, symbols or validation is spawned or implied, and a colored tree is not a proof of source validity. - Built-in light theme:
theme = "light"in config.toml,--theme lightat launch, or:thememid-session (bare opens the selector,:theme dark|lightswitches directly). Same hue family as the dark default — amber accent, violet keywords, teal types — inked down for paper; the terminal's default palette follows on the same frame. Palette overrides stay 0005's follow-on.
0.42.0 — 2026-10-08
Added
- Snippet completion engine (0069 §1, 0059 §12 D05): LSP snippet items (
insertTextFormat: 2) now expand instead of refusing. Accepting a snippet inserts the expanded text as one undo unit and selects the first placeholder (painted like the selection); typing replaces it, Tab/Shift- Tab cycle tabstops with linked placeholders moving together,$0ends the session with the caret there, Esc ends and forwards. Choices expand to the first option; malformed snippets refuse with a named error and insert nothing. - Commit-character acceptance (0069 §2): typing one of the deliberately selected completion item's
commitCharactersaccepts it, then the character types normally. Only a chosen item commits — the menu alone never does.
Changed
- Hygiene run (0069 §3): the over-ceiling
editor/notify.rs(1,109 lines) is split by owner intonotify/{mod,queue,subscribe,reconcile}.rs; the 782-lineterminal_editortest harness is split into{main,harness,journeys,perf}.rs. No behavior changes; the notify and real-PTY suites pass unchanged.
0.41.0 — 2026-10-08
Changed
- Full UI polish (0068): every card breathes. The
Spacewhich-key overlay and its submenus are rebuilt: bottom-centered, width fitted to content, one padded key column, descriptions ellipsized with padding on both sides — never a mid-word cut against the border — and a+N morefooter instead of silently dropped rows. The:command card sizes its completion-name column to the candidates and right-aligns the live match count; the hover card sizes from a render-verified wrapped-row estimate (wrapped content was silently cut at the bottom border); the blame card fits its content; cursor-line diagnostic notes ellipsize at the row's real width (tab-aware); picker borders go quiet (MUTED, like the welcome card) and the rule line reads the palette; the modeline moves dirty/multicursor/ transient state off the accent so the mode chip is the only one left; scrollbar thumbs now size to the visible fraction; narrow input fields and long diff paths ellipsize instead of vanishing or hiding their stats.
0.40.0 — 2026-10-07
Fixed
strop <new-file>saves on the first:w(0067 §1): opening a file that does not exist yet and writing it failed withwrite failed: InvalidPath: an absolute non-control resource path is required— the CLI open keeps the relative name whencanonicalizecannot resolve a missing file, and the store guard rightly refuses relative targets. The save admission now resolves the target against the editor's cwd; existing files were never affected.
- Large-repo finders truncate calmly instead of erroring (0067 §2): past the catalog bound (100,000 rows or 64 MiB)
Space f/Space /showed a red "search results exceed 100000 rows" error even though the bounded prefix stayed ranked and openable. A bound is memory backpressure, not a failure: the walk now stops with the truthful warning "kept what streamed — narrow the query", results remain usable, Enter works, and typing narrows.
Changed
- Diagnostics don't repaint while you're typing (0067 §3): with LSP enabled, partial words used to get amber underlines on every keystroke — everything looks broken mid-thought. Language servers genuinely publish that eagerly, so strop adopts Neovim's
update_in_insert = falsedefault: while the edited document is in Insert mode its diagnostic publishes are stashed instead of displayed (publishes for other documents stay live, so split panes keep working), and the latest set applies the moment Insert ends. No timers, no debounce — the mode transition is the signal. The frozen set also stops matching on the first edit, so nothing paints stale positions mid-word.
0.39.0 — 2026-10-04
Fixed
- Stable syntax highlighting while typing (0066): ordinary edits and scrolls no longer blank the pane's colors for a frame. Previously an edit changed the viewport's byte window, defeated the interim cache and repainted every visible token in the default foreground until fresh analysis landed — measured at 98 unrelated cells flashing on a single inserted space, including an untouched function below the edit. The engine now keeps the accepted syntax on screen at coordinates mapped through the mutation journal (spans keep their fragments around an edit, coverage tracks what was analyzed, indent rails survive single-line edits and re-seat on multi-line ones), and the next exact result atomically replaces it — including genuinely changed syntax such as a newly opened string or comment. Genuine syntax reinterpretation still applies; only the delivery flash is gone. Search hit counts, modeline counts and line layouts remain current-revision-only, declared pane bounds stay truthfully loading while an interim frame is painted, failed analysis invalidates its window rather than looking settled, and input-to-render never waits on analysis. Typing on an 830 KB file renders no slower than before (median frame cost improved in the release-build measurement).
- Acknowledged actions publish before the ack (0066 release evidence): a
--ui-stdioaction that changes semantic state without moving the view generation — dismissing the completion menu — was acknowledged before its delta was published, so a client reading to the acknowledgement could observe the pre-action state; the completion qualification caught this as "dismissal retained obsolete query authority". The publication now precedes both the action and viewport acknowledgements, with a pipe-level regression test that fails on the old ordering.
0.38.1 — 2026-09-30
Added
- Tab live-preview completion cycling (0059 §6.1, user-requested): with the completion menu open,
Tabfocuses the popup and inserts the first candidate as a live preview;Tab/Shift-Tabcycle with wrap-around and each highlighted option is previewed in the buffer, imports included.Escape/Ctrl-Erestores the exact typed text,Ctrl-Y/Entercommits the shown candidate as the usual single undo unit, and typing keeps refining from the real prefix. Preview edits join the insert undo unit and never create separate undo steps; the menu footer and the?insert rows document the cycle keys.
0.38.0 — 2026-09-29
Changed
- The shared UI protocol owns no Linux editor/service dependency. Normalized key data and streaming script notation now live in
strop-core; engine, headless and protocol callers use that same implementation. A native MSVC lane exercises the existing framing and readonly-client contracts. - The dispatch keymap adapts the shared
strop-corecommand inventory, soSpace ?, which-key and key dispatch read one table whose command data a native frontend can consume without the Linux engine. Dispatch handlers and coverage are unchanged.
0.37.1 — 2026-09-29
Added
- Source-owned code completion (0059): Insert-mode language-server and incremental current-source word suggestions, a caret-anchored menu, selected documentation, and validated whole-operation acceptance.
Ctrl-Spacerequests both providers,Ctrl-X Ctrl-Orequests the language service,Ctrl-N/Pchooses,Ctrl-Yaccepts, andCtrl-Edismisses. Enter/Tab accept only after deliberate selection; one Escape dismisses and leaves Insert mode.[completion] enabled = falseretires completion-only work;auto_popup = falsekeeps manual completion and its active prefix updates.
Fixed
- Release artifact ownership: the runner creates
dist/before the Docker bind mount, so host-side qualification can write its report beside root-owned binary exports. This fixes publication without weakening artifact checks.
- Crate publication respects versioned test dependencies: the publication graph now places workspace dev-dependencies before their consumers because Cargo needs their registry versions when packaging the lockfile. Path-only local test helpers remain excluded. This fixes the ordering that stalled the initial 0.36.0 publication without changing its qualified tag or binaries.
- Language-server mutation positions reject invalid coordinates rather than clamping an out-of-range edit onto valid source text. UTF-16 conversion uses indexed rope coordinates, including surrogate-boundary and CRLF checks.
- Completion ownership survives neither a service rebind nor a revoked source capability: prepared edits recheck the original server, root, language, namespace, source window and write authority independently of cancellation. Represented imports and primary edits join the Insert undo group, including collection excerpts whose import lies outside the displayed source window.
- Completion-aware macro continuation waits for the recorded provider selection and owned acceptance before consuming the next generated key; Escape cannot overtake a pending completion in a replayed macro. Repaint (
Ctrl-L) preserves the current query and deliberate selection. - Language services follow the source's workspace: a file opened through a symlinked directory or outside the editor's current repository no longer loses its language-server binding. Discovery falls back to the file's own directory and refuses an unrelated current Git root. Explicit
:trustuses the owning project language layer even when the source is in a nested folder.
0.37.0 — unpublished tag
The qualified tag remains immutable. Linux shipping-artifact qualification passed but could not write its report into Docker's root-owned export directory; the release job never published. 0.37.1 contains the same editor behavior and the corrected artifact-directory creation.
0.36.0 — 2026-09-27
Added
- SSH worker transport and consent-gated deployment (0058 WK07): an SSH workspace whose host admits a worker now deploys the matching release binary through the verified-cache state machine (SFTP upload provider over the same OpenSSH policy and SFTP v3 codec, activated only after the real protocol handshake binds the endpoint's exact release/target/incarnation) and then serves open/list/read, reviewed filesystem mutations and filesystem-notification subscriptions through
strop --worker-stdioover the ssh exec channel — the same protocol as local, with no Python prerequisite and one audited POSIX discovery line (no PATH/rc/image mutation). The first deployment to an endpoint requires an explicit worker-using action and names the selected target, version and destination on refusal; a previously authorized endpoint quietly reuses its verified cache. Restricted or SFTP-only hosts keep the exact read-only SFTP behavior, with mutations answered by the typed refusal — never an alternate write path. - Explicit worker consent without write authority (0058 WK09–WK15):
:remote worker [URI]admits an SSH worker for Git/LSP/search without granting a document edit permit. Starting a remote search admits its worker off the input path.:container-workeradmits a worker for the selected Docker engine and exact container incarnation; browsing remains read-only and never deploys. Git and LSP in those namespaces now require their bound worker; missing tools or authority refuse typed instead of running Python or a container shell. - Consumer-driven worker streams and recovery (0058 WK11–WK13): finite reads, process output and live PTY VT bytes return chunk credits only as the consumer drains them; a stalled terminal no longer loses output to a full receive queue. Dropped streams drain without retaining unbounded data. Exec exit follows output delivery. Stale PTY/exec controls cannot cross a worker incarnation. An uncertain Store retains its prepared attempt for read-only verification after a worker restart in the same observed boot/mount/principal.
- Worker-owned scoped cache retirement (0058 WK05/WK19): an admitted SSH/container worker runs native cache maintenance before publishing ready. A persistent per-cache OS lock excludes new executable leases between the bounded lease snapshot and each unlink; the worker accepts only the context on its own private receipt. It preserves every recorded lease and its own executable, removes only unleased same-context receipted objects, and reports exact partial retirements on failure. Preinstalled objects remain outside the managed cache. Generalized lock/snapshot induction holds in the refined model, with native lease/process journeys on the retained release targets.
Changed
- Intel macOS support removed in 0.36.0: supported native targets are Linux x86_64/aarch64 and macOS Apple Silicon. Intel macOS no longer has a build/CI lane, release artifact or Homebrew download. Installer, updater and remote worker discovery refuse it instead of selecting an ARM binary. Read-only SSH/SFTP browsing remains available without worker admission.
Fixed
- Release evidence checks leave the checkout unchanged: the qualification commands disable Python bytecode-cache writes before importing their readers. The exact-source freeze no longer rejects
__pycache__files created by its own verification step.
- LSP stdin failure wakes a pending flush (0058 WK20): a queued worker write could fail while the LSP mainloop waited on a separate flush waker, leaving the server's exit unreported. Writes and flush acknowledgments now share one wake slot; failure closes the queue before waking, and the poller registers before inspecting either success or failure. The deterministic failed-write regression fails before the fix and reports
BrokenPipeafterward.
- Editor exit retires its native worker (0058 WK20): ordinary UI/headless/TUI shutdown now closes local, admitted SSH and admitted container worker leases even when a background job still holds a clone. A closed clone cannot reconnect or publish a late worker. On the exact 0.36.0 static Linux build, all 64 real 17.28 MiB UI-open journeys left zero workers after editor exit, versus 64/64 survivors on the original pre-worker baseline. Open→visible p50 was 104.601 ms versus 78.876 ms while qualification jobs shared the host; this is not an isolated latency comparison. Worker kernel peak-memory p50 increased from 5,036 to 7,232 KiB. GNU x86_64/aarch64 and Apple Silicon also retired every worker across 64 native opens each. A separate 64-sample no-init BusyBox container measurement left 894 Docker-exec bootstrap
sh/catzombies (including fixture resets); no bound on an unrelated nonreaping PID 1's process table is claimed.
- Search shutdown cannot miss physical completion (0058 WK20): after a picker source posts its final event, its thread may exit without sending another wake. UI stdio, headless and TUI jobs waits now recheck that condition every 16 ms during shutdown, retaining their absolute failure deadline and never polling input→render. The eight-backend real search/shutdown journey passes in the Docker musl gate and the host GNU build.
- Unsupported native watches replay honestly on macOS (0058 WK20): the forensic
Notifyaction previously recorded a queue wake but not its settled worker refusal. The editor now records the bounded typed drain and its rescan latch in consented full-content traces. Execution-free replay therefore retains the visible on-demand freshness warning instead of silently showing an empty status; ordinary uncaptured input incurs no serialization.
- Recovered Store refuses acknowledged attempts (0058 WK18): the worker now checks the actual Unconfirmed receipt outcome in addition to the attested, matching namespace before read-only reconciliation; a caller cannot make a committed receipt eligible by selecting the recovered request name.
- Ranged worker reads close exactly once (0058 WK19): an exact-length read previously enqueued a terminal data chunk and then another terminal marker on the next loop. The second marker could reach the client after its stream was closed and poison the session (
chunk on unknown stream 0). The read now returns after enqueueing its final chunk; a subsequent health request and read remain live. - Worker cache leases follow the real session (0058 WK19): a deployment probe previously left a cache record for its own short-lived lease while the editor's live SSH worker had none. The worker now verifies its final object and matching private receipt, acquires the cache lock and registers its session before Welcome; SSH/container admission awaits that handshake, and reconnect registers a new lease. Stale crash records remain conservatively pinned; the native collector does not infer their age or liveness.
- Deleted cache objects cannot become unleased workers (0058 WK19): removing a verified object after exec but before Hello previously bypassed cache-lease registration and still granted Welcome. Cached workers now refuse an unlinked or changed final path before admission (including a Linux running-inode check); the client receives that pre-handshake protocol error instead of a ten-second timeout. The cache lock now excludes retirement until a new admission has validated and registered the running inode.
- Shutdown no longer wedges behind a full event lane (0057 VF19, the UiSessionModel storm finding): under a parallel storm of streaming-search-then-shutdown sessions, a backend could burn its whole jobs budget in
finish()instead of settling. A storm-full semantic event lane refused the forwarded picker-rankingStoppedevent; the forwarder treated the refusal as terminal, died, andpicker_ranking.retiringnever emptied. Forwarded job-channel events (one-shot facts: completions, terminal stops) now backpressure a full lane instead of being dropped — the picker stream bridge included. - Protected Store rejects stale same-time sources (0058 WK09): save preserves the original nanosecond mtime, owner/group, mode and bounded xattr set. The remote edit permit retains content, inode and xattr baselines; an external same-size change with restored mtime or a replaced inode cannot borrow that permit. Worker-loss or cancellation after Store submission remains unconfirmed until verified; no automatic write retry or source-dirty acknowledgment.
- Cache retirement no longer races another worker's Welcome (0058 WK06/WK19): the former standalone deployment-side GC and its policy-only tests are gone. The actual worker holds the stable owner-private OS lock across lease snapshot and scoped retirement; foreign contexts and corrupt lease records cannot authorize unlink. A second real worker/build survives collection. Both the SSH editor and container editor retire an old object and receipt during explicit worker admission; the direct SSH/provider journey does too. Crashed worker leases stay pinned.
- Worker activation checks the exact receipt (0058 WK18): the cache reissues a stale receipt for the verified object and checks its release, target, size, digest and provenance after writing. A receipt lost after a successful write reply refuses before launch rather than claiming readiness.
- Terminal flood exit no longer overtakes queued output (0058 WK12): worker wire frames remain ordered, but the client routes output and status into separate channels. The terminal retains the status until the output stream's final marker; a 32-chunk UI turn cannot mistake still-queued VT bytes for output sent after exit.
- Worker-leased LSP exit no longer loses its EOF wake (0058 WK10): the stdout pump closes its bounded queue before waking a parked mainloop reader. A server that exits after answering configuration now publishes its classified status instead of stranding a pending read forever.
- Worker terminal flood no longer sleeps on a stale wake (0058 WK12/WK20): the service drained all stream-arrival nudges after reading only 32 buffered output chunks, then waited for its 250 ms recovery timeout while later chunks were already queued. It now drains the wake first and rechecks after each published bounded turn. A real 256-line PTY burst changed from pre-fix p50 256.634 ms to p50 4.922 ms on the session-safe static worker; the matched pre-worker was p50 4.335 ms on the same Linux host.
- macOS exec/PTY exits retain their attested status (0058 WK20): XNU may return
EPERMfor a process-group signal when only its unreaped zombie leader remains. The worker now accepts that result only after a non-reaping exit observation and a bounded systemlibproclisting confirms the leader is the sole member; an inaccessible descendant or uncertain listing still refuses clean settlement. Direct supervisor, framed worker, SSH and real terminal journeys pass on the retained Apple Silicon profile. - Prepared writes cannot cross a worker restart (0058 WK10/WK20): a killed worker's stale
Prepareresult previously could be applied after reconnect, even after a fresh worker prepared the same absent target. Prepared reviews and protected Stores now retain the respondingSession; the client checks the live session before uploading content or sendingApply. The editor refuses a stale review before materializing its frozen content; remote Stores refuse before constructing the upload. Native killed-worker, protected-Store and review-acceptance regressions pass. - Native terminal Alt input retains ESC on macOS (0058 WK20): native qualification exposed the lost
1bbeforexin the actual PTY. Ghostty's encoder resetmacos_option_as_alton each terminal-mode refresh; Strop now reasserts logical Alt afterward, with no platform shortcut or changed child-byte expectation. The exact-byte native Apple Silicon TUI journey passes. - Native SSH fixture uses real scoped loopback sockets (0058 WK20): macOS BSM auditing rejected
sshd -ilaunched on a pipe with anUNKNOWNpeer. Each SSH test now owns and reaps two authenticated loopback OpenSSH listeners (ordinary and SFTP-only), retaining Python-free parity. Linux relays native Notify hints; macOS advertises Unsupported and must refuse subscription typed. Apple Silicon passes the real SSH deploy/read/write journey and the exact typed notification refusal. - Container workers no longer carry an extra sh/cat supervisor (0058 WK08/WK20): verified-worker probes and live leases now use the same direct native
docker execon provisioned and shellless endpoints.ShellPolicygoverns only cache/bootstrap mutations; preinstalled mode refuses tar writes before staging any bytes. Selected engine/user/cwd admission and live lease tests still pass. No-init containers remain supported with the documented risk that a nonreaping PID 1 retains orphaned bootstrap processes; Strop does not claim a bounded whole-container process table. - Trace capture no longer self-invalidates its watched workspace: notifications for the active trace file are filtered before waking the editor; unrelated file hints still reach source invalidation.
Verification
- Core-assurance migration (0057 VF20 / 0058 WK16–WK20): the pre-worker candidate, inventory and Python helper digests are preserved byte-for-byte under
verification/baseline/. The worker lane replaces helper-bundle tests with native Store, recovered receipts, PTY stream-credit and concurrent cache-install journeys, plus the existing instrumented Loom and mutant campaigns. All 109 current claims are bound to their source and evidence with zero blocked entries; publication still runs the exact-source gates. - Matched local product measurements (0058 WK20): the original clean
a05d84fpre-worker artifact and the 0.36.0 static candidate (5a9f826f, 47,390,032 bytes) each ran eight warmups and 64 real worker launches, semantic UI edits, physical TUI cell-grid edits and 256-line worker-PTY output bursts. Current local Health p50 was 0.197 ms; TUI input→paint p50 was 0.915 ms versus 0.972 ms. Full raw distributions, bytes and process observations live inverification/measurements/; these are scoped observations, not universal latency guarantees. - Retained native platform qualification: GNU Linux x86_64, GNU Linux aarch64 and Apple Silicon each passed native worker/SSH/PTY/editor journeys plus 64-sample product, real-server LSP and 17.28 MiB transfer measurements. Every current worker was absent after all 64 editor exits on each profile. The raw native bundle binds executed artifact digests, 67 runtime-source hashes, workspace/lock, pinned compiler selector and benchmark methods; missing targets, stale sources, mixed binaries and worker survivors fail the release-evidence validator.
- Real SSH and container deployment measurements: the exact static candidate ran 64 authenticated Python-free TCP OpenSSH cold/warm/live paths (p50 3730.683/2231.547/123.521 ms) and selected no-init Docker paths (3464.558/2904.992/88.516 ms). Each live admission retained one actual worker. The BusyBox sample retained 894 external
sh/catzombies, including 71 private fixture resets; that is not a live-worker count or a bounded PID-1 retirement claim. Host qualification work overlapped; these are neither isolated-host nor WAN latency guarantees. - Real-server LSP measurements: 64 painted
rust-analyzerdefinition replies on the matched WSL2 artifacts measured p50 25.780/25.666 ms before/after. The separate 64 edit→didChange→ request→paint path measured 26.055/26.017 ms; it is not standalonedidChangeacknowledgment latency. The retained native profiles have their own raw LSP comparisons in the native evidence bundle. - Python-free SSH deployment evidence (0058 WK07): a separate OpenSSH image without a Python interpreter deploys and launches the actual native worker, then exercises read/write/notification parity. CI runs this gate independently of the Python-equipped full suite.
- Symbolic worker-session safety proof (0058 WK17): TLAPS proves initialization, 19-action/stutter induction and named authority, stream-credit, exit-order and uncertain-Store invariants over the same WorkerSession model TLC exhausts. Three bounded configurations retain their exact pre-normalization graphs; a matched healthy Commit proof passes and the stale-commit mutant fails its own safety obligation. Same-source Rust admission kernels now cover framing, recovery and terminal delivery; OS effects and observer premises are still separate evidence obligations.
- Symbolic worker-deployment safety proof (0058 WK17): TLAPS now proves initialization, 15-action/stutter induction and consent/verified-activation/selected-context/owned-cleanup/live-lease safety over the same WorkerDeploy model TLC exhausts, for arbitrary nonempty client/context/digest sets. The full-state normalization preserves the exact bounded state graphs; a matched scoped-Collect proof passes and the cross-context cleanup mutant fails its own obligation. Same-source Rust admission kernels now cover catalog, content addresses, staged activation and lease-aware keep decisions; the native collector's cross-client lock/snapshot exclusion has a separate refined proof.
- Native cache exclusion model and generalized proof (0058 WK17–WK19): TLC exhausts 35,897 two-client/two-context/two-build lock/snapshot/retirement states, kills four attributable faults and reaches concurrent workers, a blocked Welcome and a crashed lease. TLAPS proves 447 induction obligations over the same transition relation, including snapshot completeness, active-object, stale-record and scoped-retirement invariants for arbitrary nonempty client, context and digest sets disjoint from the non-value sentinel. Another 34 conditional retirement obligations pass; the matched foreign-context mutation fails its own theorem. This does not prove the OS lock, native macOS/arm execution or full WK20 performance.
- Mutant calibration and worker admission proof (0057 VF19 / 0058 WK17–WK18):
verification/mutants.jsonmaps 20 seams to 99 named kill obligations. Four refined cache-model faults violate their named invariants; the two executable Rust GC mutants die under the real lock and second-worker tests in the core-assurance gate. The native session classifier verifies against the pinned Verus toolchain; OS and deployment premises are not discharged by that pure admission proof. - Same-source worker admission proofs (0058 WK18): the wire decoder, recovered-Store verifier, effect resolver, deploy compatibility/ activation and GC call verified
strop-corekernels. The proof covers those pure decisions, not codec byte scanning, SSH/Docker/OS effects, receipt provenance, host durability or cross-client GC. Live worker journeys include a lost acknowledgment and a lost or stale activation receipt. Native effects and performance are qualified separately from these pure proofs in the retained-target evidence bundle.
0.35.0 — 2026-09-17
Filesystem notifications: the editor watches the workspace natively and search stays fresh without rescanning everything.
Added
- Filesystem notifications reach the editor (0058 S7): strop subscribes to the opened workspace through the local worker — a thin direct inotify binding with generation-stamped subscriptions, parent/name guard watches, honest overflow/loss reporting, and bounded coalescing that never silently drops staleness. External changes to clean buffers reload guarded (binding and revision re-checked at publication, your own saves never echo); dirty buffers keep your edits and show "changed on disk; the buffer keeps your edits (:w! forces)"; ambiguous renames never relocate a document; directories re-list and Git signs refresh lazily. Unsupported filesystems degrade visibly, never to an invisible crawl.
- Incremental search and symbol freshness (0063 residual): unchanged files and unhinted catalog subtrees are reused (stat-validated, so a missed hint self-heals) while hinted ones rescan — edits on disk show up in the next search without a full walk.
- The native worker foundation (0058 WK01–WK04): one bounded, versioned worker protocol (incarnation-keyed handshake, typed refusals, first-class notify family), a real
strop --worker-stdiolocal mode serving filesystem and exec requests through the shared strop-fs kernel, and a Rust process supervisor with leased TERM/grace/KILL group cleanup. Local file operations ride the worker; a killed worker is a typed failure, never a silent fallback.
Verification
- The notification lifecycle is modeled in TLA+ (specs/Notify.tla): eleven safety invariants — events are hints, stale identities never act, dirty buffers are never clobbered, scan completion never erases newer invalidations — with nine kept mutants each killed by exactly its named property, gated in the model lane.
0.34.0 — 2026-09-17
The architecture release: readonly rendering behind a narrow admitted API, real draft recovery, a modal terminal, vim grammar in every input field, a process UI protocol, and the first whole-core verification lanes.
Added
- Modal terminal (0065): the embedded terminal presents honestly as its own state — a distinct TERMINAL chip, every escape discoverable in
?help (Esc stays with the child; Ctrl-\ Ctrl-N and Ctrl-W N enter inspection), a strop-owned palette (singlestrop-coretheme seed configuring the emulator through a new native palette FFI), and:terminal-refreshto pull the latest output into the pinned inspection view. Motions, visual yank and search work over terminal text like any buffer. - Real dirty/scratch draft recovery (0056 AR04): unsaved changes checkpoint continuously to a private, bounded, atomically-published store — coherent multi-document cohorts, never half-mixed, whole records demoted rather than truncated.
:recoverlists checkpoints with origin, time/revision and conflict state and restores into a checked draft without touching disk, recreating deleted targets or re-granting remote authority; remote drafts persist only with explicit consent. Crash, kill and power-loss keep the last completed checkpoint.
- Non-graphical UI protocol,
strop --ui-stdiobackend and Rust driver (0056 AR09/AR10): a bounded, versioned process protocol for frontends without a terminal. Content-Length-framed envelopes (the LSP byte convention, distinct envelopes) carry a version/build/capability handshake keyed by backend incarnation; admitted actions reuse the engine's exact admitted input surface (no parallel command table); semantic views publish as snapshots/deltas keyed by incarnation + view generation + pane document revisions; a dropped delta poisons the client until an explicit resync ships a complete snapshot; stale-generation, wrong-incarnation, oversized and malformed traffic earn typed refusals/errors, never a panic or silent divergence; OSC52 clipboard writes cross as host effect request/result; shutdown is authorized and orderly. Thestrop-ui-protocolcrate ships the pure client state machine plus a stdio driver whose deterministic schedules barrier on acknowledgements, generations and named view states — closure tests spawn the real binary over pipes (the WSL stdio lane) and pin open/edit/undo/search/save/terminal parity with the headless driver.
Improved
- Pure render cutover (0056 AR01/AR03): painting is now a readonly query over the engine. Hunk refresh, visible-window analysis/preview admission and viewport/caret adjustment moved out of the renderer into
Editor::prepare_view, an admitted engine update keyed by an explicit terminal-cell geometry and a stamp over view-relevant state — an unchanged repaint admits zero worker tickets and touches no viewport. The engine publishes a prepared view (per-pane windows keyed by document revision and view generation, with declared complete/partial/loading/stale/error bounds) that the TUI borrows; no whole-editor clones. Picker card/pane/gutter geometry lives in the engine as plain cell arithmetic, pinned cell-for-cell against the historical layout. - Input fields speak the real vim grammar (0003 §2): picker query/replace fields and the
: / ? |line are backed by persistent one-line buffers whose normal mode resolves through the samestrop_grammar::resolveas the document —dw/db/de/cw/dfx/2dw/diwwork in every field, a completed operator reranks exactly once, pure motions never do, and unsupported keys refuse with a message instead of vanishing. - One narrow admitted engine API (0056 AR02):
Editorno longer has public fields — the frontend boundary is one module of readonly borrowed queries and admitted writers. Paint cannot mutate anything; document/selection/pane mutation is engine-internal. - Bounded event and LSP transport (0056 AR06): the event channel and LSP wire queue have real byte/count bounds. Semantic events (input, paste, outcomes) are never dropped or reordered — admission refuses visibly instead; wake hints coalesce legally; superseded unsent LSP snapshots coalesce only when no request barrier needs them; shutdown drains without hangs.
- Container execution tells the truth (0056 AR07): exec requests pin the selected engine context, cwd, principal and container incarnation — a recycled container never receives a stale request. A supervised lease reaps the whole session's descendants on close or client death; missing programs and shim toolchains are typed diagnostics.
:explainnames real owners (0056 AR08/AR14): readonly buffers carry typed reasons (filesystem, remote capability, command, recovery, terminal…) instead of a generic hint, config shows the winning layer per knob, and one privacy classifier now governs capture/persistence across local, SSH and container targets.- Identities refuse to wrap (0056 AR13): arena document identities check exhaustion instead of incrementing blindly — a slot at the generation limit retires forever, so a stale id can never alias a new document.
- Installs and updates are one verified transaction (0056 AR11/12): install.sh and the updater share staged, sha256-verified, atomic publishing (interruption preserves the old binary); channel identity comes from an installation receipt, never path guessing; releases generate a single catalog that installer, updater and site consume.
- Build inputs are pinned (0056 AR15): every Docker base image is digest-pinned multi-arch, with the bump procedure documented.
Fixed
- Preview caches could survive a rename through a symlinked path (0056 AR05): purges now cover the operation's logical spellings, not only resolved ones.
Verification
- The 0057 whole-core assurance programme opened: a machine-readable boundary/claim inventory with hash-pinned evidence and candidate freeze tooling guards the tree in CI (VF01); a pinned TLAPS lane proves the search lifecycle's safety invariants inductively — with the kept mutant failing proof on exactly its own steps — and the 0045 batch-composition obligation is now proved in production editmap.rs (Verus); new Verus kernels verify mutation authority, projection admission, recovery cohort, UI freshness and ID-exhaustion decisions the production guards call; admission-freshness trace correspondence replays the protocol model through the real handlers; new gated TLA+ models cover terminal and install domains with kept mutants and witnesses (VF02–VF04, VF12, VF16–VF18 partial — the recovery and LSP-wire models exist but are not gate-chained yet: state-space calibration resumes in 0.35.0).
0.33.0 — 2026-09-16
One canonical query language across every search surface, automatic workspace symbols, per-pane scrollbars with a Git overview, and the optional cursor fade.
Added
- One typed Boolean query grammar everywhere: standalone
AND/OR/NOT(balanced-paren grouping, NOT > AND > OR, juxtaposition is an implicit AND),kind:/repo:qualifiers decided exactly from the syntax-fallback index and project catalog, and three-valued admission that never drops a line on undecidable evidence. Files, Search, Replace, the Directory filter and both symbol pickers share the one parser; operator-free queries behave byte-identically to before. - Workspace symbols without ceremony (
Space S): open a parent directory and every declaration is listed through the bounded syntax-fallback index — nested repositories, worktrees and marker projects discovered automatically, no repository list to configure. Eligible unopened projects warm their language servers behind the picker (at most four in flight) and merge server symbols into the same list; per-project status rows name missing or blocked server configuration (cold,no srv,trust,no exec) instead of leaving silence, and Enter on a status row opens the project. - Per-pane scrollbars: every editor and terminal pane reserves its rightmost column for a quiet track carrying the fractional viewport thumb and the document's added/changed/deleted Git spans; terminal panes show position over their bounded history.
- Optional cursor fade: the Normal-mode block cursor fades back in over 160 ms after focus returns or a large jump (
cursor_fade = falsein config.toml disables it wholesale). - Stored-query syntax versioning: persisted queries carry an explicit syntax version with typed rejection of unknown versions and a fixed-point v1→v2 migration that keeps previously-literal operator words and qualifiers literal.
Improved
- Replacement through the Boolean grammar: With/Review decides one identifiable target by NOT-parity and re-checks every span, so foreign or stale provider spans never edit.
- Symbol pickers filter with the real query: document and workspace symbols narrow by content and
kind:through the same admission as search; only bare content text ever reaches language servers, and replies are filtered locally before merging.
Fixed
- Query-wide options no longer leak into Boolean branches:
case:/hidden:/ignored:inside an operator expression used to become inert atoms that silently broadenedORadmission; they now live only as query-wide options and canonical formatting places them in a leading preamble. - Wildcard globs match unanchored stars correctly:
*.rsno longer missesa/src/x.rs(found by the differential reference interpreter).
Verification
- The search lifecycle is modeled in TLA+ with a kept mutant the model checker kills on exactly the named invariants; the publication boundary's guards are a Verus-verified kernel the production handlers call, with model traces replayed through the real admission paths; and the end-to-end mixed-directory fixture now covers worktrees, dirty sources, cancellation, supersede, Unicode locations and remote namespace isolation.
0.32.4 — 2026-09-14
Captures of terminal sessions cost a third of 0.32.3's bytes.
Improved
- Repeating rows travel once per frame: rows that recur anywhere in a terminal frame serialize into a first-appearance dictionary and are referenced by index runs; unique rows stay inline, so varied content pays nothing. A flooded frame collapses to its few distinct rows and repeated positions decode back to one shared row — the same session that recorded ~2.4 MB now records ~0.7 MB, complete and replayable, and slow-runner drains halve. Traces from every earlier version still decode.
0.32.3 — 2026-09-14
Captures of terminal sessions cost a tenth of the bytes.
Improved
- Terminal frames and per-action checks stopped shipping padding and duplicate text: each row's trailing default-styled space padding is re-derived from the frame's geometry on decode, and observation checks witness document text by SHA-256 digest plus its true byte length instead of serializing the whole buffer every action (the startup seed still carries full text). The same flooded-terminal session that recorded ~20 MB now records ~2.4 MB, still complete and replayable — cheaper captures, faster drains on slow runners. Traces written by earlier versions still replay.
0.32.2 — 2026-09-14
Forensic captures survive real terminal sessions.
Fixed
- A flooded terminal no longer fails the editor's exit (
:qaafter sustained output exited 1 withincomplete trace): three stacked capture bugs, each fixed at its source. Terminal frames now serialize as cell runs with the projection derived on decode, so a full-history frame costs its content size instead of ~180 MB; mutation, history, document and paste records carry bounded, explicitly-marked text excerpts beside true byte counts, so opening a large buffer can no longer degrade the whole capture; and an honestly-degraded capture (value or budget cap) is reported by the file's terminal marker instead of failing the process exit — writer I/O failures remain fatal. The opt-in capture byte bound rose to 512 MB to match what consented interactive terminal sessions legitimately record. Captures written by earlier versions still replay; the sustained-output test now asserts the trace stays complete and replays execution-free.
0.32.1 — 2026-09-14
Picker previews and terminal navigation, from first real-use feedback.
Fixed
- Picker previews never loaded (find-file stuck on
loading…, grep and symbols without syntax highlighting): the readonly-rendering split left the preview's bounded read and syntax analysis with no admission site — paint only reads caches. Frame preparation now admits both with exactly the window paint will query, restoring content, line numbers, the ▶ hit marker and syntax colors across all workspace pickers.
Added
Ctrl-Wis an editor window prefix inside terminal input (Vim's documentedt_CTRL-Wgrammar):h/j/k/l/w(and arrows) move or cycle panes,Nenters the pinned-snapshot inspection (same asCtrl-\ Ctrl-N), and.delivers the literal byte to the child. Any other follow-up passes through unchanged, and the message line hints the grammar while the prefix is open.- Terminals are real switchable buffers: the buffers picker lists them with Vim's
!job badge and their live phase (terminal #N · dir · running), jump rows use the same truthful labels, and entering a terminal records a jump soCtrl-Oreturns to your editing position.
0.32.0 — 2026-09-13
Embedded local terminals, full-screen pickers with wider previews, and a folder-view refresh (plans 0054/0055 TUI milestones; all GUI work remains deferred).
Added
- Local terminals (
:terminal): a real PTY-backed shell in the editor (strop-terminal, pinned static Ghostty VT engine; source builds require explicitly installed Zig 0.16.0 — prebuilt users need neither). Terminal input keeps Esc/Ctrl-C/Ctrl-R/Alt/F-key meanings for the child; Ctrl-\\ Ctrl-N (legacy terminals may spell it Ctrl-4) enters editor Normal inspection over a pinned read-only text snapshot; i/a follow the live cursor again. An exited session stays readable and never restarts implicitly. :terminal COMMAND, directory Terminal here,:terminal-localfor an explicit local shell from SSH/container contexts (interactive execution is refused there, never silently redirected),:terminal-stop, and:terminal-paste/:terminal-paste-cancelfor held multiline/control paste consent.:qarefuses live sessions and:qa!stops and drains them.- Terminal buffers are real read-only buffers: normal motions,
/search, selection and yank; splits mirror the live cell grid while one focused view owns PTY geometry. Background descendants are reaped (pidfd-supervised on Linux); deliberately detached processes are explicitly out of scope. - Terminal privacy: content is excluded from traces even with
--log-content. Entering a terminal records an opaque marker and downgrades the capture to metadata-only; replay refuses the omitted history.--log-terminal-contentis the separate explicit consent, visibly marked REC. Full--replayreproduces terminal sessions with no PTY/process/clipboard/network effects; input-only extraction refuses terminal traces honestly. - Keyboard capability advertisement is captured per session; legacy frontends get a truthful legacy profile and the child's kitty-protocol queries are masked to it (no fabricated enhanced keys, focus reporting follows mode 1004).
inputheadless directive for complete physical key/text/paste events with modifiers and repeat/release information;keysnow accepts chords (<a-x>,<f5>) and physical facts are preserved until the engine selects the input owner.
Changed
- Folder views align human-readable sizes and permissions behind full filenames. A quieter breadcrumb header, restrained type accents and full-width cursor/mark bands make the listing easier to scan without changing native entry identity.
- Folder marks have visible gutter indicators. Empty folders and filtered-empty listings show an explicit state instead of file-buffer filler rows; parent rows expose the existing open/actions affordances.
- Find File, Symbols and Search share one stable near-full-frame workspace instead of a smaller floating card. The file preview gets the wider share (grep 60/40, files/symbols 55/45), so source evidence reads comfortably next to the decision list. Transient pickers keep the floating card.
- Picker previews keep the rootle-style line gutter (▶ marker on the focused line, right-aligned numbers,
│divider) across files, grep and symbols; the marker now reads accent+bold on the selection band. - Rendering is readonly (0056 AR01 core): an idempotent preparation step owns every paint-path admission and viewport adjustment behind a state/geometry stamp; repeated paints allocate no worker tickets and painting itself is a pure
&Editorquery.
0.31.1 — 2026-09-13
One filesystem workspace: browse, edit names, review operations (0054).
The 0.31.0 tag was not published: its hosted release gate caught a scheduler-dependent trace-capture failure. This release includes the correction without moving the existing tag.
Added
- One namespace-aware Directory buffer for local, SSH and read-only container browsing, with current-file reveal, native-name completion, filtering and preview.
- Checked filesystem creation, rename/move, stored/buffer copy, local Trash and explicit permanent removal. Missing parents are reviewed separately; occupied destinations are never silently replaced.
- Modal filename drafts use ordinary edits, yank/paste and undo without touching disk. Review and Apply share the explicit filesystem-operation planner.
- Filesystem receipts retain committed, cancelled, partial and unconfirmed outcomes independently of their views, with verification and checked recovery.
- Directory Search here (
:fs search/Space a) captures a local or SSH namespace/root without changing cwd. Capable SSH hosts run read-onlyrgthrough supervised argv transport; source previews, opening and collection loading preserve endpoint and native filename identity. - Search source previews have absolute line-number gutters and focused-hit markers. SSH Search explicitly disables With/Review.
Fixed
- Trace admission uses the capture's byte/event budgets rather than a 64-record scheduling window. Valid bursts survive a delayed writer without blocking input; real capture limits and writer failures still produce incomplete traces.
- Filesystem operations explicitly unlock cooperative name locks when they end. A concurrent process fork can no longer retain a completed operation's lock through an inherited descriptor and spuriously refuse the next batch step.
- Search grouping, exclusions, preview caches and tab-width lookup distinguish local files and different SSH hosts even when native paths are identical.
- Invalid rg records, escaped paths, unsupported source text and oversized records fail explicitly instead of disappearing from the result set.
- Filename-review cancellation returns directly to the intact draft. Browsing or refreshing its directory preserves edited names, undo history and provenance.
- Directory selections follow confirmed renames through re-sorting and name reuse. Stale path completions and transferred LSP navigation cannot restore old bindings; deferred format-and-save work blocks conflicting relocation.
- Native Ex open operands remain local outside Directory buffers. Directory operands and completion share the captured directory namespace.
- Search bounds decoded batches, source enumeration, total result rows and retained row data; a small rg record cannot amplify into an unbounded batch.
- Local save/save-as and filesystem mutations serialize in both directions, including unresolved symlinked-parent destinations.
- Move verification and recovery require an owned post-publication version; pathname occupancy or a reused inode cannot authorize recovery.
- Empty-directory removal can retire validated, quiescent protocol locks without splitting active lock domains. Cancellation retains confirmed cleanup counts.
- Admitted effects keep their actual receipts through cancellation and cleanup failures; uncertain Trash publication retains its recovery metadata.
- Remote source identity stays with the filename under modeline width pressure, rather than disappearing with optional Git context.
0.30.0 — 2026-09-12
One Search workspace, replacement on demand (0053).
Changed
Space /andSpace Renter one retained Search investigation.Ctrl-Rshows/hides With without relaunching search or discarding drafts, field carets, modal state, selection, scope or inclusion decisions.- One large responsive card, common source/delta rows, visible workset counts, filename/parent identity and distinct source-preview background in both modes. Tiny terminals prioritize the active field and preserve the result viewport.
- Find Enter opens the selected source; With Enter prepares an owned background review. Apply edits real buffers; Save remains explicit for every target.
Ctrl-XandCtrl-Dcurate one workset shared by Collect and Review. Same-query re-entry restores only exact witnesses and reports lost decisions.- Query, replacement/workset and review generations are independent. Late review completions cannot retarget or steal newer focus; changed sources are refused.
- Full replay semantics advance for the new keyboard and owned-review contract.
Fixed
- Stale Search hits cannot open or collect unrelated current source coordinates.
- Review preparation coalesces canonical aliases, retains dirty-buffer authority and reports read-only, stale and unavailable sources alongside exact diffs.
- Long captured roots cannot overwrite Search error headlines.
- Container integration gates include the Docker client and fail, rather than silently skip, when an explicitly required engine is unavailable.
0.29.0 — 2026-09-12
Whole-editor polish and one query language (0051).
Added
- Shared
language:,path:,glob:,text:,regex:, visibility and case qualifiers across file finding, grep and replacement. Filter-only file queries work; invalid/incomplete queries cannot silently broaden a search or apply edits. - Parser-driven highlighting and Ctrl-Space query suggestions, shared modal field geometry, and separate visible hidden/ignored controls. Unignored dotfiles are included by default; repository internals remain excluded.
- Source-specific indentation controls (
:tab-size, numeric/Auto forms and:indent-style) with conservative detection and explicit provenance. - Passive source-aware delimiter highlighting through the shared cancellable resolver, including collection projection and stale-result rejection.
- Explicit replacement review, Apply and Save stages.
:save-changeopens an owned per-file persistence receipt; open and previously unopened files behave alike.
Changed
- Collection edits publish to sources and other views while typing, with source syntax, context expansion, independent match counts and ordinary Insert grouping.
- Temporary outputs and jump history share complete, journal-remapped view records. Modelines identify the effective input owner; split identity, source settings, Unicode clipping and full logical-row selection share display-cell geometry.
- Replacement rows and review buffers use source identities and exact diff roles. Shell output preserves command/cwd, both streams and the completion/failure stage.
- Ranking coalesces updates while one snapshot is in flight. The 100k-result probe fell from roughly 2 GB peak live heap to 51 MB, rather than queueing a snapshot for every stream batch.
- Source-to-view publication uses exact journal edits, preserving collection carets, marks and jump history through undo and structural source refreshes.
- Oversized collection, renderer, LSP and regression modules are split by responsibility.
Fixed
- Dirty source previews refresh on picker re-entry; cancelled/failed reads are retryable without relabeling old content as a new source.
- Replacement checks complete line witnesses, including regex context and empty matches. Edited review buffers cannot apply a hidden older proposal.
- Collection save/close tracks admitted source writes, including remote receipts; unrelated completions, failures and newer edits cannot close the view.
- Grouped collection history preflights every source's authority and revision capacity. Formatter warnings survive persistence, and stale formatting cannot save newer edits.
- Late hover replies cannot consume typing; query diagnostics remain visible without overlapping titles or advertising controls that the active field does not own.
Migration: old -t/-g picker syntax is now ordinary literal text; use the named qualifiers and :help query. In-buffer Vim search is unchanged. Replacement values remain literal, including $1. Older unversioned semantic captures refuse replay.
0.28.0 — 2026-09-11
Picker visual polish (0050) and the multibuffer's missing paint.
Added — picker rows rebuilt per kind (0050)
- Symbols: fixed-width quiet kind chips (all names share one left edge across
fn/field/variant), container in the secondary role, location right-aligned. Four quiet kind families (callables/types/ data/modules); chips are passive type info, never bright buttons. - Files: basename first and bright, the parent path secondary at the right edge, middle-elided without dropping the disambiguator; query matches emphasize in whichever field they hit.
- Grep: two-line hits — filename + directory, then a code window built around the REAL
rgsubmatch (never the first 80 chars of the line), with the match amber/bold. Line numbers in the secondary role. - Selection is one band: the whole allocated row — marker, fields, blanks — carries the selection background; query evidence survives on top of it.
- Honest counts and states: filtered lists show
visible / total; an empty filter explains itself (No symbols match “zzz”+ the way out); errors stay actionable. - Responsive layout: the decision list takes ~60% (65% for grep) of the card; narrow terminals stack a short preview below instead of two slivers; the scrollbar column is reserved before text budgets and never overwrites evidence. Preview headers identify
file:linefirst.
Added — multibuffer paint (0049 §6 remainder)
- Syntax highlighting in excerpts: body rows project the source document's own analysis spans into the view — never a synthetic combined parse.
- Query hits paint in excerpt bodies (amber/bold), sourced from the picker's real submatch geometry; LSP/symbol navigation payloads never fabricate a one-byte match.
- Focused card: the caret's card brightens its border and path.
]f/[fwalk file cards (next/previous), recorded in the jumplist.
The picker renderer split into render/picker/{mod,rows,preview}.rs by responsibility (0050 §9).
0.27.0 — 2026-09-11
The multibuffer gets its presentation (0049 §6), and picker rows gain structure coloring.
Added
- Collection cards: one rounded card per source file — top border with the path in accent, language and modified badges, excerpt bodies guttering their SOURCE line numbers, a
⋮ N source lines omittedgap row between disjoint excerpts of one file, and a full-width bottom border. Cards present in path order. The title row reports matches, files and dirty sources. Chrome rows are typed, addressable and protected (edits refuse and the view refreshes) — structure is data (CollectionRow), never parsed back from text. - Picker locator dimming: file rows dim the directory portion and keep the name bright; grep/jump/remote hits dim the
path:linelocator — computed from payload fields, not text parsing.
0.26.0 — 2026-09-11
Fixed
gb/gBwork in Visual mode: occurrence seeding enters Visual, so the repeat key must dispatch there — the visual walker now runs visual-section Leaf rows (with the path replayed into the grammar on fall-through, sogg& friends parse whole), and keymap rows declare their modes (sections), letting one binding live in both.
Added
- The symbol picker renders a colored kind chip before each row (
fn,struct,enum,mod, …) — the icon column without an icon font. Rows slim to name · container · line.
0.25.0 — 2026-09-11
The multibuffer becomes trustworthy (0049 §§5–8 core): collections undo, redo, save, navigate and never show stale text — plus occurrence selection, reviewable changes, indent inference and format-on-save.
Added
- Occurrence selection (0049 §7):
gbselects the word under the caret, then the next occurrence per press (wraps once, exhaustion named);gBselects all. A visual selection seeds its literal text.:select-next|:select-all|:select-skip|:select-popfor help and completion. Selections are real anchor/head ranges through motions,c/d/y, insert and undo — one edit group per action. In a collection, only editable excerpt bodies can match or seed. - Reviewable changes (0049 §8): multi-file rename/code-action plans open a real-buffer diff review first — per-file unified diffs, named refusals, pinned base revisions.
:apply-changeapplies exactly what was shown (a source edited since is refused by name; nothing is recomputed silently),:cancel-changediscards. Single-document plans keep applying directly. The review buffer stays as the receipt. - Indent inference and style config —
indent_style = "spaces"|"tabs",indent_detect = true(per-document detection from content on open),tab_sizeas the fallback width. Tab in insert mode now inserts the document's indent unit;>>/<<, auto-indent, guides, display width and the LSP format request all follow the document. - Format on save (helix parity):
auto_format = trueformats through the language server before a plain:w; a formatter failure or refusal never blocks the write. Local documents.
Fixed — the multibuffer correctness contract (0049 §5)
- A relative startup path no longer drops collection hits — excerpt identity uses the owner-aware
matches_target/file_identityrule like ordinary open dedup. - Plain
u/Ctrl-Rin a collection undo/redo the collection's own edit group across its actual sources (scoped receipts; the global:undo-changetail is not consulted). Preflight refuses by name when a source moved on; the receipt is never consumed on refusal. Redo preflights history depth — a monotonic revision cannot name an undone position. - Every source edit — pane, undo/redo, LSP, reload — refreshes dependent collection views immediately (single-excerpt splice, full re-render at span edges), preserving the logical caret; unsynced user input is never regenerated away. No stale text survives an acknowledged undo.
:win a collection saves the dirty sources through their own save paths with per-file outcomes;:w PATHrefuses (the view is not a file);:wqcloses only after every scoped save confirms;:qcloses the view with the sources and their edits intact. The false "q closes" title is gone — macros are intact.g<Space>, Enter on a header row, and:collection sourceopen the full source at the caret — the live document, unsaved edits included — recording the jump soCtrl-Oreturns to the collection context.- Collection background source loading no longer stalls: parallel loads no longer supersede each other, and a background open never steals focus from the pristine scratch.
- The 2,000-excerpt sync stall: line diffs trim common prefix/suffix first; the quadratic LCS only ever sees the changed middle.
- Collection views present source line numbers in the gutter, accent chrome on title/header rows, and a real provenance name in the modeline — never
[scratch]. (Full per-source syntax projection and boxed cards remain on the roadmap.)
0.24.0 — 2026-09-10
External-header LSP continuity (0049 §4): a definition jump out of the working tree keeps its language service.
Fixed
gdfrom a project file into a library/system header (e.g.vector.hpp) no longer loses the language server: the jump carries the replying server's context as a routing hint,didOpenfollows through it, and the nextgdinside the header answers. Verified against real clangd: extensionlessvectoropens withlanguageId: cpp.- Extensionless and ambiguous C/C++ headers (
.h) inherit the navigation's language instead of falling to plaintext or C. - The same external header reached from two projects keeps the first navigation's context — no silent compile-context switch.
- A manually opened file no server covers gets a truthful route ("no language context for this file — reach it via gd from a served file, or add its root to languages.toml") instead of install advice; the install hint is reserved for languages with no server at all.
- Namespaces never cross: a remote jump context never binds a local document, and server death drops both bindings and carried contexts.
0.23.0 — 2026-09-10
Escape is preserved at the terminal boundary (0048), and navigation state becomes visible (0047): document symbols, the jumplist as a picker, and mark cards that show the marks.
Fixed
- Escape could be lost on legacy terminals. Pressing Esc and typing immediately coalesced into one byte batch that crossterm decodes as Alt+key, and strop dropped the Alt bit — the mode never left Insert and the letters landed in the buffer. The terminal adapter now treats Alt as an Escape prefix consistently (strop has no Alt bindings): Escape, then the base key, in FIFO order. Capable terminals are additionally asked for unambiguous Escape encoding (kitty keyboard protocol
DISAMBIGUATE_ESCAPE_CODES, popped idempotently on exit), and the CSI-u Control aliases (Ctrl-[/I/M) keep their legacy meanings. Verified on a real PTY: coalescedESC hklsaves the typed text with the letters as Normal-mode motions. - Code-action and symbol pickers whose items arrive after the initial ranking now re-rank — the list no longer renders empty until the first keystroke.
- Grep and symbol hits from a picker now record a jumplist entry (quickfix jumps enter the jumplist in vim), so
Ctrl-Oreturns to where the picker was accepted — remote hits included.
Added
- Document symbols (0047 §1):
Space sor:symbolsopens the current file's symbols as a streaming picker — flat rows with the container path, kind and line; fuzzy-matches against the container. Both LSP reply shapes (hierarchical and flat) flatten to the same rows. Works overssh://through the workspace URI seam.Space Sis reserved for workspace symbols. - Jumplist picker (0047 §2):
Space jor:jumps— vim:jumpsas a menu: past newest-first, the current position marked, dead documents filtered; Enter jumps and records the spot forCtrl-O. - Mark cards (0047 §3):
m,'and ``which-key cards label the absorb key asa-z` and list the live marks with their landing lines — for the jump cards the list is the menu.
0.22.1 — 2026-09-10
Field-report fixes from a 0.21.0 review against real pyright and remote hosts.
Fixed
- pyright (and any server that discovers project config through the workspace) now sees it:
initializecarriesworkspaceFoldersalongside the deprecatedrootUri— rootUri alone has been insufficient since LSP 3.6, sopyrightconfig.json'sextraPaths/venvPathnever applied and every non-relative import failed. - The languages.toml
[language-server.NAME.config]block is no longer inert for servers that read settings the standard way: strop advertisesworkspace.configurationand answersworkspace/configurationpulls from the block, section-scoped. - The remote destinations picker keeps
Add a host…visible under filtering (pinned row), and Enter on filter text that matched no listed destination carries the text into the address box as the draft — typed hostnames are no longer swallowed. --log-contenttraces now carry LSP frame bodies (payloadonlsp_messageevents), so initialize params and diagnostics are readable in a capture instead of requiring a hand-rolled client.
0.22.0 — 2026-09-10
The engine extraction (0046): one strop-engine, the terminal as a consumer.
Changed
- The full editor orchestration — documents, grammar dispatch, services, sessions, replay — lives in
strop-enginewith no Ratatui/Crossterm in its production dependency graph (verified withcargo tree). The binary keeps the physical terminal, cell rendering, CLI, bench and headless frame output. Zero behavior change: the whole suite (839 tests across 29 suites), the nvim differential corpus, and the forensic replay oracle pass unchanged. - Trace frame capture is injected at the composition root: the engine records logical observations; cell grids are the frontend's. A GUI can attach to the same engine later without terminal types — no GUI work is in this release.
Added
- Collections v2 (unreleased under 0.21.x): multi-region write-back, async background loading of unopened sources, remote sources gated on 0040 write permits.
0.21.0 — 2026-09-10
Containers become real workspaces (0037 DC1b), and directory listings scale.
Added
- Language servers run inside attached containers:
:containers→ browse → open a file, and the server's LSP session ridesdocker execstdio — no SSH daemon, no tools required inside (distroless-safe), no local-path fallback. Attach discovery uses XDG/embedded layers; in-container project layers join the trust gate when they're read. - Git in containers: repository discovery (
rev-parse) and context inside the container through the same bounded exec boundary; per-commit diffs inside containers refuse by name this round. - Container directory listings stream: retention is direct-children only, so a 114 MiB image root lists instantly instead of refusing at the 16 MiB archive cap.
strop-coregained the supervised streaming primitive (stream_with) behind it.
Decided
- Container write policy: read-only.
docker cpinto a container is not an atomic conditional save;:w/:w!/:wqon container documents refuse with the policy named, never a local-path fallback.
0.20.1 — 2026-09-10
Interaction fixes from hands-on field reports, plus the verified kernel.
Fixed
- A hover or blame card landing mid-insert no longer eats the next key: transient overlays dismiss AND forward in insert mode (the "Esc needs a second press" report). Elsewhere they consume as before.
- Syntax highlighting no longer blanks for a frame after each edit: the previous analysis frame serves the interim, spans clipped to the live text.
Added
:qa/:qall(:qa!to discard) quit the editor across all buffers; unsaved buffers refuse without the bang, per vim.- The input dispatch is now a computed
InputOwner(editor/dispatch.rs): owner priority and insert-mode policy are data, not if-chain order.
Verified
- The anchor-mapping kernel (
strop-core/src/editmap.rs, called byeditor/transact.rsfor every mark/cursor/selection remap) carries its proof in-tree: spec equivalence, monotonicity, in-bounds — verified by the checksum-pinned Verus image (docker compose run --build --rm verify, now a CI job). Proof work already paid: the no-overflow precondition andstart <= new_endinvariant are now explicit where they were implicit.
0.20.0 — 2026-09-10
Shared workspace identity, semantic editing, editable collections, and existing-container browsing (plans 0042/0043/0044, 0037 DC1a).
Added
strop-workspaceowns resource identity for every consumer: remote address forms moved out of the transport, oneFilesystemnamespace type, oneResourceLocation, and a checkedContainerId. The editor binds a workspace context per filesystem in use, with incarnation counters across reconnects.:explainrenders the editor's actual decision records: workspace bindings, the current document's routing and LSP binding, server readiness and attach refusals with their actionable reasons, and effective configuration.- Semantic editing through shared change plans:
:format,:rename NEW,Space acode actions, and:undo-changegrouped undo. One plan, one revision-checked gateway application, one receipt; unbound, stale or conflicting targets are named refusals. Verified against real rust-analyzer.ChangePlan.tlamodels the lifecycle with mutants killed and witnesses reached. - Editable code collections (0044):
ctrl-oin a references/diagnostics/grep picker opens the hits as one real buffer of source excerpts; editing an excerpt writes back to its source through the change-plan gateway. Headers are protected, boundary-crossing edits refuse, sources that moved on refuse by name. - Existing containers (0037 DC1a):
:containersattaches to a running local Docker container — no SSH daemon, no provisioning, no tools required inside (distroless works). Read-only listings and bounded file reads stream through superviseddocker cp; restarts and same-name recreations are detected as stale identities. Detach never touches the container's lifecycle. - WORD text objects:
ciW/diW/daWand full nvim-faithful word-object semantics (punct runs, blank-run objects, around-blank rules), arbitrated by new differential cases against nvim. - Trace schema 3: oversize forensic values chunk with digest and completeness checks; replay assembles them; metadata exports stay payload-free.
- Bench stress fixtures: 100k-result picker stream, 1 MiB line, large project replace, cancel/reopen, dropped stale results.
Fixed
- Word objects are nvim-faithful where they previously deviated:
awincludes trailing (or leading) blanks,iwon punctuation selects the punct run, and blank runs are selectable objects. One cursor-only divergence on a refused command is recorded inKNOWN_DIVERGENCESwith its reason.
0.19.1 — 2026-09-09
Enterprise-remote fixes from the first RHEL/NFS field report.
Fixed
- Bracketed paste now reaches picker and popup inputs (the
:remotechooser, the connect-to-remote address field, replace fields): it edits the focused field instead of being silently dropped. Multi-line pastes are refused with a message; paste into the buffer and:line is unchanged. :remote editand remote saves accept paths with symlinked ancestor directories (the NFS-mounted-home norm, e.g./home/user -> /home24/user). Intermediate links are resolved component-wise under a bounded no-follow walk; a symlink as the final component, non-regular targets and hard links stay refused.- Saving into an NFS directory no longer fails at cleanup: the stage handle is closed before unlink and a transient silly-rename ENOTEMPTY is retried, so a committed save reports committed and the staging directory is removed.
- Language servers whose startup sends
window/logMessage(pyright does, unconditionally) are no longer killed as "unhandled notification". Server notifications a client need not handle are traced and ignored,window/showMessagereaches the status line, and attach failures now name the real reason (initialize refusal/timeout, protocol error, stderr) alongside the install hint. strop ssh://host/path/file.txt:LINEaccepts the same:LINEsuffix as local files;--helpnow points at the remote command surface (:help).
0.19.0 — 2026-09-09
Explicit remote editing and conflict-aware saving (plan 0040).
Added
:remote editverifies a complete remote snapshot and grants per-document write authority. Existing insert/operators/undo and ordinary:w/:wquse that same buffer. Read-only remains the default, and no remote path becomes a local save.- Owned remote saves with content/metadata conflict checks, stable cooperative locks, protected 0700 staging directories, atomic replacement and file/directory syncing. Modes, ownership, mtime and extended attributes are preserved or the save refuses.
:remote verifyreconciles unconfirmed outcomes without blind retry or rollback. Newer local edits remain dirty when an older snapshot finishes saving.- Borrowed-chunk stdin delivery retains the SSH lifetime lease after upload; fixed helpers use the selected isolated Python interpreter, including configured and versioned-only installations.
- A bounded remote-save protocol model, deliberate faulty variants, progress and reachability witnesses, plus filesystem, ownership and real SSH acceptance oracles.
Fixed
- Pending edit admission cannot enter follow mode, and a late grant rechecks the snapshot/follow state. Cancelling admission revokes even an already-queued grant.
:wqcarries its destination through save validation instead of silently ignoring it. Remote save-as is refused rather than overwriting the original remote file.- Reopening an existing remote file preserves its edits. Successful explicit refresh restores clean read-only state; failed refresh does not strand an edited buffer.
- Normal-mode Escape requests remote cancellation; graceful shutdown retains accepted Save/Verify work. Forced close revokes the document's pending authority.
Safety boundary
Locks coordinate remote-save protocol participants, not arbitrary external writers. There is no universal CAS guarantee in the final comparison/rename window. Symlinks, hard links, non-owned files, noncanonical path spellings and unsupported metadata preservation refuse writable admission. Git mutations, directory mutations, save-as, elevation and arbitrary remote commands remain outside this release. Cancellation or loss after commit begins is unconfirmed until a receipt or explicit verification establishes the durable current state.
0.18.0 — 2026-09-09
Remote browsing, responsive large-workspace editing, and broader static syntax coverage (plans 0038/0039).
Added
Space o/:remoteopens a host chooser with explicit connection, host entry, and privately remembered successful destinations. New hosts begin at the remote root;:remote homeand:remote rootremain explicit navigation actions.- Remote directory rows show file kind, POSIX permissions and server-reported byte size. Missing attributes are visibly unknown, not confused with zero; parent navigation restores the selected child.
- Structural indentation rails follow real nesting transitions rather than marking every whitespace tab stop or continuation alignment column.
- Ten more detected languages: CMake, Markdown, Java, C#, Ruby, PHP, TOML, YAML, HTML and CSS. The binary now contains 23 detected languages and Markdown's additional inline grammar. Markdown emphasis, links and fenced languages, plus HTML script/style injections, share the native highlighter and palette.
- Discoverable headless directives, bounded
settle [MS], explicitwait MS, local/headless file locations, and documented remote-directory CLI usage. - Explicit headless project trust and configured/versioned remote Python support.
STROP_REMOTE_PYTHONselects an absolute remote interpreter; an invalid override fails rather than silently choosing another executable.
Fixed
- File pickers no longer re-score, clone and sort the accumulated workspace on each source batch or keystroke. An owned ranking actor publishes immutable results; superseded queries cannot overwrite the current selection.
- Large grammar reads, syntax/injection parsing, search summaries, indentation analysis and long-line layout preparation run on persistent CPU owners. Accepted commands preserve typeahead, repeat and macro ordering through replay.
- Sparse grapheme checkpoints avoid repeated whole-prefix walks when navigating or typing near the end of a long line. LSP requests capture owned rope slices instead of materializing entire source lines.
- Git workers prepare diff text, row indexes, gutters, intra-line emphasis and commit file trees. Rendering and file stepping borrow those projections instead of reconstructing entire hunks or sidebars. Added-line emphasis uses its own byte coordinates, including unequal-length Unicode replacements.
- Remote home expansion decodes OpenSSH's documented NAME reply instead of expecting an extended-reply packet. Repositories without configured remotes retain valid Git context.
- Protocol builds pin the checksum-verified stable TLA+ release instead of a moving prerelease asset. Temporal fault checks verify the sole configured property and require a rejecting exit status; checker failures never count as successful fault detection.
Scope
Remote files remain read-only in this release. Explicit writable admission and conflict-aware atomic saving are the separately requested next release, behind RW4's transport, metadata, cancellation and concurrency safety gate.
0.17.0 — 2026-09-08
Read-only SSH workspaces: bounded log following, directory buffers, remote language services and native Git semantics (plan 0036).
Added
- Byte ranges, bounded tails and follow mode through CLI and Ex commands. Partial windows identify their byte range; follow compares content overlap, observes same-size replacements and reports resets without guessing inode identity.
- Negotiated remote-home expansion and host/path completion without implicit authentication. Explicit
:remote connect,disconnect,clearandlist; per-endpoint pooled SFTP ownership, cancellation isolation and reconnect epochs. - Real read-only remote directory buffers with entry/parent navigation, search and filtering.
- Owned remote process execution for Git and LSP, preserving native argv and cwd. Remote LSP diagnostics, hover, definition/references and source/header navigation; remote Git context, staged/unstaged diffs, log, blame, commit/file navigation and revision-pinned source links. No corresponding local path is opened or mutated.
- Connection/workspace/follow and remote-process model gates, with qualified progress, reachability witnesses and deliberately faulty variants. Release publication now requires the protocol gate as well as the platform builds.
- Remote commands and connection controls are discoverable in the searchable
:helpbuffer.
Fixed
- Remote refresh preserves line/column positions; EOF following does not drag marks and jump history to the new tail. Snapshot publication maps positions once through the shared mutation lease.
- Diagnostic/blame caches belong to a document incarnation and revision, not a path shared by different snapshots. Git surfaces retain their own repository context.
- Language-server trust and retirement use endpoint plus workspace root. Shutdown signals owned groups before reaping, including unresponsive servers.
- Live and replayed server attachment perform the same document-open transition, preserving diagnostics and the recorded native-call sequence.
- Historical diff permalinks use source coordinates, not display rows. Header/deleted rows refuse invalid locations; partial windows refuse full-file coordinates. Repository SSH aliases are evaluated on the machine that owns the repository.
Scope
Remote content stays read-only. Writes, Git mutations, arbitrary remote shell jobs, Dev Containers and additional transports remain prioritized roadmap work. SFTP reading needs OpenSSH; remote Git/LSP also require a POSIX environment, python3 and their respective tools. Process cleanup is conditional on disconnect detection and process-group membership, not a guarantee across partitions or escaped sessions.
0.16.0 — 2026-09-07
Trustworthy diagnostics and the first verified SSH log-reading milestone (plans 0033/0034). The broader remote workspace work is tracked separately in 0036.
Added
strop-remote, an independent crate for validated remote identities and owned read-only SSH/SFTP transport. Openssh://[user@]host[:port]/absolute/pathfrom the CLI,:e,:viewor a path-bearing split.- Real remote buffers support existing search/navigation/selection/yank. Escape cancels a pending read;
:e!refreshes a snapshot without losing split positions. Authentication uses existing OpenSSH configuration noninteractively, with strict host-key checking. Native Unix path bytes survive the wire; snapshots are bounded. +LINE FILEand localFILE:LINECLI locations, with checked line numbers and--for literal colon-suffixed filenames.- Bounded SFTP/lifecycle TLA+ models, fairness-qualified progress checks, non-vacuity witnesses and deliberate fault variants, tied to Rust and real SSH interoperability checks. This is not an unbounded proof of the implementation.
Fixed
- Permalinks retain full hosts, schemes, ports and nested repository paths. OpenSSH evaluates aliases (including dotted aliases); unresolved failures cannot copy guessed URLs. Native source paths remain separate from display labels.
- Malformed language configuration reaches both modeline and structured trace. Missing executables report their command and a useful installation/configuration hint; startup no longer leaves orphaned version-probe processes.
- Invalid completed normal input no longer disappears into pending state. First-nonblank insert retains one command identity for macro/repeat behavior.
- Closing the last read-only surface no longer reads a dead current document.
- Full replay preserves remote provenance rather than decoding an SSH URI as a legacy local path. Release publication follows actual workspace dependencies.
0.15.1 — 2026-09-07
Modeline and Git-surface polish, separately from the 0.15.0 correctness release (plan 0032).
Changed
- A quieter, segmented modeline with workspace-relative directory context, prominent filenames and display-cell-aware priorities. Long paths no longer crowd out live preview/status or position; the last content line reads 100%.
- Historical Git views identify their commit and actual file, not today's branch. Commit subjects carry the emphasis, author/age metadata is quieter, and the current log/file row has a restrained highlight below search/selection overlays.
- Diff headers distinguish old/new ranges; the sidebar has clearer hierarchy, stronger current-file/focus cues and grapheme-safe label clipping.
- Sidebar structure is built once per pane render. Geometry measurements do not build trees, and native file indices replace lossy display-string lookups.
Fixed
- Git changed-file paths use NUL-delimited native records, including Unicode, non-UTF-8 Unix names and rename destinations. Binary-file rows remain visible.
- Commit deltas treat their path as a literal filename, not a glob. Native selection survives identical lossy labels; control-bearing filenames remain one printable buffer row while navigation retains the real path.
- Completed Git dives no longer leave a stale loading status, and admission failures cannot be overwritten by that status.
- Headless frame text skips cells covered by wide glyphs instead of printing stale hidden characters or duplicate glyphs from TestBackend.
Verification
- In the 240-file review fixture, median traced debug-frame rendering fell from 106.9 ms to 5.8 ms (12 frames, same workstation). This is measured fixture behavior, not a portable timing guarantee.
- Layout, native-path navigation, control/Unicode labels and frame-text round trips have behavioral regressions; grammar and keybindings remain unchanged.
0.15.0 — 2026-09-07
The complete P1/P2 correctness roadmap (0031).
Fixed
- LSP replies carry their initiating request, server, document incarnation, revision and negotiated encoding. Reordered replies and revision zero cannot bypass ownership checks; closing and reopening a file starts a fresh lifecycle.
- Session and trust updates use private exclusive staging and atomic replacement. Failures preserve the old snapshot and are reported. Native filenames round-trip without opening a lossy Unicode alias; ambiguous legacy paths are rejected.
- Counted incremental searches retain every cursor's original selection and view. Prompt edits, acceptance and cancellation share one reducer. Block selection, yank and paste use configured tab/display-cell geometry; edits preserve CRLF.
- Search uses a bounded Vim-magic regex dialect with explicit unsupported-syntax errors and actual match ranges. Unicode collections retain complete ranges.
- File opens, saves, session persistence and native Git work run off input dispatch. Delayed results cannot steal focus, overwrite newer edits or mark them saved. Each pane keeps its own horizontal display-cell viewport and aligned overlays.
- Every worker result is owned and terminal, including failure, panic and cancellation. Failed or cancelled previews no longer masquerade as empty files or perpetually loading content. Git index mutations are serialized.
- Hunk discard replaces the matching full old/new extent without duplicating context. File saves retain cursor placement while unrelated anchors follow the edit journal.
Added
--replay TRACEreconstructs a complete full-content capture and injects recorded service events without repeating filesystem reads, shell commands or LSP traffic. Logical state and run-length-encoded terminal cell/style observations must agree.--export-metadata TRACEemits only event categories and sequence numbers: no keys, paths, content, commands, messages or arbitrary payloads. It is explicitly not replayable. Capture caps produce a visible terminal/incomplete marker.- Stateful transaction/service oracles generate, shrink and replay failing recipes; the protocol gate checks the clean model and its deliberately stale-delivery mutant.
Changed
- Rope/history mutation is sealed behind validated transactions with pre-edit coordinates, typed revision checks and whole-batch overlap validation.
- Diagnostic severity, byte/line/display/server coordinates, request identities and register shape use named domains at the boundaries.
- Command-table lookup is compiled once. Query parsing/emission, Git job types, test delivery helpers and conformance streams are split by responsibility.
- Changed async regressions receive terminal events instead of sleeping or sharing fixed
/tmpfiles. Fixture-only drain paths are absent from production builds.
0.14.1 — 2026-09-07
Search/terminal correctness, diagnostic session traces, and a safety review (plans 0027–0030).
The 0.14.0 tag stopped before publication: the strengthened macOS verification correctly ran but its allowlist omitted Apple's system zlib. This release accepts that system library while still rejecting non-system dependencies; tags were not moved. All application changes below are included in 0.14.1.
Fixed
- Search typing and Backspace resolve from the same origin as Enter; edits at the prompt caret update the match. Backward searches get the same card. Plain search text no longer triggers find/operator overlays.
- CRLF rows no longer emit raw carriage returns. Control bytes in text or UI metadata cannot become terminal commands; physical-terminal regressions cover shrinking lines, filler rows and escape-bearing content.
- Operator-search previews use the same counted/multicursor resolver as execution. Literal matching streams rope chunks instead of copying whole documents.
- Tiny popup geometry survives resize/restore. Ctrl-L requests a full repaint.
- Saves and updater installation use exclusive private staging. Save-as without force cannot clobber an existing destination.
- Delayed shell results validate UTF-8 boundaries before slicing. Shell and ranged Ex deletion now use transaction-owned anchor/history/syntax updates.
- Git diff text materialization runs on its worker, not on the render path.
- Legacy LSP terminal logging is removed; release library checks explicitly reject forbidden dependencies instead of relying on inverted-pipeline errexit.
Added
strop-trace: one opt-in asynchronous JSONL sink shared by core, LSP and editor.--log,--log=ALL,--log-file PATH,--log=PATH, andSTROP_LOGselect capture. It records input, state, immediate mutations, history, frames, job decisions, actual bidirectional LSP traffic and failures without disk I/O on input→render.--log-contentenables sensitive full document/paste/cell capture;--replay-script TRACEextracts an inspectable headless input reproducer. Existing logs are refused, files are private, and incomplete capture is reported.- A prioritized editor critique/roadmap (0028) and a separate research-backed hardening proposal (0030). The broader hardening redesign is plan-only.
Changed
- Separate client transport, CLI, terminal, trace and keymap-query responsibilities; renderer tests no longer inflate the production renderer.
- Restored omitted smart-indent coverage and script tokens. Headless now keeps a persistent terminal and draws the real feed path after each scripted key.
0.13.0 — 2026-09-06
PathBuf paths end to end (plan 0026) + the file-size split wave.
Fixed
- Non-UTF-8 filenames work end to end: the argv file operand comes from
args_os,open_document/open_buffer/save_as/for_pathtakePath, and the LSP/pickerdisplay().to_string()roundtrips are gone — ab"\xff.rs"file opens, highlights, saves, and a gd into it lands (regression test pins it).
Changed
- File-size discipline wave (no behavior change, public APIs unchanged):
strop-corelib.rs → buffer.rs + range.rs;strop-grammarresolve.rs → resolve/{mod,motions,objects,search};strop-gitlib.rs → {diff,repo,revision};strop-lsplib.rs → {caps,client,convert};editor/picker.rs→ picker/{mod,drain,accept, replace,preview,tests};editor/tests.rs→ tests/ one file per module. Keymap table exempt per AGENTS.md.
0.12.0 — 2026-09-06
The 1.0 perf gate (plan 0025) + jumplist fixes.
Fixed
- gd/gr now record the jumplist —
ctrl-oreturns to the origin after an LSP jump (and after accepting a grep/locations hit). Dead entries (closed buffers) are skipped without corrupting the stacks.
Added
strop --bench [scenario|all]— the perf gate suite (1/10/100MB buffers, 100k-line navigation, 1k cursors, p50/p95/p99 input-to-frame) on the production feed+render path, synthetic deterministic data,docker compose run bench. Baseline inplans/0025-perf-gate.md: input-to-frame p99 1.5ms, edits flat to 80MB, no cursor superlinearity. Perf claims now need these numbers.
0.11.0 — 2026-09-06
The transaction gateway + verified state machine (plan 0024).
Added
- The gateway:
apply(document, base_revision, changeset)— typed validation (stale revision, readonly, invalid range, missing document), one undo unit, and every side effect (history, anchors, tree bridge, clock) in one place. Project replace and git discard flow through it; the typing path flows through the same commit seam. specs/EditorProtocol.tla— the editor's document/pane/ transaction/service protocol model-checked by TLC: NoStalePane, NoMisapply, NoPartialCommit, monotonic clock. The kept mutant (an unguarded Deliver) is caught by the gate — the invariants have teeth. In the docker gate (compose run model) and CI.- The conformance harness — a String reference model and the real editor run the same generated streams through the production path; text equality + invariants asserted every step (found vim's insert-Esc cursor move and the welcome card's first key on day one).
0.10.3 — 2026-09-06
The consolidation round (plan 0023): review 4's 18 contract probes ship in the repo as the acceptance suite (editor/contract_probes.rs) — all green.
Fixed
- Split from scratch no longer strands the pane (panes rebind before the scratch drops).
- Clipboard replies retain their destination document — a switch mid-read drops the paste with a message instead of inserting into the wrong buffer.
- Saving through a symlink writes through (vim's rule: the link survives).
- More panes than cells renders instead of panicking.
df2deletes through2(a digit afterfis the target).:1yfills the registerpreads.- Stale replacement ranges over Unicode edits report stale, never panic; search previews never start mid-char.
- Unicode picker rows match (the ASCII-only scorer input was library misuse); match columns are char indices.
- Tab glyph and caret read one layout (config's tab width drives both; one LineLayout per line, not per grapheme).
Changed (architecture)
epochis THE text clock — syntax invalidation, the tree bridge, and LSP freshness all read the counter that moves on every mutation, including mid-insert-session and through undo. Anchor mapping runs on undo/redo too, and its watermark is per-document.- Git gutter results carry their document id — a snapshot can't populate the wrong buffer.
- The release target builds from the test stage — a tag that fails the gate can't ship. The installer's checksum verification is mandatory, matching the updater.
- Preview claims are precise: composition windows (search, replace, git) preview live; instant operator+object chords execute at the completing key. The site says so.
- The scorebench example measures the real
Picker::refilterpath.
0.10.2 — 2026-09-06
Fixed
- A crash (and a release-build garbage-read) in incremental syntax highlighting: the rope-chunk parse callback assumed tree-sitter requests bytes monotonically. On error recovery in large template-heavy files it backtracks;
byte - offsetunderflowed and panicked in debug, and could feed garbage slices in release. Repro: gd into/usr/include/c++/13/optionaland back. The callback now uses ropey's random-accesschunk_at_byte. Regression test pins backtracking-heavy inputs.
0.10.1 — 2026-09-06
The 1.0-hardening remainder (plan 0022): incremental tree-sitter and the nucleo decision.
Changed
- Syntax highlighting parses incrementally: the highlighter keeps the parse tree; every transaction (typing, undo, redo, git discard, ex edits) applies its ops as tree-sitter InputEdits at commit time — a cheap pointer walk — and the reparse runs from rope chunks against the old tree, never a from-scratch parse and never a materialized document String. Correctness is property-pinned: incremental spans equal a fresh parse across edit scripts.
- The picker's scorer is nucleo-matcher (docs/nucleo-decision.md): 42–50ms → 12ms on a 100k-item refilter, identical hit sets, boundary-preferred match columns for the accent render. Adopted on numbers, not principle — the bench lives at
cargo run -p strop-picker --example scorebench --release.
0.10.0 — 2026-09-06
Document identity and the last 0018 seams (plan 0021). No new user-visible features — the document model is formalized and the render path is clean.
Changed (architecture)
- The gutter never diffs on the render path: edits mark the hunk snapshot stale; a worker computes against a shared rope clone and posts the revision-keyed result. Stale snapshots drop and re-enqueue; signs clear honestly for the frame instead of painting wrong lines.
- Every interactive LSP reply is stale-droppable by construction: goto/locations answers carry the asking document's revision; a reply against another edit state never navigates.
Buffer.pathisPathBuf(0020 review): the filesystem model isn't UTF-8 — a file with non-UTF-8 bytes in its name opens, edits, and round-trips (test-pinned).DocumentSource(File / Scratch / Surface / Output): readonly and save refusal derive from the source at construction — no morereadonly/name/path conventions kept in sync by callers. The document layer splits intodocument/{mod,surfaces}.rs; the git surface model lives with the payload, and:won a readonly buffer names it.
0.9.2 — 2026-09-05
The structural half of plan 0020.
Changed (architecture)
- Universal anchor mapping: every committed transaction emits its ops through one ChangeMap applied to marks, jumplists, and every OTHER pane's selections of the document (the active pane's cursors remain each command's own job; a watermark guarantees an op never shifts an anchor twice, even across multi-commit revisions).
- Project-config trust gate: a project's
.strop/languages.tomlthat supplies the servercommand/argsis executable content — attach is refused until:trust(once, remembered per project root in the state dir). Init-options-only configs (pythonPath-style) stay free.
0.9.1 — 2026-09-05
The third review round (plan 0020): fourteen contract fixes, each with its regression test.
Fixed
:w {path}honors overwrite policy — an existing target needs:w!; identity adopts only after a successful write; a failed write changes nothing.- Streaming pickers no longer die on the second keystroke (a 0.9.0 regression): respawned rg workers forward to the same event source, tagged (picker id, query generation); stale streams are dropped.
- Project replace is byte-exact past multibyte text (Ropey's char-indexed slice was being fed byte offsets).
d/foo<Enter>works in normal mode (motion text is literal — spaces stay spaces, Enter is\r), and search composition previews its target as you type, from the typed plan.- Syntax highlighting invalidates on the document revision, not a len+first+last-byte hash that kept stale colors on same-length edits.
- LSP diagnostics compare against the server's own version clock (the buffer's edit epoch was the wrong counter); position encoding is read per event, not captured possibly pre-negotiation.
- Git hunk discard is one committed undo transaction (
urestores byte-exact), restoring from the hunk's own CRLF/newline-precise lines. - Unopened files in project replace become real buffers — one transaction model, one atomic writer, real undo, permissions kept.
- Grep rows are never fuzzy-filtered by the regex query — rg's matches are the apply set, exactly.
- UTF-8 boundaries: charwise paste and repeat-search step whole chars; charwise visual spans whole chars (three panic classes closed).
- A failed
:eleaves the current document intact (I/O before any ownership change). - Resize events reach the event loop; an idle terminal redraws.
- Sessions persist for every launch mode, including
strop file.rs. strop-pickeris a workspace member.
0.9.0 — 2026-09-05
Revision-native git and services (plan 0018): git content is byte-precise, index mutation is structured, and every async result wakes the UI.
Changed (architecture)
- Byte-precise git:
DiffLineholds raw bytes + a newline flag; git's "\\ No newline at end of file" marker is data now, never text that leaks into a staged blob. - Structured index mutation: staging/unstaging edits the index blob directly through libgit2 — no hand-serialized patches, no shell-outs, no path-quoting or CRLF hazards. Stage/unstage round-trips a missing final newline exactly (new tests pin it).
GitRevisionspans the four states: Head, Commit, Index, Worktree, MergeBase — with byte-level readers; permalinks resolve merge-bases.- Unified event source: one app channel; a reader thread translates terminal input; every job's results forward the moment they land. The 500ms idle poll is gone — hover/diagnostics/shell/git results paint immediately.
- Per-workspace language config: the process-global OnceLock is gone;
languages.tomlmerges are cached per workspace root (a second project no longer reads the first project's config). - Stale-answer guards: diagnostics carry the server's document version (older batches are dropped, not misplaced); a hover that arrives after an edit never opens over changed text.
0.8.0 — 2026-09-05
Text and terminal correctness (plan 0017): one layout layer owns the byte↔cell seam, and the editor is honest on real human text.
Added
LineLayout(strop-core): per-visible-line grapheme spans with byte↔display-cell maps — the single translation seam every visible-line consumer now reads.- Visual block mode (
ctrl-v): rectangle select on CELL columns (wide chars and tabs can't skew the columns apart),x/d/yon the rectangle,c/I/Awith per-row text replication at Esc. - Bracketed paste: pastes insert as one undo unit of TEXT — no key interpretation, no
:puns; normal-mode paste behaves likep. - The caret is cell-accurate: wide chars and tabs place it right, and splits place it in the RIGHT pane (the pane origin was never applied).
Fixed
- The paint loop walks graphemes with byte offsets — syntax, search, selection, diagnostic, and preview overlays no longer drift after the first multibyte char on a line.
l/hstep char boundaries (a byte step from a multibyte lead clamped straight back — the "stuck at é" bug).rcounts characters, not bytes (2rXover ü is two chars).*/#classify word chars by Unicode, not ASCII — café, münchen, and 変数 are words.
0.7.0 — 2026-09-05
One input machine (plan 0016, landed fully). Every normal-mode key event — letters, arrows, ctrl keys, Enter, Tab — traverses a single typed state machine whose trie IS the command table; the machine emits typed actions (a table row with count/register, or a grammar Command) and never an assembled string.
Added
- Macros:
q{reg}records,@{reg}replays (counts work,@@repeats) — replay feeds the same machine, so a macro is exactly as capable as hands on the keyboard. - vim Enter:
[count]lines down to the first non-blank (blame gutter keeps its dive). strop --dump-compat: the vim-compatibility report, generated from the command table;docs/vim-compat.mdis pinned fresh by a test. 106 live bindings listed.
Changed (architecture)
- The walker is table-driven: prefixes derive from BINDINGS, never a hardcoded list;
Command.countisOption<usize>— bareGvs1Gis typed now, and the digit-sniffing hack is gone. - Aliases are semantic:
D/C/Y/Smap to grammar commands with the walker's count/register merged — nothing replays through input. - Model-based tests pin the machine's invariants: every live row completes or pends VISIBLY; junk never poisons the next count; counts cap under adversarial input.
- The dead string-token dispatch path is deleted (clean cutover).
0.6.1 — 2026-09-05
The trust release (plan 0015): the second review round's destructive paths close, and the vim canon fills in.
Fixed (safety)
- ctrl-c is a quit intent, not a hidden force-quit — dirty work warns once ("unsaved — ctrl-c again to force-quit"); a second press exits. No exit path silently discards buffers anymore.
- A pathless
:werrors ("no file name — :w {path}") instead of reporting "written";:wqon a dirty scratch stays open;:w {file}names and adopts a path. - A failed shell filter never touches the source —
| falseused to erase the selection; now stderr explains itself in the statusline and the text stands. :wqcloses the window, not the shared document — the stale-DocumentId panic path with one document in two panes is gone.- Session undo histories carry a content identity — a history captured against dirty text never replays onto changed disk content.
didClosecompletes the LSP lifecycle — close/reopen in one process no longer leaves the server holding stale text.Buffer::byte()no longer panics on an empty rope.
Fixed (vim canon)
d0is a motion again (contextual zero); counts cap at 99 999 instead of wrapping; arrows ride the walker (2 <Right> xmoves twice, deletes one); aliases keep their counts (2D≡2d$).- Edit cascades no longer stack a phantom second cursor on the primary's landing.
- The pending-keys display shows the full structural state (
"a2d3), never a blank.
Added
- Viewport canon:
ctrl-d/u/f/b(a count is the scroll size),zz/zt/zb,H/M/L(counts are line offsets),ZZ,ctrl-^alternate buffer,gv,gi, andg;/g,over a changelist derived from the undo history. - Grammar motions:
{/}paragraphs andge/gE— they compose with operators and preview like every other motion. - Ex ranges:
:Ngoto,%/N,Mwith +/- offsets, ranged:d(yanks) and:y, and literal:[range]s/a/b/[g]as one undo unit. - LSP navigation:
grreferences,gIimplementation,gytype definition,gDdeclaration — many results land in a picker with previews, one jumps directly; pre-init requests queue and flush.]d/[dtraverse diagnostics with the message in the statusline. - Commit-viewer file sidebar is a tree: directories group as dim header rows, files indent by depth.
0.6.0 — 2026-09-05
The input layer lands (plan 0008, fully): key events walk a typed state machine, and the doc table IS the dispatch table.
Changed (architecture)
- The input walker (
editor/input.rs): counts, registers, operators, and prefixes are typed parser state — pending strings survive only as free-text line content (:/?|, the modal text fields of 0003 §1) and as the grammar's assembly interchange. - One command table: every BINDINGS row carries a stable
idplus its dispatch handler —Space ?, which-key cards, and dispatch read the same rows. Parameterized rows (r<c>,m<a>) match by placeholder; the coverage tests tokenize exactly like dispatch. - Panes own their selections and scroll (the ViewModel split):
Pane { doc, sels, view_top }— switching panes never copies state back and forth; the active pane's state IS the editor's.
Fixed
- Found by the new dispatch-coverage tests:
,didn't reverse the find (shared row handlers ignored the completing key —;/N/#/[c/Pall key-aware now);ctrl-wpane keys route through the walker; counts reach leaf commands through the table (2x, 3rx, 2p).
0.5.0 — 2026-09-05
The restructure release. All four waves of plans/0014 landed: the editor's centre is identity-typed and selection-unified, the input layer is becoming data, and git is a data model, not a sidebar.
Changed (architecture)
- Generational document ids (
strop_core::id::Arena): buffers, panes, marks, jumplist, surfaces, hunk origins, picker payloads all hold stableDocumentIds — closing a buffer can never alias another. The parallel-vectors alignment invariant is oneDocumentstruct now. - One selection model (
strop_core::selection::SelectionSet): normal = collapsed primary, visual = stretched primary, multicursor = extras. The three old fields could disagree; the set owns the invariants. MotionShapereplacesRange { linewise: bool }:Characterwise { inclusive }carries what dfx-vs-dtx need; Blockwise is the enum's extension point for visual block.- LSP server pool: one client per (workspace root, server) — Rust + Python + C++ in one session, each with its negotiated encoding.
- Leaf commands are data (
editor/registry.rs, plan 0008 stage 1): 44 normal-mode leaves dispatch from a static table; a parity test fuses dispatch and docs. Caught a dormant bug on day one:W/B/Enever moved the cursor (the grammar's cursor_after lacked BigWord arms). editor/mod.rssplit (1817 → ~400 lines): document, cursor, diagnostics, registers modules; tests live beside the behavior they pin. git_memory split into dive/blame/permalink.
Fixed
- Paste lands the cursor on the last pasted char (vim), found by the new differential harness.
f/tfind multibyte chars (f éworks; found bytes before). All dynamic command args arechar, neveru8.
Added
- The nvim differential harness (0006 tier 1): 40 cases drive strop headless and pinned nvim over the same keys; text + cursor must agree. Runs in the docker gate.
- Semantic dot-repeat:
.re-resolves the parsed command from the current position instead of replaying a key string. - ActionPlan:
strop_grammar::plan(buf, cursors, cmd)is the single object preview renders and execute applies — multicursor previews highlight every cursor's target now. - Git as four states: HEAD → index → worktree → live document are separate diffable edges. The gutter shows staged lines in the committed-adjacent tint;
Space g sstages (live→index),Space g Sunstages (index→HEAD),Space g udiscards unstaged (restore from index, not HEAD). - Selection history: visual mode
Space g h=git log -Lon the selected lines. Permalinks from commit surfaces pin that commit.
0.4.0 — 2026-09-04
The safety release. An external architecture review (now plans/0014) audited the mutation and protocol boundaries; every confirmed P0 is fixed and pinned by a failing-first test.
Fixed — safety
:wqcould close after a failed save — disk error, full filesystem, or an externally modified file meant silent data loss. A failed save now keeps the buffer open and dirty;:w!/:wq!force past an external change.- Saves are atomic (temp file + rename in the same directory, permissions preserved) and refuse to overwrite a file that changed on disk since load.
- Readonly is enforced at the mutation boundary, not by caller discipline:
Buffer::insert/deleterefuse on readonly buffers; job-owned surfaces refresh through an explicitreplace_all_system. - Staging a hunk no longer silently saves the buffer (writing every unrelated unsaved edit to the worktree): dirty buffer → "unsaved changes — :w first".
- Sessions persist on exit, not only on
:w. - rg failures surface: a bad
-t/--globfilter posts rg's stderr to the picker instead of looking like "no matches".
Fixed — protocol
- LSP document versions strictly increase per document (was:
2forever — pyright rejects stale versions). - LSP position encoding is negotiated (utf-8 offered, utf-16 honored, the spec default) with one tested conversion module — hover/goto/diagnostics land on the right column with emoji, combining marks, and astral-plane chars before the point.
Changed (breaking)
|is vim's column motion again; pipe-through-shell moved toSpace |(normal and visual). Restored before the deviation became muscle memory — seeplans/0014§"compatibility policy".
0.3.9 — 2026-09-04
Headless QA sweep (the harness now drives the real binary across 60+ scenarios); every crash class found is fixed and pinned by a test.
Fixed
- Unicode crash: word motions classified *bytes*, not chars —
wcould park the cursor inside a multibyte char and the nextxpanicked ropey. Word classes are char-aware now (é is a word char, 🦀 isn't), andx/a/~round to char boundaries. Root cause was deeper: ropey'stry_byte_to_charmaps mid-char bytes silently, so the oldclamp_boundarynever clamped anything — it now verifies via the byte↔char roundtrip. - Arrow keys did nothing anywhere: the crossterm translation dropped
KeyCode::Up/Downat the catch-all. Arrows now speak hjkl in normal, visual, insert, and on read-only git surfaces; in pickers Up/Down walk results and Left/Right move the caret. - Picker navigation: after Esc (normal mode on the field)
j/kwalk the results instead of editing the query; the prompt glyph shows the mode (❯insert,▮normal). 0went to line-start nowhere — the count parser ate the bare zero (vim: leading0is a motion, later digits are counts).- Empty
//?repeat the last search (vim) instead of erroring with a raw\rin the message. - Flaky pane tests: parallel tests shared
/tmpfixture files; fixtures are per-test tempdirs now.
Added
gsswitches source ↔ header via clangd'stextDocument/switchSourceHeader— the C/C++ header jump. No server or no counterpart says so in the modeline, never silent.
0.3.8 — 2026-09-04
Fixed
- The lingering welcome screen: the untouched initial scratch buffer is replaced by the first real thing you open (vim's
[No Name]rule) — no more welcome card reappearing mid-quit, no keep-quitting to exit.
Added
- Readonly mode for real:
strop -R file(vim's-R),:viewmarks the current buffer readonly,:view fileopens readonly. Modeline shows[RO]. (Research note: no editor — vim, nvim, helix — opens goto-definition targets readonly by default; editing the destination is intentional.:view/-Ris the standard answer.) - The modeline grows up: mode chip · git branch (
*marks a dirty worktree) · file ·[RO]· cursor count (2×) · error/warning chips · line:col · percent.
0.3.7 — 2026-09-04
Added
- Jumplist (vim's ctrl-o / ctrl-i — and ctrl-i is Tab in a terminal, same as vim): jumps record on
gg/G/%, searches,n/N, marks, buffer switches,:N, and every surface dive; ctrl-o walks back across buffers, ctrl-i/Tab forward; a new jump truncates the forward path. On commit-diff surfaces Tab keeps its sidebar-focus job.
Website
what's newlinks removed (the changelog page serves it); docs/changelog content no longer hides under the fixed topbar; footers unified with the homepage's.
0.3.6 — 2026-09-04
Input boxes get modal, replace gets vscode-grade exclusion, and the LSP wire learns its manners.
Fixed
- didOpen raced initialize (found while verifying a pyright override): the first document opened could hit the wire before initialize completed — rust-analyzer tolerates it, strict servers (pyright) drop the document. Opens now queue until Initialized; the wire reads initialize → initialized → didOpen, textbook.
Changed
- Grep is a popup card again; global replace keeps the full frame — the quick-lookup and the power-tool get different shapes.
Added
- Modal input boxes (rootle's): picker fields and the
:/|line land in insert mode; Esc enters vim normal mode *on the field* (h/l/0/$/w/b move, x/X delete, i/a/A return), the cursor changes shape, Esc again closes. One sharedLineEdit, no forks. - Replace exclusion per file: ctrl-x excludes a match, ctrl-d toggles the row's whole file (vscode's file toggle); the count chip counts both; the hint line documents the
-t rs/--globfilters. - strop.dev/docs + strop.dev/changelog: how to configure
languages.toml(project.strop/over XDG, helix-shaped, the pyright venv+extraPaths recipe) andconfig.toml, plus the full per-release changelog.
0.3.5 — 2026-09-04
The first-external-review release: a week of real use, seven confirmed bugs, all fixed.
Fixed
- Counts on non-operator commands never abort anymore:
2x,3p,2u,4.,3rx, counted inserts (3iXtypes X three times,2orepeats the opened line) — vim's multiply rule. (Highest-ranked by the reviewer: silent keystroke loss is the worst kind.) {count}Gjumps to that line instead of the end of file; bareGstill goes to the end. The two used to disagree silently.cc/Sclear the line's content and keep the newline + indent — no more merging with the next line; counted2cccollapses N lines into one fresh line (vim semantics).Centers insert at the deletion start, not one column early (typed text used to land before the last surviving char).- Permalinks with ssh host aliases (
bbgithub:org/repo.git) resolve via~/.ssh/configHost blocks, with a bare-host fallback. - Permalink errors say what happened: not a repo / no remote configured /
unsupported remote URL: <it>— the oldno remote / not a repoconflated all three. --headlesswith a missing script prints a clean error (exit 2) instead of panicking.
Added
^(first non-blank, works with operators:d^,y^),I(insert there),~(toggle case, advances),S(=cc).- Unknown bare keys are loud:
not an editor command: <key>— silent swallowing made absent features indistinguishable from broken ones. - Release attestation: every release tarball now gets signed build provenance (
gh attestation verify <file> -R stropdev/strop), ported from gripsack's pipeline.
Notes
- The reviewer's https-enterprise permalink failure (their row 5) does not reproduce on 0.3.4 — verified with the exact release artifact and their exact URL shape. If it still fails for them, the actual remote string is the next datum needed.
0.3.4 — 2026-09-04
Crash fix and a vim-gap sweep.
Fixed
- Replace-picker crash (
index out of bounds, len 0): typing inSpace Rrespawns rg per keystroke; the respawn cleared items but not rows, and the renderer indexed a stale row. Rows clear with the respawn now, plus defensive lookups (regression test included). - Sidebar
│alignment: non-current file rows were one column wider than the current row — the divider wobbled in and out. :30jumps to a line (clamps to the last content line, never the phantom past a trailing newline);:nohclears the persistent search highlight.30j-style counts:0after a count digit is a digit, not the line-start motion — counts above 9 work now.- Visual
</>indent and dedent the selection (one undo unit). - Visual pending no longer swallows keys: invalid sequences clear with a message instead of accumulating forever (which also made later keys vanish).
Verified
.repeats deletes and change+insert (dot-repeat tests pinned).
0.3.3 — 2026-09-04
The git-flow and command-line round, plus a vim-fidelity sweep.
Added
- Tab focus-cycle in commit diffs (tuicr's model): Tab/Shift-Tab hops between the file sidebar and the diff content; focused j/k steps files in place; the current file wears rootle's
▸when focused. - Syntax highlighting in diff views: commit deltas highlight code under the origin tint (delta's look); the highlighter follows
]ffile steps. :autocomplete: ex candidates render under the command card with doc strings; Tab cycles them.:!cmd: run a shell command in a job, output opens as a real readonly buffer (search it, yank it, q closes).|cmd(helix's pipe): visual selections pipe through a command and get replaced by stdout (one undo unit, never-clobber verified); bare|cmdpipes the current line.- **
*/#**: whole-word search for the word under the cursor, wrapping;;/,repeat f/F/t/T finds (,inverts). - Persistent search highlight: matches stay lit after the search commits (rootle rule); the current match is underlined. Works on readonly surfaces too, with n/N.
Fixed
- Undo: a lone paste never committed its revision —
uafter yank+paste claimed "already at oldest change". Every command now commits exactly one undo unit (nvim's rule). - Ctrl-W on some terminals: keys that arrive as raw control bytes (
\x12/\x17/\x18/\x03) now map to Ctrl-R/W/X/C — C-w pane cycling works on Windows Terminal → WSL.
0.3.2 — 2026-09-04
The finish-line polish round: dots, gaps, hues, and a diff that reads like delta's.
Added
- Intra-line emphasis in diffs (delta-style, two-tier): del/add runs pair line-by-line within a hunk, shared prefix/suffix trims, and the changed span gets a brighter background + bold over the row tint. Pure adds/deletes keep the quiet full-row tint.
- Blame gaps (rootle rule): a commit's gutter cell prints only on the first line of its run — the blame column breathes.
- Diagnostics: severity
●dot in the gutter (color reads faster than letters), colored underline on the offending span (underline_color; wavy undercurl lands when ratatui bumps to 0.30), multiline messages join with·in the EOL note. :helppalette: section-hued key columns (blue normal, purple visual, green insert, amber leader, cyan git, yellow ex), section headers with a trailing rule, bold keys.
Fixed
- Demo hiccup: the multicursor tape section typed
strop proj/demo.rsinto the still-open editor (the splits:qcloses a pane, not the app) and mangled line 1. The tape continues in the existing buffer now.
Decided (research)
- Side-by-side diff (JetBrains-style) is rejected for now — it pairs two logical rows into one display row, breaking the surface-as-buffer contract (cursor/search/yank mirror). git-delta and tuicr made the same call; the run pairing built for emphasis is the alignment engine if a split view ever lands (0010 amendment).
0.3.1 — 2026-09-04
Polish round: the demo is clean again, help wears color, and the E now tells you what it is.
Added
- Diagnostics UX: the cursor line shows its worst diagnostic as an end-of-line note (severity-colored, italic, scoped to one line — no inline-hints machinery); severity colors unified across the gutter and the note.
:helpdecoration: section headers and key columns in accent, planned(soon)rows muted.- Seeded key-soup fuzz test (12k keystrokes across buffer shapes + a frame render per shape) — it pays rent immediately (see fixes).
Fixed
- async-lsp panic in the demo ("Sender is alive"): quitting dropped the client socket while the server mainloop was still running. The runtime thread is now joined on quit (with a leak-not-panic timeout fallback).
- Byte/char units in rope mutations (found by the fuzz):
insert,delete, and history replay passed byte offsets to ropey's char-index APIs — any multibyte content (our own undo-tree buffer uses ↵/←/⑂) could panic.deletealso clamps stale ranges instead of panicking. - Paste mutated readonly buffers (found by the fuzz):
pon a git surface or the undo tree now refuses like every other edit. - Multiline diagnostic messages join with
·in the EOL note.
0.3.0 — 2026-09-04
Multicursor lands, help becomes a buffer, the demo's LSP section is real again.
Added
- Multicursor (plan 0013, nvim-0.13 interaction over a cascade executor):
Qtoggles a cursor at point,Space cstacks one onto the next line (helix'sC). Motions,n/N, operators, yank, paste, and insert mode all cascade — deletes apply bottom-up, mirrored edits shift-remap, stacked cursors edit once. Normal-mode Esc collapses to the primary cursor;ureverts a whole cascade as one unit. Secondary cursors render as solid blocks. v1 deferrals (visual-mode multi-range, mouse placement, select-next-match) are documented in 0013. :help— the keybinding table as a real readonly buffer:/searches it, motions walk it,qcloses.Space ?opens the same. The floating keybinds popup is gone (a buffer you can search beats a card you can only scroll).- Headless
statenow reportsmessageandextra_cursors.
Fixed
- Demo LSP section: the vhs image now installs a rustup toolchain with the
rust-analyzer+rust-srccomponents (apt's rust lacks rust-src; the standalone binary half-worked and then errored). - Graceful LSP shutdown: quit sends the
shutdown/exitsequence — every session used to end with the server dying "client exited without proper shutdown" and a fake failure on the statusline. - vim fidelity:
cw/cWresolve likece/cE— the trailing whitespace is no longer eaten, and at a word's last char only that word changes (pinned in the grammar contract tests). - Commit-diff sidebar width fits the file list (clamped 12–24) instead of a fixed 28 columns.
0.2.2 — 2026-09-03
Hardening + daily-driver release. The crash-on-quit class is dead, the editor opens directories, the system clipboard works, and the git surfaces got the rootle-grade navigation treatment.
Fixed (trust)
- Quit crash:
:q/:q!/:wqon the last buffer with an LSP attached panicked in the post-feed drain (lsp_sync_changedindexed an emptied buffer list). The TUI breaks onshould_quitbefore the drains now, and the sync is empty-list-safe. This was visible in the demo tape. - Panic hook restores the terminal (raw mode + alt screen) on any crash.
- Hunk-restore underflows at file top (
Space g uon a hunk at line 0). - Undo cursor lands at the start of the undone change (vim semantics), not the tail of the replayed op list; redo distinguishes insert/delete placement. History replay clamps both bounds to char boundaries.
n/Nactually work now — the keymap advertised search repeat with no dispatch behind it (found by the new coverage test).
Added
- Directory open:
strop dir/cds and lands on the file picker (helix'shx .) instead of dying on EISDIR. - System clipboard:
Space y/Space p/Space P(helix-style) on top of vim's"+register — yank stages OSC52 (works over ssh), paste reads via wl-paste/xclip/xsel/pbpaste off the input path. - Global replace (plan 0007):
Space R— two fields, live replacement preview per row,ctrl-xexcludes a match, Enter applies with one undo revision per buffer, span-verified and mtime-guarded, atomic file writes. Grep queries take rg filters (-t rs,--glob '!target/*'). Grep and replace render full-frame. - Undo-tree browser:
Space u— the revision tree as a real readonly buffer; Enter restores any revision (branches included), q closes. - Blame gutter (0011):
Space g btoggles a left-margin blame column (sha · author · age); Enter on a line dives into that line's commit. The blame card stays as the loading fallback. - Commit diff sidebar: a commit's file delta shows the changed-files list in a left sidebar (tuicr-style);
]f/[fstep through the commit's files in place. - Surface stack (0011): q/close on any git surface restores the origin buffer unconditionally, works in splits, and stale job results are generation-guarded.
- Project LSP config (0012):
.strop/languages.tomlover XDGlanguages.tomlover the embedded registry — helix-flavored[language-server.NAME.config]passthrough (pyrightextraPathsworks), absolute commands skip the PATH probe, server capabilities now gate hover/goto. - Syntax: fish, lua, sql grammars + vendored highlight queries; cpp uses the vendored query now; extensionless shell scripts resolve by basename (
.bashrc,PKGBUILD) and shebang. - Commit graph lanes: the log surface colors each graph lane distinctly, nodes bold in their lane color.
Changed
- The keybinds popup (
Space ?) and all which-key cards render from the onekeymap.rstable; a coverage test pins every dispatchable sequence to a row (0003 §5.7). "(soon)" rows render muted as planned, never as live. - Picker previews read files on worker threads — no more blocking IO in the render path (0001 §3).
Plans
- New: 0011 surface stack, 0012 project config, 0013 multicursor (nvim-0.13 interaction over helix machinery — the next big rock).
- Amended: 0005/0009 (config filename reconciliation), 0007 (status + form-factor notes).