feat(glass-office): Directory / Front Desk as the fifth Basement room - #113
Conversation
The stable directory room now lands on Front Desk, the one rehomed People & Routing authority, beside direct Agent Mail and the temporarily retained File locks. - First hostile MailPage component suite (24 tests, red-first): direct Mail and File-lock paths, exact 8/6 pagination boundaries with shrink/shrink/regrow, mobile list/detail, filters/clear, create/ack/ download/compose/send/summarize, lease create/release, empty states, and honest failure retention. - Replace React-state-only submit guards with ref-based synchronous locks (send, thread create, lease create/release) and clamp stored pagination pages at render so a shrink can never resurrect a stale later page. - Extract People & Routing verbatim into one shared usePeopleRoutingController (mounted once in App) plus PeopleRoutingSection; Directory / Front Desk is its authoritative home with a ready-surface-only pin. Team's People & Routing tab becomes an exact stable-room handoff and its remove-agent cleanup delegates to the shared controller, so no second editable truth or fetch loop exists. - Mail landing seam: entering directory selects Front Desk; internal choices persist across unrelated rerenders; Messages and File locks stay one tap away and are never labeled Directory data. - Register the hidden directory office block with unit proofs for the tenth-shortcut full-canvas byte immutability (every earlier shortcut genuinely present, 24/24 cells) and the freed-canvas repeat. - Browser proof (p5-directory-slice, @core): room identity, live routing save, both paginations with live shrink/regrow, config-only pinning with zero sensitive mutations recorded, office door disable/restore/reload with exact restored-door execution, DF mark/badge non-overlap, and inner geometry at desktop and 390px, over new stateful mock-gateway agent-mail fixtures. - Fix two real presentation defects the proof exposed: the routing card's two-column field grid overflowed its surface at 390px, and thread rows painted underneath the sidebar pagination (the list is now a bounded scroll region). Validation: typecheck, lint, unit 545/545, build, core e2e 41/41 (including updated p4-staff handoff and the p5-models/p5-breakers anchors), ExecAss validator PASS, git diff --check clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
🚧 Files skipped from review as they are similar to previous changes (5)
📝 WalkthroughWalkthroughAdds a Directory / Front Desk room with centralized People & Routing, stateful agent mail and file leases, stable room navigation, Office pinning, responsive layout updates, and comprehensive unit and end-to-end coverage. ChangesDirectory / Front Desk
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant App
participant AppContent
participant MailPage
participant PeopleRoutingSection
participant RuntimeAPI
App->>AppContent: pass shared routing controller
AppContent->>MailPage: pass activeRoomId and peopleRouting
MailPage->>PeopleRoutingSection: render Front Desk
PeopleRoutingSection->>RuntimeAPI: load or save routing configuration
RuntimeAPI-->>PeopleRoutingSection: return routing state
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (11)
apps/mission-control/e2e/mockGateway.mjs (4)
849-908: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueSeeded
message_count: 1is inconsistent with the empty message maps for threads 2-9.Only thread 1 gets a message, yet every fixture reports
message_count: 1and alatest_message_preview. The spec clicks "Front desk handoff 2", which will render an empty conversation while the list claims one message. Consider derivingmessage_countfrom the seeded map (or seeding a message per thread) so the mock stays self-consistent.🤖 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 `@apps/mission-control/e2e/mockGateway.mjs` around lines 849 - 908, The agent mail fixtures report one message and a latest-message preview for every thread, but only the first thread has a seeded message. Update createAgentMailThreadFixture and the thread initialization so threads 2–9 accurately report zero messages and no latest-message preview, while preserving the existing populated values for the first thread.
5370-5457: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueThread message routes swallow unsupported methods.
The
threadMessagesMatchblock is entered for any method; aPUT/DELETEfalls out of the block and continues through the remaining route table before hitting the generic 404 text. Same for the detail route (non-GET). Returning 405 (or gating on method in the match) keeps mock failures diagnosable.🤖 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 `@apps/mission-control/e2e/mockGateway.mjs` around lines 5370 - 5457, Update the agent-mail thread detail and message route handlers around threadDetailMatch and threadMessagesMatch so matched unsupported methods return HTTP 405 instead of falling through to the generic 404 response. Preserve the existing GET and POST behavior, and apply the same method handling consistently to both route blocks.
910-925: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winLease
expires_atis frozen at module load, so reset does not refresh TTLs.
created_at/expires_atare captured once at import andresetMockStaterestores those exact cloned values. Any suite running longer than ~14 minutes after gateway start will see leases whoseexpires_atis in the past, which can silently change "7 active file lock(s)" behavior. Consider building leases lazily (a factory invoked inresetMockState) or storing relative offsets.♻️ Sketch: build the lease fixtures from a factory
-const agentMailLeases = Array.from({ length: 7 }, (_, index) => { - const id = nextAgentMailCounter++; - return { +function createAgentMailLeaseFixtures() { + return Array.from({ length: 7 }, (_, index) => ({ lease_id: `mail-lease-${id}`, ... - }; -}); + })); +} +const agentMailLeases = createAgentMailLeaseFixtures();🤖 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 `@apps/mission-control/e2e/mockGateway.mjs` around lines 910 - 925, Replace the module-load construction of agentMailLeases with a lease factory that computes created_at and expires_at from the current time. Invoke the factory from resetMockState so each reset restores seven fresh, unexpired leases while preserving the existing lease fields and pagination data.
5538-5556: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low valueRelease is not idempotent-safe.
A second release of the same lease overwrites
released_atand still returns 200. A guard (if (lease.released_at !== null) → 409) would make double-release regressions visible in e2e instead of passing quietly.🤖 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 `@apps/mission-control/e2e/mockGateway.mjs` around lines 5538 - 5556, Update the lease-release handler around the agentMailLeases lookup to detect an already released lease before modifying it. When lease.released_at is non-null, return HTTP 409 and leave the existing timestamp unchanged; retain the current 404 response for missing leases and 200 response for the first release.apps/mission-control/e2e/p5-directory-slice.spec.ts (4)
47-51: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTreating every
>=400response as a browser error is broad.Favicon/source-map/optional probes returning 404 will fail line 703 for reasons unrelated to the Directory room. Scope the listener to
/api/v1/(or an explicit allowlist) to keep the assertion meaningful.♻️ Narrow the response listener
page.on("response", (response) => { - if (response.status() >= 400) { + if (response.status() >= 400 && response.url().includes("/api/v1/")) { browserErrors.push(`${response.status()} ${response.url()}`); } });🤖 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 `@apps/mission-control/e2e/p5-directory-slice.spec.ts` around lines 47 - 51, Scope the response listener in the P5 directory test to collect only failing responses from the `/api/v1/` endpoint path, while retaining the existing status and URL details. Do not add unrelated browser responses such as favicon, source-map, or optional probe failures to `browserErrors`.
116-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winScreenshot paths are CWD-relative.
"../../runtime/qa/..."resolves against the Playwright process CWD, not the spec file, so the artifacts land elsewhere if the suite is invoked from the repo root. Prefertest.info().outputPath(...)or a path derived fromimport.meta.url/configoutputDir.Also applies to: 232-235, 287-290, 349-352
🤖 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 `@apps/mission-control/e2e/p5-directory-slice.spec.ts` around lines 116 - 119, Update the screenshot calls in the p5-directory-slice tests to use Playwright’s test.info().outputPath(...) or another repository-root-independent path derived from import.meta.url/config outputDir. Apply the same change to all occurrences, including the screenshot calls around the referenced desktop captures, while preserving their existing filenames and options.
292-383: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valuePin flow assertions are solid; one note on the mutation counter.
sensitiveMutations.lengthis compared before/after the pin flow, but requests are recorded asynchronously — a write fired just before line 296 could be counted late and mask a regression. Awaiting a shortpage.waitForTimeout/network-idle before snapshotting the baseline (or comparing the recorded arrays instead of just lengths) would make the config-only claim airtight.🤖 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 `@apps/mission-control/e2e/p5-directory-slice.spec.ts` around lines 292 - 383, Stabilize the mutation baseline in the pin-flow test before assigning mutationsBeforePinFlow. After the preceding navigation and interactions, wait for pending request recording to settle using the existing page synchronization approach (such as a short wait or network idle), then snapshot the counter; keep the final assertion verifying that the pin flow adds no sensitive mutations.
31-34: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftSingle 700-line test with a 150s budget makes failures hard to localize.
The scenario chains room identity, routing save, mail pagination, ack/compose, lease lifecycle, pinning, office door, reload, and two viewports. Any early failure hides everything after it, and
expect(browserErrors).toEqual([])at line 703 never executes on earlier failure. Consider splitting into a fewtest.steps at minimum, or separate tests per concern (desktop parity / pinning + door / 390px layout).Also applies to: 703-704
🤖 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 `@apps/mission-control/e2e/p5-directory-slice.spec.ts` around lines 31 - 34, Split the monolithic test into focused tests or, at minimum, clearly bounded test.step blocks covering room identity/routing, mail and lease lifecycle, pinning/office-door behavior, and 390px layout versus desktop parity. Preserve the existing assertions and ensure browserErrors validation runs independently for each scenario so an earlier failure does not prevent later diagnostics.apps/mission-control/src/features/agentMail/MailPage.test.tsx (1)
146-191: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUntyped overrides defeat the controller contract.
Partial<Record<string, unknown>>plusas unknown as PeopleRoutingControllermeans a rename or added field inusePeopleRoutingControllerwon't fail typecheck here. Typing the parameter asPartial<PeopleRoutingController>and dropping the double cast (keeping a singleas PeopleRoutingControllerif needed) restores drift detection.🤖 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 `@apps/mission-control/src/features/agentMail/MailPage.test.tsx` around lines 146 - 191, Update stubPeopleRouting to accept Partial<PeopleRoutingController> instead of Partial<Record<string, unknown>>, and remove the as unknown as PeopleRoutingController cast. Retain only a direct PeopleRoutingController assertion if needed so controller field changes remain typechecked.apps/mission-control/src/features/peopleRouting/PeopleRoutingSection.tsx (1)
376-390: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueHuman ID field is
readOnlybut the surrounding help text is the only signal. Consider also marking itdisabledor addingaria-readonlyso assistive tech announces the lock; the current input is focusable and looks editable.🤖 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 `@apps/mission-control/src/features/peopleRouting/PeopleRoutingSection.tsx` around lines 376 - 390, Update the Human ID input in the card editor to explicitly communicate its locked state to assistive technologies and users by adding the appropriate disabled treatment or aria-readonly attribute alongside readOnly. Preserve the existing value and help text, and keep the field non-editable.apps/mission-control/src/features/agentMail/MailPage.tsx (1)
112-128: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRoom-landing block is duplicated verbatim with
TeamPage.apps/mission-control/src/features/team/TeamPage.tsx(lines 259-275) carries the identicallastRoomId+ landing-section logic. Extracting a smalluseRoomLandingSection(activeRoomId, map, fallback)hook would keep the two surfaces from drifting as more rooms adopt landing behavior.🤖 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 `@apps/mission-control/src/features/agentMail/MailPage.tsx` around lines 112 - 128, Extract the duplicated room-change tracking and landing-section selection from MailPage and TeamPage into a shared useRoomLandingSection hook accepting activeRoomId, the room-to-section map, and the fallback section. Replace both pages’ local lastRoomId/subTab initialization and update logic with the hook while preserving their existing landing behavior and user-selected section state.
🤖 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 `@apps/mission-control/e2e/mockGateway.mjs`:
- Around line 5502-5536: Update the POST `/api/v1/agent-mail/leases` handler to
coerce `payload.ttl_ms` to a number before validation, accepting positive
numeric strings and numbers while falling back to 900000 for non-finite or
non-positive values. Use the resulting `ttlMs` consistently for the lease’s
`ttl_ms` and `expires_at` fields.
In `@apps/mission-control/src/App.tsx`:
- Around line 614-619: Update the usePeopleRoutingController invocation in App
so it receives tokenConfigured, then gate its runtime-config loading effect on
that value and skip getRuntimeConfig until authentication is configured and
settled. Preserve the existing loading behavior once tokenConfigured is true,
preventing the startup 401 and false failure banner.
In `@apps/mission-control/src/features/peopleRouting/PeopleRoutingSection.tsx`:
- Around line 106-113: Increase the page-size argument in the usePagination call
for humanRoutingCards so each routing page displays multiple people instead of
one. Keep the existing clamped-page synchronization and visible-card retrieval
using routingPagination unchanged.
In
`@apps/mission-control/src/features/peopleRouting/usePeopleRoutingController.ts`:
- Around line 103-136: Update loadRoutingConfig to guard concurrent requests
with a monotonically increasing request-generation ref, incrementing it for each
load and checking that the response or error still belongs to the latest
generation before updating routingConfig, routingDraft, routingError, or related
state. Keep loading cleanup from an older request from overriding the newest
request’s state.
In `@apps/mission-control/src/features/team/TeamPage.tsx`:
- Around line 578-598: Change the agent-removal flow around detachAgentRouting
and removeAgent so a failed removal cannot leave routing detached without
notifying the operator: preferably call removeAgent before detachAgentRouting,
while preserving the existing success refresh and notice behavior. If the
current order must remain, update the catch handling and
friendlyRemoveAgentError usage to explicitly report that routing was detached
when removal fails.
In `@docs/plans/2026-07-23-glass-office-claude-resume.md`:
- Around line 391-397: Use a single consistent Directory room mark throughout
the checklist: update the `BF · Directory / Front Desk` identity in step 4 to
use `DF`, matching the required `DF` references in the surrounding plan.
---
Nitpick comments:
In `@apps/mission-control/e2e/mockGateway.mjs`:
- Around line 849-908: The agent mail fixtures report one message and a
latest-message preview for every thread, but only the first thread has a seeded
message. Update createAgentMailThreadFixture and the thread initialization so
threads 2–9 accurately report zero messages and no latest-message preview, while
preserving the existing populated values for the first thread.
- Around line 5370-5457: Update the agent-mail thread detail and message route
handlers around threadDetailMatch and threadMessagesMatch so matched unsupported
methods return HTTP 405 instead of falling through to the generic 404 response.
Preserve the existing GET and POST behavior, and apply the same method handling
consistently to both route blocks.
- Around line 910-925: Replace the module-load construction of agentMailLeases
with a lease factory that computes created_at and expires_at from the current
time. Invoke the factory from resetMockState so each reset restores seven fresh,
unexpired leases while preserving the existing lease fields and pagination data.
- Around line 5538-5556: Update the lease-release handler around the
agentMailLeases lookup to detect an already released lease before modifying it.
When lease.released_at is non-null, return HTTP 409 and leave the existing
timestamp unchanged; retain the current 404 response for missing leases and 200
response for the first release.
In `@apps/mission-control/e2e/p5-directory-slice.spec.ts`:
- Around line 47-51: Scope the response listener in the P5 directory test to
collect only failing responses from the `/api/v1/` endpoint path, while
retaining the existing status and URL details. Do not add unrelated browser
responses such as favicon, source-map, or optional probe failures to
`browserErrors`.
- Around line 116-119: Update the screenshot calls in the p5-directory-slice
tests to use Playwright’s test.info().outputPath(...) or another
repository-root-independent path derived from import.meta.url/config outputDir.
Apply the same change to all occurrences, including the screenshot calls around
the referenced desktop captures, while preserving their existing filenames and
options.
- Around line 292-383: Stabilize the mutation baseline in the pin-flow test
before assigning mutationsBeforePinFlow. After the preceding navigation and
interactions, wait for pending request recording to settle using the existing
page synchronization approach (such as a short wait or network idle), then
snapshot the counter; keep the final assertion verifying that the pin flow adds
no sensitive mutations.
- Around line 31-34: Split the monolithic test into focused tests or, at
minimum, clearly bounded test.step blocks covering room identity/routing, mail
and lease lifecycle, pinning/office-door behavior, and 390px layout versus
desktop parity. Preserve the existing assertions and ensure browserErrors
validation runs independently for each scenario so an earlier failure does not
prevent later diagnostics.
In `@apps/mission-control/src/features/agentMail/MailPage.test.tsx`:
- Around line 146-191: Update stubPeopleRouting to accept
Partial<PeopleRoutingController> instead of Partial<Record<string, unknown>>,
and remove the as unknown as PeopleRoutingController cast. Retain only a direct
PeopleRoutingController assertion if needed so controller field changes remain
typechecked.
In `@apps/mission-control/src/features/agentMail/MailPage.tsx`:
- Around line 112-128: Extract the duplicated room-change tracking and
landing-section selection from MailPage and TeamPage into a shared
useRoomLandingSection hook accepting activeRoomId, the room-to-section map, and
the fallback section. Replace both pages’ local lastRoomId/subTab initialization
and update logic with the hook while preserving their existing landing behavior
and user-selected section state.
In `@apps/mission-control/src/features/peopleRouting/PeopleRoutingSection.tsx`:
- Around line 376-390: Update the Human ID input in the card editor to
explicitly communicate its locked state to assistive technologies and users by
adding the appropriate disabled treatment or aria-readonly attribute alongside
readOnly. Preserve the existing value and help text, and keep the field
non-editable.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 4df0e4d8-9209-4168-a015-d1ac88c121da
📒 Files selected for processing (16)
apps/mission-control/e2e/mockGateway.mjsapps/mission-control/e2e/p4-staff-slice.spec.tsapps/mission-control/e2e/p5-directory-slice.spec.tsapps/mission-control/src/App.tsxapps/mission-control/src/app/AppContent.tsxapps/mission-control/src/features/agentMail/MailPage.test.tsxapps/mission-control/src/features/agentMail/MailPage.tsxapps/mission-control/src/features/execassOffice/officeBlocks.tsapps/mission-control/src/features/execassOffice/pinToOffice.test.tsapps/mission-control/src/features/peopleRouting/PeopleRoutingSection.test.tsxapps/mission-control/src/features/peopleRouting/PeopleRoutingSection.tsxapps/mission-control/src/features/peopleRouting/usePeopleRoutingController.tsapps/mission-control/src/features/team/TeamPage.test.tsxapps/mission-control/src/features/team/TeamPage.tsxapps/mission-control/src/styles.cssdocs/plans/2026-07-23-glass-office-claude-resume.md
…d routing authority Memory reads (status/core surfaces, routing snapshot, lane statuses) now wait for configured authentication instead of a nonempty gateway URL, show honest waiting copy, and invalidate in-flight requests when the auth boundary changes. Runtime-default save, lane-policy save, and the two source syncs are protected by synchronous same-tick locks. The duplicate full-routing write path is removed: lane policy saves flow through the shared PR #113 People & Routing controller (fresh load, clone upsert, locked restore) and refuse while Front Desk holds unsaved routing edits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
The stable
directoryroom now owns the mail route end to end: entering it lands on Front Desk, the one rehomed People & Routing authority (server-backed humans, platform links, assistant assignments, lane policies, local operator, and unmapped policies), with direct Agent Mail and the temporarily retained File locks one tap away. Agent Mail remains the canonical message store; no second people, routing, thread, or message truth was created.MailPagecomponent suite (24 tests, red-first) locking direct Mail and File-lock paths: exact 8-thread/6-lease pagination boundaries through shrink/shrink/regrow, mobile list/detail, filters/clear, thread create, acknowledge, attachment download, compose options, send, summarize, lease create/release, all empty states, and honest mutation-failure retention.sending/create/release React-state guards were provably not same-tick locks (the suite was red against them). Send, thread create, lease create, and lease release now hold ref-based locks so same-tick duplicates call the controller exactly once; owner drafts clear only after the authoritative controller succeeds. Stored pagination pages are clamped at render so a shrink can never resurrect a stale later page.TeamPageintousePeopleRoutingController(mounted once inApp) plusPeopleRoutingSection. Directory / Front Desk is its authoritative home and owns the ready-surface-only pin. Team's People & Routing tab is now an exact stable-room handoff todirectory, Team's roster/role-card routed-people facts read the same shared instance, and agent-removal cleanup delegates to it — no dual reads/writes, no second refresh loop. Save validation and failure semantics are byte-for-byte preserved (ported suite:PeopleRoutingSection.test.tsx).directoryselects Front Desk; internal section clicks and unrelated rerenders never reset the choice; a real room change relands.directoryblock (roomId: "directory"). Unit proofs cover the tenth-shortcut refusal on a genuinely full canvas (all nine earlier shortcuts present, 24/24 cells, byte-identical persisted config) and the freed-canvas repeat.p5-directory-slice.spec.ts, @core, desktop + 390px) over new stateful mock-gateway agent-mail fixtures (9 direct threads / 7 leases, reset-aware): exact room identityBF · Directory / Front Desk, live routing save, both paginations including live shrink on lease release and regrowth landing on the clamped page, config-only pinning with a recorder proving zero runtime-config/agent-mail/memory mutations, office-door disable/restore/reload with the restored door actually clicked to its exact destination, readable non-overlappingDFmark beside a live nonzero badge, inner-rect containment for Front Desk cards/controls, Mail list/detail/compose, File-lock rows, and both paginations, withconsole.error,pageerror,requestfailed, and >=400 responses all captured and asserted empty. Eleven screenshot artifacts underruntime/qa/p5-directory-slice/, each visually inspected.p4-staff-slice.spec.tswas updated so its People & Routing step follows the handoff and verifies the authoritative surface at its Directory destination before resuming Staff parity checks.Validation
🤖 Generated with Claude Code
Summary by CodeRabbit