Skip to content

LOCAL: name the titlebar app label "Orca (SwapLabs)" - #7

Merged
kiwidream merged 1 commit into
swaplabs/mainfrom
kiwidream/swaplabs-fork-rebrand-topbar
Sep 24, 2026
Merged

kiwidream merged 1 commit into
swaplabs/mainfrom
kiwidream/swaplabs-fork-rebrand-topbar

Conversation

@kiwidream

@kiwidream kiwidream commented Sep 24, 2026 •

Copy link
Copy Markdown

ELI5

The name printed in the top-left corner of the window now reads Orca (SwapLabs) instead of Orca, so you can tell at a glance that you are running our build rather than the one from upstream.

What Changed

Before: the left end of the title bar shows Orca next to the sidebar toggle.
After: it shows Orca (SwapLabs).

Nothing else moves. The label is the same clickable element as before (right-click still offers "Hide App Name"), and its accessible name changes with it, so a screen reader announces the same text that is on screen.

The mechanism: that label is a single localization entry, auto.App.5096cbbc86, and src/renderer/src/app-shell/TitlebarLeftControls.tsx is its only consumer. The entry is changed in two places that must agree — the English literal passed to translate() (used before the locale bundle loads at boot) and the catalog value in each of the six locale files. Product names are not translated here, so every locale carries the same string; the repository's own localization policy already classifies Orca and anything starting with Orca as a preserved brand term, so the new value keeps that treatment.

No new key was introduced: the key is a content hash of the original file path and text and is not regenerated by any gate, so reusing it keeps the diff to a value change instead of a rename across six catalogs.

Why

The fork ships builds that carry local patches. Upstream's packaging identity (appId, product name, updater feed) is deliberately left alone — renaming it breaks upgrading over an installed upstream app — so the window title bar is the cheapest honest place to say which build this is, and it is the one the user looks at most.

Alternatives considered:

  • Rename the app at packaging time. Rejected: it changes the install identity and the upgrade path for anyone already running upstream Orca, for a purely cosmetic gain.
  • A separate badge component next to the name. Rejected: more code and a new style surface for text that fits inside the existing label. The label's container is already max-width: min(280px, 32vw) with white-space: nowrap; Orca (SwapLabs) renders at roughly 105px at the label's 12px/600 weight, well inside that, and the title bar's left cluster is measured from the DOM so the tab strip's spacing follows automatically.
  • Change only en.json. Rejected: the other five locales would still show Orca, and the pre-boot fallback literal would disagree with the catalog.

This is a LOCAL: change under the fork policy — branding is SwapLabs-only forever, so it is based on swaplabs/main and targets swaplabs/main, and is not a candidate for upstreaming.

Linked Issue

N/A — fork-local branding, requested directly; there is no upstream issue and no SwapLabs issue for it.

Visual Proof

Not attached. Capturing it means launching a second Orca instance on a machine that is already running Orca against the same user data, which is not a safe thing to do unattended. The change is a one-string swap inside an existing element: the left end of the title bar reads Orca (SwapLabs) where it read Orca. Happy to attach a before/after from an installed fork build if you want one before merging.

Testing

Run from the worktree:

  • pnpm tc — passes.
  • pnpm test src/renderer/src/i18n/ — 20 files, 183 tests, all passing (covers the locale catalogs, the English-regression guard, and the brand/technical-literal rules).
  • pnpm run verify:localization-catalog — passes; all six locales verified against en.json.
  • pnpm run verify:localization-coverage — passes.
  • pnpm run verify:localization-runtime-catalog — passes.
  • pnpm run check:code-quality:changed — 0 new findings, design-system gate included.
  • pnpm run verify:localization-extraction — fails, but it fails identically on swaplabs/main without this change (three components.terminalPane.AgentResumeChooser.age.* keys missing from en.json, from the auto-resume work already on the branch). Verified by reverting this diff and re-running. Not introduced here, and not fixed here.

Platforms: exercised on macOS. The change is platform-independent string data with no path, shortcut, or process behaviour in it. Note that the label only renders where the OS keeps a native title bar — on Windows and Linux the same cluster draws the logo and application-menu button instead, so the text is unchanged there by design, exactly as before.

Remote/SSH, mobile, and backwards compatibility: no wire format, IPC channel, or persisted value is touched; this is renderer-local display text.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

No new test. The label has no behavioural test to extend, and the existing localization gates already assert that this key exists, is consistent across all six catalogs, and keeps its brand treatment — a test asserting the literal string would only restate the diff.

AI Disclosure

