Skip to content

feat(platform): per-channel app identity — distinct icon/name/userData per build kind - #387

Open
pnewsam wants to merge 2 commits into
paul/developer-platform-channelsfrom
paul/per-channel-app-identity
Open

feat(platform): per-channel app identity — distinct icon/name/userData per build kind#387
pnewsam wants to merge 2 commits into
paul/developer-platform-channelsfrom
paul/per-channel-app-identity

Conversation

@pnewsam

@pnewsam pnewsam commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Description

Makes each build kind a distinct app, so an internal user can distinguish them. This is the standard Electron pattern (VS Code Stable/Insiders, Slack/Canary, Discord/Canary/PTB).

Stacked on #385 (base branch paul/developer-platform-channels) — review/merge #385 first, then this retargets to staging.

Fixes: https://linear.app/mindsdb/issue/ENG-676/build-kind-app-isolation-install-non-prod-builds-as-separate-apps

What "distinct app" means here

Per non-prod channel: its own app icon (badged), bundle id (appId), app name (productName), userData dir (localStorage / terms consent / token-store file), and keychain namespace. ~/.cowork-<kind> home, server binary, logs, and UI cache were already isolated in #385 — this closes the last shared surfaces.

appId productName userData dir keychain service
prod com.mindshub.cowork MindsHub Cowork anton (frozen) cowork-oauth (frozen)
stable (→ staging env) …cowork.stable MindsHub Cowork (Staging) MindsHub Cowork (Staging) cowork-oauth-stable
preview …cowork.preview MindsHub Cowork (Preview) MindsHub Cowork (Preview) cowork-oauth-preview
dev (unpackaged) MindsHub Cowork (Dev) cowork-oauth-dev

How

  • Runtime (app-identity.ts, channels.ts appName): app.setName() for non-prod, run as the first import in index.ts (before token-store.ts reads app.getPath('userData') at load). prod's app.name is never re-set → byte-for-byte unchanged.
  • Keychain (keychain-service.ts): per-channel SERVICE_NAME (prod frozen).
  • Build (scripts/channel-identity.mjs): per-kind appId/productName/icon; prod/dev → no overrides → electron-builder.yml defaults. Threaded into release-mac-pkg-notarized.sh (overrides PRODUCT_NAME so the .app path/pkg title match, passes -c overrides) and scripts/dist-win.mjs (electron-builder programmatic API — avoids Windows shell-quoting the spaced name).
  • Icons (assets/icon-{staging,preview}.png): placeholder badged icons — the base icon with a channel-labelled band — committed as-is, to be replaced with proper artwork later.

Why it's low-risk

  • Same Apple Developer / EV cert signs any appId under the team; entitlements aren't bundle-id-pinned → no new certs.
  • No custom protocol handler, no requestSingleInstanceLock, no electron-updater feed → none of the usual side-by-side-install conflicts apply.
  • prod is a no-op in every layer (frozen appName/appId/productName/keychain; build overrides skipped).

Consequences / call-outs

  • Existing staging/preview testers get a one-time reset — their current install shares prod's anton userData; after this, non-prod becomes a distinct app with fresh localStorage/consent (old install becomes an orphan to delete). Worth a heads-up in release notes.
  • Needs one signed CI build per platform to fully validate notarization (mac) and the programmatic Windows build — can't be verified locally.

Test / verify

  • npm run typecheck, check:cowork-purity, full unit suite (637 tests) pass on the rebased branch.
  • channels.test.ts asserts prod appName is frozen to anton and non-prod names are distinct, and drift-guards channel-identity.mjs against CHANNELS (productName ↔ appName, icon ↔ iconName).
  • channel-identity.mjs: correct appId/productName/icon per kind, empty for prod/dev/unset.
  • Badged icon visually confirmed (bear + orange STAGING band).

🤖 Generated with Claude Code

@pnewsam
pnewsam force-pushed the paul/developer-platform-channels branch from 106200d to 8092821 Compare July 16, 2026 16:32
@pnewsam
pnewsam requested a review from a team as a code owner July 16, 2026 16:32
@pnewsam
pnewsam force-pushed the paul/developer-platform-channels branch from 8092821 to ba6e6f4 Compare July 16, 2026 16:44
@pnewsam
pnewsam force-pushed the paul/per-channel-app-identity branch from 1edf76a to f66268f Compare July 16, 2026 17:31

@pnewsam pnewsam left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Code review

Verdict: COMMENT — one major issue should be addressed before merge.

