Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ file is the human-readable summary of what changed and why.

### Sidebar

- **Editor context moved to the sidebar** — the open editor file and selected
line range (e.g. `footer.tsx#42-58`) no longer sit in the prompt footer's
right-side group. They render in the sidebar footer instead, just above the
`cwd:branch` line, marked with a bullet that is accented while the selection
is still pending and muted once it has been sent. With the sidebar hidden the
label is not shown at all.

- **Hidden-sidebar hint shows while busy** — the `ctrl+b N todos` hint from
[#69](https://github.com/quickbeard/codev-code/pull/69) now also renders while
a turn is running (next to `esc interrupt`), not just when the session is
Expand Down
3 changes: 3 additions & 0 deletions DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ from the squash message first, or use a plain merge.
| `packages/opencode/src/cli/ui.ts` | `wordmark`/`logo()` render the lowercase codev wordmark via `ansiWordmark` (bold brand teal + drop shadow on TTY) | Help banner matches the TUI home banner. | Keep our wordmark + logo(); take upstream's surrounding changes. |
| `packages/tui/src/util/presentation.ts` | Local logo copy replaced by the shared `ansiWordmark` (brand teal + drop shadow) | Presentation-mode wordmark was still upstream-branded. | Keep our wordmark lines; take upstream's surrounding changes. |
| `packages/tui/src/feature-plugins/sidebar/footer.tsx` | Removed the footer's brand + version line entirely (upstream's two-tone "Open"+"Code" spans, status dot, and `app.version`); the `cwd:branch` path is now the last footer item | The fork's sidebar doesn't advertise the product name/version; the path takes that bottom slot. (Supersedes the earlier single-"CoDev"-span rebrand of this line.) | Keep the line deleted; take upstream's surrounding changes. |
| `packages/tui/src/feature-plugins/sidebar/footer.tsx` | Added the editor-context label above the `cwd:branch` line: `useEditorLabel()` (fork-only `src/context/editor-label.tsx`) supplies the effective selection, an `editorFile` memo formats it via `editorContextFileLabel(...)` at a fixed width of 36 (the sidebar's 42 columns less 2 of padding a side and 2 for the bullet), and `<EditorContextLabel>` renders it — both from the fork-only `./editor-context-label.tsx`. | The open file + selected range belong with the sidebar's other ambient session state rather than in the prompt footer's crowded keybind-hint row; they sit next to the cwd for a single "where am I working" group. Moved out of the prompt footer, which no longer shows it at all. | Keep the `useEditorLabel` import, the `editorFile` memo, and the `<EditorContextLabel>` element as the box's first child; take upstream's changes to the path display below it. |
| `packages/tui/src/feature-plugins/sidebar/lsp.tsx` | Whole LSP section wrapped in `<Show when={!off()}>` so it renders nothing when `config.lsp` is falsy; the "LSPs are disabled" placeholder text removed (empty state is only "LSPs will activate as files are read") | A disabled feature shouldn't occupy sidebar space with a permanent placeholder. | Keep our `Show` wrapper + removed placeholder; take upstream's surrounding changes. |
| `packages/opencode/src/cli/cmd/*.ts` (tui, run, serve, attach, pr, upgrade, uninstall, web) | Help `describe` strings "opencode" → "codev" | `--help` is the first thing users read. | Keep our strings. |
| `packages/tui/src/app.tsx` | Terminal title "OpenCode"/"OC \|" → "CoDev Code"/"CC \|"; the configure-gate effect is a stateless forced-setup invariant — while `sync.status === "complete" && !useConnected() && dialog.stack.length === 0` it (re)opens the fork's `DialogConfigure` (component/dialog-configure.tsx, new file), so escaping any gate-launched flow or a credential-only save bounces back to the gate instead of an unconfigured chat (gates on `!useConnected()`, not `provider.length === 0` — hosted Zen free models always register); added the `codev.configure` command (`/configure`) and a `CODEV_CONFIGURE=1` mount hook for `codev configure`; home screen skipped — plain startup seeds the `dummy` session route and auto-opens a new empty session (reusing a leftover default-titled empty one via `openNewSession`/`findEmptySession`), `session.new` and the current-session `session.deleted` handler open a new session instead of navigating home, and the `-c` resolver prefers non-default-titled sessions so it lands on the last real conversation; added the `app.exit.confirm` command + `EXIT_CONFIRM_WINDOW` const (double-press Ctrl+C to quit: first press shows a toast, second within 1.5 s exits) and gathered its `app_exit_confirm` binding alongside `app_exit` | Tab title branding; first-run setup is a forced gate the user can't escape into an unconfigured chat (the invariant reopens it); `codev` starts directly in the session layout instead of the home screen; a stray Ctrl+C no longer quits on the first press. | Keep our strings, the invariant gate effect, dialog swap + command block, route seed, `openNewSession`/`findEmptySession` + call sites, the `-c` default-title filter, and the `app.exit.confirm` command + binding; take upstream's surrounding changes. |
Expand All @@ -116,6 +117,8 @@ from the squash message first, or use a plain merge.
| `packages/tui/src/component/prompt/index.tsx` | Gated the prompt footer's model/provider indicator on `useConnected()` (the shared "a real provider is configured" predicate) | Don't advertise the free hosted Zen default (e.g. "Big Pickle · OpenCode Zen") in the footer while nothing is configured — same rationale and predicate as `app.tsx`'s startup gate and the provider/model filters. A configured gateway/real provider, or a deliberately-authenticated opencode/opencode-go, still shows its model. | Keep the extra `Show` condition; take upstream's surrounding changes. |
| `packages/tui/src/component/prompt/index.tsx` | Added a `directoryLabel` memo (abbreviated cwd, `:branch` suffix when known, middle-truncated; last path segment + branch in text color, parent path muted, matching the sidebar footer) rendered as the first item of the prompt footer's right-side group, left of the editor open-file label. The memo delegates to `directoryFooterLabel(...)` and the render goes through a `<DirectoryLabel ... hidden={props.sidebarVisible}>` component — both in the fork-only `./directory-label.tsx`. `Prompt` takes a `sidebarVisible?: boolean` prop that `routes/session/index.tsx` passes from its `sidebarVisible()` memo. | Keep the working directory and branch visible at a glance from the prompt (the home-footer `Directory` only shows once a destination is selected, and the sidebar — the other place showing the branch — is hidden by default), but suppress the label while the sidebar is visible since the sidebar footer already shows the same `cwd:branch`. | Keep the `directoryLabel` memo, the `directoryFooterLabel`/`DirectoryLabel` imports from the fork-only `./directory-label.tsx`, the `sidebarVisible` prop + the `hidden={props.sidebarVisible}` gate on `<DirectoryLabel>`, and the `sidebarVisible={sidebarVisible()}` prop on `<Prompt>`; take upstream's surrounding footer changes. |
| `packages/tui/src/component/prompt/index.tsx` | The busy-state footer arm (`status().type !== "idle"`) also renders `props.hint` after the `esc interrupt` text, gated on `status().type !== "retry"` | The hidden-sidebar hint (see the `routes/session/index.tsx` hint row) only rendered in the footer `Switch`'s idle fallback arm, so it vanished for the whole duration of a running turn — exactly when todos are being created. Retry keeps its `space-between` error row uncluttered. | Keep the `<Show when={status().type !== "retry"}>{props.hint}</Show>` line in the busy `Match` arm (additive); take upstream's surrounding changes. |
| `packages/tui/src/component/prompt/index.tsx` | Removed the editor open-file label from the footer's right-side group and lifted the state behind it out of `Prompt`: the local `dismissedEditorSelectionKey` signal and the `editorContext`/`editorPath`/`editorSelectionLabel`/`editorFileLabel`/`editorFileLabelDisplay`/`editorContextLabelState` memos are gone, replaced by `useEditorLabel()` (fork-only `src/context/editor-label.tsx`) — `selection()` feeds the `prompt.editor_context.clear` `enabled` predicate and the submit-time editor part, `state()` replaces `editor.labelState()` there, and `dismiss()` replaces the local `dismissEditorContext`. The pure helpers `hasEditorRangeSelection`/`getEditorRangeLabel` moved to that context module and are imported back for `formatEditorContext`. | The label now lives in the sidebar footer (see its row above). The dismissal state had to move with it: the sidebar must label the *effective* selection — the one this file actually attaches on submit — or it would advertise context the prompt has dismissed and won't send. | Keep the deletions, the `useEditorLabel` import + its three call sites, and the helper import from `../../context/editor-label`; take upstream's surrounding changes. If upstream reworks the editor-context label, apply the change to `editorContextFileLabel` in the fork-only sidebar module rather than restoring the footer render. |
| `packages/tui/src/app.tsx` | Mounted `<EditorLabelProvider>` (fork-only `./context/editor-label`) immediately inside `<EditorContextProvider>` | The effective-editor-selection state is shared by the prompt and the sidebar footer, so it has to be provided above both. | Keep the import and the wrapper; take upstream's surrounding provider changes. If upstream moves `EditorContextProvider`, keep ours directly inside it. |
| `packages/app/index.html` | `<title>` → "CoDev Code" | Web UI tab title. | Keep our title. |
| `packages/app/src/pages/layout.tsx`, `packages/app/src/pages/layout-new.tsx` | Dropped the `<TabsInfoPopup />` mount and its import from both layouts (added upstream in v1.17.18); `HelpButton` stays. | `TabsInfoPopup` is a dev-channel-only "Introducing Tabs" promo whose `muted autoplay loop` `<video>` mounts on every layout render. The fork's Windows e2e runs on GPU-less GitHub-hosted runners (software GPU — see the runner-swap row), where continuous video decode wedges the GPU command buffer (`kTransientFailure`), making the whole app-e2e suite flaky and ~3–5× slower. It never ships (release builds use a non-`dev` `CODEV_CHANNEL`), so unmounting it costs users nothing. | Keep ours: don't re-add the `<TabsInfoPopup />` mount or restore it to the import. |
| `packages/app/src/components/help-button.tsx` | Removed the `TabsInfoPopup` component, its `introducing-tabs.mp4` import, and the imports only it used; `HelpButton` stays. (The file's `VITE_OPENCODE_CHANNEL` → `VITE_CODEV_CHANNEL` gates follow the env-var rename row.) | The popup is upstream's temporary, OpenCode-branded "Introducing Tabs" promo (upstream's own comment says it's removable after the rollout), already unmounted per the row above; removing the definition lets the 13 MB video be deleted (row below). | Keep our removal; take upstream's changes to `HelpButton`. If upstream mounts `TabsInfoPopup` somewhere new, drop that mount per the row above. When upstream deletes the popup itself, this row and the two rows around it retire. |
Expand Down
15 changes: 9 additions & 6 deletions packages/tui/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { ErrorComponent } from "./component/error-component"
import { PluginRouteMissing } from "./component/plugin-route-missing"
import { ProjectProvider, useProject } from "./context/project"
import { EditorContextProvider } from "./context/editor"
import { EditorLabelProvider } from "./context/editor-label"
import { useEvent } from "./context/event"
import { SDKProvider, useSDK } from "./context/sdk"
import { StartupLoading } from "./component/startup-loading"
Expand Down Expand Up @@ -326,12 +327,14 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
<PromptHistoryProvider>
<PromptRefProvider>
<EditorContextProvider>
<LocationProvider>
<App
onSnapshot={input.onSnapshot}
pluginHost={input.pluginHost}
/>
</LocationProvider>
<EditorLabelProvider>
<LocationProvider>
<App
onSnapshot={input.onSnapshot}
pluginHost={input.pluginHost}
/>
</LocationProvider>
</EditorLabelProvider>
</EditorContextProvider>
</PromptRefProvider>
</PromptHistoryProvider>
Expand Down
68 changes: 9 additions & 59 deletions packages/tui/src/component/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { useRoute } from "../../context/route"
import { useProject } from "../../context/project"
import { useSync } from "../../context/sync"
import { useEvent } from "../../context/event"
import { editorSelectionKey, useEditorContext, type EditorSelection } from "../../context/editor"
import { useEditorContext, type EditorSelection } from "../../context/editor"
import { getEditorRangeLabel, hasEditorRangeSelection, useEditorLabel } from "../../context/editor-label"
import { normalizePromptContent, openEditor } from "../../editor"
import { useExit } from "../../context/exit"
import { promptOffsetWidth } from "../../prompt/display"
Expand Down Expand Up @@ -118,19 +119,6 @@ function fadeColor(color: RGBA, alpha: number) {
return RGBA.fromValues(color.r, color.g, color.b, color.a * alpha)
}

function hasEditorRangeSelection(selection: EditorSelection["ranges"][number]) {
return (
selection.selection.start.line !== selection.selection.end.line ||
selection.selection.start.character !== selection.selection.end.character
)
}

function getEditorRangeLabel(selection: EditorSelection["ranges"][number]) {
if (!hasEditorRangeSelection(selection)) return
if (selection.selection.start.line === selection.selection.end.line) return `#${selection.selection.start.line}`
return `#${selection.selection.start.line}-${selection.selection.end.line}`
}

function formatEditorContext(selection: EditorSelection) {
const selected = selection.ranges.filter(hasEditorRangeSelection)
if (selected.length === 0)
Expand Down Expand Up @@ -186,38 +174,9 @@ export function Prompt(props: PromptProps) {
const animationsEnabled = createMemo(() => kv.get("animations_enabled", true))
const list = createMemo(() => props.placeholders?.normal ?? [])
const shell = createMemo(() => props.placeholders?.shell ?? [])
const fileContextEnabled = createMemo(() => kv.get("file_context_enabled", true))
const [dismissedEditorSelectionKey, setDismissedEditorSelectionKey] = createSignal<string>()
const editorContext = createMemo(() => {
const selection = fileContextEnabled() ? editor.selection() : undefined
if (!selection) return
return editorSelectionKey(selection) === dismissedEditorSelectionKey() ? undefined : selection
})
const editorPath = createMemo(() => editorContext()?.filePath)
const editorSelectionLabel = createMemo(() => {
const ranges = editorContext()?.ranges
if (!ranges) return
const first = ranges.find(hasEditorRangeSelection) ?? ranges[0]
if (!first) return
return [getEditorRangeLabel(first), ranges.length > 1 ? `+${ranges.length - 1}` : undefined]
.filter(Boolean)
.join(" ")
})
const editorFileLabel = createMemo(() => {
const value = editorPath()
if (!value) return
const filename = path.basename(value)
const file = /^index\.[^./]+$/.test(filename)
? [path.basename(path.dirname(value)), filename].filter(Boolean).join("/")
: filename
return `${file.split(path.sep).join("/")}${editorSelectionLabel() ?? ""}`
})
const editorFileLabelDisplay = createMemo(() => {
const file = editorFileLabel()
if (!file) return
return Locale.truncateMiddle(file, Math.max(12, Math.min(48, Math.floor(dimensions().width / 3))))
})
const editorContextLabelState = createMemo(() => editor.labelState())
// Fork: the effective selection lives in EditorLabel — the sidebar footer
// labels the same one this attaches on submit.
const editorLabel = useEditorLabel()
const directoryLabel = createMemo(() =>
directoryFooterLabel({
directory: project.instance.directory() || paths.cwd,
Expand Down Expand Up @@ -251,10 +210,6 @@ export function Prompt(props: PromptProps) {
}
}

function dismissEditorContext() {
setDismissedEditorSelectionKey(editorSelectionKey(editorContext()))
editor.clearSelection()
}
const fileStyleId = syntax().getStyleId("extmark.file")!
const agentStyleId = syntax().getStyleId("extmark.agent")!
const pasteStyleId = syntax().getStyleId("extmark.paste")!
Expand Down Expand Up @@ -387,9 +342,9 @@ export function Prompt(props: PromptProps) {
title: "Remove editor context",
name: "prompt.editor_context.clear",
category: "Prompt",
enabled: Boolean(editorContext()),
enabled: Boolean(editorLabel.selection()),
run: () => {
dismissEditorContext()
editorLabel.dismiss()
dialog.clear()
},
},
Expand Down Expand Up @@ -1091,9 +1046,9 @@ export function Prompt(props: PromptProps) {

// Capture mode before it gets reset
const currentMode = store.mode
const editorSelection = editorContext()
const editorSelection = editorLabel.selection()
const editorParts =
editorSelection && editor.labelState() === "pending"
editorSelection && editorLabel.state() === "pending"
? [
{
type: "text" as const,
Expand Down Expand Up @@ -1712,11 +1667,6 @@ export function Prompt(props: PromptProps) {
muted={theme.textMuted}
hidden={props.sidebarVisible}
/>
<Show when={editorContextLabelState() !== "none" ? editorFileLabelDisplay() : undefined}>
{(file) => (
<text fg={editorContextLabelState() === "pending" ? theme.secondary : theme.textMuted}>{file()}</text>
)}
</Show>
<Switch>
<Match when={store.mode === "normal"}>
<Switch>
Expand Down
Loading
Loading