feat: configurable app name and icon for whitelabel deployments#594
feat: configurable app name and icon for whitelabel deployments#594donnfelker wants to merge 4 commits intoColeMurray:mainfrom
Conversation
Brand strings ("Open-Inspect" / "Inspect") and the inline sidebar SVG
were hardcoded across the web UI, bot workers, PR footer, and outbound
HTTP User-Agent headers. Forks and whitelabel deployments had to patch
source to rebrand. Thread a single Terraform variable `app_name` (with
optional `app_icon_url`) through every surface so deployments can
override the brand without touching code.
The web build receives `NEXT_PUBLIC_APP_NAME` and
`NEXT_PUBLIC_APP_ICON_URL` (inlined into the client bundle, per
Cloudflare OpenNext build-time requirements). The control-plane and bot
workers receive an `APP_NAME` plain-text binding and resolve it per
request via a new `resolveAppName(env)` helper in
`@open-inspect/shared`. Defaults preserve existing behavior.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit unified the sidebar logo text with the full app name, which regressed the historical "Inspect" short label to "Open-Inspect". The sidebar logo is intentionally a shorter brand than the tab title and sign-in heading. Add APP_SHORT_NAME (driven by optional NEXT_PUBLIC_APP_SHORT_NAME), defaulting to "Inspect" for the built-in brand and falling through to APP_NAME when only NEXT_PUBLIC_APP_NAME is set. Whitelabel deployments can override the short label independently for cases where the long name is too wide for the sidebar. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
✅ Files skipped from review due to trivial changes (2)
🚧 Files skipped from review as they are similar to previous changes (4)
📝 WalkthroughWalkthroughThreads a configurable app name/icon through shared helpers, env types, Terraform bindings, web site config/UI, bot/provider code, and tests; uses resolveAppName() to set APP_NAME and to populate User-Agent headers and PR/comment footers; updates docs and CI secrets for optional whitelabeling. (50 words) ChangesApp Whitelabeling
Sequence Diagram(s)sequenceDiagram
participant Dev as Developer (deploy)
participant WebBuild as Web Build (Vercel/Cloudflare)
participant Worker as Worker / Control Plane
participant GitHub as GitHub API
rect rgba(200,200,255,0.5)
Dev->>WebBuild: terraform apply / set NEXT_PUBLIC_APP_NAME
WebBuild->>WebBuild: inlines NEXT_PUBLIC_APP_NAME into bundle
end
rect rgba(200,255,200,0.5)
Worker->>Worker: resolveAppName(env) -> userAgent
Worker->>GitHub: API request (User-Agent: userAgent)
GitHub-->>Worker: response
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/github-bot/test/handlers.test.ts (1)
187-193:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
handlePullRequestOpenedtest has incomplete assertion coverage forgenerateInstallationToken.The implementation correctly passes
userAgenttogenerateInstallationTokenviaresolveCallerGating, but the test only verifies the function was called without checking the arguments. Compare this tohandleReviewRequested(line 351), which assertsgenerateInstallationTokenreceives the full payload includinguserAgent: "Open-Inspect". Add the same argument verification tohandlePullRequestOpened:expect(generateInstallationToken).toHaveBeenCalledWith({ appId: "12345", privateKey: "test-key", installationId: "67890", userAgent: "Open-Inspect", });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/github-bot/test/handlers.test.ts` around lines 187 - 193, Update the test for handlePullRequestOpened to assert generateInstallationToken was called with the expected payload including userAgent; specifically, in the test that currently calls generateInstallationToken and postReaction, replace or augment the existing toHaveBeenCalled() for generateInstallationToken with a toHaveBeenCalledWith asserting an object containing appId: "12345", privateKey: "test-key", installationId: "67890", and userAgent: "Open-Inspect" so the call via resolveCallerGating is fully validated.
🧹 Nitpick comments (5)
packages/linear-bot/src/__tests__/pure-functions.test.ts (1)
8-8: 💤 Low valueConsider co-locating
buildOAuthSuccessHtmlin a utility module rather than importing from the worker entry point.
../indexinitializes the full Hono app and registers all routes at module load time. The rest of this test file imports pure utilities directly from../model-resolutionand../callbacks, not from the entry point. Importing the worker entry just to access one helper couples unrelated test suites to the entire worker initialization graph and makes future entry-point side-effects (e.g., KV access at init) silently break these tests.Moving
buildOAuthSuccessHtmlto e.g../utils/oauth-html.ts(or./webhook-handler.tsalongsideescapeHtml) and re-exporting it fromindex.tskeeps the public API intact while making the pure-function tests independent of the worker entry.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/linear-bot/src/__tests__/pure-functions.test.ts` at line 8, The test imports buildOAuthSuccessHtml from ../index which pulls in and initializes the entire Hono worker; move the pure helper into a dedicated module (e.g., create a new utils/oauth-html.ts or webhook-handler.ts) that exports buildOAuthSuccessHtml (and related helpers like escapeHtml), update index.ts to re-export buildOAuthSuccessHtml so public API is unchanged, and change the test to import buildOAuthSuccessHtml from the new utils module to avoid triggering worker initialization side effects.packages/web/src/components/ui/app-icon.tsx (1)
8-13: 💤 Low valueConsider adding an
onErrorfallback toInspectIconto handle a missing/broken icon URL at runtime.If
APP_ICON_URLis set at build time but the asset isn't served (e.g.,/vector.pngnot deployed topublic/), users see a broken-image placeholder in the sidebar/command menu. AnonErrorhandler can recover gracefully.Note: this requires adding
"use client"sinceonErroris an event handler.♻️ Proposed refactor
+"use client"; +import { useState } from "react"; import { APP_ICON_URL, APP_NAME } from "@/lib/site-config"; import { InspectIcon } from "@/components/ui/icons"; interface AppIconProps { className?: string; } export function AppIcon({ className }: AppIconProps) { + const [imgError, setImgError] = useState(false); + if (APP_ICON_URL && !imgError) { - if (APP_ICON_URL) { - return <img src={APP_ICON_URL} alt={`${APP_NAME} logo`} className={className} />; + return ( + <img + src={APP_ICON_URL} + alt={`${APP_NAME} logo`} + className={className} + onError={() => setImgError(true)} + /> + ); } return <InspectIcon className={className} />; }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/src/components/ui/app-icon.tsx` around lines 8 - 13, The AppIcon component should handle runtime image load failures: mark the file as a client component by adding "use client" at the top, add local state (e.g., failed boolean via useState) and an onError handler on the <img> rendered when APP_ICON_URL is truthy that sets failed=true, and change the render to show InspectIcon when failed is true (or when APP_ICON_URL is falsy). Update the AppIcon function to reference APP_ICON_URL, the onError handler, and InspectIcon accordingly so a broken/missing asset falls back to InspectIcon at runtime.packages/shared/src/app-name.ts (1)
9-9: 💤 Low valueRedundant
value.length > 0check aftervalue &&.After
.trim(), an all-whitespaceAPP_NAMEbecomes"", which is already falsy.value && value.length > 0is equivalent to justvalue.♻️ Proposed simplification
- return value && value.length > 0 ? value : DEFAULT_APP_NAME; + return value || DEFAULT_APP_NAME;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/shared/src/app-name.ts` at line 9, The ternary check in the return statement is redundant: replace the expression that currently uses "value && value.length > 0 ? value : DEFAULT_APP_NAME" with a simpler truthy check so the function (in app-name.ts, e.g., the exported function that computes the app name) returns value when truthy and DEFAULT_APP_NAME otherwise; update the return in that function to use a single truthy check (or logical OR) instead of testing both value and value.length.packages/web/src/lib/site-config.test.ts (1)
30-40: ⚡ Quick winImport
DEFAULT_APP_NAMEinstead of hardcoding"Open-Inspect"Both fallback assertions (lines 33 and 39) hardcode
"Open-Inspect". IfDEFAULT_APP_NAMEis ever updated in@open-inspect/shared, these tests will fail without the failure being obviously tied to the constant — and a new value would need to be chased down here manually.♻️ Proposed fix
-import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { DEFAULT_APP_NAME } from "@open-inspect/shared"; ... it("falls back to Open-Inspect when NEXT_PUBLIC_APP_NAME is unset", async () => { delete process.env.NEXT_PUBLIC_APP_NAME; const { APP_NAME } = await import("./site-config"); - expect(APP_NAME).toBe("Open-Inspect"); + expect(APP_NAME).toBe(DEFAULT_APP_NAME); }); it("falls back to Open-Inspect when NEXT_PUBLIC_APP_NAME is empty", async () => { process.env.NEXT_PUBLIC_APP_NAME = " "; const { APP_NAME } = await import("./site-config"); - expect(APP_NAME).toBe("Open-Inspect"); + expect(APP_NAME).toBe(DEFAULT_APP_NAME); });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/web/src/lib/site-config.test.ts` around lines 30 - 40, Replace the hardcoded "Open-Inspect" in the two tests with the shared constant by importing DEFAULT_APP_NAME from "@open-inspect/shared" and asserting expect(APP_NAME).toBe(DEFAULT_APP_NAME);; update the top of the test module to import DEFAULT_APP_NAME alongside the dynamic import of "./site-config" (or require it statically) and keep the existing APP_NAME checks identical otherwise.terraform/environments/production/web-cloudflare.tf (1)
17-23: ⚡ Quick win
NEXT_PUBLIC_APP_SHORT_NAMEnot wired through Terraform
APP_SHORT_NAMEis described as a new configurable feature in this PR (evidenced byNEXT_PUBLIC_APP_SHORT_NAMEsupport insite-config.tsand the PR description), but neither the build environment nor the generatedwrangler.production.toml[vars]exposes a Terraform variable for it. Terraform-managed deployments that need a short name distinct fromAPP_NAME(e.g.,"Vector"alongsideNEXT_PUBLIC_APP_NAME=Vector Bot) cannot set it via the standard Terraform path.The default behaviour (
"Inspect"fallback or inherit fromAPP_NAME) means this isn't a blocker, but it leaves the feature only half-surfaced at the infrastructure layer.♻️ Proposed addition
In
variables.tf(alongsideapp_name/app_icon_url):variable "app_short_name" { description = "Short sidebar label (defaults to app_name when not set)" type = string default = "" }Then in the build environment block:
environment = { NEXT_PUBLIC_WS_URL = local.ws_url NEXT_PUBLIC_SANDBOX_PROVIDER = var.sandbox_provider NEXT_PUBLIC_APP_NAME = var.app_name NEXT_PUBLIC_APP_ICON_URL = var.app_icon_url + NEXT_PUBLIC_APP_SHORT_NAME = var.app_short_name }And in
wrangler.production.toml[vars]:NEXT_PUBLIC_APP_NAME = "${var.app_name}" NEXT_PUBLIC_APP_ICON_URL = "${var.app_icon_url}" + NEXT_PUBLIC_APP_SHORT_NAME = "${var.app_short_name}"Also applies to: 62-75
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@terraform/environments/production/web-cloudflare.tf` around lines 17 - 23, Add a Terraform variable and wire it through the Cloudflare build vars and wrangler production vars: declare a new variable "app_short_name" (string, default ""), add NEXT_PUBLIC_APP_SHORT_NAME = var.app_short_name to the environment map used for builds (alongside NEXT_PUBLIC_APP_NAME/NEXT_PUBLIC_APP_ICON_URL), and add a corresponding NEXT_PUBLIC_APP_SHORT_NAME entry under the [vars] block in wrangler.production.toml generation so the short name can be set via Terraform rather than only falling back to APP_NAME.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@terraform/environments/production/variables.tf`:
- Around line 334-344: Add a new Terraform variable named app_short_name
(string, default empty) to expose the sidebar short label, then propagate it
into the web deployment stacks by wiring it to the APP_SHORT_NAME /
NEXT_PUBLIC_APP_SHORT_NAME build env var where web-cloudflare.tf and
web-vercel.tf set other web app envs; update any places that generate or pass
environment variables for the web build (the blocks that currently reference
app_name/app_icon_url) to also include app_short_name so deployers can set the
short label via Terraform.
In `@terraform/environments/production/web-vercel.tf`:
- Around line 64-75: Add the missing NEXT_PUBLIC_APP_SHORT_NAME env var to the
Vercel env block by creating an entry with key "NEXT_PUBLIC_APP_SHORT_NAME",
value var.app_short_name, targets ["production","preview"], sensitive = false
(placed alongside NEXT_PUBLIC_APP_NAME and NEXT_PUBLIC_APP_ICON_URL in
web-vercel.tf) and declare var.app_short_name in the Terraform variables for the
production environment (variables.tf) with a default of "" to match other
optional branding vars; this ensures packages/web/src/lib/site-config.ts can
read NEXT_PUBLIC_APP_SHORT_NAME as intended.
---
Outside diff comments:
In `@packages/github-bot/test/handlers.test.ts`:
- Around line 187-193: Update the test for handlePullRequestOpened to assert
generateInstallationToken was called with the expected payload including
userAgent; specifically, in the test that currently calls
generateInstallationToken and postReaction, replace or augment the existing
toHaveBeenCalled() for generateInstallationToken with a toHaveBeenCalledWith
asserting an object containing appId: "12345", privateKey: "test-key",
installationId: "67890", and userAgent: "Open-Inspect" so the call via
resolveCallerGating is fully validated.
---
Nitpick comments:
In `@packages/linear-bot/src/__tests__/pure-functions.test.ts`:
- Line 8: The test imports buildOAuthSuccessHtml from ../index which pulls in
and initializes the entire Hono worker; move the pure helper into a dedicated
module (e.g., create a new utils/oauth-html.ts or webhook-handler.ts) that
exports buildOAuthSuccessHtml (and related helpers like escapeHtml), update
index.ts to re-export buildOAuthSuccessHtml so public API is unchanged, and
change the test to import buildOAuthSuccessHtml from the new utils module to
avoid triggering worker initialization side effects.
In `@packages/shared/src/app-name.ts`:
- Line 9: The ternary check in the return statement is redundant: replace the
expression that currently uses "value && value.length > 0 ? value :
DEFAULT_APP_NAME" with a simpler truthy check so the function (in app-name.ts,
e.g., the exported function that computes the app name) returns value when
truthy and DEFAULT_APP_NAME otherwise; update the return in that function to use
a single truthy check (or logical OR) instead of testing both value and
value.length.
In `@packages/web/src/components/ui/app-icon.tsx`:
- Around line 8-13: The AppIcon component should handle runtime image load
failures: mark the file as a client component by adding "use client" at the top,
add local state (e.g., failed boolean via useState) and an onError handler on
the <img> rendered when APP_ICON_URL is truthy that sets failed=true, and change
the render to show InspectIcon when failed is true (or when APP_ICON_URL is
falsy). Update the AppIcon function to reference APP_ICON_URL, the onError
handler, and InspectIcon accordingly so a broken/missing asset falls back to
InspectIcon at runtime.
In `@packages/web/src/lib/site-config.test.ts`:
- Around line 30-40: Replace the hardcoded "Open-Inspect" in the two tests with
the shared constant by importing DEFAULT_APP_NAME from "@open-inspect/shared"
and asserting expect(APP_NAME).toBe(DEFAULT_APP_NAME);; update the top of the
test module to import DEFAULT_APP_NAME alongside the dynamic import of
"./site-config" (or require it statically) and keep the existing APP_NAME checks
identical otherwise.
In `@terraform/environments/production/web-cloudflare.tf`:
- Around line 17-23: Add a Terraform variable and wire it through the Cloudflare
build vars and wrangler production vars: declare a new variable "app_short_name"
(string, default ""), add NEXT_PUBLIC_APP_SHORT_NAME = var.app_short_name to the
environment map used for builds (alongside
NEXT_PUBLIC_APP_NAME/NEXT_PUBLIC_APP_ICON_URL), and add a corresponding
NEXT_PUBLIC_APP_SHORT_NAME entry under the [vars] block in
wrangler.production.toml generation so the short name can be set via Terraform
rather than only falling back to APP_NAME.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 85802609-558b-4cda-b26c-75cced3b132a
📒 Files selected for processing (46)
docs/GETTING_STARTED.mddocs/SETUP_GUIDE.mdpackages/control-plane/src/auth/github-app.tspackages/control-plane/src/auth/github.tspackages/control-plane/src/routes/shared.tspackages/control-plane/src/session/durable-object.tspackages/control-plane/src/session/pull-request-service.test.tspackages/control-plane/src/session/pull-request-service.tspackages/control-plane/src/source-control/providers/constants.tspackages/control-plane/src/source-control/providers/github-provider.test.tspackages/control-plane/src/source-control/providers/github-provider.tspackages/control-plane/src/source-control/providers/gitlab-provider.tspackages/control-plane/src/source-control/providers/types.tspackages/control-plane/src/types.tspackages/github-bot/src/github-auth.tspackages/github-bot/src/handlers.tspackages/github-bot/src/types.tspackages/github-bot/test/github-auth.test.tspackages/github-bot/test/handlers.test.tspackages/linear-bot/src/__tests__/pure-functions.test.tspackages/linear-bot/src/callbacks.helpers.test.tspackages/linear-bot/src/callbacks.tspackages/linear-bot/src/index.tspackages/linear-bot/src/types.tspackages/shared/src/app-name.test.tspackages/shared/src/app-name.tspackages/shared/src/index.tspackages/slack-bot/src/index.test.tspackages/slack-bot/src/index.tspackages/slack-bot/src/types/index.tspackages/web/src/app/(app)/page.tsxpackages/web/src/app/layout.tsxpackages/web/src/components/global-command-menu.tsxpackages/web/src/components/session-sidebar.tsxpackages/web/src/components/sidebar-layout.tsxpackages/web/src/components/ui/app-icon.tsxpackages/web/src/lib/site-config.test.tspackages/web/src/lib/site-config.tsterraform/environments/production/terraform.tfvars.exampleterraform/environments/production/variables.tfterraform/environments/production/web-cloudflare.tfterraform/environments/production/web-vercel.tfterraform/environments/production/workers-control-plane.tfterraform/environments/production/workers-github.tfterraform/environments/production/workers-linear.tfterraform/environments/production/workers-slack.tf
The installationBindings() helper added a layer of indirection that
made the four github-app helper calls harder to follow — readers had
to jump to the method definition to see what was actually being
forwarded. Inline { cacheStore, userAgent } at each call site so the
arguments are visible where they're used.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sidebar header has limited horizontal width (288px) so a long app_name like "Acme Coding Agent" overflows. Promote the existing NEXT_PUBLIC_APP_SHORT_NAME web env var to a first-class Terraform variable so production deployments can override the sidebar label the same way as app_name and app_icon_url, without requiring direct edits to the web build env. Threaded through web-cloudflare.tf (build env + wrangler vars) and web-vercel.tf (env array). Documented in tfvars.example, the local dev .env.local template, the GETTING_STARTED tfvars block, the whitelabel section, and the CI secrets table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
app_name(defaultOpen-Inspect) through the web UI, both bot workers, the control-plane PR footer, and outbound HTTPUser-Agentheaders — forks and whitelabel deployments can now rebrand without patching source.app_icon_url(custom logo + browser favicon) andNEXT_PUBLIC_APP_SHORT_NAME(the historical "Inspect" sidebar label, kept as the default).resolveAppName(env)helper in@open-inspect/sharedis the single source of truth — workers resolve at request time, the web bundle inlinesNEXT_PUBLIC_APP_NAME/NEXT_PUBLIC_APP_ICON_URL/NEXT_PUBLIC_APP_SHORT_NAMEat build time.Surfaces wired up
metadata,sidebar-layout,(app)/pageNEXT_PUBLIC_APP_NAMEsession-sidebarNEXT_PUBLIC_APP_SHORT_NAME(defaults toInspect)<AppIcon>helper,metadata.iconsNEXT_PUBLIC_APP_ICON_URLslack-bot/src/index.tsAPP_NAME(worker binding)linear-bot/src/index.tsAPP_NAMElinear-bot/src/callbacks.tsAPP_NAMEcontrol-plane/src/session/pull-request-service.tsAPP_NAMEUser-Agentheadersgithub-app.ts+github-bot/github-auth.tsAPP_NAMEDefaults preserve all existing behavior — no diff for deployments that don't set the new vars.
Backwards compatibility
app_name = "Open-Inspect",app_icon_url = "").Inspect(the historical short label) and only followsAPP_NAMEwhen explicitly overridden.github-bothelpers default toOpen-Inspectso any other callers keep working.Screenshots with config values as example
Default

With Custom App Name (no Short Name)

With Custom App Name and Short Name

Docs updated
docs/GETTING_STARTED.md— newterraform.tfvarsblock, CI secrets table entries, and a "Customizing the App Name and Icon" sectiondocs/SETUP_GUIDE.md— addsNEXT_PUBLIC_APP_NAME/NEXT_PUBLIC_APP_ICON_URLto the local dev.env.localtemplate🤖 Generated with Claude Code
Summary by CodeRabbit