Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4bdebd2
docs: P1.2 implementation plan (9 tasks — workspace 1f, settings 6a, …
Jul 14, 2026
5b91fd0
feat(panel): workspace readiness rail
Jul 14, 2026
0c362b6
fix(panel): workspace rail icon color + loading-state hint gating
Jul 14, 2026
e79e23a
feat(panel): event workspace layout, rail wiring, launch gate
Jul 14, 2026
b0edd9b
feat(panel): workspace overview — what's next + stat tiles
Jul 14, 2026
7b28360
feat(panel): port useScrollSpy hook for the event settings anchor rail
Jul 14, 2026
02d0f41
feat(panel): event settings scaffold, scroll-spy anchor rail, general…
Jul 14, 2026
e9bfc94
fix(panel): cancel GeneralCard's saved-caption timeout on unmount
Jul 14, 2026
f56838e
chore: record P1.2 Task 4 progress ledger entry
Jul 14, 2026
c70b642
feat(panel): settings fonts card — list, multipart upload, remove
Jul 14, 2026
c652d36
feat(panel): settings API-keys card — list, create with show-once rev…
Jul 14, 2026
3cbceca
chore: record P1.2 Task 6 progress ledger entry
Jul 14, 2026
2290b10
fix(panel): api-keys card — prevent stray plain_key resurfacing, add …
Jul 14, 2026
006b1c4
feat(panel): settings danger zone — typed-confirm event deletion
Jul 14, 2026
ba32324
fix(panel): danger zone — prevent cancel-during-pending race, keep di…
Jul 15, 2026
adb081a
feat(panel): organization settings screen
Jul 15, 2026
483b5d6
docs(agents): P1.2 verification
Jul 15, 2026
e741546
fix(panel): add missing column headers to API keys list
Jul 15, 2026
68e4cc4
fix(panel): final review — tenant-switch remount, route-typing cleanup
Jul 15, 2026
88c1ee0
fix(panel): fonts delete error handling, danger-zone invalidation ord…
Jul 15, 2026
e8f9ca9
chore: record P1.2 final review ledger entries
Jul 15, 2026
43ab6a2
chore: record P1.2 final re-review ledger entry — ready to merge
Jul 15, 2026
8c28ccc
fix(panel): guard GeneralCard PATCH onSuccess against stale-edit over…
Jul 15, 2026
81b88e9
fix(panel): use incrementing session ids for create/delete abort guards
Jul 15, 2026
e90d6a3
fix(panel): treat rejected clipboard writes as failures in ApiKeysCard
Jul 15, 2026
2f0b3a2
fix(panel): make FontsCard's license checkbox actually gate uploads
Jul 15, 2026
d5fe39a
test(panel): fix silent assertion bug in WorkspaceOverview zones-erro…
Jul 15, 2026
c8f719c
fix(panel): bound useScrollSpy's rAF retry loop while sections never …
Jul 15, 2026
49e49fc
docs: hyphenate "error-styled" in P1.2 workspace settings plan
Jul 15, 2026
f380e94
chore: record PR #64 CodeRabbit review round in ledger
Jul 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .superpowers/sdd/progress.md

Large diffs are not rendered by default.

295 changes: 295 additions & 0 deletions docs/superpowers/plans/2026-07-15-panel-p1.2-workspace-settings.md

Large diffs are not rendered by default.

10 changes: 7 additions & 3 deletions packages/ui/src/components/confirm-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export type ConfirmDialogProps = {
closeLabel: string;
onConfirm: () => void;
destructive?: boolean;
// Caller-driven disable on top of the typed-confirmation check below —
// e.g. while the confirm action's mutation is in flight, so a slow
// network can't be double-clicked into firing the action twice.
confirmDisabled?: boolean;
} & (
// Tier 2 (typed confirm) needs a visible label for the input's accessible
// name — @idento/ui has no i18n fallback text to supply one, so the type
Expand All @@ -26,7 +30,7 @@ export type ConfirmDialogProps = {

export function ConfirmDialog({
open, onOpenChange, title, description, confirmLabel, cancelLabel, closeLabel,
onConfirm, destructive = false, typedConfirmation, typedConfirmationLabel,
onConfirm, destructive = false, typedConfirmation, typedConfirmationLabel, confirmDisabled = false,
}: ConfirmDialogProps) {
const [typed, setTyped] = React.useState("");
const inputId = React.useId();
Expand All @@ -35,7 +39,7 @@ export function ConfirmDialog({
if (!open) setTyped("");
}, [open]);

const confirmDisabled = typedConfirmation !== undefined && typed !== typedConfirmation;
const disabled = confirmDisabled || (typedConfirmation !== undefined && typed !== typedConfirmation);

return (
<Dialog open={open} onOpenChange={onOpenChange}>
Expand All @@ -62,7 +66,7 @@ export function ConfirmDialog({
</Button>
<Button
variant={destructive ? "destructive" : "default"}
disabled={confirmDisabled}
disabled={disabled}
onClick={onConfirm}
>
{confirmLabel}
Expand Down
3 changes: 2 additions & 1 deletion panel/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ work while this rewrite runs (see root `AGENTS.md`).
`src/shared/` (api client, session, i18n, theme, cross-cutting ui glue),
`src/features/` (screen-level slices — one directory per feature, own
tests colocated). New screens/features get their own `src/features/<name>/`
directory, not a growing `src/pages/`.
directory, not a growing `src/pages/`. Cross-cutting, feature-agnostic React
hooks (e.g. `useScrollSpy`) live in `src/shared/hooks/`.
- **Routing:** TanStack Router, code-based (`createRootRoute`/`createRoute`/
`createRouter` in `src/app/router.tsx`) — not file-based. Protected routes
nest under the pathless `_app` layout route (`beforeLoad: protectedBeforeLoad`)
Expand Down
31 changes: 26 additions & 5 deletions panel/src/app/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { LoginScreen } from "../features/auth/LoginScreen";
import { QrLoginScreen } from "../features/auth/QrLoginScreen";
import { RegisterScreen } from "../features/auth/RegisterScreen";
import { ProtectedLayout, protectedBeforeLoad } from "./shell/ProtectedLayout";
import { EventWorkspaceStub } from "../features/events/EventWorkspaceStub";
import { EventWorkspaceLayout } from "../features/workspace/EventWorkspaceLayout";
import { HomePage } from "../features/home/HomePage";
import { WorkspaceOverview } from "../features/workspace/WorkspaceOverview";
import { EventSettingsPage } from "../features/workspace/settings/EventSettingsPage";
import { OrganizationPage } from "../features/organization/OrganizationPage";
import { PlaceholderPage } from "../shared/ui/PlaceholderPage";
import { getInstance } from "../shared/api/client";
import { queryClient } from "./queryClient";
Expand Down Expand Up @@ -81,17 +84,35 @@ const equipmentRoute = createRoute({
const organizationRoute = createRoute({
getParentRoute: () => protectedLayoutRoute,
path: "/organization",
component: () => <PlaceholderPage titleKey="navOrganization" />,
component: OrganizationPage,
});

const eventStubRoute = createRoute({
const eventWorkspaceRoute = createRoute({
getParentRoute: () => protectedLayoutRoute,
path: "/events/$eventId",
component: EventWorkspaceStub,
component: EventWorkspaceLayout,
});

const eventOverviewRoute = createRoute({
getParentRoute: () => eventWorkspaceRoute,
path: "/",
component: WorkspaceOverview,
});

const eventSettingsRoute = createRoute({
getParentRoute: () => eventWorkspaceRoute,
path: "/settings",
component: EventSettingsPage,
});

const routeTree = rootRoute.addChildren([
protectedLayoutRoute.addChildren([indexRoute, teamRoute, equipmentRoute, organizationRoute, eventStubRoute]),
protectedLayoutRoute.addChildren([
indexRoute,
teamRoute,
equipmentRoute,
organizationRoute,
eventWorkspaceRoute.addChildren([eventOverviewRoute, eventSettingsRoute]),
]),
loginRoute,
registerRoute,
qrLoginRoute,
Expand Down
73 changes: 0 additions & 73 deletions panel/src/features/events/EventWorkspaceStub.test.tsx

This file was deleted.

55 changes: 0 additions & 55 deletions panel/src/features/events/EventWorkspaceStub.tsx

This file was deleted.

24 changes: 24 additions & 0 deletions panel/src/features/events/eventDates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { ApiEvent } from "./eventTiming";

// A single date, or "start – end" when the dates differ, in the viewer's
// locale — no date library per plan constraints. `start_date`/`end_date`
// are bare calendar dates stored as UTC-midnight ISO timestamps (see
// CreateEventDialog), so the formatter is pinned to UTC to keep the
// displayed date stable regardless of the viewer's local timezone (without
// it, viewers behind UTC see the date roll back by one day).
//
// Extracted from LiveStrip.tsx (P1.1) so the workspace header (P1.2 Task 2)
// can reuse the exact same UTC-pinned formatting instead of re-deriving it.
export function formatDateRange(event: ApiEvent, locale: string): string | null {
if (!event.start_date) return null;
const dateFmt = new Intl.DateTimeFormat(locale, {
day: "numeric",
month: "short",
year: "numeric",
timeZone: "UTC",
});
const start = dateFmt.format(new Date(event.start_date));
if (!event.end_date) return start;
const end = dateFmt.format(new Date(event.end_date));
return start === end ? start : `${start} – ${end}`;
}
21 changes: 1 addition & 20 deletions panel/src/features/home/LiveStrip.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Button, Card, Progress, Skeleton } from "@idento/ui";
import { Link } from "@tanstack/react-router";
import { useTranslation } from "react-i18next";
import { formatDateRange } from "../events/eventDates";
import { isDateOnly, type ApiEvent } from "../events/eventTiming";
import { useEventReadiness, useEventStats } from "../events/hooks";

Expand Down Expand Up @@ -42,26 +43,6 @@ function formatRunningWindow(event: ApiEvent, locale: string, allDayLabel: strin
return parts.length > 0 ? parts.join(" · ") : null;
}

// A single date, or "start – end" when the dates differ, in the viewer's
// locale — no date library per plan constraints. `start_date`/`end_date`
// are bare calendar dates stored as UTC-midnight ISO timestamps (see
// CreateEventDialog), so the formatter is pinned to UTC to keep the
// displayed date stable regardless of the viewer's local timezone (without
// it, viewers behind UTC see the date roll back by one day).
function formatDateRange(event: ApiEvent, locale: string): string | null {
if (!event.start_date) return null;
const dateFmt = new Intl.DateTimeFormat(locale, {
day: "numeric",
month: "short",
year: "numeric",
timeZone: "UTC",
});
const start = dateFmt.format(new Date(event.start_date));
if (!event.end_date) return start;
const end = dateFmt.format(new Date(event.end_date));
return start === end ? start : `${start} – ${end}`;
}

function RunningCard({ event }: { event: ApiEvent }) {
const { t, i18n } = useTranslation();
const stats = useEventStats(event.id, { poll: true });
Expand Down
3 changes: 2 additions & 1 deletion panel/src/features/home/ReadinessCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import type { components } from "../../shared/api/schema";
type EventReadinessResponse = components["schemas"]["EventReadinessResponse"];
type ReadinessStep = components["schemas"]["ReadinessStep"];

const STEP_LABEL_KEYS: Record<ReadinessStep["key"], string> = {
// eslint-disable-next-line react-refresh/only-export-components -- Shared step-label lookup belongs with the readiness pipeline it describes; not a real Fast Refresh issue for this pattern.
export const STEP_LABEL_KEYS: Record<ReadinessStep["key"], string> = {
attendees: "readinessStepAttendees",
badge: "readinessStepBadge",
zones: "readinessStepZones",
Expand Down
Loading
Loading