From d9de2e01c238b40529a94dfcb76083731c765324 Mon Sep 17 00:00:00 2001 From: minhn4 Date: Thu, 30 Jul 2026 18:08:35 +0700 Subject: [PATCH 1/2] feat(tui): match the banner to the landing page wordmark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The home/session banner drew the codev wordmark in the active theme's `secondary` (teal in dark, purple in light) with a tint-derived drop shadow, so the TUI and the marketing site showed the same glyph art in different colors. Draw it in the landing page's brand palette instead (codev-landing-page app/components/PixelWordmark.tsx): "co" in brand navy on light and the theme foreground on dark, "dev" in brand red, no drop shadow. The glyph art is unchanged. `wordmarkHalves` keeps each row split so the two halves can take their own color, and `wordmark` is composed from it. The CLI banner follows via `ansiWordmark` — bold, no shadow, "dev" in brand red truecolor, "co" in the terminal's default foreground since the banner cannot detect light vs dark. The `codev --mini` entry/exit badge follows too: the landing page draws the same compact "C" glyph in brand red in both modes (codev-landing-page app/icon.svg), so `RunSplashTheme.mark` becomes `rgba(brand.red)`. Being mode-independent, `splashTheme` drops the forked `pick` param and returns to upstream's `(theme, indexed)` signature. Co-Authored-By: Claude Opus 5 (1M context) --- CHANGELOG.md | 10 +++ DIVERGENCES.md | 10 +-- packages/opencode/src/cli/cmd/run/theme.ts | 32 +++++----- packages/opencode/src/cli/ui.ts | 2 +- packages/opencode/test/cli/run/theme.test.ts | 7 +- packages/tui/src/component/logo.tsx | 67 +++++--------------- packages/tui/src/logo.ts | 48 ++++++++------ 7 files changed, 78 insertions(+), 98 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85d2d9450a..f9a4638878 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,16 @@ file is the human-readable summary of what changed and why. ### TUI +- **Banner now matches the website wordmark** — the `codev` pixel wordmark on the + home screen and at the top of a session is drawn in the landing page's brand + colors instead of the theme's accent: `co` in brand navy (`#19224c`) on light + and the theme foreground on dark, `dev` in brand red (`#ee0033`), with the drop + shadow behind the glyphs removed. The glyph art is unchanged. The CLI banner + (`codev --help`, `codev upgrade`, `codev web`, `codev uninstall`) follows: bold, + no shadow, `dev` in brand red and `co` in the terminal's default foreground. + The `codev --mini` entry and exit badges — the compact `C` glyph — turn brand + red too, matching the website's favicon, instead of the theme's teal/purple. + - **No more duplicated agent · model line** — the trailer under the last assistant reply (`▣ Build · MiniMax/MiniMax-M2.7 · 3m`) used to repeat the agent and model the prompt footer already shows one line below. When they diff --git a/DIVERGENCES.md b/DIVERGENCES.md index 834d8da844..dd19b81043 100644 --- a/DIVERGENCES.md +++ b/DIVERGENCES.md @@ -85,10 +85,10 @@ from the squash message first, or use a plain merge. | `packages/opencode/script/publish.ts` | `PUBLISH_NAME = "codev-code"` const; published-name→dir map; main pkg name + bin `codev` → `./bin/codev.exe` (postinstall stub file renamed from `${pkg.name}.exe`, matching the renamed platform binary); embedded README; no `repository` field in the generated package.json (npm renders it as the Repository link and derives a `#readme` Homepage from it — both kept off the npm page); env-guarded exit before the Docker/AUR/Homebrew section | npm-only publishing under the fork's names (unscoped, public on npmjs); the installed command is `codev`; upstream's other channels target anomalyco infra; the npm page should not link the fork's GitHub repo. | Keep our names, description, bin mapping, omitted `repository` + early exit; take upstream's npm-section changes above the exit. | | `packages/opencode/bin/opencode` | `base` prefix `opencode-` → `codev-code-` | Runtime platform-binary lookup must match the published names. | Keep our prefix. | | `packages/opencode/script/postinstall.mjs` | `base` prefix `opencode-` → `codev-code-`; source/target binary names `opencode`/`opencode.exe` → `codev`/`codev.exe` (matches build.ts's `bin/codev` and publish.ts's `bin/codev.exe` stub); temp-dir prefix and failure message rebranded | Install-time platform-binary fetch must match the published package and binary names. | Keep our prefix and binary names. | -| `packages/tui/src/logo.ts` | Pixel glyphs "open"+"code" → "co"+"dev"; added composed `wordmark` (plain lines) and `ansiWordmark` (bold brand teal + drop shadow) exports for CLI banners; added a `badge` export (the "c" of the wordmark — `logo.left` rows 1-3 sliced to the first glyph, with the `_` shadow-fill marks stripped to spaces so it renders as a flat outline) for the `--mini` splashes | Branded wordmark art; `ansiWordmark` feeds the CLI help banner and session epilogue. | Keep our glyphs + exports; take upstream's surrounding changes. | -| `packages/tui/src/component/logo.tsx` | Renders the lowercase codev wordmark in the theme's `secondary` color (bold, with a `tint`-derived drop shadow) instead of upstream's two-tone "open"+"code" | Home banner shows the brand wordmark in the active theme's brand color (teal in dark, purple in light). | Keep our version wholesale. | +| `packages/tui/src/logo.ts` | Pixel glyphs "open"+"code" → "co"+"dev"; added a `brand` export (`navy` `#19224c` / `red` `#ee0033`, the landing page's `--color-brand-navy`/`--color-brand-red`), a `wordmarkHalves` export (each row's "co" and "dev" halves with the shadow marks substituted to glyphs, so each half can take its own brand color), the composed `wordmark` (plain lines, built from `wordmarkHalves`) and `ansiWordmark` (bold, no shadow: "co" in the terminal's default foreground, "dev" in brand red truecolor) exports for CLI banners; added a `badge` export (the "c" of the wordmark — `logo.left` rows 1-3 sliced to the first glyph, with the `_` shadow-fill marks stripped to spaces so it renders as a flat outline) for the `--mini` splashes | Branded wordmark art; `ansiWordmark` feeds the CLI help banner and session epilogue. | Keep our glyphs + exports; take upstream's surrounding changes. | +| `packages/tui/src/component/logo.tsx` | Renders the lowercase codev wordmark from `wordmarkHalves` — one bold `` per half, no per-char shadow cells — with "co" in `brand.navy` on light / `theme.text` on dark and "dev" in `brand.red`, instead of upstream's two-tone "open"+"code" | Banner is pixel-identical to the landing page wordmark (`codev-landing-page` `app/components/PixelWordmark.tsx`: `text-brand-navy dark:text-foreground` + `text-brand-red`), so the marketing site and the TUI read as one brand. Deliberately fixed brand colors rather than the theme's `secondary`, and no drop shadow — the landing wordmark has neither. | Keep our version wholesale. If upstream restructures the component, re-apply the two-half brand coloring rather than reverting to a theme color. | | `packages/tui/src/theme/assets/codev.json` (new file), `packages/tui/src/theme/index.ts`, `packages/tui/src/context/theme.tsx` | Added the `codev` theme — upstream's `opencode.json` palette with `darkSecondary` → `#0891b2` (brand teal) — registered it in the theme index, and made it the default/fallback theme (`"opencode"` → `"codev"` in the store default, kv fallback, and load-failure fallbacks). `theme/assets/opencode.json` itself matches upstream again. | Brand accent lives in a fork-owned theme instead of edits to upstream's palette; the default theme carries the brand. | Keep our theme file, its index registration, and the fallback strings; when upstream changes the opencode palette, mirror it into `codev.json` (keeping our `darkSecondary`). | -| `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/opencode/src/cli/ui.ts` | `wordmark`/`logo()` render the lowercase codev wordmark via `ansiWordmark` (bold, no shadow, "dev" in brand red on TTY) | Help banner matches the TUI home banner. | Keep our wordmark + logo(); take upstream's surrounding changes. | | `packages/tui/src/util/presentation.ts` | `sessionEpilogue` returns only the one-line `Continue codev -s ` hint — dropped the `ansiWordmark` banner and the `Session ` line (and the now-unused `title` input) | The exit transcript (see the exit-transcript row) already prints the conversation on quit; the wordmark + title banner was redundant noise above the resume hint. | Keep the single Continue line; if upstream re-adds a banner/title here, keep it removed. | | `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. | @@ -104,8 +104,8 @@ from the squash message first, or use a plain merge. | `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. | | `packages/tui/src/routes/home.tsx` | `placeholder` suggestion pool exported (was module-private) | The empty-session view in routes/session/index.tsx feeds the same pool to its prompt, so upstream edits to the suggestions flow through. | Keep the `export` keyword; take upstream's changes to the list. | | `packages/tui/src/routes/session/permission.tsx`, `packages/opencode/src/cli/cmd/run/permission.shared.ts`, `packages/opencode/test/cli/run/permission.shared.test.ts`, `packages/tui/src/component/error-component.tsx`, `packages/opencode/src/cli/cmd/run/splash.ts` | Visible sentences/badges "OpenCode"/"opencode" → "CoDev Code"/"codev-code" (incl. the permission always-allow copy and its test expectations); permission.tsx also registers a local `app.exit.confirm` command mirroring its `app.exit` reject and gathers the confirm binding (double-press Ctrl+C — see the double-Ctrl+C row) | User-facing copy; keep Ctrl+C rejecting the permission prompt as before. | Keep our strings; keep permission.tsx's `app.exit.confirm` command + binding. | -| `packages/opencode/src/cli/cmd/run/splash.ts` | The `--mini` entry and exit splashes badge with `badge` (the "c" of the codev wordmark) instead of `go.right.slice(1)` (the "o" of the `go` mark) — the import and both `const mark = …` lines; header comment "full opencode entry logo and a compact [O] exit badge" → "full codev entry logo and a compact [C] exit badge". The badge glyphs draw with `input.theme.mark` (brand secondary) via the local `mark_fg` binding, not the muted `left`/`leftShadow` that upstream uses for the whole banner, and with no shadow — `draw`'s `shadow` param is now optional and the badge calls omit it, so the flat brand-colored outline has no interior fill. | The splash badge is the brand mark users see entering and leaving `codev --mini`; an "o" is upstream's, and the muted grey read as chrome rather than branding. `go` itself is untouched — `packages/tui/src/component/bg-pulse-render.ts` still draws the full "go" wordmark from it. | Keep the `badge` import, both `mark` assignments, the `mark_fg` badge fg, and the optional-`shadow` `draw` signature; take upstream's surrounding splash changes. If upstream restructures the badge rendering, feed it `badge` in the brand `mark` color rather than reverting to `go`/`left`. | -| `packages/opencode/src/cli/cmd/run/theme.ts`, `packages/opencode/test/cli/run/theme.test.ts` | `RunSplashTheme` gains a `mark` field (brand badge color for the mini splash — no shadow color, the badge is a flat outline). `splashTheme(theme, indexed, pick)` takes the resolved dark/light `pick` and sets `mark` to the fixed `BRAND_SECONDARY` constant (`#0891b2` dark / `#7b5bb6` light — the codev theme's `darkSecondary`/`lightSecondary`) as **truecolor, not quantized**. `RUN_THEME_FALLBACK.splash` seeds `mark` from `fallbackSplashMark = rgba("#0891b2")` (also truecolor). theme.test.ts asserts `mark` is exactly `[8,145,178]`. Deliberately NOT the run theme's own `secondary` (that is derived from the terminal palette — magenta — and quantizing it gives a dull, off-brand cell). | The mini badge must read as the exact home-screen banner teal (`logo.tsx` draws the wordmark in the same brand `#0891b2`/`#7b5bb6` truecolor); the rest of the splash stays palette-matched, but the brand mark is a deliberate truecolor exception. | Keep the `RunSplashTheme.mark` field, the `pick` param + `BRAND_SECONDARY` derivation, the fallback seed, and the test assertion; take upstream's surrounding theme changes. If upstream reworks `splashTheme`'s signature, re-thread `pick`. | +| `packages/opencode/src/cli/cmd/run/splash.ts` | The `--mini` entry and exit splashes badge with `badge` (the "c" of the codev wordmark) instead of `go.right.slice(1)` (the "o" of the `go` mark) — the import and both `const mark = …` lines; header comment "full opencode entry logo and a compact [O] exit badge" → "full codev entry logo and a compact [C] exit badge". The badge glyphs draw with `input.theme.mark` (brand red) via the local `mark_fg` binding, not the muted `left`/`leftShadow` that upstream uses for the whole banner, and with no shadow — `draw`'s `shadow` param is now optional and the badge calls omit it, so the flat brand-colored outline has no interior fill. | The splash badge is the brand mark users see entering and leaving `codev --mini`; an "o" is upstream's, and the muted grey read as chrome rather than branding. `go` itself is untouched — `packages/tui/src/component/bg-pulse-render.ts` still draws the full "go" wordmark from it. | Keep the `badge` import, both `mark` assignments, the `mark_fg` badge fg, and the optional-`shadow` `draw` signature; take upstream's surrounding splash changes. If upstream restructures the badge rendering, feed it `badge` in the brand `mark` color rather than reverting to `go`/`left`. | +| `packages/opencode/src/cli/cmd/run/theme.ts`, `packages/opencode/test/cli/run/theme.test.ts` | `RunSplashTheme` gains a `mark` field (brand badge color for the mini splash — no shadow color, the badge is a flat outline). `splashTheme` sets `mark` to the `BRAND_MARK` constant (`rgba(brand.red)` — `#ee0033`, imported from `@/cli/logo`) as **truecolor, not quantized**, in both light and dark; `RUN_THEME_FALLBACK.splash` seeds `mark` from the same constant. theme.test.ts asserts `mark` is exactly `[238,0,51]`. Deliberately NOT the run theme's own `secondary` (that is derived from the terminal palette — magenta — and quantizing it gives a dull, off-brand cell). | The badge renders the same "C" glyph as the landing page's compact mark (`codev-landing-page` `app/icon.svg`), which is brand red in both modes — so the mini splash, the favicon, and the wordmark's red "dev" all agree. The rest of the splash stays palette-matched; the brand mark is a deliberate truecolor exception. Mode-independent, so `splashTheme` keeps upstream's `(theme, indexed)` signature. | Keep the `RunSplashTheme.mark` field, the `BRAND_MARK` constant + `brand` import, the fallback seed, and the test assertion; take upstream's surrounding theme changes. | | `packages/tui/src/config/keybind.ts`, `packages/tui/src/routes/session/question.tsx`, `packages/tui/src/ui/toast.tsx`, `packages/tui/test/app-lifecycle.test.tsx` | Double-press Ctrl+C to quit. keybind.ts: `app_exit` `ctrl+c,ctrl+d,<leader>q` → `ctrl+d,<leader>q`, added `app_exit_confirm: "ctrl+c"` definition + its `app.exit.confirm` CommandMap entry. question.tsx: a local `app.exit.confirm` command mirroring its `app.exit` reject, with the confirm binding gathered in both option branches (Ctrl+C still rejects the question — single press). toast.tsx: `ToastOptions.variant` union gains `"secondary"` (resolves to `theme.secondary`, the banner brand color, for the confirm toast). app-lifecycle.test.tsx: added the "app.exit.confirm requires two presses to exit" test. See also the `app.tsx`, `dialog.tsx`, and `permission.tsx` rows for their `app.exit.confirm` handlers. | A stray Ctrl+C on an empty/unfocused prompt no longer quits instantly: the first press shows a "Press Ctrl+C again to exit" toast (1.5 s window), the second within that window quits. Ctrl+D, `<leader>q`, and the `/exit` slash commands stay immediate. | Keep our binding split, the `app_exit_confirm`/`app.exit.confirm` definition + mapping, the local confirm commands + gathered bindings, the toast `"secondary"` variant, and the test; take upstream's surrounding changes. If upstream re-adds `ctrl+c` to `app_exit`, keep it removed. | | `packages/tui/src/config/keybind.ts`, `packages/opencode/test/cli/run/footer.view.test.tsx` | `sidebar_toggle` default `"<leader>b"` → `"ctrl+b,<leader>b"` (ctrl+b first so single-binding displays — the prompt-footer sidebar hint, the sidebar tip — resolve to it); `ctrl+b`'s former jobs move to `ctrl+o`: `input_move_left` `"left,ctrl+b"` → `"left,ctrl+o"`, `session_background` `"ctrl+b"` → `"ctrl+o"`; footer.view.test.tsx asserts the run-CLI footer background hint as `"ctrl+o background"` (was `"ctrl+b background"`) since it resolves the real `session_background` default | Single-chord sidebar toggle on `ctrl+b` (tmux/VS Code sidebar muscle memory); its displaced bindings land on `ctrl+o`, which was unbound in every scope and free of terminal-encoding collisions (unlike `ctrl+h`/`ctrl+i`/`ctrl+m`) and flow control (`ctrl+s`/`ctrl+q`). | Keep our defaults on all three keybinds and the footer hint assertion; take upstream's surrounding changes. If upstream binds `ctrl+o` to something else, re-audit and pick a new free key. | | `packages/tui/src/config/keybind.ts`, `packages/tui/src/component/prompt/index.tsx`, `packages/tui/src/app.tsx` | Single-press Ctrl+C interrupt. keybind.ts: added `session_interrupt_now: "ctrl+c"` + its `session.interrupt.now` CommandMap entry (`session_interrupt` keeps `escape`; its description gains "(press twice)"). prompt/index.tsx: added the `session.interrupt.now` command — same guards as upstream's `session.interrupt` but it aborts on the first press, and its `enabled` requires a busy session AND an empty prompt so ctrl+c falls through to `prompt.clear` (draft present) or `app.exit.confirm` (idle) instead of racing them on layer order; gathered the command in the `prompt.palette` binding list; the busy footer hint reads `interruptShortcut()` (resolves `session.interrupt`, mapping `escape` → `esc` and falling back to the `session.interrupt.now` label when escape is unbound) instead of the hardcoded `esc`. app.tsx: split `app_exit_confirm` out of the shared exit `useBindings` into its own layer whose `enabled` also requires `sync.data.session_status[route.data.sessionID]` to be idle, so ctrl+c can't arm the quit confirm mid-turn (`app_exit` keeps the shared predicate). Covered by the fork-only `packages/tui/test/interrupt.view.test.tsx`. See also the double-Ctrl+C row above, whose scope this narrows. | Ctrl+C is what many users reach for to stop a running agent, so it interrupts too; `escape` stays the advertised one in the busy footer and the home tip. Gating on `enabled` rather than binding order keeps the ctrl+c precedence (clear draft → interrupt → exit confirm) deterministic, since same-priority layers resolve by registration order and `useBindings` re-registers on every reactive change. | Keep the `session_interrupt_now` definition + mapping, the `session.interrupt.now` command and its `enabled` predicate, the footer shortcut memos, the split exit layer, and the test; take upstream's surrounding changes. If upstream binds `ctrl+c` to something else in the prompt, re-audit the precedence chain end to end. | diff --git a/packages/opencode/src/cli/cmd/run/theme.ts b/packages/opencode/src/cli/cmd/run/theme.ts index 6c66ed15b3..8f51000c91 100644 --- a/packages/opencode/src/cli/cmd/run/theme.ts +++ b/packages/opencode/src/cli/cmd/run/theme.ts @@ -7,6 +7,7 @@ // palette if detection fails. import { RGBA, SyntaxStyle, type CliRenderer, type ColorInput, type TerminalColors } from "@opentui/core" import type { TuiThemeCurrent } from "@opencode-ai/plugin/tui" +import { brand } from "@/cli/logo" import type { EntryKind } from "./types" type Tone = { @@ -21,8 +22,7 @@ export type RunSplashTheme = { right: ColorInput leftShadow: ColorInput rightShadow: ColorInput - // Fork: the `codev --mini` badge glyph, in the brand secondary (teal in dark, - // purple in light) so it matches the home banner wordmark. The badge is a flat + // Fork: the `codev --mini` badge glyph, in the brand red. The badge is a flat // outline with no shadow, so there is no matching shadow color. mark: ColorInput } @@ -490,14 +490,14 @@ function quantizeTheme(theme: TuiThemeCurrent, indexed: RGBA[]): TuiThemeCurrent } } -// Fork: the codev brand secondary (packages/tui/src/theme/assets/codev.json — -// darkSecondary/lightSecondary). The mini badge uses these exact truecolor -// values, matching the home-screen banner, rather than the run theme's own -// `secondary` (which is derived from the terminal palette — magenta — and would -// quantize to a dull, off-brand cell). -const BRAND_SECONDARY = { dark: rgba("#0891b2"), light: rgba("#7b5bb6") } as const +// Fork: the landing page draws the compact "C" mark in the brand red in both +// light and dark (codev-landing-page app/icon.svg — the same glyph the badge +// renders), so the mini badge uses that exact truecolor value rather than the run +// theme's own `secondary` (which is derived from the terminal palette — magenta — +// and would quantize to a dull, off-brand cell). +const BRAND_MARK = rgba(brand.red) -function splashTheme(theme: TuiThemeCurrent, indexed: RGBA[], pick: "dark" | "light"): RunSplashTheme { +function splashTheme(theme: TuiThemeCurrent, indexed: RGBA[]): RunSplashTheme { const left = nearestIndexed(indexed, theme.textMuted) const right = nearestIndexed(indexed, theme.text) return { @@ -505,9 +505,9 @@ function splashTheme(theme: TuiThemeCurrent, indexed: RGBA[], pick: "dark" | "li right, leftShadow: splashShadow(indexed, theme.background, left, 0.14), rightShadow: splashShadow(indexed, theme.background, right, 0.14), - // Brand truecolor, left unquantized so it renders as the banner's exact teal - // (or purple in light) instead of the nearest palette cell. - mark: BRAND_SECONDARY[pick], + // Brand truecolor, left unquantized so it renders as the exact brand red + // instead of the nearest palette cell. + mark: BRAND_MARK, } } @@ -615,9 +615,6 @@ function tone(body: ColorInput, start?: ColorInput): Tone { const fallbackSplashIndexed = Array.from({ length: 256 }, (_, index) => RGBA.fromIndex(index)) const fallbackSplashLeft = RGBA.fromIndex(67) const fallbackSplashRight = RGBA.fromIndex(110) -// Fork: brand teal (the dark-mode secondary) for the mini badge when palette -// detection fails — the same exact truecolor value the live path uses. -const fallbackSplashMark = rgba("#0891b2") export const RUN_THEME_FALLBACK: RunTheme = { background: RGBA.fromValues(0, 0, 0, 0), @@ -651,7 +648,8 @@ export const RUN_THEME_FALLBACK: RunTheme = { right: fallbackSplashRight, leftShadow: splashShadow(fallbackSplashIndexed, RGBA.fromValues(0, 0, 0, 0), fallbackSplashLeft, 0.14), rightShadow: splashShadow(fallbackSplashIndexed, RGBA.fromValues(0, 0, 0, 0), fallbackSplashRight, 0.14), - mark: fallbackSplashMark, + // Fork: the brand mark stays the exact brand red when palette detection fails. + mark: BRAND_MARK, }, block: { highlight: seed.highlight, @@ -698,7 +696,7 @@ export async function resolveRunTheme(renderer: CliRenderer): Promise<RunTheme> return map( footerTheme, scrollbackTheme, - splashTheme(scrollbackTheme, indexed, pick), + splashTheme(scrollbackTheme, indexed), syntax, shared.generateSubtleSyntax(syntaxTheme), ) diff --git a/packages/opencode/src/cli/ui.ts b/packages/opencode/src/cli/ui.ts index 05f0454b8f..f37ed01487 100644 --- a/packages/opencode/src/cli/ui.ts +++ b/packages/opencode/src/cli/ui.ts @@ -44,7 +44,7 @@ export function logo(pad?: string) { if (!process.stdout.isTTY && !process.stderr.isTTY) { return wordmark.map((row) => (pad ?? "") + row).join(EOL) } - // Bold codev brand teal with the drop shadow, matching the TUI home banner. + // Bold, no shadow, "dev" in the brand red — matching the TUI home banner. return ansiWordmark(pad).join(EOL) } diff --git a/packages/opencode/test/cli/run/theme.test.ts b/packages/opencode/test/cli/run/theme.test.ts index cb1d024ab6..0c42d68dad 100644 --- a/packages/opencode/test/cli/run/theme.test.ts +++ b/packages/opencode/test/cli/run/theme.test.ts @@ -74,9 +74,10 @@ test("returns syntax styles and indexed splash colors", async () => { expectIndexed(theme.splash.right) expectIndexed(theme.splash.leftShadow) expectIndexed(theme.splash.rightShadow) - // The badge is the exact brand teal (truecolor, not a palette cell) so it - // matches the home-screen banner rather than the nearest indexed color. - expect(expectRgba(theme.splash.mark).toInts().slice(0, 3)).toEqual([8, 145, 178]) + // The badge is the exact brand red (truecolor, not a palette cell) so it + // matches the landing page's compact "C" mark rather than the nearest + // indexed color. + expect(expectRgba(theme.splash.mark).toInts().slice(0, 3)).toEqual([238, 0, 51]) expectIndexed(theme.block.highlight) expectIndexed(theme.block.warning) expectRgba(theme.footer.highlight) diff --git a/packages/tui/src/component/logo.tsx b/packages/tui/src/component/logo.tsx index 07e9598d80..2c2b7f7bbf 100644 --- a/packages/tui/src/component/logo.tsx +++ b/packages/tui/src/component/logo.tsx @@ -1,61 +1,26 @@ import { TextAttributes } from "@opentui/core" -import { For, type JSX } from "solid-js" -import { tint, useTheme } from "../context/theme" -import { logo } from "../logo" +import { For } from "solid-js" +import { useTheme } from "../context/theme" +import { brand, wordmarkHalves } from "../logo" -// Fork: lowercase "codev" wordmark (half the height of the block banner) in the -// theme's brand secondary (teal in dark, purple in light), with shadow marks. +// Fork: lowercase "codev" wordmark (half the height of the block banner), +// matching the landing page wordmark (codev-landing-page +// app/components/PixelWordmark.tsx): "co" in the brand navy on light and the +// theme foreground on dark, "dev" in the brand red, with no drop shadow. export function Logo() { - const { theme } = useTheme() - - const renderLine = (line: string, bold: boolean): JSX.Element[] => { - const brand = theme.secondary - const shadow = tint(theme.background, brand, 0.25) - const attrs = bold ? TextAttributes.BOLD : undefined - return Array.from(line).map((char) => { - if (char === "_") { - return ( - <text fg={brand} bg={shadow} attributes={attrs} selectable={false}> - {" "} - </text> - ) - } - if (char === "^") { - return ( - <text fg={brand} bg={shadow} attributes={attrs} selectable={false}> - ▀ - </text> - ) - } - if (char === "~") { - return ( - <text fg={shadow} attributes={attrs} selectable={false}> - ▀ - </text> - ) - } - if (char === ",") { - return ( - <text fg={shadow} attributes={attrs} selectable={false}> - ▄ - </text> - ) - } - return ( - <text fg={brand} attributes={attrs} selectable={false}> - {char} - </text> - ) - }) - } + const { theme, mode } = useTheme() return ( <box> - <For each={logo.left}> - {(line, index) => ( + <For each={wordmarkHalves}> + {(row) => ( <box flexDirection="row" gap={1}> - <box flexDirection="row">{renderLine(line, true)}</box> - <box flexDirection="row">{renderLine(logo.right[index()], true)}</box> + <text fg={mode() === "light" ? brand.navy : theme.text} attributes={TextAttributes.BOLD} selectable={false}> + {row.left} + </text> + <text fg={brand.red} attributes={TextAttributes.BOLD} selectable={false}> + {row.right} + </text> </box> )} </For> diff --git a/packages/tui/src/logo.ts b/packages/tui/src/logo.ts index c48d9918ff..aad9b140bf 100644 --- a/packages/tui/src/logo.ts +++ b/packages/tui/src/logo.ts @@ -5,34 +5,40 @@ export const logo = { right: [" ▄ ", "█▀▀█ █▀▀█ █__█", "█__█ █^^^ █__█", "▀▀▀▀ ▀▀▀▀ ▀▀ "], } -// Fork: the lowercase "codev" wordmark composed into plain lines (shadow marks -// substituted to glyphs) for CLI banners that color each line uniformly. +// Fork: the landing-page brand palette (codev-landing-page app/globals.css — +// --color-brand-navy / --color-brand-red). The wordmark draws "co" in navy and +// "dev" in red, matching codev-landing-page app/components/PixelWordmark.tsx. +export const brand = { + navy: "#19224c", + red: "#ee0033", +} + +// Fork: the lowercase "codev" wordmark with the shadow marks substituted to +// glyphs, kept split into its two halves so each takes its own brand color. const substituteMarks = (line: string) => Array.from(line, (char) => char === "_" ? " " : char === "^" || char === "~" ? "▀" : char === "," ? "▄" : char, ).join("") -export const wordmark = logo.left.map( - (line, index) => `${substituteMarks(line)} ${substituteMarks(logo.right[index] ?? "")}`, -) +export const wordmarkHalves = logo.left.map((line, index) => ({ + left: substituteMarks(line), + right: substituteMarks(logo.right[index] ?? ""), +})) + +// Fork: the wordmark composed into plain lines, for banners that cannot color +// the halves separately. +export const wordmark = wordmarkHalves.map((row) => `${row.left} ${row.right}`) -// Fork: ANSI-colored lowercase wordmark for CLI banners, matching the TUI home -// banner: bold brand teal with a drop shadow behind the mark cells. The shadow -// is tint(theme dark background #0a0a0a, teal, 0.25) = #0a2c34. +// Fork: ANSI-colored lowercase wordmark for CLI banners, matching the landing +// page wordmark: bold, no drop shadow, with "dev" in the brand red. "co" keeps +// the terminal's default foreground — the CLI banner cannot detect light vs +// dark, and the default foreground is the readable counterpart of the landing +// page's navy-on-white / white-on-dark. export function ansiWordmark(pad = "") { const reset = "\x1b[0m" - const teal = "\x1b[1m\x1b[38;2;8;145;178m" - const shadowFg = "\x1b[38;2;10;44;52m" - const shadowBg = "\x1b[48;2;10;44;52m" - const draw = (line: string) => - Array.from(line, (char) => { - if (char === "_") return `${shadowBg} ${reset}` - if (char === "^") return `${teal}${shadowBg}▀${reset}` - if (char === "~") return `${shadowFg}▀${reset}` - if (char === ",") return `${shadowFg}▄${reset}` - if (char === " ") return " " - return `${teal}${char}${reset}` - }).join("") - return logo.left.map((line, index) => `${pad}${draw(line)} ${draw(logo.right[index] ?? "")}`) + const bold = "\x1b[1m" + // brand.red as truecolor. + const red = "\x1b[1m\x1b[38;2;238;0;51m" + return wordmarkHalves.map((row) => `${pad}${bold}${row.left}${reset} ${red}${row.right}${reset}`) } // Fork: the "c" of the codev wordmark, drawn as the compact badge on the From b07b5e6ed5bf0afc55f507a9f41dc96840b61a69 Mon Sep 17 00:00:00 2001 From: minhn4 <nguyenm.cuny@gmail.com> Date: Thu, 30 Jul 2026 18:10:26 +0700 Subject: [PATCH 2/2] minor changes --- CHANGELOG.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f9a4638878..9e7546194a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,16 +9,6 @@ file is the human-readable summary of what changed and why. ### TUI -- **Banner now matches the website wordmark** — the `codev` pixel wordmark on the - home screen and at the top of a session is drawn in the landing page's brand - colors instead of the theme's accent: `co` in brand navy (`#19224c`) on light - and the theme foreground on dark, `dev` in brand red (`#ee0033`), with the drop - shadow behind the glyphs removed. The glyph art is unchanged. The CLI banner - (`codev --help`, `codev upgrade`, `codev web`, `codev uninstall`) follows: bold, - no shadow, `dev` in brand red and `co` in the terminal's default foreground. - The `codev --mini` entry and exit badges — the compact `C` glyph — turn brand - red too, matching the website's favicon, instead of the theme's teal/purple. - - **No more duplicated agent · model line** — the trailer under the last assistant reply (`▣ Build · MiniMax/MiniMax-M2.7 · 3m`) used to repeat the agent and model the prompt footer already shows one line below. When they @@ -73,7 +63,7 @@ file is the human-readable summary of what changed and why. rename (PR #212) can't attribute a `netgate` config as CoDev-managed. - **Custom providers get a name, not an id** — the "Custom provider" flow now - asks for an optional provider *name* ("Acme AI Gateway") instead of a raw + asks for an optional provider _name_ ("Acme AI Gateway") instead of a raw id. The id is slugged from the name (`acme-ai-gateway`, `[a-z0-9-]`, 32 chars max) and previewed live under the field; the name is written as the provider block's display name, so the model picker shows it instead of the