Implement test realm management for AI-generated tests#4265
Implement test realm management for AI-generated tests#4265
Conversation
Host Test Results 1 files ± 0 1 suites ±0 2h 12m 27s ⏱️ + 9m 42s For more details on these errors, see this check. Results for commit 5abb98e. ± Comparison against base commit 5d48ebc. ♻️ This comment has been updated with latest results. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3da0aad612
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
packages/host/dist
Outdated
| @@ -0,0 +1 @@ | |||
| /home/hassan/codez/boxel/packages/host/dist No newline at end of file | |||
There was a problem hiding this comment.
Remove machine-specific host dist symlink
This commit adds packages/host/dist as a symlink to an absolute local path (/home/hassan/codez/...), which is not portable to other developer machines or CI. Any workflow that relies on packages/host/dist will now see a dangling or incorrect path outside the repository, causing non-reproducible failures when the harness/tests try to use host assets.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
let's git ignore this instead
There was a problem hiding this comment.
Done — removed the symlink from git (git rm --cached) and added packages/host/dist to .gitignore.
|
|
||
| let realmUrl = new URL(projectCardUrl).origin + '/'; | ||
| let cardPath = new URL(projectCardUrl).pathname.slice(1); | ||
| await writeCardSource(realmUrl, `${cardPath}.json`, readResult.document, fetchOptions); |
There was a problem hiding this comment.
Propagate failure when project card update does not persist
After creating a test artifacts realm, this path writes testArtifactsRealmUrl back to the Project card but ignores the writeCardSource result. If that write fails (auth, validation, network), the function still reports success, so subsequent runs will keep creating new realms because the Project card was never updated. This silently leaks realms and gives callers a false success state.
Useful? React with 👍 / 👎.
Preview deployments |
20baf0c to
dc22611
Compare
- Add TestRun card definition (test-results.gts) with TestResultEntry FieldDef, status enums, CodeRefField, and fitted/isolated templates - Add factory-test-realm.ts with test execution orchestration, card lifecycle (createTestRun, completeTestRun, resolveTestRun), resume logic, and executeTestRunFromRealm - Add realm-operations.ts shared lib consolidating searchRealm, readCardSource, writeCardSource, pullRealmFiles, cancelAllIndexingJobs, ensureTrailingSlash - Add ensureTestArtifactsRealm for auto-creating test realms from Project card - Add testArtifactsRealmUrl field to Project card in darkfactory.gts - Extend _cancel-indexing-job to cancel pending jobs via cancelPending body param - Add combined cache:prepare infrastructure (--combined flag, additionalRealms, buildCombinedTemplateDatabase, ensureCombinedFactoryRealmTemplate) - Rename card instance folders to plural display names: Projects/, Tickets/, Knowledge Articles/, Agent Profiles/, Test Runs/, Tests/ - Move test specs and TestRun cards to target realm (not test realm) - Update all prompts to reference Tests/ in target realm - Fix 401 auth flake by always rewriting template URLs in startFactoryRealmServer - Relax assertUsableHostDist to accept dev host builds - Bump Postgres max_connections from 20 to 40 for combined template support - Add coveredRealmDirs to PreparedTemplateMetadata for combined template lookup - Add test-realm-runner fixture with HelloCard + passing/failing specs - Add smoke:test-realm script for manual verification 316/316 unit tests, 13/13 Playwright tests, 0 glint errors. Tickets: CS-10419, CS-10530, CS-10536, CS-10537, CS-10538, CS-10540 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dc22611 to
a699f1c
Compare
There was a problem hiding this comment.
Pull request overview
Implements end-to-end “test realm management” for AI-generated Playwright tests by introducing TestRun card artifacts, shared realm HTTP operations, combined template DB caching, and updated fixture/layout conventions (pluralized folders, test specs in Tests/ in the target realm).
Changes:
- Added
factory-test-realmorchestration (TestRun creation/completion, resume logic, Playwright execution + parsing) andtest-results.gtscard/field definitions for durable test result storage. - Extracted shared realm HTTP helpers into
scripts/lib/realm-operations.tsand updated scripts/tests to use it (including indexing job cancellation with optional pending-job cancellation). - Added combined cache/template support for multiple fixtures and updated tests/fixtures/prompts to match new conventions (plural instance folders, specs/results in target realm, new e2e fixtures/tests).
Reviewed changes
Copilot reviewed 60 out of 62 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/software-factory/tests/index.ts | Registers the new factory-test-realm unit test module. |
| packages/software-factory/tests/fixtures.ts | Allows template metadata to cover multiple realm dirs (combined template support). |
| packages/software-factory/tests/factory-test-realm.test.ts | Adds unit coverage for parsing, TestRun lifecycle, resume logic, and realm pulls. |
| packages/software-factory/tests/factory-test-realm.spec.ts | Adds Playwright e2e tests for executing and completing a TestRun. |
| packages/software-factory/tests/factory-target-realm.spec.ts | Updates expected card paths to pluralized folders. |
| packages/software-factory/tests/factory-skill-loader.test.ts | Updates fixture IDs to pluralized folders. |
| packages/software-factory/tests/factory-prompt-loader.test.ts | Updates prompt tests for pluralized IDs and removes test-realm mention. |
| packages/software-factory/tests/factory-entrypoint.test.ts | Updates bootstrap expectations to pluralized folders. |
| packages/software-factory/tests/factory-entrypoint.integration.test.ts | Updates integration expectations to pluralized folders. |
| packages/software-factory/tests/factory-brief.test.ts | Updates darkfactory fixture path expectations to pluralized folders. |
| packages/software-factory/tests/factory-bootstrap.test.ts | Updates bootstrap tests for pluralized folders and URL decoding. |
| packages/software-factory/tests/factory-bootstrap.spec.ts | Updates e2e bootstrap expectations and navigation paths. |
| packages/software-factory/tests/factory-agent.test.ts | Updates agent context IDs to pluralized folders. |
| packages/software-factory/tests/factory-agent.integration.test.ts | Updates integration expectations for pluralized ticket IDs. |
| packages/software-factory/test-fixtures/test-realm-runner/index.json | Adds a new realm fixture for running the test-realm e2e flow. |
| packages/software-factory/test-fixtures/test-realm-runner/home.gts | Adds a simple Home card for the test realm runner fixture. |
| packages/software-factory/test-fixtures/test-realm-runner/hello.gts | Adds a HelloCard used by e2e tests. |
| packages/software-factory/test-fixtures/test-realm-runner/Tests/hello-passing.spec.ts | Adds a passing Playwright spec for e2e coverage. |
| packages/software-factory/test-fixtures/test-realm-runner/Tests/hello-failing.spec.ts | Adds a failing Playwright spec for e2e error/failure coverage. |
| packages/software-factory/test-fixtures/test-realm-runner/HelloCard/sample.json | Adds a sample card instance used by e2e specs. |
| packages/software-factory/test-fixtures/test-realm-runner/.realm.json | Adds realm metadata for the new fixture realm. |
| packages/software-factory/test-fixtures/darkfactory-adopter/Ticket/ticket-001.json | Updates relationship links to pluralized folders. |
| packages/software-factory/test-fixtures/darkfactory-adopter/Project/demo-project.json | Updates relationship links to pluralized folders. |
| packages/software-factory/test-fixtures/darkfactory-adopter/KnowledgeArticle/agent-onboarding.json | Updates relationship links to pluralized folders. |
| packages/software-factory/src/runtime-metadata.ts | Extends prepared template metadata with coveredRealmDirs. |
| packages/software-factory/src/harness/support-services.ts | Relaxes host dist validation to allow dev/prod builds. |
| packages/software-factory/src/harness/shared.ts | Updates PG connection comment and adds combined fixture hashing. |
| packages/software-factory/src/harness/isolated-realm-stack.ts | Adds support for mounting additional realms in isolated stacks. |
| packages/software-factory/src/harness/database.ts | Adds combined template DB build for multiple fixtures. |
| packages/software-factory/src/harness/api.ts | Exposes ensureCombinedFactoryRealmTemplate and fixes realm-server URL rewrite for cloned DBs. |
| packages/software-factory/src/harness.ts | Re-exports combined template helpers and types. |
| packages/software-factory/src/factory-brief.ts | Switches to shared cardSourceMimeType constant. |
| packages/software-factory/src/factory-bootstrap.ts | Moves bootstrap artifacts to pluralized folder names. |
| packages/software-factory/src/cli/smoke-test-realm.ts | Adds CLI smoke test for the full test-realm pipeline. |
| packages/software-factory/src/cli/cache-realm.ts | Adds --combined support for multi-fixture template preparation. |
| packages/software-factory/scripts/run-realm-tests.ts | Uses shared ensureTrailingSlash helper. |
| packages/software-factory/scripts/lib/realm-operations.ts | Introduces shared realm HTTP operations (search/read/write/pull/cancel indexing). |
| packages/software-factory/scripts/lib/factory-tool-executor.ts | Uses shared ensureTrailingSlash helper. |
| packages/software-factory/scripts/lib/factory-test-realm.ts | Adds core orchestration logic for TestRun creation/completion, resume, artifacts realm, and Playwright execution. |
| packages/software-factory/scripts/lib/boxel.ts | Uses shared ensureTrailingSlash helper. |
| packages/software-factory/scripts/factory-skill-smoke.ts | Updates sample IDs to pluralized folders. |
| packages/software-factory/scripts/factory-prompt-smoke.ts | Updates sample IDs to pluralized folders. |
| packages/software-factory/scripts/factory-agent-smoke.ts | Updates sample IDs to pluralized folders. |
| packages/software-factory/realm/test-results.gts | Adds TestRun/TestResultEntry definitions and UI templates. |
| packages/software-factory/realm/darkfactory.gts | Adds testArtifactsRealmUrl field to Project. |
| packages/software-factory/prompts/ticket-test.md | Updates guidance: tests live in target realm Tests/. |
| packages/software-factory/prompts/ticket-implement.md | Updates guidance: tests live in target realm Tests/. |
| packages/software-factory/prompts/system.md | Updates system rules and removes explicit test realm mention. |
| packages/software-factory/prompts/examples/iterate-fix.md | Updates example to write tests to Tests/ in target realm. |
| packages/software-factory/prompts/examples/create-test.md | Updates example to write tests to Tests/ in target realm. |
| packages/software-factory/prompts/examples/create-card.md | Updates example to write tests to Tests/ in target realm. |
| packages/software-factory/prompts/action-schema.md | Updates schema docs: create_test/update_test must target realm. |
| packages/software-factory/playwright.global-setup.ts | Prepares template cache for the new test realm runner fixture. |
| packages/software-factory/package.json | Adds smoke:test-realm script. |
| packages/software-factory/docs/one-shot-factory-go-plan.md | Updates design doc to reflect new test artifacts & locations. |
| packages/runtime-common/realm.ts | Extends _cancel-indexing-job to optionally cancel pending jobs. |
| packages/runtime-common/job-utils.ts | Adds cancelAllJobsInConcurrencyGroup (running + pending). |
| packages/realm-server/tests/scripts/boot_preseeded.sh | Increases Postgres max_connections to 40 for test stability. |
| packages/realm-server/tests/realm-endpoints/cancel-indexing-job-test.ts | Adds coverage for cancelPending behavior and defaults. |
| packages/host/dist | Adds a tracked file pointing at a local dist directory path. |
Comments suppressed due to low confidence (3)
packages/host/dist:1
- This looks like an accidentally committed local path placeholder (and potentially leaks a developer machine path). If this is meant to represent a build artifact location, it should not be tracked in git—remove this file from the repo and add the appropriate pattern to .gitignore (or replace with a documented config mechanism).
packages/software-factory/tests/factory-brief.test.ts:1 - The test now reads the fixture from
test-fixtures/darkfactory-adopter/Tickets/ticket-001.json, but the diff shows the fixture file still lives undertest-fixtures/darkfactory-adopter/Ticket/ticket-001.json(singular). This will fail at runtime with ENOENT. Rename/move the fixture directories/files to match the new pluralized folder conventions (e.g.,Ticket/→Tickets/,Project/→Projects/,KnowledgeArticle/→Knowledge Articles/) and update any remaining references accordingly.
packages/software-factory/tests/fixtures.ts:1 resolvedRealmDiris never reassigned; it should be aconstto better communicate intent and prevent accidental mutation.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| let realmUrl = new URL(projectCardUrl).origin + '/'; | ||
| let cardPath = new URL(projectCardUrl).pathname.slice(1); | ||
| await writeCardSource(realmUrl, `${cardPath}.json`, readResult.document, fetchOptions); |
There was a problem hiding this comment.
ensureTestArtifactsRealm ignores whether persisting testArtifactsRealmUrl back to the Project card succeeded. If the write fails, subsequent runs may keep creating new realms and the returned created: true becomes misleading. Capture the result of writeCardSource(...), and return { created: false, error: ... } (or at least surface a warning/error) when it fails.
| await writeCardSource(realmUrl, `${cardPath}.json`, readResult.document, fetchOptions); | |
| try { | |
| await writeCardSource(realmUrl, `${cardPath}.json`, readResult.document, fetchOptions); | |
| } catch (err) { | |
| return { | |
| testArtifactsRealmUrl: '', | |
| created: false, | |
| error: `Failed to persist testArtifactsRealmUrl to Project card: ${ | |
| err instanceof Error ? err.message : String(err) | |
| }`, | |
| }; | |
| } |
There was a problem hiding this comment.
Fixed — now captures the writeCardSource result and returns { created: false, error } when the Project card update fails.
| authorization?: string; | ||
| fetch?: typeof globalThis.fetch; | ||
| realmServerUrl: string; | ||
| darkfactoryModuleUrl: string; |
There was a problem hiding this comment.
darkfactoryModuleUrl is accepted in ensureTestArtifactsRealm options but is never used in the function body. Remove it from the signature to avoid confusion, or use it (e.g., for writing an adoptsFrom value or any darkfactory-specific behavior if intended).
| darkfactoryModuleUrl: string; |
There was a problem hiding this comment.
Removed from the signature.
| /** | ||
| * Find spec files within a local directory, filtering by the requested | ||
| * spec paths. If specPaths are relative paths like `TestSpec/hello.spec.ts`, | ||
| * resolve them within the local dir. Falls back to finding all `.spec.ts` | ||
| * files if no specPaths match. | ||
| */ | ||
| function findSpecFiles(localDir: string, specPaths: string[]): string[] { | ||
| let found: string[] = []; | ||
| for (let specPath of specPaths) { | ||
| let fullPath = resolve(localDir, specPath); | ||
| if (existsSync(fullPath)) { | ||
| found.push(fullPath); | ||
| } | ||
| } | ||
| return found; | ||
| } |
There was a problem hiding this comment.
The doc comment says this function falls back to finding all .spec.ts files if none of specPaths match, but the implementation currently only returns matched paths (or an empty list). Either implement the fallback (e.g., walk localDir and collect *.spec.ts) or update the comment to match the actual behavior.
There was a problem hiding this comment.
Updated the doc comment to match actual behavior — no fallback walk, only returns matched paths.
| let pullTargetResult = await pullRealmFiles( | ||
| options.targetRealmUrl, | ||
| targetLocalDir, | ||
| { authorization: options.authorization, fetch: options.fetch }, | ||
| ); |
There was a problem hiding this comment.
executeTestRunFromRealm pulls the same target realm twice (once into targetLocalDir and again into specsLocalDir), which doubles the network I/O and disk writes since pullRealmFiles downloads all realm files discovered via _mtimes. Consider pulling once and either (a) point Playwright at the spec files under targetLocalDir, or (b) enhance pullRealmFiles to support an include-prefix filter so only Tests/ is downloaded for the specs directory.
There was a problem hiding this comment.
Fixed — single pull into targetLocalDir, specs discovered from the same directory.
| // Step 6: Pull test specs from target realm to local temp dir. | ||
| // Specs now live in the target realm's Tests/ folder. | ||
| // TODO: Refactor to use `boxel pull` CLI once it supports --jwt. | ||
| let pullSpecsResult = await pullRealmFiles( | ||
| options.targetRealmUrl, | ||
| specsLocalDir, | ||
| { authorization: options.authorization, fetch: options.fetch }, | ||
| ); | ||
| if (pullSpecsResult.error) { | ||
| let errorMessage = `Failed to pull test specs: ${pullSpecsResult.error}`; | ||
| await completeTestRun(testRunId, { | ||
| status: 'error', | ||
| passedCount: 0, | ||
| failedCount: 0, | ||
| errorMessage, | ||
| results: [], | ||
| }, realmOptions); | ||
| return { testRunId, status: 'error', errorMessage }; | ||
| } | ||
|
|
||
| // Find spec files in the pulled test realm. | ||
| let specFiles = findSpecFiles(specsLocalDir, options.specPaths); |
There was a problem hiding this comment.
executeTestRunFromRealm pulls the same target realm twice (once into targetLocalDir and again into specsLocalDir), which doubles the network I/O and disk writes since pullRealmFiles downloads all realm files discovered via _mtimes. Consider pulling once and either (a) point Playwright at the spec files under targetLocalDir, or (b) enhance pullRealmFiles to support an include-prefix filter so only Tests/ is downloaded for the specs directory.
| // Step 6: Pull test specs from target realm to local temp dir. | |
| // Specs now live in the target realm's Tests/ folder. | |
| // TODO: Refactor to use `boxel pull` CLI once it supports --jwt. | |
| let pullSpecsResult = await pullRealmFiles( | |
| options.targetRealmUrl, | |
| specsLocalDir, | |
| { authorization: options.authorization, fetch: options.fetch }, | |
| ); | |
| if (pullSpecsResult.error) { | |
| let errorMessage = `Failed to pull test specs: ${pullSpecsResult.error}`; | |
| await completeTestRun(testRunId, { | |
| status: 'error', | |
| passedCount: 0, | |
| failedCount: 0, | |
| errorMessage, | |
| results: [], | |
| }, realmOptions); | |
| return { testRunId, status: 'error', errorMessage }; | |
| } | |
| // Find spec files in the pulled test realm. | |
| let specFiles = findSpecFiles(specsLocalDir, options.specPaths); | |
| // Step 6: Find spec files in the pulled test realm. | |
| // Specs live in the target realm's Tests/ folder, which has already | |
| // been pulled into targetLocalDir. | |
| let specFiles = findSpecFiles(targetLocalDir, options.specPaths); |
There was a problem hiding this comment.
Same fix as above — using targetLocalDir for both card content and spec discovery.
| const markdownListReplacePattern = /^\s*[-*+]\s+/gm; | ||
| const whitespacePattern = /\s+/g; | ||
| const cardSourceMimeType = 'application/vnd.card+source'; | ||
| import { cardSourceMimeType } from '../scripts/lib/realm-operations'; |
There was a problem hiding this comment.
This introduces a dependency from src/ (library/runtime code) onto scripts/lib/*. That layering can cause bundling/build issues (scripts are often ts-node/CLI-only and not part of the shipped runtime surface). Prefer moving cardSourceMimeType (or a small shared constants module) into src/ (or another clearly shared package/module) and importing it from there, rather than from scripts/.
| import { cardSourceMimeType } from '../scripts/lib/realm-operations'; | |
| const cardSourceMimeType = 'application/vnd.card+json'; |
There was a problem hiding this comment.
Reverted — factory-brief.ts and factory-bootstrap.ts now use local constants with a comment referencing SupportedMimeType.CardSource. Cannot import the runtime value because Playwright ts-node crashes on runtime-common decorators (tracked in CS-10550).
| * Cancel ALL jobs in a concurrency group — both running (active reservations) | ||
| * and pending (unfulfilled, no active reservation). | ||
| */ | ||
| export async function cancelAllJobsInConcurrencyGroup( |
There was a problem hiding this comment.
why are we not sharing code with job-utils.ts. this seem slike it overlaps with forceCancelJobById
There was a problem hiding this comment.
cancelAllJobsInConcurrencyGroup does share code — it calls cancelRunningJobsInConcurrencyGroup (which calls forceCancelJobById) for running jobs, then calls forceCancelJobById again for each remaining pending job. It composes the existing primitives.
| @field passedCount = contains(NumberField); | ||
| @field failedCount = contains(NumberField); |
There was a problem hiding this comment.
these should probably just be computeds that operate over the results field
There was a problem hiding this comment.
Done — passedCount and failedCount are now computed fields on the TestRun card, derived from the results array.
| @field ticket = linksTo(() => Ticket); | ||
| @field specRef = contains(CodeRefField); | ||
| @field status = contains(TestRunStatusField); | ||
| @field passedCount = contains(NumberField); |
There was a problem hiding this comment.
these should probably just be computeds that operate over the results field
There was a problem hiding this comment.
Done — both are now computed fields.
| passedCount: 0, | ||
| failedCount: 0, |
There was a problem hiding this comment.
i think these can be inferred from the results field--which means we shouldnt have to specify them
There was a problem hiding this comment.
Agreed — removed from the card document builder. The card computes these from the results array.
| passedCount: 0, | ||
| failedCount: 0, |
There was a problem hiding this comment.
ditto on the inference
| passedCount: 0, | ||
| failedCount: 0, |
There was a problem hiding this comment.
ditto on the inference
There was a problem hiding this comment.
factory-test-realm is pretty monolithic. lets split this down into smaller more cohesive files.
There was a problem hiding this comment.
Split into 4 cohesive modules: test-run-types.ts (types), test-run-parsing.ts (result parsing), test-run-cards.ts (card lifecycle), test-run-execution.ts (orchestration/resume). factory-test-realm.ts is now a barrel re-export.
- Split factory-test-realm.ts into cohesive modules: test-run-types.ts, test-run-parsing.ts, test-run-cards.ts, test-run-execution.ts (barrel re-export in factory-test-realm.ts) - Make passedCount/failedCount computed fields on TestRun card (derived from results array, not stored) - Fix double-pull: pull target realm once, find specs in same dir - Remove unused darkfactoryModuleUrl param from ensureTestArtifactsRealm - Fix findSpecFiles doc comment to match actual behavior (no fallback) - Propagate write failure in ensureTestArtifactsRealm - Revert src/ → scripts/lib/ import for cardSourceMimeType (keep local constants in factory-brief.ts and factory-bootstrap.ts) - Make combined cache:prepare the default for multiple realms (remove --combined flag requirement) - Fix formatting (prettier) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The symlink was accidentally committed and breaks CI builds (ember-cli can't mkdir dist/ when a dangling symlink exists). The symlink is a worktree convenience — gitignored so it stays local only. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
factory-test-realm.ts— test execution orchestration, TestRun card lifecycle, resume logic, and result parsingtest-results.gts— TestRun CardDef + TestResultEntry FieldDef with status enums, CodeRefField, and UI templatesrealm-operations.ts— shared realm HTTP operations (search, read, write, pull, cancel indexing)_cancel-indexing-jobto cancel pending jobs (runtime-common)cache:prepareinfrastructure for multiple realms (--combinedflag)assertUsableHostDistto accept dev host buildsTry it out
Prerequisites
mise run dev-allrunningRun the smoke test
The smoke test simulates the full factory workflow — the LLM implementation phase followed by the testing phase.
Phase 1 — Simulate LLM implementation output. The smoke test creates a realm and writes what the LLM would have produced during the implementation phase:
.gts)packages/base/spec.gts) pointing to the HelloCard definitionTests/folder that exercises the cardPhase 2 — Run the testing phase. The smoke test calls
executeTestRunFromRealmwhich runs the full pipeline:status: runningin the target realm'sTest Runs/foldercd packages/software-factory MATRIX_URL=http://localhost:8008 \ MATRIX_USERNAME=your-username \ MATRIX_PASSWORD=your-password \ pnpm smoke:test-realm -- \ --target-realm-url http://localhost:4201/your-username/smoke-test-realm/What to expect on the command line:
What to expect in the Boxel app:
smoke-test-realmworkspacehello.gts), the Spec card (Spec/hello-card) pointing to it, and theTests/folder with the Playwright specTest Runs/you'll findhello-smoke-1— the TestRun card produced by the testing phaseLinear tickets
Test plan
pnpm test:node— 316/316 unit tests passpnpm test:playwright— 13/13 Playwright tests pass (0 flakes)pnpm lint:glint— 0 errors in changed files🤖 Generated with Claude Code