diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 3db36cb6b5..f05fa11427 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -127,6 +127,7 @@ from the squash message first, or use a plain merge. | `packages/tui/src/component/spinner.tsx` | `SPINNER_FRAMES` swapped from cli-spinners "dots" (10 braille frames) to cli-spinners "sand" (35 braille frames, falling-sand animation); interval stays 80 ms | Fork visual identity for the shared `Spinner` component (in-progress tool/thinking rows, startup loading, session-list dialogs). | Keep our frames array; take upstream's surrounding changes. | | `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 `` 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/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 `` (fork-only `./context/editor-label`) immediately inside `` | 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` | `` → "CoDev Code" | Web UI tab title. | Keep our title. | diff --git a/packages/tui/src/routes/session/footer.tsx b/packages/tui/src/routes/session/footer.tsx deleted file mode 100644 index c3a96254e9..0000000000 --- a/packages/tui/src/routes/session/footer.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { createMemo, Match, onCleanup, onMount, Show, Switch } from "solid-js" -import { useTheme } from "../../context/theme" -import { useSync } from "../../context/sync" -import { useDirectory } from "../../context/directory" -import { useConnected } from "../../component/use-connected" -import { createStore } from "solid-js/store" -import { useRoute } from "../../context/route" - -export function Footer() { - const { theme } = useTheme() - const sync = useSync() - const route = useRoute() - const mcp = createMemo(() => Object.values(sync.data.mcp).filter((x) => x.status === "connected").length) - const mcpError = createMemo(() => Object.values(sync.data.mcp).some((x) => x.status === "failed")) - const lsp = createMemo(() => Object.keys(sync.data.lsp)) - const permissions = createMemo(() => { - if (route.data.type !== "session") return [] - return sync.data.permission[route.data.sessionID] ?? [] - }) - const directory = useDirectory() - const connected = useConnected() - - const [store, setStore] = createStore({ - welcome: false, - }) - - onMount(() => { - // Track all timeouts to ensure proper cleanup - const timeouts: ReturnType<typeof setTimeout>[] = [] - - function tick() { - if (connected()) return - if (!store.welcome) { - setStore("welcome", true) - timeouts.push(setTimeout(() => tick(), 5000)) - return - } - - if (store.welcome) { - setStore("welcome", false) - timeouts.push(setTimeout(() => tick(), 10_000)) - return - } - } - timeouts.push(setTimeout(() => tick(), 10_000)) - - onCleanup(() => { - timeouts.forEach(clearTimeout) - }) - }) - - return ( - <box flexDirection="row" justifyContent="space-between" gap={1} flexShrink={0}> - <text fg={theme.textMuted}>{directory()}</text> - <box gap={2} flexDirection="row" flexShrink={0}> - <Switch> - <Match when={store.welcome}> - <text fg={theme.text}> - Get started <span style={{ fg: theme.textMuted }}>/connect</span> - </text> - </Match> - <Match when={connected()}> - <Show when={permissions().length > 0}> - <text fg={theme.warning}> - <span style={{ fg: theme.warning }}>△</span> {permissions().length} Permission - {permissions().length > 1 ? "s" : ""} - </text> - </Show> - <text fg={theme.text}> - <span style={{ fg: lsp().length > 0 ? theme.success : theme.textMuted }}>•</span> {lsp().length} LSP - </text> - <Show when={mcp()}> - <text fg={theme.text}> - <Switch> - <Match when={mcpError()}> - <span style={{ fg: theme.error }}>⊙ </span> - </Match> - <Match when={true}> - <span style={{ fg: theme.success }}>⊙ </span> - </Match> - </Switch> - {mcp()} MCP - </text> - </Show> - <text fg={theme.textMuted}>/status</text> - </Match> - </Switch> - </box> - </box> - ) -}