Written with Claude Code (Claude Opus 5).

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Security: none — static display text. Cross-platform: see Testing. Remote SSH / mobile: untouched. Performance: none. Backwards compatibility: none; no stored value reads this string.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason — reason given under Visual Proof
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred) — typecheck, the changed-code quality gate, the localization gates and the i18n suite were run locally; see Testing for the one pre-existing failure

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Distinguishes an installed SwapLabs fork build from upstream Orca at a glance.
The label is one i18n key used only by the titlebar's left cluster, so the
fallback literal and all six locale catalogs move together; brand text is not
translated, so every locale carries the same string.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tenki-reviewer

tenki-reviewer Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Review complete. No issues found — approved ✅.


Renames the titlebar app label from Orca to Orca (SwapLabs) across TitlebarLeftControls.tsx and all six locale files.

Reviewed commit: 018a89d

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-24T13:31:33.056846Z 018a89d PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@kiwidream
kiwidream merged commit 587c95d into swaplabs/main Sep 24, 2026
2 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 018a89d405

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

<div
className="titlebar-app-name"
aria-label={translate('auto.App.5096cbbc86', 'Orca')}
aria-label={translate('auto.App.5096cbbc86', 'Orca (SwapLabs)')}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the fork suffix for plugin locales

When a user selects an installed plugin language pack containing the previous auto.App.5096cbbc86 entry, i18next resolves that pack's value instead of this fallback because setRendererPluginLanguagePacks registers catalogs directly. Such packs will continue displaying Orca, removing (SwapLabs) and defeating this change's build-identification purpose. Keep the fork suffix outside the pack-overridable translation or move the changed copy to a key older packs cannot override.

Useful? React with 👍 / 👎.

kiwidream pushed a commit that referenced this pull request Oct 23, 2026
…ds a terminal asks main's layout (stablyai#32002)

* refactor(runtime): every reader of a terminal's pane goes through paneOfPty (L80)

With the layout-owner switch on, main's layout answers which tab and pane hold a terminal, and
nothing reads or writes the PTY record's tabId/paneKey copies or their graph stamp. Switch-off
reads the copies exactly as before.

* refactor(runtime): read a terminal's pane through the runtime's paneOfPty method

* fix(runtime): main's pane index answers paneOfPty before any window graph

A headless host never installs a window graph, so the query asked a publisher that did not exist
and every terminal read as paneless. The index is now made on first query; it publishes only once a
graph is installed, as before.

* test(runtime): pin that a query with no window publishes nothing

* test(runtime): a terminal summary reads its pane once (L80)

* feat(runtime): pin bind-first at registration, and one own-handle rule with or without a window (switch-on only)

* test(runtime): census and tests that every create path binds its pane before it registers (L80)

* test(runtime): a bind-first fixture that stands in for the spawn commit (L80)

* test(runtime): fixtures bind a terminal's pane before registering it, as the spawn commit does (L80)

Also, with the switch on: skip the bind-first check for a paired server's workspace, release a
closed tab's terminal ownership once main has dropped the pane, and publish a pane's agent status
from the terminal main's layout binds it to.

* feat(runtime): an exit after main's close still finds the pane it retires (switch-on only, L80 Q2)

Main's layout write records every pane it drops whose terminal no pane holds any more, in a new
optional session field closedTerminalPanesByPaneKey (same TTL and cap as closed-tab records).
Exit cleanup reads main's layout first, then that record by terminal id and incarnation, and
removes the entry it used. Older builds ignore the field.

* refactor(layout): one dropped-leaf step every live layout edit runs (switch-on only, L80 Q2)

The step receives each dropped pane (host, worktree, tab, leaf, terminal, incarnation), computed
across the whole layout so a moved pane is not dropped, and runs only for a commit that declares a
live edit. Today it records the closed-pane entries; a drop is recorded once per terminal.

* fix(runtime): a phone or headless tab close still stops and releases its live terminals after main's layout drops the panes (switch-on only, L80)

The headless close reads each row's live terminal before main's close drops the panes it is found
by, and a renderer-retired tab releases the owned terminal main left in no pane, as the explicit
close already did. Close tests now commit through main's layout and bind panes first.

* fix(runtime): a local workspace's terminal tab left in a paired server's partition is main's pane (switch-on only, L257)

Older builds could save a local worktree's tab rows into a runtime partition (stablyai#18202); its terminals
are local, so main's index takes those tabs for workspaces the router homes locally. A tab or
terminal the local partition also holds is indexed once, from there; SSH and server-owned
workspaces are skipped as before.

* test(layout): the legacy-close comparison leaves out the closed-pane record only layout-owner builds write (L80)

