Compare commits

...

5 Commits

Author SHA1 Message Date
Alex Yaroshuk
2a2082233d fix(app): display skill name in skill tool call (#15413) 2026-02-27 19:18:14 -06:00
Adam
267d2c82de chore: cleanup 2026-02-27 19:12:19 -06:00
Jay
0b8c1f1f7d docs: Update OpenCode Go subscription and usage details (#15415) 2026-02-27 16:04:53 -08:00
Frank
2eb1d4cb9a doc: go 2026-02-27 18:03:39 -05:00
Frank
d2a8f44c22 doc: opencode go 2026-02-27 17:38:30 -05:00
6 changed files with 177 additions and 901 deletions

View File

@@ -250,6 +250,11 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
icon: "bubble-5",
title: i18n.t("ui.tool.questions"),
}
case "skill":
return {
icon: "brain",
title: input.name || "skill",
}
default:
return {
icon: "mcp",
@@ -1900,3 +1905,25 @@ ToolRegistry.register({
)
},
})
ToolRegistry.register({
name: "skill",
render(props) {
const title = createMemo(() => props.input.name || "skill")
const running = createMemo(() => props.status === "pending" || props.status === "running")
const titleContent = () => <TextShimmer text={title()} active={running()} />
const trigger = () => (
<div data-slot="basic-tool-tool-info-structured">
<div data-slot="basic-tool-tool-info-main">
<span data-slot="basic-tool-tool-title" class="capitalize agent-title">
{titleContent()}
</span>
</div>
</div>
)
return <BasicTool icon="brain" status={props.status} trigger={trigger()} hideDetails />
},
})

View File