The per-channel app names, bundle IDs, keychain namespaces, startup import ordering, prod compatibility, and the newly added build-script drift guards look sound. However, the badged channel icon configured at build time is replaced by the production icon when the app launches, so one of the PR's central visual-isolation guarantees does not currently hold.

Summary of findings

Severity File Issue
Major scripts/channel-identity.mjs:15 Runtime window/dock icon selection overwrites the badged bundle icon with assets/icon.png.
Minor scripts/reset-onboarding.sh:49 Nearby documentation still says localStorage/terms consent are shared across channels.

Both installer jobs are currently skipped. Because this PR replaces the Windows builder invocation and changes macOS packaging identity, I would treat successful macOS and Windows installer runs as a release gate. The installed artifacts should be checked for distinct app/bundle IDs, product names, running icons, userData directories, and keychain namespaces, while confirming prod remains unchanged.

Because this is a self-authored PR, I am leaving a COMMENT rather than an approval.

Comment thread scripts/channel-identity.mjs
Comment thread scripts/reset-onboarding.sh Outdated

@hamishfagg hamishfagg left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

approving package.json changes

@pnewsam
pnewsam force-pushed the paul/developer-platform-channels branch from fe8cfc9 to ac55d19 Compare July 21, 2026 00:16
@pnewsam
pnewsam force-pushed the paul/per-channel-app-identity branch from 265698d to 38b97fc Compare July 21, 2026 00:22
@pnewsam
pnewsam force-pushed the paul/developer-platform-channels branch 5 times, most recently from 4466efd to 6c9db65 Compare July 28, 2026 20:14
…, dock icon

Give each non-prod build kind a distinct RUNTIME identity so build kinds on one
machine don't collide, building on the CHANNELS table from #385:

- channels.ts: add per-kind `appName` (the Electron app name → userData dir) and
  `iconName` (the window/dock icon). prod is frozen to `anton` / `icon.png` so
  shipped installs keep their data and look unchanged.
- app-identity.ts: a side-effect module imported FIRST in index.ts — calls
  app.setName for non-prod kinds only (before token-store reads userData at load)
  so localStorage, terms consent, and the token store isolate per channel. prod's
  app.name is never touched → byte-for-byte unchanged.
- keychain-service.ts: namespace the keychain service per channel
  (`cowork-oauth-<kind>`); prod keeps the historical `cowork-oauth`.
- app-icon.ts: pick the badged window/dock icon at runtime (the packaged bundle
  icon alone doesn't govern the running app's dock icon), falling back to the
  base icon.png if the asset is missing.

The `stable` kind targets the staging env (see #385), so its user-facing labels
read "Staging"; its invisible appId stays keyed to the kind.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pnewsam
pnewsam force-pushed the paul/per-channel-app-identity branch from e7ba1d1 to 7e304b8 Compare July 28, 2026 20:39
pnewsam added a commit that referenced this pull request Jul 28, 2026
… (ENG-340)

Follow-on to the per-channel app-identity work (#387): since each non-prod build
kind gets its own Electron app name (app.setName → a separate userData dir), the
reset/restore scripts must target the channel-specific dir, not the shared
~/Library/Application Support/anton.

- Derive ELECTRON_DIR from a per-kind app-name map (prod stays 'anton'; non-prod
  kinds mirror channels.ts appName — the `stable` kind targets the staging env so
  its app is labelled "Staging").
- Reword the localStorage/consent comments: state now lives under the channel's
  OWN userData dir, so a reset clears only the kind you pass.

Kept here (rather than in #387) so #520 owns all onboarding-script changes and
stays independently mergeable on staging; the app-name map is a hardcoded mirror
either way (bash can't import channels.ts).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dged icons

Set the packaged app's appId / productName / icon per build kind so preview and
stable installers are distinct apps with distinct dock/Finder icons:

- channel-identity.mjs: plain-JS bundle-identity mirror (build scripts can't
  import the TS channels table), keyed by build kind; prod/dev return null so the
  prod build path uses electron-builder.yml unchanged. A drift test in
  channels.test.ts locks it to CHANNELS (productName ↔ appName, icon ↔ iconName).
- release-mac-pkg-notarized.sh / dist-win.mjs: apply the identity at build time.
- assets/icon-{preview,staging}.png: placeholder badged icons (the base icon
  with a channel-labelled band), committed as-is — to be replaced with proper
  artwork later.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pnewsam
pnewsam force-pushed the paul/per-channel-app-identity branch from 7e304b8 to 389b338 Compare July 28, 2026 20:53
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.

2 participants