diff --git a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx index cefef208de4..fcd66ec5958 100644 --- a/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx +++ b/packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx @@ -37,6 +37,7 @@ export type PromptProps = { sessionID?: string visible?: boolean disabled?: boolean + maxHeight?: number onSubmit?: () => void ref?: (ref: PromptRef) => void hint?: JSX.Element @@ -821,7 +822,7 @@ export function Prompt(props: PromptProps) { textColor={keybind.leader ? theme.textMuted : theme.text} focusedTextColor={keybind.leader ? theme.textMuted : theme.text} minHeight={1} - maxHeight={6} + maxHeight={props.maxHeight ?? 6} onContentChange={() => { const value = input.plainText setStore("prompt", "input", value) diff --git a/packages/opencode/src/cli/cmd/tui/routes/home.tsx b/packages/opencode/src/cli/cmd/tui/routes/home.tsx index c011f6c6246..0210b28eb33 100644 --- a/packages/opencode/src/cli/cmd/tui/routes/home.tsx +++ b/packages/opencode/src/cli/cmd/tui/routes/home.tsx @@ -2,6 +2,7 @@ import { Prompt, type PromptRef } from "@tui/component/prompt" import { createMemo, Match, onMount, Show, Switch } from "solid-js" import { useTheme } from "@tui/context/theme" import { useKeybind } from "@tui/context/keybind" +import { useTerminalDimensions } from "@opentui/solid" import { Logo } from "../component/logo" import { Tips } from "../component/tips" import { Locale } from "@/util/locale" @@ -22,6 +23,7 @@ export function Home() { const sync = useSync() const kv = useKV() const { theme } = useTheme() + const terminal = useTerminalDimensions() const route = useRouteData("home") const promptRef = usePromptRef() const command = useCommandDialog() @@ -91,29 +93,85 @@ export function Home() { const keybind = useKeybind() + const initialPrompt = createMemo(() => sync.data.config.tui?.initial_prompt) + const size = createMemo(() => initialPrompt()?.size ?? "compact") + const width = createMemo(() => { + const value = initialPrompt()?.width_percent + if (value) return Math.max(40, Math.min(100, value)) + if (size() === "medium") return 80 + if (size() === "large") return 90 + return 75 + }) + const maxWidth = createMemo(() => { + if (size() === "compact" && !initialPrompt()?.width_percent) return 75 + const available = Math.max(50, terminal().width - 8) + return Math.max(50, Math.min(available, Math.floor((terminal().width * width()) / 100))) + }) + const height = createMemo(() => { + const value = initialPrompt()?.height_percent + if (value) return Math.max(10, Math.min(60, value)) + if (size() === "medium") return 25 + if (size() === "large") return 35 + return 0 + }) + const maxHeight = createMemo(() => { + if (size() === "compact" && !initialPrompt()?.height_percent) return 6 + const available = Math.max(6, terminal().height - 18) + return Math.max(6, Math.min(available, Math.floor((terminal().height * height()) / 100))) + }) + const top = createMemo(() => { + if (size() === "medium") return 2 + if (size() === "large") return 1 + return 4 + }) + const tipsHeight = createMemo(() => { + if (size() === "medium") return 3 + if (size() === "large") return 2 + return 4 + }) + const tipsPadding = createMemo(() => { + if (size() === "compact") return 3 + if (size() === "medium") return 2 + return 1 + }) + return ( <> - + - + { prompt = r promptRef.set(r) }} hint={Hint} + maxHeight={maxHeight()} /> - + + + + home prompt size: {size()} + + diff --git a/packages/opencode/src/config/config.ts b/packages/opencode/src/config/config.ts index 261731b8b0a..3f4b2f50985 100644 --- a/packages/opencode/src/config/config.ts +++ b/packages/opencode/src/config/config.ts @@ -930,6 +930,29 @@ export namespace Config { .enum(["auto", "stacked"]) .optional() .describe("Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column"), + initial_prompt: z + .object({ + size: z + .enum(["compact", "medium", "large"]) + .optional() + .describe("Preset size for the opening home-screen prompt composer"), + width_percent: z + .number() + .int() + .min(40) + .max(100) + .optional() + .describe("Override opening home-screen prompt width as terminal percentage (40-100)"), + height_percent: z + .number() + .int() + .min(10) + .max(60) + .optional() + .describe("Override opening home-screen prompt max input height as terminal percentage (10-60)"), + }) + .optional() + .describe("Opening home-screen prompt sizing preferences"), }) export const Server = z diff --git a/packages/opencode/test/config/config.test.ts b/packages/opencode/test/config/config.test.ts index 91b87f6498c..00695e6e6b6 100644 --- a/packages/opencode/test/config/config.test.ts +++ b/packages/opencode/test/config/config.test.ts @@ -333,6 +333,78 @@ test("handles command configuration", async () => { }) }) +test("handles tui initial prompt configuration", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await writeConfig(dir, { + $schema: "https://opencode.ai/config.json", + tui: { + initial_prompt: { + size: "large", + width_percent: 92, + height_percent: 38, + }, + }, + }) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const config = await Config.get() + expect(config.tui?.initial_prompt).toEqual({ + size: "large", + width_percent: 92, + height_percent: 38, + }) + }, + }) +}) + +test("rejects invalid tui initial prompt size", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await writeConfig(dir, { + $schema: "https://opencode.ai/config.json", + tui: { + initial_prompt: { + size: "huge", + }, + }, + }) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await expect(Config.get()).rejects.toThrow() + }, + }) +}) + +test("rejects invalid tui initial prompt percentages", async () => { + await using tmp = await tmpdir({ + init: async (dir) => { + await writeConfig(dir, { + $schema: "https://opencode.ai/config.json", + tui: { + initial_prompt: { + size: "medium", + width_percent: 120, + height_percent: 5, + }, + }, + }) + }, + }) + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await expect(Config.get()).rejects.toThrow() + }, + }) +}) + test("migrates autoshare to share field", async () => { await using tmp = await tmpdir({ init: async (dir) => { diff --git a/packages/sdk/js/src/v2/gen/types.gen.ts b/packages/sdk/js/src/v2/gen/types.gen.ts index efb7e202e12..e558189c9c4 100644 --- a/packages/sdk/js/src/v2/gen/types.gen.ts +++ b/packages/sdk/js/src/v2/gen/types.gen.ts @@ -1699,6 +1699,23 @@ export type Config = { * Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column */ diff_style?: "auto" | "stacked" + /** + * Opening home-screen prompt sizing preferences + */ + initial_prompt?: { + /** + * Preset size for the opening home-screen prompt composer + */ + size?: "compact" | "medium" | "large" + /** + * Override opening home-screen prompt width as terminal percentage (40-100) + */ + width_percent?: number + /** + * Override opening home-screen prompt max input height as terminal percentage (10-60) + */ + height_percent?: number + } } server?: ServerConfig /** diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index 85a1af9d70c..68d9d226ea0 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -9655,6 +9655,29 @@ "description": "Control diff rendering style: 'auto' adapts to terminal width, 'stacked' always shows single column", "type": "string", "enum": ["auto", "stacked"] + }, + "initial_prompt": { + "description": "Opening home-screen prompt sizing preferences", + "type": "object", + "properties": { + "size": { + "description": "Preset size for the opening home-screen prompt composer", + "type": "string", + "enum": ["compact", "medium", "large"] + }, + "width_percent": { + "description": "Override opening home-screen prompt width as terminal percentage (40-100)", + "type": "integer", + "minimum": 40, + "maximum": 100 + }, + "height_percent": { + "description": "Override opening home-screen prompt max input height as terminal percentage (10-60)", + "type": "integer", + "minimum": 10, + "maximum": 60 + } + } } } }, diff --git a/packages/web/src/content/docs/config.mdx b/packages/web/src/content/docs/config.mdx index eeccde2f791..f07b24df4c8 100644 --- a/packages/web/src/content/docs/config.mdx +++ b/packages/web/src/content/docs/config.mdx @@ -164,7 +164,12 @@ You can configure TUI-specific settings through the `tui` option. "scroll_acceleration": { "enabled": true }, - "diff_style": "auto" + "diff_style": "auto", + "initial_prompt": { + "size": "medium", + "width_percent": 80, + "height_percent": 25 + } } } ``` @@ -174,6 +179,11 @@ Available options: - `scroll_acceleration.enabled` - Enable macOS-style scroll acceleration. **Takes precedence over `scroll_speed`.** - `scroll_speed` - Custom scroll speed multiplier (default: `3`, minimum: `1`). Ignored if `scroll_acceleration.enabled` is `true`. - `diff_style` - Control diff rendering. `"auto"` adapts to terminal width, `"stacked"` always shows single column. +- `initial_prompt.size` - Preset size for the opening home-screen prompt composer. Values: `"compact"` (current behavior), `"medium"`, `"large"`. +- `initial_prompt.width_percent` - Optional opening-screen width override (percentage of terminal width, `40`-`100`). +- `initial_prompt.height_percent` - Optional opening-screen max input height override (percentage of terminal height, `10`-`60`). + +`initial_prompt` only affects the opening home screen prompt. Session prompt behavior is unchanged. [Learn more about using the TUI here](/docs/tui). diff --git a/packages/web/src/content/docs/tui.mdx b/packages/web/src/content/docs/tui.mdx index 085eb6169f8..46763ec38a8 100644 --- a/packages/web/src/content/docs/tui.mdx +++ b/packages/web/src/content/docs/tui.mdx @@ -364,6 +364,11 @@ You can customize TUI behavior through your OpenCode config file. "scroll_speed": 3, "scroll_acceleration": { "enabled": true + }, + "initial_prompt": { + "size": "medium", + "width_percent": 80, + "height_percent": 25 } } } @@ -373,6 +378,13 @@ You can customize TUI behavior through your OpenCode config file. - `scroll_acceleration` - Enable macOS-style scroll acceleration for smooth, natural scrolling. When enabled, scroll speed increases with rapid scrolling gestures and stays precise for slower movements. **This setting takes precedence over `scroll_speed` and overrides it when enabled.** - `scroll_speed` - Controls how fast the TUI scrolls when using scroll commands (minimum: `1`). Defaults to `3`. **Note: This is ignored if `scroll_acceleration.enabled` is set to `true`.** +- `initial_prompt.size` - Preset opening-screen prompt size: `compact`, `medium`, or `large`. +- `initial_prompt.width_percent` - Optional opening-screen width override as percent of terminal width (`40`-`100`). +- `initial_prompt.height_percent` - Optional opening-screen input max-height override as percent of terminal height (`10`-`60`). + +`compact` preserves the current default opening prompt behavior. + +`initial_prompt` only affects the opening home screen prompt. Session prompt behavior is unchanged. --- diff --git a/specs/bd-2lf-tui-home-prompt-sizing-plan.md b/specs/bd-2lf-tui-home-prompt-sizing-plan.md new file mode 100644 index 00000000000..29ad0610c07 --- /dev/null +++ b/specs/bd-2lf-tui-home-prompt-sizing-plan.md @@ -0,0 +1,219 @@ +# bd-2lf - TUI Home Prompt Sizing + UX (Single PR) + +## Status + +| Scope | Status | Owner | Notes | +| ------------------------------------------------ | --------- | ----- | ---------------------------------------------------------------- | +| Epic A - Config contract + behavior | completed | build | Add `tui.initial_prompt` schema, defaults, and resolution rules | +| Epic B - Home screen layout sizing | completed | build | Replace fixed width/height on opening screen | +| Epic C - UX quality improvements (same PR) | completed | build | Better context visibility + clearer affordances on home composer | +| Epic D - Validation + docs + generated artifacts | completed | build | Tests, docs, SDK/OpenAPI regeneration | +| Epic E - Issue/PR packaging for `dev` | planned | build | Single PR with rationale, verification, and UX notes | + +Current branch/worktree for WT flow: + +- Branch: `opencode-initial-prompt-sizing` +- Worktree: `../opencode-initial-prompt-sizing` +- Tracking issue: `bd-2lf` + +## Problem Statement + +The opening (home) prompt area in the TUI is too constrained for longer planning prompts. + +Observed hard constraints in code: + +- Home prompt container width is fixed to `maxWidth={75}` in `packages/opencode/src/cli/cmd/tui/routes/home.tsx`. +- Opening layout also uses fixed vertical spacing that contributes to the compact feel in `packages/opencode/src/cli/cmd/tui/routes/home.tsx`. +- Prompt textarea max height is fixed to `maxHeight={6}` in `packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx`. + +This causes prompt authoring friction for large planning prompts where users need more visible context while typing. + +## Goals + +1. Make opening-screen prompt size configurable through user config. +2. Provide presets: `compact`, `medium`, `large`. +3. Keep `compact` behavior equal to current defaults. +4. Allow optional percent overrides for width and height. +5. Include UX improvements in the same PR (not a follow-up PR). +6. Keep scope focused: apply sizing behavior to home/opening screen first. + +## Non-Goals (for this PR) + +- Do not change session prompt dock sizing behavior in web app. +- Do not redesign the full TUI home page layout. +- Do not add interactive runtime settings UI (command palette toggles can be follow-up). + +## Assumptions + +1. `compact` must remain visually equivalent to current behavior. +2. Presets should work across small and large terminal sizes with clamped values. +3. Configuration should be stable (under `tui`), not experimental. +4. UX improvements should be low-risk and preserve established TUI visual language. +5. Single PR should include code, tests, docs, and generated SDK/OpenAPI updates. + +## OpenCode Observed Standards (from this repo) + +1. Config schema is centralized in `packages/opencode/src/config/config.ts`. +2. TUI settings currently live under `tui` (`scroll_speed`, `scroll_acceleration`, `diff_style`). +3. Public config changes require generated artifacts updates: + - `packages/sdk/openapi.json` + - `packages/sdk/js/src/v2/gen/*` + - `packages/sdk/js/src/gen/*` +4. Docs include both config and tui guides: + - `packages/web/src/content/docs/config.mdx` + - `packages/web/src/content/docs/tui.mdx` +5. Config parsing/validation coverage is in `packages/opencode/test/config/config.test.ts`. + +## Proposed Config Contract + +```jsonc +{ + "$schema": "https://opencode.ai/config.json", + "tui": { + "initial_prompt": { + "size": "medium", + "width_percent": 80, + "height_percent": 25, + }, + }, +} +``` + +### Presets + +- `compact` (current): width behaves like current `75` col cap, input max height behaves like current `6` rows. +- `medium`: width `80%` of terminal, input max height `25%` of terminal. +- `large`: width `90%` of terminal, input max height `35%` of terminal. + +### Resolution + Clamping Rules + +1. Start from preset defaults. +2. Apply `width_percent`/`height_percent` overrides when provided. +3. Clamp final values to safe bounds for small terminals. +4. Preserve readable minimums so UX remains usable in narrow terminals. + +## Epic/Task Breakdown + +### Epic A - Config Contract + Resolution Logic + +Status: completed + +Tasks: + +- [x] A1. Add `tui.initial_prompt` schema in `packages/opencode/src/config/config.ts`. +- [x] A2. Add enum + numeric bounds + descriptions for all fields. +- [x] A3. Add small helper/resolver for preset + override + clamp logic (kept local to TUI route/component layer unless reused). +- [x] A4. Ensure defaults map exactly to current behavior for `compact`. + +Acceptance criteria: + +- Config accepts all valid shapes and rejects invalid enum/range values. +- `compact` produces same effective home prompt dimensions as today. + +### Epic B - Opening Screen Sizing Implementation + +Status: completed + +Tasks: + +- [x] B1. Update `packages/opencode/src/cli/cmd/tui/routes/home.tsx` to compute opening layout width from resolved size config. +- [x] B2. Replace fixed home `maxWidth={75}` usage with resolved width behavior. +- [x] B3. Update prompt component API (`packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx`) to accept optional max input height override. +- [x] B4. Apply height override only when prompt is used on home route. + +Acceptance criteria: + +- Home prompt width/height responds to presets + overrides. +- Session route prompt behavior is unchanged by default. + +### Epic C - UX Improvements in Same PR + +Status: completed + +Tasks: + +- [x] C1. Improve home composer readability for long prompts (vertical breathing room and clearer visibility of active typing area). +- [x] C2. Ensure helper/tips/hint layout remains balanced at compact, medium, and large sizes. +- [x] C3. Add subtle textual cue in home context about active size preset when not compact (if space allows). + +Acceptance criteria: + +- Users can keep more planning context in view without breaking home layout hierarchy. +- UX remains clean and consistent with current TUI style. + +### Epic D - Validation, Docs, and Generated Artifacts + +Status: completed + +Tasks: + +- [x] D1. Add/adjust tests in `packages/opencode/test/config/config.test.ts` for schema validation and parsing. +- [x] D2. Regenerate SDK/OpenAPI via `./packages/sdk/js/script/build.ts`. +- [x] D3. Update docs: + - `packages/web/src/content/docs/config.mdx` + - `packages/web/src/content/docs/tui.mdx` +- [x] D4. Validate formatting/lint/test commands from package directories (not repo root). + +Acceptance criteria: + +- Tests cover new config shape and guardrails. +- Generated files include new config fields. +- Docs describe presets, overrides, defaults, and scope (home screen). + +### Epic E - WT Flow E2E Delivery to `dev` + +Status: in_progress + +Tasks: + +- [x] E1. Create dedicated worktree + branch. +- [x] E2. Create and set issue in progress (`bd-2lf`). +- [x] E3. Implement Epics A-D on this worktree. +- [x] E4. Run verification commands and capture outputs. +- [ ] E5. Prepare issue text + PR text aligned with repo template. + +Acceptance criteria: + +- Single focused PR against `dev` includes implementation, docs, tests, and generated artifacts. +- PR description explains why this improves planning UX and how behavior is preserved for compact users. + +## Validation Plan + +Run from package directories only: + +1. Config tests (targeted): + - `bun test test/config/config.test.ts` (from `packages/opencode`) +2. TUI package checks: + - `bun run typecheck` (from `packages/opencode`) + - `bun run test` (from `packages/opencode`) if change set is broader than config-only +3. SDK/OpenAPI regeneration: + - `./packages/sdk/js/script/build.ts` (from repo root) +4. Docs build/lint check (if required by maintainers): + - Run docs validation command used by repo maintainers for `packages/web`. + +Manual UX validation: + +- Open TUI on multiple terminal sizes. +- Verify `compact`, `medium`, `large` with and without percent overrides. +- Confirm home screen remains usable with tips shown/hidden. +- Confirm regular in-session prompt behavior remains unchanged. + +## Definition of Done (Repo-Culture Aligned) + +- [x] Behavior is configurable via stable `tui` config keys. +- [x] Compact mode preserves current behavior. +- [x] Medium/large materially improve long planning prompt authoring. +- [x] No regression in non-home prompt flows. +- [x] Tests pass for changed scope. +- [x] Generated SDK/OpenAPI files are updated and committed. +- [x] Docs updated with clear examples and precedence rules. +- [ ] PR targets `dev`, explains why and how, and includes verification notes. + +## Risks and Mitigations + +- Risk: small terminals produce unusable layout. + - Mitigation: clamp width/height and preserve minimum readable values. +- Risk: accidental behavior changes in session prompt. + - Mitigation: pass sizing overrides only from home route. +- Risk: docs and generated artifacts drift. + - Mitigation: treat regeneration + docs updates as required gates.