Panel P1.2: event workspace, event settings, organization screen - #64
Conversation
…organization) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pure, props-driven WorkspaceRail for the event workspace's left rail (board 1f): progress header + 5-segment bar, Overview/Settings nav links, five locked numbered step rows (icon + sr-only status text per WCAG 1.4.1), an always-locked Check-in row, and the amber unlock-hint notice. Also exports ReadinessCell's STEP_LABEL_KEYS for reuse here. Task 2 wires this into the real workspace layout route, including the /events/$eventId/settings route this component's Settings Link type-casts past for now.
Address two Important review findings on WorkspaceRail (commit 5b91fd0): - StepRow's status icon was hardcoded text-muted-foreground for every status, so a done step's checkmark rendered gray instead of the success color the board mock shows. Moved icon shape + color into one STEP_STATUS_ICON lookup so they can't drift apart again. - The unlock hint's `readiness?.ready !== true` condition was also true during loading (readiness undefined), asserting "not ready yet" before the component actually knew the answer. Changed to `readiness !== undefined && readiness.ready !== true` so the hint is absent while loading and only appears once readiness has loaded and ready === false. Added test coverage for the per-status icon color mapping and for hint absence during the undefined/loading state.
Restructures the flat /events/$eventId stub route into a layout route
(EventWorkspaceLayout) with an overview index child and a settings child
(both placeholders for Tasks 3/4), mounting the Task 1 WorkspaceRail
alongside the routed content.
- Header row: event name, UTC-pinned date-range pill, and a launch-check-in
button gated on readiness — locked (disabled, sr-only reason text per
WCAG 1.4.1) vs enabled (opens a coming-soon dialog; the real 3-step
ceremony is P4).
- Extracted LiveStrip's formatDateRange into features/events/eventDates.ts
so the header reuses the same UTC-pinned formatting instead of
duplicating it.
- Fixed a latent WorkspaceRail bug this surfaced: the Overview Link had no
activeOptions, so TanStack Router's default fuzzy active-matching treated
it as active on the sibling /settings route too (a string-prefix match),
stomping the explicit aria-current with its own. Added
activeOptions={{ exact: true }}.
- Deleted the now-superseded EventWorkspaceStub + its test.
router.tsx edit is scoped to swapping eventStubRoute for the new layout +
children in the addChildren(...) call; the /register beforeLoad and
protectedBeforeLoad guards are untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Builds the workspace index route's Overview panel (board 1f §4), replacing the Task 2 placeholder: a "What's next" card surfacing up to two not-done readiness steps in fixed pipeline order (attendees→badge→staff→equipment, zones excluded) as locked/coming-soon rows, or an all-ready message, plus a 4-tile stat grid (Attendees/Zones/Staff readiness counts, Checked-in from stats) with independent per-tile loading/error handling so no query failure ever renders a fabricated zero. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adapted from web/src/hooks/useScrollSpy.ts: panel scrolls the WINDOW
(AppShell's <main> has no scroll-container styling) rather than a
styled <main>, so this port uses root: null (IntersectionObserver's
viewport default) instead of the source's closest('main') resolution.
Test ports the source's self-contained MockIntersectionObserver
(jsdom has none) without touching panel's global test/setup.ts.
… card Board 6a's Event Settings page: a left anchor rail (General/Fonts/API keys/Danger zone, per task-brief reconciliation #5 narrowing the board's 7-item rail) with scroll-spy active-highlighting via the ported useScrollSpy hook, and stacked card sections. Danger zone's rail link is always styled text-destructive regardless of scroll position, not just when active. GeneralCard is the only real card this task builds (Fonts/API keys/Danger zone are inline placeholders for Tasks 5-7): name/starts/ ends/location fields with dirty-tracking and a scoped PATCH that sends only the fields the user actually changed. Location can be explicitly cleared to "" (PATCH's *string pointer semantics: present-empty-string sets empty), but dates cannot — the backend treats a nil/absent date as "leave unchanged" with no way to express clearing, so emptying a previously-set date input disables Save and shows a muted note instead of silently dropping the clear or sending a value that wouldn't work. Zod validation reuses CreateEventDialog's exact message keys. Success invalidates the event query (["get", "/api/events/{id}", { params: { path: { id } } }], matching $api's actual generated key shape) so the header/rail resync too. Swaps EventWorkspaceLayout's WorkspaceSettingsPlaceholder for the real EventSettingsPage at the settings route.
Guards against calling setSaved on an unmounted component if a save succeeds right before the user navigates away from the settings page.
Adds FontsCard to Event Settings: lists uploaded fonts (name, UPLOADED pill, format/size/date caption), removes via a tier-1 destructive ConfirmDialog + DELETE + list invalidation, and uploads new fonts immediately on file pick via a dashed drop-zone with an amber license disclaimer always visible. Uses a per-call openapi-fetch bodySerializer to send multipart/form-data, verified against the installed openapi-fetch source (no manual Content-Type override needed — the client already skips the JSON header when the body is FormData). Swaps EventSettingsPage's inline FontsCardPlaceholder for the real card. Adds settingsFont* i18n keys to en/ru. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Also records the still-uncommitted Task 4 review outcome and Task 5 completion note that were already present in the working tree.
…revoke error handling Critical: close-during-pending on the create dialog let a late POST response's plain_key resurface unlabeled on the next dialog open, because createKey.reset() on close only detaches the mutation observer — it does not cancel the in-flight request or stop onSuccess from firing. Guard onSuccess with a ref that tracks whether the dialog is still open when the response lands. Minor: the revoke mutation had no onError handling and the ConfirmDialog's confirm button wasn't disabled during the request, so a failed revoke failed silently and could be double-clicked into firing twice. Add an inline i18n'd error (cleared on the next attempt, matching GeneralCard/ FontsCard's established convention) and a new confirmDisabled prop on the shared ConfirmDialog primitive.
DangerZoneCard replaces the EventSettingsPage placeholder: red-tinted
Card (border-destructive/30, text-destructive title) with one row
("Delete this event") whose destructive Button opens @idento/ui's
typed-confirmation ConfirmDialog tier, keyed on the event's actual
name (not a slug or "DELETE" literal). onConfirm calls
DELETE /api/events/{id} (verified 204 No Content against schema.d.ts
and backend/internal/handler/events.go's c.NoContent call — this
endpoint's status code claim holds, unlike Tasks 5/6's font/api-key
DELETEs); on success it invalidates the ["get", "/api/events"] list
query (Task 4's confirmed key shape, so Home refreshes) and navigates
to "/". On failure the dialog auto-closes (same convention as
ApiKeysCard's revoke flow) and an inline i18n'd error renders in the
card; the mutation itself is reset on the dialog's open->closed
transition, with a separate deleteError flag holding the visible
error so that reset doesn't race away the message the user just saw.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…alog open on retry Two reviewer-confirmed Important findings on DangerZoneCard (commit 006b1c4): 1. Cancelling the confirm dialog while a DELETE was in flight didn't abort anything — a late onSuccess/onError still force-navigated to Home or surfaced an error for a delete the user believed they'd cancelled. Fixed with a deleteAbortedRef guard, mirroring ApiKeysCard's createAbortedRef fix for the same race class, flipped only on user-driven closes (Cancel/Escape/overlay), never on the success-driven close+navigate. 2. The dialog auto-closed on a failed DELETE, wiping the typed confirmation input and forcing a full retype on every transient failure. Now the dialog stays open on failure, shows the error inline via a dynamic ConfirmDialog description (reusing the existing settingsDeleteError key), and preserves the typed name for a same-dialog retry. Mutation-reset-on-close now happens only on an explicit user close, not on every close. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Single-card tenant settings form (name/website/contact email/logo URL) styled to match GeneralCard's dirty-tracked, scoped-save pattern. Role comes from the getTenant response's own `role` field (no extra query); non-admins get all-disabled inputs plus a read-only notice and no save action. Clearing an optional field deliberately PUTs an explicit "" per the endpoint's pointer-partial-update semantics. Wires organizationRoute to the new page (single surgical swap in router.tsx).
Task 9: dead i18n key sweep (none found, both already clean) and hardcoded-string sweep (none found) across all Tasks 1-8 files. Full verification suite green: backend go test, panel typecheck/test/build/lint, @idento/ui test/typecheck, openapi schema drift check (clean), docker build. Document src/shared/hooks/ as the home for cross-cutting hooks in panel/AGENTS.md (introduced this phase for useScrollSpy).
Task 9's final verification missed that settingsKeyCreatedAt/settingsKeyLastUsed (added in Task 6) were never wired into ApiKeysCard.tsx's key list, which rendered as a bare grid with no header row at all. Add a header row above the list, reusing settingsKeyName/settingsKeyCreatedAt/settingsKeyLastUsed and sharing the same grid template as the rows so headers stay aligned with their columns. The key-preview and actions columns get empty header cells (no existing label key for either, consistent with a pure icon/button actions column not needing a forced label).
- OrganizationPage: add key={tenant.id} to OrganizationForm so it remounts
(fresh useState initializers) when OrgSwitcher changes the active tenant
without navigating away — without this, a stale form could PUT the
previous org's values into the newly-selected tenant's record.
- WorkspaceRail: remove the stale `as never` casts on the Settings Link now
that /events/$eventId/settings is a real registered route, restoring
compiler route-id checking for this link. Update WorkspaceRail.test.tsx's
comment, which referenced the now-removed cast and a not-yet-registered
route.
- EventSettingsPage: fix a stale comment claiming Danger zone was still a
placeholder — all four settings cards are real, fully-implemented
components.
…ering
- FontsCard: mirror ApiKeysCard's revoke-error handling into the delete-font
flow — onError sets an inline i18n'd error (settingsFontRemoveError, added
to en.json/ru.json) and closes the confirm dialog so it's visible;
confirmDisabled={deleteFont.isPending} prevents a double-fired DELETE from
rapid re-clicks; the error resets whenever a new remove-confirmation is
opened or a subsequent delete succeeds. Adds two tests mirroring
ApiKeysCard.test.tsx's revoke-error/pending-disable cases.
- DangerZoneCard: run queryClient.invalidateQueries unconditionally in
deleteEvent's onSuccess, before the deleteAbortedRef check — the abort
guard must only suppress user-visible UI reactions (dialog close,
navigate), never cache-correctness operations, since a cancelled dialog
doesn't actually cancel the in-flight server-side delete. Updates the
existing regression test, which previously asserted the buggy
no-invalidation behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 29 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 (13)
📝 WalkthroughWalkthroughThis PR replaces the event workspace and organization placeholders with nested workspace routes, readiness-driven overview content, event settings cards, and role-gated organization editing. It also adds shared date and scroll-spy utilities, expanded translations, confirmation-button control, comprehensive tests, and implementation documentation. ChangesPanel P1.2 workspace rollout
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant EventWorkspaceLayout
participant WorkspaceOverview
participant EventSettingsPage
participant API
User->>EventWorkspaceLayout: Open event workspace route
EventWorkspaceLayout->>API: Fetch event and readiness
API-->>EventWorkspaceLayout: Event and readiness data
EventWorkspaceLayout->>WorkspaceOverview: Render matched overview outlet
WorkspaceOverview->>API: Fetch stats and zones
API-->>WorkspaceOverview: Statistics and zones data
User->>EventSettingsPage: Open settings route
EventSettingsPage->>API: Fetch event settings data
API-->>EventSettingsPage: Event settings data
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 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.
Actionable comments posted: 10
🧹 Nitpick comments (1)
panel/src/features/workspace/WorkspaceOverview.tsx (1)
153-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider extracting this chip into
@idento/ui.As per coding guidelines, you should use UI primitives only from
@idento/uiand never re-implement them inside the app. Consider extracting this locked/muted chip into aBadgeorStatusPillvariant in@idento/uirather than styling a bare<span>.🤖 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/workspace/WorkspaceOverview.tsx` around lines 153 - 157, Extract the locked “coming soon” chip from the WorkspaceOverview markup into a reusable Badge or StatusPill variant in `@idento/ui`, then replace the bare span in WorkspaceOverview with that UI primitive while preserving the lock icon, translation text, and muted styling.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 `@docs/superpowers/plans/2026-07-15-panel-p1.2-workspace-settings.md`:
- Line 139: Update the wording in the workspace settings plan’s stat-tile error
behavior description to use “error-styled caption” instead of “error styled
caption,” without changing the surrounding behavior or terminology.
- Line 26: Update the plan’s per-commit verification gate and the repeated gate
near Line 280 to retain the repository-mandated npm run lint -w panel command,
while listing cd panel && npx eslint . only as an additional fallback for the
documented output issue. Ensure the panel verification requirements also include
npm test -w panel, npm run typecheck -w panel, and npm run build -w panel from
the repository root.
In `@panel/src/features/workspace/settings/ApiKeysCard.test.tsx`:
- Around line 258-292: Make the abort guard request-scoped rather than relying
on the resettable *_AbortedRef, so responses from an older request cannot affect
a newly reopened dialog. Update ApiKeysCard to prevent stale plainKey updates
and DangerZoneCard to prevent stale navigation or error handling, while
preserving current-session behavior. Extend the reopen-before-settle coverage in
panel/src/features/workspace/settings/ApiKeysCard.test.tsx:258-292 and
panel/src/features/workspace/settings/DangerZoneCard.test.tsx:202-240; both
sites require regression tests for the stale-response scenario.
In `@panel/src/features/workspace/settings/ApiKeysCard.tsx`:
- Line 52: Replace the boolean createAbortedRef session guard with a
monotonically changing session identifier in the create-key dialog flow. Capture
the current identifier when starting the request, and have onSuccess update
setPlainKey only when its captured identifier still matches the active session;
increment or replace the identifier when closing and reopening so an in-flight
response from a prior session remains stale.
- Around line 116-122: Update handleCopy to await navigator.clipboard.writeText
and only set copied and schedule the reset timeout after the write succeeds;
catch or otherwise handle rejected clipboard writes without marking the key as
copied.
In `@panel/src/features/workspace/settings/DangerZoneCard.tsx`:
- Line 48: The delete confirmation flow must ignore responses from a previously
cancelled DELETE after the dialog is reopened. Update deleteAbortedRef and the
handlers around the “Delete event” action so each delete session/request has
distinct identity, and ensure onSuccess/onError only act for the current session
rather than resetting a shared flag that stale responses can observe. Preserve
the existing cancellation behavior for the active delete.
In `@panel/src/features/workspace/settings/FontsCard.tsx`:
- Around line 82-101: The FontsCard upload flow currently hardcodes
license_accepted as "true" without collecting consent. Add an actionable
license-acceptance checkbox state near the existing license notice, require it
before allowing file selection or upload, pass the state as licenseAccepted into
the upload body, and disable the drop-zone until checked.
In `@panel/src/features/workspace/settings/GeneralCard.tsx`:
- Around line 75-96: Update the patchEvent mutation flow and updateField so an
in-flight PATCH response cannot overwrite newer edits. Track the edit/request
version or otherwise validate that the onSuccess result belongs to the latest
form state before applying toFormState, setBaseline, setForm, and setSaved;
preserve reset() for clearing stale mutation state.
In `@panel/src/features/workspace/WorkspaceOverview.test.tsx`:
- Around line 192-198: Update the “/ 200” assertion in the affected
WorkspaceOverview test to await within(tiles).findByText("/ 200") and assert the
resolved element with toBeInTheDocument(), rather than asserting the Promise
with toBeTruthy().
In `@panel/src/shared/hooks/useScrollSpy.ts`:
- Around line 17-54: Update the useScrollSpy effect’s trySetup flow to avoid
indefinitely scheduling requestAnimationFrame retries when sectionIds are absent
from the DOM. Use a MutationObserver or bounded retry mechanism to detect
sections that mount later, and ensure the chosen observer or retry resources are
cleaned up in the effect teardown alongside the existing IntersectionObserver.
---
Nitpick comments:
In `@panel/src/features/workspace/WorkspaceOverview.tsx`:
- Around line 153-157: Extract the locked “coming soon” chip from the
WorkspaceOverview markup into a reusable Badge or StatusPill variant in
`@idento/ui`, then replace the bare span in WorkspaceOverview with that UI
primitive while preserving the lock icon, translation text, and muted styling.
🪄 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: 0dfef7ed-49ac-41d6-91db-1956d6232fb4
📒 Files selected for processing (32)
.superpowers/sdd/progress.mddocs/superpowers/plans/2026-07-15-panel-p1.2-workspace-settings.mdpackages/ui/src/components/confirm-dialog.tsxpanel/AGENTS.mdpanel/src/app/router.tsxpanel/src/features/events/EventWorkspaceStub.test.tsxpanel/src/features/events/EventWorkspaceStub.tsxpanel/src/features/events/eventDates.tspanel/src/features/home/LiveStrip.tsxpanel/src/features/home/ReadinessCell.tsxpanel/src/features/organization/OrganizationPage.test.tsxpanel/src/features/organization/OrganizationPage.tsxpanel/src/features/workspace/EventWorkspaceLayout.test.tsxpanel/src/features/workspace/EventWorkspaceLayout.tsxpanel/src/features/workspace/WorkspaceOverview.test.tsxpanel/src/features/workspace/WorkspaceOverview.tsxpanel/src/features/workspace/WorkspaceRail.test.tsxpanel/src/features/workspace/WorkspaceRail.tsxpanel/src/features/workspace/settings/ApiKeysCard.test.tsxpanel/src/features/workspace/settings/ApiKeysCard.tsxpanel/src/features/workspace/settings/DangerZoneCard.test.tsxpanel/src/features/workspace/settings/DangerZoneCard.tsxpanel/src/features/workspace/settings/EventSettingsPage.test.tsxpanel/src/features/workspace/settings/EventSettingsPage.tsxpanel/src/features/workspace/settings/FontsCard.test.tsxpanel/src/features/workspace/settings/FontsCard.tsxpanel/src/features/workspace/settings/GeneralCard.test.tsxpanel/src/features/workspace/settings/GeneralCard.tsxpanel/src/shared/hooks/useScrollSpy.test.tspanel/src/shared/hooks/useScrollSpy.tspanel/src/shared/i18n/en.jsonpanel/src/shared/i18n/ru.json
💤 Files with no reviewable changes (2)
- panel/src/features/events/EventWorkspaceStub.test.tsx
- panel/src/features/events/EventWorkspaceStub.tsx
…write A PATCH response landing after a newer, still-unsaved edit could silently clobber the user's latest typing — reset() on keystroke only detaches the mutation observer, it doesn't cancel the in-flight request. Gate setBaseline/setForm on an edit-version captured at submit time via onMutate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
createAbortedRef/deleteAbortedRef were plain booleans re-armed on every reopen, so a second cancel-then-reopen cycle let a stale response from the FIRST (already-abandoned) request slip past the guard after the reopen — in ApiKeysCard this could leak an old, abandoned key's plaintext secret; in DangerZoneCard it could force-navigate or surface a stale error. Replace both with monotonically-incrementing session refs, captured at mutate-time via onMutate and compared exactly in onSuccess/onError, so a reopen can never "un-stale" a response tied to a previously-closed session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
handleCopy fired navigator.clipboard.writeText() without awaiting or catching it, so a rejected write (e.g. permission blocked) still flipped the button to "Copied" — misleading the user into thinking the one-time secret was actually saved to their clipboard. Only set copied/schedule the reset on a resolved write. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
license_accepted: "true" was sent unconditionally on every upload, and the license notice was inert text — "accepted" never reflected real consent. Turn the notice into a real checkbox; disable the upload input until it's checked; wire the upload body's license_accepted from the actual state. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r test
expect(within(tiles).findByText("/ 200")).toBeTruthy() asserted on an
un-awaited Promise, which is always truthy — the check passed regardless
of whether the element ever rendered. Await and assert on the resolved
element instead, and strengthen the adjacent unasserted await the same way.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mount If none of sectionIds ever appear in the DOM (e.g. the caller stuck in a persistently-failing loading state), trySetup rescheduled itself via requestAnimationFrame forever — an unbounded ~60fps busy-loop for as long as the component stayed mounted. Cap retries at 150 (~2.5s), then stop rescheduling and leave activeId at its last value. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compound-adjective grammar fix, no code impact. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Summary
/events/$eventId): readiness-pipeline rail, header with launch-check-in gate (locked untilready, opens a coming-soon dialog once unlocked), Overview panel ("what's next" + 4 stat tiles) — replaces the P1.1 stub./events/$eventId/settings): anchor-rail with scroll-spy (porteduseScrollSpyfrom the console app) across four scoped-save cards — General, Fonts (multipart upload), API keys (show-once secret reveal), Danger zone (typed-confirmation delete by event name)./organization): tenant settings form with role-based read-only gating — replaces the placeholder.schema.d.tsdrift check confirms zero diff.Process
Built via subagent-driven-development: 9 tasks, each with a fresh implementer + independent task reviewer (5 tasks needed a fix round for Critical/Important findings — two credential/race-condition bugs caught and fixed: a stray API-key secret that could resurface after a cancel-during-create race, and an equivalent race in event deletion), followed by a final whole-branch review (on the most capable available model) that caught and fixed 3 additional cross-task issues: a stale-form-state bug on tenant switch, dead type-safety casts, and a destructive-flow error-handling inconsistency. Full task-by-task trail in
.superpowers/sdd/progress.md(search "PANEL P1.2").Test plan
npm test -w panel— 181/181 passing (41 files)npm test -w @idento/ui— 131/131 passingcd backend && OPENAPI_COVERAGE=1 go test ./... -count=1— 288 tests, unaffectednpm run typecheck -w panel && npm run build -w panelcd panel && npx eslint .— cleannpm run generate:api -w panel && git diff --exit-code -- panel/src/shared/api/schema.d.ts— zero drift confirmeddocker build --load -f panel/Dockerfile -t idento-panel:p1.2-verify .— succeeds🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes