From c42c7f77933aeab6c1f96b785d82df86b158e2ae Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Fri, 14 Aug 2026 23:18:46 -0400 Subject: [PATCH] docs(tui): remove completed extraction plan --- specs/tui-package.md | 641 ------------------------------------------- 1 file changed, 641 deletions(-) delete mode 100644 specs/tui-package.md diff --git a/specs/tui-package.md b/specs/tui-package.md deleted file mode 100644 index e7b55342c73..00000000000 --- a/specs/tui-package.md +++ /dev/null @@ -1,641 +0,0 @@ -# TUI Package Extraction - -## Goal - -Move the canonical OpenCode terminal application from -`packages/opencode/src/cli/cmd/tui` into a self-contained workspace package while -the legacy CLI and the new CLI continue to use the same implementation. - -Target package: - -```text -packages/tui -name: @opencode-ai/tui -``` - -Target dependency graph: - -```text -packages/opencode ---\ - > @opencode-ai/tui -> @opencode-ai/sdk -packages/cli --------/ -``` - -The TUI may directly depend on terminal and UI infrastructure such as -`@opentui/core`, `@opentui/solid`, `@opentui/keymap`, `solid-js`, Effect, and -generic presentation libraries. It must not depend on `packages/opencode`, -`packages/cli`, or `@opencode-ai/core`. - -The SDK is the TUI's OpenCode boundary. Missing backend data or operations must -be added to the server API and generated SDK rather than imported from backend -implementation modules. - -## Migration Rules - -- Keep one canonical implementation of every TUI feature. Do not copy the full - TUI into `packages/cli` and synchronize two trees. -- Land each section below independently and commit it before starting the next - section. -- Keep each intermediate commit buildable and type-safe. -- Continue integrating team changes into whichever location is canonical for a - file at that point in the migration. -- Use temporary compatibility re-exports only when they materially reduce the - size or conflict risk of a section. Mark them for removal in a later section. -- Do not preserve private imports by creating aliases from `packages/tui` back - into `packages/opencode`. -- Do not replace private `packages/opencode` imports with `@opencode-ai/core` - imports merely to make the package compile. -- Keep tool rendering tolerant of unknown tools and wire-format changes. Local - checks over `unknown` input and metadata are acceptable; importing backend - tool implementations for type safety is not. -- Keep legacy CLI command parsing, server startup, worker management, - authentication, and config discovery outside `@opencode-ai/tui`. - -## Ownership Boundary - -### `@opencode-ai/tui` Owns - -- OpenTUI renderer lifecycle shared by both CLI hosts -- Solid application composition -- Components, routes, dialogs, themes, keymaps, and UI primitives -- SDK client synchronization and event consumption -- Tool-call and tool-result presentation -- TUI-facing plugin contracts and presentation slots -- Resolved TUI configuration types, defaults, and pure validation -- Terminal behavior such as selection, clipboard integration, and local editor - launching when it is not host-specific -- TUI-local persistence such as prompt history, stash, frecency, selected model, - and selected theme -- Presentation utilities such as locale formatting, error display, record - checks, duration formatting, and layout helpers - -### CLI Hosts Own - -- Command definitions and argument parsing -- Starting, locating, and stopping servers and workers -- Authentication and transport construction -- Process-level signal policy -- Config file discovery, precedence, migration, and environment substitution -- Plugin package discovery, installation, and backend activation -- Upgrade checks and installation metadata -- Executable build wiring and worker path defines - -### Server And SDK Own - -- OpenCode domain data displayed by the TUI -- Session, message, workspace, file, provider, model, agent, and permission - operations -- Retry, revert, fork, share, and other backend actions -- Stable wire shapes for tool parts and plugin metadata -- Server capabilities needed to conditionally expose UI behavior - -## Current Boundary - -The canonical implementation currently lives under: - -```text -packages/opencode/src/cli/cmd/tui -``` - -Its private dependency on `packages/opencode` is primarily expressed through -the `@/*` TypeScript alias, which resolves to `packages/opencode/src/*`. -`@tui/*` imports are internal to the TUI and are not themselves a package -boundary problem. - -The main private dependency groups are: - -- `@/util/*`: presentation helpers plus filesystem/process/RPC helpers -- `@/tool/*`: backend tool implementations used by renderers -- `@/session/*`, `@/provider/*`, and `@/reference/*`: backend data and actions -- `@/config/*`: config discovery, parsing, variables, and plugin resolution -- `@/plugin/*`: plugin loading and installation -- `@/cli/*`: yargs adapters, network setup, errors, and CLI presentation -- `@/server/*`: authentication and embedded server behavior -- `Global.Path`, `Flag`, and process environment reads - -The initial extraction should reduce these dependencies in place before moving -the application root. - -## Section 1: Create The Package Skeleton - -Status: Completed. The private `@opencode-ai/tui` workspace package now has an -independent OpenTUI Solid JSX configuration, narrow root export, package-local -alias, and in-memory render smoke test. Neither CLI consumes the package yet. - -Create `packages/tui` without moving the application root yet. - -Tasks: - -- Add `packages/tui/package.json` with the name `@opencode-ai/tui`. -- Add a package `tsconfig.json` configured for OpenTUI Solid JSX. -- Add `bunfig.toml` with the OpenTUI Solid preload for package-local development - and tests. -- Add package scripts for `typecheck` and package-local tests. -- Add direct dependencies used by the TUI. Do not rely on workspace hoisting. -- Add a narrow package export, initially only the package root and any explicit - testing entrypoint needed by migrated tests. -- Establish a package-local import convention. A local alias such as `@tui/*` - is acceptable, but it must resolve entirely inside `packages/tui`. -- Add a minimal package entrypoint and smoke test proving OpenTUI Solid TSX can - typecheck and render. -- Do not make either CLI consume the package yet. - -Exit criteria: - -- `packages/tui` typechecks independently. -- Its test command runs from `packages/tui`. -- The package has no dependency on `opencode`, `@opencode-ai/cli`, or - `@opencode-ai/core`. - -Checkpoint commit: - -```text -feat(tui): add standalone package skeleton -``` - -## Section 2: Move Presentation Utilities And Leaf UI - -Status: Completed. Presentation utilities, bundled themes and their pure theme -engine, keybinding/keymap mechanics, and low-coupling border, link, and spinner -primitives now live in `@opencode-ai/tui`. The legacy host consumes explicit -package exports and retains only integration wrappers or compatibility -re-exports where backend and process concerns have not moved yet. - -Move low-coupling code first so subsequent team changes land in the new package -without waiting for the application root migration. - -Tasks: - -- Move TUI presentation utilities into `packages/tui/src/util`, including the - portions of locale, error display, record checks, duration formatting, and - small functional helpers used by TUI code. -- Move pure TUI utilities already under the old TUI directory. -- Move themes and bundled theme JSON files. -- Move UI primitives and leaf components that have no private backend imports. -- Move pure keybinding schemas and keymap helpers that do not read host flags. -- Move related unit and snapshot tests. -- Update remaining old-tree consumers to import the new canonical modules. -- Use temporary compatibility re-exports from old TUI paths only if needed to - avoid a large unrelated import rewrite. -- Do not move `Filesystem`, `Process`, `Rpc`, worker startup, or config discovery - as generic utilities in this section. - -Exit criteria: - -- Moved files have no `@/...` imports. -- Tests for moved code run from `packages/tui`. -- Existing legacy TUI behavior and typecheck remain unchanged. - -Checkpoint commit: - -```text -refactor(tui): move presentation utilities and primitives -``` - -## Section 3: Remove Backend Tool Implementation Imports - -Status: Completed. Legacy and V2 tool renderers now dispatch on SDK wire names, -accept `Record` input and metadata, and use local guards for -nested presentation data. Web-search labels and structured metadata extraction -are TUI-owned, unknown tools retain the generic fallback, and no TUI source -imports backend tool implementations. The route components remain in the legacy -tree until the SDK state and route move in Section 6. - -Make tool rendering depend only on SDK wire data and local presentation logic. - -Tasks: - -- Remove imports from `@/tool/*` in TUI routes and feature plugins. -- Key built-in renderers by SDK tool name strings such as `read`, `write`, - `edit`, `apply_patch`, `grep`, `glob`, `bash`, `question`, and `task`. -- Treat tool input, output metadata, and plugin-defined fields as `unknown` at - the package boundary. -- Add small local type guards only where a renderer needs a particular field. -- Preserve a generic fallback renderer for unknown and plugin-provided tools. -- Keep renderer failures local: malformed metadata must not crash the entire - session view. -- Replace backend-derived labels or IDs with TUI-owned presentation constants or - SDK-provided values. -- Move the affected tool presentation components and tests to `packages/tui`. - -Exit criteria: - -- No TUI source imports `@/tool/*`. -- Unknown tools render through the generic fallback. -- Existing built-in tool snapshots remain equivalent unless intentionally - updated and reviewed. - -Checkpoint commit: - -```text -refactor(tui): decouple tool rendering from backend tools -``` - -## Section 4: Make Runtime Inputs Explicit - -Status: Completed for the shared runtime contract and legacy host. The TUI now -receives immutable launch-directory, path, capability, terminal/editor, startup, -and build inputs through `@opencode-ai/tui/runtime`. Movable app, component, -route, and feature-plugin code no longer reads OpenCode globals or process state; -command, config, plugin-loading, custom-theme discovery, editor/clipboard, and -Windows lifecycle adapters remain host-owned. `packages/cli` does not consume -this contract yet; that integration remains deferred to Section 9. - -Replace process-global OpenCode state with resolved TUI inputs. - -Define narrow inputs rather than one unstructured host object. Expected groups -include: - -```ts -type TuiCapabilities = { - mouse: boolean - copyOnSelect: boolean - terminalTitle: boolean - workspaces: boolean - showTimeToFirstDraw: boolean -} - -type TuiPaths = { - home: string - state: string - config: string - data: string -} - -type TuiBuildInfo = { - version: string - channel?: string -} -``` - -Tasks: - -- Inventory direct reads of `Flag`, `Global.Path`, and relevant environment - variables in movable TUI code. -- Pass resolved capabilities into the application/provider tree. -- Pass local path roots or a narrow TUI storage capability into persistence - contexts. -- Pass build/version information explicitly. -- Keep environment reads needed by legacy command or worker startup in - `packages/opencode` adapters. -- Give `packages/tui` sensible host-neutral defaults only when behavior is truly - local to a terminal client. -- Move contexts and components after their global dependencies are removed. - -Exit criteria: - -- Movable TUI code does not import `Flag` or `Global`. -- TUI tests can supply deterministic capabilities and storage paths. -- The legacy host constructs the required input through the public package API; - the new CLI integration remains deferred to Section 9. - -Checkpoint commit: - -```text -refactor(tui): make runtime capabilities explicit -``` - -## Section 5: Separate Resolved TUI Config From Host Config Loading - -Status: Completed for the package config contract and legacy host adapter. -`@opencode-ai/tui/config` now owns schemas, defaults, keybind resolution, the -resolved config type, and the Solid config provider. The legacy host retains -file discovery, precedence, JSONC parsing, substitutions, migration, -source-relative sound paths, plugin origins, dependency installation, and -Effect services. `packages/cli` remains untouched until Section 9. - -Move config semantics needed by rendering while retaining filesystem discovery -and migration in the legacy host. - -Tasks: - -- Move TUI config schemas, keybind schemas, defaults, and pure resolution to - `packages/tui`. -- Define the resolved config accepted by the public TUI entrypoint. -- Keep config path discovery, project/global precedence, migration, variable - expansion, and plugin package installation in `packages/opencode` initially. -- Make the legacy host produce the same resolved config shape. -- Add a new CLI adapter that can initially provide defaults or its own resolved - configuration. -- Update schema-generation imports to use the package's explicit config export - if schema generation still needs TUI schemas. -- Move pure config tests; retain discovery and migration integration tests in - `packages/opencode`. - -Exit criteria: - -- `packages/tui` does not import `@/config/*`. -- Config discovery can change without changing TUI rendering code. -- The old CLI still honors existing config precedence and migration behavior. - -Checkpoint commit: - -```text -refactor(tui): separate config resolution from loading -``` - -## Section 6: Move SDK State, Routes, And Backend Operations - -Status: Completed for the SDK/domain boundary. SDK, project, event, legacy sync, -V2 sync, local model state, prompt persistence, and pure prompt helpers are now -canonical in `@opencode-ai/tui`. Configured references resolve through the new -generated `reference.list` SDK operation; prompt payloads rely on optional -server-assigned IDs; local attachment reads use the package platform contract. -Legacy route files remain in place until the plugin slot boundary and app-root -move, but their only private dependencies are plugin presentation or local host -adapters rather than OpenCode domain implementations. - -Make the SDK the only OpenCode domain boundary used by the TUI. - -Tasks: - -- Move SDK client providers, event synchronization, routes, prompt UI, and - session views into `packages/tui`. -- Replace direct imports from `@/session/*`, `@/provider/*`, `@/reference/*`, - `@/lsp/*`, and other backend domains with SDK data or TUI-owned presentation - helpers. -- Replace direct backend actions such as retry with SDK calls. -- For each missing operation, add or adjust the server endpoint, regenerate the - JavaScript SDK with `./packages/sdk/js/script/build.ts`, and consume the - generated SDK API. -- Keep transport creation outside the package. Accept a base URL, headers, - custom fetch, event source, or constructed SDK client as appropriate. -- Keep local-only UI state in the TUI package rather than adding it to the - server API. -- Move affected tests and fixtures. Use real SDK/server integration where - practical instead of mocking backend modules. - -Exit criteria: - -- Domain-facing TUI code imports OpenCode data and operations only from - `@opencode-ai/sdk`. -- No TUI source imports private session, provider, reference, LSP, server, or - core domain implementations. -- SDK generation is clean after any API changes. - -Checkpoint strategy: - -This section may be split into multiple commits when an SDK gap is substantial. -Each commit must leave both the old TUI host and package tests working. Suggested -commit pattern: - -```text -feat(sdk): expose for tui clients -refactor(tui): move to sdk boundary -``` - -Final section checkpoint: - -```text -refactor(tui): move sdk state and routes into package -``` - -## Section 7: Isolate Plugin Presentation From Plugin Loading - -Status: Completed. Plugin slots, route registration, TUI-facing APIs, runtime -presentation state, and built-in feature plugins now live in -`@opencode-ai/tui`. The legacy host injects a narrow plugin host that retains -discovery, installation, manifest/config mutation, external module execution, -pure-mode filtering, and cleanup ownership. Missing or failing plugin hosts -degrade to the base TUI without blocking startup. - -Keep plugin UI extensibility without importing the legacy plugin installer and -loader into the TUI package. - -Tasks: - -- Move plugin presentation slots, route contracts, and TUI-facing APIs into - `packages/tui` or the existing public plugin TUI contract package. -- Keep package discovery, installation, manifest resolution, backend activation, - and process lifecycle in the host. -- Define the serialized or runtime plugin presentation data the TUI requires. -- Prefer SDK-delivered plugin metadata when the behavior must also work for a - remote server. -- Make plugin absence or incompatibility degrade gracefully. -- Move plugin rendering tests to `packages/tui`; retain installation/loading - integration tests in `packages/opencode`. - -Exit criteria: - -- `packages/tui` does not import `@/plugin/*` or the old TUI plugin runtime. -- Remote and local TUI clients have a defined plugin behavior. -- Plugin UI failures cannot prevent the base TUI from starting. - -Checkpoint commit: - -```text -refactor(tui): separate plugin presentation from loading -``` - -## Section 8: Move The Application Root And Renderer Lifecycle - -Status: Completed. `packages/tui` now owns the canonical application root, -provider composition, routes, components, parser presentation, renderer -configuration, and renderer lifecycle. Process mutation, Windows console -handling, backend worker startup, config loading, plugin loading, native audio, -and legacy platform implementations remain injected host adapters. Old source -paths are temporary compatibility re-exports for the legacy command host. - -Move the canonical app composition after its dependencies have already crossed -the package boundary. - -Tasks: - -- Move `app.tsx`, remaining providers, routes, components, attention handling, - keymaps, and renderer lifecycle to `packages/tui`. -- Export a narrow public API such as: - -```ts -export type TuiInput = { - url: string - directory?: string - headers?: RequestInit["headers"] - fetch?: typeof fetch - config: TuiConfig.Resolved - capabilities: TuiCapabilities - paths: TuiPaths -} - -export function run(input: TuiInput): TuiHandle -export function createRenderer(config: TuiConfig.Resolved): Promise -``` - -- Preserve the existing lifecycle guarantees: readiness, waiting until exit, - idempotent cleanup, renderer destruction, SIGHUP handling where appropriate, - and terminal restoration. -- Keep Windows process adapters outside the package if they mutate host process - state; invoke them from CLI adapters around the package lifecycle. -- Keep OpenTUI parser-worker embedding in executable build scripts. -- Move app lifecycle and rendering tests to `packages/tui`. - -Exit criteria: - -- `packages/tui` contains the canonical application root. -- The package has no imports from `packages/opencode`, `packages/cli`, or - `@opencode-ai/core`. -- The package public API is sufficient for both old and new CLI adapters. - -Checkpoint commit: - -```text -refactor(tui): move application root into package -``` - -## Section 9: Convert Both CLIs To Thin Adapters - -Status: Completed. The legacy thread and attach commands now lazily invoke the -public `@opencode-ai/tui` root while retaining worker/server/config/plugin and -process adapters. The new CLI default command launches the same package against -its authenticated daemon transport with a minimal local platform/host. Missing -legacy provider/config APIs currently degrade to the shared provider-connect -screen; source and compiled new-CLI behavior match, while named commands remain -outside the TUI path. - -Make both executable packages consume the same TUI package. - -Tasks: - -- Keep the legacy yargs commands corresponding to current `thread.ts` and - `attach.ts` in `packages/opencode`. -- Keep the legacy embedded worker and server startup in `packages/opencode`. -- Change those adapters to load config, create transport inputs, and call the - public `@opencode-ai/tui` API. -- Change `packages/cli`'s default command handler to call the same public API. -- Remove the temporary `packages/cli/src/tui` shell after the shared package is - integrated. -- Remove duplicated OpenTUI lifecycle code from both hosts. -- Ensure non-TUI subcommands remain lazily isolated from OpenTUI startup. -- Update executable build scripts to bundle the shared package, parser worker, - assets, and any retained host worker. - -Exit criteria: - -- Both CLIs launch the same package implementation. -- There is no duplicate TUI source tree in `packages/cli`. -- Legacy attach and local-worker modes still work. -- Named non-TUI commands do not launch or eagerly initialize the TUI. - -Checkpoint commit: - -```text -refactor(cli): share tui package across command hosts -``` - -## Section 10: Remove Compatibility Paths And Finish Ownership - -Status: Completed. Package source imports are self-contained, package exports -are narrowed to active host contracts, package-owned tests and snapshots live -under `packages/tui`, and the obsolete compatibility tree has been removed. -Legacy command, worker, config, plugin-loader, process, editor, audio, and event -adapters now live in explicit host-owned locations outside `src/cli/cmd/tui/`. - -Delete migration scaffolding only after both hosts consume the package. - -Tasks: - -- Remove old TUI compatibility re-exports and the obsolete directory tree under - `packages/opencode/src/cli/cmd/tui`. -- Retain and relocate only true host adapters such as legacy commands, worker, - transport setup, and config loading. -- Remove obsolete `@tui/*` path mappings from `packages/opencode`. -- Remove stale test fixtures and update all imports to package exports. -- Narrow `@opencode-ai/tui` exports to intentional public entrypoints. -- Verify package manifests list every direct dependency and no accidental - dependency is supplied only by workspace hoisting. -- Update repository documentation describing TUI ownership and development. - -Exit criteria: - -- No production import references the old TUI source location. -- No source under `packages/tui` imports `@/...`, `@opencode-ai/core`, or either - executable package. -- The old TUI directory contains no canonical implementation files. -- The dependency graph has no cycle. - -Checkpoint commit: - -```text -refactor(tui): complete standalone package extraction -``` - -## Invariants To Preserve - -- There is one canonical TUI implementation at every migration stage. -- Legacy TUI behavior remains available until its host is intentionally removed. -- The default new CLI command launches the TUI, while named subcommands continue - to route to their own handlers. -- Renderer cleanup restores the terminal on normal exit, interruption, startup - failure, and renderer destruction. -- TUI package imports do not reach into executable or backend implementation - packages. -- SDK wire data is treated as the source of truth for OpenCode domain state. -- Unknown tools and plugin data render safely without backend type imports. -- Remote-server use remains possible; the TUI must not require an in-process - backend implementation. -- TUI-local persistence remains local and does not become server state unless - there is an explicit product requirement. -- Team changes should be moved with their canonical file, not manually copied - between old and new implementations. - -## Verification Gates - -Run verification after every section, adding narrower tests for the area being -moved. - -Package checks: - -```text -cd packages/tui && bun typecheck -cd packages/tui && bun test -cd packages/opencode && bun typecheck -cd packages/cli && bun typecheck -``` - -Dependency checks: - -```text -rg "from ['\"]@/" packages/tui/src -rg '@opencode-ai/core|packages/opencode|packages/cli' packages/tui -rg 'src/cli/cmd/tui|@tui/' packages/opencode/src packages/opencode/test -``` - -SDK checks when server APIs change: - -```text -./packages/sdk/js/script/build.ts -git diff --check -``` - -Interactive smoke checks should run in `tmux` so the terminal can be captured -and cleaned up reliably: - -- Start the legacy local TUI and confirm initial render. -- Start legacy attach mode against a server. -- Start the new CLI default command and confirm it renders the same package. -- Exit each mode with Ctrl-C and verify the process and terminal are restored. -- Run representative named commands in both CLIs and verify they do not launch - the TUI. - -Compiled checks: - -- Build the current-platform `packages/opencode` binary. -- Build the current-platform `packages/cli` binary. -- Run TUI and non-TUI smoke checks against both compiled binaries. -- Verify theme JSON, audio assets, OpenTUI parser worker, and retained backend - worker assets are included. - -## Progress Tracking - -- [x] Section 1: Create the package skeleton -- [x] Section 2: Move presentation utilities and leaf UI -- [x] Section 3: Remove backend tool implementation imports -- [x] Section 4: Make runtime inputs explicit -- [x] Section 5: Separate resolved TUI config from host config loading -- [x] Section 6: Move SDK state, routes, and backend operations -- [x] Section 7: Isolate plugin presentation from plugin loading -- [x] Section 8: Move the application root and renderer lifecycle -- [x] Section 9: Convert both CLIs to thin adapters -- [x] Section 10: Remove compatibility paths and finish ownership - -Update each section's status and this checklist in the same commit that completes -the section.