fix(cli): resolve typecheck errors blocking CI#57
Conversation
Deploying getpaws with
|
| Latest commit: |
d8b3b50
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://c3561136.getpaws-6m4.pages.dev |
| Branch Preview URL: | https://fix-cli-typecheck.getpaws-6m4.pages.dev |
📝 WalkthroughWalkthroughThis pull request consolidates three categories of improvements: removes unused React imports and type declarations from CLI commands, refines conditional color prop handling in UI components, enables TypeScript composite builds across eight domain packages, and enhances the CI pipeline with additional validation steps (typecheck, syncpack, knip), Bun version pinning, and conditional test filtering. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes 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 docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 16
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (6)
apps/dashboard/src/pages/Snapshots.tsx (1)
66-77:⚠️ Potential issue | 🟡 MinorToast message may be misleading.
On successful
buildSnapshot, the status is set to'ready'(line 71) but the toast says"Build started"(line 72). If the build completes successfully and is ready, the toast should reflect that (e.g.,"Build completed"or"Snapshot ready"). If the build is truly just started asynchronously, the status shouldn't be'ready'.Suggested fix
await buildSnapshot(id); setBuildStatus('ready'); - toast.success('Build started'); + toast.success('Build completed'); } catch {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/pages/Snapshots.tsx` around lines 66 - 77, The toast message is misleading in handleBuild: after await buildSnapshot(id) you setBuildStatus('ready') but then call toast.success('Build started'); update the toast to reflect completion (e.g., 'Build completed' or 'Snapshot ready'), or if buildSnapshot is asynchronous and only starts the job, remove/set status accordingly; specifically modify handleBuild to either change toast.success to a completion message when buildSnapshot actually finishes, or change setBuildStatus to 'building' and keep the start toast when buildSnapshot only enqueues the job (refer to handleBuild, buildSnapshot, setBuildStatus, and toast.success).apps/dashboard/src/components/setup/FirstRunStep.tsx (1)
91-112:⚠️ Potential issue | 🟡 MinorPotential memory leak: polling interval is not cleaned up on unmount.
The
setIntervalcreated inws.onerroris never cleared if the component unmounts while polling is active. This could lead to state updates on an unmounted component and continued network requests.🛠️ Suggested fix using useEffect cleanup
Store the interval ID in a ref and clear it on unmount:
+ const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null); + + useEffect(() => { + return () => { + if (pollIntervalRef.current) { + clearInterval(pollIntervalRef.current); + } + }; + }, []); + // Inside ws.onerror: - const poll = setInterval(async () => { + pollIntervalRef.current = setInterval(async () => { // ... if (session.status === 'completed' || session.status === 'failed') { - clearInterval(poll); + clearInterval(pollIntervalRef.current!); + pollIntervalRef.current = null; // ... } }, 3000);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/setup/FirstRunStep.tsx` around lines 91 - 112, The polling interval created inside ws.onerror (the setInterval stored in local variable poll) can leak when the component unmounts; modify the component to store the interval ID in a ref (e.g., pollRef) when you call setInterval in ws.onerror, clear that interval whenever you clearInterval(poll) in your success/failure branch, and add a useEffect cleanup that clears pollRef.current on unmount; ensure ws.onerror still assigns/clears pollRef.current and avoid calling setStatus/setResult/setError after unmount by checking/referring to the mounted ref before updating state.apps/dashboard/src/components/setup/ServerStep.tsx (1)
145-157:⚠️ Potential issue | 🟠 MajorMake provider cards keyboard-accessible interactive elements.
At Line 145,
Cardis clickable but not keyboard-operable by default, which blocks non-pointer users from selecting providers.Suggested fix
- <Card - onClick={onClick} + <Card + role="button" + tabIndex={0} + onClick={onClick} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + onClick(); + } + }} className="cursor-pointer flex-row items-center gap-3 p-4 bg-zinc-900 border-zinc-800 rounded-xl hover:border-emerald-500/50 hover:bg-zinc-800/50 transition-all text-left group" >🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/setup/ServerStep.tsx` around lines 145 - 157, The Card component used in ServerStep is clickable via onClick but not keyboard-accessible; update the Card instance to be a proper interactive element by adding tabIndex={0}, role="button", an onKeyDown handler that triggers the same onClick for Enter and Space keys, and an appropriate accessible name (e.g., aria-label using provider.label) so screen and keyboard users can activate providers; ensure you attach these props to the same Card element that currently uses onClick so keyboard activation mirrors pointer activation.apps/dashboard/src/components/AuthGate.tsx (1)
164-185:⚠️ Potential issue | 🟠 MajorAdd explicit labels to the auth fields.
These inputs rely on placeholders only, so the form has no stable accessible names once users start typing. Keep the current visual layout with
sr-onlylabels.♿ Proposed fix
import { Alert, AlertDescription } from '@/components/ui/alert'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; ... <form onSubmit={isCreating ? handleCreateAccount : handleLogin} className="space-y-3"> + <Label htmlFor="auth-email" className="sr-only"> + Email + </Label> <Input + id="auth-email" type="email" value={email} onChange={(e) => { setEmail(e.target.value); setError(''); }} placeholder="Email" autoFocus className="h-10 bg-zinc-900 border-zinc-700 text-zinc-100 placeholder-zinc-500 focus-visible:border-emerald-400 focus-visible:ring-emerald-400/50" /> + <Label htmlFor="auth-password" className="sr-only"> + Password + </Label> <Input + id="auth-password" type="password" value={password} onChange={(e) => { setPassword(e.target.value); setError(''); }} placeholder={isCreating ? 'Password (min 8 characters)' : 'Password'} className="h-10 bg-zinc-900 border-zinc-700 text-zinc-100 placeholder-zinc-500 focus-visible:border-emerald-400 focus-visible:ring-emerald-400/50" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/AuthGate.tsx` around lines 164 - 185, The email and password Input fields in the form (used by handleCreateAccount/handleLogin) currently rely on placeholders only; add visually-hidden (sr-only) <label> elements for each input so they retain accessible names when users type: create unique ids (e.g., emailInputId, passwordInputId) and set them on the Input components via id and connect matching <label htmlFor="..."> elements with the sr-only class; keep existing onChange handlers (setEmail, setPassword, setError) and visual layout unchanged while ensuring the labels provide stable accessible names.apps/dashboard/src/pages/McpServers.tsx (1)
41-49:⚠️ Potential issue | 🟠 MajorSuccessful deletes can stay stuck in
Removing....
doRemove()only clearsremovingon failure and relies ononRemove()to make the row disappear. The parent passes a no-op callback here, so a successful delete leaves the card visible and disabled until polling catches up.Also applies to: 295-297
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/pages/McpServers.tsx` around lines 41 - 49, doRemove currently only clears removing on failure and relies on onRemove (which may be a no-op) to hide the row; as a result a successful delete can leave the card stuck in "Removing...". Change doRemove to always clear the removing state (use a finally block or setRemoving(false) after the await) and still call onRemove for parent updates; apply the same fix to the other similar delete handler mentioned (the second doRemove-like deletion block around lines 295-297) so UI is re-enabled/cleared regardless of whether the parent removes the item.apps/dashboard/src/pages/Servers.tsx (1)
125-133:⚠️ Potential issue | 🟡 MinorSilent failure on server removal may confuse users.
When
deleteServerfails, the error is caught andremovingstate is reset, but no feedback is provided to the user. Consider adding a toast notification for the failure case.Proposed fix
async function doRemove() { setRemoving(true); try { await deleteServer(server.id); onRemove(); - } catch { + } catch (err) { + toast.error(`Failed to remove server "${server.name}"`); setRemoving(false); } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/pages/Servers.tsx` around lines 125 - 133, doRemove currently swallows deleteServer errors and only resets state; update the catch block in doRemove to display a user-facing toast/error notification (including the error message) and reset removing state; reference the async function doRemove, the deleteServer call, onRemove, setRemoving, and the catch block—ensure the catch accepts an error parameter, calls the app's toast or notify utility with a clear message like "Failed to remove server: <error>", optionally log the error for debugging, and keep removing set to false so the UI stays consistent.
🧹 Nitpick comments (7)
apps/dashboard/src/components/UpdateBanner.tsx (1)
50-66: Good refactor to use shared Button component.The migration to the shared
Buttonprimitive maintains functionality while improving consistency.Consider using
Xfromlucide-reactfor the close icon to maintain consistency with other icon usage in the codebase:+import { X } from 'lucide-react'; ... <Button variant="ghost" size="icon" onClick={() => setDismissed(true)} className="text-zinc-600 hover:text-zinc-400 ml-2 size-7" > - <svg - width="14" - height="14" - viewBox="0 0 14 14" - fill="none" - stroke="currentColor" - strokeWidth="2" - > - <path d="M1 1l12 12M13 1L1 13" /> - </svg> + <X className="size-3.5" /> </Button>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/UpdateBanner.tsx` around lines 50 - 66, Replace the inline SVG close icon inside the UpdateBanner's Button with the shared lucide-react icon: import and use the X component from lucide-react in the UpdateBanner component and pass the existing size/utility classes (or equivalent props) to match styling; update the JSX inside the Button (where onClick={() => setDismissed(true)} is defined) to render <X .../> instead of the raw <svg> so the Button, UpdateBanner, and setDismissed usage remain unchanged while matching the codebase icon conventions.apps/dashboard/src/components/ui/progress.tsx (1)
7-21: Consider documenting the default track behavior.The
Progresscomponent always rendersProgressTrack > ProgressIndicatoralongsidechildren. While this provides a convenient default, consumers passing a customProgressTrackas children would get duplicate tracks. This design choice is valid but could benefit from a brief JSDoc comment.📝 Optional: Add documentation
+/** + * Progress component that includes a default track. Pass children for labels/values. + * For custom track styling, use ProgressTrack directly without this wrapper. + */ function Progress({ className, children, value, ...props }: ProgressPrimitive.Root.Props) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/ui/progress.tsx` around lines 7 - 21, The Progress component always injects a default ProgressTrack and ProgressIndicator alongside any children, which can cause duplicate tracks if callers pass their own ProgressTrack; update the Progress component JSDoc (above function Progress) to document this default behavior and advise callers to omit a custom ProgressTrack when relying on the built-in one, referencing the Progress component, ProgressPrimitive.Root, ProgressTrack, ProgressIndicator, and the children prop so consumers know the conflict and expected usage.apps/dashboard/src/components/ui/tooltip.tsx (1)
7-9: Thedata-slotattribute on Provider may have no effect.
TooltipPrimitive.Provideris typically a context provider that doesn't render a DOM element, so thedata-slotattribute won't appear in the DOM. This is harmless but inconsistent with the other components wheredata-slotis used for styling hooks.🧹 Optional cleanup
function TooltipProvider({ delay = 0, ...props }: TooltipPrimitive.Provider.Props) { - return <TooltipPrimitive.Provider data-slot="tooltip-provider" delay={delay} {...props} />; + return <TooltipPrimitive.Provider delay={delay} {...props} />; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/ui/tooltip.tsx` around lines 7 - 9, The data-slot attribute on TooltipPrimitive.Provider is ineffective because TooltipPrimitive.Provider is a context provider that doesn't render a DOM node; remove data-slot="tooltip-provider" from the TooltipProvider implementation (function TooltipProvider) so it no longer forwards that prop to TooltipPrimitive.Provider, and if you need a styling hook for DOM selection, add the data-slot to a rendered element such as TooltipPrimitive.Root or a wrapper component instead.apps/dashboard/src/components/ui/card.tsx (1)
36-43: Consider semantic heading forCardTitle.Using an
h3(or configurable heading tag) instead of adivwould improve heading/navigation semantics in screen readers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/ui/card.tsx` around lines 36 - 43, The CardTitle component currently renders a div which is non-semantic for headings; update CardTitle to render a semantic heading (e.g., h3) or make the tag configurable (an "as" prop) so callers can choose the appropriate heading level; adjust the prop type from React.ComponentProps<'div'> to React.ComponentProps<'h3'> (or a generic that uses the "as" union) and ensure className, data-slot="card-title" and {...props} are preserved when rendering within the CardTitle function.apps/dashboard/src/pages/Provision.tsx (1)
232-246: Extract the repeated breadcrumb block into a shared fragment/component.The same breadcrumb tree is duplicated in three render paths. Pulling it into a small helper/component will reduce drift and simplify future edits.
Also applies to: 255-269, 281-295
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/pages/Provision.tsx` around lines 232 - 246, Extract the repeated breadcrumb JSX (the Breadcrumb > BreadcrumbList > BreadcrumbItem ... BreadcrumbPage structure using Breadcrumb, BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, and BreadcrumbPage) into a small reusable component (e.g., ProvisionBreadcrumb or TopologyBreadcrumb) and export it from the same module or a nearby shared components file; then replace each duplicated block in Provision.tsx (the three render paths where the same tree appears) with a single invocation of that new component, passing any needed props (if any) for link targets or labels so behavior remains identical.apps/dashboard/src/hooks/useRelativeTime.ts (1)
3-23: Consider handling invalid timestamp strings.If
timestampis a malformed string (not a valid ISO date),new Date(timestamp)produces anInvalid Date, andformatRelativewould return"Invalid Date"fromtoLocaleDateString(). This is unlikely in practice but could be guarded.Optional defensive check
export function useRelativeTime(timestamp: string | undefined, intervalMs = 10_000): string { const [, setTick] = useState(0); useEffect(() => { if (!timestamp) return; const id = setInterval(() => setTick((t) => t + 1), intervalMs); return () => clearInterval(id); }, [timestamp, intervalMs]); if (!timestamp) return '-'; - return formatRelative(new Date(timestamp)); + const date = new Date(timestamp); + if (Number.isNaN(date.getTime())) return '-'; + return formatRelative(date); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/hooks/useRelativeTime.ts` around lines 3 - 23, The formatRelative function should guard against invalid Date objects: inside formatRelative (function formatRelative(date: Date)) check if Number.isNaN(date.getTime()) (or isNaN(date.getTime())) right after creating/receiving the date and return a safe fallback (e.g., 'Invalid date' or '' depending on UX) instead of letting toLocaleDateString() run; update callers in useRelativeTime as needed to handle that fallback consistently.apps/dashboard/src/router.tsx (1)
13-13: Consider lazy-loadingTopologyfor consistency.
Topologyis the only page component that remains eagerly imported while all others are lazy-loaded. If it's a heavy component, consider making it lazy as well for bundle-size consistency. However, if it's the primary landing page and needs to load quickly, keeping it eager is a valid choice.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/router.tsx` at line 13, Replace the eager import of Topology with a lazy-loaded import to match other pages: remove the existing "import { Topology } from './pages/Topology.js';" and instead create a lazy component (e.g., TopologyLazy = React.lazy(() => import('./pages/Topology.js'))) and use that in your route declarations (where Topology is referenced) wrapped in the existing Suspense fallback; update any named/default import expectations if needed to match the module export.
🤖 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/CommandPalette.tsx`:
- Around line 33-42: The global keydown handler in CommandPalette.tsx toggles
the palette even when focus is inside editable fields; update the onKeyDown
inside the useEffect to early-return when the event target is an editable
element by checking (e.target instanceof HTMLElement) and then testing tagName
(INPUT, TEXTAREA, SELECT), contentEditable (element.isContentEditable), or a
design-specific editable attribute/class if used; leave the rest of the logic
(the metaKey/ctrlKey + 'k' check and setOpen toggle) unchanged so the palette
only toggles when focus is not within an input/textarea/select/contenteditable
region.
In `@apps/dashboard/src/components/CopyButton.tsx`:
- Around line 15-20: The handleCopy function uses navigator.clipboard.writeText
without error handling; wrap the await call in a try/catch inside handleCopy
(and the other handleCopy at lines 50–55), on success keep setCopied(true) and
toast.success as-is, on failure catch the error, call toast.error with a clear
message (include error.message), ensure setCopied(false) is set or left
unchanged on error, and optionally log the error via console.error to aid
debugging; reference the handleCopy function and navigator.clipboard.writeText
when making the change.
In `@apps/dashboard/src/components/Layout.tsx`:
- Around line 145-157: The icon-only Button inside the Tooltip (the Button
rendered within TooltipTrigger that calls toggle and switches icons using theme,
Sun, and Moon) lacks an accessible name; update this and the other icon-only
controls (menu, theme, search, logout, collapsed-sidebar) to include either an
aria-label or visible hidden text so screen readers can announce their purpose.
Specifically, add an aria-label prop to the Button that uses the existing label
or a descriptive string (e.g., aria-label={label || "Toggle theme"}) or render a
visually-hidden span inside the Button, and apply the same change to the other
components that render icon-only Buttons/TooltipTriggers so each interactive
element has a clear accessible name. Ensure you modify the Button instances tied
to toggle/theme and the corresponding icon-only actions across Layout.tsx.
- Around line 353-357: The ErrorBoundary is keyed incorrectly: move the
key={location.pathname} from the inner div to the ErrorBoundary component so the
ErrorBoundary instance unmounts/remounts on route changes and its internal error
state resets; locate the ErrorBoundary wrapping the div containing <Outlet />
and apply the key to ErrorBoundary (using the same location.pathname value) and
remove the key from the inner div.
- Around line 130-139: The two ThemeToggle instances maintain independent state
causing them to fall out-of-sync; move the theme state up or centralize it so
both toggles read/update the same source: lift the useState initialization from
ThemeToggle into the parent Layout (or create a useTheme hook) and pass theme
and a toggle handler down to both ThemeToggle components; ensure the handler
updates the shared state (replacing setTheme in ThemeToggle), calls
document.documentElement.classList.toggle('dark', next === 'dark'), and writes
localStorage.setItem('paws_theme', next) so both instances reflect the current
theme and toggle reliably.
In `@apps/dashboard/src/components/SessionTable.tsx`:
- Around line 62-66: The column for startedAt uses sortingFn: 'datetime' but
startedAt is a string (ISO 8601), so change the sort to handle ISO strings:
either set sortingFn: 'basic' on the columnHelper.accessor('startedAt') column
(so string comparisons work) or supply a custom sorting function that parses
info.getValue() into a numeric timestamp and compares those values; update any
related references like the RelativeTime cell renderer accordingly to ensure
sorts use the parsed timestamp rather than expecting Date objects.
In `@apps/dashboard/src/components/ui/dialog.tsx`:
- Around line 59-72: The DialogPrimitive.Close usages rendering the shared
Button (e.g., the instance guarded by showCloseButton and the other close-button
instance) must set the Button's HTML type to "button" to avoid accidentally
submitting parent forms; update the Button props in the DialogPrimitive.Close
render blocks (the Button passed to DialogPrimitive.Close) to include
type="button" so clicks close the dialog without triggering form submission.
In `@apps/dashboard/src/components/ui/input-group.tsx`:
- Around line 53-58: The addon onClick handler in input-group.tsx currently only
focuses an <input>; update the handler (the inline onClick that calls
e.currentTarget.parentElement?.querySelector('input')?.focus()) to also find and
focus textarea controls (e.g., query for 'input, textarea' or attempt to focus
both input and textarea), so InputGroupTextarea instances receive focus when the
addon is clicked.
In `@apps/dashboard/src/components/ui/tabs.tsx`:
- Around line 6-13: The Tabs component destructures orientation but only sets
data-orientation; forward the orientation prop into TabsPrimitive.Root so the
primitive receives it for proper keyboard/ARIA behavior. In the Tabs function,
update the TabsPrimitive.Root invocation to include orientation={orientation}
(in addition to data-orientation) so the primitive's internal arrow-key handling
and state reflect the chosen orientation.
In `@apps/dashboard/src/index.css`:
- Around line 6-8: The CSS uses Tailwind v4 directives `@custom-variant` and
`@theme` which Stylelint flags as unknown; update the Stylelint config to
recognize Tailwind directives by either adding a Tailwind-focused config (e.g.,
install and extend "stylelint-config-tailwindcss") or by relaxing the
scss/at-rule-no-unknown rule to ignore these at-rules (add ignoreAtRules:
["custom-variant","theme"]). Locate the occurrences by searching for the
`@custom-variant` and `@theme` directives in the CSS and update the project
stylelint configuration accordingly so linting no longer errors on those
Tailwind v4 directives.
In `@apps/dashboard/src/pages/Fleet.tsx`:
- Around line 102-104: The alert text incorrectly reads "Failed to load trees"
but should reference workers; update the AlertDescription string to say
something like "Failed to load workers: " and keep using workers.error.message
(referenced symbol: AlertDescription and workers.error.message in Fleet.tsx) so
the displayed message becomes "Failed to load workers: {workers.error.message}"
to correctly reflect the error source.
In `@apps/dashboard/src/pages/Integrations.tsx`:
- Around line 263-271: The disconnect flow in doDisconnect sets deleting(true)
but only resets it in the catch path, so the UI can remain stuck; update
doDisconnect (the async function that calls deleteCloudConnection(conn.id) and
onRefresh()) to always call setDeleting(false) after the operation completes
(use a finally block or ensure setDeleting(false) runs after await
deleteCloudConnection and after onRefresh()) and preserve the existing error
handling (setErr) for exceptions.
In `@apps/dashboard/src/pages/McpServers.tsx`:
- Around line 173-196: The Label components are not associated with their
inputs; update the form by adding id attributes to the input elements (e.g., the
Input for name and the select for transport) and set the corresponding Label
htmlFor to those ids (e.g., id="name" / htmlFor="name" and id="transport" /
htmlFor="transport"); apply the same pattern to the other form block referenced
(lines ~199-235) so every Label component is paired with a matching control id
to improve accessibility (look for the Input, select, Label usages and the
setName/setTransport handlers to locate the correct controls).
In `@apps/dashboard/src/pages/SessionDetail.tsx`:
- Around line 173-186: Remove the redundant runtime checks and type assertions
around ExposedPort properties in SessionDetail.tsx: access the optional
properties directly (ep.access, ep.pin, ep.shareLink) instead of using `'access'
in ep` or `(ep as { access?: string }).access`; update the Badge rendering logic
(Badge component usage) to conditionally render based on ep.access / ep.pin /
ep.shareLink truthiness and simplify the className branching to use ep.access /
ep.pin values directly, and apply the same simplification for pin and shareLink
handling so all assertions and "in" checks are eliminated.
In `@apps/dashboard/src/pages/Sessions.tsx`:
- Around line 54-58: The current conditional renders the emptyState when
sessions.error is true which hides previously loaded data and treats polling
failures as "no sessions"; update the rendering logic in Sessions.tsx to avoid
collapsing errors into the empty state by either (a) moving the sessions.error
branch below the allSessions.length > 0 check so stale data (allSessions)
remains visible on transient polling errors, or (b) replace the sessions.error
branch with a proper error UI (e.g., an errorState element) so that when
sessions.error is true you render the real error state instead of emptyState;
locate the conditional using sessions.loading, sessions.data, sessions.error,
allSessions and emptyState and implement one of these fixes.
In `@apps/dashboard/vite.config.ts`:
- Line 1: The vite.config.ts currently relies on __dirname which is not
ESM-safe; update any resolution logic that uses __dirname (in this file) to use
import.meta.url-based resolution instead—convert import.meta.url to a filesystem
path (e.g., via fileURLToPath(import.meta.url) or new URL('./', import.meta.url)
then path.resolve/path.dirname) and use that value wherever __dirname was
referenced (for plugin root, alias, or path.join calls) so alias resolution
works under ESM.
---
Outside diff comments:
In `@apps/dashboard/src/components/AuthGate.tsx`:
- Around line 164-185: The email and password Input fields in the form (used by
handleCreateAccount/handleLogin) currently rely on placeholders only; add
visually-hidden (sr-only) <label> elements for each input so they retain
accessible names when users type: create unique ids (e.g., emailInputId,
passwordInputId) and set them on the Input components via id and connect
matching <label htmlFor="..."> elements with the sr-only class; keep existing
onChange handlers (setEmail, setPassword, setError) and visual layout unchanged
while ensuring the labels provide stable accessible names.
In `@apps/dashboard/src/components/setup/FirstRunStep.tsx`:
- Around line 91-112: The polling interval created inside ws.onerror (the
setInterval stored in local variable poll) can leak when the component unmounts;
modify the component to store the interval ID in a ref (e.g., pollRef) when you
call setInterval in ws.onerror, clear that interval whenever you
clearInterval(poll) in your success/failure branch, and add a useEffect cleanup
that clears pollRef.current on unmount; ensure ws.onerror still assigns/clears
pollRef.current and avoid calling setStatus/setResult/setError after unmount by
checking/referring to the mounted ref before updating state.
In `@apps/dashboard/src/components/setup/ServerStep.tsx`:
- Around line 145-157: The Card component used in ServerStep is clickable via
onClick but not keyboard-accessible; update the Card instance to be a proper
interactive element by adding tabIndex={0}, role="button", an onKeyDown handler
that triggers the same onClick for Enter and Space keys, and an appropriate
accessible name (e.g., aria-label using provider.label) so screen and keyboard
users can activate providers; ensure you attach these props to the same Card
element that currently uses onClick so keyboard activation mirrors pointer
activation.
In `@apps/dashboard/src/pages/McpServers.tsx`:
- Around line 41-49: doRemove currently only clears removing on failure and
relies on onRemove (which may be a no-op) to hide the row; as a result a
successful delete can leave the card stuck in "Removing...". Change doRemove to
always clear the removing state (use a finally block or setRemoving(false) after
the await) and still call onRemove for parent updates; apply the same fix to the
other similar delete handler mentioned (the second doRemove-like deletion block
around lines 295-297) so UI is re-enabled/cleared regardless of whether the
parent removes the item.
In `@apps/dashboard/src/pages/Servers.tsx`:
- Around line 125-133: doRemove currently swallows deleteServer errors and only
resets state; update the catch block in doRemove to display a user-facing
toast/error notification (including the error message) and reset removing state;
reference the async function doRemove, the deleteServer call, onRemove,
setRemoving, and the catch block—ensure the catch accepts an error parameter,
calls the app's toast or notify utility with a clear message like "Failed to
remove server: <error>", optionally log the error for debugging, and keep
removing set to false so the UI stays consistent.
In `@apps/dashboard/src/pages/Snapshots.tsx`:
- Around line 66-77: The toast message is misleading in handleBuild: after await
buildSnapshot(id) you setBuildStatus('ready') but then call toast.success('Build
started'); update the toast to reflect completion (e.g., 'Build completed' or
'Snapshot ready'), or if buildSnapshot is asynchronous and only starts the job,
remove/set status accordingly; specifically modify handleBuild to either change
toast.success to a completion message when buildSnapshot actually finishes, or
change setBuildStatus to 'building' and keep the start toast when buildSnapshot
only enqueues the job (refer to handleBuild, buildSnapshot, setBuildStatus, and
toast.success).
---
Nitpick comments:
In `@apps/dashboard/src/components/ui/card.tsx`:
- Around line 36-43: The CardTitle component currently renders a div which is
non-semantic for headings; update CardTitle to render a semantic heading (e.g.,
h3) or make the tag configurable (an "as" prop) so callers can choose the
appropriate heading level; adjust the prop type from React.ComponentProps<'div'>
to React.ComponentProps<'h3'> (or a generic that uses the "as" union) and ensure
className, data-slot="card-title" and {...props} are preserved when rendering
within the CardTitle function.
In `@apps/dashboard/src/components/ui/progress.tsx`:
- Around line 7-21: The Progress component always injects a default
ProgressTrack and ProgressIndicator alongside any children, which can cause
duplicate tracks if callers pass their own ProgressTrack; update the Progress
component JSDoc (above function Progress) to document this default behavior and
advise callers to omit a custom ProgressTrack when relying on the built-in one,
referencing the Progress component, ProgressPrimitive.Root, ProgressTrack,
ProgressIndicator, and the children prop so consumers know the conflict and
expected usage.
In `@apps/dashboard/src/components/ui/tooltip.tsx`:
- Around line 7-9: The data-slot attribute on TooltipPrimitive.Provider is
ineffective because TooltipPrimitive.Provider is a context provider that doesn't
render a DOM node; remove data-slot="tooltip-provider" from the TooltipProvider
implementation (function TooltipProvider) so it no longer forwards that prop to
TooltipPrimitive.Provider, and if you need a styling hook for DOM selection, add
the data-slot to a rendered element such as TooltipPrimitive.Root or a wrapper
component instead.
In `@apps/dashboard/src/components/UpdateBanner.tsx`:
- Around line 50-66: Replace the inline SVG close icon inside the UpdateBanner's
Button with the shared lucide-react icon: import and use the X component from
lucide-react in the UpdateBanner component and pass the existing size/utility
classes (or equivalent props) to match styling; update the JSX inside the Button
(where onClick={() => setDismissed(true)} is defined) to render <X .../> instead
of the raw <svg> so the Button, UpdateBanner, and setDismissed usage remain
unchanged while matching the codebase icon conventions.
In `@apps/dashboard/src/hooks/useRelativeTime.ts`:
- Around line 3-23: The formatRelative function should guard against invalid
Date objects: inside formatRelative (function formatRelative(date: Date)) check
if Number.isNaN(date.getTime()) (or isNaN(date.getTime())) right after
creating/receiving the date and return a safe fallback (e.g., 'Invalid date' or
'' depending on UX) instead of letting toLocaleDateString() run; update callers
in useRelativeTime as needed to handle that fallback consistently.
In `@apps/dashboard/src/pages/Provision.tsx`:
- Around line 232-246: Extract the repeated breadcrumb JSX (the Breadcrumb >
BreadcrumbList > BreadcrumbItem ... BreadcrumbPage structure using Breadcrumb,
BreadcrumbList, BreadcrumbItem, BreadcrumbLink, BreadcrumbSeparator, and
BreadcrumbPage) into a small reusable component (e.g., ProvisionBreadcrumb or
TopologyBreadcrumb) and export it from the same module or a nearby shared
components file; then replace each duplicated block in Provision.tsx (the three
render paths where the same tree appears) with a single invocation of that new
component, passing any needed props (if any) for link targets or labels so
behavior remains identical.
In `@apps/dashboard/src/router.tsx`:
- Line 13: Replace the eager import of Topology with a lazy-loaded import to
match other pages: remove the existing "import { Topology } from
'./pages/Topology.js';" and instead create a lazy component (e.g., TopologyLazy
= React.lazy(() => import('./pages/Topology.js'))) and use that in your route
declarations (where Topology is referenced) wrapped in the existing Suspense
fallback; update any named/default import expectations if needed to match the
module export.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9271317c-8f26-4dc0-ace3-40b4de451be2
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (75)
apps/dashboard/components.jsonapps/dashboard/index.htmlapps/dashboard/package.jsonapps/dashboard/src/components/AuthGate.tsxapps/dashboard/src/components/CommandPalette.tsxapps/dashboard/src/components/CopyButton.tsxapps/dashboard/src/components/ErrorBoundary.tsxapps/dashboard/src/components/Layout.tsxapps/dashboard/src/components/RelativeTime.tsxapps/dashboard/src/components/SessionTable.tsxapps/dashboard/src/components/StatCard.tsxapps/dashboard/src/components/StatusBadge.tsxapps/dashboard/src/components/Terminal.tsxapps/dashboard/src/components/UpdateBanner.tsxapps/dashboard/src/components/setup/CredentialsStep.tsxapps/dashboard/src/components/setup/FirstRunStep.tsxapps/dashboard/src/components/setup/GitHubStep.tsxapps/dashboard/src/components/setup/ServerStep.tsxapps/dashboard/src/components/topology/DaemonNode.tsxapps/dashboard/src/components/ui/alert.tsxapps/dashboard/src/components/ui/badge.tsxapps/dashboard/src/components/ui/breadcrumb.tsxapps/dashboard/src/components/ui/button.tsxapps/dashboard/src/components/ui/card.tsxapps/dashboard/src/components/ui/command.tsxapps/dashboard/src/components/ui/dialog.tsxapps/dashboard/src/components/ui/dropdown-menu.tsxapps/dashboard/src/components/ui/input-group.tsxapps/dashboard/src/components/ui/input.tsxapps/dashboard/src/components/ui/label.tsxapps/dashboard/src/components/ui/progress.tsxapps/dashboard/src/components/ui/scroll-area.tsxapps/dashboard/src/components/ui/select.tsxapps/dashboard/src/components/ui/separator.tsxapps/dashboard/src/components/ui/sheet.tsxapps/dashboard/src/components/ui/skeleton.tsxapps/dashboard/src/components/ui/sonner.tsxapps/dashboard/src/components/ui/table.tsxapps/dashboard/src/components/ui/tabs.tsxapps/dashboard/src/components/ui/textarea.tsxapps/dashboard/src/components/ui/tooltip.tsxapps/dashboard/src/hooks/useRelativeTime.tsapps/dashboard/src/index.cssapps/dashboard/src/lib/utils.tsapps/dashboard/src/main.tsxapps/dashboard/src/pages/AuditLog.tsxapps/dashboard/src/pages/Daemons.tsxapps/dashboard/src/pages/Fleet.tsxapps/dashboard/src/pages/Integrations.tsxapps/dashboard/src/pages/McpServers.tsxapps/dashboard/src/pages/Provision.tsxapps/dashboard/src/pages/Servers.tsxapps/dashboard/src/pages/SessionDetail.tsxapps/dashboard/src/pages/Sessions.tsxapps/dashboard/src/pages/Settings.tsxapps/dashboard/src/pages/Setup.tsxapps/dashboard/src/pages/Snapshots.tsxapps/dashboard/src/pages/Templates.tsxapps/dashboard/src/pages/Tunnels.tsxapps/dashboard/src/router.tsxapps/dashboard/vite.config.tspackage.jsonpackages/cli/src/commands/run.tsxpackages/cli/src/commands/status.tsxpackages/cli/src/ui/ProgressChecklist.tsxpackages/cli/src/ui/StatusTable.tsxpackages/domains/audit/tsconfig.jsonpackages/domains/browser/tsconfig.jsonpackages/domains/common/tsconfig.jsonpackages/domains/daemon/tsconfig.jsonpackages/domains/fleet/tsconfig.jsonpackages/domains/mcp/tsconfig.jsonpackages/domains/network/tsconfig.jsonpackages/domains/session/tsconfig.jsonpackages/domains/snapshot/tsconfig.json
💤 Files with no reviewable changes (1)
- packages/cli/src/commands/status.tsx
| useEffect(() => { | ||
| function onKeyDown(e: KeyboardEvent) { | ||
| if ((e.metaKey || e.ctrlKey) && e.key === 'k') { | ||
| e.preventDefault(); | ||
| setOpen((prev) => !prev); | ||
| } | ||
| } | ||
| document.addEventListener('keydown', onKeyDown); | ||
| return () => document.removeEventListener('keydown', onKeyDown); | ||
| }, []); |
There was a problem hiding this comment.
Don't steal Ctrl/Cmd+K from other editable fields.
The global handler toggles the palette even when focus is inside an input, textarea, select, or contenteditable region elsewhere in the app.
⌨️ Proposed fix
useEffect(() => {
function onKeyDown(e: KeyboardEvent) {
- if ((e.metaKey || e.ctrlKey) && e.key === 'k') {
+ const target = e.target as HTMLElement | null;
+ const inPalette = !!target?.closest('[data-slot="command"]');
+ if (
+ !inPalette &&
+ (target?.isContentEditable ||
+ target?.closest('input, textarea, select, [contenteditable="true"]'))
+ ) {
+ return;
+ }
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') {
e.preventDefault();
setOpen((prev) => !prev);
}
}📝 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.
| useEffect(() => { | |
| function onKeyDown(e: KeyboardEvent) { | |
| if ((e.metaKey || e.ctrlKey) && e.key === 'k') { | |
| e.preventDefault(); | |
| setOpen((prev) => !prev); | |
| } | |
| } | |
| document.addEventListener('keydown', onKeyDown); | |
| return () => document.removeEventListener('keydown', onKeyDown); | |
| }, []); | |
| useEffect(() => { | |
| function onKeyDown(e: KeyboardEvent) { | |
| const target = e.target as HTMLElement | null; | |
| const inPalette = !!target?.closest('[data-slot="command"]'); | |
| if ( | |
| !inPalette && | |
| (target?.isContentEditable || | |
| target?.closest('input, textarea, select, [contenteditable="true"]')) | |
| ) { | |
| return; | |
| } | |
| if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k') { | |
| e.preventDefault(); | |
| setOpen((prev) => !prev); | |
| } | |
| } | |
| document.addEventListener('keydown', onKeyDown); | |
| return () => document.removeEventListener('keydown', onKeyDown); | |
| }, []); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/CommandPalette.tsx` around lines 33 - 42, The
global keydown handler in CommandPalette.tsx toggles the palette even when focus
is inside editable fields; update the onKeyDown inside the useEffect to
early-return when the event target is an editable element by checking (e.target
instanceof HTMLElement) and then testing tagName (INPUT, TEXTAREA, SELECT),
contentEditable (element.isContentEditable), or a design-specific editable
attribute/class if used; leave the rest of the logic (the metaKey/ctrlKey + 'k'
check and setOpen toggle) unchanged so the palette only toggles when focus is
not within an input/textarea/select/contenteditable region.
| async function handleCopy() { | ||
| await navigator.clipboard.writeText(value); | ||
| setCopied(true); | ||
| toast.success(label ? `Copied ${label}` : 'Copied to clipboard'); | ||
| setTimeout(() => setCopied(false), 2000); | ||
| } |
There was a problem hiding this comment.
Missing error handling for clipboard API.
navigator.clipboard.writeText() can throw (e.g., if clipboard permissions are denied or the API is unavailable in certain contexts). The promise rejection would be unhandled, potentially causing console errors.
Suggested fix for both handleCopy functions
async function handleCopy() {
+ try {
await navigator.clipboard.writeText(value);
setCopied(true);
toast.success(label ? `Copied ${label}` : 'Copied to clipboard');
setTimeout(() => setCopied(false), 2000);
+ } catch {
+ toast.error('Failed to copy to clipboard');
+ }
}Also applies to: 50-55
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/CopyButton.tsx` around lines 15 - 20, The
handleCopy function uses navigator.clipboard.writeText without error handling;
wrap the await call in a try/catch inside handleCopy (and the other handleCopy
at lines 50–55), on success keep setCopied(true) and toast.success as-is, on
failure catch the error, call toast.error with a clear message (include
error.message), ensure setCopied(false) is set or left unchanged on error, and
optionally log the error via console.error to aid debugging; reference the
handleCopy function and navigator.clipboard.writeText when making the change.
| function ThemeToggle({ collapsed }: { collapsed?: boolean }) { | ||
| const [theme, setTheme] = useState<'dark' | 'light'>(() => { | ||
| return document.documentElement.classList.contains('dark') ? 'dark' : 'light'; | ||
| }); | ||
|
|
||
| function toggle() { | ||
| const next = theme === 'dark' ? 'light' : 'dark'; | ||
| setTheme(next); | ||
| document.documentElement.classList.toggle('dark', next === 'dark'); | ||
| localStorage.setItem('paws_theme', next); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cd apps/dashboard/src/components && wc -l Layout.tsxRepository: arek-e/paws
Length of output: 69
🏁 Script executed:
cd apps/dashboard/src/components && head -n 350 Layout.tsx | tail -n +120Repository: arek-e/paws
Length of output: 8275
🏁 Script executed:
cd apps/dashboard/src/components && cat -n Layout.tsx | tail -n +200Repository: arek-e/paws
Length of output: 7574
🏁 Script executed:
cd apps/dashboard/src/components && rg -i "context\|provider\|theme" Layout.tsx | head -20Repository: arek-e/paws
Length of output: 37
🏁 Script executed:
cd apps/dashboard && find . -name "*.tsx" -o -name "*.ts" | xargs rg -l "createContext.*theme\|ThemeContext\|useTheme" 2>/dev/null | head -10Repository: arek-e/paws
Length of output: 37
🏁 Script executed:
cd apps/dashboard/src/components && rg -B3 -A3 "ChevronsRight\|ChevronsLeft\|size=\"icon\"" Layout.tsxRepository: arek-e/paws
Length of output: 37
🏁 Script executed:
cd apps/dashboard && find . -name "Button.tsx" -o -name "Button.ts" | head -5Repository: arek-e/paws
Length of output: 37
🏁 Script executed:
cd apps/dashboard && find . -name "Button*" -type fRepository: arek-e/paws
Length of output: 37
🏁 Script executed:
cd apps/dashboard/src/components && grep -n "aria-label\|title=" Layout.tsxRepository: arek-e/paws
Length of output: 37
🏁 Script executed:
cd apps/dashboard && find . -path ./node_modules -prune -o -name "*Button*" -type f -print 2>/dev/null | head -20Repository: arek-e/paws
Length of output: 86
🏁 Script executed:
cd apps/dashboard && ls -la src/components/ | head -20Repository: arek-e/paws
Length of output: 1268
🏁 Script executed:
cd apps/dashboard/src/components && cat -n ErrorBoundary.tsxRepository: arek-e/paws
Length of output: 2452
🏁 Script executed:
cd apps/dashboard/src && find . -path ./node_modules -prune -o -name "*ui*" -type d -print 2>/dev/nullRepository: arek-e/paws
Length of output: 70
🏁 Script executed:
cd apps/dashboard && rg "useEffect|addEventListener|localStorage.getItem.*theme" src/components/Layout.tsxRepository: arek-e/paws
Length of output: 37
🏁 Script executed:
cd apps/dashboard && rg -A5 "useEffect" src/components/Layout.tsxRepository: arek-e/paws
Length of output: 37
🏁 Script executed:
cd apps/dashboard/src/components && sed -n '130,160p' Layout.tsxRepository: arek-e/paws
Length of output: 1090
Synchronize theme state across both toggle instances.
Lines 223 and 298 instantiate separate ThemeToggle components in mobile and desktop layouts. Each maintains its own theme state initialized from the DOM on mount. When one instance toggles, it updates the DOM and localStorage but the other instance stays mounted with stale state. On viewport changes, the newly visible toggle renders with the wrong mode and its first click performs no operation because toggle() compares the stale theme value against the actual DOM state.
Move theme state to the parent Layout component and pass it down to both ThemeToggle instances, or extract theme logic into a custom hook that reads from a consistent source (DOM, context, or shared ref).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/Layout.tsx` around lines 130 - 139, The two
ThemeToggle instances maintain independent state causing them to fall
out-of-sync; move the theme state up or centralize it so both toggles
read/update the same source: lift the useState initialization from ThemeToggle
into the parent Layout (or create a useTheme hook) and pass theme and a toggle
handler down to both ThemeToggle components; ensure the handler updates the
shared state (replacing setTheme in ThemeToggle), calls
document.documentElement.classList.toggle('dark', next === 'dark'), and writes
localStorage.setItem('paws_theme', next) so both instances reflect the current
theme and toggle reliably.
| <Tooltip> | ||
| <TooltipTrigger render={<span className="block" />}> | ||
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={toggle} | ||
| className="w-full justify-center px-0 text-xs text-muted-foreground" | ||
| > | ||
| {theme === 'dark' ? <Sun className="h-3.5 w-3.5" /> : <Moon className="h-3.5 w-3.5" />} | ||
| </Button> | ||
| </TooltipTrigger> | ||
| <TooltipContent side="right">{label}</TooltipContent> | ||
| </Tooltip> |
There was a problem hiding this comment.
Add accessible names to the icon-only controls.
These controls currently rely on icons or tooltips for meaning, so screen readers will announce them as unlabeled buttons or just “L”. Add aria-label or visually hidden text for the menu, theme, search, logout, and collapsed-sidebar actions.
Also applies to: 206-212, 275-285, 302-308, 330-346
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/Layout.tsx` around lines 145 - 157, The
icon-only Button inside the Tooltip (the Button rendered within TooltipTrigger
that calls toggle and switches icons using theme, Sun, and Moon) lacks an
accessible name; update this and the other icon-only controls (menu, theme,
search, logout, collapsed-sidebar) to include either an aria-label or visible
hidden text so screen readers can announce their purpose. Specifically, add an
aria-label prop to the Button that uses the existing label or a descriptive
string (e.g., aria-label={label || "Toggle theme"}) or render a visually-hidden
span inside the Button, and apply the same change to the other components that
render icon-only Buttons/TooltipTriggers so each interactive element has a clear
accessible name. Ensure you modify the Button instances tied to toggle/theme and
the corresponding icon-only actions across Layout.tsx.
| <ErrorBoundary> | ||
| <div key={location.pathname} className="h-full animate-page-in"> | ||
| <Outlet /> | ||
| </div> | ||
| </ErrorBoundary> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and read the Layout.tsx file
find . -name "Layout.tsx" -path "*/apps/dashboard/*" | head -5Repository: arek-e/paws
Length of output: 97
🏁 Script executed:
# Also check if the file exists at the specified path
test -f "apps/dashboard/src/components/Layout.tsx" && echo "File exists" || echo "File not found"Repository: arek-e/paws
Length of output: 66
🏁 Script executed:
# Read the Layout.tsx file to see the code around lines 353-357 and 361-365
wc -l apps/dashboard/src/components/Layout.tsxRepository: arek-e/paws
Length of output: 99
🏁 Script executed:
# Read the relevant sections
cat -n apps/dashboard/src/components/Layout.tsx | sed -n '340,375p'Repository: arek-e/paws
Length of output: 1143
🏁 Script executed:
# Look for ErrorBoundary component definition
find . -type f \( -name "*.tsx" -o -name "*.ts" -o -name "*.jsx" -o -name "*.js" \) | xargs grep -l "class.*ErrorBoundary\|function.*ErrorBoundary" | head -10Repository: arek-e/paws
Length of output: 104
🏁 Script executed:
# Also check the imports at the top of Layout.tsx to see where ErrorBoundary comes from
head -30 apps/dashboard/src/components/Layout.tsxRepository: arek-e/paws
Length of output: 888
🏁 Script executed:
# Read the ErrorBoundary component
cat -n apps/dashboard/src/components/ErrorBoundary.tsxRepository: arek-e/paws
Length of output: 2452
Put key={location.pathname} on the ErrorBoundary component, not the inner div.
Keying only the inner <div> remounts that element while the ErrorBoundary component instance persists, so its error state doesn't reset. If a route crashes, the error boundary will keep showing the previous error on subsequent routes until the user clicks "Try again" or reloads. Move the key to the ErrorBoundary itself so it unmounts and remounts with fresh state on each route change.
💡 Minimal fix
- <ErrorBoundary>
- <div key={location.pathname} className="h-full animate-page-in">
+ <ErrorBoundary key={location.pathname}>
+ <div className="h-full animate-page-in">
<Outlet />
</div>
</ErrorBoundary>
@@
- <ErrorBoundary>
- <div key={location.pathname} className="animate-page-in">
+ <ErrorBoundary key={location.pathname}>
+ <div className="animate-page-in">
<Outlet />
</div>
</ErrorBoundary>Also applies to: 361-365
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/Layout.tsx` around lines 353 - 357, The
ErrorBoundary is keyed incorrectly: move the key={location.pathname} from the
inner div to the ErrorBoundary component so the ErrorBoundary instance
unmounts/remounts on route changes and its internal error state resets; locate
the ErrorBoundary wrapping the div containing <Outlet /> and apply the key to
ErrorBoundary (using the same location.pathname value) and remove the key from
the inner div.
| async function doDisconnect() { | ||
| setDeleting(true); | ||
| try { | ||
| await deleteCloudConnection(conn.id); | ||
| onRefresh(); | ||
| } catch (e) { | ||
| setErr(e instanceof Error ? e.message : 'Failed'); | ||
| setDeleting(false); | ||
| } |
There was a problem hiding this comment.
Always clear the disconnect loading state.
deleting is only reset in the error path. If the delete succeeds but the parent refresh is slow or doesn't drop this row immediately, the card stays stuck with the action disabled.
💡 Minimal fix
async function doDisconnect() {
+ setErr('');
setDeleting(true);
try {
await deleteCloudConnection(conn.id);
- onRefresh();
+ void onRefresh();
} catch (e) {
setErr(e instanceof Error ? e.message : 'Failed');
- setDeleting(false);
+ } finally {
+ setDeleting(false);
}
}📝 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.
| async function doDisconnect() { | |
| setDeleting(true); | |
| try { | |
| await deleteCloudConnection(conn.id); | |
| onRefresh(); | |
| } catch (e) { | |
| setErr(e instanceof Error ? e.message : 'Failed'); | |
| setDeleting(false); | |
| } | |
| async function doDisconnect() { | |
| setErr(''); | |
| setDeleting(true); | |
| try { | |
| await deleteCloudConnection(conn.id); | |
| void onRefresh(); | |
| } catch (e) { | |
| setErr(e instanceof Error ? e.message : 'Failed'); | |
| } finally { | |
| setDeleting(false); | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/pages/Integrations.tsx` around lines 263 - 271, The
disconnect flow in doDisconnect sets deleting(true) but only resets it in the
catch path, so the UI can remain stuck; update doDisconnect (the async function
that calls deleteCloudConnection(conn.id) and onRefresh()) to always call
setDeleting(false) after the operation completes (use a finally block or ensure
setDeleting(false) runs after await deleteCloudConnection and after onRefresh())
and preserve the existing error handling (setErr) for exceptions.
| <div className="grid grid-cols-1 md:grid-cols-2 gap-3 mb-3"> | ||
| <div> | ||
| <Label className="text-xs text-zinc-400 mb-1">Name</Label> | ||
| <Input | ||
| type="text" | ||
| value={name} | ||
| onChange={(e) => setName(e.target.value)} | ||
| placeholder="filesystem" | ||
| required | ||
| className="bg-zinc-800 border-zinc-700 text-zinc-100 placeholder-zinc-600 focus-visible:border-emerald-400/50 focus-visible:ring-emerald-400/20" | ||
| /> | ||
| </div> | ||
| <div> | ||
| <Label className="text-xs text-zinc-400 mb-1">Transport</Label> | ||
| <select | ||
| value={transport} | ||
| onChange={(e) => setTransport(e.target.value as typeof transport)} | ||
| className="w-full px-3 py-2 text-sm bg-zinc-800 border border-zinc-700 rounded text-zinc-100 focus:outline-none focus:border-emerald-400/50" | ||
| > | ||
| <option value="stdio">stdio</option> | ||
| <option value="sse">SSE</option> | ||
| <option value="streamable-http">Streamable HTTP</option> | ||
| </select> | ||
| </div> |
There was a problem hiding this comment.
Associate each Label with its control.
These labels are only visual right now. Without matching htmlFor/id pairs, the add-server form is effectively unlabeled for assistive tech.
♿ Proposed fix
- <Label className="text-xs text-zinc-400 mb-1">Name</Label>
+ <Label htmlFor="mcp-server-name" className="text-xs text-zinc-400 mb-1">
+ Name
+ </Label>
<Input
+ id="mcp-server-name"
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="filesystem"
required
className="bg-zinc-800 border-zinc-700 text-zinc-100 placeholder-zinc-600 focus-visible:border-emerald-400/50 focus-visible:ring-emerald-400/20"
/>
</div>
<div>
- <Label className="text-xs text-zinc-400 mb-1">Transport</Label>
+ <Label htmlFor="mcp-server-transport" className="text-xs text-zinc-400 mb-1">
+ Transport
+ </Label>
<select
+ id="mcp-server-transport"
value={transport}
onChange={(e) => setTransport(e.target.value as typeof transport)}
className="w-full px-3 py-2 text-sm bg-zinc-800 border border-zinc-700 rounded text-zinc-100 focus:outline-none focus:border-emerald-400/50"
>
<option value="stdio">stdio</option>
<option value="sse">SSE</option>
<option value="streamable-http">Streamable HTTP</option>
</select>
</div>
...
- <Label className="text-xs text-zinc-400 mb-1">Command</Label>
+ <Label htmlFor="mcp-server-command" className="text-xs text-zinc-400 mb-1">
+ Command
+ </Label>
<Input
+ id="mcp-server-command"
type="text"
value={command}
onChange={(e) => setCommand(e.target.value)}
placeholder="npx"
required
className="bg-zinc-800 border-zinc-700 text-zinc-100 placeholder-zinc-600 focus-visible:border-emerald-400/50 focus-visible:ring-emerald-400/20"
/>
</div>
<div>
- <Label className="text-xs text-zinc-400 mb-1">Arguments</Label>
+ <Label htmlFor="mcp-server-args" className="text-xs text-zinc-400 mb-1">
+ Arguments
+ </Label>
<Input
+ id="mcp-server-args"
type="text"
value={args}
onChange={(e) => setArgs(e.target.value)}
placeholder="-y `@modelcontextprotocol/server-filesystem` /tmp"
className="bg-zinc-800 border-zinc-700 text-zinc-100 placeholder-zinc-600 focus-visible:border-emerald-400/50 focus-visible:ring-emerald-400/20"
/>
</div>
...
- <Label className="text-xs text-zinc-400 mb-1">URL</Label>
+ <Label htmlFor="mcp-server-url" className="text-xs text-zinc-400 mb-1">
+ URL
+ </Label>
<Input
+ id="mcp-server-url"
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="http://localhost:3001/mcp"
required
className="bg-zinc-800 border-zinc-700 text-zinc-100 placeholder-zinc-600 focus-visible:border-emerald-400/50 focus-visible:ring-emerald-400/20"
/>Also applies to: 199-235
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/pages/McpServers.tsx` around lines 173 - 196, The Label
components are not associated with their inputs; update the form by adding id
attributes to the input elements (e.g., the Input for name and the select for
transport) and set the corresponding Label htmlFor to those ids (e.g., id="name"
/ htmlFor="name" and id="transport" / htmlFor="transport"); apply the same
pattern to the other form block referenced (lines ~199-235) so every Label
component is paired with a matching control id to improve accessibility (look
for the Input, select, Label usages and the setName/setTransport handlers to
locate the correct controls).
| {'access' in ep && (ep as { access?: string }).access && ( | ||
| <Badge | ||
| variant="outline" | ||
| className={`rounded ${ | ||
| (ep as { access?: string }).access === 'sso' | ||
| ? 'bg-blue-400/10 text-blue-400 border-blue-400/20' | ||
| : (ep as { access?: string }).access === 'pin' | ||
| ? 'bg-amber-400/10 text-amber-400 border-amber-400/20' | ||
| : 'bg-purple-400/10 text-purple-400 border-purple-400/20' | ||
| }`} | ||
| > | ||
| {(ep as { access?: string }).access} | ||
| </Badge> | ||
| )} |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Find the ExposedPort type definition to verify current structure
ast-grep --pattern 'interface ExposedPort {
$$$
}'
# Also check for type alias
rg -n 'type ExposedPort' --type=tsRepository: arek-e/paws
Length of output: 391
🏁 Script executed:
# Find and examine the ExposedPortSchema definition
rg -n 'ExposedPortSchema' --type=ts -A 15 packages/domains/session/src/types.tsRepository: arek-e/paws
Length of output: 1800
🏁 Script executed:
# Also examine the SessionDetail.tsx file around the problematic lines
sed -n '160,230p' apps/dashboard/src/pages/SessionDetail.tsxRepository: arek-e/paws
Length of output: 3641
🏁 Script executed:
# Check the imports in SessionDetail.tsx and type of ep
head -50 apps/dashboard/src/pages/SessionDetail.tsxRepository: arek-e/paws
Length of output: 1959
🏁 Script executed:
# Search for how ExposedPort is imported/used in SessionDetail.tsx
rg -n 'ExposedPort\|import.*types\|import.*session' apps/dashboard/src/pages/SessionDetail.tsx -B 2 -A 2Repository: arek-e/paws
Length of output: 37
🏁 Script executed:
# Check the API client to see what getSession returns
find apps/dashboard -name 'client.ts' -o -name 'client.js' | head -5Repository: arek-e/paws
Length of output: 87
🏁 Script executed:
# Search for getSession function definition
rg -n 'getSession' apps/dashboard/src/api/ -A 5Repository: arek-e/paws
Length of output: 873
🏁 Script executed:
# Double-check the imports in client.ts to confirm Session and ExposedPort are from types
head -30 apps/dashboard/src/api/client.tsRepository: arek-e/paws
Length of output: 950
Remove unnecessary runtime checks and type assertions; the ExposedPort type already includes these properties.
The ExposedPort type (from @paws/domain-session) already fully defines the access, pin, and shareLink properties as optional. The repeated checks like 'access' in ep and type assertions like (ep as { access?: string }).access are unnecessary. Simplify by accessing the properties directly:
{ep.access && (
<Badge
variant="outline"
className={`rounded ${
ep.access === 'sso'
? 'bg-blue-400/10 text-blue-400 border-blue-400/20'
: ep.access === 'pin'
? 'bg-amber-400/10 text-amber-400 border-amber-400/20'
: 'bg-purple-400/10 text-purple-400 border-purple-400/20'
}`}
>
{ep.access}
</Badge>
)}Apply the same pattern for pin and shareLink properties.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/pages/SessionDetail.tsx` around lines 173 - 186, Remove
the redundant runtime checks and type assertions around ExposedPort properties
in SessionDetail.tsx: access the optional properties directly (ep.access,
ep.pin, ep.shareLink) instead of using `'access' in ep` or `(ep as { access?:
string }).access`; update the Badge rendering logic (Badge component usage) to
conditionally render based on ep.access / ep.pin / ep.shareLink truthiness and
simplify the className branching to use ep.access / ep.pin values directly, and
apply the same simplification for pin and shareLink handling so all assertions
and "in" checks are eliminated.
| {sessions.loading && !sessions.data ? ( | ||
| <div className="bg-zinc-900 border border-zinc-800 rounded-lg p-6 animate-pulse h-48" /> | ||
| <Skeleton className="h-48" /> | ||
| ) : sessions.error ? ( | ||
| <div className="bg-zinc-900 border border-zinc-800 rounded-lg p-8 text-center space-y-3"> | ||
| <pre className="text-zinc-600 text-xs leading-tight font-mono inline-block"> | ||
| {` /\\_/\\ | ||
| ( -.- ) zzz | ||
| > ^ <`} | ||
| </pre> | ||
| <p className="text-zinc-400 text-sm">No sessions yet.</p> | ||
| <p className="text-zinc-500 text-xs"> | ||
| Create a session via the API or CLI to see it here. | ||
| </p> | ||
| </div> | ||
| ) : sessions.data && sessions.data.sessions.length > 0 ? ( | ||
| <SessionTable sessions={sessions.data.sessions} /> | ||
| emptyState | ||
| ) : allSessions.length > 0 ? ( |
There was a problem hiding this comment.
Don't collapse polling failures into the empty state.
This branch currently shows “No sessions yet” whenever polling errors, and because it runs before the allSessions.length > 0 check it can also hide previously loaded data after a transient refresh failure. Render a real error state here, or move the error branch below the data branch so stale data stays visible.
💡 Minimal fix
{sessions.loading && !sessions.data ? (
<Skeleton className="h-48" />
- ) : sessions.error ? (
- emptyState
) : allSessions.length > 0 ? (
<>
<div className="flex items-center gap-3">
@@
</Card>
)}
</>
+ ) : sessions.error ? (
+ <Card className="bg-zinc-900 border-zinc-800 py-0 shadow-none">
+ <CardContent className="p-8 text-center space-y-2">
+ <p className="text-sm text-red-400">Failed to load sessions.</p>
+ <p className="text-xs text-zinc-500">{sessions.error.message}</p>
+ </CardContent>
+ </Card>
) : (
emptyState
)}📝 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.
| {sessions.loading && !sessions.data ? ( | |
| <div className="bg-zinc-900 border border-zinc-800 rounded-lg p-6 animate-pulse h-48" /> | |
| <Skeleton className="h-48" /> | |
| ) : sessions.error ? ( | |
| <div className="bg-zinc-900 border border-zinc-800 rounded-lg p-8 text-center space-y-3"> | |
| <pre className="text-zinc-600 text-xs leading-tight font-mono inline-block"> | |
| {` /\\_/\\ | |
| ( -.- ) zzz | |
| > ^ <`} | |
| </pre> | |
| <p className="text-zinc-400 text-sm">No sessions yet.</p> | |
| <p className="text-zinc-500 text-xs"> | |
| Create a session via the API or CLI to see it here. | |
| </p> | |
| </div> | |
| ) : sessions.data && sessions.data.sessions.length > 0 ? ( | |
| <SessionTable sessions={sessions.data.sessions} /> | |
| emptyState | |
| ) : allSessions.length > 0 ? ( | |
| {sessions.loading && !sessions.data ? ( | |
| <Skeleton className="h-48" /> | |
| ) : allSessions.length > 0 ? ( | |
| <> | |
| <div className="flex items-center gap-3"> | |
| {/* ... data rendering continues ... */} | |
| </div> | |
| </> | |
| ) : sessions.error ? ( | |
| <Card className="bg-zinc-900 border-zinc-800 py-0 shadow-none"> | |
| <CardContent className="p-8 text-center space-y-2"> | |
| <p className="text-sm text-red-400">Failed to load sessions.</p> | |
| <p className="text-xs text-zinc-500">{sessions.error.message}</p> | |
| </CardContent> | |
| </Card> | |
| ) : ( | |
| emptyState | |
| )} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/pages/Sessions.tsx` around lines 54 - 58, The current
conditional renders the emptyState when sessions.error is true which hides
previously loaded data and treats polling failures as "no sessions"; update the
rendering logic in Sessions.tsx to avoid collapsing errors into the empty state
by either (a) moving the sessions.error branch below the allSessions.length > 0
check so stale data (allSessions) remains visible on transient polling errors,
or (b) replace the sessions.error branch with a proper error UI (e.g., an
errorState element) so that when sessions.error is true you render the real
error state instead of emptyState; locate the conditional using
sessions.loading, sessions.data, sessions.error, allSessions and emptyState and
implement one of these fixes.
| @@ -1,9 +1,35 @@ | |||
| import path from 'node:path'; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
In Vite (v6+) with package.json "type": "module", is __dirname guaranteed in vite.config.ts? What alias syntax is officially recommended for ESM config files?
💡 Result:
No, dirname is not guaranteed in vite.config.ts when package.json has "type": "module" in Vite v6+. With "type": "module", Node treats .ts/.js files as ESM where dirname is unavailable natively. Vite preprocesses config files and injects dirname support (via replacements or injection), but issues show it fails in some cases like multipage apps or certain loaders, requiring manual polyfill. Manual polyfill (officially recommended workaround in docs/examples): import { dirname } from 'node:path' import { fileURLToPath } from 'node:url' const dirname = dirname(fileURLToPath(import.meta.url)) For aliases in ESM config files, official docs recommend resolve.alias as Record<string, string> or Array<{find: string|RegExp, replacement: string}> with absolute paths using the polyfill: Object syntax: resolve: { alias: { '@': path.resolve(dirname, 'src') } } Array syntax (for RegExp or complex): resolve: { alias: [ { find: '@', replacement: path.resolve(dirname, 'src') } ] } Examples from Vite docs, GitHub discussions (#19060), and community sources confirm this syntax works in ESM configs.
Citations:
- 1: https://v6.vite.dev/config
- 2: "__dirname is not defined in ES module scope" in vite.config.js with module type in package.json vitejs/vite#6946
- 3: Have a "Rollup failed to resolve import " problem. vitejs/vite#19060
- 4: feat: better config
__dirnamesupport vitejs/vite#8442 - 5: https://main.vitejs.dev/config/shared-options.html
- 6: https://vite.dev/config/
- 7: https://vite.dev/config/shared-options
- 8: https://stackoverflow.com/questions/68217795/vite-resolve-alias-how-to-resolve-paths
- 9: https://futurestud.io/tutorials/vite-create-resolve-aliases-for-imports-like-the-symbol
🏁 Script executed:
find . -path "*/apps/dashboard/vite.config.ts" -type fRepository: arek-e/paws
Length of output: 86
🏁 Script executed:
cat -n apps/dashboard/vite.config.ts | head -40Repository: arek-e/paws
Length of output: 1370
🏁 Script executed:
cat -n apps/dashboard/package.json | grep -A 2 -B 2 '"type"'Repository: arek-e/paws
Length of output: 214
Use ESM-safe alias resolution instead of __dirname.
In ESM modules ("type": "module" in package.json), __dirname is not guaranteed in vite.config.ts and Vite's dirname injection can fail depending on the loader mode. Replace with import.meta.url-based resolution.
Suggested change
-import path from 'node:path';
+import { fileURLToPath } from 'node:url';
...
resolve: {
alias: {
- '@': path.resolve(__dirname, './src'),
+ '@': fileURLToPath(new URL('./src', import.meta.url)),
},
},🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/vite.config.ts` at line 1, The vite.config.ts currently relies
on __dirname which is not ESM-safe; update any resolution logic that uses
__dirname (in this file) to use import.meta.url-based resolution instead—convert
import.meta.url to a filesystem path (e.g., via fileURLToPath(import.meta.url)
or new URL('./', import.meta.url) then path.resolve/path.dirname) and use that
value wherever __dirname was referenced (for plugin root, alias, or path.join
calls) so alias resolution works under ESM.
a073081 to
cc4ed57
Compare
- Remove unused imports (useCallback, Session) in run.tsx - Remove unused hasAlert variable in status.tsx - Fix exactOptionalPropertyTypes in ProgressChecklist.tsx and StatusTable.tsx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cc4ed57 to
d8b3b50
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/ci.yml:
- Around line 26-27: Change the mutable dependency install and set the Turbo env
at job level: update the run step that currently executes "bun install" to use a
frozen lockfile ("bun install --frozen-lockfile") and add the environment
variable TURBO_NO_DAEMON=1 to the job-level environment so it applies to all
steps instead of only inline on specific turbo commands; ensure existing inline
TURBO_NO_DAEMON usages remain safe or are removed after adding the job-level
env.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0732f364-332d-466e-86c0-65252a15aa6c
⛔ Files ignored due to path filters (1)
bun.lockis excluded by!**/*.lock
📒 Files selected for processing (13)
.github/workflows/ci.ymlpackages/cli/src/commands/run.tsxpackages/cli/src/commands/status.tsxpackages/cli/src/ui/ProgressChecklist.tsxpackages/cli/src/ui/StatusTable.tsxpackages/domains/audit/tsconfig.jsonpackages/domains/browser/tsconfig.jsonpackages/domains/daemon/tsconfig.jsonpackages/domains/fleet/tsconfig.jsonpackages/domains/mcp/tsconfig.jsonpackages/domains/network/tsconfig.jsonpackages/domains/session/tsconfig.jsonpackages/domains/snapshot/tsconfig.json
💤 Files with no reviewable changes (1)
- packages/cli/src/commands/status.tsx
✅ Files skipped from review due to trivial changes (8)
- packages/domains/snapshot/tsconfig.json
- packages/domains/mcp/tsconfig.json
- packages/domains/session/tsconfig.json
- packages/domains/network/tsconfig.json
- packages/domains/daemon/tsconfig.json
- packages/domains/fleet/tsconfig.json
- packages/domains/browser/tsconfig.json
- packages/cli/src/commands/run.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/domains/audit/tsconfig.json
- packages/cli/src/ui/StatusTable.tsx
- packages/cli/src/ui/ProgressChecklist.tsx
| bun-version: '1.3.9' | ||
| - run: bun install |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify workflow install commands that don't pin lockfile behavior
fd -e yml -e yaml .github/workflows | while read -r f; do
echo "== $f =="
rg -nP 'bun install(?!.*--frozen-lockfile)' "$f" || true
doneRepository: arek-e/paws
Length of output: 428
🏁 Script executed:
cat -n .github/workflows/ci.yml | head -60Repository: arek-e/paws
Length of output: 1473
🏁 Script executed:
grep -n "TURBO_NO_DAEMON" .github/workflows/ci.yml || echo "TURBO_NO_DAEMON not found"Repository: arek-e/paws
Length of output: 316
🏁 Script executed:
grep -n "bun install" .github/workflows/ci.ymlRepository: arek-e/paws
Length of output: 82
Add --frozen-lockfile to bun install and set TURBO_NO_DAEMON=1 at the job level.
Line 27 runs mutable dependency resolution (bun install), which can cause non-reproducible CI outcomes and unexpected dependency drift. Additionally, per CI workflow requirements, TURBO_NO_DAEMON=1 must be set to prevent Turbo daemon hangs; currently it's only applied inline to specific turbo commands (lines 33, 39) rather than at the job level.
Suggested fixes
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
+ TURBO_NO_DAEMON: 1- - run: bun install
+ - run: bun install --frozen-lockfile🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.github/workflows/ci.yml around lines 26 - 27, Change the mutable dependency
install and set the Turbo env at job level: update the run step that currently
executes "bun install" to use a frozen lockfile ("bun install
--frozen-lockfile") and add the environment variable TURBO_NO_DAEMON=1 to the
job-level environment so it applies to all steps instead of only inline on
specific turbo commands; ensure existing inline TURBO_NO_DAEMON usages remain
safe or are removed after adding the job-level env.
Summary
useCallback,Session) inrun.tsxhasAlertvariable instatus.tsxexactOptionalPropertyTypesinProgressChecklist.tsxandStatusTable.tsxcomposite: trueto all 8 domain package tsconfigsThese errors were blocking the pre-push hook and CI typecheck step.
Test plan
turbo run typecheck --filter='@paws/cli'passesturbo run typecheck --filter='!site'— only pre-existing dashboardexactOptionalPropertyTypeserrors remain (same on main)🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
Chores
Bug Fixes