@@ -224,7 +224,7 @@ export default defineConfig({
"zh-CN": "使用",
"zh-TW": "使用",
},
items: ["tui", "cli", "web", "ide", "zen", "share", "github", "gitlab"],
items: ["go", "tui", "cli", "web", "ide", "zen", "share", "github", "gitlab"],
},
{

View File

@@ -0,0 +1,149 @@
---
title: Go
description: Low cost subscription for open coding models.
---
import config from "../../../config.mjs"
export const console = config.console
export const email = `mailto:${config.email}`
OpenCode Go is a low cost **$10/month** subscription that gives you reliable access to popular open coding models.
:::note
OpenCode Go is currently in beta.
:::
Go works like any other provider in OpenCode. You subscribe to OpenCode Go and
get your API key. It's **completely optional** and you don't need to use it to
use OpenCode.
It is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access.
---
## Background
Open models have gotten really good. They now reach performance close to
proprietary models for coding tasks. And because many providers can serve them
competitively, they are usually far cheaper.
However, getting reliable, low latency access to them can be difficult. Providers
vary in quality and availability.
:::tip
We tested a select group of models and providers that work well with OpenCode.
:::
To fix this, we did a couple of things:
1. We tested a select group of open models and talked to their teams about how to
best run them.
2. We then worked with a few providers to make sure these were being served
correctly.
3. Finally, we benchmarked the combination of the model/provider and came up
with a list that we feel good recommending.
OpenCode Go gives you access to these models for **$10/month**.
---
## How it works
OpenCode Go works like any other provider in OpenCode.
1. You sign in to **<a href={console}>OpenCode Zen</a>**, subscribe to Go, and
copy your API key.
2. You run the `/connect` command in the TUI, select `OpenCode Go`, and paste
your API key.
3. Run `/models` in the TUI to see the list of models available through Go.
:::note
Only one member per workspace can subscribe to OpenCode Go.
:::
The current list of models includes:
- **Kimi K2.5**
- **GLM-5**
- **MiniMax M2.5**
The list of models may change as we test and add new ones.
---
## Usage limits
OpenCode Go includes the following limits:
- **5 hour limit** — $4 of usage
- **Weekly limit** — $10 of usage
- **Monthly limit** — $20 of usage
In terms of tokens, $20 of usage is roughly equivalent to:
- 69 million GLM-5 tokens
- 121 million Kimi K2.5 tokens
- 328 million MiniMax M2.5 tokens
You can view your current usage in the **<a href={console}>console</a>**.
:::tip
If you reach the usage limit, you can continue using the free models.
:::
Usage limits may change as we learn from early usage and feedback.
---
### Pricing
OpenCode Go is a **$10/month** subscription plan. Below are the prices **per 1M tokens**.
| Model | Input | Output | Cached Read |
| ------------ | ----- | ------ | ----------- |
| GLM-5 | $1.00 | $3.20 | $0.20 |
| Kimi K2.5 | $0.60 | $3.00 | $0.10 |
| MiniMax M2.5 | $0.30 | $1.20 | $0.03 |
---
### Usage beyond limits
If you also have credits on your Zen balance, you can enable the **Use balance**
option in the console. When enabled, Go will fall back to your Zen balance
after you've reached your usage limits instead of blocking requests.
---
## Endpoints
You can also access Go models through the following API endpoints.
| Model | Model ID | Endpoint | AI SDK Package |
| ------------ | ------------ | ------------------------------------------------ | --------------------------- |
| GLM-5 | glm-5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| Kimi K2.5 | kimi-k2.5 | `https://opencode.ai/zen/go/v1/chat/completions` | `@ai-sdk/openai-compatible` |
| MiniMax M2.5 | minimax-m2.5 | `https://opencode.ai/zen/go/v1/messages` | `@ai-sdk/anthropic` |
The [model id](/docs/config/#models) in your OpenCode config
uses the format `opencode-go/<model-id>`. For example, for Kimi K2.5, you would
use `opencode-go/kimi-k2.5` in your config.
---
## Privacy
The plan is designed primarily for international users, with models hosted in the US, EU, and Singapore for stable global access.
<a href={email}>Contact us</a> if you have any questions.
---
## Goals
We created OpenCode Go to:
1. Make AI coding **accessible** to more people with a low cost subscription.
2. Provide **reliable** access to the best open coding models.
3. Curate models that are **tested and benchmarked** for coding agent use.
4. Have **no lock-in** by allowing you to use any other provider with OpenCode as well.

View File

@@ -1,426 +0,0 @@
# File Component Unification Plan
Single path for text, diff, and media
---
## Define goal
Introduce one public UI component API that renders plain text files or diffs from the same entry point, so selection, comments, search, theming, and media behavior are maintained once.
### Goal
- Add a unified `File` component in `packages/ui/src/components/file.tsx` that chooses plain or diff rendering from props.
- Centralize shared behavior now split between `packages/ui/src/components/code.tsx` and `packages/ui/src/components/diff.tsx`.
- Bring the existing find/search UX to diff rendering through a shared engine.
- Consolidate media rendering logic currently split across `packages/ui/src/components/session-review.tsx` and `packages/app/src/pages/session/file-tabs.tsx`.
- Provide a clear SSR path for preloaded diffs without keeping a third independent implementation.
### Non-goal
- Do not change `@pierre/diffs` behavior or fork its internals.
- Do not redesign line comment UX, diff visuals, or keyboard shortcuts.
- Do not remove legacy `Code`/`Diff` APIs in the first pass.
- Do not add new media types beyond parity unless explicitly approved.
- Do not refactor unrelated session review or file tab layout code outside integration points.
---
## Audit duplication
The current split duplicates runtime logic and makes feature parity drift likely.
### Duplicate categories
- Rendering lifecycle is duplicated in `code.tsx` and `diff.tsx`, including instance creation, cleanup, `onRendered` readiness, and shadow root lookup.
- Theme sync is duplicated in `code.tsx`, `diff.tsx`, and `diff-ssr.tsx` through similar `applyScheme` and `MutationObserver` code.
- Line selection wiring is duplicated in `code.tsx` and `diff.tsx`, including drag state, shadow selection reads, and line-number bridge integration.
- Comment annotation rerender flow is duplicated in `code.tsx`, `diff.tsx`, and `diff-ssr.tsx`.
- Commented line marking is split across `markCommentedFileLines` and `markCommentedDiffLines`, with similar timing and effect wiring.
- Diff selection normalization (`fixSelection`) exists twice in `diff.tsx` and `diff-ssr.tsx`.
- Search exists only in `code.tsx`, so diff lacks find and the feature cannot be maintained in one place.
- Contexts are split (`context/code.tsx`, `context/diff.tsx`), which forces consumers to choose paths early.
- Media rendering is duplicated outside the core viewers in `session-review.tsx` and `file-tabs.tsx`.
### Drift pain points
- Any change to comments, theming, or selection requires touching multiple files.
- Diff SSR and client diff can drift because they carry separate normalization and marking code.
- Search cannot be added to diff cleanly without more duplication unless the viewer runtime is unified.
---
## Design architecture
Use one public component with a discriminated prop shape and split shared behavior into small runtime modules.
### Public API proposal
- Add `packages/ui/src/components/file.tsx` as the primary client entry point.
- Export a single `File` component that accepts a discriminated union with two primary modes.
- Use an explicit `mode` prop (`"text"` or `"diff"`) to avoid ambiguous prop inference and keep type errors clear.
### Proposed prop shape
- Shared props:
- `annotations`
- `selectedLines`
- `commentedLines`
- `onLineSelected`
- `onLineSelectionEnd`
- `onLineNumberSelectionEnd`
- `onRendered`
- `class`
- `classList`
- selection and hover flags already supported by current viewers
- Text mode props:
- `mode: "text"`
- `file` (`FileContents`)
- text renderer options from `@pierre/diffs` `FileOptions`
- Diff mode props:
- `mode: "diff"`
- `before`
- `after`
- `diffStyle`
- diff renderer options from `FileDiffOptions`
- optional `preloadedDiff` only for SSR-aware entry or hydration adapter
- Media props (shared, optional):
- `media` config for `"auto" | "off"` behavior
- path/name metadata
- optional lazy loader (`readFile`) for session review use
- optional custom placeholders for binary or removed content
### Internal module split
- `packages/ui/src/components/file.tsx`
Public unified component and mode routing.
- `packages/ui/src/components/file-ssr.tsx`
Unified SSR entry for preloaded diff hydration.
- `packages/ui/src/components/file-search.tsx`
Shared find bar UI and host registration.
- `packages/ui/src/components/file-media.tsx`
Shared image/audio/svg/binary rendering shell.
- `packages/ui/src/pierre/file-runtime.ts`
Common render lifecycle, instance setup, cleanup, scheme sync, and readiness notification.
- `packages/ui/src/pierre/file-selection.ts`
Shared selection/drag/line-number bridge controller with mode adapters.
- `packages/ui/src/pierre/diff-selection.ts`
Diff-specific `fixSelection` and row/side normalization reused by client and SSR.
- `packages/ui/src/pierre/file-find.ts`
Shared find engine (scan, highlight API, overlay fallback, match navigation).
- `packages/ui/src/pierre/media.ts`
MIME normalization, data URL helpers, and media type detection.
### Wrapper strategy
- Keep `packages/ui/src/components/code.tsx` as a thin compatibility wrapper over unified `File` in text mode.
- Keep `packages/ui/src/components/diff.tsx` as a thin compatibility wrapper over unified `File` in diff mode.
- Keep `packages/ui/src/components/diff-ssr.tsx` as a thin compatibility wrapper over unified SSR entry.
---
## Phase delivery
Ship this in small phases so each step is reviewable and reversible.
### Phase 0: Align interfaces
- Document the final prop contract and adapter behavior before moving logic.
- Add a short migration note in the plan PR description so reviewers know wrappers stay in place.
#### Acceptance
- Final prop names and mode shape are agreed up front.
- No runtime code changes land yet.
### Phase 1: Extract shared runtime pieces
- Move duplicated theme sync and render readiness logic from `code.tsx` and `diff.tsx` into shared runtime helpers.
- Move diff selection normalization (`fixSelection` and helpers) out of both `diff.tsx` and `diff-ssr.tsx` into `packages/ui/src/pierre/diff-selection.ts`.
- Extract shared selection controller flow into `packages/ui/src/pierre/file-selection.ts` with mode callbacks for line parsing and normalization.
- Keep `code.tsx`, `diff.tsx`, and `diff-ssr.tsx` behavior unchanged from the outside.
#### Acceptance
- `code.tsx`, `diff.tsx`, and `diff-ssr.tsx` are smaller and call shared helpers.
- Line selection, comments, and theme sync still work in current consumers.
- No consumer imports change yet.
### Phase 2: Introduce unified client entry
- Create `packages/ui/src/components/file.tsx` and wire it to shared runtime pieces.
- Route text mode to `@pierre/diffs` `File` or `VirtualizedFile` and diff mode to `FileDiff` or `VirtualizedFileDiff`.
- Preserve current performance rules, including virtualization thresholds and large-diff options.
- Keep search out of this phase if it risks scope creep, but leave extension points in place.
#### Acceptance
- New unified component renders text and diff with parity to existing components.
- `code.tsx` and `diff.tsx` can be rewritten as thin adapters without behavior changes.
- Existing consumers still work through old `Code` and `Diff` exports.
### Phase 3: Add unified context path
- Add `packages/ui/src/context/file.tsx` with `FileComponentProvider` and `useFileComponent`.
- Update `packages/ui/src/context/index.ts` to export the new context.
- Keep `context/code.tsx` and `context/diff.tsx` as compatibility shims that adapt to `useFileComponent`.
- Migrate `packages/app/src/app.tsx` and `packages/enterprise/src/routes/share/[shareID].tsx` to provide the unified component once wrappers are stable.
#### Acceptance
- New consumers can use one context path.
- Existing `useCodeComponent` and `useDiffComponent` hooks still resolve and render correctly.
- Provider wiring in app and enterprise stays compatible during transition.
### Phase 4: Share find and enable diff search
- Extract the find engine and find bar UI from `code.tsx` into shared modules.
- Hook the shared find host into unified `File` for both text and diff modes.
- Keep current shortcuts (`Ctrl/Cmd+F`, `Ctrl/Cmd+G`, `Shift+Ctrl/Cmd+G`) and active-host behavior.
- Preserve CSS Highlight API support with overlay fallback.
#### Acceptance
- Text mode search behaves the same as today.
- Diff mode now supports the same find UI and shortcuts.
- Multiple viewer instances still route shortcuts to the focused/active host correctly.
### Phase 5: Consolidate media rendering
- Extract media type detection and data URL helpers from `session-review.tsx` and `file-tabs.tsx` into shared UI helpers.
- Add `file-media.tsx` and let unified `File` optionally render media or binary placeholders before falling back to text/diff.
- Migrate `session-review.tsx` and `file-tabs.tsx` to pass media props instead of owning media-specific branches.
- Keep session-specific layout and i18n strings in the consumer where they are not generic.
#### Acceptance
- Image/audio/svg/binary handling no longer duplicates core detection and load state logic.
- Session review and file tabs still render the same media states and placeholders.
- Text/diff comment and selection behavior is unchanged when media is not shown.
### Phase 6: Align SSR and preloaded diffs
- Create `packages/ui/src/components/file-ssr.tsx` with the same unified prop shape plus `preloadedDiff`.
- Reuse shared diff normalization, theme sync, and commented-line marking helpers.
- Convert `packages/ui/src/components/diff-ssr.tsx` into a thin adapter that forwards to the unified SSR entry in diff mode.
- Migrate enterprise share page imports to `@opencode-ai/ui/file-ssr` when convenient, but keep `diff-ssr` export working.
#### Acceptance
- Preloaded diff hydration still works in `packages/enterprise/src/routes/share/[shareID].tsx`.
- SSR diff and client diff now share normalization and comment marking helpers.
- No duplicate `fixSelection` implementation remains.
### Phase 7: Clean up and document
- Remove dead internal helpers left behind in `code.tsx` and `diff.tsx`.
- Add a short migration doc for downstream consumers that want to switch from `Code`/`Diff` to unified `File`.
- Mark `Code`/`Diff` contexts and components as compatibility APIs in comments or docs.
#### Acceptance
- No stale duplicate helpers remain in legacy wrappers.
- Unified path is the default recommendation for new UI work.
---
## Preserve compatibility
Keep old APIs working while moving internals under them.
### Context migration strategy
- Introduce `FileComponentProvider` without deleting `CodeComponentProvider` or `DiffComponentProvider`.
- Implement `useCodeComponent` and `useDiffComponent` as adapters around the unified context where possible.
- If full adapter reuse is messy at first, keep old contexts and providers as thin wrappers that internally provide mapped unified props.
### Consumer migration targets
- `packages/app/src/pages/session/file-tabs.tsx` should move from `useCodeComponent` to `useFileComponent`.
- `packages/ui/src/components/session-review.tsx`, `session-turn.tsx`, and `message-part.tsx` should move from `useDiffComponent` to `useFileComponent`.
- `packages/app/src/app.tsx` and `packages/enterprise/src/routes/share/[shareID].tsx` should eventually provide only the unified provider.
- Keep legacy hooks available until all call sites are migrated and reviewed.
### Compatibility checkpoints
- `@opencode-ai/ui/code`, `@opencode-ai/ui/diff`, and `@opencode-ai/ui/diff-ssr` imports must keep working during migration.
- Existing prop names on `Code` and `Diff` wrappers should remain stable to avoid broad app changes in one PR.
---
## Unify search
Port the current find feature into a shared engine and attach it to both modes.
### Shared engine plan
- Move keyboard host registry and active-target logic out of `code.tsx` into `packages/ui/src/pierre/file-find.ts`.
- Move the find bar UI into `packages/ui/src/components/file-search.tsx`.
- Keep DOM-based scanning and highlight/overlay rendering shared, since both text and diff render into the same shadow-root patterns.
### Diff-specific handling
- Search should scan both unified and split diff columns through the same selectors used in the current code find feature.
- Match navigation should scroll the active range into view without interfering with line selection state.
- Search refresh should run after `onRendered`, diff style changes, annotation rerenders, and query changes.
### Scope guard
- Preserve the current DOM-scan behavior first, even if virtualized search is limited to mounted rows.
- If full-document virtualized search is required, treat it as a follow-up with a text-index layer rather than blocking the core refactor.
---
## Consolidate media
Move media rendering logic into shared UI so text, diff, and media routing live behind one entry.
### Ownership plan
- Put media detection and normalization helpers in `packages/ui/src/pierre/media.ts`.
- Put shared rendering UI in `packages/ui/src/components/file-media.tsx`.
- Keep layout-specific wrappers in `session-review.tsx` and `file-tabs.tsx`, but remove duplicated media branching and load-state code from them.
### Proposed media props
- `media.mode`: `"auto"` or `"off"` for default behavior.
- `media.path`: file path for extension checks and labels.
- `media.current`: loaded file content for plain-file views.
- `media.before` and `media.after`: diff-side values for image/audio previews.
- `media.readFile`: optional lazy loader for session review expansion.
- `media.renderBinaryPlaceholder`: optional consumer override for binary states.
- `media.renderLoading` and `media.renderError`: optional consumer overrides when generic text is not enough.
### Parity targets
- Keep current image and audio support from session review.
- Keep current SVG and binary handling from file tabs.
- Defer video or PDF support unless explicitly requested.
---
## Align SSR
Make SSR diff hydration a mode of the unified viewer instead of a parallel implementation.
### SSR plan
- Add `packages/ui/src/components/file-ssr.tsx` as the unified SSR entry with a diff-only path in phase one.
- Reuse shared diff helpers for `fixSelection`, theme sync, and commented-line marking.
- Keep the private `fileContainer` hydration workaround isolated in the SSR module so client code stays clean.
### Integration plan
- Keep `packages/ui/src/components/diff-ssr.tsx` as a forwarding adapter for compatibility.
- Update enterprise share route to the unified SSR import after client and context migrations are stable.
- Align prop names with the client `File` component so `SessionReview` can swap client/SSR providers without branching logic.
### Defer item
- Plain-file SSR hydration is not needed for this refactor and can stay out of scope.
---
## Verify behavior
Use typechecks and targeted UI checks after each phase, and avoid repo-root runs.
### Typecheck plan
- Run `bun run typecheck` from `packages/ui` after phases 1-7 changes there.
- Run `bun run typecheck` from `packages/app` after migrating file tabs or app provider wiring.
- Run `bun run typecheck` from `packages/enterprise` after SSR/provider changes on the share route.
### Targeted UI checks
- Text mode:
- small file render
- virtualized large file render
- drag selection and line-number selection
- comment annotations and commented-line marks
- find shortcuts and match navigation
- Diff mode:
- unified and split styles
- large diff fallback options
- diff selection normalization across sides
- comments and commented-line marks
- new find UX parity
- Media:
- image, audio, SVG, and binary states in file tabs
- image and audio diff previews in session review
- lazy load and error placeholders
- SSR:
- enterprise share page preloaded diffs hydrate correctly
- theme switching still updates hydrated diffs
### Regression focus
- Watch scroll restore behavior in `packages/app/src/pages/session/file-tabs.tsx`.
- Watch multi-instance find shortcut routing in screens with many viewers.
- Watch cleanup paths for listeners and virtualizers to avoid leaks.
---
## Manage risk
Keep wrappers and adapters in place until the unified path is proven.
### Key risks
- Selection regressions are the highest risk because text and diff have similar but not identical line semantics.
- SSR hydration can break subtly if client and SSR prop shapes drift.
- Shared find host state can misroute shortcuts when many viewers are mounted.
- Media consolidation can accidentally change placeholder timing or load behavior.
### Rollback strategy
- Land each phase in separate PRs or clearly separated commits on `dev`.
- If a phase regresses behavior, revert only that phase and keep earlier extractions.
- Keep `code.tsx`, `diff.tsx`, and `diff-ssr.tsx` wrappers intact until final verification, so a rollback only changes internals.
- If diff search is unstable, disable it behind the unified component while keeping the rest of the refactor.
---
## Order implementation
Follow this sequence to keep reviews small and reduce merge risk.
1. Finalize prop shape and file names for the unified component and context.
2. Extract shared diff normalization, theme sync, and render-ready helpers with no public API changes.
3. Extract shared selection controller and migrate `code.tsx` and `diff.tsx` to it.
4. Add the unified client `File` component and convert `code.tsx`/`diff.tsx` into wrappers.
5. Add `FileComponentProvider` and migrate provider wiring in `app.tsx` and enterprise share route.
6. Migrate consumer hooks (`file-tabs`, `session-review`, `message-part`, `session-turn`) to the unified context.
7. Extract and share find engine/UI, then enable search in diff mode.
8. Extract media helpers/UI and migrate `session-review.tsx` and `file-tabs.tsx`.
9. Add unified `file-ssr.tsx`, convert `diff-ssr.tsx` to a wrapper, and migrate enterprise imports.
10. Remove dead duplication and write a short migration note for future consumers.
---
## Decide open items
Resolve these before coding to avoid rework mid-refactor.
### API decisions
- Should the unified component require `mode`, or should it infer mode from props for convenience.
- Should the public export be named `File` only, or also ship a temporary alias like `UnifiedFile` for migration clarity.
- Should `preloadedDiff` live on the main `File` props or only on `file-ssr.tsx`.
### Search decisions
- Is DOM-only search acceptable for virtualized content in the first pass.
- Should find state reset on every rerender, or preserve query and index across diff style toggles.
### Media decisions
- Which placeholders and strings should stay consumer-owned versus shared in UI.
- Whether SVG should be treated as media-only, text-only, or a mixed mode with both preview and source.
- Whether video support should be included now or explicitly deferred.
### Migration decisions
- How long `CodeComponentProvider` and `DiffComponentProvider` should remain supported.
- Whether to migrate all consumers in one PR after wrappers land, or in follow-up PRs by surface area.
- Whether `diff-ssr` should remain as a permanent alias for compatibility.

View File

@@ -1,240 +0,0 @@
# Session Composer Refactor Plan
## Goal
Improve structure, ownership, and reuse for the bottom-of-session composer area without changing user-visible behavior.
Scope:
- `packages/ui/src/components/dock-prompt.tsx`
- `packages/app/src/components/session-todo-dock.tsx`
- `packages/app/src/components/question-dock.tsx`
- `packages/app/src/pages/session/session-prompt-dock.tsx`
- related shared UI in `packages/app/src/components/prompt-input.tsx`
## Decisions Up Front
1. **`session-prompt-dock` should stay route-scoped.**
It is session-page orchestration, so it belongs under `pages/session`, not global `src/components`.
2. **The orchestrator should keep blocking ownership.**
A single component should decide whether to show blockers (`question`/`permission`) or the regular prompt input. This avoids drift and duplicate logic.
3. **Current component does too much.**
Split state derivation, permission actions, and rendering into smaller units while preserving behavior.
4. **There is style duplication worth addressing.**
The prompt top shell and lower tray (`prompt-input.tsx`) visually overlap with dock shells/footers and todo containers. We should extract reusable dock surface primitives.
---
## Phase 0 (Mandatory Gate): Baseline E2E Coverage
No refactor work starts until this phase is complete and green locally.
### 0.1 Deterministic test harness
Add a test-only way to put a session into exact dock states, so tests do not rely on model/tool nondeterminism.
Proposed implementation:
- Add a guarded e2e route in backend (enabled only when a dedicated env flag is set by e2e-local runner).
- New route file: `packages/opencode/src/server/routes/e2e.ts`
- Mount from: `packages/opencode/src/server/server.ts`
- Gate behind env flag (for example `OPENCODE_E2E=1`) so this route is never exposed in normal runs.
- Add seed helpers in app e2e layer:
- `packages/app/e2e/actions.ts` (or `fixtures.ts`) helpers to:
- seed question request for a session
- seed permission request for a session
- seed/update todos for a session
- clear seeded blockers/todos
- Update e2e-local runner to set the flag:
- `packages/app/script/e2e-local.ts`
### 0.2 New e2e spec
Create a focused spec:
- `packages/app/e2e/session/session-composer-dock.spec.ts`
Test matrix (minimum required):
1. **Default prompt dock**
- no blocker state
- assert prompt input is visible and focusable
- assert blocker cards are absent
2. **Blocked question flow**
- seed question request for session
- assert question dock renders
- assert prompt input is not shown/active
- answer and submit
- assert unblock and prompt input returns
3. **Blocked permission flow**
- seed permission request with patterns + optional description
- assert permission dock renders expected actions
- assert prompt input is not shown/active
- test each response path (`once`, `always`, `reject`) across tests
- assert unblock behavior
4. **Todo dock transitions and collapse behavior**
- seed todos with `pending`/`in_progress`
- assert todo dock appears above prompt and can collapse/expand
- update todos to all completed/cancelled
- assert close animation path and eventual hide
5. **Keyboard focus behavior while blocked**
- with blocker active, typing from document context must not focus prompt input
- blocker actions remain keyboard reachable
Notes:
- Prefer stable selectors (`data-component`, `data-slot`, role/name).
- Extend `packages/app/e2e/selectors.ts` as needed.
- Use `expect.poll` for async transitions.
### 0.3 Gate commands (must pass before Phase 1)
Run from `packages/app` (never from repo root):
```bash
bun test:e2e:local -- e2e/session/session-composer-dock.spec.ts
bun test:e2e:local -- e2e/prompt/prompt.spec.ts e2e/prompt/prompt-multiline.spec.ts e2e/commands/input-focus.spec.ts
bun test:e2e:local
```
If any fail, stop and fix before refactor.
---
## Phase 1: Structural Refactor (No Intended Behavior Changes)
### 1.1 Colocate session-composer files
Create a route-local composer folder:
```txt
packages/app/src/pages/session/composer/
session-composer-region.tsx # rename/move from session-prompt-dock.tsx
session-composer-state.ts # derived state + actions
session-permission-dock.tsx # extracted from inline JSX
session-question-dock.tsx # moved from src/components/question-dock.tsx
session-todo-dock.tsx # moved from src/components/session-todo-dock.tsx
index.ts
```
Import updates:
- `packages/app/src/pages/session.tsx` imports `SessionComposerRegion` from `pages/session/composer`.
### 1.2 Split responsibilities
- Keep `session-composer-region.tsx` focused on rendering orchestration:
- blocker mode vs normal mode
- relative stacking (todo above prompt)
- handoff fallback rendering
- Move side-effect/business pieces into `session-composer-state.ts`:
- derive `questionRequest`, `permissionRequest`, `blocked`, todo visibility state
- permission response action + in-flight state
- todo close/open animation state
### 1.3 Remove duplicate blocked logic in `session.tsx`
Current `session.tsx` computes `blocked` independently. Make the composer state the single source for blocker status consumed by both:
- page-level keydown autofocus guard
- composer rendering guard
### 1.4 Keep prompt gating in orchestrator
`session-composer-region` should remain responsible for choosing whether `PromptInput` renders when blocked.
Rationale:
- this is layout-mode orchestration, not prompt implementation detail
- keeps blocker and prompt transitions coordinated in one place
### 1.5 Phase 1 acceptance criteria
- No intentional behavior deltas.
- Phase 0 suite remains green.
- `session-prompt-dock` no longer exists as a large mixed-responsibility component.
- Session composer files are colocated under `pages/session/composer`.
---
## Phase 2: Reuse + Styling Maintainability
### 2.1 Extract shared dock surface primitives
Create reusable shell/tray wrappers to remove repeated visual scaffolding:
- primary elevated surface (prompt top shell / dock body)
- secondary tray surface (prompt bottom bar / dock footer / todo shell)
Proposed targets:
- `packages/ui/src/components` for shared primitives if reused by both app and ui components
- or `packages/app/src/pages/session/composer` first, then promote to ui after proving reuse
### 2.2 Apply primitives to current components
Adopt in:
- `packages/app/src/components/prompt-input.tsx`
- `packages/app/src/pages/session/composer/session-todo-dock.tsx`
- `packages/ui/src/components/dock-prompt.tsx` (where appropriate)
Focus on deduping patterns seen in:
- prompt elevated shell styles (`prompt-input.tsx` form container)
- prompt lower tray (`prompt-input.tsx` bottom panel)
- dock prompt footer/body and todo dock container
### 2.3 De-risk style ownership
- Move dock-specific styling out of overly broad files (for example, avoid keeping new dock-specific rules buried in unrelated message-part styling files).
- Keep slot names stable unless tests are updated in the same PR.
### 2.4 Optional follow-up (if low risk)
Evaluate extracting shared question/permission presentational pieces used by:
- `packages/app/src/pages/session/composer/session-question-dock.tsx`
- `packages/ui/src/components/message-part.tsx`
Only do this if behavior parity is protected by tests and the change is still reviewable.
### 2.5 Phase 2 acceptance criteria
- Reduced duplicated shell/tray styling code.
- No regressions in blocker/todo/prompt transitions.
- Phase 0 suite remains green.
---
## Implementation Sequence (single branch)
1. **Step A - Baseline safety net**
- Add e2e harness + new session composer dock spec + selector/helpers.
- Must pass locally before any refactor work proceeds.
2. **Step B - Phase 1 colocation/splitting**
- Move/rename files, extract state and permission component, keep behavior.
3. **Step C - Phase 1 dedupe blocked source**
- Remove duplicate blocked derivation and wire page autofocus guard to shared source.
4. **Step D - Phase 2 style primitives**
- Introduce shared surface primitives and migrate prompt/todo/dock usage.
5. **Step E (optional) - shared question/permission presentational extraction**
---
## Rollback Strategy
- Keep each step logically isolated and easy to revert.
- If regressions occur, revert the latest completed step first and rerun the Phase 0 suite.
- If style extraction destabilizes behavior, keep structural Phase 1 changes and revert only Phase 2 styling commits.

View File

@@ -1,234 +0,0 @@
# Session Review Cross-Diff Search Plan
One search input for all diffs in the review pane
---
## Goal
Add a single search UI to `SessionReview` that searches across all diff files in the accordion and supports next/previous navigation across files.
Navigation should auto-open the target accordion item and reveal the active match inside the existing unified `File` diff viewer.
---
## Non-goals
- Do not change diff rendering visuals, line comments, or file selection behavior.
- Do not add regex, fuzzy search, or replace.
- Do not change `@pierre/diffs` internals.
---
## Current behavior
- `SessionReview` renders one `File` diff viewer per accordion item, but only mounts the viewer when that item is expanded.
- Large diffs may be blocked behind the `MAX_DIFF_CHANGED_LINES` gate until the user clicks "render anyway".
- `File` owns a local search engine (`createFileFind`) with:
- query state
- hit counting
- current match index
- highlighting (CSS Highlight API or overlay fallback)
- `Cmd/Ctrl+F` and `Cmd/Ctrl+G` keyboard handling
- `FileSearchBar` is currently rendered per viewer.
- There is no parent-level search state in `SessionReview`.
---
## UX requirements
- Add one search bar in the `SessionReview` header (input, total count, prev, next, close).
- Show a global count like `3/17` across all searchable diffs.
- `Cmd/Ctrl+F` inside the session review pane opens the session-level search.
- `Cmd/Ctrl+G`, `Shift+Cmd/Ctrl+G`, `Enter`, and `Shift+Enter` navigate globally.
- Navigating to a match in a collapsed file auto-expands that file.
- The active match scrolls into view and is highlighted in the target viewer.
- Media/binary diffs are excluded from search.
- Empty query clears highlights and resets to `0/0`.
---
## Architecture proposal
Use a hybrid model:
- A **session-level match index** for global searching/counting/navigation across all diffs.
- The existing **per-viewer search engine** for local highlighting and scrolling in the active file.
This avoids mounting every accordion item just to search while reusing the existing DOM highlight behavior.
### High-level pieces
- `SessionReview` owns the global query, hit list, and active hit index.
- `File` exposes a small controlled search handle (register, set query, clear, reveal hit).
- `SessionReview` keeps a map of mounted file viewers and their search handles.
- `SessionReview` resolves next/prev hits, expands files as needed, then tells the target viewer to reveal the hit.
---
## Data model and interfaces
```ts
type SessionSearchHit = {
file: string
side: "additions" | "deletions"
line: number
col: number
len: number
}
type SessionSearchState = {
query: string
hits: SessionSearchHit[]
active: number
}
```
```ts
type FileSearchReveal = {
side: "additions" | "deletions"
line: number
col: number
len: number
}
type FileSearchHandle = {
setQuery: (value: string) => void
clear: () => void
reveal: (hit: FileSearchReveal) => boolean
refresh: () => void
}
```
```ts
type FileSearchControl = {
shortcuts?: "global" | "disabled"
showBar?: boolean
register: (handle: FileSearchHandle | null) => void
}
```
---
## Integration steps
### Phase 1: Expose controlled search on `File`
- Extend `createFileFind` and `File` to support a controlled search handle.
- Keep existing per-viewer search behavior as the default path.
- Add a way to disable per-viewer global shortcuts when hosted inside `SessionReview`.
#### Acceptance
- `File` still supports local search unchanged by default.
- `File` can optionally register a search handle and accept controlled reveal calls.
### Phase 2: Add session-level search state in `SessionReview`
- Add a single search UI in the `SessionReview` header (can reuse `FileSearchBar` visuals or extract shared presentational pieces).
- Build a global hit list from `props.diffs` string content.
- Index hits by file/side/line/column/length.
#### Acceptance
- Header search appears once for the pane.
- Global hit count updates as query changes.
- Media/binary diffs are excluded.
### Phase 3: Wire global navigation to viewers
- Register a `FileSearchHandle` per mounted diff viewer.
- On next/prev, resolve the active global hit and:
1. expand the target file if needed
2. wait for the viewer to mount/render
3. call `handle.setQuery(query)` and `handle.reveal(hit)`
#### Acceptance
- Next/prev moves across files.
- Collapsed targets auto-open.
- Active match is highlighted in the target diff.
### Phase 4: Handle large-diff gating
- Lift `render anyway` state from local accordion item state into a file-keyed map in `SessionReview`.
- If navigation targets a gated file, force-render it before reveal.
#### Acceptance
- Global search can navigate into a large diff without manual user expansion/render.
### Phase 5: Keyboard and race-condition polish
- Route `Cmd/Ctrl+F`, `Cmd/Ctrl+G`, `Shift+Cmd/Ctrl+G` to session search when focus is in the review pane.
- Add token/cancel guards so fast navigation does not reveal stale targets after async mounts.
#### Acceptance
- Keyboard shortcuts consistently target session-level search.
- No stale reveal jumps during rapid navigation.
---
## Edge cases
- Empty query: clear all viewer highlights, reset count/index.
- No results: keep the search bar open, disable prev/next.
- Added/deleted files: index only the available side.
- Collapsed files: queue reveal until `onRendered` fires.
- Large diffs: auto-force render before reveal.
- Split diff mode: handle duplicate text on both sides without losing side info.
- Do not clear line comment draft or selected lines when navigating search results.
---
## Testing plan
### Unit tests
- Session hit-index builder:
- line/column mapping
- additions/deletions side tagging
- wrap-around next/prev behavior
- `File` controlled search handle:
- `setQuery`
- `clear`
- `reveal` by side/line/column in unified and split diff
### Component / integration tests
- Search across multiple diffs and navigate across collapsed accordion items.
- Global counter updates correctly (`current/total`).
- Split and unified diff styles both navigate correctly.
- Large diff target auto-renders on navigation.
- Existing line comment draft remains intact while searching.
### Manual verification
- `Cmd/Ctrl+F` opens session-level search in the review pane.
- `Cmd/Ctrl+G` / `Shift+Cmd/Ctrl+G` navigate globally.
- Highlighting and scroll behavior stay stable with many open diffs.
---
## Risks and rollback
### Key risks
- Global index and DOM highlights can drift if line/column mapping does not match viewer DOM content exactly.
- Keyboard shortcut conflicts between session-level search and per-viewer search.
- Performance impact when indexing many large diffs in one session.
### Rollback plan
- Gate session-level search behind a `SessionReview` prop/flag during rollout.
- If unstable, disable the session-level path and keep existing per-viewer search unchanged.
---
## Open questions
- Should search match file paths as well as content, or content only?
- In split mode, should the same text on both sides count as two matches?
- Should auto-navigation into gated large diffs silently render them, or show a prompt first?
- Should the session-level search bar reuse `FileSearchBar` directly or split out a shared non-portal variant?