Skip to content

Local emulator#1225

Closed
N2D4 wants to merge 4 commits intodevfrom
local-emulator-frfr
Closed

Local emulator#1225
N2D4 wants to merge 4 commits intodevfrom
local-emulator-frfr

Conversation

@N2D4
Copy link
Contributor

@N2D4 N2D4 commented Feb 26, 2026

Note

Medium Risk
Medium risk: introduces a new Prisma migration/table plus a new internal endpoint that provisions projects and long-lived API keys, and changes config override write paths to reject updates for local-emulator projects.

Overview
Adds a NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR mode that provisions local-emulator projects from an absolute config-file path and threads that behavior through backend, dashboard, and CI.

Backend adds the LocalEmulatorProject mapping table and a hidden /internal/local-emulator/project endpoint that upserts a project/tenancy, reuses or creates an ApiKeySet, and returns credentials plus a placeholder branch override; the seed script now ensures the local-emulator owner team/user exist.

Environment-level config overrides become read-only for local-emulator projects (API routes and setEnvironmentConfigOverride block writes/resets), and the dashboard updates UX accordingly (disable project creation, add “Open config file” flow, auto-login, and hide env/email config editing in emulator mode). CI replaces the old Docker emulator check with a full local-emulator E2E workflow, and removes legacy docker emulator compose/docs.

Written by Cursor Bugbot for commit c398730. This will update automatically on new commits. Configure here.

Summary by CodeRabbit

  • New Features

    • Local emulator mode: provision path-based projects with deterministic credentials via a new endpoint; dashboard can open config-file projects and shows clear emulator messaging.
  • Bug Fixes

    • Blocks environment-level config edits for local-emulator projects; preserves branch overrides.
  • Chores

    • Added env flags, DB migration and seed changes to support local emulator.
    • Removed legacy Docker emulator orchestration and docs; added E2E tests and a new CI workflow, removed the old workflow.

Copilot AI review requested due to automatic review settings February 26, 2026 22:05
@vercel
Copy link

vercel bot commented Feb 26, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
stack-backend Ready Ready Preview, Comment Feb 27, 2026 11:09pm
stack-dashboard Ready Ready Preview, Comment Feb 27, 2026 11:09pm
stack-demo Ready Ready Preview, Comment Feb 27, 2026 11:09pm
stack-docs Ready Ready Preview, Comment Feb 27, 2026 11:09pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

📝 Walkthrough

Walkthrough

Removes legacy Docker emulator artifacts and composer; adds local-emulator support across backend, database, dashboard, E2E tests, and CI; introduces LocalEmulatorProject model, seed/API endpoints, runtime guards for env overrides, and dashboard UI/flow changes for read-only emulator mode.

Changes