* test(pty): a pane whose failed start withdrew its tab resolves to terminal_not_found in layout-owner builds (L80 #7)

Main's layout no longer holds the pane, so there is no pane to answer for; window-owned builds keep
the disconnected stub from the exited terminal's record.

* test(runtime): orca-runtime suites bind panes first, as the real store and spawn commit do (L80)

Fake PTY spawns commit the binding into the pane the runtime wrote, stores list their partitions,
and SSH sessions sit in their ssh:<conn> partition; terminals registered directly bind first.

* fix(runtime): main's layout names a pane's terminal only when the workspace and stamped incarnation match (switch-on only, L80)

A record made before its adoption registers (a host restart) or a local id reused after one
matched a saved binding by id alone, so a phone or paired desktop could be handed a second handle
for the pane before the terminal's own handle arrived and stay on it.

* test(runtime): a restored pane is only unnamed in the adoption gap, and no local record rescues a paired server's tab (L80)

* test(runtime): recovery, close and routing suites set up terminals as production does (L80)

A recoverable pane is one the host inventory dropped with no exit, live terminals register
rather than appear only in the window graph, and SSH stores route as the real Store does.

* test(runtime): suspects 3, 4 and 5 bind, register and apply as production does; PR-1's main-layout closes in the part-10 and part-11 close tests (L80)

* test(runtime): a fixture store applies main's layout commands as a profile does; PR-1's own closes in the part-05, part-08 and UUID-leaf close tests (L80)

* test(runtime): the profile-store stand-in moves to its own fixture, keeping bind-first under the line limit (L80)

* test(runtime): orphan adoption reads the pane from main's layout with the switch on, not the record copy (L80)

* test(runtime): a phone's pane close and a pinned tab close are main's layout closes with the switch on (stablyai#31571), in the no-widening and surface-close tables (L80)

* test(runtime): a paired phone close commits in main's layout with no relay ack when the switch is on (stablyai#31571) (L80)

* fix(terminal): a parked pane resumes its agent once main has closed the other pane that held the session (layout-owner build, L80 P1)

* test(runtime): the parked-pane suite's restart holder is a saved pane with no registered terminal, under the line limit (L80 P1)

* fix(terminal): a spawn commit that binds a reused local terminal id unbinds it from the saved pane of the older process (layout-owner build, L80 P2)

* test(runtime): the legacy OSC row test uses the UUID leaf main's layout holds and registers its live terminal with the switch on (L80 E5)

* test(runtime): the pty-exit echo close of a dead headless tab is expected to fail with the switch on until W4's close PR (L255)

* fix(terminal): a terminal record that arrives, changes incarnation or moves workspace after main composed its panes re-composes them, so a restored pane lists once (layout-owner build, L80)

* fix(terminal): a pane bound to a terminal a paired server runs keeps that binding and host, and is never indexed as a local spilled tab (layout-owner build, L80/L257)

* fix(terminal): a pane main owns no longer proves its terminal runs; the host's inventory and the terminal's record decide, so a dropped shell reads disconnected (layout-owner build, L80)

* fix(terminal): an SSH pane whose host says its shell ended keeps its leaf on a negative or failed code, as the window does, so recovery starts a shell in it; a stale handle learns the connected replacement (layout-owner build, L80)

* fix(agent-launch): a terminal main's layout still binds counts as running through a host listing gap, so a launch never starts a second shell in its pane (layout-owner build, L80)

* fix(terminal): a disconnected terminal main still binds stays a stop target, never reads stopped unproven, and keeps an exit wait waiting until a certified exit (layout-owner build, L80)

* test(terminal): the wait unit fixture answers the exit-settled dependency

* test(terminal): realistic fixtures for three phone-row tests: unique leaf ids per pane, and a window that draws main's own panes (layout-owner build, L80)

* fix(terminal): a stale handle learns a pane's replacement only when it was resolved for that pane, its own terminal ended, and the replacement is connected there now (layout-owner build, L80)

* test(session-tabs): switch-on variant for a window's chat placement (main's layout order rules); headless phone tab order is L283

* test(terminal-close): switch-on close reds name their ledger rows (L255, L256, L259a, L259b); a same-id respawn's commit stamps its incarnation; the crossing terminal binds first

* test(terminal): the crossing registration is refused by main's workspace fence (L256 switch-on variant); the placement test's tabs are main's saved layout

* refactor(terminal): the stale-handle guard reads an issue-time pane stamp on the handle record, not a second map; moved-pane, restart and single-reader tests (layout-owner build, L80)

* test(session-tabs): switch-on variants: the desktop's tab name is main's rename, and a mobile-created tab's live shell is the one its commit registers (layout-owner build, L80)

* fix(terminal): a re-issued handle keeps its issue-time pane while no pane holds its terminal (review)

* fix(terminal): stop targets and exit waits use main's holds check (workspace and incarnation), and any exit event settles a later wait (review)

* test(session-tabs): name the switch-on placement and split-group reds L288 and L289

* fix(terminal): an exit drops a closed pane's record in the same write that retires the pane, and an unbind folds into the binding write (layout-owner build, L80)

The exit used to remove main's closed-pane entry in a session write of its own, before the
retirement write. It now reads the entry, and the retirement write for that pane drops it,
re-checked by terminal id and incarnation. An exit that keeps the surface (relay contact lost,
intentional stop) keeps the entry until a confirmed exit or its TTL.

A reused terminal id's unbind is now a pure computation that applyPtyBinding applies through
its existing writes. Two split-pane tests still expected a -1 exit to close the pane; they now
follow the ruling that any negative code keeps it. Static-gate fixes: test fixtures and tests
use typed member access instead of Reflect, and no longer use type assertions.

* test(runtime): the shared fixture store announces its session writes and the L283 store lists its partitions, as a profile Store does; L289's test passes on that store (layout-owner build, L80)

The fixture store bindPaneFirst fills now tells write listeners of every session write, direct or
by a layout command, so main's post-commit merge runs as it does on a real Store. The notice is
not enumerable, so a test's own `{ ...store }` copy, which keeps its writes elsewhere, does not
inherit a subscription it would never fire. With it, the targeted split-group create (L289) passes
at this head, so its it.fails mark goes. L283's test runs on a store that names its partitions and
stays it.fails("L283").

* fix(terminal): recovery checks the caller's handle after the cheap guards again, so the switch-off build re-issues no handle on a refused recovery (layout-owner build, L80)

The stale-handle switch point had moved the handle lookup ahead of the pane, terminal and
workspace checks. That lookup can re-issue a handle, so a switch-off recovery refused on those
checks gained a side effect base never had. The lookup runs after them again, and the switch-on
replacement check lives in one method.

* test(runtime): the layout-owner start fixture's spawn commits the pane binding before main registers the terminal, as the host does (L80 switch-on variant)

The window-tab start test came in with the main merge after bind-first; its fake spawn returned
a terminal no pane was bound to, so registration refused it. The fixture's spawn now binds the
pane first, as every other bind-first fixture does.

* fix(terminal): a new process under a reused terminal id starts with no exit code, so an exit wait on it cannot settle on the old process's code (layout-owner build, L80)

The record of a reused id kept the earlier process's lastExitCode, and with the switch on an exit
wait settles on any recorded code once the terminal reads disconnected. A new incarnation now
clears the code and cause. Also drops the L283 test's partition-list option: the fixture store
already lists its partitions.

* test(runtime): switch-on variants for four tests the full local run never loaded (L80)

- A restored pane's host lists the terminal it started; a pane main owns vouches for none.
- The other pane holding a slept session is in main's layout, so an unverifiable holder blocks
  the resume as it does through a window.
- Main sleeps a workspace whose panes it lists itself, even with a renderer attached (stablyai#31571).
- A graph sync no longer reconnects a disconnected terminal of main's pane; the inventory decides.

* test(runtime): the phone Markdown route tests use stablyai#31667's realistic store, and the untitled cases wait on L268 with the switch on

The window's tab uses main's id (the file path), and the store lists its partitions and saves each
open editor's unified tab and group, as the window does (the fixture from stablyai#31667). With the
switch on, the three untitled cases fail on L268, main's row merge dropping the untitled flag,
which stablyai#31667 fixes; they are it.fails until A1b's history includes it.

* refactor(runtime): two merged files back under the line limit, by moving A1b's additions to their helpers (L80)

After the main merge, the holder-pane check and a closed tab's live-terminal lookup pushed two
files past the line limit. The holder check is now paneHoldsSession (main-terminal-surfaces.ts)
behind one method, and the closed tab's lookup is one parent-class method. Behaviour unchanged.

* test(runtime): the headless chat tests open the chat on the publish path, as every product path does; L283 stays it.fails for the main-owned reorder

openChat recorded the chat only in the runtime's snapshot, so main's layout never held it and the
phone's reorder was refused as an unknown tab. On the publish path main's layout holds it; the
reorder is accepted but does not survive the row rebuild after a close (L283, reopened).

* test(runtime): bind-first census follows the one spawn-commit step (stablyai#31596); the unbound-registration test keeps its spawn from the suites' stand-in commit

After the main merge both start lanes register in spawn-commit-assembly.ts, after
savePtySpawnPaneBinding (or, adopted, on the live owner's pane); the census lists that file. A
phone create now writes its pane before the spawn, so the suites' stand-in spawn commit bound it
and the test of a path that registers unbound passed for the wrong reason; that test's controller
now opts out of the stand-in commit.

* test(runtime): convert four switch-on tests the disguised full runs hid

Real switch-on runs showed four files whose fakes registered terminals unbound
or confirmed a stop without its exit. Bind first, stamp the source as main
does, and deliver the exit a confirmed stop delivers. Switch-off unchanged.

* test: type the two converted fakes instead of casting them

The PTY controller fake now takes the controller's optional pane fields, and the
switch-on store is a typed RuntimeStore, so neither needs an assertion.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant