chore(branding): strip OpenCode residue for Phase 1#18
Conversation
Fixes P0 brand residue discovered during v0.2.3 smoke test:
- packages/app/index.html: <title> OpenCode → PawWork
- packages/ui/src/assets/favicon/site.webmanifest: name + short_name
→ PawWork (controls PWA add-to-homescreen label)
- packages/desktop-electron/package.json: homepage → pawwork.ai
(Yuhan owns the domain), author → PawWork
- root package.json: name opencode → pawwork, description updated
from generic dev tool tagline to actual PawWork positioning
('Desktop AI workstation for knowledge workers'), repository.url
pointed to Astro-Han/pawwork
Reduces theme system from 38 themes to 1 (pawwork only): Motivation - Target user is non-technical knowledge worker, not developer. They won't open a theme picker to choose 'Dracula' vs 'Monokai'. - Each theme JSON must match desktop-theme.schema.json. Future refactors that add CSS tokens would touch 38 files. - Upstream cherry-picks that adjust color semantics become 38x maintenance burden for near-zero user value. - Aligns with simplicity principle in AGENTS.md. Changes - Delete 37 theme JSON files (kept pawwork.json only, which is already the default via DEFAULT_THEME_ID). - default-themes.ts: collapse 38 imports/exports/registrations to just pawworkTheme. - theme/index.ts: drop 37 re-exports, keep DEFAULT_THEMES and pawworkTheme. - context.tsx: remove oc2ThemeJson import + oc-2 seed entry in store.themes. (Other oc-2 special-case branches left as dead code — they never fire without an oc-2 theme to load, and cleaning them up is a separate polish commit.) Regression audit - No other file imports the removed theme constants (grep clean). - User config with 'theme: dracula' falls back gracefully: see context.tsx L308 console.warn + default theme fallback. - CLI TUI theme system (packages/opencode/src/cli/cmd/tui/) is a separate subsystem, unaffected by this change. - Typecheck passes across all 7 packages. Known gap - pawwork.json has both 'light' and 'dark' sections but 'dark' palette is a copy of 'light' (not actually dark). context.tsx L135 hardcodes pawwork → light. Designing a proper dark palette is follow-up work.
Drops 14 inherited locales (ar/br/bs/da/de/es/fr/ja/ko/no/pl/ru/th/tr)
that have no active maintainer on the PawWork team, and keeps only
the locales that map to actual target audiences:
- en: global OSS community
- zh: 纯刻 internal + 中文市场 (primary)
- zht: Traditional Chinese fallback for zh-Hant systems
Rationale
- Inherited 16 languages were 'free' from OpenCode but become a
liability once branding/key-set drifts — non-maintained locales
silently show stale OpenCode text (confirmed in scan: zh/en have
~27 refs each, others unverified, all would need careful sweep).
- Phase 1 audience is Chinese + English (per AGENTS.md '双语产品').
- Recovery is cheap: any removed locale can be restored from git
history later when real demand appears.
Files deleted
- packages/app/src/i18n/{ar,br,bs,da,de,es,fr,ja,ko,no,pl,ru,th,tr}.ts
- packages/ui/src/i18n/{ar,br,bs,da,de,es,fr,ja,ko,no,pl,ru,th,tr}.ts
- packages/desktop-electron/src/renderer/i18n/{ar,br,bs,da,de,es,fr,ja,ko,no,pl,ru}.ts
(th/tr never existed in desktop-electron subset)
Loaders updated
- packages/app/src/context/language.tsx: Locale union/LOCALES/INTL/
LABEL_KEY/loaders/localeMatchers all collapsed to 3 entries.
- packages/desktop-electron/src/renderer/i18n/index.ts: same narrowing.
- packages/app/src/i18n/parity.test.ts: now only asserts zh + zht
translate the target keys (down from 16 locales).
Typecheck passes across all 7 packages.
Sweeps en/zh/zht i18n values where 'OpenCode' refers to the app itself (e.g. update toasts, dialog descriptions, settings labels). Preserved by design: - 'OpenCode Zen' service name — third-party model routing service PawWork uses as backend. Renaming would misrepresent upstream. - 'opencode.ai/zen' URL — same reason, real Zen service link. - 'OpenCode Go' service name — same reason. - 'opencode.json' config file references — will be handled in a separate rename commit alongside the actual config loader change. - All dictionary keys (e.g. 'dialog.provider.opencode.note') — keys are identifiers referenced elsewhere in code; renaming them is a larger refactor touching consumers, separate pass. Technique: sed two-pass with sentinels to replace 'OpenCode' while protecting 'OpenCode Zen' / 'OpenCode Go' phrases from the sweep. Dry-run diff reviewed per file before applying. Typecheck passes all 7 packages.
Product-level config file rename: PawWork now prefers pawwork.json
/ pawwork.jsonc but still reads opencode.json / opencode.jsonc for
backward compatibility (existing 纯刻 team installs and any prior
Yuhan-local configs keep working without migration).
Loader additions
- config.ts globalConfigFile candidates array
- config.ts loadGlobal reads (pawwork merged after opencode so it
overrides if both exist)
- config.ts project + managedDir for-loops (same ordering)
- mcp.ts resolveConfigPath candidates (pawwork first; default-to
changed from opencode.json to pawwork.json for new installs)
User-facing strings updated to pawwork.json
- cli/error.ts provider/model mismatch hint
- cli/cmd/providers.ts credential-stored warning (x2)
- cli/cmd/mcp.ts OAuth-capable server hint
- cli/cmd/tui/.../tips-view.tsx home tips
- command/template/initialize.txt (AI-generation template)
- app/src/i18n/{en,zh,zht}.ts (2 strings each: plugins empty state
+ checkConfig hint)
Not changed (intentional)
- .opencode/ directory name detection — additive support for
.pawwork/ would be a scope expansion; legacy path still works.
- CLI TUI theme file 'theme/opencode.json' import — separate
subsystem theme, no user impact.
- Brew/scoop manifest URLs in installation/index.ts — upstream
OpenCode distribution channels, not PawWork's.
- Zen service URL 'opencode.ai/zen' — real third-party service.
- Code comments mentioning 'opencode.json' for historical context
(tui.ts, tui-migrate.ts, provider.ts).
Typecheck passes all 7 packages.
|
I don't think this is ready to merge. This change affects the upgrade path for existing installs, not just the settings surface. Startup still trusts I'm treating this as P1 and merge-blocking. Before merge, we should migrate unknown stored theme ids to |
Pre-fix path: if a stored opencode-theme-id was dracula, nightowl, oc-1/oc-2 or any other theme we bundled before this cleanup, the preload script would inject cached CSS for the missing theme and context.tsx would silently skip applying anything. Users on upgrade could be stuck with stale CSS and a data-theme attribute pointing at a theme that no longer exists. Treat pawwork as the single valid bundled theme: preload rewrites any other stored id to pawwork and clears the cached CSS; context init and onMount gate the stored id against knownThemes() before trusting it. Regression tests cover oc-1, oc-2, dracula, nightowl, amoled.
- i18n: resolve zh-TW / zh-HK / zh-MO to zht instead of zh. Both detectors previously only treated strings containing the literal "hant" as Traditional Chinese, so region-form locales common on Taiwan/HK/Macao systems fell through to Simplified. Fixed in packages/app/src/context/language.tsx and packages/desktop-electron/src/renderer/i18n/index.ts with matching helpers and unit tests. - theme: remove dead oc-2 branches and the now-unused normalize() remap from the theme context. With only `pawwork` bundled, the "known themes" gate is the single rule for onStorage / setTheme / previewTheme. Also trim the label map to just `pawwork`, removing residual OpenCode-era labels. - brand: rebrand remaining user-visible "OpenCode" occurrences to PawWork: apple-mobile-web-app-title in favicon.tsx, desktop theme schema title/description, server-error fallback pointing at opencode.json, and the status-popover plugin-empty split token that keyed off "opencode.json" while the translation already says pawwork.json.
- Rewrite the theme-migration e2e ("legacy oc-1 -> oc-2" -> "legacy
theme ids -> pawwork"). The old assertion lined up with the pre-
Phase-1 two-theme world; the new one seeds `dracula` and checks
migration to pawwork plus cached CSS cleanup, matching the actual
preload logic.
- Skip two e2es that depended on multi-theme and dark-mode switching
(color-scheme toggle, theme dropdown with >1 option). Phase-1
bundles a single light-locked pawwork theme, so these tests can
not meaningfully run until a dark palette or a second theme
lands.
- Drop the dark-mode click + assertion from the font-rehydrate e2e
and rename it accordingly, so the font persistence check still
runs.
- Update the "changing language" e2e from Deutsch to 简体中文, the
actual Phase-1 locale.
- Rename the tautological theme-preload test. The previous name
("does not touch localStorage when the stored theme is already
PawWork") was wrong because preload unconditionally writes
`opencode-color-scheme=light`; the renamed test now asserts that
write and keeps the dark-mode override check honest.
|
Addressed the review plus two additional crosscheck rounds. Three follow-up commits pushed:
Fixes the original concern.
Second crosscheck round (Codex + Claude) surfaced:
Third round caught stale e2e tests:
Pre-existing on All tests green ( |
The project-config loader in loadAll() called projectFiles("opencode", ...)
and stopped there, so a repo-level pawwork.json was never discovered on
startup even though the rest of the PR had switched write paths, help
text, and error messages to that filename. Walk the directory tree twice:
once for opencode.json(c) (legacy), once for pawwork.json(c). Merge
order preserves the rest of the cascade: pawwork files override opencode
files at the same level, matching how globalConfigFile and the managed-
dir loops already behave.
|
Confirmed — Fixed in
|
- Sidebar help button opened https://opencode.ai/desktop-feedback, which is an OpenCode feedback page and offers nothing to a PawWork user. Send it to the PawWork GitHub issues page instead, the actual place we track feedback. - Release-notes highlight fetch pointed at opencode.ai/changelog.json, so existing upgrade paths would have shown OpenCode changelog entries in the "What's new" dialog. Switch to the GitHub Releases API for this repo and synthesize a single highlight per release from the release body's first meaningful line (markdown headings skipped, bullet markers stripped, truncated to 200 chars). The structured `highlights` schema remains supported for any future switch to a hosted changelog.json. Added highlights.test.ts covering the new body-summary path and the legacy schema regression.
|
Retargeted two remaining OpenCode URLs in
Not touched in this PR (docs links to upstream OpenCode docs that are low-frequency and still useful for advanced users; queued as a separate docs-link-cleanup):
|
The dev-only DebugBar pinned to the bottom-right corner was overlapping session UI such as the "next step" button on the Show Case cards, making parts of the app unreachable during local review. Add a persistent collapse toggle: clicking the × shrinks the bar to a "debug" pill in the same corner; clicking the pill re-expands it. State survives reload via localStorage (`pawwork-debug-bar-collapsed`).
|
Follow-up Separate product bug discovered while reviewing but out of scope for this PR, filed as #21: document-processing and data-analysis Show Case skills skip |
P1: project config precedence. The `loadAll` loop called
`projectFiles("opencode")` and `projectFiles("pawwork")` separately,
then concatenated root→child sequences. A parent-directory
`pawwork.json` could therefore override a nearer child
`opencode.json`, breaking the "innermost wins" invariant used
elsewhere in the cascade. Extend `projectFiles` to accept an array
of names, interleaving them inside a single `findUp` pass. Callers
pass `["opencode", "pawwork"]`, so per-directory pawwork wins over
opencode while the innermost directory still wins overall.
P2: OAuth + uninstall brand residue. Auth success/failure browser
pages and the uninstall CLI intro/outro text still said "OpenCode".
Rebranded to PawWork.
P2: drop the tautological `oc-theme-preload` null assertions in
`theme-preload.test.ts`. The current preload script never creates
that element (context.tsx only removes it), so the assertions were
always true regardless of behavior.
P2 finish of the brand sweep: - mcp/oauth-provider.ts: `client_name` and `client_uri` sent during OAuth registration were still "OpenCode" and "https://opencode.ai", so MCP provider consent screens showed the upstream brand instead of PawWork. Rebranded both. - cli/cmd/tui/app.tsx: terminal window title and post-update dialog text still read "OpenCode" (and the session-title prefix was "OC | ..."). Rebranded to "PawWork" / "PW | ...". P3 clean-up in theme-preload.test.ts: the "keeps PawWork light-only even when system prefers dark" test mocked `matchMedia` to return dark, but the current preload script never reads `matchMedia` - the assertion only held because preload hardcodes light. Renamed the test to describe what is actually verified (stored dark scheme is overridden to light) and removed the dead mock. Not in this PR, filed for follow-up: - `.opencode/` directory cascade in config.ts:1433 merges in nearest-to-root order (via `Filesystem.up`), so outer project configs override inner ones. Pre-existing upstream behavior; untouched by this PR. Separate issue.
|
Round 6 crosscheck (both reviewers complete, no truncation). Two commits pushed:
Pre-existing upstream bug filed separately as #22: Declined (intentional / out of scope):
|
TUI brand sweep (5 user-visible strings that the first sweep missed because they live in the CLI TUI, not the Electron renderer): - routes/session/permission.tsx: "until OpenCode is restarted" ×2 and "Tell OpenCode what to do differently" rebranded. - feature-plugins/sidebar/footer.tsx: the Getting-started footer line "OpenCode includes free models" rebranded. - feature-plugins/home/tips-view.tsx: five prose tips rebranded (OAuth auto-handle, auto-format, LSP, plugin example, `opencode serve` description). CLI command strings themselves (`opencode run`, `opencode serve`, etc.) are left alone because the binary name is `opencode`; renaming that belongs with the `@opencode-ai/*` package-rename PR. Comments (no behavior change): - oc-theme-preload.js: note that unconditional `color-scheme=light` write assumes pawwork is the only bundled theme and is light-locked; must become theme-aware when a dark-capable theme lands (tracked in #23). - config.ts project-root cascade: expand the precedence comment to cross-reference `Filesystem.findUp({ rootFirst: true })` semantics so a future refactor does not accidentally flip the invariant. - config.ts managed-dir cascade: mirror the same "pawwork wins within directory" comment that the project-root site already has.
|
Round 7 (final) crosscheck — both reviewers complete, no truncation. One commit TUI brand sweep (user-visible strings the earlier sweep missed):
CLI command strings ( Comments (no behavior change):
Deferred to #23: the color-scheme settings dropdown shows system/light/dark even though pawwork is light-locked. Pawwork-dark is next up; the dropdown becomes functional when that lands. Not patched in this PR to avoid throwaway interim UI. Declined as out of scope / intentional:
14 commits total on this branch, 7 crosscheck rounds passed. |
P1 defensive / user-visible fixes:
- oc-theme-preload.js: wrap localStorage reads and writes in try/catch.
Private-mode / storage-disabled browsers would otherwise throw before
the dataset attributes are set, producing an unstyled first paint.
- highlights.tsx: log the HTTP status when the GitHub releases fetch
returns non-ok (403 rate-limit, 304 not-modified, 404, etc). Previously
silently discarded; now at least visible in devtools for diagnosis.
P2 user-visible brand routing:
- error-component.tsx (TUI crash reporter): retarget the "open issue"
URL from github.com/anomalyco/opencode to github.com/Astro-Han/pawwork
so PawWork crash reports land in the PawWork repo, not upstream.
Also rename `opentui: fatal:` title prefix and `opencode-version`
query param to pawwork equivalents.
- tui/app.tsx "Open docs" action, dialog-custom-provider "Learn more",
and settings-general theme "Learn more": all retargeted from
opencode.ai/docs/... to github.com/Astro-Han/pawwork#readme. PawWork
has no standalone docs site yet; README is the current source of truth.
P2 remove OpenCode Go upsell: PawWork does not resell or integrate with
OpenCode Go paid tier. Deleted `dialog-go-upsell.tsx`, its detection
kv keys and `event.on("session.status", ...)` handler in
routes/session/index.tsx, and the `GO_UPSELL_MESSAGE` constant + its
FreeUsageLimitError branch in session/retry.ts (now just returns a
plain "Free usage exceeded").
P2 dev-only a11y (debug-bar): bumped the collapsed pill and the inline
close button from `opacity-40` → `opacity-60` and added
`focus-visible:ring-2 focus-visible:ring-interactive-base`. Previously
the pill was near-invisible for keyboard users with no focus style.
P3 remove stale tips: two tips-view.tsx entries referenced functionality
we don't ship — `/share` which publishes to opencode.ai and a
`ghcr.io/anomalyco/opencode` docker image. Neither PawWork has its own
share service nor a docker distribution; deleted the tips.
|
Round 8 crosscheck results addressed in P1 defensive / diagnostic:
P2 brand routing:
P2 remove OpenCode Go upsell (net −95 lines):
P2 dev-only a11y (debug-bar): pill + close button bumped from P3 stale tips: removed two Declined / intentional (in order of the review table):
15 commits, 8 crosscheck rounds passed. |
- error.ts: drop "opencode" subject from MCP failure; remove stale `opencode models` hint, keep pawwork.json config pointer - config.ts: remove $schema auto-inject that silently wrote https://opencode.ai/config.json into user files; delete its test - config.ts + paths.ts: extract PROJECT_CONFIG_NAMES / PROJECT_CONFIG_FILENAMES with ordering invariant comment; replace 4 hardcoded filename arrays (global, project, managed, .opencode) so pawwork-wins-over-opencode precedence has a single source - config.test.ts: add regression test pinning pawwork.json vs opencode.json same-directory precedence - theme context: add canSwitchColorScheme() accessor; settings-general hides color-scheme row and layout only registers theme.scheme.* / theme.cycle commands when actually switchable, so Phase 1 ships no no-op controls or command-palette entries - initialize.txt: "OpenCode config" -> "PawWork config" - theme-preload.test: merge duplicate color-scheme locking test - config.ts: de-brand tui-keys deprecation log
- desktop-electron notification: remove external opencode.ai icon URL; Electron falls back to the packaged PawWork app icon - sidebar project avatar: delete OPENCODE_PROJECT_ID special-case that pulled https://opencode.ai/favicon.svg for upstream's demo project (PawWork users never have that project ID) - ACP handshake: rename user-visible labels to PawWork (authMethod name, terminal-auth label, agentInfo.name). The `opencode auth login` instruction stays literal since the CLI binary is still named opencode (rename blocked on Zen proxy header work). Deliberately not addressed in this PR: - TUI dialog-provider OpenCode Go copy: TUI is out of Phase 1 GUI scope - GUI dialog-select-provider opencode-go "Recommended" tag: the service name is explicitly preserved per PR scope; the recommendation curation is a separate product question, not brand residue
Summary
Sweeps user-visible OpenCode brand residue discovered during the v0.2.3 smoke test (rolled back on 2026-04-17). The published v0.2.3 Release was deleted because the dmg shipped with Apple's design-template placeholder icons and various OpenCode strings in dialogs, settings, update toasts, etc. This PR covers the explicit-brand surfaces and narrows the Phase-1 product scope (1 theme, 2-3 locales) so a future re-release lands clean.
Scope and rationale
622a648package.jsonauthor/homepage/description/repo URLOpenCodestrings the user would see in tab title / PWA shortcut / About-panel metadataff02a27086deb4b5696b8OpenCode→PawWorkinen/zh/zhtvaluesOpenCode Zen/OpenCode Goservice names andopencode.ai/zenURL — Zen is a real third-party model-routing service PawWork uses as backend, renaming would misattribute upstream5c346546pawwork.json/pawwork.jsoncalongside legacyopencode.json/opencode.jsoncpawwork.jsonas the canonical filenameDeliberately out of scope
.opencode/directory-name detection (additive.pawwork/= larger refactor; legacy still works)@opencode-ai/*import path renames (~574 refs, blocked on Zen proxy header dependency verification perproject_rename_blockersmemory)opencode-theme-id/opencode.global.datlocalStorage keys (DevTools-only visibility, renaming breaks existing user data)$idURL indesktop-theme.schema.jsonpackages/opencode/src/cli/cmd/tui/context/theme/opencode.json(separate subsystem)installation/index.ts(upstream OpenCode distribution channels)pawwork-darkpalette —pawwork.json's dark section is currently a copy of light;context.tsxforces light via hardcoded check; designing a real dark variant is follow-upVerification
bun run typecheckgreen across all 7 packages after each commitpackages/app/src/i18n/parity.test.ts) now asserts zh + zht translate target keys (down from 16)Test plan
build.ymlwithphase=full, channel=prod, arch=arm64v0.2.Xproduced with correct paw-print icon visible on mounted dmgopencode.jsonconfigs still load without warningspawwork.jsonwhen presentNot included in this PR
pawwork-darkpalette design