feat: scene-based PA demo with cinematic transitions#35
feat: scene-based PA demo with cinematic transitions#35
Conversation
- Add Dashboard Dockerfile (multi-stage Node build → nginx) and nginx.conf with SPA routing and /api reverse proxy to Gateway - Simplify GraphQL client to use relative /api/graphql (works with both Vite dev proxy and nginx prod proxy) - Update AppHost with IsPublishMode conditional: Vite dev server for local, containerized nginx for Azure deployment - Add Azure hosting NuGet packages for PostgreSQL and Redis - Generate azd infrastructure (Bicep + ACA templates) with .Env references for parameter resolution - Add azd-sync-secrets.sh to sync dotnet user-secrets → azd env vars Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ase state Install motion + @xyflow/react deps. Create DemoProvider context that manages scene navigation (encounter/fleet/case) and selected case ID state. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Horizontal pill-style nav bar with Encounter/Fleet/Case Detail buttons. Active pill shows teal fill, inactive shows outline. Includes Reset Demo button. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… scaffolding Add SceneTransition component using motion/react AnimatePresence for opacity fade transitions between scenes. Scaffold /fleet and /case/$caseId routes with SceneNav integration and placeholder content. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add SeedDemoData static class that generates 48 deterministic PARequestModel instances for the PA dashboard demo, distributed across all 7 Athena sandbox patients with realistic clinical content, procedures (MRI Lumbar, MRI Cervical, CT Abdomen/Pelvis), and status distribution (6 processing, 8 ready, 15 submitted, 8 waiting_for_insurance, 9 approved, 2 denied). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ponent Add deterministic fleet seed data generator (48 PA requests across 6 statuses) and KPI cards component with color-coded borders and active filter highlighting. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…anel component Add DEMO_CHART_DATA with problems (ICD codes), medications, allergies (NKDA), imaging history, and lab results. Create ChartTabPanel component that renders tab-specific content for the sidebar chart browser. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add compact FleetCard with patient initials, status dot, CPT code, payer badge, and confidence display. FleetView provides a filterable grid with highlight support. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add PatientNode, EvidenceNode, CriteriaNode, DecisionNode components with shared NodeCard wrapper. Each node has typed data props, styled borders, and React Flow handles for graph connectivity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add horizontal pipeline visualization with 6 stages (Order Signed through Payer Response), interactive counts, active stage highlighting, and animated SVG connectors. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…hart tabs Add chart tab browser (Problems, Meds, Allergies, Vitals, Imaging, Labs) to the sidebar when chartData is provided. Enhanced mode shows extended encounter stages (Intake through Sign) and optional PA detection stages. Legacy mode (no chartData) preserves all existing behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add CaseGraph component that builds a React Flow visualization from PARequest data (patient -> evidence -> criteria -> decision). Includes AnimatedEdge with SVG animateMotion dot and buildCaseGraphData utility. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aphQL wiring Assemble the fleet dashboard page with KPI cards, case pipeline, fleet grid, and activity feed sidebar. Falls back to seed data when GraphQL is unavailable. KPI card clicks filter the fleet view by status. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…chine Add pre-flagged states (chart-browsing, order-entry, pa-detected, documenting) and new actions (addOrder, startDocumenting, transitionToFleet) to support the enhanced Scene 1 demo flow. order-entry auto-transitions to pa-detected after 1200ms. Default startState='idle' preserves all existing behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # apps/dashboard/src/routeTree.gen.ts # apps/dashboard/src/routes/fleet.tsx
…nd EhrHeader enhancements Add AuthDetectionBanner component with amber/teal accent, payer name, and policy reference (LCD L34220). Enhance EhrHeader with encounter type pill badge and optional facility name. All existing behavior preserved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add CaseTimeline component with completed/active/pending phase rendering. Implement full case.$caseId route with split-panel layout: evidence graph (left) and detail cards (right) including timeline, patient summary, expandable criteria evidence, and clinical summary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # apps/dashboard/src/routeTree.gen.ts # apps/dashboard/src/routes/case.$caseId.tsx
…yout animations Add DemoProvider (context with autoPlay cycling, resetDemo), SceneTransition (AnimatePresence wrapper with direction-based variants), SceneNav (pill navigation with auto-play toggle), and extracted transitionVariants. 9 new tests covering scene switching, auto-play cycling, non-linear navigation, and state reset. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…y point Wire DemoProvider + SceneNav into ehr-demo page, add "View in Fleet" button after PA submission completes, create stub fleet and case route files with DemoProvider/SceneNav wiring. All 12 original ehr-demo tests preserved, 2 new integration tests added (14 total). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tion Resolve merge conflicts between parallel agent streams: - Take integration agent's enhanced DemoProvider (autoPlay, resetDemo), SceneNav (auto-play toggle), SceneTransition (direction variants) - Keep fleet/case-detail agents' full route implementations - Add DemoProvider wrapping to fleet and case routes - Replace case route's local SceneNav with shared component - Regenerate route tree Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…entation The integration agent used aria-current="page" instead of data-active for active scene indication (better accessibility). Update tests to match. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughIntroduces a three‑scene Prior Authorization dashboard (Enhanced EHR, Fleet, Case Detail) with demo orchestration, React Flow graph visualizations, new UI components and hooks, deterministic fleet seed data and backend seed generator, containerized dashboard (Docker + Nginx), Azure infra templates, and broad test coverage and route additions. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant SceneNav
participant DemoProvider
participant SceneTransition
participant Scene as SceneComponent
User->>SceneNav: click scene pill
SceneNav->>DemoProvider: setScene(key)
DemoProvider->>DemoProvider: update scene state
DemoProvider-->>SceneTransition: new sceneKey
SceneTransition->>Scene: render (initial → animate)
Scene-->>User: scene displayed
sequenceDiagram
participant Client
participant FleetPage
participant GraphQL
participant SeedGen as generateFleetData
participant KPICards
participant FleetView
Client->>FleetPage: render /fleet
FleetPage->>GraphQL: request PARequests/Stats
alt GraphQL returns data
GraphQL-->>FleetPage: data
else GraphQL unavailable
FleetPage->>SeedGen: generateFleetData()
SeedGen-->>FleetPage: fallback data
end
FleetPage->>KPICards: pass stats, onFilter
KPICards->>FleetPage: onFilter('ready')
FleetPage->>FleetView: provide filtered requests
FleetView-->>Client: render filtered cards
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
✨ Finishing Touches
🧪 Generate unit tests (beta)
|
There was a problem hiding this comment.
Actionable comments posted: 8
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/dashboard/src/components/ehr/EncounterSidebar.tsx (1)
48-62:⚠️ Potential issue | 🟠 MajorEnhanced mode still uses the legacy PA visibility/index logic.
With
startState='chart-browsing',showPAStagesis already true, so the enhanced sidebar renders Prior Auth beforepaDetectedis set. The same legacy index mapping also mislabels the 2-step enhanced flow:reviewinglights upPA Submit, andsubmittingmarks both stages completed. The enhanced path needs its own gating and active-step mapping instead of reusing the legacy one.Also applies to: 191-195, 254-267
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/ehr/EncounterSidebar.tsx` around lines 48 - 62, The enhanced sidebar is incorrectly reusing the legacy PA visibility/index logic (getPAActiveIndex), causing Prior Auth to render before paDetected and mis-mapping the 2-step enhanced flow; add a separate mapping and gating for the enhanced path (e.g., create getPAEnhancedActiveIndex(flowState: EhrDemoState) and a boolean gate like shouldShowPAEnhanced(startState, paDetected) or use an isEnhanced flag) and replace legacy usages in the enhanced rendering branches (where getPAActiveIndex is currently used; also update the other occurrences referenced in the review) so that the enhanced flow only shows PA stages after paDetected and maps 'reviewing'/'submitting' to the correct 2-step active/completed indices.
🟡 Minor comments (21)
docs/market-research/palantir-dashboard.md-8-9 (1)
8-9:⚠️ Potential issue | 🟡 MinorFix markdown formatting inconsistency.
Line 8 contains an errant
>blockquote character and excessive spacing (> * Left:) that breaks the list formatting. Additionally, the bullet style is inconsistent—line 8 uses*while line 9 uses-.📝 Proposed formatting fix
-**Top Half:** > * Left: App Logo (Blue square with "PA"), App Name "Prior Auth Hub", and subtitle "Meridian Health System" in muted text. - - Right: A search bar ("Search cases, patients, providers..."), a green dot with "System Operational", a "Notifications" button, and a user avatar square with "DR". +**Top Half:** + - Left: App Logo (Blue square with "PA"), App Name "Prior Auth Hub", and subtitle "Meridian Health System" in muted text. + - Right: A search bar ("Search cases, patients, providers..."), a green dot with "System Operational", a "Notifications" button, and a user avatar square with "DR".As per coding guidelines:
docs/**→ Check for accuracy and completeness.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/market-research/palantir-dashboard.md` around lines 8 - 9, Remove the stray blockquote character and extra spaces in the "Top Half" list item and make the bullet style consistent across the two lines; specifically, change "> * Left: ..." to a normal list entry (remove the leading ">") and use the same bullet character for both entries (either "-" or "*") with consistent indentation so the "Left:" and "Right:" subitems render as a proper list in palantir-dashboard.md.orchestration/AuthScript.AppHost/infra/main.parameters.json-17-21 (1)
17-21:⚠️ Potential issue | 🟡 MinorDouble
AZURE_prefix in environment variable names.
AZURE_AZURE_OPENAI_ENDPOINTandAZURE_AZURE_OPENAI_KEYappear to have a redundant prefix. If the convention isAZURE_<PARAM_NAME>, these should likely beAZURE_OPENAI_ENDPOINTandAZURE_OPENAI_KEYrespectively.Suggested fix
"azure_openai_endpoint": { - "value": "${AZURE_AZURE_OPENAI_ENDPOINT}" + "value": "${AZURE_OPENAI_ENDPOINT}" }, "azure_openai_key": { - "value": "${AZURE_AZURE_OPENAI_KEY}" + "value": "${AZURE_OPENAI_KEY}" },🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@orchestration/AuthScript.AppHost/infra/main.parameters.json` around lines 17 - 21, The parameter values for azure_openai_endpoint and azure_openai_key use duplicated AZURE_ prefixes; update the value expressions to reference the correct environment variables AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_KEY (replace "${AZURE_AZURE_OPENAI_ENDPOINT}" with "${AZURE_OPENAI_ENDPOINT}" and "${AZURE_AZURE_OPENAI_KEY}" with "${AZURE_OPENAI_KEY}") so the keys match the intended AZURE_<PARAM_NAME> convention.docs/designs/2026-03-15-pa-dashboard-demo.md-17-28 (1)
17-28:⚠️ Potential issue | 🟡 MinorAdd languages to fenced code blocks to satisfy markdownlint (MD040).
Several fenced sections are missing language identifiers; use
textfor diagrams/pseudocode blocks.As per coding guidelines, `docs/**`: "Check for accuracy and completeness. Light review only."📝 Example fix pattern
-``` +```text Scene 1: "The Encounter" Scene 2: "The Command Center" Scene 3: "The Case Detail" ... -``` +```Also applies to: 115-117, 182-185, 282-289, 315-319, 398-433
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/designs/2026-03-15-pa-dashboard-demo.md` around lines 17 - 28, The fenced diagram blocks (e.g., the Scene 1/Scene 2/Scene 3 ASCII diagram containing "Scene 1: \"The Encounter\"" and other similar pseudocode/diagram blocks) are missing a language identifier and thus violate markdownlint MD040; update each such code fence to include a language tag `text` (for example convert ``` to ```text) for all diagram/pseudocode fenced blocks referenced (including the block starting with Scene 1 and the other blocks noted in the comment) so the docs pass linting while preserving the ASCII content.orchestration/AuthScript.AppHost/next-steps.md-48-49 (1)
48-49:⚠️ Potential issue | 🟡 MinorFix wording typo in the infrastructure note.
Line 48 reads “named
manifestsnext the project file”; it should be “next to the project file.”✏️ Proposed doc fix
-In addition, for each project resource referenced by your app host, a `containerApp.tmpl.yaml` file will be created in a directory named `manifests` next the project file. This file contains the infrastructure as code for running the project on Azure Container Apps. +In addition, for each project resource referenced by your app host, a `containerApp.tmpl.yaml` file will be created in a directory named `manifests` next to the project file. This file contains the infrastructure as code for running the project on Azure Container Apps.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@orchestration/AuthScript.AppHost/next-steps.md` around lines 48 - 49, Fix the wording typo in next-steps.md: change the phrase "named `manifests` next the project file" to "named `manifests` next to the project file" so the sentence reads that the `containerApp.tmpl.yaml` file is created in a directory named `manifests` next to the project file; update the sentence in the paragraph containing `containerApp.tmpl.yaml` and `manifests` to reflect this corrected wording.apps/dashboard/src/components/fleet/KPICards.tsx-77-80 (1)
77-80:⚠️ Potential issue | 🟡 MinorSet an explicit button type to prevent accidental form submission.
Line 77 should include
type="button"for safe embedding in form contexts.✅ Proposed fix
<button + type="button" key={card.key} data-testid={`kpi-card-${card.key}`} onClick={() => onFilter(card.key)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/fleet/KPICards.tsx` around lines 77 - 80, The button in KPICards.tsx that renders each card (the element using key={card.key}, data-testid={`kpi-card-${card.key}`} and onClick={() => onFilter(card.key)}) should explicitly set type="button" to avoid accidental form submission when this component is used inside a form; update the button element to include the type="button" attribute next to its existing props.apps/dashboard/src/components/case/CaseTimeline.tsx-22-27 (1)
22-27:⚠️ Potential issue | 🟡 MinorUse a collision-resistant key for mapped phases.
Line 26 uses
key={phase.name}; duplicate phase names will produce unstable reconciliation.🛠️ Proposed key fix
- {phases.map((phase, i) => { + {phases.map((phase, i) => { const isLast = i === phases.length - 1; return ( - <div key={phase.name} className="flex gap-3"> + <div key={`${phase.name}-${i}`} className="flex gap-3">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/CaseTimeline.tsx` around lines 22 - 27, The mapped phases in CaseTimeline (phases.map inside the CaseTimeline component) use key={phase.name}, which is not collision-resistant; change the key to a unique identifier such as phase.id (or another stable unique property on the phase object) and fall back to a deterministic combination if needed (e.g., `${phase.id ?? phase.name}-${i}`) so React reconciliation remains stable; update the JSX where key is set (currently key={phase.name}) to use this unique identifier.apps/dashboard/src/components/case/__tests__/CaseTimeline.test.tsx-5-11 (1)
5-11:⚠️ Potential issue | 🟡 MinorRemove the unused motion mock and
anytype violations.The
CaseTimelinecomponent uses Tailwind CSS animations (e.g.,animate-ping) and doesn't import or use Framer Motion. The mock on lines 5–11 is unnecessary and introduces avoidableanytypes that violate TypeScript strictness.Cleanup
-vi.mock('motion/react', () => ({ - motion: { - div: ({ children, ...props }: any) => <div {...props}>{children}</div>, - span: ({ children, ...props }: any) => <span {...props}>{children}</span>, - }, - AnimatePresence: ({ children }: any) => <>{children}</>, -}));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/__tests__/CaseTimeline.test.tsx` around lines 5 - 11, Remove the unnecessary vi.mock('motion/react', ...) block from CaseTimeline.test.tsx (the mocked motion.div, motion.span and AnimatePresence) because CaseTimeline uses Tailwind animations not Framer Motion; delete the entire mock and any lingering references to the mocked symbols (motion/react, motion.div, motion.span, AnimatePresence) and replace any "any" type annotations introduced there with proper typings or let TypeScript infer types to satisfy strict mode in the test file.apps/dashboard/src/components/case/__tests__/AnimatedEdge.test.tsx-32-32 (1)
32-32:⚠️ Potential issue | 🟡 MinorRemove the
as anycast to improve type safety.Line 32 suppresses type checking unnecessarily. Extract the component's prop type using
Parameters<typeof AnimatedEdge>[0]and apply it tobaseEdgePropsto let TypeScript validate the test setup properly.Suggested approach
import { AnimatedEdge } from '../AnimatedEdge'; +type AnimatedEdgeProps = Parameters<typeof AnimatedEdge>[0]; + describe('AnimatedEdge', () => { - const baseEdgeProps = { + const baseEdgeProps: AnimatedEdgeProps = { id: 'edge-1', source: 'node-1', target: 'node-2', @@ - <AnimatedEdge {...(baseEdgeProps as any)} /> + <AnimatedEdge {...baseEdgeProps} /> </svg>, );🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/__tests__/AnimatedEdge.test.tsx` at line 32, Remove the `as any` cast on the AnimatedEdge render and give baseEdgeProps the proper prop type so TypeScript can validate the test; import or reference the component type by using Parameters<typeof AnimatedEdge>[0] as the type for baseEdgeProps (replace its current declaration), then render <AnimatedEdge {...baseEdgeProps} /> without the `as any` cast so the compiler enforces correct props for AnimatedEdge.apps/dashboard/src/components/case/__tests__/CaseGraph.test.tsx-5-27 (1)
5-27:⚠️ Potential issue | 🟡 MinorReplace
anyin mocks to resolve ESLint warnings and improve type safety.Lines 6, 11, 16, 17, 25, and 27 use
any, triggering ESLint warnings per the@typescript-eslint/no-explicit-anyrule. Typing these mocks explicitly improves type safety without affecting test behavior.Suggested typed mock shape
+import type { ReactNode, ComponentPropsWithoutRef } from 'react'; +type FlowMockProps = { + nodes: Array<unknown>; + edges: Array<unknown>; + children?: ReactNode; +}; vi.mock('@xyflow/react', () => ({ - ReactFlow: ({ nodes, edges, children }: any) => ( + ReactFlow: ({ nodes, edges, children }: FlowMockProps) => ( <div data-testid="react-flow" data-nodes={nodes.length} data-edges={edges.length}> {children} </div> ), - ReactFlowProvider: ({ children }: any) => <>{children}</>, + ReactFlowProvider: ({ children }: { children?: ReactNode }) => <>{children}</>, Background: () => null, Controls: () => null, Handle: () => null, Position: { Top: 'top', Bottom: 'bottom', Left: 'left', Right: 'right' }, - useNodesState: (init: any) => [init, vi.fn(), vi.fn()], - useEdgesState: (init: any) => [init, vi.fn(), vi.fn()], + useNodesState: <T,>(init: T[]) => [init, vi.fn(), vi.fn()], + useEdgesState: <T,>(init: T[]) => [init, vi.fn(), vi.fn()], getSmoothStepPath: () => ['M 0 0', 0, 0], BaseEdge: () => null, })); vi.mock('motion/react', () => ({ motion: { - div: ({ children, ...props }: any) => <div {...props}>{children}</div>, + div: ({ children, ...props }: ComponentPropsWithoutRef<'div'>) => ( + <div {...props}>{children}</div> + ), }, - AnimatePresence: ({ children }: any) => <>{children}</>, + AnimatePresence: ({ children }: { children?: ReactNode }) => <>{children}</>, }));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/__tests__/CaseGraph.test.tsx` around lines 5 - 27, Replace explicit any in the mocks by importing and using the real types from the libraries and React: type the ReactFlow mock props as React.PropsWithChildren<{ nodes: Node[]; edges: Edge[] }> (or the library's ReactFlowProps) and mark the component as React.FC; type ReactFlowProvider and AnimatePresence props as React.PropsWithChildren<unknown> or React.FC; type Background, Controls, Handle, and BaseEdge as React.FC or the specific component prop types from '@xyflow/react'; type Position as the library's Position type; type useNodesState/useEdgesState to return [Node[], React.Dispatch<React.SetStateAction<Node[]>>, unknown] and similarly for edges using the Node/Edge types from '@xyflow/react'; and type motion.div props as React.ComponentType<React.PropsWithChildren<MotionProps>> with children: React.ReactNode; this removes any by referencing the library types (Node, Edge, MotionProps, ReactFlowProps) when declaring the mock implementations like ReactFlow, useNodesState, useEdgesState, motion.div, and AnimatePresence.apps/dashboard/src/components/case/__tests__/CustomNodes.test.tsx-15-27 (1)
15-27:⚠️ Potential issue | 🟡 MinorFix the
anytype to resolve the pipeline warning.The pipeline flags
Unexpected anyat line 26. Since you're mocking React Flow's NodeProps, you can type this more precisely.🔧 Proposed fix
+import type { NodeProps } from '@xyflow/react'; + // Helper to build mock node props -function mockNodeProps<T extends Record<string, unknown>>(data: T) { +function mockNodeProps<T extends Record<string, unknown>>(data: T): NodeProps<T> { return { id: 'test-node', data, type: 'custom', selected: false, isConnectable: true, zIndex: 0, positionAbsoluteX: 0, positionAbsoluteY: 0, - } as any; + } as NodeProps<T>; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/__tests__/CustomNodes.test.tsx` around lines 15 - 27, The test helper mockNodeProps currently uses an unsafe any cast causing the pipeline "Unexpected any" warning; replace the any with the proper React Flow type by importing NodeProps from 'reactflow' and change the return cast to NodeProps<T> (i.e., ensure mockNodeProps<T> returns NodeProps<T> and import { NodeProps } from 'reactflow'), keeping the same object shape but removing the any cast.apps/dashboard/src/components/fleet/FleetCard.tsx-28-35 (1)
28-35:⚠️ Potential issue | 🟡 MinorEdge case:
getInitialsmay produce undefined characters.If
namecontains empty parts (e.g., double spaces or leading/trailing spaces),part[0]returnsundefined, which.join('')converts to the string"undefined".🛡️ Defensive fix
function getInitials(name: string): string { return name .split(' ') + .filter(Boolean) .map((part) => part[0]) .join('') .toUpperCase() .slice(0, 2); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/fleet/FleetCard.tsx` around lines 28 - 35, The getInitials function can produce "undefined" when name has empty segments; update getInitials to first trim the input, split on spaces, filter out empty parts, and then safely take the first character of each part (e.g., via part.charAt(0) or checking part.length) before joining, uppercasing and slicing; reference the getInitials(name: string) function and ensure empty parts are removed so only valid characters contribute to the result.apps/dashboard/src/routes/__tests__/case.test.tsx-27-40 (1)
27-40:⚠️ Potential issue | 🟡 MinorContinue fixing
anyin remaining mocks.🔧 Proposed fix for motion and router mocks
vi.mock('motion/react', () => ({ motion: { - div: ({ children, ...props }: any) => <div {...props}>{children}</div>, - span: ({ children, ...props }: any) => <span {...props}>{children}</span>, + div: ({ children, ...props }: React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode }) => <div {...props}>{children}</div>, + span: ({ children, ...props }: React.HTMLAttributes<HTMLSpanElement> & { children?: React.ReactNode }) => <span {...props}>{children}</span>, }, - AnimatePresence: ({ children }: any) => <>{children}</>, + AnimatePresence: ({ children }: { children?: React.ReactNode }) => <>{children}</>, })); vi.mock('@tanstack/react-router', () => ({ createFileRoute: () => () => ({}), - Link: ({ children, ...props }: any) => <a {...props}>{children}</a>, + Link: ({ children, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement> & { children?: React.ReactNode }) => <a {...props}>{children}</a>, useNavigate: () => vi.fn(), }));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/__tests__/case.test.tsx` around lines 27 - 40, The mocks use untyped "any" props; update the mocked components in this test so their props are properly typed instead of any: for motion.div and motion.span use React.HTMLAttributes<HTMLDivElement> / React.HTMLAttributes<HTMLSpanElement> (with children?: React.ReactNode) or React.FC<PropsWithChildren<...>> to match real element props, type AnimatePresence as React.FC<{children?: React.ReactNode}>, type Link props as React.AnchorHTMLAttributes<HTMLAnchorElement> (children?: React.ReactNode), and type useNavigate to return the correct navigate signature (e.g., (to: string | {to: string}, opts?: any) => void) while leaving createFileRoute typed as a function; update the vi.mock declarations for motion/react and `@tanstack/react-router` to use these concrete React types for motion.div, motion.span, AnimatePresence, Link, and the useNavigate mock.apps/dashboard/src/routes/__tests__/case.test.tsx-7-21 (1)
7-21:⚠️ Potential issue | 🟡 MinorPipeline warnings: Replace
anywith minimal types in mocks.The CI is flagging multiple
anyusages. For test mocks, lightweight inline types suffice:🔧 Proposed fix for mock typing
-vi.mock('@xyflow/react', () => ({ - ReactFlow: ({ nodes, edges, children }: any) => ( +vi.mock('@xyflow/react', () => ({ + ReactFlow: ({ nodes, edges, children }: { nodes: unknown[]; edges: unknown[]; children?: React.ReactNode }) => ( <div data-testid="react-flow" data-nodes={nodes.length} data-edges={edges.length}> {children} </div> ), - ReactFlowProvider: ({ children }: any) => <>{children}</>, + ReactFlowProvider: ({ children }: { children?: React.ReactNode }) => <>{children}</>, Background: () => null, Controls: () => null, Handle: () => null, Position: { Top: 'top', Bottom: 'bottom', Left: 'left', Right: 'right' }, - useNodesState: (init: any) => [init, vi.fn(), vi.fn()], - useEdgesState: (init: any) => [init, vi.fn(), vi.fn()], + useNodesState: <T,>(init: T) => [init, vi.fn(), vi.fn()], + useEdgesState: <T,>(init: T) => [init, vi.fn(), vi.fn()], getSmoothStepPath: () => ['M 0 0', 0, 0], BaseEdge: () => null, }));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/__tests__/case.test.tsx` around lines 7 - 21, Replace the broad any types in the React Flow test mocks with minimal, specific types: use React.ReactNode for children in ReactFlow and ReactFlowProvider, type the ReactFlow mock props as { nodes: Array<any>; edges: Array<any>; children?: React.ReactNode } (or tighter element/node types if available) for ReactFlow, type Position as the existing string literals, and change useNodesState/useEdgesState signatures from (init: any) => [...] to (init: Array<any> | unknown[]) => [typeof init, jest.Mock, jest.Mock] (or use generic <T> if available) so the mocks (ReactFlow, ReactFlowProvider, useNodesState, useEdgesState, Background, Controls, Handle, getSmoothStepPath, BaseEdge, Position) avoid any while remaining lightweight for tests.orchestration/AuthScript.AppHost/infra/gateway.tmpl.yaml-68-69 (1)
68-69:⚠️ Potential issue | 🟡 MinorFix HTTP_PORTS port default to match configured binding.
Line 69 uses
targetPortOrDefault 0which produces an invalid port0for the HTTP_PORTS environment variable. The gateway's http binding is configured on port 8000 (per manifest.json), so this should betargetPortOrDefault 8000to align with the actual binding and the secondary port 8001 (resulting in8000;8001).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@orchestration/AuthScript.AppHost/infra/gateway.tmpl.yaml` around lines 68 - 69, The HTTP_PORTS env value uses targetPortOrDefault 0 which yields an invalid port 0; update the template in gateway.tmpl.yaml so the call targetPortOrDefault uses 8000 (matching the gateway http binding in manifest.json) so HTTP_PORTS becomes "8000;8001" instead of "0;8001"; change the targetPortOrDefault invocation where HTTP_PORTS is set to reference 8000 and leave the secondary port 8001 unchanged.apps/dashboard/src/components/demo/DemoProvider.tsx-25-25 (1)
25-25:⚠️ Potential issue | 🟡 MinorMove context to a separate file to fix Fast Refresh warning.
The pipeline warning indicates that exporting
DemoContextalongside theDemoProvidercomponent breaks React Fast Refresh. Extract the context creation to a dedicated file.Proposed structure
Create
apps/dashboard/src/components/demo/DemoContext.ts:import { createContext } from 'react'; import type { DemoContextValue } from './types'; export const DemoContext = createContext<DemoContextValue | null>(null);Then import it in
DemoProvider.tsx:-export const DemoContext = createContext<DemoContextValue | null>(null); +import { DemoContext } from './DemoContext';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/demo/DemoProvider.tsx` at line 25, Extract the context creation out of the DemoProvider module by moving the createContext call that defines DemoContext into its own module (e.g., a new DemoContext.ts) that exports DemoContext (typed with DemoContextValue), then update the DemoProvider to import DemoContext from that new module instead of exporting it alongside the component; ensure the exported symbol names remain DemoContext and DemoProvider and that DemoContextValue's type import is preserved where needed.apps/dashboard/src/routes/ehr-demo.tsx-269-276 (1)
269-276:⚠️ Potential issue | 🟡 Minor"View in Fleet" button lacks onClick handler.
The button renders but has no action wired. If this is intentional scaffolding for future integration (per the plan's task-015/016), consider adding a TODO comment. Otherwise, wire it to the DemoContext's
setScene('fleet').Suggested fix if navigation is intended
+import { useContext } from 'react'; +import { DemoContext } from '@/components/demo/DemoProvider'; // Inside EhrDemoPage: +const demoCtx = useContext(DemoContext); <button type="button" + onClick={() => demoCtx?.setScene('fleet')} className="inline-flex items-center gap-2 rounded-lg bg-teal-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-teal-700" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/ehr-demo.tsx` around lines 269 - 276, The "View in Fleet" button in ehr-demo.tsx currently has no onClick handler; either wire it to the DemoContext by importing/using DemoContext (or useDemoContext hook) and call setScene('fleet') inside the button's onClick, or if this is intentional scaffolding add a clear TODO comment on the button explaining it's awaiting task-015/016 integration; locate the button element (the JSX button with className "inline-flex items-center gap-2 ..." and the ArrowRight icon) and update it accordingly to invoke DemoContext.setScene('fleet') or include the TODO.docs/designs/2026-03-15-deploy-ehr-demo.md-20-33 (1)
20-33:⚠️ Potential issue | 🟡 MinorLabel the ASCII diagram fence.
This block is the source of the current MD040 warning. Give it an explicit language such as
textso the design doc stays lint-clean.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/designs/2026-03-15-deploy-ehr-demo.md` around lines 20 - 33, The fenced ASCII diagram currently has an unlabeled code block causing MD040 lint warnings; update the triple-backtick fence that wraps the ASCII diagram so it includes an explicit language label (e.g., change ``` to ```text) to silence the MD040 rule and keep the design doc lint-clean; locate the diagram block in the deploy-ehr-demo design doc (the triple-backtick block containing "Internet" and the ASCII arrows) and add the language label to the opening fence.orchestration/AuthScript.AppHost/infra/resources.bicep-48-58 (1)
48-58:⚠️ Potential issue | 🟡 MinorApply the shared tags to the storage account too.
tagsis documented as applying to all resources, butstorageVolumeis the one top-level resource here that doesn't set it. That will break tag-based cost allocation and can trip tag-enforcement policies on the file-share backing store.🏷️ Minimal fix
resource storageVolume 'Microsoft.Storage/storageAccounts@2022-05-01' = { name: 'vol${resourceToken}' location: location + tags: tags kind: 'StorageV2' sku: { name: 'Standard_LRS' }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@orchestration/AuthScript.AppHost/infra/resources.bicep` around lines 48 - 58, The storage account resource 'storageVolume' is missing the shared tags, so add the shared tags property to its declaration: include a tags: tags entry inside the resource block for storageVolume (matching the project's shared tags variable/name used elsewhere) so the storageVolume inherits the same tag set as other resources and satisfies tag-based cost allocation and enforcement policies.docs/designs/2026-03-15-deploy-ehr-demo.md-153-165 (1)
153-165:⚠️ Potential issue | 🟡 MinorClarify the layer: the
.WithEnvironment()call belongs in AppHost, not Gateway Program.cs.The code snippet shows AppHost resource wiring (
WithEnvironment) but claims it's for "Gateway Program.cs". The Gateway actually reads CORS origins from configuration via theAddCorsPolicy()extension method (which usesservices.AddCors()and.WithOrigins()). The environment variable should be set inorchestration/AuthScript.AppHost/AppHost.cson the Gateway resource, not pasted into the Gateway's Program.cs. Clarify which file (AppHost or Gateway Program.cs) is being configured and align the code syntax with the actual implementation layer.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/designs/2026-03-15-deploy-ehr-demo.md` around lines 153 - 165, The snippet incorrectly places the AppHost-specific .WithEnvironment("AllowedOrigins", ...) in "Gateway Program.cs"; move the environment-variable wiring to the AppHost resource (orchestration/AuthScript.AppHost/AppHost.cs) where AppHost is created and configured, and update the docs to state that the Gateway reads CORS from configuration via AddCorsPolicy (which uses services.AddCors() and .WithOrigins()), so set the AllowedOrigins env var on AppHost (use AppHost's .WithEnvironment) and ensure Gateway Program.cs simply relies on configuration loaded by AddCorsPolicy/AddCors.apps/dashboard/src/components/ehr/useEhrDemoFlow.ts-83-86 (1)
83-86:⚠️ Potential issue | 🟡 MinorRemove ref mutation during render and use callback dependency instead.
Lines 85–86 mutate
onTransitionRef.currentduring the render phase, which violates React's rules. Instead, calloptions?.onTransitiondirectly intransitionToFleetwith it as a dependency:Fix
export function useEhrDemoFlow(options?: EhrDemoFlowOptions): EhrDemoFlow { const startState = options?.startState ?? 'idle'; - const onTransitionRef = useRef(options?.onTransition); - onTransitionRef.current = options?.onTransition; const [state, setState] = useState<EhrDemoState>(startState); @@ const transitionToFleet = useCallback(() => { - onTransitionRef.current?.(); - }, []); + options?.onTransition?.(); + }, [options?.onTransition]);This also applies to lines 214–216.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/ehr/useEhrDemoFlow.ts` around lines 83 - 86, The code mutates onTransitionRef.current during render in useEhrDemoFlow; remove that ref mutation and instead reference options?.onTransition directly (or capture it via a useCallback/useEffect dependency) where transitions occur — specifically update transitionToFleet (and the other transition functions around the area corresponding to lines 214–216) to call options?.onTransition passed in as a dependency or parameter so you don't assign to onTransitionRef during render; ensure any callback created with useCallback lists options?.onTransition in its dependency array.apps/dashboard/src/routes/fleet.tsx-6-6 (1)
6-6:⚠️ Potential issue | 🟡 MinorRemove the unused
usePAStats()hook and its import.The hook is called at line 82 but the result is discarded (assigned to
_graphqlStatswith a leading underscore), triggering linter warnings and making an unnecessary GraphQL request.✂️ Cleanup
-import { usePARequests, usePAStats, useActivity, type ActivityItem } from '@/api/graphqlService'; +import { usePARequests, useActivity, type ActivityItem } from '@/api/graphqlService';- const { data: _graphqlStats } = usePAStats();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/fleet.tsx` at line 6, The code calls usePAStats() and assigns its result to _graphqlStats but never uses it; remove the unused hook call and its import to stop the extra GraphQL request and linter warnings. Delete usePAStats from the import list (where usePARequests, useActivity, type ActivityItem are imported) and remove the line that calls usePAStats() (the assignment to _graphqlStats), ensuring no other references to _graphqlStats remain.
🧹 Nitpick comments (15)
apps/gateway/Gateway.API.Tests/Data/SeedDemoDataTests.cs (1)
94-107: This still doesn't prove repeatability.The test on Lines 98-106 only checks that the generated timestamps span roughly a week. It will still pass if two invocations produce different
CreatedAt,UpdatedAt,SubmittedAt, orServiceDatevalues. Once the live clock is removed fromSeedDemoData.GenerateSeedRequests(), add a second-call comparison so the determinism contract is actually covered.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/gateway/Gateway.API.Tests/Data/SeedDemoDataTests.cs` around lines 94 - 107, The test currently only checks timestamp span but not determinism; after removing any live-clock usage in SeedDemoData.GenerateSeedRequests(), call GenerateSeedRequests() a second time within SeedDemoData_TimestampsSpreadOverWeek and assert that the two result sets are identical for the timestamp fields (CreatedAt, UpdatedAt, SubmittedAt, ServiceDate) — e.g., compare the sequences element-by-element or by projecting those fields and asserting sequence equality (use the existing requests variable and a new requests2 from GenerateSeedRequests()) to ensure repeatability.apps/gateway/Gateway.API/Data/SeedDemoData.cs (1)
104-109: Keep the status rules in one place.
statusdrives confidence, criteria selection,ReadyAt, andSubmittedAtin separate branches here. The Template 4 comment on Lines 104-109 already diverges from the mapping on Line 159, which is a sign these rules are starting to drift. A small per-status config/strategy would make future changes much safer.As per coding guidelines, "SOLID: One public type per file, Strategy over switches, constructor injection".
Also applies to: 149-170
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/gateway/Gateway.API/Data/SeedDemoData.cs` around lines 104 - 109, Centralize the per-status rules used during demo data seeding (the logic that sets status, criteria selection, ReadyAt, SubmittedAt, and confidence) into a single strategy/config object instead of duplicating branches; create a small internal type (e.g., DemoStatusConfig or ClaimStatusStrategy) that maps each status to its criteria template, confidence, ReadyAt/SubmittedAt behavior and use that map from the seeding code (replace the separate branches that build Template 4 and the later status mapping logic). Update the seeder to look up the config for a given status and apply the unified rules so all status-related decisions live in one place; keep the new type internal to this file (or a single new file if public is required) and wire it into the existing seeding methods that currently reference status, criteria selection, ReadyAt and SubmittedAt.apps/dashboard/src/components/case/EvidenceNode.tsx (1)
25-25: Define a named props interface forEvidenceNodeinstead of inline intersection typing.This keeps the component aligned with the project TSX props standard and improves consistency with adjacent components.
♻️ Proposed refactor
export interface EvidenceNodeData { text: string; source: string; } +interface EvidenceNodeProps extends NodeProps { + data: EvidenceNodeData; +} + /** * Custom React Flow node displaying a piece of clinical evidence. * Layout: evidence text + small source badge (colored by type). */ -export function EvidenceNode({ data }: NodeProps & { data: EvidenceNodeData }) { +export function EvidenceNode({ data }: EvidenceNodeProps) { const sourceColor = SOURCE_COLORS[data.source] ?? 'bg-slate-100 text-slate-600';As per coding guidelines,
apps/dashboard/**/*.tsx: "Props: Define interface for props, use destructuring".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/EvidenceNode.tsx` at line 25, The EvidenceNode component currently uses an inline intersection type for its props (NodeProps & { data: EvidenceNodeData }); replace this with a named interface (e.g., interface EvidenceNodeProps extends NodeProps { data: EvidenceNodeData }) and update the component signature to use destructured props: function EvidenceNode({ data }: EvidenceNodeProps) so it follows the project's TSX props standard and matches adjacent components; ensure to export or keep visibility consistent with the original component.apps/dashboard/src/components/fleet/CasePipeline.tsx (1)
74-105: Consider adding accessibility attributes to stage buttons.The pipeline buttons control filtering state but lack
aria-pressedoraria-labelattributes. Screen reader users would benefit from knowing the current active state.Accessibility improvement
<button key={stage.key} data-testid={`pipeline-stage-${stage.key}`} onClick={() => onFilter(stage.key)} + aria-pressed={isActive} + aria-label={`${stage.label}: ${count} cases`} className={cn(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/fleet/CasePipeline.tsx` around lines 74 - 105, Add ARIA attributes to the pipeline stage button: set aria-pressed to the boolean isActive and add a descriptive aria-label (e.g., `Filter by {stage.label}, {count} cases` or similar) so screen readers announce the control and its active state; update the button rendered in CasePipeline (the <button key={stage.key} ...> element with data-testid={`pipeline-stage-${stage.key}`}) to include these attributes while keeping existing props and handlers (onClick, data-testid, className) intact.apps/dashboard/src/components/case/PatientNode.tsx (1)
17-22: Initials computation assumes non-empty name parts.
w[0]will beundefinedif a name contains consecutive spaces or is empty. For demo purposes with controlled data this is fine, but production code would benefit from filtering empty parts:const initials = data.name .split(' ') .filter(Boolean) .map((w) => w[0]) .join('') .toUpperCase() .slice(0, 2);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/PatientNode.tsx` around lines 17 - 22, The initials computation in PatientNode (const initials) assumes non-empty name parts and can produce undefined when data.name has consecutive spaces; update the logic that builds initials to filter out empty segments before mapping (i.e., call .split(' ') then .filter(Boolean) on the resulting array) so .map((w) => w[0]) only runs on non-empty words, then continue with .join('').toUpperCase().slice(0, 2) using data.name as the source.apps/dashboard/src/components/fleet/FleetView.tsx (1)
4-19: Tightenfiltertyping to the request status union.Line 6 uses
string | null, which permits invalid status values. Prefer typingfilterfromFleetPARequest['status']for stronger compile-time safety.Suggested typing refinement
interface FleetViewProps { requests: FleetPARequest[]; - filter: string | null; + filter: FleetPARequest['status'] | null; highlightedCaseId?: string; onSelectCase: (id: string) => void; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/fleet/FleetView.tsx` around lines 4 - 19, The filter prop is currently typed as string | null which allows invalid statuses; update FleetViewProps so filter is typed as FleetPARequest['status'] | null and adjust the FleetView parameter type accordingly so the existing visible calculation (const visible = filter ? requests.filter((r) => r.status === filter) : requests) remains type-safe; ensure any callers pass the narrowed union type or cast accordingly to satisfy the new FleetViewProps signature.apps/dashboard/src/components/demo/__tests__/DemoProvider.test.tsx (1)
8-21: RefactorrenderWithProviderto use a cleaner testing pattern.Consider using
renderHookwith a wrapper component instead of manually managing context capture. This approach is more idiomatic for testing hooks and avoids render-time side effects.Suggested refactor
-import { render, screen, act } from '@testing-library/react'; -import { createElement, useContext } from 'react'; +import { renderHook, act } from '@testing-library/react'; +import { useContext, type PropsWithChildren } from 'react'; function renderWithProvider(providerProps?: { autoPlay?: boolean }) { - let contextValue: DemoContextValue | null = null; - - function Consumer() { - contextValue = useContext(DemoContext); - if (!contextValue) throw new Error('DemoContext not found'); - return createElement('div', { 'data-testid': 'scene' }, contextValue.scene); - } - - const result = render( - createElement(DemoProvider, { ...providerProps, children: createElement(Consumer) }), - ); - - return { result, getContext: () => contextValue! }; + const Wrapper = ({ children }: PropsWithChildren) => ( + <DemoProvider {...providerProps}>{children}</DemoProvider> + ); + + const { result } = renderHook(() => { + const ctx = useContext(DemoContext); + if (!ctx) throw new Error('DemoContext not found'); + return ctx; + }, { wrapper: Wrapper }); + + return { getContext: () => result.current }; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/demo/__tests__/DemoProvider.test.tsx` around lines 8 - 21, The test helper renderWithProvider manually mounts a Consumer component to capture DemoContext via useContext (functions: renderWithProvider, Consumer, getContext, DemoProvider, DemoContextValue), which is fragile; refactor to use renderHook from testing-library/react with a wrapper that returns <DemoProvider {...providerProps}>{children}</DemoProvider> so you can call renderHook(() => useContext(DemoContext), { wrapper }) and access the typed context value directly, removing the manual Consumer and data-testid element and simplifying getContext to return the hook's result.current.apps/dashboard/src/components/ehr/ChartTabPanel.tsx (1)
5-8: Consider narrowingactiveTabto a union type.Using
stringforactiveTabis flexible but loses type safety. A union type would catch invalid tab values at compile time.♻️ Suggested improvement
+type ChartTab = 'problems' | 'medications' | 'allergies' | 'imaging' | 'vitals' | 'labs'; + interface ChartTabPanelProps { - activeTab: string; + activeTab: ChartTab; chartData: ChartData; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/ehr/ChartTabPanel.tsx` around lines 5 - 8, The prop type ChartTabPanelProps uses activeTab: string which loses type safety; define a TabKey union type (e.g., type TabKey = 'overview' | 'labs' | 'vitals' | 'meds' or whatever actual tab ids are used in this component set) and change ChartTabPanelProps.activeTab to that union, update the ChartTabPanel component and any callers to use the TabKey type so invalid tab values are caught at compile time; also export or reuse the TabKey where other tab components/readers expect the same keys to keep types consistent.docs/plans/2026-03-15-deploy-ehr-demo.md (1)
128-136: Optional: Add language specifiers to code blocks.Markdownlint flags lines 130 and 174 for missing language identifiers.
📝 Fix markdown lint warnings
- Add Dashboard to `build:containers` script: - ``` + ```bash docker build -f apps/dashboard/Dockerfile -t authscript-dashboard .And for the dependency graph (line 174), use `text` or leave as-is since ASCII art doesn't benefit from highlighting: ```diff -``` +```text Task 1 (GraphQL client) ──► Task 2 (Dockerfile) ──► Task 3 (AppHost)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/plans/2026-03-15-deploy-ehr-demo.md` around lines 128 - 136, Add explicit language identifiers to the two markdown code fences mentioned in the diff: mark the Docker build command block with "bash" (the one showing docker build -f apps/dashboard/Dockerfile -t authscript-dashboard .) and mark the dependency graph ASCII art block with "text" (or leave as-is if preferred), so markdownlint warnings for missing language identifiers are resolved; update the code fences surrounding the Docker command and the dependency graph accordingly in the deployment plan document.apps/dashboard/src/routes/__tests__/case.test.tsx (1)
42-47: Avoidas anyfor module access.♻️ Type the dynamic import
async function renderCaseDetailPage() { const mod = await import('../case.$caseId'); - // The module exports CaseDetailPage as the component - const Component = (mod as any).CaseDetailPage; + const Component = (mod as { CaseDetailPage: React.ComponentType }).CaseDetailPage; return render(createElement(Component)); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/__tests__/case.test.tsx` around lines 42 - 47, Replace the unsafe "as any" cast in renderCaseDetailPage by typing the dynamic import to the module shape: assert the imported module as the proper module type (e.g., use "as typeof import('../case.$caseId')" or define an interface like "{ CaseDetailPage: ComponentType }" and cast the import to that) and then pull CaseDetailPage from that typed module; update the renderCaseDetailPage function to use the typed import instead of "(mod as any).CaseDetailPage" so TypeScript knows the component type.orchestration/AuthScript.AppHost/infra/dashboard.tmpl.yaml (1)
12-14: Remove .NET runtime configuration from static frontend.The dashboard is an nginx-based static frontend container. The
dotnet.autoConfigureDataProtectionsetting is irrelevant and can be removed.Proposed fix
configuration: activeRevisionsMode: single - runtime: - dotnet: - autoConfigureDataProtection: true ingress: external: true🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@orchestration/AuthScript.AppHost/infra/dashboard.tmpl.yaml` around lines 12 - 14, Remove the irrelevant .NET runtime configuration from the static nginx frontend template by deleting the runtime: dotnet: autoConfigureDataProtection entry in dashboard.tmpl.yaml; specifically remove the autoConfigureDataProtection key and its parent dotnet/runtime stanza so the static frontend YAML no longer contains .NET-specific settings and remains valid YAML.orchestration/AuthScript.AppHost/infra/postgres.tmpl.yaml (1)
12-14: Remove irrelevant .NET runtime configuration.The
dotnet.autoConfigureDataProtectionsetting is unnecessary for a Postgres container image — it only applies to ASP.NET Core applications. This appears to be copy-paste from other service templates.Proposed fix
configuration: activeRevisionsMode: single - runtime: - dotnet: - autoConfigureDataProtection: true ingress: external: false🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@orchestration/AuthScript.AppHost/infra/postgres.tmpl.yaml` around lines 12 - 14, Remove the irrelevant .NET runtime configuration by deleting the runtime.dotnet.autoConfigureDataProtection entry from the template; if the runtime.dotnet block becomes empty after removal, delete the entire runtime.dotnet (or runtime) block to avoid leaving an empty section in the Postgres container template (target the runtime and dotnet keys shown in the diff).docs/plans/2026-03-15-pa-dashboard-demo.md (1)
9-28: Add language specifiers to fenced code blocks.The ASCII dependency diagrams should specify a language (e.g.,
textorplaintext) to satisfy markdownlint and improve accessibility.Proposed fix
-``` +```text Group 1 (Foundation) ─── sequentialApply similarly to the diagram at line 22 and the parallelization summary at line 561.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/plans/2026-03-15-pa-dashboard-demo.md` around lines 9 - 28, The fenced ASCII diagrams currently use unmarked code fences; update each relevant fence to include a language specifier (e.g., change ``` to ```text or ```plaintext) for the block that begins with "Group 1 (Foundation) ─── sequential" and the block that begins with "task-001 ──► task-002 ──► task-003 ──┬──► ..." as well as the parallelization summary block referenced in the review, so markdownlint passes and screen readers treat them as plain text.apps/dashboard/src/components/case/CaseGraph.tsx (1)
36-39: MovebuildCaseGraphDatainto a sibling.tsmodule.Line 36 is what triggers the current Fast Refresh warning: this
.tsxfile now exports non-component helpers alongsideCaseGraph. Pull the graph builder/status helpers into a separate module so tests can still import them without disabling React refresh for the component file.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/CaseGraph.tsx` around lines 36 - 39, buildCaseGraphData is a non-component helper exported from the CaseGraph .tsx which triggers Fast Refresh warnings; move that function (and any related helpers for graph building/status) into a new sibling .ts module, export it there using the same signature (buildCaseGraphData(paRequest: PARequest): { nodes: Node[]; edges: Edge[] }), then update CaseGraph to import buildCaseGraphData (and any moved helpers) from the new module and update tests to import the helper from the new .ts file so the .tsx file only exports React components.orchestration/AuthScript.AppHost/infra/resources.bicep (1)
18-25: Verify whether the registry really needs the default public posture.This ACR is only being introduced for internal build/pull flows, but the template leaves networking/scanning/quarantine at defaults. If you're not planning private networking yet, at least confirm the team is comfortable with a publicly reachable Basic registry for the demo environment.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@orchestration/AuthScript.AppHost/infra/resources.bicep` around lines 18 - 25, The ACR resource (resource containerRegistry with name replace('acr-${resourceToken}', '-', '') and sku 'Basic') is left with default public networking and policy posture; confirm with the team whether a publicly reachable Basic registry is acceptable for the demo, and if not update the template to lock it down: replace or augment the containerRegistry definition to enforce private access (add networkRuleSet / private endpoints or disable public network access), enable appropriate image scanning/quarantine policies under policies (quarantinePolicy/vulnerabilityAssessment) and/or bump sku if required for private endpoints, or add a short comment documenting the team's acceptance of the current public defaults.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8f7da362-b318-420c-81b4-e68da7a36d0f
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json,!**/package-lock.json
📒 Files selected for processing (77)
apps/dashboard/Dockerfileapps/dashboard/nginx.confapps/dashboard/package.jsonapps/dashboard/src/api/graphqlClient.tsapps/dashboard/src/components/case/AnimatedEdge.tsxapps/dashboard/src/components/case/CaseGraph.tsxapps/dashboard/src/components/case/CaseTimeline.tsxapps/dashboard/src/components/case/CriteriaNode.tsxapps/dashboard/src/components/case/DecisionNode.tsxapps/dashboard/src/components/case/EvidenceNode.tsxapps/dashboard/src/components/case/NodeCard.tsxapps/dashboard/src/components/case/PatientNode.tsxapps/dashboard/src/components/case/__tests__/AnimatedEdge.test.tsxapps/dashboard/src/components/case/__tests__/CaseGraph.test.tsxapps/dashboard/src/components/case/__tests__/CaseTimeline.test.tsxapps/dashboard/src/components/case/__tests__/CustomNodes.test.tsxapps/dashboard/src/components/demo/DemoProvider.tsxapps/dashboard/src/components/demo/SceneNav.tsxapps/dashboard/src/components/demo/SceneTransition.tsxapps/dashboard/src/components/demo/__tests__/DemoProvider.test.tsxapps/dashboard/src/components/demo/__tests__/SceneNav.test.tsxapps/dashboard/src/components/demo/__tests__/SceneTransition.test.tsxapps/dashboard/src/components/demo/transitionVariants.tsapps/dashboard/src/components/ehr/AuthDetectionBanner.tsxapps/dashboard/src/components/ehr/ChartTabPanel.tsxapps/dashboard/src/components/ehr/EhrHeader.tsxapps/dashboard/src/components/ehr/EncounterSidebar.tsxapps/dashboard/src/components/ehr/__tests__/AuthDetectionBanner.test.tsxapps/dashboard/src/components/ehr/__tests__/ChartTabPanel.test.tsxapps/dashboard/src/components/ehr/__tests__/EhrHeader.test.tsxapps/dashboard/src/components/ehr/__tests__/EncounterSidebar.test.tsxapps/dashboard/src/components/ehr/__tests__/useEhrDemoFlow.test.tsapps/dashboard/src/components/ehr/useEhrDemoFlow.tsapps/dashboard/src/components/fleet/CasePipeline.tsxapps/dashboard/src/components/fleet/FleetCard.tsxapps/dashboard/src/components/fleet/FleetView.tsxapps/dashboard/src/components/fleet/KPICards.tsxapps/dashboard/src/components/fleet/__tests__/CasePipeline.test.tsxapps/dashboard/src/components/fleet/__tests__/FleetCard.test.tsxapps/dashboard/src/components/fleet/__tests__/FleetView.test.tsxapps/dashboard/src/components/fleet/__tests__/KPICards.test.tsxapps/dashboard/src/lib/__tests__/demoData.test.tsapps/dashboard/src/lib/__tests__/fleetSeedData.test.tsapps/dashboard/src/lib/demoData.tsapps/dashboard/src/lib/fleetSeedData.tsapps/dashboard/src/routeTree.gen.tsapps/dashboard/src/routes/__tests__/case.test.tsxapps/dashboard/src/routes/__tests__/ehr-demo.test.tsxapps/dashboard/src/routes/__tests__/fleet.test.tsxapps/dashboard/src/routes/case.$caseId.tsxapps/dashboard/src/routes/ehr-demo.tsxapps/dashboard/src/routes/fleet.tsxapps/gateway/Gateway.API.Tests/Data/SeedDemoDataTests.csapps/gateway/Gateway.API/Data/SeedDemoData.csdocs/designs/2026-03-15-deploy-ehr-demo.mddocs/designs/2026-03-15-pa-dashboard-demo.mddocs/market-research/palantir-dashboard.mddocs/plans/2026-03-15-deploy-ehr-demo.mddocs/plans/2026-03-15-pa-dashboard-demo.mdorchestration/AuthScript.AppHost/.gitignoreorchestration/AuthScript.AppHost/AppHost.csorchestration/AuthScript.AppHost/AuthScript.AppHost.csprojorchestration/AuthScript.AppHost/azure.yamlorchestration/AuthScript.AppHost/infra/dashboard.tmpl.yamlorchestration/AuthScript.AppHost/infra/dashboard/Dockerfileorchestration/AuthScript.AppHost/infra/gateway.tmpl.yamlorchestration/AuthScript.AppHost/infra/intelligence.tmpl.yamlorchestration/AuthScript.AppHost/infra/intelligence/Dockerfileorchestration/AuthScript.AppHost/infra/main.biceporchestration/AuthScript.AppHost/infra/main.parameters.jsonorchestration/AuthScript.AppHost/infra/postgres.tmpl.yamlorchestration/AuthScript.AppHost/infra/redis.tmpl.yamlorchestration/AuthScript.AppHost/infra/resources.biceporchestration/AuthScript.AppHost/manifest.jsonorchestration/AuthScript.AppHost/next-steps.mdpackage.jsonscripts/azd-sync-secrets.sh
| FROM docker.io/nginx:alpine | ||
|
|
||
| # Copy built SPA assets | ||
| COPY --from=build /app/apps/dashboard/dist /usr/share/nginx/html | ||
|
|
||
| # Copy nginx config template (envsubst resolves $GATEWAY_URL at startup) | ||
| COPY apps/dashboard/nginx.conf /etc/nginx/templates/default.conf.template | ||
|
|
||
| EXPOSE 80 | ||
|
|
||
| HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ | ||
| CMD wget -q --spider http://localhost/health || exit 1 |
There was a problem hiding this comment.
Harden runtime image to avoid running as root.
The final stage lacks a USER directive, and scanner DS-0002 flags this. Please switch to an unprivileged nginx image/user and align exposed/healthcheck port accordingly.
Suggested hardening baseline
-FROM docker.io/nginx:alpine
+FROM docker.io/nginxinc/nginx-unprivileged:stable-alpine
# Copy built SPA assets
COPY --from=build /app/apps/dashboard/dist /usr/share/nginx/html
# Copy nginx config template (envsubst resolves $GATEWAY_URL at startup)
COPY apps/dashboard/nginx.conf /etc/nginx/templates/default.conf.template
-EXPOSE 80
+EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
- CMD wget -q --spider http://localhost/health || exit 1
+ CMD wget -q --spider http://localhost:8080/health || exit 1Also ensure nginx is configured to listen on 8080 to match this change.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| FROM docker.io/nginx:alpine | |
| # Copy built SPA assets | |
| COPY --from=build /app/apps/dashboard/dist /usr/share/nginx/html | |
| # Copy nginx config template (envsubst resolves $GATEWAY_URL at startup) | |
| COPY apps/dashboard/nginx.conf /etc/nginx/templates/default.conf.template | |
| EXPOSE 80 | |
| HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ | |
| CMD wget -q --spider http://localhost/health || exit 1 | |
| FROM docker.io/nginxinc/nginx-unprivileged:stable-alpine | |
| # Copy built SPA assets | |
| COPY --from=build /app/apps/dashboard/dist /usr/share/nginx/html | |
| # Copy nginx config template (envsubst resolves $GATEWAY_URL at startup) | |
| COPY apps/dashboard/nginx.conf /etc/nginx/templates/default.conf.template | |
| EXPOSE 8080 | |
| HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ | |
| CMD wget -q --spider http://localhost:8080/health || exit 1 |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/Dockerfile` around lines 23 - 34, The final Dockerfile stage
runs nginx as root and must be hardened by switching to an unprivileged runtime
user and adjusting ports; update the Dockerfile to run nginx under a non-root
user (e.g., USER nginx or create a low-privilege user and set appropriate
ownership on /usr/share/nginx/html), change EXPOSE 80 to EXPOSE 8080, and update
the HEALTHCHECK to probe http://localhost:8080/health; also update the nginx
config template referenced by COPY apps/dashboard/nginx.conf (ensure the server
listen directive uses 8080) so nginx actually listens on the new port.
| const reset = useCallback(() => { | ||
| cancelledRef.current = true; | ||
| setState('idle'); | ||
| setState(startState); | ||
| setPaRequest(null); | ||
| setPreCheckCriteria(null); | ||
| setEncounter(DEMO_ENCOUNTER_BASE); | ||
| setDocState('idle'); | ||
| setInsertedHpiText(null); | ||
| setError(null); | ||
| }, [startState]); | ||
|
|
||
| // --- Enhanced flow actions --- | ||
|
|
||
| const addOrder = useCallback(() => { | ||
| if (state !== 'chart-browsing') return; | ||
| setState('order-entry'); | ||
|
|
||
| // Auto-transition to pa-detected after delay | ||
| setTimeout(() => { | ||
| setState('pa-detected'); | ||
| }, ORDER_ENTRY_DELAY_MS); | ||
| }, [state]); |
There was a problem hiding this comment.
addOrder() can resurrect the flow after reset.
The delayed setState('pa-detected') is never cancelled. If the user resets during the 1.2s order-entry delay, the stale timeout will still fire and push the state back into pa-detected. Store the timer id and clear it from reset() and cleanup.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/ehr/useEhrDemoFlow.ts` around lines 186 - 207,
The delayed transition in addOrder() can fire after reset; store the timer id in
a ref (e.g., orderTimerRef) when calling setTimeout(…, ORDER_ENTRY_DELAY_MS)
inside addOrder(), and in reset() call clearTimeout(orderTimerRef.current) and
set orderTimerRef.current = null; also clear that timer in the component cleanup
(useEffect return) to prevent any stale setState after unmount; optionally also
guard the timeout callback by checking cancelledRef.current before calling
setState('pa-detected') for extra safety.
| export const Route = createFileRoute('/case/$caseId')({ | ||
| component: CaseDetailPage, | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and examine the file
find . -path "*/apps/dashboard/src/routes/case.$caseId.tsx" -type fRepository: lvlup-sw/authscript
Length of output: 45
🏁 Script executed:
# Read the file to see full implementation
cat -n apps/dashboard/src/routes/case.\$caseId.tsxRepository: lvlup-sw/authscript
Length of output: 11330
🏁 Script executed:
# Search for DEMO_PA_RESULT usage in the codebase
rg "DEMO_PA_RESULT" --type tsx --type tsRepository: lvlup-sw/authscript
Length of output: 90
🏁 Script executed:
# Look for useParams or route parameter access in case detail files
rg "useParams|useRoute|\$caseId|getCaseId" apps/dashboard/src/routes/ --type tsx --type ts -A 3Repository: lvlup-sw/authscript
Length of output: 90
The detail route ignores $caseId, rendering only demo data.
CaseDetailPage hardcodes const paRequest = DEMO_PA_RESULT (line 126), so /case/1 and /case/48 display identical data. Once fleet navigation is wired, users will see the same record regardless of which case they select.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/routes/case`.$caseId.tsx around lines 13 - 15, The
CaseDetailPage is using a hardcoded const paRequest = DEMO_PA_RESULT so the
Route export (createFileRoute('/case/$caseId') with component: CaseDetailPage)
ignores the $caseId param; change the flow to read the route param and load real
data instead of demo data—either add a loader that reads params.caseId and
fetches the case (and return it to the component) or update CaseDetailPage to
call useParams() (or accept a prop) and fetch the record by caseId, replacing
DEMO_PA_RESULT with the fetched paRequest; ensure the loader/params key names
match the createFileRoute param ($caseId) and that CaseDetailPage consumes the
provided data.
| const handlePipelineFilter = (stage: string) => { | ||
| // Map pipeline stages to fleet filter statuses | ||
| const stageToStatus: Record<string, string | null> = { | ||
| order_signed: null, | ||
| pa_detected: null, | ||
| processing: 'processing', | ||
| ready: 'ready', | ||
| submitted: 'submitted', | ||
| payer_response: null, | ||
| }; | ||
| const status = stageToStatus[stage]; | ||
| if (status) { | ||
| setActiveFilter((prev) => (prev === status ? null : status)); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Half of the pipeline buttons are currently inert.
order_signed, pa_detected, and payer_response map to null, and handlePipelineFilter() only updates state when the mapped value is truthy. Those stages can never become active or change the fleet view, even though CasePipeline renders them as clickable filters. Keep pipeline-stage selection separate from the single-status filter, or derive grouped request subsets before passing data to FleetView.
Also applies to: 164-167
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/routes/fleet.tsx` around lines 107 - 121, The handler
handlePipelineFilter currently maps several pipeline stages to null in
stageToStatus so stages like order_signed, pa_detected, and payer_response never
activate; change the logic to manage pipeline-stage selection separately from
the single-status filter by introducing/using a pipelineStage state (or toggle
behavior) instead of mapping to null, e.g., have handlePipelineFilter toggle
pipelineStage for any stage (and only setActiveFilter when a stage maps to a
real status like 'processing'|'ready'|'submitted'), and ensure CasePipeline
renders communicate the selected pipelineStage to FleetView (or pre-filter data
into FleetView) so those stages become selectable and affect the fleet view;
also apply the same fix to the analogous logic referenced around the other
occurrence (the block that mirrors lines 164-167).
| <FleetView | ||
| requests={fleetData} | ||
| filter={activeFilter} | ||
| onSelectCase={() => { | ||
| // Will be wired to case detail navigation in future task | ||
| }} | ||
| /> |
There was a problem hiding this comment.
Case selection never reaches scene 3.
FleetView exposes onSelectCase, but this handler is a no-op. That leaves the new /case/$caseId route unreachable from the grid, so the command-center scene cannot drill into the case-detail scene.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/routes/fleet.tsx` around lines 191 - 197, The onSelectCase
prop passed to FleetView is a no-op, preventing navigation into the case detail
route; replace the empty handler in the FleetView call with a function that
accepts the selected case id and navigates to the case route (e.g., use
react-router's useNavigate or history.push to go to `/case/${caseId}`), so
update the FleetView invocation in fleet.tsx to call navigate(`/case/${caseId}`)
(or the project's routing helper) inside the handler to open the case-detail
scene.
| public static List<PARequestModel> GenerateSeedRequests() | ||
| { | ||
| var rng = new Random(42); // deterministic seed | ||
| var now = DateTimeOffset.UtcNow; |
There was a problem hiding this comment.
The generator still depends on wall clock and host culture.
Line 132 uses DateTimeOffset.UtcNow, and Line 180 formats ServiceDate with the process culture. Two runs on different days or different locales will therefore emit different payloads even though this helper is documented as deterministic. Please freeze a reference instant for demo data, or move the clock behind an injected dependency, and format ServiceDate with CultureInfo.InvariantCulture.
Also applies to: 180-180
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/gateway/Gateway.API/Data/SeedDemoData.cs` around lines 129 - 132,
GenerateSeedRequests currently uses DateTimeOffset.UtcNow and formats
PARequestModel.ServiceDate with the ambient culture, causing non-deterministic
outputs; change it to use a frozen reference instant (e.g., a fixed
DateTimeOffset constant like DemoDataEpoch) or accept an injected
IClock/DateTimeProvider into GenerateSeedRequests so the clock is controllable
for tests, and update any ServiceDate.ToString(...) calls to use
CultureInfo.InvariantCulture when serializing/formatting ServiceDate; ensure
Random remains seeded (Random(42)) so only the time/culture dependency is
removed.
| FROM docker.io/nginx:alpine | ||
|
|
||
| # Copy built SPA assets | ||
| COPY --from=build /app/apps/dashboard/dist /usr/share/nginx/html | ||
|
|
||
| # Copy nginx config template (envsubst resolves $GATEWAY_URL at startup) | ||
| COPY apps/dashboard/nginx.conf /etc/nginx/templates/default.conf.template | ||
|
|
||
| EXPOSE 80 | ||
|
|
||
| HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ | ||
| CMD wget -q --spider http://localhost/health || exit 1 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify runtime user declaration and port consistency
rg -n '^\s*USER\b' orchestration/AuthScript.AppHost/infra/dashboard/Dockerfile
rg -n '^\s*EXPOSE\b' orchestration/AuthScript.AppHost/infra/dashboard/Dockerfile
rg -n '^\s*listen\s+' apps/dashboard/nginx.confRepository: lvlup-sw/authscript
Length of output: 92
🏁 Script executed:
cat apps/dashboard/nginx.confRepository: lvlup-sw/authscript
Length of output: 891
🏁 Script executed:
cat orchestration/AuthScript.AppHost/infra/dashboard/DockerfileRepository: lvlup-sw/authscript
Length of output: 1214
Add non-root USER to run nginx unprivileged.
The image runs as root by default (no USER directive), which is a security gap. The nginx Alpine image includes a built-in nginx user. Apply the suggested hardening:
- Add
USER nginxbefore EXPOSE - Change
EXPOSE 80toEXPOSE 8080(non-root cannot bind ports < 1024) - Update
apps/dashboard/nginx.confline 2: changelisten 80;tolisten 8080; - Update HEALTHCHECK to query port 8080:
http://localhost:8080/health
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@orchestration/AuthScript.AppHost/infra/dashboard/Dockerfile` around lines 23
- 34, Run the nginx process as the non-root nginx user and switch the container
to a non-privileged port: add a USER nginx directive in the Dockerfile before
EXPOSE, change EXPOSE 80 to EXPOSE 8080, and update the HEALTHCHECK in the
Dockerfile to hit http://localhost:8080/health; also update the nginx config
file apps/dashboard/nginx.conf (the listen directive on line 2) to use listen
8080; ensure these three changes are applied together so the container runs
unprivileged and nginx binds to 8080.
| @secure() | ||
| param athena_client_id string | ||
| @secure() | ||
| param athena_client_secret string | ||
| param athena_practice_id string | ||
| param azure_openai_endpoint string | ||
| @secure() | ||
| param azure_openai_key string | ||
| @secure() | ||
| param github_token string | ||
| @secure() | ||
| param google_api_key string | ||
| param llm_provider string | ||
| @secure() | ||
| param openai_api_key string | ||
| @secure() | ||
| param openai_org_id string | ||
| @metadata({azd: { | ||
| type: 'generate' | ||
| config: {length:22} | ||
| } | ||
| }) | ||
| @secure() | ||
| param postgres_password string | ||
| @metadata({azd: { | ||
| type: 'generate' | ||
| config: {length:22,noSpecial:true} | ||
| } | ||
| }) | ||
| @secure() | ||
| param redis_password string |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check what parameters resources.bicep actually expects
echo "=== Parameters declared in resources.bicep ==="
rg -n "^param " orchestration/AuthScript.AppHost/infra/resources.bicep || echo "resources.bicep not found or no params"
echo ""
echo "=== Check if secrets are wired elsewhere (e.g., Container Apps secrets) ==="
rg -l "postgres_password|redis_password|openai_api_key|athena_client" orchestration/AuthScript.AppHost/infra/Repository: lvlup-sw/authscript
Length of output: 404
🏁 Script executed:
# First, let's see the full main.bicep file to check if these parameters are used anywhere
wc -l orchestration/AuthScript.AppHost/infra/main.bicepRepository: lvlup-sw/authscript
Length of output: 115
🏁 Script executed:
# Check the full main.bicep to find any references to the secure parameters
cat orchestration/AuthScript.AppHost/infra/main.bicepRepository: lvlup-sw/authscript
Length of output: 2759
🏁 Script executed:
# Also check what's in main.parameters.json
cat orchestration/AuthScript.AppHost/infra/main.parameters.jsonRepository: lvlup-sw/authscript
Length of output: 1415
Secure parameters declared but not used anywhere in the template.
Lines 15-45 declare the following parameters:
athena_client_id,athena_client_secret,athena_practice_idazure_openai_endpoint,azure_openai_keygithub_token,google_api_key,llm_provideropenai_api_key,openai_org_idpostgres_password,redis_password
These are provided via main.parameters.json but are neither referenced in main.bicep nor passed to the resources module (lines 56-64). Since resources.bicep only accepts location, tags, and principalId, either these parameters need to be forwarded to that module (if the resources require them for Container Apps secrets), or they should be removed as dead code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@orchestration/AuthScript.AppHost/infra/main.bicep` around lines 15 - 45, The
declared secure parameters (athena_client_id, athena_client_secret,
athena_practice_id, azure_openai_endpoint, azure_openai_key, github_token,
google_api_key, llm_provider, openai_api_key, openai_org_id, postgres_password,
redis_password) are not used; either forward them into the resources module
invocation or remove them: if resources.bicep needs these as Container App
secrets, add matching params to resources.bicep and pass them from the
main.bicep module call (update the module call that currently passes location,
tags, principalId to also pass these parameter names), otherwise delete the
unused `@secure`() param declarations from main.bicep (and from
main.parameters.json) to eliminate dead code.
Navigation bug: - Move DemoProvider to __root.tsx layout (shared across routes) - Remove per-route DemoProvider wrappers from ehr-demo, fleet, case - Wire SceneNav to TanStack Router (useNavigate + useLocation) - Sync active scene pill from current URL pathname Animations: - KPI count-up animation via motion animate() with 800ms easeOut + stagger - FleetCard/FleetView motion.div wrappers with layout + stagger animations - CaseGraph sequential node reveal (Patient→Evidence→Criteria→Decision) - AuthDetectionBanner replaced CSS animation with motion AnimatePresence Accessibility: - FleetCard: role="button", tabIndex, keyboard handler (Enter/Space) - CasePipeline: aria-label on container, aria-hidden on decorative SVG - CaseTimeline: aria-labels on phase indicators Code quality: - Extract getInitials() and getConfidenceColor() to shared formatUtils.ts - Replace magic string 'LCD L34220' with LCD_L34220_POLICY.policyId - Rename misleading _c to criterion in CaseGraph - Deep-clone criteria templates in SeedDemoData.cs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
♻️ Duplicate comments (1)
apps/gateway/Gateway.API/Data/SeedDemoData.cs (1)
132-132:⚠️ Potential issue | 🟠 MajorMake seed output deterministic across runs/locales.
Line 132 uses wall clock time, and Line 180 uses ambient culture formatting; both make output vary by execution date/machine locale, which breaks deterministic seed behavior.
Proposed fix
namespace Gateway.API.Data; +using System.Globalization; using Gateway.API.GraphQL.Models; @@ public static class SeedDemoData { + private static readonly DateTimeOffset DemoDataEpoch = new(2025, 01, 15, 12, 0, 0, TimeSpan.Zero); @@ public static List<PARequestModel> GenerateSeedRequests() { var rng = new Random(42); // deterministic seed - var now = DateTimeOffset.UtcNow; + var now = DemoDataEpoch; @@ - var serviceDate = now.AddDays(rng.Next(1, 30)).ToString("MMMM d, yyyy"); + var serviceDate = now.AddDays(rng.Next(1, 30)).ToString("MMMM d, yyyy", CultureInfo.InvariantCulture);Also applies to: 180-180
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/gateway/Gateway.API/Data/SeedDemoData.cs` at line 132, The seed uses DateTimeOffset.UtcNow (variable now) and ambient culture formatting which makes output non-deterministic; replace the dynamic now with a fixed, hard-coded DateTimeOffset constant (e.g., new DateTimeOffset(2023,1,1,0,0,0,TimeSpan.Zero) or a chosen epoch) used throughout SeedDemoData, and change any ToString/format calls (the formatting at the later use around line with culture-dependent output) to use explicit formatting with CultureInfo.InvariantCulture (e.g., .ToString("o", CultureInfo.InvariantCulture)) so the seed output is deterministic across runs and locales.
🧹 Nitpick comments (8)
apps/dashboard/src/components/fleet/CasePipeline.tsx (2)
78-85: Consider adding explicittype="button".While buttons default to
type="submit"only within forms, explicitly settingtype="button"is a defensive practice that prevents accidental form submissions if this component is ever composed within a form context.Proposed diff
<button key={stage.key} + type="button" data-testid={`pipeline-stage-${stage.key}`} onClick={() => onFilter(stage.key)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/fleet/CasePipeline.tsx` around lines 78 - 85, The button rendered in the CasePipeline component (the element using key={stage.key}, data-testid={`pipeline-stage-${stage.key}`} and onClick={() => onFilter(stage.key)}) should include an explicit type attribute to avoid accidental form submissions; add type="button" to that <button> element (the one also using isActive and cn(...) for className) so it behaves as a non-submitting button when composed inside forms.
29-71: SVG animation approach works but has a minor consideration.The inline
<style>block defining theflowDotkeyframe works fine for a single instance. If multipleCasePipelinecomponents are rendered simultaneously, the keyframe definition duplicates—browsers handle this gracefully, but it's slightly inefficient.For a demo component this is acceptable. If you ever need to scale this, consider moving the keyframe to a global CSS file or Tailwind's
@layer utilities.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/fleet/CasePipeline.tsx` around lines 29 - 71, The inline `@keyframes` flowDot inside the CasePipeline component's SVG causes duplicate keyframe definitions when multiple CasePipeline instances render; move the flowDot keyframe out of the component into a shared stylesheet (e.g., global CSS or Tailwind `@layer` utilities) and remove the <style> block from the SVG so the SVG's animated line still uses style={{ animation: 'flowDot 1.5s linear infinite' }} but relies on the globally defined keyframes.apps/gateway/Gateway.API/Data/SeedDemoData.cs (1)
155-161: Prefer strategy dispatch over status switch.This status-based switch is clear, but a strategy map (status → template selector) will scale better as statuses evolve and aligns with project standards.
Refactor sketch
+private static readonly Dictionary<string, Func<Random, CriterionModel[]>> CriteriaTemplateByStatus = new(StringComparer.Ordinal) +{ + ["denied"] = _ => CriteriaTemplates[2], + ["approved"] = rng => rng.Next(2) == 0 ? CriteriaTemplates[0] : CriteriaTemplates[3], + ["processing"] = _ => CriteriaTemplates[4], +}; @@ - var criteriaTemplate = status switch - { - "denied" => CriteriaTemplates[2], - "approved" => rng.Next(2) == 0 ? CriteriaTemplates[0] : CriteriaTemplates[3], - "processing" => CriteriaTemplates[4], - _ => rng.Next(3) == 0 ? CriteriaTemplates[1] : CriteriaTemplates[0], - }; + var criteriaTemplate = CriteriaTemplateByStatus.TryGetValue(status, out var selector) + ? selector(rng) + : (rng.Next(3) == 0 ? CriteriaTemplates[1] : CriteriaTemplates[0]);As per coding guidelines,
apps/gateway/**/*.csshould follow: “SOLID: … Strategy over switches …”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/gateway/Gateway.API/Data/SeedDemoData.cs` around lines 155 - 161, Replace the status switch that assigns criteriaTemplate with a strategy dispatch: create a dictionary/map from status (string) to a selector function that takes rng and returns the chosen item from CriteriaTemplates, then look up the selector for the current status and call it (falling back to a default selector when missing). Update the code that currently uses the switch (variable criteriaTemplate, status, rng, and CriteriaTemplates) to use that map (e.g., selectors.TryGetValue(status, out selector) ? selector(rng) : defaultSelector(rng)) so new statuses can be added by registering new selector entries instead of editing a switch.apps/dashboard/src/routes/fleet.tsx (3)
81-81: Remove unused_graphqlStatsfetch.The
usePAStatshook is called but the result is never used. This creates an unnecessary network request every 5 seconds (per the hook's refetch interval).♻️ Proposed fix
// GraphQL data (may not be available) const { data: graphqlRequests } = usePARequests(); - const { data: _graphqlStats } = usePAStats(); const { data: activity = [] } = useActivity();🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/fleet.tsx` at line 81, The code calls the hook usePAStats and assigns its result to _graphqlStats but never uses it, causing unnecessary periodic fetches; remove the unused call to usePAStats (and remove its import if now unused) from the file so no background refetch occurs—search for the usePAStats invocation and the _graphqlStats variable in fleet.tsx and delete that line (and clean up the import statement for usePAStats if orphaned).
85-94: Consider validating GraphQL status values.The direct cast
r.status as FleetStatus(line 90) assumes all GraphQL statuses besides'draft'map toFleetStatus. If the backend introduces new statuses, this could produce invalidFleetStatusvalues silently.For a demo, this is acceptable. For production, consider a validation function that maps unknown statuses to a default (e.g.,
'processing').🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/fleet.tsx` around lines 85 - 94, The current mapping in the useMemo for fleetData unsafely casts r.status to FleetStatus which can produce invalid values if backend adds new statuses; create a small validator/mapper function (e.g., mapGraphqlStatusToFleetStatus) that accepts the GraphQL status from graphqlRequests and returns a validated FleetStatus (explicitly handle known statuses and return a default like 'processing' for unknowns), then use that mapper in the useMemo when creating FleetPARequest entries instead of the direct cast; update references in the mapping logic and keep generateFleetData as the fallback.
26-30: Redundant status check.The
r.status !== 'waiting_for_insurance'check is unnecessary since'waiting_for_insurance' in countsis alreadyfalse. Not blocking, but could simplify.♻️ Simplified version
for (const r of requests) { - if (r.status in counts && r.status !== 'waiting_for_insurance') { + if (r.status in counts) { counts[r.status as Exclude<FleetStatus, 'waiting_for_insurance'>] += 1; } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/fleet.tsx` around lines 26 - 30, The loop over requests has a redundant check — remove the unnecessary r.status !== 'waiting_for_insurance' condition inside the for (const r of requests) loop and only rely on the existence check if (r.status in counts) before incrementing counts; keep the explicit cast counts[r.status as Exclude<FleetStatus, 'waiting_for_insurance'>] += 1 to satisfy the type system while simplifying the conditional.apps/dashboard/src/routes/__tests__/case.test.tsx (1)
44-49: Consider adding type safety to the dynamic import.The
(mod as any).CaseDetailPageassertion loses type safety. A minor improvement would be to type the expected exports.♻️ Proposed fix
async function renderCaseDetailPage() { - const mod = await import('../case.$caseId'); - // The module exports CaseDetailPage as the component - const Component = (mod as any).CaseDetailPage; + const mod = await import('../case.$caseId') as { CaseDetailPage: React.ComponentType }; + const Component = mod.CaseDetailPage; return render(createElement(DemoProvider, null, createElement(Component))); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/routes/__tests__/case.test.tsx` around lines 44 - 49, The dynamic import in renderCaseDetailPage currently casts the module to any and loses type safety; update the import to assert the module shape (e.g. as Promise<{ CaseDetailPage: React.ComponentType<any> }>) so (mod).CaseDetailPage has a typed signature, then reference that typed CaseDetailPage when creating the element with DemoProvider and render; ensure the type covers the actual props if any instead of using any and keep usages of render, createElement and DemoProvider unchanged.apps/dashboard/src/components/case/CaseGraph.tsx (1)
27-31: ExtracttoStatusandbuildCaseGraphDatato a separate file for Fast Refresh compatibility.The static analysis correctly identifies that exporting non-component functions alongside components breaks Vite's Fast Refresh. Consider moving
toStatusandbuildCaseGraphDatato a dedicated utility file (e.g.,caseGraphUtils.ts).♻️ Proposed structure
Create
apps/dashboard/src/components/case/caseGraphUtils.ts:import type { Node, Edge } from '@xyflow/react'; import type { PARequest } from '@/api/graphqlService'; import { DEMO_PA_RESULT_SOURCES, LCD_L34220_POLICY } from '@/lib/demoData'; export function toStatus(met: boolean | null): 'met' | 'not_met' | 'indeterminate' { if (met === true) return 'met'; if (met === false) return 'not_met'; return 'indeterminate'; } export function buildCaseGraphData(paRequest: PARequest): { nodes: Node[]; edges: Edge[]; } { // ... existing implementation }Then import into
CaseGraph.tsx:-export function toStatus(...) { ... } -export function buildCaseGraphData(...) { ... } +import { toStatus, buildCaseGraphData } from './caseGraphUtils'; +export { toStatus, buildCaseGraphData };Also applies to: 37-40
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/case/CaseGraph.tsx` around lines 27 - 31, Extract the pure helper functions toStatus and buildCaseGraphData into a new module (e.g., caseGraphUtils.ts) and export them; update CaseGraph.tsx to import these helpers instead of defining them alongside the component so Vite Fast Refresh won’t be broken. Specifically, move the implementations of toStatus and buildCaseGraphData (referenced in CaseGraph.tsx) into the new file, export both functions, and replace the local definitions with imports from the new module; ensure any type imports (Node, Edge, PARequest) and demo constants used by buildCaseGraphData are also imported into caseGraphUtils.ts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/dashboard/src/components/ehr/__tests__/AuthDetectionBanner.test.tsx`:
- Around line 7-15: Replace the loose any types and the unused destructuring in
the test mocks: change the div mock signature from "({ children, ...props }:
any) => { ... }" to accept properly typed React props (e.g. use props:
React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode } or
React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>), stop
destructuring out motion fields (remove initial/animate/exit/transition
destructure) and spread the remaining props onto the div, and change
AnimatePresence from "({ children }: any) => <>{children}</>" to a typed
function component like "( { children }: { children?: React.ReactNode } ) =>
<>{children}</>" so there are no any types or unused bindings; update the mocks
named "div" and "AnimatePresence" in AuthDetectionBanner.test.tsx accordingly.
In `@apps/dashboard/src/components/ehr/AuthDetectionBanner.tsx`:
- Around line 20-36: The banner rendered by motion.div currently announces
nothing to assistive tech; add live-region/alert semantics to the animated
container (the motion.div that wraps the Authorization Determination Engine and
the PA message) by giving it an appropriate role (e.g., role="status" or
role="alert") and aria-live and aria-atomic attributes so screen readers
announce dynamic text like the PA message ("PA Required — {payer} {policyId}
applies to CPT {cptCode}") when it appears; keep the existing animation props
(initial/animate/exit/transition) and ensure the attributes are passed to the
motion.div element.
In `@apps/dashboard/src/components/fleet/__tests__/FleetCard.test.tsx`:
- Around line 6-19: The mock uses unsafe "any" types causing lint/build
failures; update the motion.div and AnimatePresence mock signatures to use
proper React types instead of any (e.g., for motion.div use
React.ComponentProps<'div'> or React.HTMLAttributes<HTMLDivElement> for the
props parameter and for AnimatePresence use { children: React.ReactNode }), keep
the same filtering of motion-specific keys (initial, animate, exit, transition,
layout, whileHover, whileTap, whileFocus, whileInView) and return a plain <div
{...domProps}>{children}</div>; ensure relevant React types are
imported/available in the test file and remove all "any" usages.
In `@apps/dashboard/src/components/fleet/__tests__/FleetView.test.tsx`:
- Around line 6-18: The mock currently destructures motion-only props into
unused variables and types props as any; update the mock for motion.div to use a
concrete div prop type and rename unused motion props with underscore prefixes
to satisfy the linter: change the parameter from "({ children, ...props }: any)"
to "(props: React.ComponentProps<'div'])", then inside destructure "const {
children, initial: _initial, animate: _animate, exit: _exit, transition:
_transition, layout: _layout, whileHover: _whileHover, whileTap: _whileTap,
whileFocus: _whileFocus, whileInView: _whileInView, ...domProps } = props" and
return <div {...domProps}>{children}</div>; similarly type AnimatePresence props
(e.g., React.PropsWithChildren) to remove any usage of any while keeping
behavior of motion.div and AnimatePresence mocks.
In `@apps/dashboard/src/routes/ehr-demo.tsx`:
- Around line 264-275: The "View in Fleet" button rendered when isComplete lacks
a click handler; either wire it to the demo context or disable it: import and
call the demo context's setScene (e.g., setScene('fleet')) in an onClick on that
button (or use your router's navigate/useNavigate to go to the Fleet route) so
clicking transitions to the Fleet scene, or if navigation is intentionally
omitted, add a disabled attribute and/or a TODO comment on the button to make
the intent explicit; update the button element that renders alongside isComplete
and keep the ArrowRight icon as-is.
---
Duplicate comments:
In `@apps/gateway/Gateway.API/Data/SeedDemoData.cs`:
- Line 132: The seed uses DateTimeOffset.UtcNow (variable now) and ambient
culture formatting which makes output non-deterministic; replace the dynamic now
with a fixed, hard-coded DateTimeOffset constant (e.g., new
DateTimeOffset(2023,1,1,0,0,0,TimeSpan.Zero) or a chosen epoch) used throughout
SeedDemoData, and change any ToString/format calls (the formatting at the later
use around line with culture-dependent output) to use explicit formatting with
CultureInfo.InvariantCulture (e.g., .ToString("o",
CultureInfo.InvariantCulture)) so the seed output is deterministic across runs
and locales.
---
Nitpick comments:
In `@apps/dashboard/src/components/case/CaseGraph.tsx`:
- Around line 27-31: Extract the pure helper functions toStatus and
buildCaseGraphData into a new module (e.g., caseGraphUtils.ts) and export them;
update CaseGraph.tsx to import these helpers instead of defining them alongside
the component so Vite Fast Refresh won’t be broken. Specifically, move the
implementations of toStatus and buildCaseGraphData (referenced in CaseGraph.tsx)
into the new file, export both functions, and replace the local definitions with
imports from the new module; ensure any type imports (Node, Edge, PARequest) and
demo constants used by buildCaseGraphData are also imported into
caseGraphUtils.ts.
In `@apps/dashboard/src/components/fleet/CasePipeline.tsx`:
- Around line 78-85: The button rendered in the CasePipeline component (the
element using key={stage.key}, data-testid={`pipeline-stage-${stage.key}`} and
onClick={() => onFilter(stage.key)}) should include an explicit type attribute
to avoid accidental form submissions; add type="button" to that <button> element
(the one also using isActive and cn(...) for className) so it behaves as a
non-submitting button when composed inside forms.
- Around line 29-71: The inline `@keyframes` flowDot inside the CasePipeline
component's SVG causes duplicate keyframe definitions when multiple CasePipeline
instances render; move the flowDot keyframe out of the component into a shared
stylesheet (e.g., global CSS or Tailwind `@layer` utilities) and remove the
<style> block from the SVG so the SVG's animated line still uses style={{
animation: 'flowDot 1.5s linear infinite' }} but relies on the globally defined
keyframes.
In `@apps/dashboard/src/routes/__tests__/case.test.tsx`:
- Around line 44-49: The dynamic import in renderCaseDetailPage currently casts
the module to any and loses type safety; update the import to assert the module
shape (e.g. as Promise<{ CaseDetailPage: React.ComponentType<any> }>) so
(mod).CaseDetailPage has a typed signature, then reference that typed
CaseDetailPage when creating the element with DemoProvider and render; ensure
the type covers the actual props if any instead of using any and keep usages of
render, createElement and DemoProvider unchanged.
In `@apps/dashboard/src/routes/fleet.tsx`:
- Line 81: The code calls the hook usePAStats and assigns its result to
_graphqlStats but never uses it, causing unnecessary periodic fetches; remove
the unused call to usePAStats (and remove its import if now unused) from the
file so no background refetch occurs—search for the usePAStats invocation and
the _graphqlStats variable in fleet.tsx and delete that line (and clean up the
import statement for usePAStats if orphaned).
- Around line 85-94: The current mapping in the useMemo for fleetData unsafely
casts r.status to FleetStatus which can produce invalid values if backend adds
new statuses; create a small validator/mapper function (e.g.,
mapGraphqlStatusToFleetStatus) that accepts the GraphQL status from
graphqlRequests and returns a validated FleetStatus (explicitly handle known
statuses and return a default like 'processing' for unknowns), then use that
mapper in the useMemo when creating FleetPARequest entries instead of the direct
cast; update references in the mapping logic and keep generateFleetData as the
fallback.
- Around line 26-30: The loop over requests has a redundant check — remove the
unnecessary r.status !== 'waiting_for_insurance' condition inside the for (const
r of requests) loop and only rely on the existence check if (r.status in counts)
before incrementing counts; keep the explicit cast counts[r.status as
Exclude<FleetStatus, 'waiting_for_insurance'>] += 1 to satisfy the type system
while simplifying the conditional.
In `@apps/gateway/Gateway.API/Data/SeedDemoData.cs`:
- Around line 155-161: Replace the status switch that assigns criteriaTemplate
with a strategy dispatch: create a dictionary/map from status (string) to a
selector function that takes rng and returns the chosen item from
CriteriaTemplates, then look up the selector for the current status and call it
(falling back to a default selector when missing). Update the code that
currently uses the switch (variable criteriaTemplate, status, rng, and
CriteriaTemplates) to use that map (e.g., selectors.TryGetValue(status, out
selector) ? selector(rng) : defaultSelector(rng)) so new statuses can be added
by registering new selector entries instead of editing a switch.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: bfc50bb2-a677-447b-b227-f4371ae7fda2
📒 Files selected for processing (24)
apps/dashboard/src/components/case/CaseGraph.tsxapps/dashboard/src/components/case/CaseTimeline.tsxapps/dashboard/src/components/case/PatientNode.tsxapps/dashboard/src/components/demo/SceneNav.tsxapps/dashboard/src/components/demo/__tests__/SceneNav.test.tsxapps/dashboard/src/components/ehr/AuthDetectionBanner.tsxapps/dashboard/src/components/ehr/__tests__/AuthDetectionBanner.test.tsxapps/dashboard/src/components/fleet/CasePipeline.tsxapps/dashboard/src/components/fleet/FleetCard.tsxapps/dashboard/src/components/fleet/FleetView.tsxapps/dashboard/src/components/fleet/KPICards.tsxapps/dashboard/src/components/fleet/__tests__/FleetCard.test.tsxapps/dashboard/src/components/fleet/__tests__/FleetView.test.tsxapps/dashboard/src/components/fleet/__tests__/KPICards.test.tsxapps/dashboard/src/lib/__tests__/formatUtils.test.tsapps/dashboard/src/lib/formatUtils.tsapps/dashboard/src/routes/__root.tsxapps/dashboard/src/routes/__tests__/case.test.tsxapps/dashboard/src/routes/__tests__/ehr-demo.test.tsxapps/dashboard/src/routes/__tests__/fleet.test.tsxapps/dashboard/src/routes/case.$caseId.tsxapps/dashboard/src/routes/ehr-demo.tsxapps/dashboard/src/routes/fleet.tsxapps/gateway/Gateway.API/Data/SeedDemoData.cs
✅ Files skipped from review due to trivial changes (1)
- apps/dashboard/src/lib/tests/formatUtils.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
- apps/dashboard/src/components/case/CaseTimeline.tsx
- apps/dashboard/src/components/demo/SceneNav.tsx
- apps/dashboard/src/components/demo/tests/SceneNav.test.tsx
- apps/dashboard/src/components/fleet/tests/KPICards.test.tsx
- apps/dashboard/src/components/fleet/FleetCard.tsx
- apps/dashboard/src/components/fleet/FleetView.tsx
- apps/dashboard/src/routes/tests/fleet.test.tsx
- apps/dashboard/src/components/case/PatientNode.tsx
- apps/dashboard/src/components/fleet/KPICards.tsx
| div: ({ children, ...props }: any) => { | ||
| const { | ||
| initial, animate: _animate, exit, transition, | ||
| ...domProps | ||
| } = props; | ||
| return <div {...domProps}>{children}</div>; | ||
| }, | ||
| }, | ||
| AnimatePresence: ({ children }: any) => <>{children}</>, |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check if the file exists and get its content
fd "AuthDetectionBanner.test.tsx" apps/dashboard/Repository: lvlup-sw/authscript
Length of output: 135
🏁 Script executed:
# Read the test file to see the actual code
cat -n "apps/dashboard/src/components/ehr/__tests__/AuthDetectionBanner.test.tsx" | head -40Repository: lvlup-sw/authscript
Length of output: 1445
🏁 Script executed:
# Check if there's a linting config in the project
fd -e eslintrc -e eslint.config -e ".eslintrc*" apps/dashboard/Repository: lvlup-sw/authscript
Length of output: 428
🏁 Script executed:
# Check tsconfig.json for strict mode settings
fd "tsconfig.json" apps/dashboard/ -x cat -n {}Repository: lvlup-sw/authscript
Length of output: 866
🏁 Script executed:
# Also check the root tsconfig
cat -n "tsconfig.json" | head -30Repository: lvlup-sw/authscript
Length of output: 108
🏁 Script executed:
# Search for ESLint configuration files in the repo
fd --full-path "eslint" . --type f | head -20Repository: lvlup-sw/authscript
Length of output: 96
🏁 Script executed:
# Check for eslintrc or eslint config in package.json
fd "package.json" -x grep -A 10 "eslintConfig" {} 2>/dev/null | head -30Repository: lvlup-sw/authscript
Length of output: 45
🏁 Script executed:
# Look for eslint config in common locations
ls -la | grep -i eslintRepository: lvlup-sw/authscript
Length of output: 45
🏁 Script executed:
# Check the ESLint configuration in the dashboard
cat -n "apps/dashboard/eslint.config.js" | head -50Repository: lvlup-sw/authscript
Length of output: 1167
🏁 Script executed:
# Also check for any stricter TypeScript checking in the dashboard tsconfig
fd "tsconfig.json" apps/ -x cat -n {}Repository: lvlup-sw/authscript
Length of output: 866
Fix the mock typing to resolve TypeScript strict mode violations.
Lines 7 and 15 use any types which violate the ESLint no-explicit-any rule, and the destructuring pattern on lines 8–11 creates an unused binding (_animate) that TypeScript's noUnusedLocals flag catches. Replace any with proper React types and remove the problematic destructuring:
Proposed fix
+import type { HTMLAttributes, PropsWithChildren } from 'react';
import { describe, it, expect, vi } from 'vitest';
import { render, screen } from '@testing-library/react';
// Mock motion/react for test environment
+type MotionDivMockProps = HTMLAttributes<HTMLDivElement> & {
+ initial?: unknown;
+ animate?: unknown;
+ exit?: unknown;
+ transition?: unknown;
+};
+
vi.mock('motion/react', () => ({
motion: {
- div: ({ children, ...props }: any) => {
- const {
- initial, animate: _animate, exit, transition,
- ...domProps
- } = props;
+ div: ({ children, ...props }: PropsWithChildren<MotionDivMockProps>) => {
+ const domProps: MotionDivMockProps = { ...props };
+ delete domProps.initial;
+ delete domProps.animate;
+ delete domProps.exit;
+ delete domProps.transition;
return <div {...domProps}>{children}</div>;
},
},
- AnimatePresence: ({ children }: any) => <>{children}</>,
+ AnimatePresence: ({ children }: PropsWithChildren) => <>{children}</>,
}));🧰 Tools
🪛 GitHub Actions: CI
[error] 9-9: 'initial' is assigned a value but never used. (no-unused-vars)
[warning] 7-7: Unexpected any. Specify a different type
[error] 9-9: 'initial' is assigned a value but never used. (no-unused-vars)
🪛 GitHub Check: Dashboard Build & Test
[failure] 9-9:
'transition' is assigned a value but never used
[failure] 9-9:
'exit' is assigned a value but never used
[failure] 9-9:
'_animate' is assigned a value but never used
[failure] 9-9:
'initial' is assigned a value but never used
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/ehr/__tests__/AuthDetectionBanner.test.tsx`
around lines 7 - 15, Replace the loose any types and the unused destructuring in
the test mocks: change the div mock signature from "({ children, ...props }:
any) => { ... }" to accept properly typed React props (e.g. use props:
React.HTMLAttributes<HTMLDivElement> & { children?: React.ReactNode } or
React.PropsWithChildren<React.HTMLAttributes<HTMLDivElement>>), stop
destructuring out motion fields (remove initial/animate/exit/transition
destructure) and spread the remaining props onto the div, and change
AnimatePresence from "({ children }: any) => <>{children}</>" to a typed
function component like "( { children }: { children?: React.ReactNode } ) =>
<>{children}</>" so there are no any types or unused bindings; update the mocks
named "div" and "AnimatePresence" in AuthDetectionBanner.test.tsx accordingly.
| <motion.div | ||
| initial={{ opacity: 0, y: -20 }} | ||
| animate={{ opacity: 1, y: 0 }} | ||
| exit={{ opacity: 0, y: -20 }} | ||
| transition={{ duration: 0.3 }} | ||
| className="overflow-hidden rounded-lg border border-amber-200 bg-amber-50 shadow-sm" | ||
| > | ||
| <div className="px-4 py-1"> | ||
| <span className="text-[10px] font-semibold uppercase tracking-widest text-teal-600"> | ||
| Authorization Determination Engine | ||
| </span> | ||
| </div> | ||
| <div className="flex items-center gap-3 border-t border-amber-100 px-4 py-2.5"> | ||
| <ShieldAlert className="h-5 w-5 shrink-0 text-amber-600" /> | ||
| <span className="text-sm font-medium text-amber-900"> | ||
| PA Required — {payer} {policyId} applies to CPT {cptCode} | ||
| </span> |
There was a problem hiding this comment.
Announce the PA-required state to screen readers.
This status appears dynamically but has no live-region semantics, so assistive tech may miss it. Add alert semantics on the animated container.
Proposed fix
<motion.div
+ role="alert"
+ aria-atomic="true"
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
className="overflow-hidden rounded-lg border border-amber-200 bg-amber-50 shadow-sm"
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <motion.div | |
| initial={{ opacity: 0, y: -20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -20 }} | |
| transition={{ duration: 0.3 }} | |
| className="overflow-hidden rounded-lg border border-amber-200 bg-amber-50 shadow-sm" | |
| > | |
| <div className="px-4 py-1"> | |
| <span className="text-[10px] font-semibold uppercase tracking-widest text-teal-600"> | |
| Authorization Determination Engine | |
| </span> | |
| </div> | |
| <div className="flex items-center gap-3 border-t border-amber-100 px-4 py-2.5"> | |
| <ShieldAlert className="h-5 w-5 shrink-0 text-amber-600" /> | |
| <span className="text-sm font-medium text-amber-900"> | |
| PA Required — {payer} {policyId} applies to CPT {cptCode} | |
| </span> | |
| <motion.div | |
| role="alert" | |
| aria-atomic="true" | |
| initial={{ opacity: 0, y: -20 }} | |
| animate={{ opacity: 1, y: 0 }} | |
| exit={{ opacity: 0, y: -20 }} | |
| transition={{ duration: 0.3 }} | |
| className="overflow-hidden rounded-lg border border-amber-200 bg-amber-50 shadow-sm" | |
| > | |
| <div className="px-4 py-1"> | |
| <span className="text-[10px] font-semibold uppercase tracking-widest text-teal-600"> | |
| Authorization Determination Engine | |
| </span> | |
| </div> | |
| <div className="flex items-center gap-3 border-t border-amber-100 px-4 py-2.5"> | |
| <ShieldAlert className="h-5 w-5 shrink-0 text-amber-600" /> | |
| <span className="text-sm font-medium text-amber-900"> | |
| PA Required — {payer} {policyId} applies to CPT {cptCode} | |
| </span> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/ehr/AuthDetectionBanner.tsx` around lines 20 -
36, The banner rendered by motion.div currently announces nothing to assistive
tech; add live-region/alert semantics to the animated container (the motion.div
that wraps the Authorization Determination Engine and the PA message) by giving
it an appropriate role (e.g., role="status" or role="alert") and aria-live and
aria-atomic attributes so screen readers announce dynamic text like the PA
message ("PA Required — {payer} {policyId} applies to CPT {cptCode}") when it
appears; keep the existing animation props (initial/animate/exit/transition) and
ensure the attributes are passed to the motion.div element.
| vi.mock('motion/react', () => ({ | ||
| motion: { | ||
| div: ({ children, ...props }: any) => { | ||
| // Filter out motion-specific props that are not valid DOM attributes | ||
| const { | ||
| initial, animate: _animate, exit, transition, layout, | ||
| whileHover, whileTap, whileFocus, whileInView, | ||
| ...domProps | ||
| } = props; | ||
| return <div {...domProps}>{children}</div>; | ||
| }, | ||
| }, | ||
| AnimatePresence: ({ children }: any) => <>{children}</>, | ||
| })); |
There was a problem hiding this comment.
Same lint errors and any usage as FleetView.test.tsx.
Apply the same fix here to address the build failures and guideline violations.
🛠️ Proposed fix
-vi.mock('motion/react', () => ({
- motion: {
- div: ({ children, ...props }: any) => {
- // Filter out motion-specific props that are not valid DOM attributes
- const {
- initial, animate: _animate, exit, transition, layout,
- whileHover, whileTap, whileFocus, whileInView,
- ...domProps
- } = props;
- return <div {...domProps}>{children}</div>;
- },
- },
- AnimatePresence: ({ children }: any) => <>{children}</>,
-}));
+vi.mock('motion/react', () => ({
+ motion: {
+ div: ({
+ children,
+ initial: _initial,
+ animate: _animate,
+ exit: _exit,
+ transition: _transition,
+ layout: _layout,
+ whileHover: _whileHover,
+ whileTap: _whileTap,
+ whileFocus: _whileFocus,
+ whileInView: _whileInView,
+ ...domProps
+ }: React.ComponentProps<'div'> & Record<string, unknown>) => {
+ return <div {...domProps}>{children}</div>;
+ },
+ },
+ AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>,
+}));As per coding guidelines: "no any".
🧰 Tools
🪛 GitHub Check: Dashboard Build & Test
[failure] 12-12:
'whileHover' is assigned a value but never used
[failure] 11-11:
'layout' is assigned a value but never used
[failure] 11-11:
'transition' is assigned a value but never used
[failure] 11-11:
'exit' is assigned a value but never used
[failure] 11-11:
'_animate' is assigned a value but never used
[failure] 11-11:
'initial' is assigned a value but never used
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/fleet/__tests__/FleetCard.test.tsx` around
lines 6 - 19, The mock uses unsafe "any" types causing lint/build failures;
update the motion.div and AnimatePresence mock signatures to use proper React
types instead of any (e.g., for motion.div use React.ComponentProps<'div'> or
React.HTMLAttributes<HTMLDivElement> for the props parameter and for
AnimatePresence use { children: React.ReactNode }), keep the same filtering of
motion-specific keys (initial, animate, exit, transition, layout, whileHover,
whileTap, whileFocus, whileInView) and return a plain <div
{...domProps}>{children}</div>; ensure relevant React types are
imported/available in the test file and remove all "any" usages.
| vi.mock('motion/react', () => ({ | ||
| motion: { | ||
| div: ({ children, ...props }: any) => { | ||
| const { | ||
| initial, animate: _animate, exit, transition, layout, | ||
| whileHover, whileTap, whileFocus, whileInView, | ||
| ...domProps | ||
| } = props; | ||
| return <div {...domProps}>{children}</div>; | ||
| }, | ||
| }, | ||
| AnimatePresence: ({ children }: any) => <>{children}</>, | ||
| })); |
There was a problem hiding this comment.
Fix lint errors and avoid any in mock.
The build is failing due to unused destructured variables. Also, any violates the coding guidelines. Consider using a proper type or suppressing only the motion-specific props with an underscore prefix.
🛠️ Proposed fix
-vi.mock('motion/react', () => ({
- motion: {
- div: ({ children, ...props }: any) => {
- const {
- initial, animate: _animate, exit, transition, layout,
- whileHover, whileTap, whileFocus, whileInView,
- ...domProps
- } = props;
- return <div {...domProps}>{children}</div>;
- },
- },
- AnimatePresence: ({ children }: any) => <>{children}</>,
-}));
+vi.mock('motion/react', () => ({
+ motion: {
+ div: ({
+ children,
+ initial: _initial,
+ animate: _animate,
+ exit: _exit,
+ transition: _transition,
+ layout: _layout,
+ whileHover: _whileHover,
+ whileTap: _whileTap,
+ whileFocus: _whileFocus,
+ whileInView: _whileInView,
+ ...domProps
+ }: React.ComponentProps<'div'> & Record<string, unknown>) => {
+ return <div {...domProps}>{children}</div>;
+ },
+ },
+ AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>,
+}));As per coding guidelines: "no any".
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| vi.mock('motion/react', () => ({ | |
| motion: { | |
| div: ({ children, ...props }: any) => { | |
| const { | |
| initial, animate: _animate, exit, transition, layout, | |
| whileHover, whileTap, whileFocus, whileInView, | |
| ...domProps | |
| } = props; | |
| return <div {...domProps}>{children}</div>; | |
| }, | |
| }, | |
| AnimatePresence: ({ children }: any) => <>{children}</>, | |
| })); | |
| vi.mock('motion/react', () => ({ | |
| motion: { | |
| div: ({ | |
| children, | |
| initial: _initial, | |
| animate: _animate, | |
| exit: _exit, | |
| transition: _transition, | |
| layout: _layout, | |
| whileHover: _whileHover, | |
| whileTap: _whileTap, | |
| whileFocus: _whileFocus, | |
| whileInView: _whileInView, | |
| ...domProps | |
| }: React.ComponentProps<'div'> & Record<string, unknown>) => { | |
| return <div {...domProps}>{children}</div>; | |
| }, | |
| }, | |
| AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>, | |
| })); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/fleet/__tests__/FleetView.test.tsx` around
lines 6 - 18, The mock currently destructures motion-only props into unused
variables and types props as any; update the mock for motion.div to use a
concrete div prop type and rename unused motion props with underscore prefixes
to satisfy the linter: change the parameter from "({ children, ...props }: any)"
to "(props: React.ComponentProps<'div'])", then inside destructure "const {
children, initial: _initial, animate: _animate, exit: _exit, transition:
_transition, layout: _layout, whileHover: _whileHover, whileTap: _whileTap,
whileFocus: _whileFocus, whileInView: _whileInView, ...domProps } = props" and
return <div {...domProps}>{children}</div>; similarly type AnimatePresence props
(e.g., React.PropsWithChildren) to remove any usage of any while keeping
behavior of motion.div and AnimatePresence mocks.
| {/* Fleet transition trigger — visible after PA submission completes */} | ||
| {isComplete && ( | ||
| <div className="flex justify-center pt-2"> | ||
| <button | ||
| type="button" | ||
| className="inline-flex items-center gap-2 rounded-lg bg-teal-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-teal-700" | ||
| > | ||
| View in Fleet | ||
| <ArrowRight className="h-4 w-4" aria-hidden="true" /> | ||
| </button> | ||
| </div> | ||
| )} |
There was a problem hiding this comment.
"View in Fleet" button has no click handler.
The button renders correctly when isComplete but doesn't navigate anywhere. If this is intentional for the demo phase, consider adding a disabled state or a TODO comment. Otherwise, wire it to the demo context's setScene or use router navigation.
🔧 Proposed fix using demo context
+import { useDemoContext } from '@/components/demo/DemoProvider';
+import { useNavigate } from '@tanstack/react-router';
export function EhrDemoPage() {
const flow = useEhrDemoFlow();
+ const { setScene } = useDemoContext();
+ const navigate = useNavigate();
// ...
{isComplete && (
<div className="flex justify-center pt-2">
<button
type="button"
+ onClick={() => {
+ setScene('fleet');
+ navigate({ to: '/fleet' });
+ }}
className="inline-flex items-center gap-2 rounded-lg bg-teal-600 px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-colors hover:bg-teal-700"
>
View in Fleet
<ArrowRight className="h-4 w-4" aria-hidden="true" />
</button>
</div>
)}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/routes/ehr-demo.tsx` around lines 264 - 275, The "View in
Fleet" button rendered when isComplete lacks a click handler; either wire it to
the demo context or disable it: import and call the demo context's setScene
(e.g., setScene('fleet')) in an onClick on that button (or use your router's
navigate/useNavigate to go to the Fleet route) so clicking transitions to the
Fleet scene, or if navigation is intentionally omitted, add a disabled attribute
and/or a TODO comment on the button to make the intent explicit; update the
button element that renders alongside isComplete and keep the ArrowRight icon
as-is.
Summary
Three-scene demo experience for PA workflow, targeting both clinician and investor audiences:
Changes
New Components (17 files)
components/demo/— DemoProvider context, SceneNav, SceneTransition, transition variantscomponents/fleet/— KPICards, FleetCard, FleetView, CasePipelinecomponents/case/— CaseGraph, CaseTimeline, PatientNode, EvidenceNode, CriteriaNode, DecisionNode, AnimatedEdge, NodeCardcomponents/ehr/— AuthDetectionBanner, ChartTabPanelEnhanced Components (5 files)
EhrHeader.tsx— encounter type pill, facility nameEncounterSidebar.tsx— chart tab navigation + PA stage trackinguseEhrDemoFlow.ts— 5 new states, backward compatibleehr-demo.tsx— DemoProvider + SceneNav integration, "View in Fleet" transitionNew Routes
/fleet— Fleet dashboard page/case/$caseId— Case detail drilldownNew Dependencies
motion^12 (page transitions, micro-animations)@xyflow/react^12 (node graph visualization)Backend
SeedDemoData.cs— 48 deterministic mock PA requests for PostgreSQLTest Plan
🤖 Generated with Claude Code