Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughRemoves the legacy in-route LookaheadDashboard and replaces it with a modular dashboard and supporting components under Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Dashboard as LookaheadDashboard
participant API as /api/proxy
participant Backend
participant Cache as SWR
User->>Dashboard: select project / upload programme file
Dashboard->>API: POST (streaming body, duplex)
API->>Backend: forward streaming upload
Backend-->>API: 202 Accepted (uploadId)
API-->>Dashboard: uploadId (with Cache-Control)
loop poll
Dashboard->>API: GET /status (proxied, 9s timeout)
API->>Backend: fetch status
Backend-->>API: status update
API-->>Dashboard: status
end
Dashboard->>Cache: mutate/invalidate snapshot/alerts/versions
Dashboard->>API: GET snapshots/alerts/versions (proxied)
API->>Backend: proxy fetch
Backend-->>API: data
API-->>Dashboard: response (errors mapped via withErrorBoundary)
User->>Dashboard: dismiss alert / request delete version
Dashboard->>API: DELETE version
API->>Backend: forward delete
Backend-->>API: delete result
API-->>Dashboard: response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai resume |
✅ Actions performedReviews resumed. |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 9
🧹 Nitpick comments (1)
src/components/lookahead/WindowSelector.tsx (1)
25-35: Expose selected state to assistive tech on window-size buttons.At Line 25, selection is only conveyed by styling. Add
aria-pressed(and explicittype="button") so keyboard/screen-reader users get state feedback.Suggested fix
<button key={w} + type="button" + aria-pressed={windowSize === w} onClick={() => onSetWindowSize(w)} className={`px-4 py-1.5 rounded-md text-sm font-semibold transition-all ${ windowSize === w ? "bg-[var(--navy)] text-white shadow-sm" : "text-slate-500 hover:text-slate-800" }`} >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/lookahead/WindowSelector.tsx` around lines 25 - 35, The window-size toggle buttons in the WindowSelector component only use visual styling to indicate selection; update the button elements used in the render (the element created in the map that calls onSetWindowSize) to include an explicit type="button" and an accessible pressed state by adding aria-pressed={windowSize === w} so assistive tech and keyboard users receive the selected state (keep existing onClick and className logic intact).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/app/api/proxy/route.ts`:
- Around line 154-159: The code sets fetchOpts.body = request.body (a one-time
ReadableStream) so retries after a 401 will attempt to resend a consumed stream
and multipart uploads break; fix by detecting streaming/multipart uploads (e.g.,
inspect request.headers.get("content-type") for "multipart/form-data" or check
if request.body is a ReadableStream) and either (A) buffer the body for
multipart requests using await request.arrayBuffer() and assign that buffer to
fetchOpts.body (and still set (fetchOpts as RequestInit & { duplex: string
}).duplex = "half" only when using a stream), or (B) disable automatic retry for
streaming/multipart requests: do not attempt the retry path on 401 and instead
return the 401 to the client so the client can re-submit after token refresh;
implement the chosen behavior around where fetchOpts and request.body are set
and where the first fetch and retry logic occurs.
In `@src/components/lookahead/DemandHeatmap.tsx`:
- Around line 87-89: The barWidth calculation can divide by zero (maxDemand ===
0) producing Infinity and invalid CSS; update the computation for barWidth (and
any place that sets the width style) to guard against zero/NaN by returning 0
when maxDemand is falsy or the result is not finite (e.g. if (!maxDemand)
barWidth = 0 or compute then if (!Number.isFinite(barWidth)) barWidth = 0), and
ensure the width style uses a finite fallback (e.g. use '0%' when barWidth is
invalid) so variables row, maxDemand, barWidth and the width style assignment
are all protected.
In `@src/components/lookahead/LookaheadDashboard.tsx`:
- Around line 144-154: The response from uploadProgramme can arrive after the
user changes projects, causing stale upload state to be applied; in
handleFileSelected capture the current projectId (e.g., const currentProject =
projectId) or use an AbortController/token before calling uploadProgramme, then
after awaiting the result verify that projectId still equals the captured
currentProject (or that the request wasn't aborted) before calling
setUploadPhase({ kind: "polling", uploadId: ... }) and startPolling(...); if the
project changed or the request was aborted, discard the result and do not update
state or start polling.
- Around line 109-138: The polling loop using setInterval with an async callback
can start overlapping fetchUploadStatus calls; add an in-flight guard (e.g.,
isFetchingRef) checked at the top of the interval callback and set to true
before awaiting fetchUploadStatus and set to false in a finally block to ensure
only one request runs at a time, or replace setInterval with recursive
setTimeout chaining that awaits fetchUploadStatus before scheduling the next
poll; update references in this code path (pollingRef, pollingGenerationRef,
generation, POLL_MAX_ATTEMPTS, stopPolling, setUploadPhase, fetchUploadStatus)
to use the new guard/timing approach so concurrent requests are prevented and
cleanup (stopPolling) still clears pollingRef and resets the in-flight flag.
In `@src/components/lookahead/PlanningAlerts.tsx`:
- Around line 69-79: The dismiss button in PlanningAlerts.tsx currently omits an
explicit type so it defaults to "submit" and may trigger form submissions;
update the button element that calls onDismiss(alert.key) to include
type="button" to prevent unintended submits, keeping the existing onClick,
className, aria-label and conditional color logic unchanged.
In `@src/components/lookahead/StatCards.tsx`:
- Around line 74-77: The subtitle assignment in the StatCards component
currently sets sub to an empty string when heatmap is present but
heatmap.assets.length === 0; update the ternary for the sub field (the
expression using heatmap, heatmap.assets.slice(0,
3).map(formatAssetType).join(", ") and the ellipsis logic) so that when heatmap
exists but heatmap.assets.length is 0 it returns the fallback string "No assets"
(e.g., use heatmap.assets.length > 0 ? ... : "No assets"), ensuring the fallback
also applies when heatmap is null.
In `@src/components/lookahead/UploadBanner.tsx`:
- Around line 79-84: The dismiss button in the UploadBanner component lacks an
accessible name; update the button (the element with onClick={onDismiss} that
renders the X icon) to include an accessible label such as aria-label="Dismiss"
or add screen-reader-only text inside the button so screen readers announce its
purpose; ensure the label describes the action (e.g., "Close upload banner" or
"Dismiss") and keep the onDismiss handler and X icon unchanged.
In `@src/components/lookahead/VersionHistory.tsx`:
- Around line 22-25: The buttons in VersionHistory.tsx currently omit an
explicit type and thus default to type="submit", which can cause accidental form
submissions; update each <button> element in the VersionHistory component
(including the one that toggles setIsOpen and the other click-handler buttons)
to include type="button" so they behave as non-submit controls and avoid
submitting surrounding forms.
- Around line 108-110: The trash action currently uses "opacity-0
group-hover:opacity-100" which hides it from keyboard users; update the element
in VersionHistory.tsx (the delete/trash action JSX that has className="opacity-0
group-hover:opacity-100 ...", and title="Delete this version") to also respond
to keyboard focus by adding focus:opacity-100 and focus-visible:opacity-100 (or
equivalent) to the className and provide an accessible name via aria-label
(e.g., aria-label="Delete this version") or include a visually-hidden label
(sr-only) instead of relying only on title so screen readers and keyboard users
can access the control. Ensure the button remains reachable via keyboard
(tabIndex if needed) and keep the existing hover behavior.
---
Nitpick comments:
In `@src/components/lookahead/WindowSelector.tsx`:
- Around line 25-35: The window-size toggle buttons in the WindowSelector
component only use visual styling to indicate selection; update the button
elements used in the render (the element created in the map that calls
onSetWindowSize) to include an explicit type="button" and an accessible pressed
state by adding aria-pressed={windowSize === w} so assistive tech and keyboard
users receive the selected state (keep existing onClick and className logic
intact).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 6f6f080f-7300-44ca-8f4a-7ca8e96028c1
📒 Files selected for processing (14)
src/app/(dashboard)/lookahead/_components/LookaheadDashboard.tsxsrc/app/(dashboard)/lookahead/page.tsxsrc/app/api/proxy/route.tssrc/components/lookahead/DemandHeatmap.tsxsrc/components/lookahead/EmptyForecastState.tsxsrc/components/lookahead/LookaheadDashboard.tsxsrc/components/lookahead/PlanningAlerts.tsxsrc/components/lookahead/StatCards.tsxsrc/components/lookahead/UploadBanner.tsxsrc/components/lookahead/VersionHistory.tsxsrc/components/lookahead/WindowSelector.tsxsrc/components/lookahead/utils.tssrc/lib/swr.tssrc/types/index.ts
💤 Files with no reviewable changes (1)
- src/app/(dashboard)/lookahead/_components/LookaheadDashboard.tsx
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
src/components/lookahead/LookaheadDashboard.tsx (1)
159-175:⚠️ Potential issue | 🟠 MajorThe project-switch guard still cannot see the new project.
At Lines 167 and 171,
projectIdis the same value captured whenhandleFileSelectedstarted, so an olduploadProgramme()response can still move the new project UI intopollingorerrorafter Lines 205-208 switch projects.Suggested fix
const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null); const pollingGenerationRef = useRef(0); + const uploadGenerationRef = useRef(0); @@ const handleFileSelected = useCallback( async (file: File | null) => { if (!file || !projectId) return; + const generation = ++uploadGenerationRef.current; const targetProject = projectId; setUploadPhase({ kind: "uploading" }); try { const result = await uploadProgramme(targetProject, file); - // Discard result if the user switched projects while uploading - if (pollingGenerationRef.current !== 0 && targetProject !== projectId) return; + if (uploadGenerationRef.current !== generation) return; setUploadPhase({ kind: "polling", uploadId: result.upload_id }); startPolling(result.upload_id); } catch (err) { - if (targetProject !== projectId) return; + if (uploadGenerationRef.current !== generation) return; setUploadPhase({ kind: "error", message: getApiErrorMessage(err) }); } }, [projectId, startPolling], ); @@ const handleProjectSelect = useCallback( (proj: ApiProject) => { if (!proj?.id) return; + uploadGenerationRef.current += 1; stopPolling(); setShowProjectSelector(false); setProjectId(proj.id);Also applies to: 202-210
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/lookahead/LookaheadDashboard.tsx` around lines 159 - 175, The guard comparing projectId after async work uses the stale captured projectId in handleFileSelected, so responses can incorrectly update UI for a newly selected project; fix by maintaining a live ref (e.g., projectIdRef) that you update whenever projectId changes and replace comparisons of the captured projectId with projectIdRef.current after the await(s) and before calling setUploadPhase/startPolling (and in the catch block and the other similar block around lines 202-210); update references to use projectIdRef.current (or an equivalent getter) when validating targetProject !== current project before mutating state or starting polling.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/lookahead/LookaheadDashboard.tsx`:
- Around line 518-550: StatCards is being rendered even when the forecast empty
states will be shown, causing a misleading zeroed dashboard; update the
rendering logic so StatCards is only rendered when not loading and there is a
valid forecast (i.e. projectId is present, heatmap exists, and
visibleWeeks.length > 0). Concretely, change the condition around the
StatCards/forecast block to check a single predicate (e.g. const hasForecast =
!!projectId && !!heatmap && visibleWeeks.length > 0) and render <StatCards
stats={stats} ... /> only when hasForecast && !isLoading && !snapshotLoading;
otherwise render the existing <EmptyForecastState reason="no-project" |
"no-data" /> branches. This ensures StatCards (function/component name
StatCards) does not show when EmptyForecastState will be shown.
- Around line 236-246: The code in LookaheadDashboard.tsx computes monday and
then uses monday.toISOString().split("T")[0], which converts to UTC and can
shift the date; replace that UTC conversion with a local YYYY-MM-DD formatter
(e.g., build currentWeekStr from monday.getFullYear(), monday.getMonth()+1 and
monday.getDate() with zero-padding, or use monday.toLocaleDateString('en-CA'))
so currentWeekStr reflects the local Monday; then use that local string when
computing startIdx against weeks.
---
Duplicate comments:
In `@src/components/lookahead/LookaheadDashboard.tsx`:
- Around line 159-175: The guard comparing projectId after async work uses the
stale captured projectId in handleFileSelected, so responses can incorrectly
update UI for a newly selected project; fix by maintaining a live ref (e.g.,
projectIdRef) that you update whenever projectId changes and replace comparisons
of the captured projectId with projectIdRef.current after the await(s) and
before calling setUploadPhase/startPolling (and in the catch block and the other
similar block around lines 202-210); update references to use
projectIdRef.current (or an equivalent getter) when validating targetProject !==
current project before mutating state or starting polling.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 30c775c0-3a89-410e-a364-260b7abc7a99
📒 Files selected for processing (8)
src/app/api/proxy/route.tssrc/components/lookahead/DemandHeatmap.tsxsrc/components/lookahead/LookaheadDashboard.tsxsrc/components/lookahead/PlanningAlerts.tsxsrc/components/lookahead/StatCards.tsxsrc/components/lookahead/UploadBanner.tsxsrc/components/lookahead/VersionHistory.tsxsrc/components/lookahead/WindowSelector.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
- src/components/lookahead/WindowSelector.tsx
- src/components/lookahead/PlanningAlerts.tsx
- src/components/lookahead/VersionHistory.tsx
- src/components/lookahead/DemandHeatmap.tsx
- src/app/api/proxy/route.ts
- src/components/lookahead/UploadBanner.tsx
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/components/lookahead/WindowSelector.tsx (1)
6-6: Redundant type alias can be removed.
WindowSizeis just an alias forLookaheadWindowSizewith no added value. Consider usingLookaheadWindowSizedirectly in thePropsinterface to reduce indirection.♻️ Suggested simplification
-type WindowSize = LookaheadWindowSize; - interface Props { - windowSize: WindowSize; - onSetWindowSize: (size: WindowSize) => void; + windowSize: LookaheadWindowSize; + onSetWindowSize: (size: LookaheadWindowSize) => void; lastUpdated: string | null; }And update line 24:
- {(["2W", "4W", "6W"] as WindowSize[]).map((w) => ( + {(["2W", "4W", "6W"] as LookaheadWindowSize[]).map((w) => (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/lookahead/WindowSelector.tsx` at line 6, Remove the redundant type alias WindowSize and use LookaheadWindowSize directly in the Props interface: delete the line declaring "type WindowSize = LookaheadWindowSize", update the Props interface (and any other references in this file) to replace WindowSize with LookaheadWindowSize, and run a quick grep/IDE search in WindowSelector.tsx to ensure no remaining references to WindowSize remain.src/components/lookahead/VersionHistory.tsx (1)
22-26: Expose accordion state to assistive tech.Line 22 toggle is missing
aria-expanded/aria-controls. Add these plus a panelidso screen readers get collapse state.Accessibility tweak
<button type="button" onClick={() => setIsOpen((v) => !v)} + aria-expanded={isOpen} + aria-controls="programme-history-panel" className="w-full flex items-center justify-between px-5 py-3.5 text-sm font-bold text-slate-600 hover:text-slate-900 transition-colors" > @@ - <div className="border-t border-slate-100 p-3 space-y-1 max-h-64 overflow-y-auto custom-scrollbar"> + <div + id="programme-history-panel" + className="border-t border-slate-100 p-3 space-y-1 max-h-64 overflow-y-auto custom-scrollbar" + >Also applies to: 41-41
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/lookahead/VersionHistory.tsx` around lines 22 - 26, The toggle button currently using onClick={() => setIsOpen((v) => !v)} should expose its state to assistive tech: bind aria-expanded to the isOpen state (aria-expanded={isOpen}) and add aria-controls pointing to the panel's id; give the collapsible panel a unique id (e.g., versionHistoryPanel or similar) and ensure the button's aria-controls value matches that id so screen readers can detect collapse state; apply the same change for the second toggle instance referenced by the component (the other button using setIsOpen) and ensure the panel element has role="region" or appropriate semantic container if not already present.src/components/lookahead/DemandHeatmap.tsx (1)
154-188: Consider extracting status text logic for readability.The nested ternary chain is correct but hard to follow. Extracting to a helper function could improve maintainability.
♻️ Optional: Extract status text to a helper
function StatusText({ row }: { row: LookaheadRow }) { if (row.demand_hours === 0) { return row.booked_hours > 0 ? ( <span className="text-[10px] text-slate-400"> {row.booked_hours}h booked · No forecast demand </span> ) : ( <span className="text-[10px] text-slate-300">No activity forecast</span> ); } return ( <> <span className="text-[10px] text-slate-400">{row.demand_hours}h needed</span> {row.booked_hours > 0 && ( <span className="text-[10px] text-teal font-medium">{row.booked_hours}h booked</span> )} {row.gap_hours > 0 ? ( <span className="text-[10px] text-red-500 font-semibold">{row.gap_hours}h unbooked</span> ) : row.booked_hours > 0 ? ( <span className="text-[10px] text-green-600 font-medium">All booked ✓</span> ) : ( <span className="text-[10px] text-red-400">Nothing booked yet</span> )} </> ); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/lookahead/DemandHeatmap.tsx` around lines 154 - 188, The nested ternary rendering for the status text in DemandHeatmap.tsx is hard to read—extract it into a small helper component or function (e.g., StatusText({ row }: { row: LookaheadRow })) that returns the same JSX branches based on row.demand_hours, row.booked_hours and row.gap_hours, preserve all classNames and text exactly, then replace the inline ternary block with a single <StatusText row={row} /> invocation so the logic is isolated and more maintainable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/components/lookahead/DemandHeatmap.tsx`:
- Around line 204-207: The legend in the DemandHeatmap component currently shows
a single swatch with class bg-red-500 for "Unbooked" while gap bars use multiple
colors by demand_level; update the legend JSX (the block that currently renders
the div with className "w-3 h-1.5 rounded-full bg-red-500" and the "Unbooked"
label) to either render multiple swatches matching the demand_level color
classes (bg-amber-300, bg-amber-400, bg-orange-500, bg-red-500) with a shared
"Unbooked" label, or replace the label with a short note like "Unbooked — color
intensity reflects urgency" so the legend accurately represents the varying
unbooked colors.
In `@src/components/lookahead/VersionHistory.tsx`:
- Around line 10-11: The component currently uses a single deletingId which
allows starting another delete before the first resolves; change the prop and
state to track multiple inflight deletes (e.g., deletingIds: Set<string> or
string[] and isDeleting(uploadId) checks) and update all usages: replace
deletingId with deletingIds in VersionHistory and any parent
(LookaheadDashboard), update the delete handler (e.g., onDelete /
handleDeleteVersion) to add the uploadId to deletingIds before firing the async
delete request and remove it when the promise resolves/rejects, and use
isDeleting(uploadId) to disable the specific row/button; update types and call
sites that referenced deletingId (lines noted) accordingly.
---
Nitpick comments:
In `@src/components/lookahead/DemandHeatmap.tsx`:
- Around line 154-188: The nested ternary rendering for the status text in
DemandHeatmap.tsx is hard to read—extract it into a small helper component or
function (e.g., StatusText({ row }: { row: LookaheadRow })) that returns the
same JSX branches based on row.demand_hours, row.booked_hours and row.gap_hours,
preserve all classNames and text exactly, then replace the inline ternary block
with a single <StatusText row={row} /> invocation so the logic is isolated and
more maintainable.
In `@src/components/lookahead/VersionHistory.tsx`:
- Around line 22-26: The toggle button currently using onClick={() =>
setIsOpen((v) => !v)} should expose its state to assistive tech: bind
aria-expanded to the isOpen state (aria-expanded={isOpen}) and add aria-controls
pointing to the panel's id; give the collapsible panel a unique id (e.g.,
versionHistoryPanel or similar) and ensure the button's aria-controls value
matches that id so screen readers can detect collapse state; apply the same
change for the second toggle instance referenced by the component (the other
button using setIsOpen) and ensure the panel element has role="region" or
appropriate semantic container if not already present.
In `@src/components/lookahead/WindowSelector.tsx`:
- Line 6: Remove the redundant type alias WindowSize and use LookaheadWindowSize
directly in the Props interface: delete the line declaring "type WindowSize =
LookaheadWindowSize", update the Props interface (and any other references in
this file) to replace WindowSize with LookaheadWindowSize, and run a quick
grep/IDE search in WindowSelector.tsx to ensure no remaining references to
WindowSize remain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fbafb578-f80a-4e23-bc8d-bdeae455c6a6
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (8)
.gitignoresrc/components/lookahead/DemandHeatmap.tsxsrc/components/lookahead/LookaheadDashboard.tsxsrc/components/lookahead/PlanningAlerts.tsxsrc/components/lookahead/StatCards.tsxsrc/components/lookahead/UploadBanner.tsxsrc/components/lookahead/VersionHistory.tsxsrc/components/lookahead/WindowSelector.tsx
✅ Files skipped from review due to trivial changes (3)
- src/components/lookahead/PlanningAlerts.tsx
- .gitignore
- src/components/lookahead/LookaheadDashboard.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/lookahead/UploadBanner.tsx
- src/components/lookahead/StatCards.tsx
- DemandHeatmap: replace single unbooked swatch with four colour-graded swatches (amber-300/400, orange-500, red-500) matching GAP_BAR_COLOR; extract nested status-text ternary into StatusText helper component - VersionHistory: change deletingId (single) to deletingIds (Set<string>) so concurrent deletes each disable only their own row; add aria-expanded, aria-controls, id, and role="region" to toggle/panel for screen readers - WindowSelector: remove redundant WindowSize alias; use LookaheadWindowSize directly throughout - LookaheadDashboard: update delete handler and VersionHistory prop to use the new deletingIds Set
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/lookahead/LookaheadDashboard.tsx (1)
411-420: Consider addingaria-expandedfor screen reader accessibility.The project selector button controls dropdown visibility but lacks
aria-expanded. Adding it would improve the experience for assistive technology users.♿ Suggested accessibility improvement
<Button onClick={() => setShowProjectSelector((v) => !v)} + aria-expanded={showProjectSelector} + aria-haspopup="listbox" className="bg-navy hover:bg-(--navy-hover) text-white rounded-lg px-5 py-5 h-auto text-sm font-bold shadow-md shadow-slate-900/10 flex items-center gap-2" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/lookahead/LookaheadDashboard.tsx` around lines 411 - 420, The project-selector Button that toggles setShowProjectSelector is missing an accessibility state; add an aria-expanded attribute to the Button tied to the dropdown state (use showProjectSelector) so screen readers know when the selector is open (e.g., aria-expanded={showProjectSelector}), and ensure the Button element (component name Button) still receives the onClick handler and accessible label (selectedProject?.name fallback) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/components/lookahead/LookaheadDashboard.tsx`:
- Around line 411-420: The project-selector Button that toggles
setShowProjectSelector is missing an accessibility state; add an aria-expanded
attribute to the Button tied to the dropdown state (use showProjectSelector) so
screen readers know when the selector is open (e.g.,
aria-expanded={showProjectSelector}), and ensure the Button element (component
name Button) still receives the onClick handler and accessible label
(selectedProject?.name fallback) unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 35116262-28d3-4e54-9b78-7bd3bf4695f1
📒 Files selected for processing (4)
src/components/lookahead/DemandHeatmap.tsxsrc/components/lookahead/LookaheadDashboard.tsxsrc/components/lookahead/VersionHistory.tsxsrc/components/lookahead/WindowSelector.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/lookahead/VersionHistory.tsx
Summary by CodeRabbit
New Features
Bug Fixes / Reliability
Performance