Completes the stringly-error conversion: 341 remaining
Result<_, String> signatures drop to two documented boundary sites
(a String-compat wrapper consumed across a crate boundary and a
test-only diagnostic helper). Eighty-plus enums follow the
established recipe — one enum per failure domain in a sibling
module, byte-identical Display text, From impls replacing map_err
adapters. mcp_live is fully typed (the screenshot channel included)
and all six temporary String bridges are deleted. The flaky
cli-model-discovery trio is made load-proof: exec'd sleeps so the
probe's kill actually closes the pipes, a serializing test lock,
and an escalating budget with a deadline-boundedness assertion.
Pure code motion: twenty-eight remaining oversized modules split into
spine + sibling layouts with re-exports keeping every import path and
test name stable. op-codegen, op-smoke, op-mcp, op-design-lint,
op-host-services, op-host-desktop, op-cli, op-i18n, op-editor-core,
op-editor-ui, and op-pen-loader no longer have any file over the cap.
CliError/SkillInstallError carry every op-cli failure (94 sites),
ProgramError types batch_program (14), WebCanvasError maps route
failures onto their existing HTTP statuses (23 -> 6 deliberate
boundary adapters). Display output is byte-identical to the old
strings so caller-visible text and tests are unchanged.
New leaf crate op-util single-sources hex-color parsing (9 divergent
copies, one with a non-ASCII panic), JSON escaping (one copy was
lossy), and HTML/XML escaping (one copy missed the quote entity — an
attribute-injection gap). Desktop now delegates image generate/search,
settings payload serde, and the --mcp/--serve-web argv dispatch to
op-host-services / op-editor-host-core instead of carrying drifted
copies. Byte-identical widget_host twin files collapse into shared
op-editor-core host_ui_transitions. Auth routes, the MCP port, product
name, env-var names, service URLs, and status colors move to single
shared constants / theme tokens; the stale claude-sonnet-4-5 default
model id is corrected.
The opencode target wrote a plugin entry the loader can never use (no
JS entrypoint, and plugin packages' skills/ are never scanned), so
'op install --target opencode' delivered nothing. Mirror the codex
layout instead: bundle under ~/.config/opencode/openpencil-skill with
a skills/ symlink opencode's {skill,skills}/**/SKILL.md scan picks up.
The installer-owned entry is recreated on every install so stale
squatters can't shadow it; remove_path only treats NotFound as absent
and handles dangling Windows directory symlinks.
Preflight headless document loading so malformed or binary archives fail with a clear, actionable error before the MCP server starts.
Keep the CLI parser dependency lightweight by disabling op-pen-loader default features and include the updated lockfile plus regression coverage.
op design gains explicit script routing (--script flag, implied for
@file.js/.mjs payloads) and the vendored openpencil-skill bundle
(0.8.0) teaches script mode with the same contract the internal
SCRIPT_FORMAT prompt uses.
Four Windows runtime defects from the platform audit:
- The binary stayed in the console subsystem, parking a console window
behind the GUI when launched from Explorer. Release builds now set
windows_subsystem = "windows"; debug keeps stderr tracing visible.
- Background CLI probes (model discovery, provider version checks) and
the vendored Claude SDK's per-turn spawns lacked CREATE_NO_WINDOW,
flashing console windows once the GUI detaches from the console.
- MCP stdio servers naming .cmd/.bat shims (npx and most npm-installed
servers) could not spawn: CreateProcess cannot execute shims and Rust
1.77+ refuses them as program names. vendor/agent now resolves the
command PATHEXT-style against the PATH the server will actually see
(per-server env override wins) and routes only genuine shims through
cmd /c — real executables keep direct spawn semantics.
- cmd /C start truncated URLs at `&` (every OAuth authorize URL). The
URL now travels double-quoted via raw_arg so cmd keeps it literal.
The inline 新建分支 name input painted a static '|' and the host never woke
the loop for branch_create_focused, so the caret sat frozen. Blink it on the
shared commit-caret cadence (paint_menu_input), wake next_animation_deadline
for branch_create_focused, and seed/reset commit_caret_anchor_ms on focus +
keypress so it starts solid then blinks.