feat: default semantic rendering to libghostty-vt#157
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Make
libghostty-vtthe default semantic renderer for snapshots, render waits, hashes, batch render-wait steps, and host semantic replay when the optional native backend is available. Keepghostty-webas the default visual renderer for PNG screenshots and WebM exports.Behavior changes
libghostty-vtand fall back toghostty-webwhen native support is unavailable;ghostty-web.--renderer,AGENT_TTY_RENDERER, or Homeconfig.json.defaultRendererstill overrides both semantic and visual defaults.Dogfooding
dogfood/20260616-default-semantic-renderer/dogfood/20260616-default-semantic-renderer/README.md.Validation
npm run format:checknpm run lintnpm run typechecknpx vitest run test/unit/host/renderer.test.ts test/unit/pty/createPty.test.ts test/unit/renderer/capabilities.test.ts test/unit/renderer/libghosttyVtBackend.test.ts test/unit/commands/wait.test.tsnpx vitest run --maxWorkers=1 test/integration/backend-selection.test.tsnpx vitest run --maxWorkers=1 test/integration/host-renderer-rpc.test.tsnpm run testnpm run buildnpm run smoke:install -- --skip-buildnpm run verifybash dogfood/20260616-default-semantic-renderer/commands.shNote:
npm run test:integration -- test/integration/backend-selection.test.ts test/integration/host-renderer-rpc.test.tsexceeded the 240s local workspace timeout when run in parallel; the same two files passed individually with--maxWorkers=1.📋 Implementation Plan
Implementation Plan: Make
libghostty-vtthe Default Semantic RendererExecutive summary
Make
libghostty-vtthe default semantic renderer for render waits, snapshots, screen hashing, batch render-wait steps, and the host's internal semantic replay work, while keepingghostty-webas the default visual renderer for PNG screenshots and WebM exports.Do not flip the single global
DEFAULT_RENDERER_NAMEfromghostty-webtolibghostty-vt. The safer implementation is a small default-resolution split:--renderer,AGENT_TTY_RENDERER, orconfig.json.defaultRenderer, respect that choice as the requested renderer for both semantic and visual operations; WebM remains a special case where a requestedlibghostty-vtis accepted but normalized toghostty-webbecause the actual video producer is browser-backed.libghostty-vtwhen@coder/libghostty-vt-nodeis available;ghostty-webwhen the optional native package is unavailable;ghostty-webto avoid native-addon dependency and double boot/replay overhead.This preserves the existing visual proof contract while moving the common agent loop (
run→wait→snapshot) to the faster in-process renderer on supported platforms.Evidence and constraints verified
src/renderer/names.tscurrently definesRendererNameSchema = ['ghostty-web', 'libghostty-vt']andDEFAULT_RENDERER_NAME = 'ghostty-web'.src/cli/context.tscurrently resolves oneCommandContext.rendererDefaultfrom--renderer,AGENT_TTY_RENDERER,config.json.defaultRenderer, thenDEFAULT_RENDERER_NAME.src/cli/main.tsstores the resolved semantic renderer inprocess.env.AGENT_TTY_RENDERER; the per-session host uses that environment variable when no renderer is provided in RPC params.snapshot,wait, andbatchconsumecontext.rendererDefaultfor semantic renderer work.screenshotandrecord export --format webmalso consumecontext.rendererDefaulttoday, even though their default should remain visual/browser-backed.LibghosttyVtBackend.screenshot()delegates to an internalGhosttyWebBackendfallback after replayinglibghostty-vt; using it as the screenshot default would do avoidable native boot/replay plus browser boot/replay.generateWebmExport()already maps a requestedlibghostty-vtrenderer toghostty-webbefore constructing the video backend, so WebM would not hard-fail from a global flip, but directghostty-webvisual default is still simpler and avoids misleading intent.@coder/libghostty-vt-nodeis an optional dependency.src/renderer/readiness.tsalready exposesprobeLibghosttyVt()and actionable dashboard-readiness errors.doctor --jsonin this workspace reports both browser-backed screenshot/WebM andlibghostty-vtdashboard capability as available.docs/adr/0006-session-dashboard-follows-event-log.mdalready justifieslibghostty-vtfor fast in-process semantic replay and rejectsghostty-webfor the dashboard due to Chromium dependency and multi-second boot.Explore-agent review summary
Three read-only Explore lanes inspected code paths, tests, docs, and dogfood references. Two lanes recommended the semantic-vs-visual split because of optional native dependency risk and visual-command overhead. One lane noted that a raw global default flip would be mechanically viable because screenshot and WebM have fallback/mapping paths, but that approach still changes visual defaults and makes missing-native behavior worse. The final plan adopts the semantic-vs-visual split because it matches the product goal from the original discussion and is the least surprising rollout.
Target behavior
Default behavior when no renderer is configured
libghostty-vtis availablelibghostty-vtis unavailablewaitrender conditionslibghostty-vtghostty-websnapshotlibghostty-vtghostty-webbatchrender-wait stepslibghostty-vtghostty-weblibghostty-vtghostty-webAGENT_TTY_RENDERER.dashboardlibghostty-vtrequiredscreenshotghostty-webghostty-webLibghosttyVtBackend.screenshot()double replay by default.record export --format webmghostty-webghostty-webrecord export --format asciicastSemantic fallback to
ghostty-webis best effort: it still requires the browser-backed renderer stack to be usable.doctor --jsonand capability discovery should surface a clear unavailable/degraded state when neitherlibghostty-vtnorghostty-webcan satisfy render replay.Explicit renderer behavior
If a renderer is explicitly configured by root
--renderer,AGENT_TTY_RENDERER, or Homeconfig.json.defaultRenderer, keep the existing mental model: that chosen renderer is the default renderer for all commands.Examples:
agent-tty --renderer ghostty-web snapshot ...usesghostty-webeven when native is available.agent-tty --renderer libghostty-vt screenshot ...is allowed and continues to produce a PNG throughLibghosttyVtBackend's honestghostty-webfallback metadata.AGENT_TTY_RENDERER=libghostty-vtorconfig.jsonwith{ "defaultRenderer": "libghostty-vt" }intentionally routes screenshots through the native/fallback path too; document that this requires native availability and may add double replay/boot overhead for screenshots. For WebM, the request is accepted butgenerateWebmExport()still normalizes the actual producer toghostty-weband metadata must sayghostty-web.AGENT_TTY_RENDERER=ghostty-webrestores legacy behavior globally.config.jsonwith{ "defaultRenderer": "ghostty-web" }restores legacy behavior for that Home.Implementation phases
Phase 1 — Introduce explicit semantic/visual defaults
Files:
src/renderer/names.tssrc/cli/context.tstest/unit/renderer/registry.test.tstest/unit/cli/context.test.tsPlan:
DEFAULT_RENDERER_NAME: RendererName = 'ghostty-web'for compatibility in code paths that still need a single safe renderer fallback, but add a code comment making clear it is no longer the product's semantic default. Treat it as the legacy/safe single-renderer fallback only.src/renderer/names.ts:src/renderer/names.tsotherwise pure and easy to import. Put any native probing in CLI/context or readiness helpers, not in the schema/constant module.src/cli/context.ts, add a small resolver for renderer defaults. Recommended shape:configuredRenderer = options.renderer ?? env.AGENT_TTY_RENDERER ?? configFile?.defaultRenderer.configuredRenderer !== undefined, validate it withresolveRendererDefault(configuredRenderer)and set both semantic and visual defaults to the validated value.configuredRenderer === undefined, use a process-memoized, non-fatalprobeLibghosttyVt()result. Set semantic tolibghostty-vtwhen available orghostty-webwhen unavailable, and set visual toghostty-web.--renderer libghostty-vtshould still validate as a supported renderer name and then fail later with the existing actionable boot/readiness error if the native module cannot load.CommandContextwith a visual default while preserving the existing semantic field name to minimize churn:resolveRendererDefault(raw?: string)as the synchronous validator used for explicit values and tests.RendererNamevalues;rg -n "rendererDefault|rendererVisualDefault|DEFAULT_RENDERER_NAME" src testduring implementation and update every fixture/consumer deliberately; do not rely only on the file list in this plan.Quality gate after Phase 1:
Expected test work:
CommandContextfixtures to includerendererVisualDefault.resolveCommandContextif small; otherwise mockprobeLibghosttyVt()in the test file.ghostty-webwithout failing context resolution.ghostty-webonly when no renderer was explicitly configured, and that explicit env/configlibghostty-vtintentionally makes screenshot take the native/fallback route while explicit WebM still reportsghostty-webas the actual producer.Phase 2 — Route commands to the correct default
Files:
src/cli/commands/snapshot.tssrc/cli/commands/wait.tssrc/cli/commands/batch.tssrc/cli/commands/screenshot.tssrc/cli/commands/record-export.tssrc/cli/main.tstest/unit/commands/Plan:
context.rendererDefault:snapshotlive RPC and offline replay;waitrender conditions and offline render wait;batchrender-wait driver.context.rendererVisualDefault:screenshotlive RPC and offline replay;record export --format webm'sgenerateWebmExport({ rendererName })input.record export --format asciicastunchanged.src/cli/main.tssettingprocess.env.AGENT_TTY_RENDERER = context.rendererDefaultbecause the host's environment default is a semantic renderer default. Update debug logging to include both semantic and visual defaults if that can be done without noisy output changes.--rendererhelp text insrc/cli/main.tsso it no longer implies one universal default. Suggested wording:Renderer backend override. Defaults vary by command: semantic actions prefer libghostty-vt when available; visual artifacts default to ghostty-web.create-spawned hosts inherit the semantic default throughAGENT_TTY_RENDERER. DirectrunHost()without this env may keep the legacyDEFAULT_RENDERER_NAME/ghostty-webfallback because direct host invocation is internal and not the supported CLI path; document this decision in code comments or tests if it is non-obvious.Quality gate after Phase 2:
Expected assertions:
libghostty-vtstill maps toghostty-webinsidegenerateWebmExport().Phase 3 — Update runtime introspection and version reporting
Files:
src/cli/commands/version.tssrc/renderer/capabilities.tssrc/renderer/readiness.tsif helper wording/types need reusetest/unit/commands/version.test.tstest/unit/renderer/capabilities.test.tstest/unit/renderer/readiness.test.tstest/integration/cli.test.tsPlan:
buildVersionResult().rendererBackendsfrom['ghostty-web']to['ghostty-web', 'libghostty-vt']orRendererNameSchema.optionsif importing that list does not create an awkward dependency.snapshot/waitas purely built-in. Preferred behavior:snapshotis available when at least one semantic renderer is usable (libghostty-vtavailable, orghostty-web/Playwright usable enough for render replay), otherwise unavailable with a semantic-renderer reason.waitis available under the same condition; non-render waits (--exit,--idle-ms) remain operational, but the current coarsewaitcapability should be honest about render waits because that is the documented capability.record-export-asciicastremains built-in.screenshotandrecord-export-webmremain browser-backed visual capabilities.doctor --jsonmatches actual default renderer behavior.version --jsoncapability discovery best-effort. Thedashboardcapability already reportslibghostty-vtavailability viaprobeLibghosttyVt().rendererBackendvalues change.doctor --jsonoutput manually after implementation to ensure it clearly distinguishes:Quality gate after Phase 3:
Phase 4 — Update integration and e2e coverage
Files/tests to inspect and update:
test/integration/backend-selection.test.tstest/integration/host-renderer-rpc.test.tstest/integration/cross-backend-screen-hash.test.tstest/integration/wait-render.test.tstest/e2e/libghostty-vt-renderer.test.tstest/e2e/hello-prompt.test.tstest/e2e/renderer-errors.test.tsPlan:
probeLibghosttyVt()rather than relying on local assumptions.snapshot/renderwaitshould reportlibghostty-vt;snapshot/renderwaitshould reportghostty-web;screenshotand WebM should reportghostty-webin both cases.--renderer ghostty-webstill forces semantic operations toghostty-web;--renderer libghostty-vtstill selects native semantic operations and exercises screenshot fallback behavior where already covered.inspect/rendererRuntime.backendvalues only where the default changed. Avoid broad snapshot churn.Quality gate after Phase 4:
Phase 5 — Update docs and release notes
Files:
README.mddocs/USAGE.mddocs/TROUBLESHOOTING.mdRELEASE.mddesign/ARCHITECTURE.mddogfood/CATALOG.mdCHANGELOG.mdif this change is prepared as a release-facing entryPlan:
ghostty-webis the universal default. Proposed wording:libghostty-vt— default for semantic actions (wait,snapshot, screen hash) when the optional native package is available; also powers the dashboard.ghostty-web— default for visual actions (screenshot, WebM export); browser-backed reference visual renderer.docs/TROUBLESHOOTING.mdline(s) that currently sayghostty-webis the reference renderer for snapshots, screenshots, and replay video. Split semantic and visual troubleshooting.docs/USAGE.mdrenderer section around screenshot/WebM to mention the semantic-vs-visual defaults and how to restore legacy behavior with--renderer ghostty-web,AGENT_TTY_RENDERER=ghostty-web, or config.RELEASE.mdknown limitations. Do not claim native pixel parity; say semantic operations may default to nativelibghostty-vt, while visual artifacts remainghostty-webreference artifacts.design/ARCHITECTURE.mdtop-level decisions/current shipped status so it no longer claimsghostty-webis the only default renderer. Keep the tiered truth model: event log truth, semantic native truth, reference visual truth.skills/agent-tty/. Current search found no renderer-specific guidance, but if any examples are updated, keep public examples usingagent-tty ...rather than repo-localnpx tsxcommands.Quality gate after Phase 5:
npm run format:check npm run lint rg -n "ghostty-web.*default|default.*ghostty-web|reference renderer for snapshots" README.md docs RELEASE.md design skills dogfood -SDogfooding and reviewer proof bundle
Create a new proof bundle under
dogfood/20260616-default-semantic-renderer/after implementation. The bundle should be self-contained and reviewer-friendly, modeled afterdogfood/20260424-libghostty-vt-renderer/.Bundle contents
README.md— what changed, what the bundle proves, exact reviewer checks.commands.sh— reproducible script with an isolated absoluteAGENT_TTY_HOMEfrommktemp -d.environment.txt— Node/npm versions, OS, git HEAD,version --json,doctor --json, native-addon probe info.default-wait.json— default semantic wait envelope.default-snapshot.json— default semantic snapshot envelope.default-screenshot.json— default visual screenshot envelope.default-webm.json— default visual WebM export envelope.explicit-ghostty-web-snapshot.json— legacy override proof.explicit-libghostty-vt-screenshot.json— explicit native screenshot-fallback proof, if native is available.explicit-libghostty-vt-webm.json— proof that an explicit native WebM request is accepted but still recordsghostty-webas the actual producer.inspect.json— artifact manifest/runtime summary after captures.screenshots/default-screenshot.png— copied screenshot artifact.videos/default-webm.webm— copied WebM artifact.recordings/default.cast— asciicast export, optional but useful baseline.Dogfood script flow
npx tsx src/cli/main.tsinside the script and document that choice.hello-promptfixture or a tiny shell fixture in a new session.--renderer:--renderer:screenshots/andvideos/.inspect --json, destroy the session, and validate all JSON envelopes withjq.file/sha256sum;fileorffprobeif available;Dogfood acceptance checks
default-snapshot.jsonreportsrendererBackend: "libghostty-vt".npm install --omit=optional; do not rely on brittle ad hoc deletion of files fromnode_modules.rendererBackend: "ghostty-web".default-screenshot.jsonreportsrendererBackend: "ghostty-web".default-webm.jsonreportsmetadata.rendererBackend: "ghostty-web".--renderer ghostty-websemantic operation reportsghostty-web.--renderer libghostty-vtscreenshot request either succeeds through fallback with honestghostty-webproducer metadata or fails with the existing actionable native-dependency message if native is unavailable.--renderer libghostty-vt record export --format webmsucceeds when visual dependencies are available and reportsmetadata.rendererBackend: "ghostty-web"because WebM is always browser-produced.Final validation gates
Run the narrowest checks after each phase, then run the full gate before claiming implementation success:
npm run format:check npm run lint npm run typecheck npm run test npm run build npm run smoke:install -- --skip-buildPreferred final repo gate if
miseis available:If
miseis unavailable, use:Manual CLI checks with isolated Home:
Acceptance criteria
RendererNameSchemastill accepts exactlyghostty-webandlibghostty-vtunless a separate reviewed decision addsautolater.libghostty-vton supported installs and gracefully fall back toghostty-webwhen native support is absent.ghostty-weband do not bootlibghostty-vt; explicitlibghostty-vtscreenshot requests may boot native plus fallback, while explicitlibghostty-vtWebM requests still normalize to aghostty-webproducer.libghostty-vtand fails fast with the existing actionable message when unavailable.doctor --json/capability discovery is updated or explicitly documented as deferred; preferred acceptance is that snapshot/render-wait capabilities require at least one usable semantic renderer.ghostty-webbehavior.Risks and mitigations
ghostty-webwhen usable; surface clear capability/doctor failures when neither semantic path is usable; keep explicitghostty-weboverride documented.rendererDefaultas the semantic field name and add onlyrendererVisualDefault; update context fixtures surgically.ghostty-webafter semantic defaults changed.resolveCommandContextprobe adds overhead to non-render commands.waitincludes both render and non-render modes.waitcapability; if deferred, document that deferral explicitly and keep tests aligned.ghostty-webas universal default.Advisor review status
Approved after advisor round 2 review. Round 1 requested changes around capability reporting, CLI help,
DEFAULT_RENDERER_NAMEsemantics, memoized/non-fatal native probing, host inheritance tests, expanded test inventory, explicit visual override costs, and realistic native-unavailable dogfood. Round 2 requested a correction that explicitlibghostty-vtWebM requests are accepted but still produced byghostty-web, plus best-effort/browser requirements for semantic fallback and quick/full capability behavior. All requested changes have been incorporated, and the advisor reported no remaining blockers.Generated with
mux• Model:openai:gpt-5.5• Thinking:xhigh