Procedures were re-fetching the acting user from DB using the session
email, which breaks if email changes between session creation and request.
ctx.dbUser is populated by protectedProcedure and is always current.
Also removed the now-unused findVacationActor helper function.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves approve, reject, cancel, and request vacation business logic
out of the tRPC procedure layer into packages/application, matching
the pattern used by allocation use-cases.
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>
Moves approve, reject, cancel, and request vacation business logic
out of the tRPC procedure layer into packages/application, matching
the pattern used by allocation use-cases.
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>
Prevents mutations from committing without an audit trail if the
auditLog.create call fails after the main write already succeeded.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Moves read, assignment-procedures, assignment-mutations, and demand
procedures into allocation/ so the domain boundary is discoverable
without grep.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eliminates O(resource × allocation × days) iteration in findCapacityWindows
by pre-computing vacation date sets and using direct range overlap math.
Adds performance regression test (50 resources × 20 allocs × 365 days < 500ms).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dailyCostCents, hoursPerDay, percentage now validated at API boundary
- vacation router no longer uses ctx.db as any
- scenarioData reads through typed Zod schema
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents orphaned Assignment rows when a DemandRequirement is deleted.
Adds (resourceId, status, endDate) and (projectId, status, endDate)
indexes to support capacity range queries.
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>
- New allocation.checkConflicts managerProcedure: returns per-day overbooking
breakdown (availableHours, existingHours, requestedHours, overageHours,
maxOverbookPercent) plus vacation overlap list for the requested period.
Read-only — used by AllocationModal for pre-submission warnings.
- createAssignment(): replace the hard >5-day overbooking block with a soft
CONFLICT error. When allowOverbooking: true is passed the assignment is
created and overbookingAcknowledged is set to true on the record.
- allowOverbooking field added to CreateAssignmentBaseSchema (optional)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Assignment.overbookingAcknowledged Boolean @default(false) — audit trail
for intentional overbookings
- CreateAssignmentBaseSchema gets allowOverbooking?: boolean flag so callers
can explicitly opt in to overbooking
- Export AllocationConflictCheckResult, AllocationConflictDay,
AllocationVacationOverlap types from @capakraken/shared for use in the
new conflict-check API procedure and AllocationModal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- event-bus: wrap each subscriber.fn call in try/catch so one throwing subscriber cannot kill delivery to all others
- event-bus: log Redis parse errors instead of swallowing them silently; add .catch() on Redis publish promise for async fallback to local delivery
- pruning.ts: new runPruning() deletes expired invite tokens, expired password-reset tokens, and read notifications older than 90 days
- settings.runPruning: expose pruning as adminProcedure mutation
- trpc.ts: E2E_TEST_MODE rate-limit bypass is now a no-op in production (NODE_ENV=production); logs a startup warning if misconfigured
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- applyProjectScenario: wrap assignment loop in db.$transaction to prevent partial updates
- vacation approve/reject: fix TOCTOU race via updateMany with status-guard in WHERE + CONFLICT on count=0
- vacation cancel: wrap vacation.update + entitlement.updateMany in $transaction
- batchApprove: collect mutations, wrap in $transaction, dispatch SSE/notifications after commit
- Fix dead-code bug in createHappyPathDb where $transaction was assigned after return
- Add atomicity and concurrency tests
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>
Hardcoded dates (2026-03-20 / 2026-04-05) were now in the past, causing
the demand window filter (endDate >= now) to exclude the mock demand
requirement and miss the expected staffing anomaly.
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>