Panel P4.1: check-in loop — idempotent check-in, station UI, launch ceremony - #77
Conversation
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
P4.1 Task 1: adds events.checkin_settings JSONB plus the checkin_stations
and checkin_actions tables Tasks 2-3 will use, and the GET/PUT
/api/events/{id}/checkin-settings endpoints (openapi-first, contract- and
pgxmock-tested) for the per-event check-in station configuration.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
P4.1 Task 2: builds POST/GET /api/events/{event_id}/checkin-stations and
POST .../checkin-stations/{id}/heartbeat on Task 1's checkin_stations
table. Registration is an upsert keyed on (event_id, name) — re-registering
the same name updates its zone binding and refreshes last_seen_at instead
of erroring or duplicating; heartbeat 0-row (unknown/foreign station id)
maps to a store.ErrCheckinStationNotFound sentinel -> 404; zone_id, when
present, must belong to the same event (400 otherwise). openapi-first with
kin-openapi contract tests (incl. the upsert same-id/zone-updated proof)
and pgxmock tests asserting the exact ON CONFLICT/guarded-UPDATE SQL text.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e_id GetEventZoneByID surfaces raw pgx.ErrNoRows for a missing zone rather than (nil, nil), so RegisterCheckinStation's err != nil check was catching a genuinely non-existent zone_id before it reached the zone==nil 400 branch, returning 500 instead. Fold pgx.ErrNoRows into the not-found path (matching the checkins_override.go / checkins_batch.go precedent) while still surfacing real DB errors as 500. Also fixes the contract test's fakeStore stub, which returned (nil, nil) for an unknown zone and therefore never exercised the real no-rows code path.
P4.1 Task 3: the phase's core zero-double-checkin endpoint. store.CheckInAttendee performs a guarded UPDATE (WHERE checkin_status = false) in one transaction, falling back to a joined SELECT to distinguish "already checked in" (original metadata preserved) from a genuinely missing attendee; store.UndoCheckin mirrors this for clearing a check-in (idempotent, no-op on an already-clear attendee); store.GetCheckinActions backs the recent-scans feed. The handler short-circuits blocked attendees to a distinct "blocked" outcome before ever attempting a write, and validates a caller-supplied station_id (400 if foreign) via a new GetCheckinStationByID lookup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Extract a shared InsertCheckinAction store method (interface.go/pg_store.go)
consumed by CheckInAttendee/UndoCheckin (tx-scoped, unchanged behavior) and
by the printed-counter endpoint (pool-scoped, new): POST
/api/attendees/{attendee_id}/printed now accepts an optional
{event_id?, station_id?} body and, when event_id is present, logs a
'reprint' checkin_actions row after the counter increment commits. Absent
body stays counter-only (back-compat for the badge-editor's bulk print).
Regenerate panel/src/shared/api/schema.d.ts (also picks up Tasks 1-3's
deferred schema additions).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ndee's event MarkAttendeePrinted discarded requireAttendeeOwnership's *models.Attendee return value and trusted the reprint body's bare event_id/station_id as-is, letting an authenticated caller who owns the attendee log a reprint row into an arbitrary other event's/tenant's checkin_actions feed. Now the attendee's own EventID is the source of truth: a body event_id is validated against it (400 "Attendee does not belong to this event" on mismatch, same wording as StationCheckin/UndoCheckin/BadgeZPL), and a present station_id is checked via the existing resolveCheckinStation helper before any insert is attempted. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Panel data layer for P4.1's check-in loop (Task 5): CheckinSettings type + DEFAULT_CHECKIN_SETTINGS + parseCheckinSettings (defensive null/partial/ out-of-range narrowing, mirrors badge/templateTypes.ts's parser shape) in settingsTypes.ts, and the nine $api hooks + three query-key helpers (useCheckinSettings/useSaveCheckinSettings, useCheckinStations/ useRegisterStation/useStationHeartbeat, useCheckinActions, useStationCheckin/ useUndoCheckin, CHECKIN_SETTINGS_KEY/CHECKIN_STATIONS_KEY/CHECKIN_ACTIONS_KEY) in hooks.ts, consuming Tasks 1-4's already-regenerated schema.d.ts. Mutation hygiene: useStationCheckin/useUndoCheckin unconditionally invalidate both CHECKIN_ACTIONS_KEY and attendees/hooks.ts' ATTENDEES_LIST_KEY. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…elected value
useSaveCheckinSettings' onSuccess was writing the already-select-ed
CheckinSettings object into the CHECKIN_SETTINGS_KEY cache slot instead
of the raw {settings} envelope useCheckinSettings' own select expects
there. Any mounted useCheckinSettings observer had its select re-run
against the wrong shape the instant a save succeeded, flashing
DEFAULT_CHECKIN_SETTINGS until the follow-up invalidateQueries refetch
resolved. Pass the mutation's raw data through unmodified, matching the
useSaveTemplate.ts precedent.
Adds a regression test that renders both hooks together and asserts the
read hook's data immediately after the save resolves, using a delayed
server.use() override plus explicit destructuring in the render
callback so the assertion isn't masked by TanStack Query's
tracked-properties re-render optimization or by the refetch completing
before the check runs.
Maps the check-in station's four outcomes onto @idento/ui's existing verdict vocabulary (checked_in->allowed, already_checked_in-> already_checked_in, blocked->no_access, not_found->not_registered) and adds useCheckinFlow, the state machine that resolves a scanned code or manually-picked attendee, fires the idempotent check-in mutation, and prints ONLY on a checked_in outcome when print_on_checkin is enabled -- a print failure surfaces without undoing the check-in. Also extends P3.2's PrintAttendeeOptions with an optional printContext forwarded to the /printed body, preserving back-compat (no body key when absent). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds useScanInput (wedge hidden-input + Enter-to-emit, scanner 200ms
agent-poll with {code,time} dedup + degraded flag, manual no-op) and
ScanInput (mode-appropriate affordance plus the always-present debounced
manual search box backed by useAttendeesPage). Extends agentClient with
getLastScan/clearLastScan against the agent's existing /scan/last and
/scan/clear contract (agent/openapi.yaml — no gap found), and adds an
optional `enabled` escape hatch to useAttendeesPage so the manual search
doesn't fetch the roster before the operator types anything.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds eventCheckinRoute as a top-level protected route (sibling of eventWorkspaceRoute, not one of its children) so /events/$eventId/checkin renders StationPage rail-less, escaping the workspace shell. StationPage wires Task 5's settings, Task 6's verdict flow, and Task 7's scan input into a near-fullscreen split layout (top bar, VerdictCard, a placeholder rail region for Task 9); missing/invalid ?station= redirects to the launch ceremony. VerdictCard renders the four outcomes through @idento/ui's verdictClasses, with first-scan metadata on already_checked_in. The routing proof (StationPage.test.tsx) mounts a router shaped like the real registration and confirms none of the workspace rail's nav markers leak through, plus a deliberately-misregistered counter-example proving the assertion actually discriminates.
Fills StationPage's Task 8 placeholder aside with the last-50 checkin-actions feed (name/code/action/time, newest-first, server order trusted verbatim). Per-row Reprint fetches the full Attendee (the feed's own slim projection can't feed attendeeToPreviewData) and sends through usePrintBadge with a printContext so the backend logs the reprint, then refetches the feed itself (P4.2 will make this live via SSE). Undo and Reprint both use hand-built confirm dialogs that block every dismissal path while their mutation is in flight, per the P3.2 PR-#74 convention. Details is a compact DropdownMenu popover (name/code/scan time) rather than the full attendee drawer.
…ns rail anyMutationPending stayed false while a dialog was open but unconfirmed, so a row's still-enabled trigger could open the OTHER dialog type concurrently and both could be confirmed independently against the same attendee. Gate every list-level trigger on reprintTarget !== null || undoTarget !== null too, so once either dialog is open no other row's Reprint/Undo trigger can open a second, competing one.
Adds useConnectionState(eventId), folding navigator.onLine/the browser's online/offline events and the check-in actions feed's own isError into one debounced online signal. StationPage shows the amber "Connection is unstable" banner while offline, blocks check-in submits before any network call is attempted (an explicit offline verdict instead of a silently dropped scan), and keeps manual search read-only against the cached roster (no check-in CTA). RecentScansRail's Undo/Reprint are disabled the same way. Display/UX degradation only -- no offline write queue, per this phase's spec. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the P4.1 launch ceremony (event/station confirm, editable check-in settings, agent printer check reusing TestPrintDialog, Start check-in CTA that upserts a station then navigates to the station route) as a new top-level rail-less route, mirroring Task 8's sibling-registration pattern. Unlocks the workspace rail's pinned Check-in row into a real link once readiness is green, and wires the pre-existing header CTA (previously a "coming soon" placeholder dialog) to the same destination. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds useHeartbeat(eventId, stationId): fires an immediate heartbeat POST on mount then every 20s via Task 5's useStationHeartbeat mutation, clearing the interval on unmount. Uses fire-and-forget .mutate() so a failed heartbeat is non-fatal (never throws, retried next tick). Mounted unconditionally in StationPage alongside its other hooks.
Task 13 — verification-only, no new features. i18n sweep (63 checkin*/ launch* keys, exact en/ru parity, real Russian translations, no hardcoded strings); full gates green (panel typecheck/test/lint/build, packages/ui tests, zero API-client drift, backend go test + golangci-lint); router.tsx/ backend/web cross-checks all clean; spec walk against docs/superpowers/specs/2026-07-17-panel-p4.1-checkin-loop-design.md §3-§7 confirms every section satisfied with no reconciliations needed. Marks all plan checkboxes done. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…logging as reprints Final whole-branch review of the P4.1 check-in loop found two Important integration gaps visible only once every task was assembled: - settings.manual_search_enabled (Task 5/11) had no consumer at the station: ScanInput always rendered the manual search box regardless of the toggle. StationPage now threads the setting into ScanInput as manualSearchEnabled, which gates the entire search box/results/hints block (wedge/scanner scan-input mechanism is unaffected). - useCheckinFlow's implicit auto-print (the automatic badge print on a fresh checked_in outcome) passed printContext to printAttendee, causing the backend to log a mislabeled 'reprint' feed row for what was actually the first print of a check-in already logged by CheckInAttendee itself. The implicit print now omits printContext, falling back to the pre-existing counter-only behavior; RecentScansRail's own Reprint button (a genuine operator-initiated reprint) is untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@codex review |
|
Warning Review limit reached
Next review available in: 6 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (12)
📝 WalkthroughWalkthroughAdds a complete event check-in loop: backend settings, stations, idempotent check-in/undo actions, reprint logging, OpenAPI contracts, panel scanner flows, station UI, launch ceremony, offline handling, heartbeat, printing context, and localized translations. ChangesCheck-in loop
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7681e290b7
ℹ️ 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".
There was a problem hiding this comment.
Actionable comments posted: 18
🧹 Nitpick comments (5)
backend/internal/handler/openapi_contract_attendee_printed_p4_test.go (2)
87-139: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRun the independent contract tests in parallel.
backend/internal/handler/openapi_contract_attendee_printed_p4_test.go#L87-L139: addt.Parallel()to each independent top-level test in this file.backend/internal/handler/openapi_contract_checkin_p4_test.go#L45-L75: addt.Parallel()to each independent top-level test and eligible subtests in this file.As per coding guidelines, “Write unit tests using table-driven patterns and parallel execution.”
🤖 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 `@backend/internal/handler/openapi_contract_attendee_printed_p4_test.go` around lines 87 - 139, Add t.Parallel() at the start of each independent top-level test in backend/internal/handler/openapi_contract_attendee_printed_p4_test.go lines 87-139 and backend/internal/handler/openapi_contract_checkin_p4_test.go lines 45-75; also parallelize eligible subtests in the check-in contract test file. Ensure tests do not rely on shared mutable state before enabling parallel execution.Source: Coding guidelines
55-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winMake the action fake enforce event scoping.
The insert and read fakes ignore
eventID, so the core test would still pass if a reprint were written to the wrong event—the exact regression this suite is intended to prevent.Proposed assertions
insertCheckinAction: func(eventID, attendeeID uuid.UUID, action string, stationID *uuid.UUID, staffUserID uuid.UUID) error { + if eventID != event.ID { + t.Fatalf("InsertCheckinAction eventID = %s, want %s", eventID, event.ID) + } + if staffUserID == uuid.Nil { + t.Fatal("InsertCheckinAction received a nil staffUserID") + } actions = append(actions, store.CheckinActionRow{ ... }) return nil }, -getCheckinActions: func(uuid.UUID, int) ([]store.CheckinActionRow, error) { +getCheckinActions: func(eventID uuid.UUID, _ int) ([]store.CheckinActionRow, error) { + if eventID != event.ID { + t.Fatalf("GetCheckinActions eventID = %s, want %s", eventID, event.ID) + } return actions, nil },🤖 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 `@backend/internal/handler/openapi_contract_attendee_printed_p4_test.go` around lines 55 - 66, Update the insertCheckinAction and getCheckinActions fakes to enforce event scoping by storing the supplied eventID with each action and filtering reads to the requested eventID. Ensure actions written under a different event are not returned, so the test fails when reprint data is associated with the wrong event.backend/internal/handler/checkin.go (1)
147-161: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftPreserve wrapped, correlated diagnostics for internal failures.
These branches return sanitized responses but either discard the underlying error or emit plain-text logs. Route them through a shared helper that wraps with
fmt.Errorf, emits structured JSON with request/trace IDs, and keeps client responses sanitized.
backend/internal/handler/checkin.go#L147-L161: record wrapped user-resolution and check-in failures.backend/internal/handler/attendee_printed.go#L128-L152: record wrapped counter and reprint-action failures using structured logging.backend/internal/handler/checkin.go#L80-L87: preserve the station lookup error.backend/internal/handler/checkin.go#L217-L223: preserve the undo failure.backend/internal/handler/checkin.go#L252-L255: preserve the feed-query failure.backend/internal/handler/checkin_settings.go#L104-L107: preserve the settings-read failure.backend/internal/handler/checkin_settings.go#L140-L142: preserve the settings-write failure.backend/internal/handler/checkin_stations.go#L78-L90: preserve zone verification and station-upsert failures.backend/internal/handler/checkin_stations.go#L112-L117: preserve heartbeat failures.backend/internal/handler/checkin_stations.go#L133-L136: preserve station-list failures.As per coding guidelines, “Always check and handle errors explicitly, using wrapped errors for traceability” and “Include unique request IDs and trace context in all logs for correlation.”
🤖 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 `@backend/internal/handler/checkin.go` around lines 147 - 161, Introduce or reuse a shared handler error-reporting helper that wraps failures with fmt.Errorf, emits structured JSON logs containing request and trace IDs, and preserves sanitized client responses. Apply it to backend/internal/handler/checkin.go:147-161, 80-87, 217-223, and 252-255; backend/internal/handler/attendee_printed.go:128-152; backend/internal/handler/checkin_settings.go:104-107 and 140-142; and backend/internal/handler/checkin_stations.go:78-90, 112-117, and 133-136, covering each user-resolution, check-in, counter, reprint, lookup, undo, feed, settings, zone, station, heartbeat, and station-list failure without changing expected status responses.Source: Coding guidelines
backend/internal/store/pg_store_checkin_test.go (1)
24-161: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftConvert the new scenario families to table-driven parallel tests.
Group settings, station, check-in, undo, and action-feed cases into tables with subtests, calling
t.Parallel()where isolated. This removes repeated mock setup and follows the backend test policy.As per coding guidelines, “Write unit tests using table-driven patterns and parallel execution.”
Also applies to: 177-435, 473-936
🤖 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 `@backend/internal/store/pg_store_checkin_test.go` around lines 24 - 161, Convert the new check-in test cases around TestGetCheckinSettingsReturnsStoredJSON and TestUpdateCheckinSettingsIssuesGuardedUpdate into table-driven subtests, consolidating repeated mock setup and assertions for each scenario. Call t.Parallel() for isolated subtests while preserving the stored, NULL, no-row, successful update, and soft-delete no-op behaviors; apply the same table-driven parallel structure to the related settings, station, undo, and action-feed test families.Source: Coding guidelines
panel/src/features/checkin/StationPage.test.tsx (1)
59-80: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftTest the actual route registrations.
Both tests construct the desired route relationship themselves, so production routing can regress while they remain green.
panel/src/features/checkin/StationPage.test.tsx#L59-L80: exercise or inspect the production station route and its parent.panel/src/features/checkin/LaunchCeremony.test.tsx#L52-L72: exercise or inspect the production launch route and its parent.🤖 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 `@panel/src/features/checkin/StationPage.test.tsx` around lines 59 - 80, Update buildCorrectRouter in panel/src/features/checkin/StationPage.test.tsx (lines 59-80) to exercise or inspect the production station route and verify its parent rather than recreating the route registration. Apply the same change in panel/src/features/checkin/LaunchCeremony.test.tsx (lines 52-72) for the production launch route and parent; both sites require direct updates.
🤖 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 `@backend/internal/handler/attendee_printed.go`:
- Around line 109-126: Update the stationID validation in the attendee printed
handler to return a bad-request error whenever station_id is supplied without
event_id. Keep the existing UUID parsing and resolveCheckinStation validation
for requests containing both values, and ensure invalid combinations exit before
the counter increment or feed-row processing.
In `@backend/internal/store/interface.go`:
- Around line 109-118: Update UpdateCheckinSettings to detect zero affected rows
from the deleted_at-guarded update and return the store’s sentinel error instead
of succeeding. In the handler that invokes UpdateCheckinSettings, map that
sentinel to the appropriate HTTP error so a concurrent soft-delete does not
return 200.
In `@backend/internal/store/pg_store.go`:
- Around line 738-751: Wrap every database, JSON decoding, action-insert, and
transaction-commit error in the affected store methods, including
GetCheckinSettings, with operation-specific fmt.Errorf messages using %w so
errors.Is behavior is preserved. Keep existing special handling such as
pgx.ErrNoRows and nil-result behavior unchanged.
- Around line 1080-1086: Update the recent-actions query in the method
containing the s.db.Query call to order by ca.created_at DESC, then ca.id DESC
for deterministic ordering of tied timestamps. Extend the corresponding
migration index to include ca.id in the same ordering.
In `@backend/migrations/000019_checkin_loop.up.sql`:
- Around line 10-28: Update the checkin_stations and checkin_actions table
definitions to enforce same-event relationships at the database level: add
composite uniqueness keys and composite foreign keys tying
checkin_stations.zone_id to event_zones and checkin_actions.attendee_id to
attendees, plus station_id to checkin_stations, alongside each row’s event_id.
Preserve nullable behavior and existing delete actions while ensuring
cross-event references are rejected.
In `@panel/src/app/router.tsx`:
- Around line 153-154: Update validateCheckinStationSearch and
checkinStationBeforeLoad to validate the station ID as a UUID, verify that the
station belongs to the current event, and redirect invalid, nonexistent, or
foreign stations before mounting the station workflow. Preserve the existing
valid-station navigation behavior.
In `@panel/src/features/checkin/LaunchCeremony.tsx`:
- Around line 111-130: The check-in surfaces currently operate on default
settings before the remote configuration loads. In
panel/src/features/checkin/LaunchCeremony.tsx lines 111-130, gate setting edits
and whole-object saves on the settings query having successfully seeded the form
via settingsSeededRef; in panel/src/features/checkin/StationPage.tsx lines
74-87, block scan/search submissions until settings load and display the loading
or failure state instead of using defaults.
- Around line 66-70: Replace the hand-built controls with shared primitives from
`@idento/ui`: add the missing Select primitive under packages/ui and use it for
zone and scan-mode selection in LaunchCeremony.tsx and printer selection in
RecentScansRail.tsx; use the shared Button primitive for the attendee action in
ScanInput.tsx. Remove the local SELECT_CLASSNAME and preserve the existing
control behavior and values.
In `@panel/src/features/checkin/RecentScansRail.test.tsx`:
- Around line 397-413: Update the test around AttendeesListObserver to track
requests to the attendees endpoint separately from actionsHitCount. After
completing the Undo flow, assert that the attendees request count increases,
while retaining the existing feed refetch assertion and request-body checks.
- Around line 317-340: Expand the dismissal-guard tests around the reprint
dialog to cover Escape and outside-click interactions in addition to Cancel.
While the print request is pending, trigger each dismissal path and assert the
dialog remains open, then retain the existing assertion that it closes after the
operation completes; update both affected test cases using the existing dialog
and print-flow helpers.
In `@panel/src/features/checkin/RecentScansRail.tsx`:
- Around line 151-159: Revalidate the online state at the start of both reprint
and undo confirmation handlers, including handleReprintConfirm, and return
before initiating any external call when offline. Also disable both dialog
confirm buttons while online is false, covering the relevant button render paths
near the referenced sections.
In `@panel/src/features/checkin/ScanInput.tsx`:
- Around line 86-104: Update the useAttendeesPage call in ScanInput to include
!readOnly in its enabled condition, preventing uncached searches while offline.
Also update showNoMatches so it requires a successful query before displaying
the empty-result state, while preserving the existing loading, fetching, and
results checks.
- Around line 106-110: Update the pick function to return immediately when
enabled is false, preventing onPickAttendee from running during an active
check-in resolution; also disable the attendee result action in the rendering
around the result button so it cannot be triggered while disabled, while
preserving the existing behavior when enabled is true.
In `@panel/src/features/checkin/searchParams.ts`:
- Around line 20-22: Update validateCheckinStationSearch to accept station only
when it is a non-empty string and a valid UUID; otherwise return station as
undefined so invalid station values redirect the operator to launch.
In `@panel/src/features/checkin/settingsTypes.ts`:
- Around line 85-90: Update the verdict_auto_dismiss_sec validation in the
settings parser to require Number.isInteger in addition to numeric and finite
checks, falling back to DEFAULT_CHECKIN_SETTINGS.verdict_auto_dismiss_sec for
fractional values before clamping. Add coverage for a fractional raw value in
settingsTypes.test.ts.
In `@panel/src/features/checkin/useScanInput.ts`:
- Around line 119-130: Update the polling logic around lastHandledRef and
clearLastScan so a duplicate code/time pair retries the pending buffer clear
instead of returning immediately after a failed clear, while still avoiding a
second onCodeRef emission. Track whether clearing succeeded, preserve
healthy-state handling, and add an MSW test covering an initial clear failure
followed by a retry that invokes onCode only once.
In `@panel/src/shared/agent/agentClient.ts`:
- Around line 172-186: Update the scan consumption flow around clearLastScan so
clearing is atomic with reading the scan: use the agent’s atomic consume
endpoint, or pass the observed code and time to a conditional clear request.
Ensure a newer scan arriving after scan A is read is preserved rather than
erased, and update the request handling and endpoint usage consistently.
In `@panel/src/shared/i18n/en.json`:
- Line 558: Update the scanner-degraded messaging used by ScanInput to avoid
directing operators to manual search when manualSearchEnabled is false, while
preserving the existing wording when it is available. Add matching generic
fallback translations in panel/src/shared/i18n/en.json:558-558 and
panel/src/shared/i18n/ru.json:560-560, and extend
panel/src/features/checkin/ScanInput.test.tsx:236-243 to cover scanner failure
with manualSearchEnabled: false and assert that unavailable manual-search
controls are not referenced.
---
Nitpick comments:
In `@backend/internal/handler/checkin.go`:
- Around line 147-161: Introduce or reuse a shared handler error-reporting
helper that wraps failures with fmt.Errorf, emits structured JSON logs
containing request and trace IDs, and preserves sanitized client responses.
Apply it to backend/internal/handler/checkin.go:147-161, 80-87, 217-223, and
252-255; backend/internal/handler/attendee_printed.go:128-152;
backend/internal/handler/checkin_settings.go:104-107 and 140-142; and
backend/internal/handler/checkin_stations.go:78-90, 112-117, and 133-136,
covering each user-resolution, check-in, counter, reprint, lookup, undo, feed,
settings, zone, station, heartbeat, and station-list failure without changing
expected status responses.
In `@backend/internal/handler/openapi_contract_attendee_printed_p4_test.go`:
- Around line 87-139: Add t.Parallel() at the start of each independent
top-level test in
backend/internal/handler/openapi_contract_attendee_printed_p4_test.go lines
87-139 and backend/internal/handler/openapi_contract_checkin_p4_test.go lines
45-75; also parallelize eligible subtests in the check-in contract test file.
Ensure tests do not rely on shared mutable state before enabling parallel
execution.
- Around line 55-66: Update the insertCheckinAction and getCheckinActions fakes
to enforce event scoping by storing the supplied eventID with each action and
filtering reads to the requested eventID. Ensure actions written under a
different event are not returned, so the test fails when reprint data is
associated with the wrong event.
In `@backend/internal/store/pg_store_checkin_test.go`:
- Around line 24-161: Convert the new check-in test cases around
TestGetCheckinSettingsReturnsStoredJSON and
TestUpdateCheckinSettingsIssuesGuardedUpdate into table-driven subtests,
consolidating repeated mock setup and assertions for each scenario. Call
t.Parallel() for isolated subtests while preserving the stored, NULL, no-row,
successful update, and soft-delete no-op behaviors; apply the same table-driven
parallel structure to the related settings, station, undo, and action-feed test
families.
In `@panel/src/features/checkin/StationPage.test.tsx`:
- Around line 59-80: Update buildCorrectRouter in
panel/src/features/checkin/StationPage.test.tsx (lines 59-80) to exercise or
inspect the production station route and verify its parent rather than
recreating the route registration. Apply the same change in
panel/src/features/checkin/LaunchCeremony.test.tsx (lines 52-72) for the
production launch route and parent; both sites require direct updates.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: dcdab587-cd3b-4d8c-bb60-8c1bf55ac216
📒 Files selected for processing (55)
backend/internal/handler/attendee_printed.gobackend/internal/handler/checkin.gobackend/internal/handler/checkin_settings.gobackend/internal/handler/checkin_stations.gobackend/internal/handler/handler.gobackend/internal/handler/openapi_contract_attendee_printed_p4_test.gobackend/internal/handler/openapi_contract_checkin_p4_test.gobackend/internal/handler/testsupport_test.gobackend/internal/models/models.gobackend/internal/store/interface.gobackend/internal/store/pg_store.gobackend/internal/store/pg_store_checkin_test.gobackend/migrations/000019_checkin_loop.down.sqlbackend/migrations/000019_checkin_loop.up.sqlbackend/openapi.yamldocs/superpowers/plans/2026-07-17-panel-p4.1-checkin-loop.mddocs/superpowers/specs/2026-07-17-panel-p4.1-checkin-loop-design.mdpanel/src/app/router.tsxpanel/src/features/attendees/hooks.test.tsxpanel/src/features/attendees/hooks.tspanel/src/features/badge/zpl/usePrintBadge.test.tsxpanel/src/features/badge/zpl/usePrintBadge.tspanel/src/features/checkin/LaunchCeremony.test.tsxpanel/src/features/checkin/LaunchCeremony.tsxpanel/src/features/checkin/RecentScansRail.test.tsxpanel/src/features/checkin/RecentScansRail.tsxpanel/src/features/checkin/ScanInput.test.tsxpanel/src/features/checkin/ScanInput.tsxpanel/src/features/checkin/StationPage.test.tsxpanel/src/features/checkin/StationPage.tsxpanel/src/features/checkin/VerdictCard.tsxpanel/src/features/checkin/hooks.test.tsxpanel/src/features/checkin/hooks.tspanel/src/features/checkin/searchParams.tspanel/src/features/checkin/settingsTypes.test.tspanel/src/features/checkin/settingsTypes.tspanel/src/features/checkin/useCheckinFlow.test.tsxpanel/src/features/checkin/useCheckinFlow.tspanel/src/features/checkin/useConnectionState.test.tsxpanel/src/features/checkin/useConnectionState.tspanel/src/features/checkin/useHeartbeat.test.tsxpanel/src/features/checkin/useHeartbeat.tspanel/src/features/checkin/useScanInput.test.tsxpanel/src/features/checkin/useScanInput.tspanel/src/features/checkin/verdict.test.tspanel/src/features/checkin/verdict.tspanel/src/features/workspace/EventWorkspaceLayout.test.tsxpanel/src/features/workspace/EventWorkspaceLayout.tsxpanel/src/features/workspace/WorkspaceRail.test.tsxpanel/src/features/workspace/WorkspaceRail.tsxpanel/src/shared/agent/agentClient.test.tspanel/src/shared/agent/agentClient.tspanel/src/shared/api/schema.d.tspanel/src/shared/i18n/en.jsonpanel/src/shared/i18n/ru.json
…ce-number clear, settings soft-delete race, reprint station validation, feed tie-breaker Fixes 5 findings from Codex/CodeRabbit review on the P4.1 check-in loop backend, TDD throughout: - CheckInAttendee's guarded UPDATE now also requires blocked = false, closing a TOCTOU race where an attendee blocked between StationCheckin's pre-read and the UPDATE could still be checked in; the 0-row fallback distinguishes this newly-blocked case (outcome "blocked") from already_checked_in. - UndoCheckin now clears checked_in_device_number alongside the other check-in metadata, so a panel undo of a mobile-batch check-in no longer leaves stale device info on the row. - UpdateCheckinSettings returns the new ErrEventNotFound sentinel on a 0-row soft-delete race instead of silently succeeding; the handler maps it to 404 instead of a fabricated 200. - MarkAttendeePrinted now 400s when station_id is supplied without event_id, instead of silently discarding it. - GetCheckinActions orders by (created_at DESC, id DESC) for a deterministic "last 50" feed; migration 000019's index (still unapplied anywhere) extended in place to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…validation, block reason, mark-printed distinction, connectivity polling, offline gating, settings-load gating, misc coverage Fixes 14 bot-review findings (Codex + CodeRabbit) on the P4.1 check-in loop panel: - F: catch submitCode/submitAttendee rejections in StationPage and surface a visible, recoverable error via a new requestError state - G: UUID-format validation on ?station= (searchParams.ts) - H: render attendee.block_reason on the blocked/no_access verdict - I: distinguish MarkPrintedError from a genuine print failure - J: poll useCheckinActions every 20s so a backend outage mid-shift flips the connection signal without an unrelated trigger - K: stop the degraded manual-search box from issuing new uncached requests, and gate the no-matches empty state on query success - L: re-gate the reprint/undo confirm buttons (and their handlers) on connectivity after a dialog is already open - M: make pick() honor enabled=false and disable the result button - N: gate LaunchCeremony's settings form/Save and StationPage's scan surface on the real settings load, not DEFAULT_CHECKIN_SETTINGS - O: reject fractional verdict_auto_dismiss_sec instead of clamping it - P: retry a failed agent scan-buffer clear without re-emitting onCode - Q: conditional degraded-scanner copy when manual search is disabled - R: exercise Escape/outside-click in the dismissal-guard tests - S: assert the attendees-list hit count in the undo invalidation test Full writeup with the fix + tests for each finding, plus a cross-finding self-review, in .superpowers/sdd/pr77-panel-fix-report.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
🧹 Nitpick comments (2)
backend/internal/store/pg_store_checkin_test.go (1)
145-166: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRun these isolated pgxmock tests in parallel.
Add
t.Parallel()at the start of each changed test. Their mock pools and identifiers are independently scoped.As per coding guidelines, “Write unit tests using table-driven patterns and parallel execution.”
Proposed change
func TestUpdateCheckinSettingsSoftDeleteRaceReturnsSentinel(t *testing.T) { + t.Parallel() mock, err := pgxmock.NewPool() func TestCheckInAttendeeNewlyBlockedReturnsBlockedOutcome(t *testing.T) { + t.Parallel() mock, err := pgxmock.NewPool() func TestUndoCheckinClearsDeviceNumber(t *testing.T) { + t.Parallel() mock, err := pgxmock.NewPool()Also applies to: 624-664, 767-799
🤖 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 `@backend/internal/store/pg_store_checkin_test.go` around lines 145 - 166, Add t.Parallel() at the start of TestUpdateCheckinSettingsSoftDeleteRaceReturnsSentinel and each other changed test in the referenced ranges. Keep the existing independently scoped pgxmock pools and identifiers unchanged while enabling these tests to run concurrently.Source: Coding guidelines
panel/src/features/checkin/searchParams.test.ts (1)
61-65: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert the launch-ceremony redirect destination.
toThrow()also passes for an unrelated error or a redirect to the wrong event/path. Capture the thrown redirect and verify it targets/events/evt-1/checkin/launch.🤖 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 `@panel/src/features/checkin/searchParams.test.ts` around lines 61 - 65, Update the test for checkinStationBeforeLoad to capture the thrown redirect when station is undefined, then assert its destination is /events/evt-1/checkin/launch. Keep the existing eventId input and ensure the assertion verifies the redirect target rather than only checking that an error was thrown.
🤖 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.
Nitpick comments:
In `@backend/internal/store/pg_store_checkin_test.go`:
- Around line 145-166: Add t.Parallel() at the start of
TestUpdateCheckinSettingsSoftDeleteRaceReturnsSentinel and each other changed
test in the referenced ranges. Keep the existing independently scoped pgxmock
pools and identifiers unchanged while enabling these tests to run concurrently.
In `@panel/src/features/checkin/searchParams.test.ts`:
- Around line 61-65: Update the test for checkinStationBeforeLoad to capture the
thrown redirect when station is undefined, then assert its destination is
/events/evt-1/checkin/launch. Keep the existing eventId input and ensure the
assertion verifies the redirect target rather than only checking that an error
was thrown.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e7014bc2-27b7-4f99-a19f-afb4ef327f36
📒 Files selected for processing (30)
backend/internal/handler/attendee_printed.gobackend/internal/handler/checkin_settings.gobackend/internal/handler/openapi_contract_attendee_printed_p4_test.gobackend/internal/handler/openapi_contract_checkin_p4_test.gobackend/internal/store/interface.gobackend/internal/store/pg_store.gobackend/internal/store/pg_store_checkin_test.gobackend/migrations/000019_checkin_loop.up.sqlpanel/src/features/checkin/LaunchCeremony.test.tsxpanel/src/features/checkin/LaunchCeremony.tsxpanel/src/features/checkin/RecentScansRail.test.tsxpanel/src/features/checkin/RecentScansRail.tsxpanel/src/features/checkin/ScanInput.test.tsxpanel/src/features/checkin/ScanInput.tsxpanel/src/features/checkin/StationPage.test.tsxpanel/src/features/checkin/StationPage.tsxpanel/src/features/checkin/VerdictCard.test.tsxpanel/src/features/checkin/VerdictCard.tsxpanel/src/features/checkin/searchParams.test.tspanel/src/features/checkin/searchParams.tspanel/src/features/checkin/settingsTypes.test.tspanel/src/features/checkin/settingsTypes.tspanel/src/features/checkin/useCheckinFlow.test.tsxpanel/src/features/checkin/useCheckinFlow.tspanel/src/features/checkin/useConnectionState.test.tsxpanel/src/features/checkin/useConnectionState.tspanel/src/features/checkin/useScanInput.test.tsxpanel/src/features/checkin/useScanInput.tspanel/src/shared/i18n/en.jsonpanel/src/shared/i18n/ru.json
🚧 Files skipped from review as they are similar to previous changes (19)
- backend/migrations/000019_checkin_loop.up.sql
- panel/src/shared/i18n/ru.json
- panel/src/features/checkin/searchParams.ts
- panel/src/features/checkin/settingsTypes.ts
- panel/src/features/checkin/RecentScansRail.test.tsx
- backend/internal/handler/attendee_printed.go
- panel/src/features/checkin/ScanInput.test.tsx
- panel/src/features/checkin/StationPage.tsx
- panel/src/features/checkin/ScanInput.tsx
- backend/internal/store/interface.go
- panel/src/features/checkin/LaunchCeremony.test.tsx
- panel/src/features/checkin/useScanInput.test.tsx
- panel/src/features/checkin/useCheckinFlow.ts
- panel/src/features/checkin/LaunchCeremony.tsx
- panel/src/features/checkin/StationPage.test.tsx
- panel/src/features/checkin/RecentScansRail.tsx
- backend/internal/handler/openapi_contract_attendee_printed_p4_test.go
- panel/src/features/checkin/useScanInput.ts
- backend/internal/handler/openapi_contract_checkin_p4_test.go
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e9cca2003
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e9cca2003
ℹ️ 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".
…number clear on checkin, openapi dependency docs Fixes 3 further Codex findings on the P4.1 check-in loop backend, TDD throughout for the store-layer findings: - CheckInAttendee's 0-row fallback could misclassify an attendee that is neither checked in nor blocked (a narrow race) as already_checked_in, which is factually wrong and skips printing entirely. The guarded-UPDATE-then-fallback sequence is now retried once (bounded, 2 attempts total) inside the same transaction on that specific "conflict" state; if the retry lands there too, the store returns the new ErrCheckinConflict sentinel, mapped to 409 by StationCheckin. - CheckInAttendee's guarded UPDATE now also clears checked_in_device_number, mirroring UndoCheckin's existing clear (previous bot-review round) — a fresh panel check-in no longer inherits a stale device number left over from an earlier mobile check-in. - openapi.yaml's MarkAttendeePrinted request schema now documents the station_id/event_id dependency and the event_id/attendee mismatch rejection the handler has enforced since two prior rounds, in prose (kin-openapi's version has no clean dependentRequired support). Also documents CheckInAttendee's new 409. schema.d.ts regenerated to match (doc comments + the new 409 response type only). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, unsaved-settings launch guard, test-badge config parity, wedge refocus Fixes 5 Codex bot-review findings on the P4.1 check-in loop: - StationPage.tsx gates the scan surface (never check-in itself) behind an explicit "waiting for printer" state when print_on_checkin is on but the agent hasn't resolved a default printer yet, mirroring the existing settings-loading gate. - useCheckinFlow.ts's auto-print now checks printBadge.fontsStatus before attempting a print (matching every other print surface's own gating) and surfaces a distinct printFontsPending state instead of risking a stale font-list race. - LaunchCeremony.tsx's "Start check-in" is disabled while there's an unsaved settings edit or a save in flight, so navigating to the station can no longer silently discard visible-but-unsaved settings. - LaunchCeremony.tsx's "Test badge" now resolves label config through the same resolveBadgeConfig helper the real check-in print path uses (extracted from usePrintBadge.ts), so a configless legacy template's test print validates the actual 50x30mm@203dpi fallback, not the editor's 90x55mm@300dpi default. - useScanInput.ts's wedge capture input now re-focuses shortly after any blur (not just on a wedgeActive transition), unless the operator is actively using a text field or a dialog/menu that's now open. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
panel/src/features/badge/zpl/usePrintBadge.ts (1)
91-91: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winDerive
event_idfrom the hook’seventId.Accepting another event ID in
printContextpermits template/attendee scope and reprint-recording scope to diverge. Keep onlystationIdin the option.Proposed change
- printContext?: { eventId: string; stationId: string | null }; + printContext?: { stationId: string | null }; ... - body: { event_id: opts.printContext.eventId, station_id: opts.printContext.stationId }, + body: { event_id: eventId, station_id: opts.printContext.stationId },Also applies to: 213-217
🤖 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 `@panel/src/features/badge/zpl/usePrintBadge.ts` at line 91, Update the printContext option used by usePrintBadge to remove eventId and retain only stationId. Ensure event_id is always derived from the hook’s eventId throughout template/attendee scope and reprint-recording logic, eliminating any alternate event ID source.backend/internal/store/pg_store_checkin_test.go (1)
577-612: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winParallelize these isolated tests and table-drive the conflict variants.
Each test owns its
pgxmockpool and UUIDs, so addt.Parallel(). The success/exhausted retry cases should share a scenario table to prevent their expectations drifting.As per coding guidelines, new Go tests must use table-driven patterns and parallel execution.
Also applies to: 733-828
🤖 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 `@backend/internal/store/pg_store_checkin_test.go` around lines 577 - 612, The isolated tests in the check-in test section, including TestCheckInAttendeeClearsStaleDeviceNumber and the tests through the referenced conflict-variant range, should call t.Parallel() after setup as appropriate. Consolidate the success and exhausted retry conflict cases into a shared table-driven test, keeping each scenario’s pgxmock expectations and assertions defined by the table to prevent drift.Source: Coding guidelines
🤖 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 `@panel/src/features/checkin/useCheckinFlow.test.tsx`:
- Around line 103-117: Replace the wall-clock settleFonts delay in
panel/src/features/checkin/useCheckinFlow.test.tsx:103-117 with an observable
query/request completion gate, preserving deterministic font-status
synchronization. In panel/src/features/checkin/useCheckinFlow.test.tsx:384-389,
explicitly release the delayed font request before asserting that no print
occurred. In panel/src/features/checkin/StationPage.test.tsx:379-398, keep
printer handlers pending until the waiting state is asserted, then release them;
update the affected useCheckinFlow and StationPage test flows without relying on
scheduler timing.
---
Nitpick comments:
In `@backend/internal/store/pg_store_checkin_test.go`:
- Around line 577-612: The isolated tests in the check-in test section,
including TestCheckInAttendeeClearsStaleDeviceNumber and the tests through the
referenced conflict-variant range, should call t.Parallel() after setup as
appropriate. Consolidate the success and exhausted retry conflict cases into a
shared table-driven test, keeping each scenario’s pgxmock expectations and
assertions defined by the table to prevent drift.
In `@panel/src/features/badge/zpl/usePrintBadge.ts`:
- Line 91: Update the printContext option used by usePrintBadge to remove
eventId and retain only stationId. Ensure event_id is always derived from the
hook’s eventId throughout template/attendee scope and reprint-recording logic,
eliminating any alternate event ID source.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: b6b43f56-9766-4afb-9a7c-dea6cb85bc10
📒 Files selected for processing (20)
backend/internal/handler/checkin.gobackend/internal/store/interface.gobackend/internal/store/pg_store.gobackend/internal/store/pg_store_checkin_test.gobackend/openapi.yamlpanel/src/features/badge/templateTypes.test.tspanel/src/features/badge/templateTypes.tspanel/src/features/badge/zpl/usePrintBadge.tspanel/src/features/checkin/LaunchCeremony.test.tsxpanel/src/features/checkin/LaunchCeremony.tsxpanel/src/features/checkin/StationPage.test.tsxpanel/src/features/checkin/StationPage.tsxpanel/src/features/checkin/VerdictCard.tsxpanel/src/features/checkin/useCheckinFlow.test.tsxpanel/src/features/checkin/useCheckinFlow.tspanel/src/features/checkin/useScanInput.test.tsxpanel/src/features/checkin/useScanInput.tspanel/src/shared/api/schema.d.tspanel/src/shared/i18n/en.jsonpanel/src/shared/i18n/ru.json
🚧 Files skipped from review as they are similar to previous changes (13)
- panel/src/features/checkin/VerdictCard.tsx
- panel/src/shared/i18n/ru.json
- panel/src/features/checkin/useScanInput.test.tsx
- backend/internal/handler/checkin.go
- panel/src/features/checkin/useCheckinFlow.ts
- panel/src/features/checkin/StationPage.tsx
- panel/src/features/checkin/LaunchCeremony.tsx
- backend/internal/store/interface.go
- panel/src/shared/i18n/en.json
- panel/src/features/checkin/useScanInput.ts
- backend/openapi.yaml
- backend/internal/store/pg_store.go
- panel/src/shared/api/schema.d.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b5c8bff8fd
ℹ️ 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".
RecentScansRail.test.tsx's two three-dismissal-path tests (reprint and undo) used a 40ms real-timer mock delay while performing three sequential userEvent interactions plus a waitFor before their final assertion. That window is tight enough to flake under CI's slower/more loaded runners (observed: PR #77 CI run 29632317448, "Undo check-in" dialog not found) because the mutation can genuinely resolve mid-sequence and close the dialog via its own success path -- not because dismissal-blocking failed. Widened both to 300ms, matching this feature's own precedent for similar-shaped tests (useCheckinFlow.test.tsx uses 400ms). No production code changed; handleUndoOpenChange/preventUndoDialogDismiss's blocking logic was verified correct. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…, printer-gate polling, scanner poll overlap guard, Button primitive, test race fix Fixes 7 CodeRabbit/Codex findings from the third bot-review round on the P4.1 check-in loop: LaunchCeremony's settings-seed ref and useCheckinFlow's verdict/timer state now reset per event/station instead of leaking across a route-reused instance; StationPage's printer-readiness gate now polls useAgentPrinters (new refetch) every 10s while blocking so a late-connecting agent unblocks scanning without an unrelated trigger; useScanInput's 200ms scanner poll now guards against overlapping getLastScan()/clearLastScan() round trips; ScanInput's manual-search result row now uses @idento/ui's Button instead of a hand-rolled <button>; LaunchCeremony gates Start check-in on settings actually having loaded, not just being non-dirty; StationPage.test.tsx's flaky printer-waiting test now uses a deferred promise instead of a fixed delay racing an unbounded findByText wait. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
…00020) (#78) * docs: P4.1 check-in loop design spec Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs: P4.1 check-in loop implementation plan Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Migration 000020: composite FKs for same-event check-in relationships CodeRabbit review on PR #77 flagged that 000019's single-column FKs let checkin_stations.zone_id, checkin_actions.attendee_id, and checkin_actions.station_id point at a row from a different event than the row's own event_id. The app layer already validates this on every write path, but this closes the gap at the DB level as defense-in-depth (000019 had already merged to main, so this is a new migration rather than an in-place edit). Verified against a real Postgres 16 instance that cross-event inserts are rejected and same-event/NULL cases still succeed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add 000021: validate 000020's composite FKs outside the lock window CodeRabbit nitpick on #78: adding a FK with a full validation scan holds an ACCESS EXCLUSIVE lock on the referencing table for the scan's duration. 000020's three composite FKs now go in NOT VALID (skips the scan), and this new migration validates them separately via ALTER TABLE ... VALIDATE CONSTRAINT, which only needs a SHARE UPDATE EXCLUSIVE lock and doesn't block concurrent reads/writes. Verified against real Postgres: convalidated=false right after 000020, flips to true after 000021, and new-row rejection behavior is unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: CI Bot <ci@example.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…in-memory copy Addendum to the round-2 Finding 1 fix: the store's UPDATE now clears test_passed_at when config actually changes, but PatchEquipmentDevice's response was still built from the pre-fetched `existing` struct patched up in memory — so a config-changing PATCH could return a stale non-null test_passed_at for hardware that never passed a test. The handler now re-reads the row via GetEquipmentDeviceForTenant after the UPDATE and echoes that; a nil re-read (concurrent-delete race after a successful UPDATE) maps to the house 404 shape, same soft-delete-race precedent as PutCheckinSettings' ErrEventNotFound handling (PR #77 Finding C). Contract tests: config-changing PATCH response has test_passed_at null; rename-only PATCH response preserves it; vanished-row re-read is 404 (and the re-read provably happens — two get calls). The rename test's fake is now stateful so the re-read reflects the persisted update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…etup wizards (#83) * docs(spec): P4.3 equipment hub + device registry design Board screens 5a-5d extracted (all four fully drawn); user-approved decisions: camera deferred entirely, server-authoritative default mirrored to agent, tenant-wide readiness rule, wedge+COM scanners, normalized registry tables keyed by agent machine id (backend #5), new agent GET /info for machine identity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(plan): P4.3 equipment hub implementation plan — 11 tasks Agent /info -> migration+store -> endpoints+regen -> readiness -> panel info/cache -> registry hooks+reconcile -> hub page -> printer wizard -> scanner wizard -> default-precedence repoint -> final sweep. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent: GET /info — persisted machine id, hostname, version, uptime * backend: equipment registry storage — migration 000023, models, store methods Adds the P4.3 per-tenant, per-machine device registry storage layer: the equipment_machines/equipment_devices tables (migration 000023, with the partial-unique-index single-default-printer guarantee and the class='printer' CHECK), the EquipmentMachine/EquipmentDevice models, and the 8-method PGStore implementation (upsert-machine, get-machine+devices, get-device-for-tenant, create/update/delete-device, set-default-printer, mark-test-passed, tenant-has-tested-default-printer) behind the Store interface's new Equipment Registry block, with ErrDeviceNotFound as its not-found sentinel. Covered by pgxmock tests pinning every statement's SQL, plus a TEST_DATABASE_URL-gated real-Postgres integration test proving the schema guarantees pgxmock can't (partial unique index, CHECK constraint, cascade delete, cross-tenant disjointness) and the id = ANY($3) UUID-array bind used by the seen-devices freshness touch. * backend: equipment registry endpoints — tenant-scoped CRUD, default rule, test-passed Adds the 7 P4.3 equipment-device-registry HTTP endpoints (PUT/GET machine, POST device, PATCH/DELETE device, PUT default-printer, POST test-passed), openapi.yaml documentation, contract tests, and the regenerated panel client. ORG-level resources scoped by tenant_id alone (no requireEventOwnership); 23505 (partial-unique-index race on make_default) maps to a clean 409 instead of a raw constraint error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * backend: readiness equipment step — tenant has a tested default printer GetEventReadiness now sources the equipment step from TenantHasTestedDefaultPrinter(tenantID) instead of a hardcoded not_done. Equipment still never blocks ready (spec §4.3), same as zones. Extends the readiness contract tests with true/false/store-error cases and fixes two other fakeStore fixtures (badge-template P3 readiness tests) that now need the new store method wired to avoid a nil-func panic. openapi.yaml's readiness description drops the "P3/P4 wiring" caveat and states the real rule; panel schema.d.ts regenerated in the same commit. * panel: agent /info client, cached machine identity, useAgentInfo with 8s retry * panel: fix useAgentInfo review findings — gate info on enabled, warn-don't-fail cache write, machine_id-overwrite pin * panel: equipment registry hooks + agent reconcile logic Task 6 of P4.3: TanStack Query hooks over the generated equipment endpoints (machine GET/PUT, device POST/PATCH/DELETE, default-printer PUT, test-passed POST), each invalidating EQUIPMENT_MACHINE_KEY on success, plus pure reconcile.ts matching registry devices against the live agent by config.agent_name/port_name (never display_name) — wedge scanners always resolve to Liveness "none" per the honesty rule. Adds AgentScanner + agentClient.getScanners() (GET /scanners) as the scanner-side counterpart to getPrinters. * docs(spec): drop impossible ip:port cross-check — agent /printers carries name+type only * panel: equipment hub — agent card, registry-reconciled device columns, agent-down state Board 5a/5d: replaces the /equipment PlaceholderPage with the real hub. AgentCard is a state machine over useAgentInfo (connected/connected_legacy/ checking/disconnected), rendering board 5a's meta line (base-url · version · hostname · uptime) and board 5d's red "Start the agent" card (numbered steps, download link, Retry, auto-retry caption — the actual 8s retry timer already lives inside useAgentInfo). DeviceCard is one parameterized column component reused for Printers/Scanners, reconciling the registry (useEquipmentMachine) against the live agent (useAgentPrinters + a new local useAgentScanners) via reconcile.ts: green/amber liveness dots (never color-alone — amber pairs with visible "Saved · not seen since <date>" text), no dot at all for USB-wedge scanners (the agent has no visibility into them), unsaved live printers surfaced as a Save… affordance, and a DEFAULT chip on the registry default. The whole grid grays to opacity-.55 with actions hidden when the agent is down, but stays readable from a cached machine_id — it only fully hides for a true cold start (no cache, agent down). The reconcile-on-load PUT (seen_device_ids) fires exactly once per machine_id per visit via a ref guard, gated on info + the registry settling + both live lists settling; an empty registry (404) still upserts, since that's what registers a new machine. The overflow menu (rename/set default/delete) reuses the house ConfirmDialog pattern. Printer/scanner setup buttons and the unsaved-row Save affordance are honestly disabled (data-testid="wizard-todo") — no fake wizard ahead of Tasks 8/9. Also extends ReadinessCell's regression coverage for the equipment step's newly-reachable "done" status (no component change needed — the existing generic step renderer already handles it). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * panel: equipment hub review fixes — success-gated reconcile, distinct registry-error state, v5 pending skeleton Task 7 review round (2 Important + 3 Minor, all taken): 1. Reconcile upsert now fires only when BOTH live lists (printers/ scanners) have succeeded — an errored fetch reads as an empty list and would under-report seen_device_ids (live devices later showing a false "not seen since"). A skipped attempt does not consume the once-per-machine_id ref budget, so a later successful refetch still reconciles exactly once (test drives error -> zero PUTs -> recovery -> exactly one PUT). 2. A genuine (non-404) machine-registry failure renders a distinct error state with Retry (ZonesPage's list-error shape, new equipmentRegistryLoadError key EN+RU) instead of masquerading as "No printers/scanners saved yet" — the exact silent-empty-list the board's 5d caption calls out. True 404 stays the empty state. 3. Loading skeleton now gates on v5-correct pending semantics (isPending for the enabled query + the pre-identity checking window) instead of isLoading, which is never true for a disabled query. 4. Added the missing clear-default overflow test (PUT device_id: null). 5. Pinned the legacy-agent-with-cached-identity edge: saved devices stay visible from the cached machine_id (a reachable-but-legacy agent must never show less than a fully dead one), hint renders, no reconcile. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * panel: printer setup wizard — find/test/save with physical verification Board 5b's 3-step wizard (Find -> Test -> Save) with physical verification via a Cyrillic test label, plus a retest entry point for a saved printer row's Test print button. Wires the hub's three printer mount points (header chooser, per-column Set up printer, unsaved-row Save) from Task 7. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * panel: printer wizard review fixes — readable mirror warning, network address at Save, dismissal locks Important 1: mirror failure no longer auto-closes in the same tick (the warning never painted under React 18 batching + unanimated Radix unmount) — dialog stays open with an explicit Close, BulkBar convention. Important 2: a network printer without a known address collects ip/port at the Save step instead of dead-ending on a guaranteed-400 create. Minors: in-handler busy re-checks, complete dismissal locking (+tests), Find list excludes registered printers, retest carries the real kind, label-worded timeout copy (equipmentWizardTimeout, EN+RU). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * panel: scanner setup wizard — wedge listen-detect + COM port flow Board 5c's ScannerWizard: a segmented USB-wedge | COM toggle, each leading to its own listen step with a physical verification gate (Save/fields don't exist until a real scan lands), plus a retest entry point mirroring Task 8's controller-resolved pattern. useWedgeListen does window-scoped keydown capture with its own inter-key-gap/silence-timeout detection (a different technique from useScanInput's focused-input + Enter idiom, since this hook must first tell a scanner apart from a human typing). agentClient gains getScannerPorts/addComScanner/removeComScanner against agent/openapi.yaml's already-documented /scanners/* routes. Hub wiring: "+ Set up scanner" and the "+ Add device" chooser's Scanner option are now the real enabled affordance (the last wizard-todo placeholder is gone); scanner rows get a "Test scan" button (wedge rows always, live com rows); a saved com device's delete flow best-effort releases its agent-side port, warning (not failing) on mirror failure. Controller correction honored: retest mode calls useMarkTestPassed on any confirmed detection (wedge or com), since POST .../test-passed is device-generic and only printer tests feed the readiness gate. * panel: scanner wizard review fixes — discard-first COM listen, honest test_passed gating, abortable consume poll Task 9 review round (1 Critical + 2 Important + 1 Minor): - CRITICAL: the agent's scan buffer is process-wide (not per port/session), so the COM listen phase could pass a pre-session (or wrong-device) scan off as this session's physical verification — and retest auto-stamped test_passed_at from it with zero operator action. The poll is now an imperative effect (useScanInput's interval idiom) whose first successful consume per listen session is a pure discard; /scan/consume's atomic read-and-clear guarantees everything read after it arrived after listening began. - Important-1 (reviewer's adjudication): Save no longer requires a detection — PrinterWizard's brief-mandated test_passed:false precedent governs. Fields are editable from the start; test_passed on create is simply whether a scan landed this session. - Important-2: consumeLastScan accepts an optional AbortSignal and the poll's cleanup aborts the in-flight request on cancel/close — a post-close consume can no longer eat a scan the station needs. - Minor: COM listen tests converted to fake timers (no real 700ms intervals; equipment suite ~3.5s). * panel: wedge listen editable-target guard — typing in the wizard's fields can't fabricate a detection Task 9 review round 2 (1 new Important + 1 Minor): - Important: round 1's always-editable fields exposed the window-level wedge listener to fast human typing -- a 3+ char burst at wedge speed into the Device name input fabricated a detection (and a later test_passed:true) with zero physical scan. useWedgeListen now ignores keydowns targeted at editable elements (input/textarea/select/ contenteditable) and clears the accumulator on such an event (no mixed bursts). Accepted, documented limitation (controller decision): a physical scan while a text field is focused won't detect -- its chars land in the field natively (wedge physics any app has); "Scan again" re-arms with button focus. - Minor: the abort-on-cancel comment now states its honest scope -- the abort stops the client's wait and further polls; it cannot un-consume a request the agent already processed. * panel: move equipment machine query to shared/agent (layering) * panel: default-printer precedence — server registry wins over agent config * panel: revert printers-gated registry lookup; fix root cause with harness-level agent-info/registry MSW defaults (task 10 review) * panel: P4.3 whole-branch review polish — RU copy naturalness, no dangling meta separator RU copy: past-tense equipmentNotSeenSince (matches equipmentNotSeenYet), "штрихкод"/"сканирование" terminology consistency in the scan-confirmation strings, and a comma-splice fix in equipmentDefaultFooter. Values only, keys/EN unchanged. AgentCard: build the connected meta line by joining only non-empty segments so a blank hostname can't leave a dangling "· ·" gap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(sdd): record P4.3 execution trail — 11 tasks, review rounds, final whole-branch review Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * agent: propagate generateUUIDv4 rand failure instead of Fatal-ing CodeRabbit (PR #83): generateUUIDv4 called log.Fatalf on a crypto/rand failure, but it's reachable from loadConfig's MachineID-upgrade branch, which runs inside HTTP handlers (e.g. /printers) while holding configMu — a transient RNG failure would os.Exit the whole agent mid-event instead of failing one request. generateUUIDv4 now returns (string, error); loadConfig's upgrade branch propagates the error through its existing error return; main()'s startup path still Fatalf's on its own discretion (nothing to serve yet at that point). All loadConfig call sites already 500 or degrade gracefully on error, so no handler behavior changed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * backend: enforce default-printer device_id presence + per-kind config shapes Two Codex findings (PR #83): 1. PUT /api/equipment/machines/{machine_id}/default-printer bound the request body's device_id into a bare *uuid.UUID, which json.Unmarshal leaves nil for both an absent key and an explicit null — so an accidentally-omitted device_id silently cleared the machine's default printer, identical to the documented clear-request. Fixed by decoding into a json.RawMessage field first (DisallowUnknownFields) so presence is observable: key absent -> 400 ("device_id is required; send null to clear"), null -> clear (unchanged), a uuid string -> set (invalid uuid -> 400). 2. validateEquipmentDeviceConfig shared one config shape across both printer kinds and both scanner kinds, so DisallowUnknownFields only caught genuinely unrecognized keys — a usb_wedge scanner could carry com's port_name, a com scanner could carry usb_wedge's terminator, and a system printer could carry network's ip/port/dpi, all stored verbatim and misleading reconciliation readers. Split into four per-kind decode shapes (network printer: agent_name/ip/port/dpi; system printer: agent_name only; com: port_name only; wedge: terminator only) so a cross-kind key is now rejected as unknown. openapi.yaml updated to match (EquipmentDefaultPrinterRequest's required- device_id behavior, EquipmentDeviceCreateRequest's per-kind key prose); panel/src/shared/api/schema.d.ts regenerated via `npm run generate:api -w panel` in this same commit (doc-comment diff only, no type-shape change). Contract tests added for both: {} on default-printer -> 400, malformed uuid -> 400; wedge+port_name, com+terminator, system printer+ip -> 400. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * panel: PR #83 bot-review round 1 fixes — equipment hub (10 findings) Fixes all 10 CodeRabbit/Codex bot-review findings on the P4.3 equipment hub (2 counted once as duplicates -> 8 distinct changes), TDD red-first for each: 1/2. AgentCard's uptime meta line now routes through react-i18next (equipmentUptime, EN+RU) instead of a hardcoded English string; PrinterWizard's manual-add port guard now matches the Save-step address form's Number.isInteger 1..65535 rule instead of Number.isFinite (which let ""->0 and 70000+ through). 3. EquipmentPage/DeviceCard disable every Add/Set up/Save control while machineId is null (legacy agent, no cached identity) -- their handlers already no-op there, so the affordance is now honest about it. 4. useCreateDevice/useDeleteDevice/useSetDefaultPrinter/ useMarkTestPassed invalidate every event's readiness query (the [method, path-template] prefix idiom) since equipment is org-level but its readiness-gated content is per-event. 5. ScannerWizard's retest Close button (and the Escape/outside-click paths) now gate on markTestPassed's own pending state, so a late test-passed failure can no longer be silently dropped by closing mid-flight. 6. PrinterWizard's shared Test/Save footer now includes `printing` in Save's busy check, so a device row can no longer be created and the dialog closed while the physical test send is still in flight. 7. ScannerWizard's explicit Cancel button now gates on `comAdding` too, matching handleOpenChange/preventDialogDismiss. 8. useAgentPrinters resolves configuredDefault to null while a modern agent's registry query is still pending, instead of racing to the agent's own (possibly stale) configured default. 9. useEquipmentMachine sets retry:false -- a 404 there is a documented normal state, matching useAgentInfo/useAgentPrinters convention. 10. DeviceCard's row-menu trigger is now the @idento/ui Button (ghost/icon), replacing a hand-rolled <button>. Gates: npm run test -w panel (121 files / 1382 tests), npm run typecheck -w panel, npx eslint . -- all clean. Full report at .superpowers/sdd/pr83-round1-panel-report.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: drop force-added internal fix reports from the branch — .superpowers/sdd is scratch, gitignored by design * backend: equipment store — atomic test-pass stamp on create, unconditional clear on config PATCH (#83 round 2) Finding 1: UpdateEquipmentDevice now clears test_passed_at whenever the supplied config differs (jsonb-semantic) from the device's stored config, enforced in the UPDATE's own CASE over the OLD row — a PATCH swapping in different hardware can no longer leave a stale "tested" stamp that TenantHasTestedDefaultPrinter's readiness check keeps trusting. Finding 2: CreateEquipmentDevice takes testPassed and stamps test_passed_at inside its own INSERT (RETURNING), replacing the handler's separate post-create MarkEquipmentDeviceTestPassed call — closing the window where the create could commit and the second write could fail, leaving an already-visible, wrongly-unstamped device a retry would then 409/duplicate against. Pinned pgxmock SQL updated for both statements; added config-changed vs identical-config-rename cases and testPassed true/false cases. Added real-Postgres integration subtests proving the jsonb-semantic CASE behavior pgxmock can't evaluate. openapi.yaml PATCH device description updated; panel client regenerated (npm run generate:api -w panel). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * backend: PATCH device response echoes the re-read row, not the stale in-memory copy Addendum to the round-2 Finding 1 fix: the store's UPDATE now clears test_passed_at when config actually changes, but PatchEquipmentDevice's response was still built from the pre-fetched `existing` struct patched up in memory — so a config-changing PATCH could return a stale non-null test_passed_at for hardware that never passed a test. The handler now re-reads the row via GetEquipmentDeviceForTenant after the UPDATE and echoes that; a nil re-read (concurrent-delete race after a successful UPDATE) maps to the house 404 shape, same soft-delete-race precedent as PutCheckinSettings' ErrEventNotFound handling (PR #77 Finding C). Contract tests: config-changing PATCH response has test_passed_at null; rename-only PATCH response preserves it; vanished-row re-read is 404 (and the re-read provably happens — two get calls). The rename test's fake is now stateful so the re-read reflects the persisted update. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * panel: PR #83 bot-review round 2 fixes — equipment hub wizard races (9 findings) Closes the two wizard state-machine races (stale session state surviving close, letting a reopen auto-fire a print or auto-pass a retest off leftover data), gates retest-confirm/Back on an in-flight test print, swaps two hand-rolled list-item buttons for @idento/ui's Button, extracts one isBusy per wizard, routes the test-label preview through i18n, mirrors row-menu "Make default" onto the agent (warn-don't-fail, same idiom as the wizard's own mirror), and adds usePatchDevice to the readiness-invalidation set now that the backend clears test_passed_at on a config-changing PATCH. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: CI Bot <ci@example.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Summary
checkin_actionsfeed.usePrintBadge; verdict rendering reuses@idento/ui's existing verdict vocabulary (no new colors); physical-output dialogs follow the established block-dismissal-while-sending convention.Test plan
OPENAPI_COVERAGE=1 go test ./... -count=1(416 tests) +golangci-lint run ./internal/...— cleannpm run typecheck -w panel && npm test -w panel(1072 tests) +npx eslint .+npm run build -w panel— cleanpackages/uiuntouched-green (132 tests)npm run generate:api -w panel— zero schema driftrouter.tsxdiff is purely additive (only the two new top-level routes;/registerguards byte-for-byte unchanged)web/diff is empty (frozen for this phase)manual_search_enabledsetting wired up; implicit auto-print no longer mislabeled as a reprint in the feed)🤖 Generated with Claude Code
Summary by CodeRabbit