chrono pulled in iana-time-zone, num-traits and autocfg, with a build script, for a handful of fixed formats, RFC 3339 parsing and local offsets. The new opencode-util crate depends only on libc: civil dates from day counts, the formats the client prints, chrono-compatible parsing, and offsets from localtime_r after one tzset. Local readings follow chrono's choices in gaps and overlaps, so stats ranges are unchanged.
chrono stays as a dev-dependency of opencode-util for a differential test that compares every format, parse and local instant over a sweep of timestamps under UTC, America/New_York, Australia/Lord_Howe and Asia/Kathmandu.
The idna_adapter 1.0.0 stub turns IDNA off in url, which drops ICU4X: 21 crates, 2 build scripts and 4 proc macros. A non-ASCII host now fails to parse; punycode hosts still work. The client names the cause when a base URL such as --server has one (deviation A10-ascii-hosts, decision E1).
notify brought six crates and a build script for one non-recursive directory watch. On Linux, read inotify through rustix in a thread that feeds the same debounce. A queue overflow counts as a change and re-adds the watch; a directory that goes away is watched again when it comes back. Other platforms keep notify until they are worked on.
TS uploads the open session's environment as an effect of the connection status, which turns connected right after server.connected. Rust uploaded on server.connected only while the status already showed connected, so with the TS order the upload waited for the next session entry. The upload now runs on the transition to connected; server.connected only reads the location again.
TS mini drops every kind "tool" commit in RunFooter.append while mini.tools is hide, the default, so a ! shell turn prints nothing. The verbosity presets gate model tools and skills in the transport; shell turns are dropped only by the footer's generic filter, a side effect. Rust keeps printing them. M.2/shell-output now sets tools: show and compares the output rows.
opentui-rust now highlights a Markdown heading that ends the text without a line
ending (opentui-rust branch rs-diff-markdown), so the new docs/lexer.md in the
E.7b/blocks patch row shows its heading as TS does. The P-TUI checkpoint is
equal and the deviation is gone.
The location was read again only when the coalesced connection status went from down to up, so a resync or fast reconnect skipped it, and the read left out the config that the reconnect invalidated. The server.connected signal now triggers it, as context/location.tsx does, and the read includes the config as the TUI's location.sync in context/data.tsx does.
The event stream found a restarted service inside its 2 s connect timeout, which cancelled ensure before the 5 s spawn delay let it start a replacement, so a crashed managed service was never replaced. The transport now exposes reconnect, Follow implements it, and the store's event source runs it between attempts as TS createClientConnection does. The stream's own request no longer resolves the service inside the timeout.
Handshake.Identity.version is a NonEmptyString, and TS's
SimulationServer.start defaults it to 'unknown'. opencode_drive's
handshake sent an empty version as given; it now reports 'unknown', and
the TUI no longer defaults it itself.
The version pattern took 127.0.0 from a loopback address, so
'http://127.0.0.1:45678' became '<version>.1:45678' and the port was
never normalized. The port pattern now runs first, and the version
pattern skips three parts of a longer dotted number.
P-TUI and P-APP compared writes by method, path and body only, so a write
with another query, or one the server rejected, still matched. Writes now
carry their sorted query, and in P-TUI any status other than 2xx. P-TUI
also fails a requests deviation whose file does not exist.
The home checkpoint masked the full screen, and the dialog checkpoints
everything outside the panel. Only the home placeholder row (T17) differs,
so that row is the only mask left.
A checkpoint that named any deviation passed whatever its cells showed,
in P-TUI and P-APP. Each entry now states the cells it allows, and with
styleOnly only cells whose symbols match. The home placeholder (T17)
becomes a rectangle mask on both sides instead of a deviation, and P-APP
masks the recorded cells again, so a mask edited into a recording covers
both.
TS lays the metadata row out as separate texts with gap={1}, so the gap
cells keep the foreground beneath them. Rust drew them as spaces in the
muted color. A transparent foreground keeps the one beneath, so the row
now equals TS cell for cell and the P15 and C01 deviations go.
mini built a fixed client, so a background service that restarted on a new port or password left it reconnecting for good. It now uses the following client, as the TUI does and as TS mini passes service.reconnect. /new also uploads the session environment, as the first session does.
Every hydration (connect, reconnect, resize) now lists the inbox with the messages: listed prompts become pending, so one delivered later prints as the user's row, and the composer's queue follows the list. A reconnect or resize also lists the permissions and forms again, so a request settled while the stream was down leaves the footer, and an idle hydration keeps the blocker's status. The fake server gains DROP, a then event that closes the event streams.
The default model loads beside the provider catalog. When the catalog came first, the default model never got its variants, so the variant cycle had none. It now resolves them and the saved variant, and a model chosen meanwhile wins, as loadDefaultModel does.
A ! shell turn holds no server drain, so Esc Esc now aborts its session.shell request and the turn ends at once, as TS does; an answered shell turn waits up to 1.5 s for session.shell.ended. A turn that fails before its first step, or is interrupted for a reason other than the user's gesture, prints the terminal event's error after it settles. A new status clears the notice, as eventPatch does.
The borrow_mut temporary in the for-loop head lived for the whole loop, so pushing a new watch panicked with RefCell already borrowed. The TUI panic test now disables the failed plugin before enabling it, as /plugins offers disable for a plugin that failed while active.
/plugins offered enable for a plugin that failed while active (a panic, or a failed rebuild with the previous version running), so it restarted instead of stopping. Registry entries now carry active, which the dialog uses as TS does; a runtime panic removes the plugin's keymap layer; starting a build again clears its stale rebuild failure. The loader drops deleted skipped directories, watches where a missing <cwd>/.opencode/plugins would be made, and the TUI replaces a watch whose directory was made again.
Native::load ran setup with an empty Weak host, no ID, and no storage keys, so storage, theme_mode, and toasts failed or were lost during setup. Load now takes the host, the plugin's ID comes from describe metadata, setup declares storage keys with a StorageKey host request, and setup's effects apply on activation. JSON-only additions; the table layout and ABI minor are unchanged.
A C plugin could leave a vtable callback NULL, which Rust fn pointers cannot hold, and the loader read major and minor before checking the table size or alignment. The callbacks are now Option fn pointers (same layout), the loader checks alignment and size before the header, and the shims and Host check null instance, out, and api pointers. The layout is unchanged, so the ABI minor stays 1.2.
The turn checkpoint waited for the answer text and 1 s of a still screen.
The turn ends only after the session wait answers, and the busy status row
draws a static square, so under load the TS screen could rest on the busy
row without the turn summary for longer than the settle. With the wait
route delayed to 1.5 s the old steps fail the same way as the batch run.
Both checkpoints now wait for the idle status row, and the turn also for
its summary line.
A test wrote a script and executed it right away. When another test
thread forked while the write descriptor was open, the child held it until
its own exec, and the exec of the script failed with ETXTBSY. This made
interop_with_a_typescript_service fail once in a full run.
ts_command::write_script now writes the script and sets its mode from a
sh child, so the test process never holds a write descriptor to it. Every
test that writes and then executes a script uses it.
The TUI reconciled a session's drafts once per store sync, against the
session as the batch left it. When this client's commit was acknowledged
and another client switched the model in the same batch, the commit never
matched, the draft stayed, and the switch did not show. TS reconciles at
each selection event.
The store now records the session's agent and model after each selection
event in Changes, and Local reconciles against each of them in order
before the current session.
Port stream-v2.subagent.ts as a state machine in mini/src/subagents.rs: it finds child sessions from
the listed subagent calls, the family index, the running sessions, and live events of unknown sessions,
and keeps their tabs, requests, and the selected child's transcript. The transcript now routes events of
other sessions and its own tool events to it, and forms of no session to the footer.
The footer adds the subagent panel, the inspector (footer.subagent.tsx), session.child.first,
session.background, composer.subagent.interrupt, the 'View subagents' menu entry, and the status-row
hints. Children's permissions and forms follow the session's own, then global forms, whose answers name
the location in a header. The form body's padding no longer leaks into a permission that replaces it.