Covers auth redirect, resource/project list filtering, timeline render,
sidebar navigation, and staffing panel. Gives deploy confidence for
the main happy paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Runtime errors in components now show a friendly "Something went wrong"
screen instead of a white page. Timeline and staffing panel are
individually wrapped. Route-level error.tsx handles server component errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Resources, projects, and allocations filter state now syncs to/from
URL so filters survive refresh and can be shared via link.
Text inputs are debounced (300ms) to avoid URL churn.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents redundant re-renders when parent state changes by stabilising
event handler references and memoising expensive derived data in
TimelineView, TimelineResourcePanel, and TimelineProjectPanel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Throw at startup in production if REDIS_URL/DATABASE_URL/NEXTAUTH_SECRET missing
- Warn in development when REDIS_URL falls back to localhost
- QueryClient: add gcTime, disable refetchOnWindowFocus, skip retry on 4xx
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Read-only capacity snapshot with utilization donut, top 5 active projects,
open demand alert banner, and quick-link grid — single-column card layout
optimised for PWA standalone mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sends a Monday digest to all ADMIN + MANAGER users with:
- Team utilization % for the next 4 weeks
- Overbooked resource count
- Open demand count
- Upcoming vacation count
- Top 5 most utilized resources
Route: GET /api/cron/weekly-digest (secured by CRON_SECRET).
HTML template and plain-text fallback included.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows resources with available capacity in a selected date window.
- Filter by date range (with DateRangePresets), min hours/day slider, and free-text search
- Cards show role, chapter, available h/day with color-coded capacity bar
- Links to individual resource profiles
- "Bench" nav entry added to Resources section in AppShell
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allocation bars that have active optimistic overrides (post-drag,
awaiting server confirmation) now pulse subtly via animate-pulse.
The pending set is derived from the existing optimisticAllocations
map keys, requiring no additional state.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tracks scroll position via requestAnimationFrame to avoid re-renders
on every pixel. Allocation bars outside the visible horizontal window
(+ 10-column overscan) are skipped during render, reducing DOM nodes
significantly at day zoom (365 days × 40px = 14,600px canvas).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Double-clicking an allocation bar opens an inline editor overlay
with start date, end date, and hours/day fields. Saves via
trpc.allocation.update, closes on Escape or click outside.
Only visible to users with manage permissions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds an Export button that downloads visible/filtered allocation rows
as an xlsx file via the existing downloadWorkbookSheets utility.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Shows top 3 resource suggestions (name, utilization, available h/d) below the
demand details using the existing staffing.getProjectStaffingSuggestions query.
Includes a shimmer loading skeleton while fetching. Each "Fill" button opens
the fill demand modal with the demand pre-loaded.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add "Shift Dates…" action to the batch action bar. Opens a modal with a
signed integer input; on confirm calls the existing timeline.batchShiftAllocations
procedure (allocationIds, daysDelta, mode="move").
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- EmptyState shared component; replace AllocationsClient inline empty state
- DateRangePresets (this month/quarter/3 months/year) integrated into AllocationModal
- Debounce conflict-check inputs in AllocationModal (400ms) using existing useDebounce
- Dashboard layout save feedback via SuccessToast after DB write completes
- Scenarios nav item in Planning sidebar + /scenarios list page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: useDashboardLayout initialised React state with createDefaultDashboardLayout()
(1 widget), so the wrong default rendered during the ~100–500ms window while React Query
fetched the user session and DB layout after login. On reload within staleTime the cache
hit resolved instantly, masking the bug.
Fix: add isHydrated boolean state that becomes true only once localStorage OR DB
hydration has settled; DashboardClient renders a GridLayoutSkeleton until then.
Also adds router.refresh() in the sign-in handler to bust the Next.js Router Cache
so the post-login navigation always lands on a fresh server component tree.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New ConflictWarningPanel component: amber box with per-day overbooking
table (capacity / already booked / new / overage) and sky-blue info box
for vacation overlap. Overbooking section has an 'I understand' checkbox
that must be ticked before Save is enabled; vacation overlap is
informational only.
- AllocationModal: fires allocation.checkConflicts reactively when
resourceId, dates and hoursPerDay are all set. Shows ConflictWarningPanel
between form body and footer. Passes allowOverbooking: true to the
createAssignment mutation when the user acknowledges. Acknowledgment
resets whenever key fields change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
AnimatedModal: add disableBackdropClose prop (default false, no impact
on existing consumers). When true, overlay onClick is removed.
ProjectWizard: remove handleBackdropClick — backdrop click no longer
closes the wizard. Only the X button and Cancel close it.
EstimateWizard already had no backdrop-click handler; no change needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract EMPTY_VALIDATE_INPUT as module constant (prevents new object on every render)
- Extract IssueList component + ISSUE_STYLES map (eliminates blocker/warning copy-paste)
- Extract ReadinessIssue type from ReadinessReport
- Reuse buildValidateInput in handleSubmit (single source for path mapping)
- Guard setValidateInput(null) in onChange — only resets when not already null
- Remove unnecessary `as ReadinessReport` cast (tRPC infers the type)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This override was blocking all dark: Tailwind classes on amber-50 elements.
Components now use explicit dark:bg-amber-950/30 instead.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a "Validate" button that calls the existing `validateImportBatch`
tRPC query before staging. Shows a readiness report inline:
- Green/amber/red status line based on canCommitWithStrictSourceData
- Record counts (resources, projects, assignments, vacations)
- Blocker issues in red with resolution hints
- Warnings in amber with resolution hints
- Fallback assumptions listed in gray
Also fixes a pre-existing bug where handleSubmit mapped wrong filePaths
keys to API fields (keys are resources/projects/assignments, not the
API field names).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The warning was showing briefly on every page load because
`!aiConfigQuery.data?.configured` evaluated to true while the query
was still in-flight (data === undefined). Guard with `!isLoading` so
the amber box only appears after the query resolves with configured=false.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All chapter text inputs now show autocomplete suggestions from the
database (distinct chapter values from active resources) via HTML
<datalist> wired to trpc.resource.chapters:
- ResourceModal: chapter input
- RateCardsClient: rate card line chapter input
- EffortRulesClient: effort rule chapter input
- ExperienceMultipliersClient: replaces hardcoded CHAPTER_PRESETS
with live data, falls back to presets when no data available
Also revert blueprintRolePresetsInputSchema to z.array(z.unknown())
to restore compatibility with StaffingRequirement[] call sites.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add batchHardDelete adminProcedure to resource-mutations router
- Per-row Delete button visible to ADMIN role only
- Delete Selected button in BatchActionBar for ADMIN role only
- Two-step confirmation dialogs with permanent-action warnings
- Audit log written for each deleted resource
Co-Authored-By: claude-flow <ruv@ruv.net>
#49 — upgrade MyVacationsClient account-not-linked inline text to a
prominent centred amber card with icon, heading, and admin-action guidance.
#57 — replace vague AI suggestions hint with actionable copy explaining
the Step 3 dependency before the user wonders why the list is empty.
Co-Authored-By: claude-flow <ruv@ruv.net>
#58: Split the merged "Type: BD / INT" field in the wizard review step into
separate "Order Type", "Allocation Type", and "Status on create" rows so
users can clearly distinguish commercial classification from lifecycle status.
#60: Relabel FillOpenDemandModal staging CTA from "+ Add to Plan" to
"+ Queue Assignment" and the proceed CTA from "Review (N)" to
"Review Queued (N)" to make the staged/non-final nature of the action clear.
Also correct the project detail Assignments label from "N active" to
"N planned" and update the tooltip to include PROPOSED in the definition.
Co-Authored-By: claude-flow <ruv@ruv.net>
#66: Project detail "Open Demands" summary incorrectly counted COMPLETED
demands as open. Fix: add `status !== "COMPLETED"` to the activeDemands
filter in /projects/[id]/page.tsx.
#59/#67: Project creation and edit had two bugs:
1. Both invalidated `project.list` but the page queries `project.listWithCosts`
— the list never refreshed after a save.
2. Success toasts were either absent (ProjectModal) or mounted inside the
wizard component that unmounts before the toast finishes.
Fix: correct invalidation key to listWithCosts; add optional onSuccess prop
to both ProjectWizard and ProjectModal; ProjectsClient wires onSuccess to a
persistent SuccessToast rendered outside the modals.
Co-Authored-By: claude-flow <ruv@ruv.net>
#55: Add SuccessToast after new resource is created. ResourceModal gains an
optional onSuccess(displayName) prop; ResourcesClient wires it to a toast
that auto-dismisses after 2.5 s.
#56: Fix useFocusTrap stale-closure bug. Focusable elements are now queried
dynamically inside handleKeyDown (not captured once at mount), so Tab
navigation stays correct as the form re-renders. Initial focus is deferred
via requestAnimationFrame so the browser layout is stable before focus() fires.
Co-Authored-By: claude-flow <ruv@ruv.net>
Adds a transactional hard-delete procedure behind adminProcedure that
removes a resource's assignments and vacations first, then the record
itself, and writes an audit log entry. The ResourceModal exposes a
"Delete Resource" button (edit mode, ADMIN role only) with an inline
confirm step before the mutation fires.
Co-Authored-By: claude-flow <ruv@ruv.net>
- #51: Add permanent redirect /login → /auth/signin in next.config.ts
so users/testers who type the common alias land on the correct auth page
- #53: Add "Allocations → New Planning Entry" link to empty states of
ProjectDemandsTable and ProjectAssignmentsTable; add shortcut link in
demands table header for canEdit users
- #54: Track confirmed dropdown selection in ResourcePersonPicker —
green ring + checkmark icon shown when user picks from suggestions;
cleared on any manual keypress so free-text is clearly unconfirmed
Co-Authored-By: claude-flow <ruv@ruv.net>
- e2e/dev-system/nav-smoke.spec.ts: new Playwright spec in the dev-system
suite; iterates every href from navSections + adminNavEntries, asserts
HTTP status ≠ 404 and no "page not found" text for an authenticated admin
- e2e/navigation.spec.ts: add "all nav routes resolve" smoke block covering
16 routes not previously tested in the isolated test suite
All 35 routes pass against live dev server. Catches dead nav links before
users encounter them.
Co-Authored-By: claude-flow <ruv@ruv.net>
- #47: Remove misleading asterisk from Budget (EUR) label in project
wizard — budget is optional per canGoNext() logic
- #48: Parse Zod validation JSON in wizard submit error handler so users
see "Responsible person is required" instead of raw JSON array
- #45: Expose isEntriesError from timeline query context; TimelineView
now renders an explicit error message instead of a silent empty canvas
when the getEntriesView query fails
Co-Authored-By: claude-flow <ruv@ruv.net>
- Split auth config into auth.config.ts (edge-safe, no argon2) and auth-edge.ts
for middleware use; auth.ts now spreads the shared config
- Middleware wraps with auth() to redirect unauthenticated requests to /auth/signin
before any page render; passes through /auth/, /api/, /invite/ paths
- SessionGuard client component watches useSession() and redirects on
status=unauthenticated, closing the SPA navigation gap
- QueryCache + MutationCache in TRPCProvider redirect on UNAUTHORIZED tRPC errors
without retrying; SessionProvider polls session state every 5 minutes
- Middleware tests updated for async auth wrapper and auth-edge mock
Co-Authored-By: claude-flow <ruv@ruv.net>
Four new test files — 27 tests total:
- role-router-auth.test.ts (8): UNAUTHORIZED/FORBIDDEN on all mutations for
unauthenticated/USER callers; MANAGER and ADMIN happy paths
- webhook-router-auth.test.ts (6): adminProcedure guard verified for all
six webhook procedures across USER/MANAGER/ADMIN roles
- comment-sanitization-router.test.ts (4): proves stripHtml runs before
db.comment.create — script tags stripped, plain text and @mentions preserved
- auth-anomaly-check/route.test.ts (+5 unit tests): detectAuthAnomalies()
unit coverage — empty window, global threshold, per-entity threshold, null
entityId, and both anomaly types firing simultaneously
Co-Authored-By: claude-flow <ruv@ruv.net>
- useTimelineDrag: onProjectBarMouseDown and single-alloc drag path now reset
multiSelectRef + multiSelectState before starting a new drag, so the
FloatingActionBar is dismissed immediately when an unrelated drag begins
- FloatingActionBar.test.tsx: 4 regression tests for the null-render guard
(count=0) and all three label variants
- useTimelineSSE.test.ts: 2 new tests — tab hides during pending reconnect
timer (clears timer, resyncs on next open) and first-ever connection fails
before any open (retry open still resyncs correctly)
- assistant-tools-user-admin-inventory-read.test.ts: add isActive to expected
findMany select shape (already in production, test was stale)
Co-Authored-By: claude-flow <ruv@ruv.net>
- Invite flow: admin can invite users by email with role selection; accept-invite page
sets password and creates the account; 72-hour token expiry; E2E tests
- User deactivate/reactivate/delete: new tRPC procedures + UI buttons; deactivation
revokes all active sessions immediately; delete cascades vacation/broadcast records;
isActive field added via migration 20260402000000_user_isactive
- Auth: block login for inactive users with audit entry
- Favicon: SVG favicon + ICO/PNG fallbacks (16, 32, 180, 192, 512px); manifest updated
- Dashboard: GridLayout dynamic-import loading skeleton prevents blank dark area
on first login before react-grid-layout chunk is cached
- Admin users: remove max-w-5xl constraint so table uses full page width
- Dev: docker container restart workflow documented in LEARNINGS.md; Prisma generate
must run inside the container after schema changes (named node_modules volume)
Co-Authored-By: claude-flow <ruv@ruv.net>
Introduce getAppBaseUrl() in packages/api/src/lib/app-base-url.ts:
- Reads NEXTAUTH_URL (trimmed, trailing slash stripped)
- production: throws if NEXTAUTH_URL is missing/empty so broken
localhost links in emails are caught at runtime, not silently sent
- development/test: falls back to http://localhost:3100 with a
one-time console.warn
Replace the duplicated inline fallback in:
- packages/api/src/router/invite.ts (invite email link)
- packages/api/src/router/auth.ts (password reset email link)
Extend GET /api/health to report:
"baseUrl": { "configured": bool, "isLocalhost": bool }
so deployment checks can detect a misconfigured NEXTAUTH_URL.
Co-Authored-By: claude-flow <ruv@ruv.net>
- global-setup.ts: create reset-test@planarchy.dev directly via DB
(argon2id hash computed in Node.js, inserted via docker exec psql stdin
with correct camelCase quoted column names + createdAt/updatedAt;
ON_ERROR_STOP=1 so failures propagate rather than being swallowed)
- helpers.ts: resetPasswordViaApi now updates passwordHash directly in DB
(bypasses tRPC batch mutation format issues entirely);
getLatestEmailTo decodes MIME parts per Content-Transfer-Encoding
(quoted-printable soft line breaks were truncating 64-char tokens to ~14 chars)
- invite-flow.spec.ts: use fresh unauthenticated browser context for
the invite accept page (admin context was inheriting cookies)
- docker-compose.yml: hardcode SMTP_HOST=mailhog for Docker app service
(host .env value localhost doesn't reach Mailhog inside Docker network)
All 3 email E2E tests pass: invite flow, password reset flow, invalid token.
Co-Authored-By: claude-flow <ruv@ruv.net>