Cohort / File(s) Summary
CI Workflows
\.github/workflows/docker-emulator-test.yaml, \.github/workflows/e2e-api-tests-local-emulator.yaml
Deletes legacy Docker emulator workflow; adds E2E workflow that boots services, waits for readiness, and runs emulator-focused tests.
Docker artifacts & docs
docker/emulator/docker.compose.yaml, docker/emulator/inbucket-nginx.conf, docker/README.md, docker/readme.md
Removes emulator docker-compose, nginx proxy, and emulator run/build docs.
DB schema, migration & tests
apps/backend/prisma/schema.prisma, apps/backend/prisma/migrations/.../migration.sql, apps/backend/prisma/migrations/.../tests/*
Adds LocalEmulatorProject model/table (PK absoluteFilePath, unique projectId) with cascade FK; includes migration tests for cascade delete and uniqueness enforcement.
Backend: local-emulator core
apps/backend/src/lib/local-emulator.ts, apps/backend/src/lib/config.tsx, apps/backend/prisma/seed.ts
New local-emulator utilities/constants and placeholder branch config; config read/write guards for environment-level overrides; seed updated to use emulator constants and membership/admin setup.
Backend: API routes & guards
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx, .../config/override/[level]/route.tsx, .../reset-keys/route.tsx
Adds POST endpoint to provision path-based emulator projects/credentials; adds runtime checks blocking environment override writes for emulator projects.
Env flag additions
apps/backend/.env, apps/backend/.env.development, apps/dashboard/.env, apps/dashboard/.env.development
Introduces NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR and sets dev defaults; small dashboard dev flag tweak.
Dashboard: project flows & UI
apps/dashboard/src/app/.../projects/page-client.tsx, .../new-project/page-client.tsx, .../projects/page.tsx, .../layout-client.tsx, .../sidebar-layout.tsx
Local-emulator-aware UI: open config-file dialog instead of create-project, suppress emulator auto-redirects, adjust sign-in/loading guards, and simplify header/sidebar rendering.
Dashboard: settings & utilities
apps/dashboard/src/app/.../projects/[projectId]/emails/page-client.tsx, apps/dashboard/src/lib/config-update.tsx, apps/dashboard/src/lib/env.tsx, apps/dashboard/src/lib/utils.tsx
Removes EmulatorModeCard; introduces read-only alerts and blocks env config updates in emulator mode; consolidates public env var keys and removes redirect helper.
E2E tests
apps/e2e/tests/backend/.../local-emulator-project.test.ts, apps/e2e/tests/backend/.../config-local-emulator.test.ts
Adds tests for provisioning emulator projects, credential reuse, path validation, and environment-vs-branch override restrictions.

Sequence Diagram

sequenceDiagram
    actor User as Dashboard User
    participant Dashboard as Dashboard Client
    participant API as Backend API
    participant Prisma as Prisma Client
    participant DB as PostgreSQL

    User->>Dashboard: submit absolute config file path
    Dashboard->>API: POST /internal/local-emulator/project { absolute_file_path }
    API->>API: validate local-emulator enabled & path
    API->>Prisma: derive/check projectId & upsert LocalEmulatorProject
    Prisma->>DB: SELECT/INSERT Project + LocalEmulatorProject
    DB-->>Prisma: project row / confirmation
    Prisma-->>API: project and mapping result
    API->>Prisma: find/create credentials keys for project
    Prisma->>DB: SELECT/INSERT keys
    DB-->>Prisma: keys
    API-->>Dashboard: return project_id, secret_server_key, super_secret_admin_key, branch_config_override_string
    Dashboard->>User: navigate to project using returned credentials
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 I found a path and gave it a name,

I stitched a project from a file and a flame.
Keys tumble out like carrots in spring,
Sandbox hushes — read-only bells ring.
Hop, hop, the local stack softly sings!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.64% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Local emulator' is vague and generic, lacking specificity about the changeset's key features or scope. Revise the title to be more specific, such as 'Add local emulator mode with NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR' or 'Introduce local emulator provisioning endpoint and config restrictions'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is well-structured and comprehensive, covering objectives, implementation details, config changes, UI updates, and risk assessment, despite the repository template being minimal.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch local-emulator-frfr

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request refactors and enhances the local emulator functionality by renaming environment variables, removing old Docker emulator infrastructure, and introducing a new path-based project mapping system. The changes enable developers to work with local config files mapped to emulator projects in the Stack dashboard.

Changes:

  • Renamed NEXT_PUBLIC_STACK_EMULATOR_ENABLED and NEXT_PUBLIC_STACK_EMULATOR_PROJECT_ID to a single NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR flag
  • Removed docker-compose based emulator setup and associated documentation
  • Introduced database-backed local emulator project mapping via absolute file paths
  • Added UI flow for opening config files in local emulator mode with read-only environment config restrictions
  • Added comprehensive E2E tests and CI workflow for local emulator mode

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
apps/backend/src/lib/local-emulator.ts New library defining constants and utilities for local emulator functionality
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx New endpoint for creating/retrieving local emulator projects by file path
apps/backend/src/lib/config.tsx Modified to fetch branch config overrides from LocalEmulatorProject table and block environment config updates
apps/backend/src/app/api/latest/internal/config/override/[level]/route.tsx Added local emulator environment config blocking logic
apps/backend/src/app/api/latest/internal/config/override/[level]/reset-keys/route.tsx Added local emulator environment config blocking logic
apps/backend/prisma/schema.prisma Added LocalEmulatorProject model with file path to project ID mapping
apps/backend/prisma/migrations/20260226100000_add_local_emulator_project_mapping/migration.sql Database migration creating LocalEmulatorProject table
apps/backend/prisma/seed.ts Updated to use new local emulator constants and simplified emulator project seeding
apps/dashboard/src/lib/env.tsx Renamed emulator environment variables
apps/dashboard/src/lib/utils.tsx Removed redirectToProjectIfEmulator function
apps/dashboard/src/lib/config-update.tsx Added local emulator check to block environment config updates
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx Removed conditional UI elements based on emulator mode
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx Added local emulator read-only UI with alerts
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx Added config file opening dialog for local emulator mode
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx Added local emulator blocking message for project creation
apps/dashboard/src/app/(main)/(protected)/layout-client.tsx Updated to use renamed environment variable
apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts New E2E tests for local emulator project endpoint
apps/e2e/tests/backend/endpoints/api/v1/internal/config-local-emulator.test.ts New E2E tests for config restrictions in local emulator
.github/workflows/e2e-api-tests-local-emulator.yaml New CI workflow for local emulator E2E tests
docker/readme.md Removed (deleted old emulator documentation)
docker/emulator/docker.compose.yaml Removed (deleted docker-compose emulator setup)
.github/workflows/docker-emulator-test.yaml Removed (deleted old emulator workflow)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (10)
.github/workflows/e2e-api-tests-local-emulator.yaml (4)

113-113: Redundant & operators in background service commands.

Similar to the Docker Compose step, the & at the end of each run command is redundant when using background-action. The action already handles backgrounding the process.

🧹 Suggested cleanup for all background services
-          run: pnpm run start:backend --log-order=stream &
+          run: pnpm run start:backend --log-order=stream

Apply the same pattern to lines 123, 133, 143, and 153.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/e2e-api-tests-local-emulator.yaml at line 113, Remove the
redundant trailing ampersand (&) from backgrounded commands that use the
background-action; specifically update each run step that currently uses the
string "pnpm run start:backend --log-order=stream &" (and the other similar run
commands on the same pattern at the other service steps) to omit the trailing
"&" so the command becomes "pnpm run start:backend --log-order=stream" — apply
the same change for the analogous run entries referenced in the workflow.

160-161: Consider documenting the sleep duration.

The 10-second sleep before running tests appears to be a timing workaround. If this is required for services to stabilize after startup, a comment explaining the reason would be helpful.

📝 Suggested documentation
+      # Allow background services to fully initialize before running tests
       - name: Wait 10 seconds
         run: sleep 10
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/e2e-api-tests-local-emulator.yaml around lines 160 - 161,
Add a short inline comment above the "Wait 10 seconds" workflow step explaining
why the 10s sleep is necessary (e.g., to allow the emulator/service to
initialize before tests) and what conditions it is covering, and if applicable
replace or augment the step name or comment to reference a link or note on a
more robust readiness check (so maintainers know this is a timing workaround and
where to find alternatives).

166-172: Consider documenting the repeated test runs.

Running tests 3 times on main/dev branches is an unusual pattern. If this is for flakiness detection or stability verification, adding a comment would help future maintainers understand the intent.

📝 Suggested documentation
+      # Run tests multiple times on main/dev to detect flaky tests
       - name: Run tests again (attempt 1)
         if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'
         run: pnpm test run
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/e2e-api-tests-local-emulator.yaml around lines 166 - 172,
Add a short comment above the repeated test steps to document why tests are run
multiple times on main/dev (e.g., flakiness mitigation or stability checks) and
clarify intent; update the step names "Run tests again (attempt 1)" and "Run
tests again (attempt 2)" (or add inline comments) to state the reason (e.g.,
"retry for flaky test detection" or "stability verification") so future
maintainers understand the pattern.

46-46: Redundant background operator & in command.

The & at the end of the docker compose command is redundant since background-action already runs the command in the background. The command will work, but the trailing & is unnecessary.

🧹 Suggested cleanup
-          run: docker compose -f docker/dependencies/docker.compose.yaml up --pull always -d &
+          run: docker compose -f docker/dependencies/docker.compose.yaml up --pull always -d
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/e2e-api-tests-local-emulator.yaml at line 46, Remove the
redundant background operator by editing the GitHub Actions step that currently
uses the command string "docker compose -f
docker/dependencies/docker.compose.yaml up --pull always -d &" and drop the
trailing "&"; keep the rest of the run command identical so the
background-action semantics handle detaching without the extra ampersand.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx (1)

149-183: Add email configuration UI state to Playground for documentation and testing.

The Playground was updated in this PR but does not showcase the new local-emulator read-only state pattern introduced in the email configuration section. Update the Playground to document this state (read-only copy, conditional buttons, informational alert) to keep design behavior testable and referenceable alongside other dashboard UI patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/emails/page-client.tsx
around lines 149 - 183, Add a Playground variant that demonstrates the new
email-config read-only emulator state by rendering the same header and controls
using the isLocalEmulator flag and emailConfig.isShared check: display the
Typography copy for the emulator ("Email server settings are read-only in the
local emulator") when isLocalEmulator is true, hide the TestSendingDialog
trigger when emailConfig.isShared || isLocalEmulator, hide the
EditEmailServerDialog trigger when isLocalEmulator is true, and render the Alert
with AlertDescription when isLocalEmulator is true so the Playground shows the
read-only copy, conditional buttons, and informational alert; reference the
existing components/props used in the page (isLocalEmulator,
emailConfig.isShared, TestSendingDialog, EditEmailServerDialog, Alert,
AlertDescription, Typography, Button) so the Playground mirrors the real UI
behavior.
apps/dashboard/.env.development (1)

12-12: Consider documenting the reason for disabling the debugger.

The change from true to false for NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR appears unrelated to the local emulator feature. If intentional, a comment explaining the rationale would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/.env.development` at line 12, The environment variable
NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR was changed from true to false
without explanation; either revert it if that was accidental or add a concise
comment above NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR in the
.env.development indicating the reason for disabling the debugger (e.g., to
avoid noisy logs during local emulator runs or to prevent exposing debug UI),
and include any conditions when it should be re-enabled to guide future
maintainers.
apps/dashboard/src/lib/config-update.tsx (1)

272-275: Consider using a toast instead of alert() for better UX.

While alert() works, it blocks the UI and is inconsistent with the modern toast-based feedback used elsewhere in the dashboard. A toast would provide a non-blocking notification.

💡 Suggested improvement using toast
+import { toast } from "@/components/ui/use-toast";
+
 // In the callback:
 if (isLocalEmulator) {
-  alert("These settings are read-only in the local emulator. Update them in your production deployment instead.");
+  toast({
+    title: "Read-only in local emulator",
+    description: "Update these settings in your production deployment instead.",
+    variant: "destructive",
+  });
   return false;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/lib/config-update.tsx` around lines 272 - 275, Replace the
blocking alert() call in the isLocalEmulator branch with the dashboard's
non-blocking toast notification so UX stays consistent: instead of alert("These
settings are read-only..."), call the existing toast/notification utility used
elsewhere in the app (e.g., toast or notify) with the same message and an
appropriate level (info/warning), keep the return false behavior, and add/import
the toast helper if not already present so the change is local to the
isLocalEmulator conditional.
apps/backend/src/lib/local-emulator.ts (1)

6-7: Consider adding a comment explaining the intentional hardcoded password.

LOCAL_EMULATOR_ADMIN_PASSWORD is hardcoded, which is intentional for local development. A brief comment would help future maintainers understand this is deliberate and not a security oversight.

📝 Suggested documentation
 export const LOCAL_EMULATOR_ADMIN_EMAIL = "local-emulator@stack-auth.com";
+// Intentionally hardcoded for local development only. This password is used
+// to seed the local emulator admin user and is not used in production.
 export const LOCAL_EMULATOR_ADMIN_PASSWORD = "LocalEmulatorPassword";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/lib/local-emulator.ts` around lines 6 - 7, Add a brief
inline comment above LOCAL_EMULATOR_ADMIN_PASSWORD (and optionally above
LOCAL_EMULATOR_ADMIN_EMAIL) stating that the hardcoded password is intentional
for local development/emulation only, not used in production, and include
guidance to change or override it via environment variables or secrets for any
shared/dev environments; reference the constants LOCAL_EMULATOR_ADMIN_PASSWORD
and LOCAL_EMULATOR_ADMIN_EMAIL so maintainers understand the intent and where to
look.
apps/e2e/tests/backend/endpoints/api/v1/internal/config-local-emulator.test.ts (1)

21-26: Same partial projectKeys pattern as in local-emulator-project.test.ts.

This sets only projectId and superSecretAdminKey. Consider creating a shared helper that consistently sets all required keys from the provisioned response, including secretServerKey which is also returned by the endpoint.

♻️ More complete projectKeys
   backendContext.set({
     projectKeys: {
       projectId: response.body.project_id,
+      secretServerKey: response.body.secret_server_key,
       superSecretAdminKey: response.body.super_secret_admin_key,
     },
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/e2e/tests/backend/endpoints/api/v1/internal/config-local-emulator.test.ts`
around lines 21 - 26, The test is only setting partial projectKeys via
backendContext.set({ projectKeys: { projectId, superSecretAdminKey } }) and
misses other keys (e.g., secretServerKey) returned by the provision endpoint;
add a shared helper (e.g., setProjectKeysFromResponse(response) or
populateProjectKeys) that extracts all required keys from the provision response
(project_id, super_secret_admin_key, secret_server_key, etc.) and calls
backendContext.set({ projectKeys: { ... } }), then replace the inline object in
this test and local-emulator-project.test.ts with that helper to ensure
consistency.
apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts (1)

7-7: Consider importing LOCAL_EMULATOR_OWNER_TEAM_ID from the source module.

This constant is duplicated from apps/backend/src/lib/local-emulator.ts. If the value changes in the source, this test will silently use a stale value and may pass incorrectly or fail misleadingly.

♻️ Suggested import
-const LOCAL_EMULATOR_OWNER_TEAM_ID = "5a0c858b-d9e9-49d4-9943-8ce385d86428";
+import { LOCAL_EMULATOR_OWNER_TEAM_ID } from "@/lib/local-emulator";

Note: You may need to adjust the import path based on how the E2E test environment resolves backend modules.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts`
at line 7, Replace the duplicated hardcoded LOCAL_EMULATOR_OWNER_TEAM_ID in the
test with a single import of that constant from the source module that defines
it (the local-emulator module) so the test always uses the canonical value;
remove the const declaration in the test, add an import for
LOCAL_EMULATOR_OWNER_TEAM_ID, and if necessary adjust the test's module
resolution/import path so the test can import from the local-emulator module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/e2e-api-tests-local-emulator.yaml:
- Around line 130-158: The mock-oauth-server background action's wait-on URL is
incorrect—update the "Start mock-oauth-server in background" action to wait on
http://localhost:8114 (instead of http://localhost:8102) so the
background-action verifies the mock-oauth-server is ready; leave the "Start
run-email-queue in background" and "Start run-cron-jobs in background" steps
unchanged since they should continue waiting on the backend URL
(http://localhost:8102); alternatively, if you prefer configurable ports, pass
PORT or similar env into the mock-oauth-server action (e.g., set env PORT=8102)
and keep wait-on aligned with that env.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/page-client.tsx:
- Around line 89-105: The current code masks JSON parse failures by using
response.json().catch(() => null) and then treating responseBody as if it were
valid; instead, call response.json() inside a try/catch and if parsing throws,
rethrow a descriptive error (including the original parse error message and
context like the request/endpoint) so parse failures fail loudly; keep the
subsequent response.ok checks that inspect responseBody, but remove the
.catch(() => null) and ensure any JSON parse exception is propagated as a new
Error before the response.ok handling (referencing response.json() and
responseBody in your changes).

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/emails/page-client.tsx:
- Line 133: The current assignment to isLocalEmulator uses
getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true" which silently
treats any unexpected value as false; change it to explicitly validate the env
var value: read the raw string via
getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR"), then if it equals "true"
set isLocalEmulator = true, else if it equals "false" set isLocalEmulator =
false, otherwise throw a clear Error (or console.error + throw) mentioning
NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR is invalid so the app fails fast; update the
code around isLocalEmulator in page-client.tsx to perform this validation.

In
`@apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts`:
- Around line 87-92: The backendContext.set call is only setting projectId and
superSecretAdminKey, causing niceBackendFetch to send undefined headers when
called with accessType "admin"; update the backendContext.set invocations (the
ones around the local-emulator-project.test file where projectKeys is set) to
include all four keys returned in the response: publishableClientKey,
secretServerKey, superSecretAdminKey, and projectId so that niceBackendFetch
(which reads these headers) has all required values for accessType "admin".

---

Nitpick comments:
In @.github/workflows/e2e-api-tests-local-emulator.yaml:
- Line 113: Remove the redundant trailing ampersand (&) from backgrounded
commands that use the background-action; specifically update each run step that
currently uses the string "pnpm run start:backend --log-order=stream &" (and the
other similar run commands on the same pattern at the other service steps) to
omit the trailing "&" so the command becomes "pnpm run start:backend
--log-order=stream" — apply the same change for the analogous run entries
referenced in the workflow.
- Around line 160-161: Add a short inline comment above the "Wait 10 seconds"
workflow step explaining why the 10s sleep is necessary (e.g., to allow the
emulator/service to initialize before tests) and what conditions it is covering,
and if applicable replace or augment the step name or comment to reference a
link or note on a more robust readiness check (so maintainers know this is a
timing workaround and where to find alternatives).
- Around line 166-172: Add a short comment above the repeated test steps to
document why tests are run multiple times on main/dev (e.g., flakiness
mitigation or stability checks) and clarify intent; update the step names "Run
tests again (attempt 1)" and "Run tests again (attempt 2)" (or add inline
comments) to state the reason (e.g., "retry for flaky test detection" or
"stability verification") so future maintainers understand the pattern.
- Line 46: Remove the redundant background operator by editing the GitHub
Actions step that currently uses the command string "docker compose -f
docker/dependencies/docker.compose.yaml up --pull always -d &" and drop the
trailing "&"; keep the rest of the run command identical so the
background-action semantics handle detaching without the extra ampersand.

In `@apps/backend/src/lib/local-emulator.ts`:
- Around line 6-7: Add a brief inline comment above
LOCAL_EMULATOR_ADMIN_PASSWORD (and optionally above LOCAL_EMULATOR_ADMIN_EMAIL)
stating that the hardcoded password is intentional for local
development/emulation only, not used in production, and include guidance to
change or override it via environment variables or secrets for any shared/dev
environments; reference the constants LOCAL_EMULATOR_ADMIN_PASSWORD and
LOCAL_EMULATOR_ADMIN_EMAIL so maintainers understand the intent and where to
look.

In `@apps/dashboard/.env.development`:
- Line 12: The environment variable
NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR was changed from true to false
without explanation; either revert it if that was accidental or add a concise
comment above NEXT_PUBLIC_STACK_DEBUGGER_ON_ASSERTION_ERROR in the
.env.development indicating the reason for disabling the debugger (e.g., to
avoid noisy logs during local emulator runs or to prevent exposing debug UI),
and include any conditions when it should be re-enabled to guide future
maintainers.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/emails/page-client.tsx:
- Around line 149-183: Add a Playground variant that demonstrates the new
email-config read-only emulator state by rendering the same header and controls
using the isLocalEmulator flag and emailConfig.isShared check: display the
Typography copy for the emulator ("Email server settings are read-only in the
local emulator") when isLocalEmulator is true, hide the TestSendingDialog
trigger when emailConfig.isShared || isLocalEmulator, hide the
EditEmailServerDialog trigger when isLocalEmulator is true, and render the Alert
with AlertDescription when isLocalEmulator is true so the Playground shows the
read-only copy, conditional buttons, and informational alert; reference the
existing components/props used in the page (isLocalEmulator,
emailConfig.isShared, TestSendingDialog, EditEmailServerDialog, Alert,
AlertDescription, Typography, Button) so the Playground mirrors the real UI
behavior.

In `@apps/dashboard/src/lib/config-update.tsx`:
- Around line 272-275: Replace the blocking alert() call in the isLocalEmulator
branch with the dashboard's non-blocking toast notification so UX stays
consistent: instead of alert("These settings are read-only..."), call the
existing toast/notification utility used elsewhere in the app (e.g., toast or
notify) with the same message and an appropriate level (info/warning), keep the
return false behavior, and add/import the toast helper if not already present so
the change is local to the isLocalEmulator conditional.

In
`@apps/e2e/tests/backend/endpoints/api/v1/internal/config-local-emulator.test.ts`:
- Around line 21-26: The test is only setting partial projectKeys via
backendContext.set({ projectKeys: { projectId, superSecretAdminKey } }) and
misses other keys (e.g., secretServerKey) returned by the provision endpoint;
add a shared helper (e.g., setProjectKeysFromResponse(response) or
populateProjectKeys) that extracts all required keys from the provision response
(project_id, super_secret_admin_key, secret_server_key, etc.) and calls
backendContext.set({ projectKeys: { ... } }), then replace the inline object in
this test and local-emulator-project.test.ts with that helper to ensure
consistency.

In
`@apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts`:
- Line 7: Replace the duplicated hardcoded LOCAL_EMULATOR_OWNER_TEAM_ID in the
test with a single import of that constant from the source module that defines
it (the local-emulator module) so the test always uses the canonical value;
remove the const declaration in the test, add an import for
LOCAL_EMULATOR_OWNER_TEAM_ID, and if necessary adjust the test's module
resolution/import path so the test can import from the local-emulator module.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53c1c9e and ca89fd8.

📒 Files selected for processing (32)
  • .github/workflows/docker-emulator-test.yaml
  • .github/workflows/e2e-api-tests-local-emulator.yaml
  • apps/backend/.env
  • apps/backend/.env.development
  • apps/backend/prisma/migrations/20260226100000_add_local_emulator_project_mapping/migration.sql
  • apps/backend/prisma/migrations/20260226100000_add_local_emulator_project_mapping/tests/cascades-on-project-delete.ts
  • apps/backend/prisma/migrations/20260226100000_add_local_emulator_project_mapping/tests/persists-and-enforces-uniqueness.ts
  • apps/backend/prisma/schema.prisma
  • apps/backend/prisma/seed.ts
  • apps/backend/src/app/api/latest/internal/config/override/[level]/reset-keys/route.tsx
  • apps/backend/src/app/api/latest/internal/config/override/[level]/route.tsx
  • apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
  • apps/backend/src/lib/config.tsx
  • apps/backend/src/lib/local-emulator.ts
  • apps/dashboard/.env
  • apps/dashboard/.env.development
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/layout-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/lib/config-update.tsx
  • apps/dashboard/src/lib/env.tsx
  • apps/dashboard/src/lib/utils.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/internal/config-local-emulator.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/internal/local-emulator-project.test.ts
  • docker/README.md
  • docker/emulator/docker.compose.yaml
  • docker/emulator/inbucket-nginx.conf
  • docker/readme.md
💤 Files with no reviewable changes (7)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/layout.tsx
  • apps/dashboard/src/lib/utils.tsx
  • docker/readme.md
  • docker/emulator/inbucket-nginx.conf
  • docker/README.md
  • docker/emulator/docker.compose.yaml
  • .github/workflows/docker-emulator-test.yaml

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 26, 2026

Greptile Summary

Introduces local emulator mode gated by NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR, enabling deterministic project provisioning from config file paths with read-only environment configs.

Key Changes:

  • New LocalEmulatorProject database model maps absolute file paths to project IDs with CASCADE delete
  • Internal endpoint POST /api/v1/internal/local-emulator/project provisions projects with long-lived API keys (expires 2099)
  • Environment config overrides become read-only for local emulator projects (blocked at API and lib layers)
  • Dashboard disables normal project creation, adds "Open config file" flow with path validation
  • Seed script creates dedicated local emulator owner team and admin user
  • Comprehensive E2E tests cover provisioning, reuse, and config restrictions

Issues Found:

  • UUID generation in getDeterministicProjectIdFromPath skips hash indices 12 and 16, potentially causing collisions

Confidence Score: 3/5

  • Safe for local development but contains a deterministic UUID generation bug that could cause project ID collisions
  • The implementation has strong architectural boundaries and comprehensive tests, but the UUID generation bug in getDeterministicProjectIdFromPath (line 23-26 of route.tsx) skips hash characters, breaking the deterministic guarantee and potentially causing different file paths to map to the same project ID. All other aspects are well-implemented with proper security gating, defense-in-depth config restrictions, and thorough test coverage.
  • apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx needs the UUID generation fixed before merge

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx New endpoint for local emulator project provisioning. Contains UUID generation bug in getDeterministicProjectIdFromPath that skips hash characters.
apps/backend/src/lib/config.tsx Adds environment config restrictions for local emulator projects with proper defense-in-depth at multiple layers.
apps/backend/src/app/api/latest/internal/config/override/[level]/route.tsx Enforces read-only environment config for local emulator projects in PUT and PATCH handlers.
apps/backend/prisma/schema.prisma Adds LocalEmulatorProject model with proper cascade delete and unique constraints.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx Adds "Open config file" flow for local emulator with proper path validation for Unix/Windows.

Sequence Diagram

sequenceDiagram
    participant Dashboard
    participant Internal API
    participant LocalEmulatorEndpoint
    participant Database
    participant SeedScript

    Note over SeedScript,Database: Setup Phase (when NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=true)
    SeedScript->>Database: Create LOCAL_EMULATOR_OWNER_TEAM
    SeedScript->>Database: Create LOCAL_EMULATOR_ADMIN_USER
    SeedScript->>Database: Add user to team

    Note over Dashboard,Database: Project Provisioning Flow
    Dashboard->>Internal API: POST /internal/local-emulator/project<br/>{absolute_file_path}
    Internal API->>LocalEmulatorEndpoint: Validate path is absolute
    LocalEmulatorEndpoint->>Database: Check for existing LocalEmulatorProject
    
    alt Project exists
        LocalEmulatorEndpoint->>LocalEmulatorEndpoint: Reuse existing project_id
    else New project
        LocalEmulatorEndpoint->>LocalEmulatorEndpoint: Generate deterministic UUID from path hash
    end
    
    LocalEmulatorEndpoint->>Database: Upsert Project
    LocalEmulatorEndpoint->>Database: Upsert Tenancy
    LocalEmulatorEndpoint->>Database: Upsert LocalEmulatorProject mapping
    LocalEmulatorEndpoint->>Database: Get or create API key set
    LocalEmulatorEndpoint-->>Dashboard: Return {project_id, keys, branch_override}

    Note over Dashboard,Database: Config Restriction Flow
    Dashboard->>Internal API: PUT/PATCH /config/override/environment
    Internal API->>Database: Check isLocalEmulatorProject(projectId)
    
    alt Is local emulator project
        Internal API-->>Dashboard: 400 "cannot be changed in local emulator"
    else Normal project
        Internal API->>Database: Update environment config
        Internal API-->>Dashboard: 200 OK
    end
Loading

Last reviewed commit: ca89fd8

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

32 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/dashboard/src/lib/config-update.tsx (1)

266-285: Consider extracting the local emulator check to a shared constant or utility.

The pattern getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true" is duplicated on lines 47 and 266. Extracting it to a module-level constant or a dedicated hook (e.g., useIsLocalEmulator) would improve maintainability and reduce the risk of inconsistency.

♻️ Example extraction
+const IS_LOCAL_EMULATOR = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true";
+
 export function ConfigUpdateDialogProvider({ children }: { children: React.ReactNode }) {
   // ...
   const showPushableDialog = useCallback(async (...) => {
     // ...
-    const isLocalEmulator = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true";
+    const isLocalEmulator = IS_LOCAL_EMULATOR;
     // ...
   }, []);
   // ...
 }

 export function useUpdateConfig() {
   const { showPushableDialog } = useConfigUpdateDialog();
-  const isLocalEmulator = getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true";
+  const isLocalEmulator = IS_LOCAL_EMULATOR;
   // ...
-  }, [isLocalEmulator, showPushableDialog]);
+  }, [showPushableDialog]); // isLocalEmulator is now a stable module-level constant
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/lib/config-update.tsx` around lines 266 - 285, Extract the
duplicated environment check
getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true" into a shared
utility (either a module-level constant or a hook like useIsLocalEmulator) and
replace the inline checks in this file (the isLocalEmulator local variable
inside the useCallback) and the other occurrence referenced in the review with
an import/use of that shared symbol; update this file to call the new utility
(e.g., const isLocalEmulator = useIsLocalEmulator() or import {
IS_LOCAL_EMULATOR } from ...) and remove the direct getPublicEnvVar call,
keeping existing behavior in the useCallback that shows an alert and prevents
updates when true, and ensure showPushableDialog and project.updateConfig paths
remain unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/dashboard/src/lib/config-update.tsx`:
- Around line 266-285: Extract the duplicated environment check
getPublicEnvVar("NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR") === "true" into a shared
utility (either a module-level constant or a hook like useIsLocalEmulator) and
replace the inline checks in this file (the isLocalEmulator local variable
inside the useCallback) and the other occurrence referenced in the review with
an import/use of that shared symbol; update this file to call the new utility
(e.g., const isLocalEmulator = useIsLocalEmulator() or import {
IS_LOCAL_EMULATOR } from ...) and remove the direct getPublicEnvVar call,
keeping existing behavior in the useCallback that shows an alert and prevents
updates when true, and ensure showPushableDialog and project.updateConfig paths
remain unchanged.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ca89fd8 and 9c004e8.

📒 Files selected for processing (1)
  • apps/dashboard/src/lib/config-update.tsx

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx (1)

177-187: Wrap async handler in runAsynchronouslyWithAlert for proper error handling.

While the Button component handles loading states, errors thrown in handleOpenConfigFile (lines 55, 62, 69, 89, 98, 100, 109) should be shown to users via alerts rather than potentially being swallowed. The coding guidelines specify using runAsynchronouslyWithAlert for error handling in async handlers.

♻️ Proposed fix
+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
          <Button
            onClick={async () => {
              if (isLocalEmulator) {
                setOpenConfigFileDialog(true);
                return;
              }
              router.push("/new-project");
              return await wait(2000);
            }}
          >{isLocalEmulator ? "Open config file" : "Create Project"}
          </Button>

And for line 219:

-          <Button onClick={handleOpenConfigFile} loading={openingConfigFile}>
+          <Button onClick={() => runAsynchronouslyWithAlert(handleOpenConfigFile())} loading={openingConfigFile}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
around lines 177 - 187, The inline async onClick handler for the Button should
be wrapped with runAsynchronouslyWithAlert to surface any thrown errors to the
user; replace the current async () => { ... } with () =>
runAsynchronouslyWithAlert(async () => { if (isLocalEmulator) {
setOpenConfigFileDialog(true); return; } router.push("/new-project"); await
wait(2000); }); so the same logic (isLocalEmulator check,
setOpenConfigFileDialog, router.push, wait) runs inside
runAsynchronouslyWithAlert and errors from this handler (and related functions
like handleOpenConfigFile) are shown via alerts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`:
- Around line 98-105: The ON CONFLICT clause in the
globalPrismaClient.$executeRaw insert into "LocalEmulatorProject" currently does
DO UPDATE SET "projectId" = EXCLUDED."projectId", which can overwrite an
existing mapping when projectId is newly generated; change the conflict behavior
to preserve the original mapping (either use DO NOTHING on conflict or only
update "updatedAt") so existing absoluteFilePath -> projectId mappings are not
replaced; update the INSERT statement targeting "LocalEmulatorProject"
(references: absoluteFilePath, projectId, globalPrismaClient.$executeRaw) to use
the safer ON CONFLICT behavior.
- Around line 58-108: The current getOrCreateLocalEmulatorProjectId has a race
where two concurrent callers can generate different projectIds and create
orphaned Project/Tenancy rows; fix by making the operation atomic: wrap the
logic in a single Prisma transaction (Prisma.$transaction) with a serializable
isolation level and perform an INSERT ... ON CONFLICT ... RETURNING "projectId"
(using globalPrismaClient.$executeRaw/ $queryRaw inside the transaction) to
obtain a canonical projectId for the given "absoluteFilePath", then call
project.upsert and tenancy.upsert using that returned projectId (keep references
to getOrCreateLocalEmulatorProjectId, LocalEmulatorProject, project.upsert,
tenancy.upsert and the $executeRaw/$queryRaw usage).

---

Nitpick comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/page-client.tsx:
- Around line 177-187: The inline async onClick handler for the Button should be
wrapped with runAsynchronouslyWithAlert to surface any thrown errors to the
user; replace the current async () => { ... } with () =>
runAsynchronouslyWithAlert(async () => { if (isLocalEmulator) {
setOpenConfigFileDialog(true); return; } router.push("/new-project"); await
wait(2000); }); so the same logic (isLocalEmulator check,
setOpenConfigFileDialog, router.push, wait) runs inside
runAsynchronouslyWithAlert and errors from this handler (and related functions
like handleOpenConfigFile) are shown via alerts.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c004e8 and 6311e69.

📒 Files selected for processing (2)
  • apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/backend/src/lib/config.tsx (1)

1079-1108: Consider adding test coverage for the allowing cases.

The test verifies that writes are blocked when all guard conditions are met, which is good. However, it would strengthen confidence to also test that writes are allowed when:

  1. STACK_SEED_MODE is "true" (seeding should bypass the guard)
  2. The project is not a local emulator project
  3. Local emulator mode is disabled

This would ensure the guard doesn't accidentally block legitimate writes.

💡 Example additional test case for seed mode bypass
import.meta.vitest?.test('setEnvironmentConfigOverride allows writes in seed mode', async ({ expect }) => {
  const vi = import.meta.vitest?.vi;
  if (!vi) {
    throw new StackAssertionError("Vitest context is required for in-source tests.");
  }

  const envUtils = await import("@stackframe/stack-shared/dist/utils/env");
  const localEmulator = await import("./local-emulator");

  const getEnvVariableSpy = vi.spyOn(envUtils, "getEnvVariable").mockImplementation((name: string, defaultValue?: string) => {
    if (name === "STACK_SEED_MODE") {
      return "true"; // Seed mode enabled
    }
    return defaultValue ?? "test-value";
  });
  const isLocalEmulatorEnabledSpy = vi.spyOn(localEmulator, "isLocalEmulatorEnabled").mockReturnValue(true);
  const isLocalEmulatorProjectSpy = vi.spyOn(localEmulator, "isLocalEmulatorProject").mockResolvedValue(true);

  try {
    // Should not throw - mock the DB call if needed or use a different assertion
    // This is a conceptual example; actual implementation depends on DB mocking strategy
  } finally {
    isLocalEmulatorProjectSpy.mockRestore();
    isLocalEmulatorEnabledSpy.mockRestore();
    getEnvVariableSpy.mockRestore();
  }
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/lib/config.tsx` around lines 1079 - 1108, Add positive test
coverage confirming setEnvironmentConfigOverride does not throw when the
local-emulator guard should be bypassed: create three new tests that mock
env.utils.getEnvVariable and ./local-emulator helpers and assert success (or
non-throw) for (1) STACK_SEED_MODE === "true", (2) isLocalEmulatorProject
returns false, and (3) isLocalEmulatorEnabled returns false; restore mocks in
finally blocks and reuse the same LOCAL_EMULATOR_ENV_CONFIG_BLOCKED_MESSAGE and
setEnvironmentConfigOverride symbol names so readers can locate the original
failing test and the guard logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/backend/src/lib/config.tsx`:
- Around line 1079-1108: Add positive test coverage confirming
setEnvironmentConfigOverride does not throw when the local-emulator guard should
be bypassed: create three new tests that mock env.utils.getEnvVariable and
./local-emulator helpers and assert success (or non-throw) for (1)
STACK_SEED_MODE === "true", (2) isLocalEmulatorProject returns false, and (3)
isLocalEmulatorEnabled returns false; restore mocks in finally blocks and reuse
the same LOCAL_EMULATOR_ENV_CONFIG_BLOCKED_MESSAGE and
setEnvironmentConfigOverride symbol names so readers can locate the original
failing test and the guard logic.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6311e69 and c398730.

📒 Files selected for processing (2)
  • apps/backend/src/app/api/latest/internal/config/override/[level]/route.tsx
  • apps/backend/src/lib/config.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/backend/src/app/api/latest/internal/config/override/[level]/route.tsx

@N2D4
Copy link
Contributor Author

N2D4 commented Mar 9, 2026

closing in favor of #1233

@N2D4 N2D4 closed this Mar 9, 2026
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