feat(favicon): environment-distinct favicon (dev/staging), same wordmark#5588
feat(favicon): environment-distinct favicon (dev/staging), same wordmark#5588waleedlatif1 wants to merge 7 commits into
Conversation
Recolored the existing "sim" wordmark favicon (background only, mark pixel-identical to prod) for dev (orange) and staging (yellow), so a dev/staging browser tab is never mistaken for prod. Production is untouched. Deployment tier is read via the existing OTEL_DEPLOYMENT_ENVIRONMENT / APPCONFIG_ENVIRONMENT / NODE_ENV fallback chain already used for OTel's deployment.environment attribute (this app deploys to AWS/ECS, not Vercel, and every deployed tier already sets OTEL_DEPLOYMENT_ENVIRONMENT in Secrets Manager) - no new infra env var needed. Wired into both the /favicon.ico rewrite (next.config.ts) and the <link rel="icon"> tags from generateBrandedMetadata() (ee/whitelabeling/metadata.ts); a whitelabeled deployment's own brand.faviconUrl always wins regardless of tier.
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryLow Risk Overview Metadata & PWA:
Includes Reviewed by Cursor Bugbot for commit 1e42a73. Configure here. |
Greptile SummaryThis PR adds environment-specific favicon behavior while preserving tenant branding. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Important Files Changed
Reviews (6): Last reviewed commit: "fix(favicon): revert marketing-ISR-break..." | Re-trigger Greptile |
- dev favicon: orange -> light pink - staging favicon: yellow -> lighter yellow (mark stays pixel-identical, background color only) - env-flags.ts's CI check requires every exported const to be is-/get- prefixed; deploymentEnv was a plain const. Converted to getDeploymentEnv(), matching the file's existing convention for non-boolean values (getCostMultiplier(), getAllowedIntegrationsFromEnv()).
Addresses review findings on PR #5588: - Cursor (Medium): next.config.ts's rewrites() runs once during the shared, environment-agnostic Docker build (docker/app.Dockerfile builds with dummy env values; bootstrap.ts hydrates process.env from AWS Secrets Manager only at container boot, after the build already ran) - so resolving getDeploymentEnv() inside next.config.ts baked in whichever tier happened to be active during that shared build, never the tier the container actually runs as. Fixed by rewriting /favicon.ico to a stable path (/api/favicon) and resolving the destination inside that route handler instead, which runs per-request at runtime like generateBrandedMetadata() already correctly does. Marked force-dynamic per Next's own docs: generated icon routes are statically cached by default unless they use a request-time API or dynamic config. - Greptile (P1): a whitelabeled deployment's favicon slots (16/32/192/512 PNGs, apple-touch-icon) fell back to the environment-tinted set instead of neutral branding when brand.faviconUrl was set. Now falls back to the production (neutral) asset set specifically for whitelabeled deployments, matching pre-PR behavior for those slots. - Greptile (P2, ico-serves-svg): verified via `git show origin/staging` that /favicon.ico -> /icon.svg is pre-existing, unchanged production behavior from before this PR - not addressed here, out of scope. Also: added app/api/favicon/route.ts to the check:api-validation INDIRECT_ZOD_ROUTES allowlist (no client-supplied input - only reads the server's own deployment-tier env var) and bumped the audit baseline.
|
@cursor review |
…l favicon Addresses the second round of review findings on PR #5588: - Cursor (High): app/layout.tsx exported a static `export const metadata = generateBrandedMetadata()`. Next.js treats a static metadata object as truly constant (eligible for build-time resolution on any statically-rendered route sharing the layout), so it doesn't know this one secretly depends on process.env via getDeploymentEnv() - same bake-in risk already fixed for next.config.ts. Converted to generateMetadata(), the documented way to force per-request resolution. - Greptile (P1): the earlier whitelabel fallback fixed the *tint* but not the underlying precedence gap - Sim's exact-size PNG/apple entries were still emitted alongside the tenant's generic sizes:'any' entry, and browsers can prefer the more specific size match. Now the tenant favicon fully replaces every icon slot instead of being appended alongside Sim's. - Cursor (Medium): app/api/favicon/route.ts never checked brand.faviconUrl, so a whitelabeled dev/staging deployment still served Sim's tinted icon at the legacy /favicon.ico path. Now checks brand.faviconUrl first, matching generateBrandedMetadata()'s precedence.
|
@cursor review |
…tEnv() test coverage Self-review pass, not a review-comment fix: - app/api/favicon/route.ts maintained its own env-to-svg-path map (FAVICON_DESTINATIONS) duplicating ICON_SETS in ee/whitelabeling/metadata.ts. The two could silently drift apart if an asset path ever changed in only one place. Exported ICON_SETS from metadata.ts (and the ee/whitelabeling barrel) and had the route reuse it directly instead. - Added lib/core/config/env-flags.test.ts covering getDeploymentEnv()'s resolution chain: each real AWS Secrets Manager value (dev/staging/prod), the APPCONFIG_ENVIRONMENT production->prod mapping, the NODE_ENV fallback, precedence ordering between the three signals, and an unrecognized-value case bucketing safely to development rather than throwing. This logic went through three rounds of review fixes: worth a regression net now that it's settled.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
2 issues from previous reviews remain unresolved.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 5412f70. Configure here.
Addresses the third round of review findings on PR #5588: - Cursor (Medium): generateMetadata() alone does not force per-request evaluation - only the overall page being dynamic does, and a plain process.env read is not a Next.js "dynamic signal" Next can detect. Verified this doesn't matter for any route TODAY (0 of 1007 real page routes are static/ISR-eligible - only non-HTML RSS/sitemap/robots/OG files are), but it was a latent gap: any future static or ISR page added under the root layout would silently get build-time (production-tier) favicon metadata baked in, with no error. Added `export const dynamic = 'force-dynamic'` to app/layout.tsx, matching the same declaration already on app/manifest.ts and app/api/favicon/route.ts. Verified via full build that the 9 legitimately-static routes (RSS/sitemap/robots/OG-image files, which don't render through the root layout's page tree) are unaffected - same count, same routes, before and after. - Greptile (P1, reported as two separate threads): app/manifest.ts (PWA install/home-screen icons) never checked brand.faviconUrl at all, so a whitelabeled deployment would still show Sim's icon when installed even after the HTML favicon was fixed. Now uses brand.faviconUrl when set, same precedence as generateBrandedMetadata(). - Cursor (Low): the `brand.faviconUrl ? 'production' : getDeploymentEnv()` ternary computed a value never actually read when brand.faviconUrl is set (every consumer already short-circuits on brand.faviconUrl first). Simplified to always resolve the environment tier.
|
@cursor review |
…st env-tint Addresses the fourth round of review findings on PR #5588: - Cursor (High, confirmed via testing, not just code review): the previous commit's `export const dynamic = 'force-dynamic'` on app/layout.tsx would have forced the entire marketing site ((landing) route group - blog/library posts, changelog, solutions pages, all with their own `revalidate` exports) into per-request rendering, breaking their Lighthouse/LCP budget. My earlier "verified via build, no regression" claim was itself wrong: my local build masked this because NEXT_PUBLIC_APP_URL=localhost makes isHosted() false, which makes app/layout.tsx render next-runtime-env's RuntimePublicEnvScript - which unconditionally calls unstable_noStore() - already forcing marketing pages dynamic before my change even ran, for a completely unrelated pre-existing reason. Rebuilt with NEXT_PUBLIC_APP_URL=https://www.sim.ai (matching real hosted deployments, isHosted() true, the safe script path) to get an unmasked answer: confirmed force-dynamic was the actual regression (marketing pages went from ● to ƒ), and confirmed the revert restores them to ○ with their normal 1h revalidate window. Documented the accepted tradeoff instead: marketing-page favicon metadata may lag by up to one revalidate window after a deploy (bounded, self-healing at the next ISR regeneration, which runs in the live server process after bootstrap.ts has loaded secrets) - the actual workspace app (this feature's real audience) is already fully dynamic today (session/auth), so it needs no explicit marker. - Cursor (Medium): app/manifest.ts's non-whitelabeled branch still hardcoded production favicon paths instead of ICON_SETS[getDeploymentEnv()], so PWA install/home-screen icons stayed production-tinted while the HTML favicon was correctly env-tinted. Now uses the same per-tier set.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 1e42a73. Configure here.

Summary
OTEL_DEPLOYMENT_ENVIRONMENT/APPCONFIG_ENVIRONMENT/NODE_ENVfallback chain already used for OTel'sdeployment.environmentattribute — this app deploys to AWS/ECS, not Vercel, and every deployed tier already setsOTEL_DEPLOYMENT_ENVIRONMENTin Secrets Manager (confirmed directly against/dev/sim/env-vars,/staging/sim/env-vars,/production/sim/env-vars), so no new infra env var was needed./favicon.icorewrite (next.config.ts) and the<link rel="icon">tags fromgenerateBrandedMetadata()(ee/whitelabeling/metadata.ts). A whitelabeled deployment's ownbrand.faviconUrlalways wins regardless of tier.favicon-dev//favicon-staging/to the proxy middleware's static-asset exclusions, matching the existingfavicon/exclusion.Type of Change
Testing
bun run build) passes cleanproviders/meta/index.ts)Checklist