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
3 changes: 2 additions & 1 deletion DIVERGENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ from the squash message first, or use a plain merge.
| `packages/tui/src/routes/session/index.tsx` | Sidebar visibility is session-local: removed upstream's persisted `kv.signal<"auto" \| "hide">("sidebar", "auto")` (and the `sidebar() === "auto" && wide()` branch of `sidebarVisible`); visibility is driven only by the non-persisted `sidebarOpen` signal, which the `session.sidebar.toggle` command flips (supersedes the earlier `"auto"` → `"hide"` default change) | The right sidebar starts hidden in every session regardless of terminal width or what the user did last session; `ctrl+b`/`<leader>b` only affects the current session. A stale `sidebar` key in existing kv.json files is simply never read. | Keep the kv signal removed and `sidebarVisible` reading only `parentID` + `sidebarOpen`; take upstream's surrounding changes. |
| `packages/tui/src/routes/session/index.tsx` | Hidden-sidebar surfaces: a `sidebarHint` memo (modified-file count from `sync.data.session_diff`, only when the sidebar is hidden and the session isn't a subagent; label from `sidebarHintLabel` in the fork-only `routes/session/sidebar-hint.ts`) plus a `sidebarShortcut` (`useCommandShortcut("session.sidebar.toggle")`), passed as the `Prompt`'s previously unused `hint` prop — e.g. `ctrl+b 5 modified files`; and a compact `<TodoPanel>` (fork-only `routes/session/todo-panel.tsx` + `todo-panel-model.ts`) mounted inside `<Show when={visible()}>` under an extra `<Show when={!sidebarVisible()}>`, directly above the `session_prompt` slot — up to 4 checkbox items windowed around the active todo plus a summary line (`2 of 6 finished · ctrl+b to show full TODO`), shown only while an unfinished todo exists | The sidebar starts hidden in this fork (see the row above), so users would otherwise never see the agent's plan or modified files or how to reveal them. The panel shows the active todos inline, so the footer hint only needs to cover modified files. | Keep our `sidebarShortcut`/`sidebarHint` memos, the `sidebar-hint` and `todo-panel` imports, the `TodoPanel` mount above the `session_prompt` slot, and the `hint={…}` prop on `<Prompt>` (all additive); take upstream's surrounding changes. If upstream starts passing its own `hint` to this `<Prompt>`, merge the two under one `Show`; if upstream adds its own children to the bottom box, keep `TodoPanel` directly above the prompt slot. |
| `packages/tui/src/routes/session/index.tsx` | Assistant-message trailer dedup: a `suppressed` memo in `AssistantMessage` hides the `▣ Agent · Model` prefix on the last message when it matches the currently selected agent (`local.agent.current()?.name === message.mode`) and model (`local.model.current()` vs the message's `providerID`/`modelID`), since the prompt footer right below already shows that selection; the duration still renders as `Baked for <duration>` (and `interrupted` keeps rendering on aborts), and the trailer box is skipped entirely when suppression leaves nothing to show | The last message's trailer and the prompt footer showed the same agent + model twice, one line apart. | Keep the `suppressed` memo and the `Show` gates around the trailer spans; take upstream's surrounding trailer changes (new suffixes should go inside the existing `<text>` with a `·` separator like `interrupted`). |
| `packages/tui/src/routes/session/index.tsx` | Trailer extracted from `AssistantMessage` into an exported `MessageTrailer` component (it owns the `model`/`final`/`duration`/`suppressed`/`aborted` memos, which moved with it) and the last assistant message's copy is hoisted out of the transcript: `AssistantMessage` renders it inline only `when={!props.last}`, and `Session` renders `<MessageTrailer message={lastAssistant()} last spaced={!todoPanelVisible()} />` in the pinned block between the todo panel and the `session_prompt` slot. Spacing moved with it — the pinned instance uses box `paddingTop` (only when no todo panel precedes it) / `paddingBottom` instead of the inline `<text marginTop={1}>`. `Session` gained a `todoPanelVisible` memo built on the fork-only `todoActive` predicate. | `Baked for <duration>` is footer-grade status, not transcript content: pinned below the todo panel it stays put instead of scrolling away with history, and the two status blocks above the prompt read as one group. | Keep `MessageTrailer` and both call sites; take upstream's surrounding changes. If upstream restructures the trailer, re-apply the extraction and keep the last message's copy out of the `<For>` over messages. |
| `packages/tui/src/routes/session/index.tsx` | Trailer extracted from `AssistantMessage` into an exported `MessageTrailer` component (it owns the `model`/`final`/`duration`/`suppressed`/`aborted` memos, which moved with it) and the last assistant message's copy is hoisted out of the transcript: `AssistantMessage` renders it inline only `when={!props.last}`, and `Session` renders `<MessageTrailer message={lastAssistant()} last />` in the pinned block between the todo panel and the `session_prompt` slot. That block is a `<box flexShrink={0} paddingTop={1} gap={1}>` wrapping the todo panel, the trailer and the prompt slot, so a blank row appears only between the parts that actually render and no part carries padding for a neighbour that may be absent; the pinned trailer therefore keeps only `paddingLeft={3}` and the inline one keeps its `<text marginTop={1}>`. | `Baked for <duration>` is footer-grade status, not transcript content: pinned below the todo panel it stays put instead of scrolling away with history, and the two status blocks above the prompt read as one group. | Keep `MessageTrailer` and both call sites; take upstream's surrounding changes. If upstream restructures the trailer, re-apply the extraction and keep the last message's copy out of the `<For>` over messages. |
| `packages/plugin/src/tui.ts` | `home_bottom` slot props widened from `{}` to `{ placement?: "center" \| "start" }` | The empty-session banner asks `home_bottom` contributors to left-align; optional prop keeps the home route and third-party plugins (no prop → centered) unchanged. | Keep our `placement` prop; take upstream's other slot-map changes. |
| `packages/tui/src/feature-plugins/home/tips.tsx` | `home_bottom` slot fn reads the new `placement` prop; with `"start"` the tips box left-aligns (`flex-start`) and drops its `paddingTop` 3 | The top-left session banner needs the tip flush under the logo; the centered home route keeps upstream spacing. | Keep our `placement` threading + conditional alignItems/paddingTop; take upstream's surrounding changes. |
| `packages/tui/src/feature-plugins/system/which-key.tsx` | `HomeHint` reads the new `placement` prop from the `home_bottom` slot; with `"start"` it left-aligns | Same top-left banner alignment as the tips row above. | Keep our `placement` threading + conditional alignItems; take upstream's surrounding changes. |
Expand Down Expand Up @@ -130,6 +130,7 @@ from the squash message first, or use a plain merge.
| `packages/tui/src/component/prompt/index.tsx` | Busy footer spinner is cli-spinners "aesthetic" instead of the Knight Rider block scanner: module-level `FOOTER_FRAMES` (`▰▱` ramp, 7 chars, 8 frames), `spinnerDef` keeps the agent-color resolution but returns those frames plus a per-char color fn (`▰` → agent color, `▱` → `deriveInactiveColor(color, 0.3)`) instead of `createFrames`/`createColors` from `ui/spinner`, and the `<spinner>` interval is 120 ms (was 40; cli-spinners' stock 80 felt too fast). `ui/spinner.ts` itself is untouched; only its `deriveInactiveColor` import remains. | Fork visual identity for the busy indicator next to `esc interrupt`; the agent accent color still drives it. | Keep `FOOTER_FRAMES`, our `spinnerDef` body, the `deriveInactiveColor` import, and the 120 ms interval; take upstream's surrounding changes. If upstream reworks `spinnerDef`, re-apply the aesthetic frames + per-char color fn on top. |
| `packages/tui/src/component/prompt/index.tsx`, `packages/tui/src/routes/session/index.tsx` | Compact prompt/footer spacing: the input box's interior `paddingTop={1}` is replaced by a height-1 half-block top-edge row above the box (`border={["top"]}`, `horizontal: "▄"` when `backgroundElement` is opaque, `" "` otherwise — mirror of the existing `"▀"` bottom-edge row; the agent/model meta row keeps its `paddingTop={1}`), and the session content container loses its `paddingBottom={1}` and its `gap={1}` (the gap fired between every pair of children — transcript↔prompt, and prompt↔the trailing flexGrow spacer, where it left a phantom blank row under the footer even at zero spacer height; the half-block lip is now the only transcript↔prompt separation) so the prompt footer line sits on the terminal's last row | The prompt box was a row taller than its content needed, a full blank row sat between the transcript and the box, and the footer floated 2 rows above the bottom edge; the half-block lip keeps a visible box edge above the text at half the height. | Keep the top-edge lip row and the padding/gap removals; take upstream's surrounding changes. If upstream restyles the box edges, re-apply the symmetric half-block lips and keep the container un-padded. |
| `packages/tui/src/routes/session/footer.tsx` | Deleted. The file exported a `Footer` component (cwd, pending-permission count, LSP/MCP counts, `/status`) that nothing imports — it is dead upstream too, superseded there and here by the sidebar footer and the home footer. | Its contents live in `feature-plugins/sidebar/footer.tsx` (cwd:branch, editor-context label) and `feature-plugins/home/footer.tsx` (dir, MCP, `/status`, version); keeping an unreferenced third copy invites edits to a component that never renders. | Take upstream's version only if upstream starts importing it again; otherwise re-delete (`git rm`) after a sync reintroduces the file. |
| `packages/tui/src/component/prompt/index.tsx` | The agent/permission-mode/model/provider/variant row moved out of the input box: it was the second child of the `backgroundElement` box under the `<textarea>` (with `paddingTop={1}`), and is now the first child of the prompt's root box, above the `▄` top-edge row, carrying `paddingLeft={3} paddingRight={2}` so it lands on the same columns as before. The `session_prompt_right` slot moved with it. | The line describes what the *next* prompt will use, not what is in the box, so it reads as a header for the input rather than a caption under it, and it now groups with the pinned trailer and todo panel as one status block above the box. Net row count is unchanged: the box loses its interior blank row, the pinned block's `gap` spends one. | Keep the row in its new position; take upstream's surrounding changes. If upstream restructures the input box, re-lift the row rather than merging it back inside. |
| `packages/tui/src/routes/session/question.tsx` | Selection/hover highlight color: a `highlight` memo (`tint(theme.backgroundPanel, theme.text, 0.1)`) replaces `theme.backgroundElement` on the active option row (number + label boxes, both the option list and the "Type your own answer" row) and on the question/Confirm tab hover state | `backgroundElement` sits ~1.04x off `backgroundPanel` in light themes (`#f5f5f5` on `#fafafa` for `codev`), so the selected answer had no visible highlight; blending the panel toward the theme's text color keeps a neutral band that is ~1.2x in both modes and works for generated/system themes too. | Keep the `highlight` memo and its four/two call sites; take upstream's surrounding changes. If upstream adds new `backgroundElement` highlights in this panel, point them at `highlight()` too. |
| `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. |
Expand Down
92 changes: 44 additions & 48 deletions packages/tui/src/component/prompt/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,50 @@ export function Prompt(props: PromptProps) {
return (
<>
<box ref={(r: BoxRenderable) => (anchor = r)} visible={props.visible !== false} width="100%">
{/* Fork: the agent/model line sits above the input box, grouped with
the pinned trailer, instead of inside it under the textarea. */}
<box flexDirection="row" flexShrink={0} paddingLeft={3} paddingRight={2} gap={1} justifyContent="space-between">
<box flexDirection="row" gap={1}>
<Show when={local.agent.current()} fallback={<box height={1} />}>
{(agent) => (
<>
<text fg={fadeColor(highlight(), agentMetaAlpha())}>
{store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)}
</text>
<Show when={store.mode === "normal"}>
<text
fg={fadeColor(local.permission.mode === "auto" ? theme.error : theme.textMuted, agentMetaAlpha())}
>
{local.permission.mode === "auto" ? "auto" : "manual"}
</text>
</Show>
<Show when={store.mode === "normal" && connected()}>
<box flexDirection="row" gap={1}>
<text fg={fadeColor(theme.textMuted, modelMetaAlpha())}>·</text>
<text flexShrink={0} fg={fadeColor(leader() ? theme.textMuted : theme.text, modelMetaAlpha())}>
{local.model.parsed().model}
</text>
<text fg={fadeColor(theme.textMuted, modelMetaAlpha())}>{currentProviderLabel()}</text>
<Show when={showVariant()}>
<text fg={fadeColor(theme.textMuted, variantMetaAlpha())}>·</text>
<text>
<span style={{ fg: fadeColor(theme.warning, variantMetaAlpha()), bold: true }}>
{local.model.variant.current()}
</span>
</text>
</Show>
</box>
</Show>
</>
)}
</Show>
</box>
<Show when={hasRightContent()}>
<box flexDirection="row" gap={1} alignItems="center">
{props.right}
</box>
</Show>
</box>
<box height={1} paddingLeft={1}>
<box
height={1}
Expand Down Expand Up @@ -1448,54 +1492,6 @@ export function Prompt(props: PromptProps) {
cursorColor={props.disabled ? theme.backgroundElement : theme.text}
syntaxStyle={syntax()}
/>
<box flexDirection="row" flexShrink={0} paddingTop={1} gap={1} justifyContent="space-between">
<box flexDirection="row" gap={1}>
<Show when={local.agent.current()} fallback={<box height={1} />}>
{(agent) => (
<>
<text fg={fadeColor(highlight(), agentMetaAlpha())}>
{store.mode === "shell" ? "Shell" : Locale.titlecase(agent().name)}
</text>
<Show when={store.mode === "normal"}>
<text
fg={fadeColor(
local.permission.mode === "auto" ? theme.error : theme.textMuted,
agentMetaAlpha(),
)}
>
{local.permission.mode === "auto" ? "auto" : "manual"}
</text>
</Show>
<Show when={store.mode === "normal" && connected()}>
<box flexDirection="row" gap={1}>
<text fg={fadeColor(theme.textMuted, modelMetaAlpha())}>·</text>
<text
flexShrink={0}
fg={fadeColor(leader() ? theme.textMuted : theme.text, modelMetaAlpha())}
>
{local.model.parsed().model}
</text>
<text fg={fadeColor(theme.textMuted, modelMetaAlpha())}>{currentProviderLabel()}</text>
<Show when={showVariant()}>
<text fg={fadeColor(theme.textMuted, variantMetaAlpha())}>·</text>
<text>
<span style={{ fg: fadeColor(theme.warning, variantMetaAlpha()), bold: true }}>
{local.model.variant.current()}
</span>
</text>
</Show>
</box>
</Show>
</>
)}
</Show>
</box>
<Show when={hasRightContent()}>
<box flexDirection="row" gap={1} alignItems="center">
{props.right}
</box>
</Show>
</box>
</box>
</box>
<box height={1} paddingLeft={1}>
Expand Down
Loading
Loading