light mode - #4

Merged
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components
Feb 9, 2026
Merged

light mode#4
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 9, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-02-08 at 22 05 13@2xCleanShot 2026-02-08 at 22 05 17@2x

Summary by CodeRabbit

  • New Features

    • Large library of reusable UI components added (accordions, dialogs, menus, inputs, selects, command palette, toasts, sheets, tables, etc.) for richer, consistent interfaces.
  • Theme

    • Dark theme support with system detection, cross-tab sync, and a theme toggle in the sidebar.
  • Style

    • Replaced hard-coded colors with semantic design tokens and global CSS variables for consistent light/dark theming and improved visuals.

@coderabbitai

coderabbitaiBot commented Feb 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds a large set of new UI components and utilities to the renderer, introduces a CSS-variable based theme (light/dark/system) and a useTheme hook, replaces hard-coded color tokens with semantic tokens across core renderer components, adds a cn utility and components.json, adjusts deps/config and a small smoke-test script tweak.

Changes

Cohort / File(s)Summary
UI component library
apps/renderer/src/components/ui/*
Adds 30+ new UI modules (accordion, alert, autocomplete, avatar, badge, button, card, checkbox, collapsible, combobox, command, dialog, empty, field, fieldset, form, input-group, input, kbd, label, menu, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, spinner, switch, table, tabs, textarea, toast). Each wraps @base-ui/react primitives, applies data-slot attributes, CVA variants and composed Tailwind classes. Review API shapes, typings, and consistent class/slot usage.
Core renderer components
apps/renderer/src/App.tsx, apps/renderer/src/components/ChatView.tsx, apps/renderer/src/components/ChatMarkdown.tsx, apps/renderer/src/components/DiffPanel.tsx, apps/renderer/src/components/Sidebar.tsx
Replaces hex color classes with semantic tokens; ChatView integrates new InputGroup, Select, Button components and removes previous textarea auto-resize logic; ChatMarkdown and DiffPanel styling tokens updated; Sidebar adds theme toggle and theme-aware styling. Validate behavioral changes where logic was removed (e.g., textarea resizing, model menu handling).
Theming & CSS
apps/renderer/src/index.css, apps/renderer/src/hooks/useTheme.ts
Introduces CSS variable theme system with light/dark variants and a .no-transitions helper; adds many CSS token definitions and dark overrides. Adds useTheme hook (light
Utilities & small libs
apps/renderer/src/lib/utils.ts, apps/renderer/components.json
Adds cn(...inputs) util combining clsx + twMerge. Adds components.json with UI framework config, aliases, Tailwind/tokens and registries. Confirm imports use the new alias and utility.
Config & build
apps/renderer/tsconfig.json, apps/renderer/vite.config.ts, biome.json
Adds #/* path alias and resolves # in Vite; sets exactOptionalPropertyTypes: false in tsconfig; relaxes biome a11y rule noSvgWithoutTitle.
Dependencies
apps/renderer/package.json
Adds runtime deps including @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge and reorders react-markdown. Verify lockfile and install.
Markup & scripts
apps/renderer/index.html, apps/desktop/scripts/smoke-test.mjs
Removes body Tailwind class in index.html. Small smoke-test script tweak: reorder imports from node:child_process and use template literal for error output. Minor script change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check❓ InconclusiveThe PR title 'light mode' is vague and does not clearly describe the actual scope of changes, which include comprehensive UI component library implementation, theming system, styling updates, and design system token migration across the renderer application.Consider a more descriptive title that captures the main work, such as 'Add design system UI components and light/dark theme support' or 'Implement base UI component library with theming.'
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch juliusmarminge/use-base-components

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/renderer/src/components/ChatView.tsx (1)

312-344: Consider using cn utility for conditional class handling.

The deeply nested ternaries for status indicator styling reduce readability. Using the cn utility with object syntax would make the phase-based styling clearer and more maintainable.

♻️ Suggested refactor using cn utility
+const statusContainerClasses: Record<string, string> = {+ running: "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100",+ connecting: "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100",+ ready: "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100",+ disconnected: "border-border bg-secondary text-muted-foreground",+};++const statusDotClasses: Record<string, string> = {+ running: "bg-sky-200",+ connecting: "bg-amber-200",+ ready: "bg-emerald-200",+ disconnected: "bg-muted-foreground/40",+};

Then use:

<divclassName={cn("inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px]",statusContainerClasses[phase]??statusContainerClasses.disconnected)}>

Based on learnings: "Use Tailwind CSS for styling - no inline styles, use cn for conditional classes".

apps/renderer/src/components/Sidebar.tsx (1)

138-152: Prefer Coss UI for the new icon-only theme toggle.

To keep styling and interaction patterns consistent with the rest of the app, consider swapping this raw <button> for the equivalent Coss UI icon/button primitive.

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeappBot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Add light/dark/system theme cycling and apply token-based theming across Sidebar, ChatView, and global styles to introduce light mode

Introduce a useTheme hook with localStorage persistence and a Sidebar header button to cycle system/light/dark; switch global and app styling to CSS variables and semantic Tailwind tokens; refactor ChatView and Sidebar UI to use shared UI primitives; remove thread status badges, editor/approval flows, and keyboard shortcuts; update model selection in new project flow.

📍Where to Start

Start with the useTheme hook in useTheme.ts and its integration in the Sidebar in Sidebar.tsx, then review theme token application in index.css.


Macroscope summarized 0a535ae.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/Sidebar.tsx`:
- Around line 225-230: The button currently uses the disabled prop which
conflicts with accessibility guidance and tooltips; instead remove the disabled
attribute on the button element, add aria-disabled={isPickingFolder} to reflect
state, keep/adjust the visual "disabled" classes conditionally when
isPickingFolder, and add a click guard at the top of handlePickFolder (or inline
in the onClick wrapper) that returns early if isPickingFolder so clicks are
ignored; ensure tooltip behavior is preserved by using aria-disabled rather than
disabled.
In `@apps/renderer/src/components/ui/combobox.tsx`:
- Around line 196-214: The SVG in the Combobox component uses an incorrect xmlns
value; update the xmlns on the SVG inside ComboboxPrimitive.ItemIndicator to the
correct "http://www.w3.org/2000/svg" string so the icon renders properly (look
for the SVG element in the ComboboxPrimitive.ItemIndicator within the
combobox.tsx component and change the xmlns attribute value).
In `@apps/renderer/src/components/ui/empty.tsx`:
- Around line 94-105: The component EmptyDescription is typed as
React.ComponentProps<"p"> but renders a <div>, causing a props/semantic
mismatch; fix it by making the types and markup consistent—either change the
rendered element to <p> (keep the same props type) or change the prop type to
React.ComponentProps<"div"> (keep the <div>); update the function signature
(EmptyDescription) accordingly and ensure spreading {...props}, className and
data-slot="empty-description" remain correct for the chosen element.
In `@apps/renderer/src/components/ui/scroll-area.tsx`:
- Around line 5-35: Replace the custom use of ScrollAreaPrimitive
(ScrollAreaPrimitive.Root, ScrollAreaPrimitive.Viewport,
ScrollAreaPrimitive.Corner) and the local ScrollBar components with the Coss UI
ScrollArea wrapper: install the package via the shadcn CLI (`pnpm dlx
shadcn@latest add `@coss/scroll-area``), import Coss's ScrollArea,
ScrollAreaViewport and Corner (or the exported equivalents) and re-create this
component as a thin wrapper named ScrollArea that forwards props and children
while preserving the scrollFade and scrollbarGutter booleans by mapping them to
equivalent className masks and gutter classes on the Coss component (apply the
same mask expressions and `data-has-overflow-*` gutter classes), and ensure
data-slot attributes (scroll-area-viewport, scroll-area-corner) remain on the
mapped children so existing consumers continue to work.
In `@apps/renderer/src/components/ui/select.tsx`:
- Around line 128-142: The SVG used inside SelectPrimitive.ItemIndicator has a
typo in its xmlns attribute; update the xmlns on the SVG element within
SelectPrimitive.ItemIndicator to the correct namespace
"http://www.w3.org/2000/svg" so the checkmark renders correctly across browsers
(locate the SVG in the SelectPrimitive.ItemIndicator block and replace the
incorrect "http://www.w3.org/1500/svg" value).
In `@apps/renderer/src/components/ui/skeleton.tsx`:
- Around line 1-3: The file uses React.ComponentProps<"div"> in the Skeleton
component but doesn't import the React namespace; add an import for React (e.g.,
add "import React from 'react'" or "import type React from 'react'") at the top
of apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.
🧹 Nitpick comments (6)
apps/renderer/src/components/ui/table.tsx (1)

36-47: Consider extracting long class strings for maintainability.

The class string on line 40 is quite long with many frame-conditional styles. While this is a common pattern in shadcn-style UI libraries, consider extracting the frame-specific styles into a separate variable or using Tailwind's @apply directive in a CSS file for better readability.

That said, this is a stylistic preference and the current implementation is functionally correct.

apps/renderer/src/components/ui/separator.tsx (1)

13-13: The CSS selector for vertical self-stretch is fragile and likely won't work as intended.

The selector not-[[class^='h-']]:not-[[class*='_h-']] checks whether the class attribute string starts with 'h-' or contains '_h-'. Since other classes (e.g., shrink-0) will appear first in the composed className, the [class^='h-'] condition will almost never match—even when a height utility like h-10 is present.

Consider simplifying by always applying self-stretch for vertical orientation and letting explicit height classes override it naturally, or document that consumers should use inline styles / CSS variables for custom heights:

♻️ Suggested simplification
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
apps/renderer/src/components/ui/toast.tsx (1)

220-250: Consider extracting shared toast content into a reusable component.

The toast content rendering here (icon, title, description, action button) is nearly identical to lines 128-158 in the Toasts component. Extracting this into a shared ToastContent component would reduce duplication and ensure consistency when making future changes.

♻️ Suggested extraction
// Shared component to use in both Toasts and AnchoredToastsfunctionToastContentBody({ toast }: {toast: Toast.ToastObject}){constIcon=toast.type
? TOAST_ICONS[toast.typeaskeyoftypeofTOAST_ICONS]
: null;return(<Toast.ContentclassName="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm ..."><divclassName="flex gap-2">{Icon&&(<divclassName="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"data-slot="toast-icon"><IconclassName="in-data-[type=loading]:animate-spin ..."/></div>)}<divclassName="flex flex-col gap-0.5"><Toast.TitleclassName="font-medium"data-slot="toast-title"/><Toast.DescriptionclassName="text-muted-foreground"data-slot="toast-description"/></div></div>{toast.actionProps&&(<Toast.ActionclassName={buttonVariants({size: "xs"})}data-slot="toast-action">{toast.actionProps.children}</Toast.Action>)}</Toast.Content>);}
apps/renderer/src/components/ui/kbd.tsx (1)

5-26: Prefer Coss UI primitives for consistency.

These custom Kbd/KbdGroup wrappers diverge from the guideline to use Coss UI components (installed via the ShadCN CLI) for consistent styling and Base UI accessibility alignment. If a Coss kbd component exists, consider swapping to it (or document why bespoke wrappers are required).

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling. Install new components using the ShadCN CLI (e.g., bunx --bun shadcnlatest add coss/button). Refer to Base UI docs for accessibility guidelines on primitives.

apps/renderer/src/components/ui/textarea.tsx (1)

7-10: Consider documenting or removing the number type for size.

The size prop accepts "sm" | "default" | "lg" | number, but the internal styling logic (lines 35-38) only handles the string literals. When a number is passed, it's set on data-size but no corresponding styles are applied.

If numeric sizes are intended for custom CSS handling via data-size, consider adding a brief comment documenting this. Otherwise, consider restricting the type to the supported string literals only.

apps/renderer/src/index.css (1)

227-242: Consider using CSS variables for scrollbar colors for consistency.

The scrollbar styling uses hardcoded rgba() values while the rest of the file uses CSS variables. For better theme consistency, consider using the defined tokens:

♻️ Optional: Use CSS variables for scrollbar colors
 ::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);+ background: var(--muted-foreground);+ opacity: 0.15;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 0, 0, 0.25);+ background: var(--muted-foreground);+ opacity: 0.25;
}
.dark ::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);+ background: var(--muted-foreground);+ opacity: 0.1;
}
.dark ::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.18);+ background: var(--muted-foreground);+ opacity: 0.18;
}

Note: Opacity on scrollbar thumbs may have limited browser support; the current approach works reliably.

Comment on lines 225 to 230
<button
type="button"
className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => void handlePickFolder()}
disabled={isPickingFolder}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid the disabled prop; use aria-disabled and a click guard instead.

The current disabled usage conflicts with the repo’s accessibility guidance and can break tooltips. Use styling plus a guard in the handler.

🛠️ Suggested fix
- className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"- onClick={() => void handlePickFolder()}- disabled={isPickingFolder}+ className={`mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary ${+ isPickingFolder ? "cursor-not-allowed opacity-60" : ""+ }`}+ aria-disabled={isPickingFolder}+ onClick={() => {+ if (isPickingFolder) return;+ void handlePickFolder();+ }}
Based on learnings: Avoid using disabled props on buttons as they harm accessibility and break tooltips.
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/Sidebar.tsx` around lines 225 - 230, The button
currently uses the disabled prop which conflicts with accessibility guidance and
tooltips; instead remove the disabled attribute on the button element, add
aria-disabled={isPickingFolder} to reflect state, keep/adjust the visual
"disabled" classes conditionally when isPickingFolder, and add a click guard at
the top of handlePickFolder (or inline in the onClick wrapper) that returns
early if isPickingFolder so clicks are ignored; ensure tooltip behavior is
preserved by using aria-disabled rather than disabled.

Comment on lines +196 to +214
<ComboboxPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</ComboboxPrimitive.ItemIndicator>
<div className="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

Same issue as in select.tsx: the xmlns attribute has http://www.w3.org/1500/svg instead of http://www.w3.org/2000/svg.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/combobox.tsx` around lines 196 - 214, The SVG
in the Combobox component uses an incorrect xmlns value; update the xmlns on the
SVG inside ComboboxPrimitive.ItemIndicator to the correct
"http://www.w3.org/2000/svg" string so the icon renders properly (look for the
SVG element in the ComboboxPrimitive.ItemIndicator within the combobox.tsx
component and change the xmlns attribute value).

Comment on lines +94 to +105
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: Component typed as <p> but renders <div>.

EmptyDescription accepts React.ComponentProps<"p"> but renders a <div>. This inconsistency could confuse consumers and allows <p>-specific props that won't apply to the rendered <div>.

💡 Proposed fix

Either change the rendered element to match the type:

-function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {+function EmptyDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div

Or render a <p> to match the type (if semantically appropriate):

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
- <div+ <p
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
- />+ />
);
}
📝 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.

Suggested change
functionEmptyDescription({ className, ...props}: React.ComponentProps<"p">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
functionEmptyDescription({ className, ...props}: React.ComponentProps<"div">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/empty.tsx` around lines 94 - 105, The
component EmptyDescription is typed as React.ComponentProps<"p"> but renders a
<div>, causing a props/semantic mismatch; fix it by making the types and markup
consistent—either change the rendered element to <p> (keep the same props type)
or change the prop type to React.ComponentProps<"div"> (keep the <div>); update
the function signature (EmptyDescription) accordingly and ensure spreading
{...props}, className and data-slot="empty-description" remain correct for the
chosen element.

Comment on lines +5 to +35
function ScrollArea({
className,
children,
scrollFade = false,
scrollbarGutter = false,
...props
}: ScrollAreaPrimitive.Root.Props & {
scrollFade?: boolean;
scrollbarGutter?: boolean;
}) {
return (
<ScrollAreaPrimitive.Root
className={cn("size-full min-h-0", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
scrollFade &&
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
scrollbarGutter &&
"data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
)}
data-slot="scroll-area-viewport"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation="vertical" />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Coss UI ScrollArea component documentation installation shadcn

💡 Result:

Coss UI – ScrollArea (shadcn CLI install + docs)

  • Docs:coss.com/ui/docs/components/scroll-area (coss.com)
  • Install (shadcn CLI):
    pnpm dlx shadcn@latest add `@coss/scroll-area`
    (coss.com)
  • Use:
    import{ScrollArea}from"@/components/ui/scroll-area"
    (coss.com)

If you meant the core shadcn/ui ScrollArea (Radix/Base UI), the install command is:

pnpm dlx shadcn@latest add scroll-area

(ui.shadcn.com)

Citations:


🏁 Script executed:

# Check for existing UI component usage patterns in the repository
find apps/renderer/src/components/ui -type f -name "*.tsx"| head -20

Repository: pingdotgg/codething-mvp

Length of output: 957


🏁 Script executed:

# Check package.json to see what UI libraries are currently in use
cat package.json | grep -A 20 '"dependencies"'| head -40

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Search for existing imports of UI components to understand patterns
rg "from.*coss|from.*shadcn" apps/renderer/src/components/ui --no-heading -A 1 | head -30

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Check if ScrollAreaPrimitive is imported from `@radix-ui` or another source
head -50 apps/renderer/src/components/ui/scroll-area.tsx

Repository: pingdotgg/codething-mvp

Length of output: 2171


Replace with Coss UI ScrollArea component for consistency with codebase guidelines.

Coss UI provides a ScrollArea component that should be installed via ShadCN CLI: pnpm dlx shadcn@latest add @coss/scroll-area``. The custom extensions (scrollFade, `scrollbarGutter`) can be preserved by wrapping the Coss UI component instead of using Base UI directly. This ensures alignment with the team's component catalog and styling standards.

🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/scroll-area.tsx` around lines 5 - 35, Replace
the custom use of ScrollAreaPrimitive (ScrollAreaPrimitive.Root,
ScrollAreaPrimitive.Viewport, ScrollAreaPrimitive.Corner) and the local
ScrollBar components with the Coss UI ScrollArea wrapper: install the package
via the shadcn CLI (`pnpm dlx shadcn@latest add `@coss/scroll-area``), import
Coss's ScrollArea, ScrollAreaViewport and Corner (or the exported equivalents)
and re-create this component as a thin wrapper named ScrollArea that forwards
props and children while preserving the scrollFade and scrollbarGutter booleans
by mapping them to equivalent className masks and gutter classes on the Coss
component (apply the same mask expressions and `data-has-overflow-*` gutter
classes), and ensure data-slot attributes (scroll-area-viewport,
scroll-area-corner) remain on the mapped children so existing consumers continue
to work.

Comment on lines +128 to +142
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

The xmlns attribute has a typo: http://www.w3.org/1500/svg should be http://www.w3.org/2000/svg. This invalid namespace may cause the checkmark SVG to fail to render in some browsers.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/select.tsx` around lines 128 - 142, The SVG
used inside SelectPrimitive.ItemIndicator has a typo in its xmlns attribute;
update the xmlns on the SVG element within SelectPrimitive.ItemIndicator to the
correct namespace "http://www.w3.org/2000/svg" so the checkmark renders
correctly across browsers (locate the SVG in the SelectPrimitive.ItemIndicator
block and replace the incorrect "http://www.w3.org/1500/svg" value).

Comment on lines +1 to +3
import { cn } from "#/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/renderer/src/components/ui/skeleton.tsx | head -20

Repository: pingdotgg/codething-mvp

Length of output: 723


🏁 Script executed:

find . -name "tsconfig.json" -type f | head -5

Repository: pingdotgg/codething-mvp

Length of output: 160


🏁 Script executed:

cat tsconfig.json 2>/dev/null || cat ./apps/renderer/tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -exec cat {} \;

Repository: pingdotgg/codething-mvp

Length of output: 388


Add React type import for ComponentProps reference.

Line 3 references React.ComponentProps<"div"> without importing the React namespace. While the modern JSX transform (jsx: "react-jsx") eliminates the need for React in JSX elements, explicit namespace references like React.ComponentProps still require the import and will cause a type-checking error.

+import type * as React from "react";
import { cn } from "#/lib/utils";
📝 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.

Suggested change
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
importtype*asReactfrom"react";
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/skeleton.tsx` around lines 1 - 3, The file
uses React.ComponentProps<"div"> in the Skeleton component but doesn't import
the React namespace; add an import for React (e.g., add "import React from
'react'" or "import type React from 'react'") at the top of
apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/ChatView.tsx`:
- Around line 478-482: The UI currently renders effort labels by capitalizing
the first character in the map over REASONING_OPTIONS (in the SelectItem
rendering), which turns values like "xhigh" into "Xhigh"; replace this logic by
defining a display-name mapping (e.g., a constant object like
REASONING_DISPLAY_NAMES keyed by the same values in REASONING_OPTIONS) and use
that map when rendering the SelectItem label and when comparing to
DEFAULT_REASONING, falling back to a nicer formatter (e.g., full word
replacements or Title Case) only if a key is missing; update the SelectItem
render to use REASONING_DISPLAY_NAMES[effort] || fallbackFormatter(effort).
🧹 Nitpick comments (1)
apps/renderer/src/components/ChatView.tsx (1)

451-456: Minor: Redundant dark: prefixes for transparent and semantic tokens.

bg-transparent is identical in both modes, and bg-accent is a semantic token that already adapts to the theme. The dark: prefixes are unnecessary here.

♻️ Simplified styling
 <SelectTrigger
size="sm"
- className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"+ className="w-auto min-w-0 border-0 shadow-none bg-transparent before:hidden data-[popup-open]:bg-accent"
>

Apply to both SelectTrigger instances (lines 451-456 and 471-476).

Also applies to: 471-476

Comment on lines +478 to +482
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
{effort.charAt(0).toUpperCase() + effort.slice(1)}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalization of "xhigh" produces awkward "Xhigh" in the UI.

The inline capitalization effort.charAt(0).toUpperCase() + effort.slice(1) transforms "xhigh" to "Xhigh", which isn't user-friendly. Consider a display name mapping for better readability.

💡 Suggested approach
+const EFFORT_LABELS: Record<string, string> = {+ xhigh: "Extra High",+ high: "High",+ medium: "Medium",+ low: "Low",+};+
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
- {effort.charAt(0).toUpperCase() + effort.slice(1)}+ {EFFORT_LABELS[effort] ?? effort}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
))}
📝 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.

Suggested change
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{effort.charAt(0).toUpperCase()+effort.slice(1)}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
constEFFORT_LABELS: Record<string,string>={
xhigh: "Extra High",
high: "High",
medium: "Medium",
low: "Low",
};
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{EFFORT_LABELS[effort]??effort}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ChatView.tsx` around lines 478 - 482, The UI
currently renders effort labels by capitalizing the first character in the map
over REASONING_OPTIONS (in the SelectItem rendering), which turns values like
"xhigh" into "Xhigh"; replace this logic by defining a display-name mapping
(e.g., a constant object like REASONING_DISPLAY_NAMES keyed by the same values
in REASONING_OPTIONS) and use that map when rendering the SelectItem label and
when comparing to DEFAULT_REASONING, falling back to a nicer formatter (e.g.,
full word replacements or Title Case) only if a key is missing; update the
SelectItem render to use REASONING_DISPLAY_NAMES[effort] ||
fallbackFormatter(effort).

juliusmarmingeand others added 3 commits February 9, 2026 01:12
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the juliusmarminge/use-base-components branch from 35deea6 to 0a535aeCompareFebruary 9, 2026 09:13
@t3dotgg
t3dotgg merged commit c51f252 into mainFeb 9, 2026
1 check passed
t3dotgg added a commit that referenced this pull request Feb 9, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
praveenperera pushed a commit to praveenperera/t3code that referenced this pull request Mar 23, 2026
nikrabaev added a commit to nikrabaev/t3code that referenced this pull request Apr 25, 2026
The original route URL /$environmentId/$weaveRunId collided with the
existing chat thread route /$environmentId/$threadId — TanStack Router
matches by URL pattern, not by route ID, so the chat route always won.
Opening any weave run dropped onto the chat route, which then ran its
'thread doesn't exist → redirect' effect in a tight loop and exhausted
React's update depth.
Add a literal 'weave' segment to the weave URL so the patterns are
distinct:
before: /$envId/$weaveRunId vs /$envId/$threadId
after : /$envId/weave/$weaveRunId vs /$envId/$threadId
The plan flagged this exact risk in §'Open design questions' pingdotgg#4 and
prescribed this fallback. Files renamed; consumers (ChatView dispatch,
WeaveBlueprintList node nav, WeaveRunSidebarItem Link, WeaveCreatedMarker
Link) updated. Route tree regenerated by Vite plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 14, 2026
Two cleanups flagged by thermo-review:
- Phase partition (Standards #1 + Quality #3b): the active/recent split
hardcoded the terminal-phase list twice as complementary filters. Extract
TERMINAL_PHASES + partitionIssuesByPhase into sandcastleView.ts so the two
buckets can't drift, with unit tests.
- Issue rows (Quality #3c): the Active and Recent sections hand-rolled the
same #number link-or-span + phaseLabel badge. Extract one SandcastleIssueRow
component with a variant prop; classNames preserved verbatim (no visual
change). Collapses ~80 lines to ~40.
Also replace as-never test casts (Quality pingdotgg#4) with a real RepositoryIdentity
fixture mirroring environmentGrouping.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 17, 2026
Gate computeRevertTurnCountByUserMessageId on the union of the span's
agent edit sets (ADR-0004 decision 4), not a single turn; normalize web
agent paths to repo-relative and match the checkpoint diff by equality
instead of a suffix heuristic. Review findings: spec gating, quality pingdotgg#4/pingdotgg#5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 3, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: contracts for blob store, mirror RPCs, and registry
M1 analysis pass recorded in the plan doc (deviations: settings-flag home,
reactor gating pattern, no server-side peer endpoint discovery, mirror RPC
transport, D4 credential handshake).
Contracts:
- WorkspaceProjectId branded id (D1)
- packages/contracts/src/roaming.ts: RoamingBlobRecord/manifest/conflict (D3),
registry payload, RoamingProjectShell, peer + mirror + enrollment RPC
schemas, HTTP path constants
- roaming:mirror auth scope; `roaming` server setting (default off)
- OrchestrationShellSnapshot.roamingProjects (decode-default []) + shell
stream variants; optional workspaceProjectId on project shapes and
project.meta-updated
- internal command project.roaming.enroll with decider/projector/engine
wiring (emits project.meta-updated; double-enroll rejected)
Test literals updated for the new snapshot field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Roaming M1 contracts: apply review findings
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: roaming_blobs migration + RoamingBlobStore
Migration 033 (roaming_blobs + roaming_blob_conflicts, PK (kind,key)) and a
SqlClient-backed RoamingBlobStore: writeLocal (version bump, sha256 of the
verbatim payload string, author environment id), applyRemote implementing the
D3 reconciliation rule (higher version wins; equal version + different hash
records a conflict carrying the full remote record and keeps local),
manifest/get/getMany/listConflicts, and a changes stream that emits only on
accepted writes. Reuses persistence error types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: accepted writes clear superseded conflict rows
codex review P2: a recorded equal-version conflict stayed in
roaming_blob_conflicts after a later accepted write moved the key past the
contested version, so listConflicts() kept surfacing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: apply independent review findings
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 20, 2026
ServerSettings.pushNotifications was hand-edited in settings.json with
the app stopped. It now has a "Phone push" section in Settings, General:
topic URL, click-through base URL, per-phase toggles, presence
suppression window, and a Send test button.
The write path already existed end to end (ServerSettingsPatch exposed
pushNotifications; server.updateSettings deep-merges), so the only new
plumbing is server.sendTestPushNotification: it fires a push with the
saved settings, bypasses presence suppression by design (the user is at
the machine clicking the button), and returns delivery problems as
{sent, detail} values rather than errors so the UI shows them inline.
Because splitPatch is shallow, the UI always writes the full
pushNotifications object.
Verification caveat, recorded in FORK_ROADMAP.md: in an isolated
browser-paired dev environment, server-settings writes from the web UI
silently never reach the server, for this section and for long-shipped
settings alike (optimistic update, no RPC, no error). Pre-existing
behavior, not a regression from this change; the Electron daily driver
is the real test. The send-test function is covered by focused tests
(delivery, click-header omission, rejection, timeout-free unreachable
host, empty topic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 21, 2026
…ttings writes
Two live bugs in the roadmap pingdotgg#4 ship:
- server.sendTestPushNotification was missing from RPC_REQUIRED_SCOPE, so
every call defected server-side ("has no declared authorization scope")
before reaching the handler. That map is a third registration point beyond
the contracts group and the ws handler; wsRpcScopes.test.ts now asserts it
covers every rpc in WsRpcGroup.
- Settings toggles persisted but never reflected in the UI when the config
projection was stalled (durable subscriptions resume late/never after a
reconnect — diagnosis written up under FORK_ROADMAP pingdotgg#5). useSettings.ts now
holds the settings object the server returns from updateSettings as an
acked overlay merged over the atom value, cleared when the projection
delivers a fresh value. This also stops stale full-object spreads from
clobbering earlier writes.
Verified in an isolated env: healthy toggle round-trip, toggle during the
post-reconnect stall window (reflects immediately, server state tracks UI),
send test returns "Delivered (HTTP 200)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
…tgg#4)
* feat(web): proxy the dev server at a Moatless backend
The fork's client talks to Moatless rather than a local t3 server, so the
dev server has to stand in as the API origin.
- T3CODE_PROXY_TARGET (alias MOATLESS_BASE_URL) selects the proxy target,
falling back to the VITE_WS_URL-derived one so stock `pnpm dev` is
unchanged. /api, /ws, /attachments and /.well-known all follow it.
- VITE_WS_URL / VITE_HTTP_URL are blanked when the override is active so
the client resolves to window.location.origin instead of reaching past
the proxy, which is what holds the credential.
- Authorization: Bearer is injected and cookie stripped only when the
target is not loopback, so a Moatless key can never reach a local t3
server.
- Credential-shaped VITE_* values are deleted from process.env before
Vite reads them, and a build-time assertion rejects any `define` value
containing an API-key prefix. Vite inlines the whole import.meta.env
object and Moatless sandboxes inject VITE_API_KEY=mvk_…, so without
this the key ships in the bundle.
- allowedHosts: true — a hosted sandbox's preview hostname is not
knowable ahead of time.
Two additions are specific to running inside a hosted sandbox:
T3CODE_PROXY_TARGET_OVERRIDE is read from repo env files only. loadRepoEnv
lets the ambient environment win over .env.local, which is right for
everything else it resolves, but a sandbox injects T3CODE_PROXY_TARGET
pointing at its own bundled server and a checkout needs to be able to say
"no, use mine" without editing the deployment.
Fast Refresh is turned off when NODE_ENV=production. @vitejs/plugin-react
enables the oxc Fast Refresh transform for every `serve` run but injects
its runtime preamble only when the config resolves as development, so a
sandbox that exports NODE_ENV=production process-wide gets modules full
of $RefreshReg$ calls with nothing to answer them and a blank page with
no server-side error. Forcing NODE_ENV=development instead trades that
for react/compiler-runtime's `c` export vanishing behind a lexer-opaque
conditional, so applying the plugin's own skipFastRefresh rule to the
transform is the smaller fix.
* docs: full spec for the t3code -> Moatless adapter
Records the architecture the fork settled on (a server-side adapter
rather than swapping client-side seams), the verified domain and message
mappings, the contract surface the adapter has to serve, the milestone
state including what M2 still owes, and the eleven measured Moatless API
gaps each workaround points back to.
* docs: record the M3 browser verification and the merged-repository case
The unit tests prove the projections decode; only a browser proves an
untouched t3 client accepts them. Records what the Playwright run against
the live adapter actually asserted, including the one 404 it tolerates
and why that is correct.
Also documents a case the run surfaced: two Moatless repositories can
point at the same git remote, and t3 groups the sidebar by repository
identity, so they render under one header. Left alone deliberately —
synthesizing a distinct canonicalKey would misreport identity that t3
also uses for worktree and VCS association.
* fix(web): don't pin the HMR socket to a wildcard bind address
Upstream pins hmr.protocol/host/clientPort so the socket connects reliably
inside Electron's BrowserWindow, whose window loads http://localhost:<port>
directly. A hosted sandbox sets HOST=0.0.0.0, which turned that into a literal
ws://0.0.0.0:5733/ in the served /@vite/client — an address no browser can dial,
and mixed-content-blocked once the page is served over an HTTPS preview
hostname. Unset, Vite's client derives the socket URL from the page's own
origin, which is right behind a proxy and for a plain local run alike, so pin
the endpoint only when HOST names a dialable address.
Also records in the plan doc why the preview URL 504s: the gateway's packets
never reach the pod (no socket on 5733 for the full 30s of an external request,
while the untouched t3 sidecar on 13773 fails the same way), so that one is
infrastructure, not this checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the root cause of the preview-URL 504
Traefik IngressRoutes and the sandbox Service are built per task from the
repository's declared server ports, but the NetworkPolicy that says who may
dial a sandbox takes its Traefik ingress ports from a fixed list in values.yaml
that stops at 8888. t3code uses 5733 and 13773, so the CNI drops the SYN and
Traefik times out into a 504.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: preview URL fixed — record which policy actually gated it
The chart's Traefik ingress rule names a namespace this cluster does not have,
so it could never match. The live gate was moatless-sandbox-preview-ingress, an
ArgoCD-managed policy carrying a copy of the same static port list. With its
ports block removed, the preview host serves / in 42ms and the shell endpoint in
54ms with 7 projects and 98 open threads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: spec the client/server contract surfaces
The contract lives in packages/contracts as 31 TypeScript modules with no
generated artifact, so the only way to see the whole surface was to read
rpc.ts and environmentHttp.ts and follow imports. Index it instead: all 70
WsRpcGroup methods with payload/success/scope, all 20 dispatchable commands,
all 23 EnvironmentHttpApi endpoints, the ServerConfig handshake and its
version-negotiation rules, and the subscription semantics a schema-correct
server can still break.
Also records what is deliberately outside the typed surfaces (raw HTTP
routes, the IPC/relay/MCP boundaries, the Schema.Unknown envelopes) so
"fully typed" is not overclaimed, and notes that WS_METHODS.projectsList /
projectsAdd / projectsRemove are dead constants with no Rpc.make.
Tables were generated by parsing the source and verified back against it:
method names, line numbers, stream flags, and required scopes all match
rpc.ts and ws.ts RPC_REQUIRED_SCOPE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* wip
* docs: map the client/server contract onto Moatless
Feature-by-feature walk of all three contract surfaces against the Moatless
backend: 23 HTTP endpoints, 70 RPC methods, and the 20-member command union.
Records which concepts are isomorphic, which only look alike, and what each
side has that the other cannot express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the preview mapping — tab vs. server process
T3's preview.* is the in-app browser tab (PreviewSessionSnapshot is
navStatus/canGoBack/viewport); Moatless's preview server is the process
serving the app. T3's process side is a ProjectScript in a terminal, found
by PortScanner. The declared-vs-discovered difference lands on
subscribeDiscoveredLocalServers, not preview.open.
Retallies the WS surface: 4 equivalent, 26 approximate, 40 absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the message-event claim — two events, not one
agent.message is the slim persisted log row (ADR-0003 / migration 0089);
message.upsert is a separate derived event carrying an assembled UiMessage,
relayed live and never persisted. Streaming assembled content is possible;
full tool detail is not, and the content-carrying event is the one that
cannot be replayed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: design the T3/Moatless convergence
Four moves, deliberately not all in the same direction: T3 adopts Moatless's
task+sandbox model, preview servers, and per-sandbox diagnostics scoping;
Moatless adopts T3's sequenced resumable streaming contract.
Records the governing decision (the deployment is the environment, the
sandbox is thread-scoped state), the two hard parts (unpersisted
message.upsert, bigserial as a cursor), and what each move costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: add local Moatless adapter workflow
* chore: preserve repository links
* feat(web): use Moatless auth for primary UI
* fix(web): allow Moatless cookie login after 401 session
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

light mode - #4

Merged
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components
Feb 9, 2026
Merged

light mode#4
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 9, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-02-08 at 22 05 13@2xCleanShot 2026-02-08 at 22 05 17@2x

Summary by CodeRabbit

  • New Features

    • Large library of reusable UI components added (accordions, dialogs, menus, inputs, selects, command palette, toasts, sheets, tables, etc.) for richer, consistent interfaces.
  • Theme

    • Dark theme support with system detection, cross-tab sync, and a theme toggle in the sidebar.
  • Style

    • Replaced hard-coded colors with semantic design tokens and global CSS variables for consistent light/dark theming and improved visuals.

@coderabbitai

coderabbitaiBot commented Feb 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds a large set of new UI components and utilities to the renderer, introduces a CSS-variable based theme (light/dark/system) and a useTheme hook, replaces hard-coded color tokens with semantic tokens across core renderer components, adds a cn utility and components.json, adjusts deps/config and a small smoke-test script tweak.

Changes

Cohort / File(s)Summary
UI component library
apps/renderer/src/components/ui/*
Adds 30+ new UI modules (accordion, alert, autocomplete, avatar, badge, button, card, checkbox, collapsible, combobox, command, dialog, empty, field, fieldset, form, input-group, input, kbd, label, menu, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, spinner, switch, table, tabs, textarea, toast). Each wraps @base-ui/react primitives, applies data-slot attributes, CVA variants and composed Tailwind classes. Review API shapes, typings, and consistent class/slot usage.
Core renderer components
apps/renderer/src/App.tsx, apps/renderer/src/components/ChatView.tsx, apps/renderer/src/components/ChatMarkdown.tsx, apps/renderer/src/components/DiffPanel.tsx, apps/renderer/src/components/Sidebar.tsx
Replaces hex color classes with semantic tokens; ChatView integrates new InputGroup, Select, Button components and removes previous textarea auto-resize logic; ChatMarkdown and DiffPanel styling tokens updated; Sidebar adds theme toggle and theme-aware styling. Validate behavioral changes where logic was removed (e.g., textarea resizing, model menu handling).
Theming & CSS
apps/renderer/src/index.css, apps/renderer/src/hooks/useTheme.ts
Introduces CSS variable theme system with light/dark variants and a .no-transitions helper; adds many CSS token definitions and dark overrides. Adds useTheme hook (light
Utilities & small libs
apps/renderer/src/lib/utils.ts, apps/renderer/components.json
Adds cn(...inputs) util combining clsx + twMerge. Adds components.json with UI framework config, aliases, Tailwind/tokens and registries. Confirm imports use the new alias and utility.
Config & build
apps/renderer/tsconfig.json, apps/renderer/vite.config.ts, biome.json
Adds #/* path alias and resolves # in Vite; sets exactOptionalPropertyTypes: false in tsconfig; relaxes biome a11y rule noSvgWithoutTitle.
Dependencies
apps/renderer/package.json
Adds runtime deps including @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge and reorders react-markdown. Verify lockfile and install.
Markup & scripts
apps/renderer/index.html, apps/desktop/scripts/smoke-test.mjs
Removes body Tailwind class in index.html. Small smoke-test script tweak: reorder imports from node:child_process and use template literal for error output. Minor script change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check❓ InconclusiveThe PR title 'light mode' is vague and does not clearly describe the actual scope of changes, which include comprehensive UI component library implementation, theming system, styling updates, and design system token migration across the renderer application.Consider a more descriptive title that captures the main work, such as 'Add design system UI components and light/dark theme support' or 'Implement base UI component library with theming.'
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch juliusmarminge/use-base-components

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/renderer/src/components/ChatView.tsx (1)

312-344: Consider using cn utility for conditional class handling.

The deeply nested ternaries for status indicator styling reduce readability. Using the cn utility with object syntax would make the phase-based styling clearer and more maintainable.

♻️ Suggested refactor using cn utility
+const statusContainerClasses: Record<string, string> = {+ running: "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100",+ connecting: "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100",+ ready: "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100",+ disconnected: "border-border bg-secondary text-muted-foreground",+};++const statusDotClasses: Record<string, string> = {+ running: "bg-sky-200",+ connecting: "bg-amber-200",+ ready: "bg-emerald-200",+ disconnected: "bg-muted-foreground/40",+};

Then use:

<divclassName={cn("inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px]",statusContainerClasses[phase]??statusContainerClasses.disconnected)}>

Based on learnings: "Use Tailwind CSS for styling - no inline styles, use cn for conditional classes".

apps/renderer/src/components/Sidebar.tsx (1)

138-152: Prefer Coss UI for the new icon-only theme toggle.

To keep styling and interaction patterns consistent with the rest of the app, consider swapping this raw <button> for the equivalent Coss UI icon/button primitive.

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeappBot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Add light/dark/system theme cycling and apply token-based theming across Sidebar, ChatView, and global styles to introduce light mode

Introduce a useTheme hook with localStorage persistence and a Sidebar header button to cycle system/light/dark; switch global and app styling to CSS variables and semantic Tailwind tokens; refactor ChatView and Sidebar UI to use shared UI primitives; remove thread status badges, editor/approval flows, and keyboard shortcuts; update model selection in new project flow.

📍Where to Start

Start with the useTheme hook in useTheme.ts and its integration in the Sidebar in Sidebar.tsx, then review theme token application in index.css.


Macroscope summarized 0a535ae.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/Sidebar.tsx`:
- Around line 225-230: The button currently uses the disabled prop which
conflicts with accessibility guidance and tooltips; instead remove the disabled
attribute on the button element, add aria-disabled={isPickingFolder} to reflect
state, keep/adjust the visual "disabled" classes conditionally when
isPickingFolder, and add a click guard at the top of handlePickFolder (or inline
in the onClick wrapper) that returns early if isPickingFolder so clicks are
ignored; ensure tooltip behavior is preserved by using aria-disabled rather than
disabled.
In `@apps/renderer/src/components/ui/combobox.tsx`:
- Around line 196-214: The SVG in the Combobox component uses an incorrect xmlns
value; update the xmlns on the SVG inside ComboboxPrimitive.ItemIndicator to the
correct "http://www.w3.org/2000/svg" string so the icon renders properly (look
for the SVG element in the ComboboxPrimitive.ItemIndicator within the
combobox.tsx component and change the xmlns attribute value).
In `@apps/renderer/src/components/ui/empty.tsx`:
- Around line 94-105: The component EmptyDescription is typed as
React.ComponentProps<"p"> but renders a <div>, causing a props/semantic
mismatch; fix it by making the types and markup consistent—either change the
rendered element to <p> (keep the same props type) or change the prop type to
React.ComponentProps<"div"> (keep the <div>); update the function signature
(EmptyDescription) accordingly and ensure spreading {...props}, className and
data-slot="empty-description" remain correct for the chosen element.
In `@apps/renderer/src/components/ui/scroll-area.tsx`:
- Around line 5-35: Replace the custom use of ScrollAreaPrimitive
(ScrollAreaPrimitive.Root, ScrollAreaPrimitive.Viewport,
ScrollAreaPrimitive.Corner) and the local ScrollBar components with the Coss UI
ScrollArea wrapper: install the package via the shadcn CLI (`pnpm dlx
shadcn@latest add `@coss/scroll-area``), import Coss's ScrollArea,
ScrollAreaViewport and Corner (or the exported equivalents) and re-create this
component as a thin wrapper named ScrollArea that forwards props and children
while preserving the scrollFade and scrollbarGutter booleans by mapping them to
equivalent className masks and gutter classes on the Coss component (apply the
same mask expressions and `data-has-overflow-*` gutter classes), and ensure
data-slot attributes (scroll-area-viewport, scroll-area-corner) remain on the
mapped children so existing consumers continue to work.
In `@apps/renderer/src/components/ui/select.tsx`:
- Around line 128-142: The SVG used inside SelectPrimitive.ItemIndicator has a
typo in its xmlns attribute; update the xmlns on the SVG element within
SelectPrimitive.ItemIndicator to the correct namespace
"http://www.w3.org/2000/svg" so the checkmark renders correctly across browsers
(locate the SVG in the SelectPrimitive.ItemIndicator block and replace the
incorrect "http://www.w3.org/1500/svg" value).
In `@apps/renderer/src/components/ui/skeleton.tsx`:
- Around line 1-3: The file uses React.ComponentProps<"div"> in the Skeleton
component but doesn't import the React namespace; add an import for React (e.g.,
add "import React from 'react'" or "import type React from 'react'") at the top
of apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.
🧹 Nitpick comments (6)
apps/renderer/src/components/ui/table.tsx (1)

36-47: Consider extracting long class strings for maintainability.

The class string on line 40 is quite long with many frame-conditional styles. While this is a common pattern in shadcn-style UI libraries, consider extracting the frame-specific styles into a separate variable or using Tailwind's @apply directive in a CSS file for better readability.

That said, this is a stylistic preference and the current implementation is functionally correct.

apps/renderer/src/components/ui/separator.tsx (1)

13-13: The CSS selector for vertical self-stretch is fragile and likely won't work as intended.

The selector not-[[class^='h-']]:not-[[class*='_h-']] checks whether the class attribute string starts with 'h-' or contains '_h-'. Since other classes (e.g., shrink-0) will appear first in the composed className, the [class^='h-'] condition will almost never match—even when a height utility like h-10 is present.

Consider simplifying by always applying self-stretch for vertical orientation and letting explicit height classes override it naturally, or document that consumers should use inline styles / CSS variables for custom heights:

♻️ Suggested simplification
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
apps/renderer/src/components/ui/toast.tsx (1)

220-250: Consider extracting shared toast content into a reusable component.

The toast content rendering here (icon, title, description, action button) is nearly identical to lines 128-158 in the Toasts component. Extracting this into a shared ToastContent component would reduce duplication and ensure consistency when making future changes.

♻️ Suggested extraction
// Shared component to use in both Toasts and AnchoredToastsfunctionToastContentBody({ toast }: {toast: Toast.ToastObject}){constIcon=toast.type
? TOAST_ICONS[toast.typeaskeyoftypeofTOAST_ICONS]
: null;return(<Toast.ContentclassName="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm ..."><divclassName="flex gap-2">{Icon&&(<divclassName="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"data-slot="toast-icon"><IconclassName="in-data-[type=loading]:animate-spin ..."/></div>)}<divclassName="flex flex-col gap-0.5"><Toast.TitleclassName="font-medium"data-slot="toast-title"/><Toast.DescriptionclassName="text-muted-foreground"data-slot="toast-description"/></div></div>{toast.actionProps&&(<Toast.ActionclassName={buttonVariants({size: "xs"})}data-slot="toast-action">{toast.actionProps.children}</Toast.Action>)}</Toast.Content>);}
apps/renderer/src/components/ui/kbd.tsx (1)

5-26: Prefer Coss UI primitives for consistency.

These custom Kbd/KbdGroup wrappers diverge from the guideline to use Coss UI components (installed via the ShadCN CLI) for consistent styling and Base UI accessibility alignment. If a Coss kbd component exists, consider swapping to it (or document why bespoke wrappers are required).

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling. Install new components using the ShadCN CLI (e.g., bunx --bun shadcnlatest add coss/button). Refer to Base UI docs for accessibility guidelines on primitives.

apps/renderer/src/components/ui/textarea.tsx (1)

7-10: Consider documenting or removing the number type for size.

The size prop accepts "sm" | "default" | "lg" | number, but the internal styling logic (lines 35-38) only handles the string literals. When a number is passed, it's set on data-size but no corresponding styles are applied.

If numeric sizes are intended for custom CSS handling via data-size, consider adding a brief comment documenting this. Otherwise, consider restricting the type to the supported string literals only.

apps/renderer/src/index.css (1)

227-242: Consider using CSS variables for scrollbar colors for consistency.

The scrollbar styling uses hardcoded rgba() values while the rest of the file uses CSS variables. For better theme consistency, consider using the defined tokens:

♻️ Optional: Use CSS variables for scrollbar colors
 ::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);+ background: var(--muted-foreground);+ opacity: 0.15;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 0, 0, 0.25);+ background: var(--muted-foreground);+ opacity: 0.25;
}
.dark ::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);+ background: var(--muted-foreground);+ opacity: 0.1;
}
.dark ::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.18);+ background: var(--muted-foreground);+ opacity: 0.18;
}

Note: Opacity on scrollbar thumbs may have limited browser support; the current approach works reliably.

Comment on lines 225 to 230
<button
type="button"
className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => void handlePickFolder()}
disabled={isPickingFolder}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid the disabled prop; use aria-disabled and a click guard instead.

The current disabled usage conflicts with the repo’s accessibility guidance and can break tooltips. Use styling plus a guard in the handler.

🛠️ Suggested fix
- className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"- onClick={() => void handlePickFolder()}- disabled={isPickingFolder}+ className={`mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary ${+ isPickingFolder ? "cursor-not-allowed opacity-60" : ""+ }`}+ aria-disabled={isPickingFolder}+ onClick={() => {+ if (isPickingFolder) return;+ void handlePickFolder();+ }}
Based on learnings: Avoid using disabled props on buttons as they harm accessibility and break tooltips.
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/Sidebar.tsx` around lines 225 - 230, The button
currently uses the disabled prop which conflicts with accessibility guidance and
tooltips; instead remove the disabled attribute on the button element, add
aria-disabled={isPickingFolder} to reflect state, keep/adjust the visual
"disabled" classes conditionally when isPickingFolder, and add a click guard at
the top of handlePickFolder (or inline in the onClick wrapper) that returns
early if isPickingFolder so clicks are ignored; ensure tooltip behavior is
preserved by using aria-disabled rather than disabled.

Comment on lines +196 to +214
<ComboboxPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</ComboboxPrimitive.ItemIndicator>
<div className="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

Same issue as in select.tsx: the xmlns attribute has http://www.w3.org/1500/svg instead of http://www.w3.org/2000/svg.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/combobox.tsx` around lines 196 - 214, The SVG
in the Combobox component uses an incorrect xmlns value; update the xmlns on the
SVG inside ComboboxPrimitive.ItemIndicator to the correct
"http://www.w3.org/2000/svg" string so the icon renders properly (look for the
SVG element in the ComboboxPrimitive.ItemIndicator within the combobox.tsx
component and change the xmlns attribute value).

Comment on lines +94 to +105
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: Component typed as <p> but renders <div>.

EmptyDescription accepts React.ComponentProps<"p"> but renders a <div>. This inconsistency could confuse consumers and allows <p>-specific props that won't apply to the rendered <div>.

💡 Proposed fix

Either change the rendered element to match the type:

-function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {+function EmptyDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div

Or render a <p> to match the type (if semantically appropriate):

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
- <div+ <p
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
- />+ />
);
}
📝 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.

Suggested change
functionEmptyDescription({ className, ...props}: React.ComponentProps<"p">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
functionEmptyDescription({ className, ...props}: React.ComponentProps<"div">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/empty.tsx` around lines 94 - 105, The
component EmptyDescription is typed as React.ComponentProps<"p"> but renders a
<div>, causing a props/semantic mismatch; fix it by making the types and markup
consistent—either change the rendered element to <p> (keep the same props type)
or change the prop type to React.ComponentProps<"div"> (keep the <div>); update
the function signature (EmptyDescription) accordingly and ensure spreading
{...props}, className and data-slot="empty-description" remain correct for the
chosen element.

Comment on lines +5 to +35
function ScrollArea({
className,
children,
scrollFade = false,
scrollbarGutter = false,
...props
}: ScrollAreaPrimitive.Root.Props & {
scrollFade?: boolean;
scrollbarGutter?: boolean;
}) {
return (
<ScrollAreaPrimitive.Root
className={cn("size-full min-h-0", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
scrollFade &&
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
scrollbarGutter &&
"data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
)}
data-slot="scroll-area-viewport"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation="vertical" />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Coss UI ScrollArea component documentation installation shadcn

💡 Result:

Coss UI – ScrollArea (shadcn CLI install + docs)

  • Docs:coss.com/ui/docs/components/scroll-area (coss.com)
  • Install (shadcn CLI):
    pnpm dlx shadcn@latest add `@coss/scroll-area`
    (coss.com)
  • Use:
    import{ScrollArea}from"@/components/ui/scroll-area"
    (coss.com)

If you meant the core shadcn/ui ScrollArea (Radix/Base UI), the install command is:

pnpm dlx shadcn@latest add scroll-area

(ui.shadcn.com)

Citations:


🏁 Script executed:

# Check for existing UI component usage patterns in the repository
find apps/renderer/src/components/ui -type f -name "*.tsx"| head -20

Repository: pingdotgg/codething-mvp

Length of output: 957


🏁 Script executed:

# Check package.json to see what UI libraries are currently in use
cat package.json | grep -A 20 '"dependencies"'| head -40

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Search for existing imports of UI components to understand patterns
rg "from.*coss|from.*shadcn" apps/renderer/src/components/ui --no-heading -A 1 | head -30

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Check if ScrollAreaPrimitive is imported from `@radix-ui` or another source
head -50 apps/renderer/src/components/ui/scroll-area.tsx

Repository: pingdotgg/codething-mvp

Length of output: 2171


Replace with Coss UI ScrollArea component for consistency with codebase guidelines.

Coss UI provides a ScrollArea component that should be installed via ShadCN CLI: pnpm dlx shadcn@latest add @coss/scroll-area``. The custom extensions (scrollFade, `scrollbarGutter`) can be preserved by wrapping the Coss UI component instead of using Base UI directly. This ensures alignment with the team's component catalog and styling standards.

🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/scroll-area.tsx` around lines 5 - 35, Replace
the custom use of ScrollAreaPrimitive (ScrollAreaPrimitive.Root,
ScrollAreaPrimitive.Viewport, ScrollAreaPrimitive.Corner) and the local
ScrollBar components with the Coss UI ScrollArea wrapper: install the package
via the shadcn CLI (`pnpm dlx shadcn@latest add `@coss/scroll-area``), import
Coss's ScrollArea, ScrollAreaViewport and Corner (or the exported equivalents)
and re-create this component as a thin wrapper named ScrollArea that forwards
props and children while preserving the scrollFade and scrollbarGutter booleans
by mapping them to equivalent className masks and gutter classes on the Coss
component (apply the same mask expressions and `data-has-overflow-*` gutter
classes), and ensure data-slot attributes (scroll-area-viewport,
scroll-area-corner) remain on the mapped children so existing consumers continue
to work.

Comment on lines +128 to +142
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

The xmlns attribute has a typo: http://www.w3.org/1500/svg should be http://www.w3.org/2000/svg. This invalid namespace may cause the checkmark SVG to fail to render in some browsers.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/select.tsx` around lines 128 - 142, The SVG
used inside SelectPrimitive.ItemIndicator has a typo in its xmlns attribute;
update the xmlns on the SVG element within SelectPrimitive.ItemIndicator to the
correct namespace "http://www.w3.org/2000/svg" so the checkmark renders
correctly across browsers (locate the SVG in the SelectPrimitive.ItemIndicator
block and replace the incorrect "http://www.w3.org/1500/svg" value).

Comment on lines +1 to +3
import { cn } from "#/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/renderer/src/components/ui/skeleton.tsx | head -20

Repository: pingdotgg/codething-mvp

Length of output: 723


🏁 Script executed:

find . -name "tsconfig.json" -type f | head -5

Repository: pingdotgg/codething-mvp

Length of output: 160


🏁 Script executed:

cat tsconfig.json 2>/dev/null || cat ./apps/renderer/tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -exec cat {} \;

Repository: pingdotgg/codething-mvp

Length of output: 388


Add React type import for ComponentProps reference.

Line 3 references React.ComponentProps<"div"> without importing the React namespace. While the modern JSX transform (jsx: "react-jsx") eliminates the need for React in JSX elements, explicit namespace references like React.ComponentProps still require the import and will cause a type-checking error.

+import type * as React from "react";
import { cn } from "#/lib/utils";
📝 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.

Suggested change
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
importtype*asReactfrom"react";
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/skeleton.tsx` around lines 1 - 3, The file
uses React.ComponentProps<"div"> in the Skeleton component but doesn't import
the React namespace; add an import for React (e.g., add "import React from
'react'" or "import type React from 'react'") at the top of
apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/ChatView.tsx`:
- Around line 478-482: The UI currently renders effort labels by capitalizing
the first character in the map over REASONING_OPTIONS (in the SelectItem
rendering), which turns values like "xhigh" into "Xhigh"; replace this logic by
defining a display-name mapping (e.g., a constant object like
REASONING_DISPLAY_NAMES keyed by the same values in REASONING_OPTIONS) and use
that map when rendering the SelectItem label and when comparing to
DEFAULT_REASONING, falling back to a nicer formatter (e.g., full word
replacements or Title Case) only if a key is missing; update the SelectItem
render to use REASONING_DISPLAY_NAMES[effort] || fallbackFormatter(effort).
🧹 Nitpick comments (1)
apps/renderer/src/components/ChatView.tsx (1)

451-456: Minor: Redundant dark: prefixes for transparent and semantic tokens.

bg-transparent is identical in both modes, and bg-accent is a semantic token that already adapts to the theme. The dark: prefixes are unnecessary here.

♻️ Simplified styling
 <SelectTrigger
size="sm"
- className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"+ className="w-auto min-w-0 border-0 shadow-none bg-transparent before:hidden data-[popup-open]:bg-accent"
>

Apply to both SelectTrigger instances (lines 451-456 and 471-476).

Also applies to: 471-476

Comment on lines +478 to +482
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
{effort.charAt(0).toUpperCase() + effort.slice(1)}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalization of "xhigh" produces awkward "Xhigh" in the UI.

The inline capitalization effort.charAt(0).toUpperCase() + effort.slice(1) transforms "xhigh" to "Xhigh", which isn't user-friendly. Consider a display name mapping for better readability.

💡 Suggested approach
+const EFFORT_LABELS: Record<string, string> = {+ xhigh: "Extra High",+ high: "High",+ medium: "Medium",+ low: "Low",+};+
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
- {effort.charAt(0).toUpperCase() + effort.slice(1)}+ {EFFORT_LABELS[effort] ?? effort}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
))}
📝 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.

Suggested change
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{effort.charAt(0).toUpperCase()+effort.slice(1)}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
constEFFORT_LABELS: Record<string,string>={
xhigh: "Extra High",
high: "High",
medium: "Medium",
low: "Low",
};
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{EFFORT_LABELS[effort]??effort}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ChatView.tsx` around lines 478 - 482, The UI
currently renders effort labels by capitalizing the first character in the map
over REASONING_OPTIONS (in the SelectItem rendering), which turns values like
"xhigh" into "Xhigh"; replace this logic by defining a display-name mapping
(e.g., a constant object like REASONING_DISPLAY_NAMES keyed by the same values
in REASONING_OPTIONS) and use that map when rendering the SelectItem label and
when comparing to DEFAULT_REASONING, falling back to a nicer formatter (e.g.,
full word replacements or Title Case) only if a key is missing; update the
SelectItem render to use REASONING_DISPLAY_NAMES[effort] ||
fallbackFormatter(effort).

juliusmarmingeand others added 3 commits February 9, 2026 01:12
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the juliusmarminge/use-base-components branch from 35deea6 to 0a535aeCompareFebruary 9, 2026 09:13
@t3dotgg
t3dotgg merged commit c51f252 into mainFeb 9, 2026
1 check passed
t3dotgg added a commit that referenced this pull request Feb 9, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
praveenperera pushed a commit to praveenperera/t3code that referenced this pull request Mar 23, 2026
nikrabaev added a commit to nikrabaev/t3code that referenced this pull request Apr 25, 2026
The original route URL /$environmentId/$weaveRunId collided with the
existing chat thread route /$environmentId/$threadId — TanStack Router
matches by URL pattern, not by route ID, so the chat route always won.
Opening any weave run dropped onto the chat route, which then ran its
'thread doesn't exist → redirect' effect in a tight loop and exhausted
React's update depth.
Add a literal 'weave' segment to the weave URL so the patterns are
distinct:
before: /$envId/$weaveRunId vs /$envId/$threadId
after : /$envId/weave/$weaveRunId vs /$envId/$threadId
The plan flagged this exact risk in §'Open design questions' pingdotgg#4 and
prescribed this fallback. Files renamed; consumers (ChatView dispatch,
WeaveBlueprintList node nav, WeaveRunSidebarItem Link, WeaveCreatedMarker
Link) updated. Route tree regenerated by Vite plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 14, 2026
Two cleanups flagged by thermo-review:
- Phase partition (Standards #1 + Quality #3b): the active/recent split
hardcoded the terminal-phase list twice as complementary filters. Extract
TERMINAL_PHASES + partitionIssuesByPhase into sandcastleView.ts so the two
buckets can't drift, with unit tests.
- Issue rows (Quality #3c): the Active and Recent sections hand-rolled the
same #number link-or-span + phaseLabel badge. Extract one SandcastleIssueRow
component with a variant prop; classNames preserved verbatim (no visual
change). Collapses ~80 lines to ~40.
Also replace as-never test casts (Quality pingdotgg#4) with a real RepositoryIdentity
fixture mirroring environmentGrouping.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 17, 2026
Gate computeRevertTurnCountByUserMessageId on the union of the span's
agent edit sets (ADR-0004 decision 4), not a single turn; normalize web
agent paths to repo-relative and match the checkpoint diff by equality
instead of a suffix heuristic. Review findings: spec gating, quality pingdotgg#4/pingdotgg#5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 3, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: contracts for blob store, mirror RPCs, and registry
M1 analysis pass recorded in the plan doc (deviations: settings-flag home,
reactor gating pattern, no server-side peer endpoint discovery, mirror RPC
transport, D4 credential handshake).
Contracts:
- WorkspaceProjectId branded id (D1)
- packages/contracts/src/roaming.ts: RoamingBlobRecord/manifest/conflict (D3),
registry payload, RoamingProjectShell, peer + mirror + enrollment RPC
schemas, HTTP path constants
- roaming:mirror auth scope; `roaming` server setting (default off)
- OrchestrationShellSnapshot.roamingProjects (decode-default []) + shell
stream variants; optional workspaceProjectId on project shapes and
project.meta-updated
- internal command project.roaming.enroll with decider/projector/engine
wiring (emits project.meta-updated; double-enroll rejected)
Test literals updated for the new snapshot field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Roaming M1 contracts: apply review findings
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: roaming_blobs migration + RoamingBlobStore
Migration 033 (roaming_blobs + roaming_blob_conflicts, PK (kind,key)) and a
SqlClient-backed RoamingBlobStore: writeLocal (version bump, sha256 of the
verbatim payload string, author environment id), applyRemote implementing the
D3 reconciliation rule (higher version wins; equal version + different hash
records a conflict carrying the full remote record and keeps local),
manifest/get/getMany/listConflicts, and a changes stream that emits only on
accepted writes. Reuses persistence error types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: accepted writes clear superseded conflict rows
codex review P2: a recorded equal-version conflict stayed in
roaming_blob_conflicts after a later accepted write moved the key past the
contested version, so listConflicts() kept surfacing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: apply independent review findings
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 20, 2026
ServerSettings.pushNotifications was hand-edited in settings.json with
the app stopped. It now has a "Phone push" section in Settings, General:
topic URL, click-through base URL, per-phase toggles, presence
suppression window, and a Send test button.
The write path already existed end to end (ServerSettingsPatch exposed
pushNotifications; server.updateSettings deep-merges), so the only new
plumbing is server.sendTestPushNotification: it fires a push with the
saved settings, bypasses presence suppression by design (the user is at
the machine clicking the button), and returns delivery problems as
{sent, detail} values rather than errors so the UI shows them inline.
Because splitPatch is shallow, the UI always writes the full
pushNotifications object.
Verification caveat, recorded in FORK_ROADMAP.md: in an isolated
browser-paired dev environment, server-settings writes from the web UI
silently never reach the server, for this section and for long-shipped
settings alike (optimistic update, no RPC, no error). Pre-existing
behavior, not a regression from this change; the Electron daily driver
is the real test. The send-test function is covered by focused tests
(delivery, click-header omission, rejection, timeout-free unreachable
host, empty topic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 21, 2026
…ttings writes
Two live bugs in the roadmap pingdotgg#4 ship:
- server.sendTestPushNotification was missing from RPC_REQUIRED_SCOPE, so
every call defected server-side ("has no declared authorization scope")
before reaching the handler. That map is a third registration point beyond
the contracts group and the ws handler; wsRpcScopes.test.ts now asserts it
covers every rpc in WsRpcGroup.
- Settings toggles persisted but never reflected in the UI when the config
projection was stalled (durable subscriptions resume late/never after a
reconnect — diagnosis written up under FORK_ROADMAP pingdotgg#5). useSettings.ts now
holds the settings object the server returns from updateSettings as an
acked overlay merged over the atom value, cleared when the projection
delivers a fresh value. This also stops stale full-object spreads from
clobbering earlier writes.
Verified in an isolated env: healthy toggle round-trip, toggle during the
post-reconnect stall window (reflects immediately, server state tracks UI),
send test returns "Delivered (HTTP 200)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
…tgg#4)
* feat(web): proxy the dev server at a Moatless backend
The fork's client talks to Moatless rather than a local t3 server, so the
dev server has to stand in as the API origin.
- T3CODE_PROXY_TARGET (alias MOATLESS_BASE_URL) selects the proxy target,
falling back to the VITE_WS_URL-derived one so stock `pnpm dev` is
unchanged. /api, /ws, /attachments and /.well-known all follow it.
- VITE_WS_URL / VITE_HTTP_URL are blanked when the override is active so
the client resolves to window.location.origin instead of reaching past
the proxy, which is what holds the credential.
- Authorization: Bearer is injected and cookie stripped only when the
target is not loopback, so a Moatless key can never reach a local t3
server.
- Credential-shaped VITE_* values are deleted from process.env before
Vite reads them, and a build-time assertion rejects any `define` value
containing an API-key prefix. Vite inlines the whole import.meta.env
object and Moatless sandboxes inject VITE_API_KEY=mvk_…, so without
this the key ships in the bundle.
- allowedHosts: true — a hosted sandbox's preview hostname is not
knowable ahead of time.
Two additions are specific to running inside a hosted sandbox:
T3CODE_PROXY_TARGET_OVERRIDE is read from repo env files only. loadRepoEnv
lets the ambient environment win over .env.local, which is right for
everything else it resolves, but a sandbox injects T3CODE_PROXY_TARGET
pointing at its own bundled server and a checkout needs to be able to say
"no, use mine" without editing the deployment.
Fast Refresh is turned off when NODE_ENV=production. @vitejs/plugin-react
enables the oxc Fast Refresh transform for every `serve` run but injects
its runtime preamble only when the config resolves as development, so a
sandbox that exports NODE_ENV=production process-wide gets modules full
of $RefreshReg$ calls with nothing to answer them and a blank page with
no server-side error. Forcing NODE_ENV=development instead trades that
for react/compiler-runtime's `c` export vanishing behind a lexer-opaque
conditional, so applying the plugin's own skipFastRefresh rule to the
transform is the smaller fix.
* docs: full spec for the t3code -> Moatless adapter
Records the architecture the fork settled on (a server-side adapter
rather than swapping client-side seams), the verified domain and message
mappings, the contract surface the adapter has to serve, the milestone
state including what M2 still owes, and the eleven measured Moatless API
gaps each workaround points back to.
* docs: record the M3 browser verification and the merged-repository case
The unit tests prove the projections decode; only a browser proves an
untouched t3 client accepts them. Records what the Playwright run against
the live adapter actually asserted, including the one 404 it tolerates
and why that is correct.
Also documents a case the run surfaced: two Moatless repositories can
point at the same git remote, and t3 groups the sidebar by repository
identity, so they render under one header. Left alone deliberately —
synthesizing a distinct canonicalKey would misreport identity that t3
also uses for worktree and VCS association.
* fix(web): don't pin the HMR socket to a wildcard bind address
Upstream pins hmr.protocol/host/clientPort so the socket connects reliably
inside Electron's BrowserWindow, whose window loads http://localhost:<port>
directly. A hosted sandbox sets HOST=0.0.0.0, which turned that into a literal
ws://0.0.0.0:5733/ in the served /@vite/client — an address no browser can dial,
and mixed-content-blocked once the page is served over an HTTPS preview
hostname. Unset, Vite's client derives the socket URL from the page's own
origin, which is right behind a proxy and for a plain local run alike, so pin
the endpoint only when HOST names a dialable address.
Also records in the plan doc why the preview URL 504s: the gateway's packets
never reach the pod (no socket on 5733 for the full 30s of an external request,
while the untouched t3 sidecar on 13773 fails the same way), so that one is
infrastructure, not this checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the root cause of the preview-URL 504
Traefik IngressRoutes and the sandbox Service are built per task from the
repository's declared server ports, but the NetworkPolicy that says who may
dial a sandbox takes its Traefik ingress ports from a fixed list in values.yaml
that stops at 8888. t3code uses 5733 and 13773, so the CNI drops the SYN and
Traefik times out into a 504.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: preview URL fixed — record which policy actually gated it
The chart's Traefik ingress rule names a namespace this cluster does not have,
so it could never match. The live gate was moatless-sandbox-preview-ingress, an
ArgoCD-managed policy carrying a copy of the same static port list. With its
ports block removed, the preview host serves / in 42ms and the shell endpoint in
54ms with 7 projects and 98 open threads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: spec the client/server contract surfaces
The contract lives in packages/contracts as 31 TypeScript modules with no
generated artifact, so the only way to see the whole surface was to read
rpc.ts and environmentHttp.ts and follow imports. Index it instead: all 70
WsRpcGroup methods with payload/success/scope, all 20 dispatchable commands,
all 23 EnvironmentHttpApi endpoints, the ServerConfig handshake and its
version-negotiation rules, and the subscription semantics a schema-correct
server can still break.
Also records what is deliberately outside the typed surfaces (raw HTTP
routes, the IPC/relay/MCP boundaries, the Schema.Unknown envelopes) so
"fully typed" is not overclaimed, and notes that WS_METHODS.projectsList /
projectsAdd / projectsRemove are dead constants with no Rpc.make.
Tables were generated by parsing the source and verified back against it:
method names, line numbers, stream flags, and required scopes all match
rpc.ts and ws.ts RPC_REQUIRED_SCOPE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* wip
* docs: map the client/server contract onto Moatless
Feature-by-feature walk of all three contract surfaces against the Moatless
backend: 23 HTTP endpoints, 70 RPC methods, and the 20-member command union.
Records which concepts are isomorphic, which only look alike, and what each
side has that the other cannot express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the preview mapping — tab vs. server process
T3's preview.* is the in-app browser tab (PreviewSessionSnapshot is
navStatus/canGoBack/viewport); Moatless's preview server is the process
serving the app. T3's process side is a ProjectScript in a terminal, found
by PortScanner. The declared-vs-discovered difference lands on
subscribeDiscoveredLocalServers, not preview.open.
Retallies the WS surface: 4 equivalent, 26 approximate, 40 absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the message-event claim — two events, not one
agent.message is the slim persisted log row (ADR-0003 / migration 0089);
message.upsert is a separate derived event carrying an assembled UiMessage,
relayed live and never persisted. Streaming assembled content is possible;
full tool detail is not, and the content-carrying event is the one that
cannot be replayed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: design the T3/Moatless convergence
Four moves, deliberately not all in the same direction: T3 adopts Moatless's
task+sandbox model, preview servers, and per-sandbox diagnostics scoping;
Moatless adopts T3's sequenced resumable streaming contract.
Records the governing decision (the deployment is the environment, the
sandbox is thread-scoped state), the two hard parts (unpersisted
message.upsert, bigserial as a cursor), and what each move costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: add local Moatless adapter workflow
* chore: preserve repository links
* feat(web): use Moatless auth for primary UI
* fix(web): allow Moatless cookie login after 401 session
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

light mode - #4

Merged
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components
Feb 9, 2026
Merged

light mode#4
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 9, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-02-08 at 22 05 13@2xCleanShot 2026-02-08 at 22 05 17@2x

Summary by CodeRabbit

  • New Features

    • Large library of reusable UI components added (accordions, dialogs, menus, inputs, selects, command palette, toasts, sheets, tables, etc.) for richer, consistent interfaces.
  • Theme

    • Dark theme support with system detection, cross-tab sync, and a theme toggle in the sidebar.
  • Style

    • Replaced hard-coded colors with semantic design tokens and global CSS variables for consistent light/dark theming and improved visuals.

@coderabbitai

coderabbitaiBot commented Feb 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds a large set of new UI components and utilities to the renderer, introduces a CSS-variable based theme (light/dark/system) and a useTheme hook, replaces hard-coded color tokens with semantic tokens across core renderer components, adds a cn utility and components.json, adjusts deps/config and a small smoke-test script tweak.

Changes

Cohort / File(s)Summary
UI component library
apps/renderer/src/components/ui/*
Adds 30+ new UI modules (accordion, alert, autocomplete, avatar, badge, button, card, checkbox, collapsible, combobox, command, dialog, empty, field, fieldset, form, input-group, input, kbd, label, menu, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, spinner, switch, table, tabs, textarea, toast). Each wraps @base-ui/react primitives, applies data-slot attributes, CVA variants and composed Tailwind classes. Review API shapes, typings, and consistent class/slot usage.
Core renderer components
apps/renderer/src/App.tsx, apps/renderer/src/components/ChatView.tsx, apps/renderer/src/components/ChatMarkdown.tsx, apps/renderer/src/components/DiffPanel.tsx, apps/renderer/src/components/Sidebar.tsx
Replaces hex color classes with semantic tokens; ChatView integrates new InputGroup, Select, Button components and removes previous textarea auto-resize logic; ChatMarkdown and DiffPanel styling tokens updated; Sidebar adds theme toggle and theme-aware styling. Validate behavioral changes where logic was removed (e.g., textarea resizing, model menu handling).
Theming & CSS
apps/renderer/src/index.css, apps/renderer/src/hooks/useTheme.ts
Introduces CSS variable theme system with light/dark variants and a .no-transitions helper; adds many CSS token definitions and dark overrides. Adds useTheme hook (light
Utilities & small libs
apps/renderer/src/lib/utils.ts, apps/renderer/components.json
Adds cn(...inputs) util combining clsx + twMerge. Adds components.json with UI framework config, aliases, Tailwind/tokens and registries. Confirm imports use the new alias and utility.
Config & build
apps/renderer/tsconfig.json, apps/renderer/vite.config.ts, biome.json
Adds #/* path alias and resolves # in Vite; sets exactOptionalPropertyTypes: false in tsconfig; relaxes biome a11y rule noSvgWithoutTitle.
Dependencies
apps/renderer/package.json
Adds runtime deps including @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge and reorders react-markdown. Verify lockfile and install.
Markup & scripts
apps/renderer/index.html, apps/desktop/scripts/smoke-test.mjs
Removes body Tailwind class in index.html. Small smoke-test script tweak: reorder imports from node:child_process and use template literal for error output. Minor script change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check❓ InconclusiveThe PR title 'light mode' is vague and does not clearly describe the actual scope of changes, which include comprehensive UI component library implementation, theming system, styling updates, and design system token migration across the renderer application.Consider a more descriptive title that captures the main work, such as 'Add design system UI components and light/dark theme support' or 'Implement base UI component library with theming.'
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch juliusmarminge/use-base-components

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/renderer/src/components/ChatView.tsx (1)

312-344: Consider using cn utility for conditional class handling.

The deeply nested ternaries for status indicator styling reduce readability. Using the cn utility with object syntax would make the phase-based styling clearer and more maintainable.

♻️ Suggested refactor using cn utility
+const statusContainerClasses: Record<string, string> = {+ running: "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100",+ connecting: "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100",+ ready: "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100",+ disconnected: "border-border bg-secondary text-muted-foreground",+};++const statusDotClasses: Record<string, string> = {+ running: "bg-sky-200",+ connecting: "bg-amber-200",+ ready: "bg-emerald-200",+ disconnected: "bg-muted-foreground/40",+};

Then use:

<divclassName={cn("inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px]",statusContainerClasses[phase]??statusContainerClasses.disconnected)}>

Based on learnings: "Use Tailwind CSS for styling - no inline styles, use cn for conditional classes".

apps/renderer/src/components/Sidebar.tsx (1)

138-152: Prefer Coss UI for the new icon-only theme toggle.

To keep styling and interaction patterns consistent with the rest of the app, consider swapping this raw <button> for the equivalent Coss UI icon/button primitive.

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeappBot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Add light/dark/system theme cycling and apply token-based theming across Sidebar, ChatView, and global styles to introduce light mode

Introduce a useTheme hook with localStorage persistence and a Sidebar header button to cycle system/light/dark; switch global and app styling to CSS variables and semantic Tailwind tokens; refactor ChatView and Sidebar UI to use shared UI primitives; remove thread status badges, editor/approval flows, and keyboard shortcuts; update model selection in new project flow.

📍Where to Start

Start with the useTheme hook in useTheme.ts and its integration in the Sidebar in Sidebar.tsx, then review theme token application in index.css.


Macroscope summarized 0a535ae.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/Sidebar.tsx`:
- Around line 225-230: The button currently uses the disabled prop which
conflicts with accessibility guidance and tooltips; instead remove the disabled
attribute on the button element, add aria-disabled={isPickingFolder} to reflect
state, keep/adjust the visual "disabled" classes conditionally when
isPickingFolder, and add a click guard at the top of handlePickFolder (or inline
in the onClick wrapper) that returns early if isPickingFolder so clicks are
ignored; ensure tooltip behavior is preserved by using aria-disabled rather than
disabled.
In `@apps/renderer/src/components/ui/combobox.tsx`:
- Around line 196-214: The SVG in the Combobox component uses an incorrect xmlns
value; update the xmlns on the SVG inside ComboboxPrimitive.ItemIndicator to the
correct "http://www.w3.org/2000/svg" string so the icon renders properly (look
for the SVG element in the ComboboxPrimitive.ItemIndicator within the
combobox.tsx component and change the xmlns attribute value).
In `@apps/renderer/src/components/ui/empty.tsx`:
- Around line 94-105: The component EmptyDescription is typed as
React.ComponentProps<"p"> but renders a <div>, causing a props/semantic
mismatch; fix it by making the types and markup consistent—either change the
rendered element to <p> (keep the same props type) or change the prop type to
React.ComponentProps<"div"> (keep the <div>); update the function signature
(EmptyDescription) accordingly and ensure spreading {...props}, className and
data-slot="empty-description" remain correct for the chosen element.
In `@apps/renderer/src/components/ui/scroll-area.tsx`:
- Around line 5-35: Replace the custom use of ScrollAreaPrimitive
(ScrollAreaPrimitive.Root, ScrollAreaPrimitive.Viewport,
ScrollAreaPrimitive.Corner) and the local ScrollBar components with the Coss UI
ScrollArea wrapper: install the package via the shadcn CLI (`pnpm dlx
shadcn@latest add `@coss/scroll-area``), import Coss's ScrollArea,
ScrollAreaViewport and Corner (or the exported equivalents) and re-create this
component as a thin wrapper named ScrollArea that forwards props and children
while preserving the scrollFade and scrollbarGutter booleans by mapping them to
equivalent className masks and gutter classes on the Coss component (apply the
same mask expressions and `data-has-overflow-*` gutter classes), and ensure
data-slot attributes (scroll-area-viewport, scroll-area-corner) remain on the
mapped children so existing consumers continue to work.
In `@apps/renderer/src/components/ui/select.tsx`:
- Around line 128-142: The SVG used inside SelectPrimitive.ItemIndicator has a
typo in its xmlns attribute; update the xmlns on the SVG element within
SelectPrimitive.ItemIndicator to the correct namespace
"http://www.w3.org/2000/svg" so the checkmark renders correctly across browsers
(locate the SVG in the SelectPrimitive.ItemIndicator block and replace the
incorrect "http://www.w3.org/1500/svg" value).
In `@apps/renderer/src/components/ui/skeleton.tsx`:
- Around line 1-3: The file uses React.ComponentProps<"div"> in the Skeleton
component but doesn't import the React namespace; add an import for React (e.g.,
add "import React from 'react'" or "import type React from 'react'") at the top
of apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.
🧹 Nitpick comments (6)
apps/renderer/src/components/ui/table.tsx (1)

36-47: Consider extracting long class strings for maintainability.

The class string on line 40 is quite long with many frame-conditional styles. While this is a common pattern in shadcn-style UI libraries, consider extracting the frame-specific styles into a separate variable or using Tailwind's @apply directive in a CSS file for better readability.

That said, this is a stylistic preference and the current implementation is functionally correct.

apps/renderer/src/components/ui/separator.tsx (1)

13-13: The CSS selector for vertical self-stretch is fragile and likely won't work as intended.

The selector not-[[class^='h-']]:not-[[class*='_h-']] checks whether the class attribute string starts with 'h-' or contains '_h-'. Since other classes (e.g., shrink-0) will appear first in the composed className, the [class^='h-'] condition will almost never match—even when a height utility like h-10 is present.

Consider simplifying by always applying self-stretch for vertical orientation and letting explicit height classes override it naturally, or document that consumers should use inline styles / CSS variables for custom heights:

♻️ Suggested simplification
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
apps/renderer/src/components/ui/toast.tsx (1)

220-250: Consider extracting shared toast content into a reusable component.

The toast content rendering here (icon, title, description, action button) is nearly identical to lines 128-158 in the Toasts component. Extracting this into a shared ToastContent component would reduce duplication and ensure consistency when making future changes.

♻️ Suggested extraction
// Shared component to use in both Toasts and AnchoredToastsfunctionToastContentBody({ toast }: {toast: Toast.ToastObject}){constIcon=toast.type
? TOAST_ICONS[toast.typeaskeyoftypeofTOAST_ICONS]
: null;return(<Toast.ContentclassName="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm ..."><divclassName="flex gap-2">{Icon&&(<divclassName="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"data-slot="toast-icon"><IconclassName="in-data-[type=loading]:animate-spin ..."/></div>)}<divclassName="flex flex-col gap-0.5"><Toast.TitleclassName="font-medium"data-slot="toast-title"/><Toast.DescriptionclassName="text-muted-foreground"data-slot="toast-description"/></div></div>{toast.actionProps&&(<Toast.ActionclassName={buttonVariants({size: "xs"})}data-slot="toast-action">{toast.actionProps.children}</Toast.Action>)}</Toast.Content>);}
apps/renderer/src/components/ui/kbd.tsx (1)

5-26: Prefer Coss UI primitives for consistency.

These custom Kbd/KbdGroup wrappers diverge from the guideline to use Coss UI components (installed via the ShadCN CLI) for consistent styling and Base UI accessibility alignment. If a Coss kbd component exists, consider swapping to it (or document why bespoke wrappers are required).

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling. Install new components using the ShadCN CLI (e.g., bunx --bun shadcnlatest add coss/button). Refer to Base UI docs for accessibility guidelines on primitives.

apps/renderer/src/components/ui/textarea.tsx (1)

7-10: Consider documenting or removing the number type for size.

The size prop accepts "sm" | "default" | "lg" | number, but the internal styling logic (lines 35-38) only handles the string literals. When a number is passed, it's set on data-size but no corresponding styles are applied.

If numeric sizes are intended for custom CSS handling via data-size, consider adding a brief comment documenting this. Otherwise, consider restricting the type to the supported string literals only.

apps/renderer/src/index.css (1)

227-242: Consider using CSS variables for scrollbar colors for consistency.

The scrollbar styling uses hardcoded rgba() values while the rest of the file uses CSS variables. For better theme consistency, consider using the defined tokens:

♻️ Optional: Use CSS variables for scrollbar colors
 ::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);+ background: var(--muted-foreground);+ opacity: 0.15;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 0, 0, 0.25);+ background: var(--muted-foreground);+ opacity: 0.25;
}
.dark ::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);+ background: var(--muted-foreground);+ opacity: 0.1;
}
.dark ::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.18);+ background: var(--muted-foreground);+ opacity: 0.18;
}

Note: Opacity on scrollbar thumbs may have limited browser support; the current approach works reliably.

Comment on lines 225 to 230
<button
type="button"
className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => void handlePickFolder()}
disabled={isPickingFolder}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid the disabled prop; use aria-disabled and a click guard instead.

The current disabled usage conflicts with the repo’s accessibility guidance and can break tooltips. Use styling plus a guard in the handler.

🛠️ Suggested fix
- className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"- onClick={() => void handlePickFolder()}- disabled={isPickingFolder}+ className={`mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary ${+ isPickingFolder ? "cursor-not-allowed opacity-60" : ""+ }`}+ aria-disabled={isPickingFolder}+ onClick={() => {+ if (isPickingFolder) return;+ void handlePickFolder();+ }}
Based on learnings: Avoid using disabled props on buttons as they harm accessibility and break tooltips.
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/Sidebar.tsx` around lines 225 - 230, The button
currently uses the disabled prop which conflicts with accessibility guidance and
tooltips; instead remove the disabled attribute on the button element, add
aria-disabled={isPickingFolder} to reflect state, keep/adjust the visual
"disabled" classes conditionally when isPickingFolder, and add a click guard at
the top of handlePickFolder (or inline in the onClick wrapper) that returns
early if isPickingFolder so clicks are ignored; ensure tooltip behavior is
preserved by using aria-disabled rather than disabled.

Comment on lines +196 to +214
<ComboboxPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</ComboboxPrimitive.ItemIndicator>
<div className="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

Same issue as in select.tsx: the xmlns attribute has http://www.w3.org/1500/svg instead of http://www.w3.org/2000/svg.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/combobox.tsx` around lines 196 - 214, The SVG
in the Combobox component uses an incorrect xmlns value; update the xmlns on the
SVG inside ComboboxPrimitive.ItemIndicator to the correct
"http://www.w3.org/2000/svg" string so the icon renders properly (look for the
SVG element in the ComboboxPrimitive.ItemIndicator within the combobox.tsx
component and change the xmlns attribute value).

Comment on lines +94 to +105
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: Component typed as <p> but renders <div>.

EmptyDescription accepts React.ComponentProps<"p"> but renders a <div>. This inconsistency could confuse consumers and allows <p>-specific props that won't apply to the rendered <div>.

💡 Proposed fix

Either change the rendered element to match the type:

-function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {+function EmptyDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div

Or render a <p> to match the type (if semantically appropriate):

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
- <div+ <p
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
- />+ />
);
}
📝 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.

Suggested change
functionEmptyDescription({ className, ...props}: React.ComponentProps<"p">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
functionEmptyDescription({ className, ...props}: React.ComponentProps<"div">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/empty.tsx` around lines 94 - 105, The
component EmptyDescription is typed as React.ComponentProps<"p"> but renders a
<div>, causing a props/semantic mismatch; fix it by making the types and markup
consistent—either change the rendered element to <p> (keep the same props type)
or change the prop type to React.ComponentProps<"div"> (keep the <div>); update
the function signature (EmptyDescription) accordingly and ensure spreading
{...props}, className and data-slot="empty-description" remain correct for the
chosen element.

Comment on lines +5 to +35
function ScrollArea({
className,
children,
scrollFade = false,
scrollbarGutter = false,
...props
}: ScrollAreaPrimitive.Root.Props & {
scrollFade?: boolean;
scrollbarGutter?: boolean;
}) {
return (
<ScrollAreaPrimitive.Root
className={cn("size-full min-h-0", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
scrollFade &&
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
scrollbarGutter &&
"data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
)}
data-slot="scroll-area-viewport"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation="vertical" />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Coss UI ScrollArea component documentation installation shadcn

💡 Result:

Coss UI – ScrollArea (shadcn CLI install + docs)

  • Docs:coss.com/ui/docs/components/scroll-area (coss.com)
  • Install (shadcn CLI):
    pnpm dlx shadcn@latest add `@coss/scroll-area`
    (coss.com)
  • Use:
    import{ScrollArea}from"@/components/ui/scroll-area"
    (coss.com)

If you meant the core shadcn/ui ScrollArea (Radix/Base UI), the install command is:

pnpm dlx shadcn@latest add scroll-area

(ui.shadcn.com)

Citations:


🏁 Script executed:

# Check for existing UI component usage patterns in the repository
find apps/renderer/src/components/ui -type f -name "*.tsx"| head -20

Repository: pingdotgg/codething-mvp

Length of output: 957


🏁 Script executed:

# Check package.json to see what UI libraries are currently in use
cat package.json | grep -A 20 '"dependencies"'| head -40

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Search for existing imports of UI components to understand patterns
rg "from.*coss|from.*shadcn" apps/renderer/src/components/ui --no-heading -A 1 | head -30

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Check if ScrollAreaPrimitive is imported from `@radix-ui` or another source
head -50 apps/renderer/src/components/ui/scroll-area.tsx

Repository: pingdotgg/codething-mvp

Length of output: 2171


Replace with Coss UI ScrollArea component for consistency with codebase guidelines.

Coss UI provides a ScrollArea component that should be installed via ShadCN CLI: pnpm dlx shadcn@latest add @coss/scroll-area``. The custom extensions (scrollFade, `scrollbarGutter`) can be preserved by wrapping the Coss UI component instead of using Base UI directly. This ensures alignment with the team's component catalog and styling standards.

🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/scroll-area.tsx` around lines 5 - 35, Replace
the custom use of ScrollAreaPrimitive (ScrollAreaPrimitive.Root,
ScrollAreaPrimitive.Viewport, ScrollAreaPrimitive.Corner) and the local
ScrollBar components with the Coss UI ScrollArea wrapper: install the package
via the shadcn CLI (`pnpm dlx shadcn@latest add `@coss/scroll-area``), import
Coss's ScrollArea, ScrollAreaViewport and Corner (or the exported equivalents)
and re-create this component as a thin wrapper named ScrollArea that forwards
props and children while preserving the scrollFade and scrollbarGutter booleans
by mapping them to equivalent className masks and gutter classes on the Coss
component (apply the same mask expressions and `data-has-overflow-*` gutter
classes), and ensure data-slot attributes (scroll-area-viewport,
scroll-area-corner) remain on the mapped children so existing consumers continue
to work.

Comment on lines +128 to +142
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

The xmlns attribute has a typo: http://www.w3.org/1500/svg should be http://www.w3.org/2000/svg. This invalid namespace may cause the checkmark SVG to fail to render in some browsers.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/select.tsx` around lines 128 - 142, The SVG
used inside SelectPrimitive.ItemIndicator has a typo in its xmlns attribute;
update the xmlns on the SVG element within SelectPrimitive.ItemIndicator to the
correct namespace "http://www.w3.org/2000/svg" so the checkmark renders
correctly across browsers (locate the SVG in the SelectPrimitive.ItemIndicator
block and replace the incorrect "http://www.w3.org/1500/svg" value).

Comment on lines +1 to +3
import { cn } from "#/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/renderer/src/components/ui/skeleton.tsx | head -20

Repository: pingdotgg/codething-mvp

Length of output: 723


🏁 Script executed:

find . -name "tsconfig.json" -type f | head -5

Repository: pingdotgg/codething-mvp

Length of output: 160


🏁 Script executed:

cat tsconfig.json 2>/dev/null || cat ./apps/renderer/tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -exec cat {} \;

Repository: pingdotgg/codething-mvp

Length of output: 388


Add React type import for ComponentProps reference.

Line 3 references React.ComponentProps<"div"> without importing the React namespace. While the modern JSX transform (jsx: "react-jsx") eliminates the need for React in JSX elements, explicit namespace references like React.ComponentProps still require the import and will cause a type-checking error.

+import type * as React from "react";
import { cn } from "#/lib/utils";
📝 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.

Suggested change
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
importtype*asReactfrom"react";
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/skeleton.tsx` around lines 1 - 3, The file
uses React.ComponentProps<"div"> in the Skeleton component but doesn't import
the React namespace; add an import for React (e.g., add "import React from
'react'" or "import type React from 'react'") at the top of
apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/ChatView.tsx`:
- Around line 478-482: The UI currently renders effort labels by capitalizing
the first character in the map over REASONING_OPTIONS (in the SelectItem
rendering), which turns values like "xhigh" into "Xhigh"; replace this logic by
defining a display-name mapping (e.g., a constant object like
REASONING_DISPLAY_NAMES keyed by the same values in REASONING_OPTIONS) and use
that map when rendering the SelectItem label and when comparing to
DEFAULT_REASONING, falling back to a nicer formatter (e.g., full word
replacements or Title Case) only if a key is missing; update the SelectItem
render to use REASONING_DISPLAY_NAMES[effort] || fallbackFormatter(effort).
🧹 Nitpick comments (1)
apps/renderer/src/components/ChatView.tsx (1)

451-456: Minor: Redundant dark: prefixes for transparent and semantic tokens.

bg-transparent is identical in both modes, and bg-accent is a semantic token that already adapts to the theme. The dark: prefixes are unnecessary here.

♻️ Simplified styling
 <SelectTrigger
size="sm"
- className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"+ className="w-auto min-w-0 border-0 shadow-none bg-transparent before:hidden data-[popup-open]:bg-accent"
>

Apply to both SelectTrigger instances (lines 451-456 and 471-476).

Also applies to: 471-476

Comment on lines +478 to +482
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
{effort.charAt(0).toUpperCase() + effort.slice(1)}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalization of "xhigh" produces awkward "Xhigh" in the UI.

The inline capitalization effort.charAt(0).toUpperCase() + effort.slice(1) transforms "xhigh" to "Xhigh", which isn't user-friendly. Consider a display name mapping for better readability.

💡 Suggested approach
+const EFFORT_LABELS: Record<string, string> = {+ xhigh: "Extra High",+ high: "High",+ medium: "Medium",+ low: "Low",+};+
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
- {effort.charAt(0).toUpperCase() + effort.slice(1)}+ {EFFORT_LABELS[effort] ?? effort}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
))}
📝 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.

Suggested change
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{effort.charAt(0).toUpperCase()+effort.slice(1)}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
constEFFORT_LABELS: Record<string,string>={
xhigh: "Extra High",
high: "High",
medium: "Medium",
low: "Low",
};
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{EFFORT_LABELS[effort]??effort}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ChatView.tsx` around lines 478 - 482, The UI
currently renders effort labels by capitalizing the first character in the map
over REASONING_OPTIONS (in the SelectItem rendering), which turns values like
"xhigh" into "Xhigh"; replace this logic by defining a display-name mapping
(e.g., a constant object like REASONING_DISPLAY_NAMES keyed by the same values
in REASONING_OPTIONS) and use that map when rendering the SelectItem label and
when comparing to DEFAULT_REASONING, falling back to a nicer formatter (e.g.,
full word replacements or Title Case) only if a key is missing; update the
SelectItem render to use REASONING_DISPLAY_NAMES[effort] ||
fallbackFormatter(effort).

juliusmarmingeand others added 3 commits February 9, 2026 01:12
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the juliusmarminge/use-base-components branch from 35deea6 to 0a535aeCompareFebruary 9, 2026 09:13
@t3dotgg
t3dotgg merged commit c51f252 into mainFeb 9, 2026
1 check passed
t3dotgg added a commit that referenced this pull request Feb 9, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
praveenperera pushed a commit to praveenperera/t3code that referenced this pull request Mar 23, 2026
nikrabaev added a commit to nikrabaev/t3code that referenced this pull request Apr 25, 2026
The original route URL /$environmentId/$weaveRunId collided with the
existing chat thread route /$environmentId/$threadId — TanStack Router
matches by URL pattern, not by route ID, so the chat route always won.
Opening any weave run dropped onto the chat route, which then ran its
'thread doesn't exist → redirect' effect in a tight loop and exhausted
React's update depth.
Add a literal 'weave' segment to the weave URL so the patterns are
distinct:
before: /$envId/$weaveRunId vs /$envId/$threadId
after : /$envId/weave/$weaveRunId vs /$envId/$threadId
The plan flagged this exact risk in §'Open design questions' pingdotgg#4 and
prescribed this fallback. Files renamed; consumers (ChatView dispatch,
WeaveBlueprintList node nav, WeaveRunSidebarItem Link, WeaveCreatedMarker
Link) updated. Route tree regenerated by Vite plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 14, 2026
Two cleanups flagged by thermo-review:
- Phase partition (Standards #1 + Quality #3b): the active/recent split
hardcoded the terminal-phase list twice as complementary filters. Extract
TERMINAL_PHASES + partitionIssuesByPhase into sandcastleView.ts so the two
buckets can't drift, with unit tests.
- Issue rows (Quality #3c): the Active and Recent sections hand-rolled the
same #number link-or-span + phaseLabel badge. Extract one SandcastleIssueRow
component with a variant prop; classNames preserved verbatim (no visual
change). Collapses ~80 lines to ~40.
Also replace as-never test casts (Quality pingdotgg#4) with a real RepositoryIdentity
fixture mirroring environmentGrouping.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 17, 2026
Gate computeRevertTurnCountByUserMessageId on the union of the span's
agent edit sets (ADR-0004 decision 4), not a single turn; normalize web
agent paths to repo-relative and match the checkpoint diff by equality
instead of a suffix heuristic. Review findings: spec gating, quality pingdotgg#4/pingdotgg#5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 3, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: contracts for blob store, mirror RPCs, and registry
M1 analysis pass recorded in the plan doc (deviations: settings-flag home,
reactor gating pattern, no server-side peer endpoint discovery, mirror RPC
transport, D4 credential handshake).
Contracts:
- WorkspaceProjectId branded id (D1)
- packages/contracts/src/roaming.ts: RoamingBlobRecord/manifest/conflict (D3),
registry payload, RoamingProjectShell, peer + mirror + enrollment RPC
schemas, HTTP path constants
- roaming:mirror auth scope; `roaming` server setting (default off)
- OrchestrationShellSnapshot.roamingProjects (decode-default []) + shell
stream variants; optional workspaceProjectId on project shapes and
project.meta-updated
- internal command project.roaming.enroll with decider/projector/engine
wiring (emits project.meta-updated; double-enroll rejected)
Test literals updated for the new snapshot field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Roaming M1 contracts: apply review findings
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: roaming_blobs migration + RoamingBlobStore
Migration 033 (roaming_blobs + roaming_blob_conflicts, PK (kind,key)) and a
SqlClient-backed RoamingBlobStore: writeLocal (version bump, sha256 of the
verbatim payload string, author environment id), applyRemote implementing the
D3 reconciliation rule (higher version wins; equal version + different hash
records a conflict carrying the full remote record and keeps local),
manifest/get/getMany/listConflicts, and a changes stream that emits only on
accepted writes. Reuses persistence error types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: accepted writes clear superseded conflict rows
codex review P2: a recorded equal-version conflict stayed in
roaming_blob_conflicts after a later accepted write moved the key past the
contested version, so listConflicts() kept surfacing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: apply independent review findings
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 20, 2026
ServerSettings.pushNotifications was hand-edited in settings.json with
the app stopped. It now has a "Phone push" section in Settings, General:
topic URL, click-through base URL, per-phase toggles, presence
suppression window, and a Send test button.
The write path already existed end to end (ServerSettingsPatch exposed
pushNotifications; server.updateSettings deep-merges), so the only new
plumbing is server.sendTestPushNotification: it fires a push with the
saved settings, bypasses presence suppression by design (the user is at
the machine clicking the button), and returns delivery problems as
{sent, detail} values rather than errors so the UI shows them inline.
Because splitPatch is shallow, the UI always writes the full
pushNotifications object.
Verification caveat, recorded in FORK_ROADMAP.md: in an isolated
browser-paired dev environment, server-settings writes from the web UI
silently never reach the server, for this section and for long-shipped
settings alike (optimistic update, no RPC, no error). Pre-existing
behavior, not a regression from this change; the Electron daily driver
is the real test. The send-test function is covered by focused tests
(delivery, click-header omission, rejection, timeout-free unreachable
host, empty topic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 21, 2026
…ttings writes
Two live bugs in the roadmap pingdotgg#4 ship:
- server.sendTestPushNotification was missing from RPC_REQUIRED_SCOPE, so
every call defected server-side ("has no declared authorization scope")
before reaching the handler. That map is a third registration point beyond
the contracts group and the ws handler; wsRpcScopes.test.ts now asserts it
covers every rpc in WsRpcGroup.
- Settings toggles persisted but never reflected in the UI when the config
projection was stalled (durable subscriptions resume late/never after a
reconnect — diagnosis written up under FORK_ROADMAP pingdotgg#5). useSettings.ts now
holds the settings object the server returns from updateSettings as an
acked overlay merged over the atom value, cleared when the projection
delivers a fresh value. This also stops stale full-object spreads from
clobbering earlier writes.
Verified in an isolated env: healthy toggle round-trip, toggle during the
post-reconnect stall window (reflects immediately, server state tracks UI),
send test returns "Delivered (HTTP 200)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
…tgg#4)
* feat(web): proxy the dev server at a Moatless backend
The fork's client talks to Moatless rather than a local t3 server, so the
dev server has to stand in as the API origin.
- T3CODE_PROXY_TARGET (alias MOATLESS_BASE_URL) selects the proxy target,
falling back to the VITE_WS_URL-derived one so stock `pnpm dev` is
unchanged. /api, /ws, /attachments and /.well-known all follow it.
- VITE_WS_URL / VITE_HTTP_URL are blanked when the override is active so
the client resolves to window.location.origin instead of reaching past
the proxy, which is what holds the credential.
- Authorization: Bearer is injected and cookie stripped only when the
target is not loopback, so a Moatless key can never reach a local t3
server.
- Credential-shaped VITE_* values are deleted from process.env before
Vite reads them, and a build-time assertion rejects any `define` value
containing an API-key prefix. Vite inlines the whole import.meta.env
object and Moatless sandboxes inject VITE_API_KEY=mvk_…, so without
this the key ships in the bundle.
- allowedHosts: true — a hosted sandbox's preview hostname is not
knowable ahead of time.
Two additions are specific to running inside a hosted sandbox:
T3CODE_PROXY_TARGET_OVERRIDE is read from repo env files only. loadRepoEnv
lets the ambient environment win over .env.local, which is right for
everything else it resolves, but a sandbox injects T3CODE_PROXY_TARGET
pointing at its own bundled server and a checkout needs to be able to say
"no, use mine" without editing the deployment.
Fast Refresh is turned off when NODE_ENV=production. @vitejs/plugin-react
enables the oxc Fast Refresh transform for every `serve` run but injects
its runtime preamble only when the config resolves as development, so a
sandbox that exports NODE_ENV=production process-wide gets modules full
of $RefreshReg$ calls with nothing to answer them and a blank page with
no server-side error. Forcing NODE_ENV=development instead trades that
for react/compiler-runtime's `c` export vanishing behind a lexer-opaque
conditional, so applying the plugin's own skipFastRefresh rule to the
transform is the smaller fix.
* docs: full spec for the t3code -> Moatless adapter
Records the architecture the fork settled on (a server-side adapter
rather than swapping client-side seams), the verified domain and message
mappings, the contract surface the adapter has to serve, the milestone
state including what M2 still owes, and the eleven measured Moatless API
gaps each workaround points back to.
* docs: record the M3 browser verification and the merged-repository case
The unit tests prove the projections decode; only a browser proves an
untouched t3 client accepts them. Records what the Playwright run against
the live adapter actually asserted, including the one 404 it tolerates
and why that is correct.
Also documents a case the run surfaced: two Moatless repositories can
point at the same git remote, and t3 groups the sidebar by repository
identity, so they render under one header. Left alone deliberately —
synthesizing a distinct canonicalKey would misreport identity that t3
also uses for worktree and VCS association.
* fix(web): don't pin the HMR socket to a wildcard bind address
Upstream pins hmr.protocol/host/clientPort so the socket connects reliably
inside Electron's BrowserWindow, whose window loads http://localhost:<port>
directly. A hosted sandbox sets HOST=0.0.0.0, which turned that into a literal
ws://0.0.0.0:5733/ in the served /@vite/client — an address no browser can dial,
and mixed-content-blocked once the page is served over an HTTPS preview
hostname. Unset, Vite's client derives the socket URL from the page's own
origin, which is right behind a proxy and for a plain local run alike, so pin
the endpoint only when HOST names a dialable address.
Also records in the plan doc why the preview URL 504s: the gateway's packets
never reach the pod (no socket on 5733 for the full 30s of an external request,
while the untouched t3 sidecar on 13773 fails the same way), so that one is
infrastructure, not this checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the root cause of the preview-URL 504
Traefik IngressRoutes and the sandbox Service are built per task from the
repository's declared server ports, but the NetworkPolicy that says who may
dial a sandbox takes its Traefik ingress ports from a fixed list in values.yaml
that stops at 8888. t3code uses 5733 and 13773, so the CNI drops the SYN and
Traefik times out into a 504.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: preview URL fixed — record which policy actually gated it
The chart's Traefik ingress rule names a namespace this cluster does not have,
so it could never match. The live gate was moatless-sandbox-preview-ingress, an
ArgoCD-managed policy carrying a copy of the same static port list. With its
ports block removed, the preview host serves / in 42ms and the shell endpoint in
54ms with 7 projects and 98 open threads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: spec the client/server contract surfaces
The contract lives in packages/contracts as 31 TypeScript modules with no
generated artifact, so the only way to see the whole surface was to read
rpc.ts and environmentHttp.ts and follow imports. Index it instead: all 70
WsRpcGroup methods with payload/success/scope, all 20 dispatchable commands,
all 23 EnvironmentHttpApi endpoints, the ServerConfig handshake and its
version-negotiation rules, and the subscription semantics a schema-correct
server can still break.
Also records what is deliberately outside the typed surfaces (raw HTTP
routes, the IPC/relay/MCP boundaries, the Schema.Unknown envelopes) so
"fully typed" is not overclaimed, and notes that WS_METHODS.projectsList /
projectsAdd / projectsRemove are dead constants with no Rpc.make.
Tables were generated by parsing the source and verified back against it:
method names, line numbers, stream flags, and required scopes all match
rpc.ts and ws.ts RPC_REQUIRED_SCOPE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* wip
* docs: map the client/server contract onto Moatless
Feature-by-feature walk of all three contract surfaces against the Moatless
backend: 23 HTTP endpoints, 70 RPC methods, and the 20-member command union.
Records which concepts are isomorphic, which only look alike, and what each
side has that the other cannot express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the preview mapping — tab vs. server process
T3's preview.* is the in-app browser tab (PreviewSessionSnapshot is
navStatus/canGoBack/viewport); Moatless's preview server is the process
serving the app. T3's process side is a ProjectScript in a terminal, found
by PortScanner. The declared-vs-discovered difference lands on
subscribeDiscoveredLocalServers, not preview.open.
Retallies the WS surface: 4 equivalent, 26 approximate, 40 absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the message-event claim — two events, not one
agent.message is the slim persisted log row (ADR-0003 / migration 0089);
message.upsert is a separate derived event carrying an assembled UiMessage,
relayed live and never persisted. Streaming assembled content is possible;
full tool detail is not, and the content-carrying event is the one that
cannot be replayed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: design the T3/Moatless convergence
Four moves, deliberately not all in the same direction: T3 adopts Moatless's
task+sandbox model, preview servers, and per-sandbox diagnostics scoping;
Moatless adopts T3's sequenced resumable streaming contract.
Records the governing decision (the deployment is the environment, the
sandbox is thread-scoped state), the two hard parts (unpersisted
message.upsert, bigserial as a cursor), and what each move costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: add local Moatless adapter workflow
* chore: preserve repository links
* feat(web): use Moatless auth for primary UI
* fix(web): allow Moatless cookie login after 401 session
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

light mode - #4

Merged
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components
Feb 9, 2026
Merged

light mode#4
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 9, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-02-08 at 22 05 13@2xCleanShot 2026-02-08 at 22 05 17@2x

Summary by CodeRabbit

  • New Features

    • Large library of reusable UI components added (accordions, dialogs, menus, inputs, selects, command palette, toasts, sheets, tables, etc.) for richer, consistent interfaces.
  • Theme

    • Dark theme support with system detection, cross-tab sync, and a theme toggle in the sidebar.
  • Style

    • Replaced hard-coded colors with semantic design tokens and global CSS variables for consistent light/dark theming and improved visuals.

@coderabbitai

coderabbitaiBot commented Feb 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds a large set of new UI components and utilities to the renderer, introduces a CSS-variable based theme (light/dark/system) and a useTheme hook, replaces hard-coded color tokens with semantic tokens across core renderer components, adds a cn utility and components.json, adjusts deps/config and a small smoke-test script tweak.

Changes

Cohort / File(s)Summary
UI component library
apps/renderer/src/components/ui/*
Adds 30+ new UI modules (accordion, alert, autocomplete, avatar, badge, button, card, checkbox, collapsible, combobox, command, dialog, empty, field, fieldset, form, input-group, input, kbd, label, menu, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, spinner, switch, table, tabs, textarea, toast). Each wraps @base-ui/react primitives, applies data-slot attributes, CVA variants and composed Tailwind classes. Review API shapes, typings, and consistent class/slot usage.
Core renderer components
apps/renderer/src/App.tsx, apps/renderer/src/components/ChatView.tsx, apps/renderer/src/components/ChatMarkdown.tsx, apps/renderer/src/components/DiffPanel.tsx, apps/renderer/src/components/Sidebar.tsx
Replaces hex color classes with semantic tokens; ChatView integrates new InputGroup, Select, Button components and removes previous textarea auto-resize logic; ChatMarkdown and DiffPanel styling tokens updated; Sidebar adds theme toggle and theme-aware styling. Validate behavioral changes where logic was removed (e.g., textarea resizing, model menu handling).
Theming & CSS
apps/renderer/src/index.css, apps/renderer/src/hooks/useTheme.ts
Introduces CSS variable theme system with light/dark variants and a .no-transitions helper; adds many CSS token definitions and dark overrides. Adds useTheme hook (light
Utilities & small libs
apps/renderer/src/lib/utils.ts, apps/renderer/components.json
Adds cn(...inputs) util combining clsx + twMerge. Adds components.json with UI framework config, aliases, Tailwind/tokens and registries. Confirm imports use the new alias and utility.
Config & build
apps/renderer/tsconfig.json, apps/renderer/vite.config.ts, biome.json
Adds #/* path alias and resolves # in Vite; sets exactOptionalPropertyTypes: false in tsconfig; relaxes biome a11y rule noSvgWithoutTitle.
Dependencies
apps/renderer/package.json
Adds runtime deps including @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge and reorders react-markdown. Verify lockfile and install.
Markup & scripts
apps/renderer/index.html, apps/desktop/scripts/smoke-test.mjs
Removes body Tailwind class in index.html. Small smoke-test script tweak: reorder imports from node:child_process and use template literal for error output. Minor script change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check❓ InconclusiveThe PR title 'light mode' is vague and does not clearly describe the actual scope of changes, which include comprehensive UI component library implementation, theming system, styling updates, and design system token migration across the renderer application.Consider a more descriptive title that captures the main work, such as 'Add design system UI components and light/dark theme support' or 'Implement base UI component library with theming.'
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch juliusmarminge/use-base-components

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/renderer/src/components/ChatView.tsx (1)

312-344: Consider using cn utility for conditional class handling.

The deeply nested ternaries for status indicator styling reduce readability. Using the cn utility with object syntax would make the phase-based styling clearer and more maintainable.

♻️ Suggested refactor using cn utility
+const statusContainerClasses: Record<string, string> = {+ running: "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100",+ connecting: "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100",+ ready: "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100",+ disconnected: "border-border bg-secondary text-muted-foreground",+};++const statusDotClasses: Record<string, string> = {+ running: "bg-sky-200",+ connecting: "bg-amber-200",+ ready: "bg-emerald-200",+ disconnected: "bg-muted-foreground/40",+};

Then use:

<divclassName={cn("inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px]",statusContainerClasses[phase]??statusContainerClasses.disconnected)}>

Based on learnings: "Use Tailwind CSS for styling - no inline styles, use cn for conditional classes".

apps/renderer/src/components/Sidebar.tsx (1)

138-152: Prefer Coss UI for the new icon-only theme toggle.

To keep styling and interaction patterns consistent with the rest of the app, consider swapping this raw <button> for the equivalent Coss UI icon/button primitive.

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeappBot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Add light/dark/system theme cycling and apply token-based theming across Sidebar, ChatView, and global styles to introduce light mode

Introduce a useTheme hook with localStorage persistence and a Sidebar header button to cycle system/light/dark; switch global and app styling to CSS variables and semantic Tailwind tokens; refactor ChatView and Sidebar UI to use shared UI primitives; remove thread status badges, editor/approval flows, and keyboard shortcuts; update model selection in new project flow.

📍Where to Start

Start with the useTheme hook in useTheme.ts and its integration in the Sidebar in Sidebar.tsx, then review theme token application in index.css.


Macroscope summarized 0a535ae.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/Sidebar.tsx`:
- Around line 225-230: The button currently uses the disabled prop which
conflicts with accessibility guidance and tooltips; instead remove the disabled
attribute on the button element, add aria-disabled={isPickingFolder} to reflect
state, keep/adjust the visual "disabled" classes conditionally when
isPickingFolder, and add a click guard at the top of handlePickFolder (or inline
in the onClick wrapper) that returns early if isPickingFolder so clicks are
ignored; ensure tooltip behavior is preserved by using aria-disabled rather than
disabled.
In `@apps/renderer/src/components/ui/combobox.tsx`:
- Around line 196-214: The SVG in the Combobox component uses an incorrect xmlns
value; update the xmlns on the SVG inside ComboboxPrimitive.ItemIndicator to the
correct "http://www.w3.org/2000/svg" string so the icon renders properly (look
for the SVG element in the ComboboxPrimitive.ItemIndicator within the
combobox.tsx component and change the xmlns attribute value).
In `@apps/renderer/src/components/ui/empty.tsx`:
- Around line 94-105: The component EmptyDescription is typed as
React.ComponentProps<"p"> but renders a <div>, causing a props/semantic
mismatch; fix it by making the types and markup consistent—either change the
rendered element to <p> (keep the same props type) or change the prop type to
React.ComponentProps<"div"> (keep the <div>); update the function signature
(EmptyDescription) accordingly and ensure spreading {...props}, className and
data-slot="empty-description" remain correct for the chosen element.
In `@apps/renderer/src/components/ui/scroll-area.tsx`:
- Around line 5-35: Replace the custom use of ScrollAreaPrimitive
(ScrollAreaPrimitive.Root, ScrollAreaPrimitive.Viewport,
ScrollAreaPrimitive.Corner) and the local ScrollBar components with the Coss UI
ScrollArea wrapper: install the package via the shadcn CLI (`pnpm dlx
shadcn@latest add `@coss/scroll-area``), import Coss's ScrollArea,
ScrollAreaViewport and Corner (or the exported equivalents) and re-create this
component as a thin wrapper named ScrollArea that forwards props and children
while preserving the scrollFade and scrollbarGutter booleans by mapping them to
equivalent className masks and gutter classes on the Coss component (apply the
same mask expressions and `data-has-overflow-*` gutter classes), and ensure
data-slot attributes (scroll-area-viewport, scroll-area-corner) remain on the
mapped children so existing consumers continue to work.
In `@apps/renderer/src/components/ui/select.tsx`:
- Around line 128-142: The SVG used inside SelectPrimitive.ItemIndicator has a
typo in its xmlns attribute; update the xmlns on the SVG element within
SelectPrimitive.ItemIndicator to the correct namespace
"http://www.w3.org/2000/svg" so the checkmark renders correctly across browsers
(locate the SVG in the SelectPrimitive.ItemIndicator block and replace the
incorrect "http://www.w3.org/1500/svg" value).
In `@apps/renderer/src/components/ui/skeleton.tsx`:
- Around line 1-3: The file uses React.ComponentProps<"div"> in the Skeleton
component but doesn't import the React namespace; add an import for React (e.g.,
add "import React from 'react'" or "import type React from 'react'") at the top
of apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.
🧹 Nitpick comments (6)
apps/renderer/src/components/ui/table.tsx (1)

36-47: Consider extracting long class strings for maintainability.

The class string on line 40 is quite long with many frame-conditional styles. While this is a common pattern in shadcn-style UI libraries, consider extracting the frame-specific styles into a separate variable or using Tailwind's @apply directive in a CSS file for better readability.

That said, this is a stylistic preference and the current implementation is functionally correct.

apps/renderer/src/components/ui/separator.tsx (1)

13-13: The CSS selector for vertical self-stretch is fragile and likely won't work as intended.

The selector not-[[class^='h-']]:not-[[class*='_h-']] checks whether the class attribute string starts with 'h-' or contains '_h-'. Since other classes (e.g., shrink-0) will appear first in the composed className, the [class^='h-'] condition will almost never match—even when a height utility like h-10 is present.

Consider simplifying by always applying self-stretch for vertical orientation and letting explicit height classes override it naturally, or document that consumers should use inline styles / CSS variables for custom heights:

♻️ Suggested simplification
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
apps/renderer/src/components/ui/toast.tsx (1)

220-250: Consider extracting shared toast content into a reusable component.

The toast content rendering here (icon, title, description, action button) is nearly identical to lines 128-158 in the Toasts component. Extracting this into a shared ToastContent component would reduce duplication and ensure consistency when making future changes.

♻️ Suggested extraction
// Shared component to use in both Toasts and AnchoredToastsfunctionToastContentBody({ toast }: {toast: Toast.ToastObject}){constIcon=toast.type
? TOAST_ICONS[toast.typeaskeyoftypeofTOAST_ICONS]
: null;return(<Toast.ContentclassName="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm ..."><divclassName="flex gap-2">{Icon&&(<divclassName="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"data-slot="toast-icon"><IconclassName="in-data-[type=loading]:animate-spin ..."/></div>)}<divclassName="flex flex-col gap-0.5"><Toast.TitleclassName="font-medium"data-slot="toast-title"/><Toast.DescriptionclassName="text-muted-foreground"data-slot="toast-description"/></div></div>{toast.actionProps&&(<Toast.ActionclassName={buttonVariants({size: "xs"})}data-slot="toast-action">{toast.actionProps.children}</Toast.Action>)}</Toast.Content>);}
apps/renderer/src/components/ui/kbd.tsx (1)

5-26: Prefer Coss UI primitives for consistency.

These custom Kbd/KbdGroup wrappers diverge from the guideline to use Coss UI components (installed via the ShadCN CLI) for consistent styling and Base UI accessibility alignment. If a Coss kbd component exists, consider swapping to it (or document why bespoke wrappers are required).

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling. Install new components using the ShadCN CLI (e.g., bunx --bun shadcnlatest add coss/button). Refer to Base UI docs for accessibility guidelines on primitives.

apps/renderer/src/components/ui/textarea.tsx (1)

7-10: Consider documenting or removing the number type for size.

The size prop accepts "sm" | "default" | "lg" | number, but the internal styling logic (lines 35-38) only handles the string literals. When a number is passed, it's set on data-size but no corresponding styles are applied.

If numeric sizes are intended for custom CSS handling via data-size, consider adding a brief comment documenting this. Otherwise, consider restricting the type to the supported string literals only.

apps/renderer/src/index.css (1)

227-242: Consider using CSS variables for scrollbar colors for consistency.

The scrollbar styling uses hardcoded rgba() values while the rest of the file uses CSS variables. For better theme consistency, consider using the defined tokens:

♻️ Optional: Use CSS variables for scrollbar colors
 ::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);+ background: var(--muted-foreground);+ opacity: 0.15;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 0, 0, 0.25);+ background: var(--muted-foreground);+ opacity: 0.25;
}
.dark ::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);+ background: var(--muted-foreground);+ opacity: 0.1;
}
.dark ::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.18);+ background: var(--muted-foreground);+ opacity: 0.18;
}

Note: Opacity on scrollbar thumbs may have limited browser support; the current approach works reliably.

Comment on lines 225 to 230
<button
type="button"
className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => void handlePickFolder()}
disabled={isPickingFolder}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid the disabled prop; use aria-disabled and a click guard instead.

The current disabled usage conflicts with the repo’s accessibility guidance and can break tooltips. Use styling plus a guard in the handler.

🛠️ Suggested fix
- className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"- onClick={() => void handlePickFolder()}- disabled={isPickingFolder}+ className={`mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary ${+ isPickingFolder ? "cursor-not-allowed opacity-60" : ""+ }`}+ aria-disabled={isPickingFolder}+ onClick={() => {+ if (isPickingFolder) return;+ void handlePickFolder();+ }}
Based on learnings: Avoid using disabled props on buttons as they harm accessibility and break tooltips.
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/Sidebar.tsx` around lines 225 - 230, The button
currently uses the disabled prop which conflicts with accessibility guidance and
tooltips; instead remove the disabled attribute on the button element, add
aria-disabled={isPickingFolder} to reflect state, keep/adjust the visual
"disabled" classes conditionally when isPickingFolder, and add a click guard at
the top of handlePickFolder (or inline in the onClick wrapper) that returns
early if isPickingFolder so clicks are ignored; ensure tooltip behavior is
preserved by using aria-disabled rather than disabled.

Comment on lines +196 to +214
<ComboboxPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</ComboboxPrimitive.ItemIndicator>
<div className="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

Same issue as in select.tsx: the xmlns attribute has http://www.w3.org/1500/svg instead of http://www.w3.org/2000/svg.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/combobox.tsx` around lines 196 - 214, The SVG
in the Combobox component uses an incorrect xmlns value; update the xmlns on the
SVG inside ComboboxPrimitive.ItemIndicator to the correct
"http://www.w3.org/2000/svg" string so the icon renders properly (look for the
SVG element in the ComboboxPrimitive.ItemIndicator within the combobox.tsx
component and change the xmlns attribute value).

Comment on lines +94 to +105
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: Component typed as <p> but renders <div>.

EmptyDescription accepts React.ComponentProps<"p"> but renders a <div>. This inconsistency could confuse consumers and allows <p>-specific props that won't apply to the rendered <div>.

💡 Proposed fix

Either change the rendered element to match the type:

-function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {+function EmptyDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div

Or render a <p> to match the type (if semantically appropriate):

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
- <div+ <p
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
- />+ />
);
}
📝 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.

Suggested change
functionEmptyDescription({ className, ...props}: React.ComponentProps<"p">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
functionEmptyDescription({ className, ...props}: React.ComponentProps<"div">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/empty.tsx` around lines 94 - 105, The
component EmptyDescription is typed as React.ComponentProps<"p"> but renders a
<div>, causing a props/semantic mismatch; fix it by making the types and markup
consistent—either change the rendered element to <p> (keep the same props type)
or change the prop type to React.ComponentProps<"div"> (keep the <div>); update
the function signature (EmptyDescription) accordingly and ensure spreading
{...props}, className and data-slot="empty-description" remain correct for the
chosen element.

Comment on lines +5 to +35
function ScrollArea({
className,
children,
scrollFade = false,
scrollbarGutter = false,
...props
}: ScrollAreaPrimitive.Root.Props & {
scrollFade?: boolean;
scrollbarGutter?: boolean;
}) {
return (
<ScrollAreaPrimitive.Root
className={cn("size-full min-h-0", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
scrollFade &&
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
scrollbarGutter &&
"data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
)}
data-slot="scroll-area-viewport"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation="vertical" />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Coss UI ScrollArea component documentation installation shadcn

💡 Result:

Coss UI – ScrollArea (shadcn CLI install + docs)

  • Docs:coss.com/ui/docs/components/scroll-area (coss.com)
  • Install (shadcn CLI):
    pnpm dlx shadcn@latest add `@coss/scroll-area`
    (coss.com)
  • Use:
    import{ScrollArea}from"@/components/ui/scroll-area"
    (coss.com)

If you meant the core shadcn/ui ScrollArea (Radix/Base UI), the install command is:

pnpm dlx shadcn@latest add scroll-area

(ui.shadcn.com)

Citations:


🏁 Script executed:

# Check for existing UI component usage patterns in the repository
find apps/renderer/src/components/ui -type f -name "*.tsx"| head -20

Repository: pingdotgg/codething-mvp

Length of output: 957


🏁 Script executed:

# Check package.json to see what UI libraries are currently in use
cat package.json | grep -A 20 '"dependencies"'| head -40

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Search for existing imports of UI components to understand patterns
rg "from.*coss|from.*shadcn" apps/renderer/src/components/ui --no-heading -A 1 | head -30

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Check if ScrollAreaPrimitive is imported from `@radix-ui` or another source
head -50 apps/renderer/src/components/ui/scroll-area.tsx

Repository: pingdotgg/codething-mvp

Length of output: 2171


Replace with Coss UI ScrollArea component for consistency with codebase guidelines.

Coss UI provides a ScrollArea component that should be installed via ShadCN CLI: pnpm dlx shadcn@latest add @coss/scroll-area``. The custom extensions (scrollFade, `scrollbarGutter`) can be preserved by wrapping the Coss UI component instead of using Base UI directly. This ensures alignment with the team's component catalog and styling standards.

🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/scroll-area.tsx` around lines 5 - 35, Replace
the custom use of ScrollAreaPrimitive (ScrollAreaPrimitive.Root,
ScrollAreaPrimitive.Viewport, ScrollAreaPrimitive.Corner) and the local
ScrollBar components with the Coss UI ScrollArea wrapper: install the package
via the shadcn CLI (`pnpm dlx shadcn@latest add `@coss/scroll-area``), import
Coss's ScrollArea, ScrollAreaViewport and Corner (or the exported equivalents)
and re-create this component as a thin wrapper named ScrollArea that forwards
props and children while preserving the scrollFade and scrollbarGutter booleans
by mapping them to equivalent className masks and gutter classes on the Coss
component (apply the same mask expressions and `data-has-overflow-*` gutter
classes), and ensure data-slot attributes (scroll-area-viewport,
scroll-area-corner) remain on the mapped children so existing consumers continue
to work.

Comment on lines +128 to +142
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

The xmlns attribute has a typo: http://www.w3.org/1500/svg should be http://www.w3.org/2000/svg. This invalid namespace may cause the checkmark SVG to fail to render in some browsers.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/select.tsx` around lines 128 - 142, The SVG
used inside SelectPrimitive.ItemIndicator has a typo in its xmlns attribute;
update the xmlns on the SVG element within SelectPrimitive.ItemIndicator to the
correct namespace "http://www.w3.org/2000/svg" so the checkmark renders
correctly across browsers (locate the SVG in the SelectPrimitive.ItemIndicator
block and replace the incorrect "http://www.w3.org/1500/svg" value).

Comment on lines +1 to +3
import { cn } from "#/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/renderer/src/components/ui/skeleton.tsx | head -20

Repository: pingdotgg/codething-mvp

Length of output: 723


🏁 Script executed:

find . -name "tsconfig.json" -type f | head -5

Repository: pingdotgg/codething-mvp

Length of output: 160


🏁 Script executed:

cat tsconfig.json 2>/dev/null || cat ./apps/renderer/tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -exec cat {} \;

Repository: pingdotgg/codething-mvp

Length of output: 388


Add React type import for ComponentProps reference.

Line 3 references React.ComponentProps<"div"> without importing the React namespace. While the modern JSX transform (jsx: "react-jsx") eliminates the need for React in JSX elements, explicit namespace references like React.ComponentProps still require the import and will cause a type-checking error.

+import type * as React from "react";
import { cn } from "#/lib/utils";
📝 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.

Suggested change
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
importtype*asReactfrom"react";
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/skeleton.tsx` around lines 1 - 3, The file
uses React.ComponentProps<"div"> in the Skeleton component but doesn't import
the React namespace; add an import for React (e.g., add "import React from
'react'" or "import type React from 'react'") at the top of
apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/ChatView.tsx`:
- Around line 478-482: The UI currently renders effort labels by capitalizing
the first character in the map over REASONING_OPTIONS (in the SelectItem
rendering), which turns values like "xhigh" into "Xhigh"; replace this logic by
defining a display-name mapping (e.g., a constant object like
REASONING_DISPLAY_NAMES keyed by the same values in REASONING_OPTIONS) and use
that map when rendering the SelectItem label and when comparing to
DEFAULT_REASONING, falling back to a nicer formatter (e.g., full word
replacements or Title Case) only if a key is missing; update the SelectItem
render to use REASONING_DISPLAY_NAMES[effort] || fallbackFormatter(effort).
🧹 Nitpick comments (1)
apps/renderer/src/components/ChatView.tsx (1)

451-456: Minor: Redundant dark: prefixes for transparent and semantic tokens.

bg-transparent is identical in both modes, and bg-accent is a semantic token that already adapts to the theme. The dark: prefixes are unnecessary here.

♻️ Simplified styling
 <SelectTrigger
size="sm"
- className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"+ className="w-auto min-w-0 border-0 shadow-none bg-transparent before:hidden data-[popup-open]:bg-accent"
>

Apply to both SelectTrigger instances (lines 451-456 and 471-476).

Also applies to: 471-476

Comment on lines +478 to +482
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
{effort.charAt(0).toUpperCase() + effort.slice(1)}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalization of "xhigh" produces awkward "Xhigh" in the UI.

The inline capitalization effort.charAt(0).toUpperCase() + effort.slice(1) transforms "xhigh" to "Xhigh", which isn't user-friendly. Consider a display name mapping for better readability.

💡 Suggested approach
+const EFFORT_LABELS: Record<string, string> = {+ xhigh: "Extra High",+ high: "High",+ medium: "Medium",+ low: "Low",+};+
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
- {effort.charAt(0).toUpperCase() + effort.slice(1)}+ {EFFORT_LABELS[effort] ?? effort}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
))}
📝 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.

Suggested change
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{effort.charAt(0).toUpperCase()+effort.slice(1)}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
constEFFORT_LABELS: Record<string,string>={
xhigh: "Extra High",
high: "High",
medium: "Medium",
low: "Low",
};
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{EFFORT_LABELS[effort]??effort}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ChatView.tsx` around lines 478 - 482, The UI
currently renders effort labels by capitalizing the first character in the map
over REASONING_OPTIONS (in the SelectItem rendering), which turns values like
"xhigh" into "Xhigh"; replace this logic by defining a display-name mapping
(e.g., a constant object like REASONING_DISPLAY_NAMES keyed by the same values
in REASONING_OPTIONS) and use that map when rendering the SelectItem label and
when comparing to DEFAULT_REASONING, falling back to a nicer formatter (e.g.,
full word replacements or Title Case) only if a key is missing; update the
SelectItem render to use REASONING_DISPLAY_NAMES[effort] ||
fallbackFormatter(effort).

juliusmarmingeand others added 3 commits February 9, 2026 01:12
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the juliusmarminge/use-base-components branch from 35deea6 to 0a535aeCompareFebruary 9, 2026 09:13
@t3dotgg
t3dotgg merged commit c51f252 into mainFeb 9, 2026
1 check passed
t3dotgg added a commit that referenced this pull request Feb 9, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
praveenperera pushed a commit to praveenperera/t3code that referenced this pull request Mar 23, 2026
nikrabaev added a commit to nikrabaev/t3code that referenced this pull request Apr 25, 2026
The original route URL /$environmentId/$weaveRunId collided with the
existing chat thread route /$environmentId/$threadId — TanStack Router
matches by URL pattern, not by route ID, so the chat route always won.
Opening any weave run dropped onto the chat route, which then ran its
'thread doesn't exist → redirect' effect in a tight loop and exhausted
React's update depth.
Add a literal 'weave' segment to the weave URL so the patterns are
distinct:
before: /$envId/$weaveRunId vs /$envId/$threadId
after : /$envId/weave/$weaveRunId vs /$envId/$threadId
The plan flagged this exact risk in §'Open design questions' pingdotgg#4 and
prescribed this fallback. Files renamed; consumers (ChatView dispatch,
WeaveBlueprintList node nav, WeaveRunSidebarItem Link, WeaveCreatedMarker
Link) updated. Route tree regenerated by Vite plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 14, 2026
Two cleanups flagged by thermo-review:
- Phase partition (Standards #1 + Quality #3b): the active/recent split
hardcoded the terminal-phase list twice as complementary filters. Extract
TERMINAL_PHASES + partitionIssuesByPhase into sandcastleView.ts so the two
buckets can't drift, with unit tests.
- Issue rows (Quality #3c): the Active and Recent sections hand-rolled the
same #number link-or-span + phaseLabel badge. Extract one SandcastleIssueRow
component with a variant prop; classNames preserved verbatim (no visual
change). Collapses ~80 lines to ~40.
Also replace as-never test casts (Quality pingdotgg#4) with a real RepositoryIdentity
fixture mirroring environmentGrouping.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 17, 2026
Gate computeRevertTurnCountByUserMessageId on the union of the span's
agent edit sets (ADR-0004 decision 4), not a single turn; normalize web
agent paths to repo-relative and match the checkpoint diff by equality
instead of a suffix heuristic. Review findings: spec gating, quality pingdotgg#4/pingdotgg#5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 3, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: contracts for blob store, mirror RPCs, and registry
M1 analysis pass recorded in the plan doc (deviations: settings-flag home,
reactor gating pattern, no server-side peer endpoint discovery, mirror RPC
transport, D4 credential handshake).
Contracts:
- WorkspaceProjectId branded id (D1)
- packages/contracts/src/roaming.ts: RoamingBlobRecord/manifest/conflict (D3),
registry payload, RoamingProjectShell, peer + mirror + enrollment RPC
schemas, HTTP path constants
- roaming:mirror auth scope; `roaming` server setting (default off)
- OrchestrationShellSnapshot.roamingProjects (decode-default []) + shell
stream variants; optional workspaceProjectId on project shapes and
project.meta-updated
- internal command project.roaming.enroll with decider/projector/engine
wiring (emits project.meta-updated; double-enroll rejected)
Test literals updated for the new snapshot field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Roaming M1 contracts: apply review findings
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: roaming_blobs migration + RoamingBlobStore
Migration 033 (roaming_blobs + roaming_blob_conflicts, PK (kind,key)) and a
SqlClient-backed RoamingBlobStore: writeLocal (version bump, sha256 of the
verbatim payload string, author environment id), applyRemote implementing the
D3 reconciliation rule (higher version wins; equal version + different hash
records a conflict carrying the full remote record and keeps local),
manifest/get/getMany/listConflicts, and a changes stream that emits only on
accepted writes. Reuses persistence error types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: accepted writes clear superseded conflict rows
codex review P2: a recorded equal-version conflict stayed in
roaming_blob_conflicts after a later accepted write moved the key past the
contested version, so listConflicts() kept surfacing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: apply independent review findings
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 20, 2026
ServerSettings.pushNotifications was hand-edited in settings.json with
the app stopped. It now has a "Phone push" section in Settings, General:
topic URL, click-through base URL, per-phase toggles, presence
suppression window, and a Send test button.
The write path already existed end to end (ServerSettingsPatch exposed
pushNotifications; server.updateSettings deep-merges), so the only new
plumbing is server.sendTestPushNotification: it fires a push with the
saved settings, bypasses presence suppression by design (the user is at
the machine clicking the button), and returns delivery problems as
{sent, detail} values rather than errors so the UI shows them inline.
Because splitPatch is shallow, the UI always writes the full
pushNotifications object.
Verification caveat, recorded in FORK_ROADMAP.md: in an isolated
browser-paired dev environment, server-settings writes from the web UI
silently never reach the server, for this section and for long-shipped
settings alike (optimistic update, no RPC, no error). Pre-existing
behavior, not a regression from this change; the Electron daily driver
is the real test. The send-test function is covered by focused tests
(delivery, click-header omission, rejection, timeout-free unreachable
host, empty topic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 21, 2026
…ttings writes
Two live bugs in the roadmap pingdotgg#4 ship:
- server.sendTestPushNotification was missing from RPC_REQUIRED_SCOPE, so
every call defected server-side ("has no declared authorization scope")
before reaching the handler. That map is a third registration point beyond
the contracts group and the ws handler; wsRpcScopes.test.ts now asserts it
covers every rpc in WsRpcGroup.
- Settings toggles persisted but never reflected in the UI when the config
projection was stalled (durable subscriptions resume late/never after a
reconnect — diagnosis written up under FORK_ROADMAP pingdotgg#5). useSettings.ts now
holds the settings object the server returns from updateSettings as an
acked overlay merged over the atom value, cleared when the projection
delivers a fresh value. This also stops stale full-object spreads from
clobbering earlier writes.
Verified in an isolated env: healthy toggle round-trip, toggle during the
post-reconnect stall window (reflects immediately, server state tracks UI),
send test returns "Delivered (HTTP 200)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
…tgg#4)
* feat(web): proxy the dev server at a Moatless backend
The fork's client talks to Moatless rather than a local t3 server, so the
dev server has to stand in as the API origin.
- T3CODE_PROXY_TARGET (alias MOATLESS_BASE_URL) selects the proxy target,
falling back to the VITE_WS_URL-derived one so stock `pnpm dev` is
unchanged. /api, /ws, /attachments and /.well-known all follow it.
- VITE_WS_URL / VITE_HTTP_URL are blanked when the override is active so
the client resolves to window.location.origin instead of reaching past
the proxy, which is what holds the credential.
- Authorization: Bearer is injected and cookie stripped only when the
target is not loopback, so a Moatless key can never reach a local t3
server.
- Credential-shaped VITE_* values are deleted from process.env before
Vite reads them, and a build-time assertion rejects any `define` value
containing an API-key prefix. Vite inlines the whole import.meta.env
object and Moatless sandboxes inject VITE_API_KEY=mvk_…, so without
this the key ships in the bundle.
- allowedHosts: true — a hosted sandbox's preview hostname is not
knowable ahead of time.
Two additions are specific to running inside a hosted sandbox:
T3CODE_PROXY_TARGET_OVERRIDE is read from repo env files only. loadRepoEnv
lets the ambient environment win over .env.local, which is right for
everything else it resolves, but a sandbox injects T3CODE_PROXY_TARGET
pointing at its own bundled server and a checkout needs to be able to say
"no, use mine" without editing the deployment.
Fast Refresh is turned off when NODE_ENV=production. @vitejs/plugin-react
enables the oxc Fast Refresh transform for every `serve` run but injects
its runtime preamble only when the config resolves as development, so a
sandbox that exports NODE_ENV=production process-wide gets modules full
of $RefreshReg$ calls with nothing to answer them and a blank page with
no server-side error. Forcing NODE_ENV=development instead trades that
for react/compiler-runtime's `c` export vanishing behind a lexer-opaque
conditional, so applying the plugin's own skipFastRefresh rule to the
transform is the smaller fix.
* docs: full spec for the t3code -> Moatless adapter
Records the architecture the fork settled on (a server-side adapter
rather than swapping client-side seams), the verified domain and message
mappings, the contract surface the adapter has to serve, the milestone
state including what M2 still owes, and the eleven measured Moatless API
gaps each workaround points back to.
* docs: record the M3 browser verification and the merged-repository case
The unit tests prove the projections decode; only a browser proves an
untouched t3 client accepts them. Records what the Playwright run against
the live adapter actually asserted, including the one 404 it tolerates
and why that is correct.
Also documents a case the run surfaced: two Moatless repositories can
point at the same git remote, and t3 groups the sidebar by repository
identity, so they render under one header. Left alone deliberately —
synthesizing a distinct canonicalKey would misreport identity that t3
also uses for worktree and VCS association.
* fix(web): don't pin the HMR socket to a wildcard bind address
Upstream pins hmr.protocol/host/clientPort so the socket connects reliably
inside Electron's BrowserWindow, whose window loads http://localhost:<port>
directly. A hosted sandbox sets HOST=0.0.0.0, which turned that into a literal
ws://0.0.0.0:5733/ in the served /@vite/client — an address no browser can dial,
and mixed-content-blocked once the page is served over an HTTPS preview
hostname. Unset, Vite's client derives the socket URL from the page's own
origin, which is right behind a proxy and for a plain local run alike, so pin
the endpoint only when HOST names a dialable address.
Also records in the plan doc why the preview URL 504s: the gateway's packets
never reach the pod (no socket on 5733 for the full 30s of an external request,
while the untouched t3 sidecar on 13773 fails the same way), so that one is
infrastructure, not this checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the root cause of the preview-URL 504
Traefik IngressRoutes and the sandbox Service are built per task from the
repository's declared server ports, but the NetworkPolicy that says who may
dial a sandbox takes its Traefik ingress ports from a fixed list in values.yaml
that stops at 8888. t3code uses 5733 and 13773, so the CNI drops the SYN and
Traefik times out into a 504.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: preview URL fixed — record which policy actually gated it
The chart's Traefik ingress rule names a namespace this cluster does not have,
so it could never match. The live gate was moatless-sandbox-preview-ingress, an
ArgoCD-managed policy carrying a copy of the same static port list. With its
ports block removed, the preview host serves / in 42ms and the shell endpoint in
54ms with 7 projects and 98 open threads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: spec the client/server contract surfaces
The contract lives in packages/contracts as 31 TypeScript modules with no
generated artifact, so the only way to see the whole surface was to read
rpc.ts and environmentHttp.ts and follow imports. Index it instead: all 70
WsRpcGroup methods with payload/success/scope, all 20 dispatchable commands,
all 23 EnvironmentHttpApi endpoints, the ServerConfig handshake and its
version-negotiation rules, and the subscription semantics a schema-correct
server can still break.
Also records what is deliberately outside the typed surfaces (raw HTTP
routes, the IPC/relay/MCP boundaries, the Schema.Unknown envelopes) so
"fully typed" is not overclaimed, and notes that WS_METHODS.projectsList /
projectsAdd / projectsRemove are dead constants with no Rpc.make.
Tables were generated by parsing the source and verified back against it:
method names, line numbers, stream flags, and required scopes all match
rpc.ts and ws.ts RPC_REQUIRED_SCOPE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* wip
* docs: map the client/server contract onto Moatless
Feature-by-feature walk of all three contract surfaces against the Moatless
backend: 23 HTTP endpoints, 70 RPC methods, and the 20-member command union.
Records which concepts are isomorphic, which only look alike, and what each
side has that the other cannot express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the preview mapping — tab vs. server process
T3's preview.* is the in-app browser tab (PreviewSessionSnapshot is
navStatus/canGoBack/viewport); Moatless's preview server is the process
serving the app. T3's process side is a ProjectScript in a terminal, found
by PortScanner. The declared-vs-discovered difference lands on
subscribeDiscoveredLocalServers, not preview.open.
Retallies the WS surface: 4 equivalent, 26 approximate, 40 absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the message-event claim — two events, not one
agent.message is the slim persisted log row (ADR-0003 / migration 0089);
message.upsert is a separate derived event carrying an assembled UiMessage,
relayed live and never persisted. Streaming assembled content is possible;
full tool detail is not, and the content-carrying event is the one that
cannot be replayed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: design the T3/Moatless convergence
Four moves, deliberately not all in the same direction: T3 adopts Moatless's
task+sandbox model, preview servers, and per-sandbox diagnostics scoping;
Moatless adopts T3's sequenced resumable streaming contract.
Records the governing decision (the deployment is the environment, the
sandbox is thread-scoped state), the two hard parts (unpersisted
message.upsert, bigserial as a cursor), and what each move costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: add local Moatless adapter workflow
* chore: preserve repository links
* feat(web): use Moatless auth for primary UI
* fix(web): allow Moatless cookie login after 401 session
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

light mode - #4

Merged
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components
Feb 9, 2026
Merged

light mode#4
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 9, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-02-08 at 22 05 13@2xCleanShot 2026-02-08 at 22 05 17@2x

Summary by CodeRabbit

  • New Features

    • Large library of reusable UI components added (accordions, dialogs, menus, inputs, selects, command palette, toasts, sheets, tables, etc.) for richer, consistent interfaces.
  • Theme

    • Dark theme support with system detection, cross-tab sync, and a theme toggle in the sidebar.
  • Style

    • Replaced hard-coded colors with semantic design tokens and global CSS variables for consistent light/dark theming and improved visuals.

@coderabbitai

coderabbitaiBot commented Feb 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds a large set of new UI components and utilities to the renderer, introduces a CSS-variable based theme (light/dark/system) and a useTheme hook, replaces hard-coded color tokens with semantic tokens across core renderer components, adds a cn utility and components.json, adjusts deps/config and a small smoke-test script tweak.

Changes

Cohort / File(s)Summary
UI component library
apps/renderer/src/components/ui/*
Adds 30+ new UI modules (accordion, alert, autocomplete, avatar, badge, button, card, checkbox, collapsible, combobox, command, dialog, empty, field, fieldset, form, input-group, input, kbd, label, menu, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, spinner, switch, table, tabs, textarea, toast). Each wraps @base-ui/react primitives, applies data-slot attributes, CVA variants and composed Tailwind classes. Review API shapes, typings, and consistent class/slot usage.
Core renderer components
apps/renderer/src/App.tsx, apps/renderer/src/components/ChatView.tsx, apps/renderer/src/components/ChatMarkdown.tsx, apps/renderer/src/components/DiffPanel.tsx, apps/renderer/src/components/Sidebar.tsx
Replaces hex color classes with semantic tokens; ChatView integrates new InputGroup, Select, Button components and removes previous textarea auto-resize logic; ChatMarkdown and DiffPanel styling tokens updated; Sidebar adds theme toggle and theme-aware styling. Validate behavioral changes where logic was removed (e.g., textarea resizing, model menu handling).
Theming & CSS
apps/renderer/src/index.css, apps/renderer/src/hooks/useTheme.ts
Introduces CSS variable theme system with light/dark variants and a .no-transitions helper; adds many CSS token definitions and dark overrides. Adds useTheme hook (light
Utilities & small libs
apps/renderer/src/lib/utils.ts, apps/renderer/components.json
Adds cn(...inputs) util combining clsx + twMerge. Adds components.json with UI framework config, aliases, Tailwind/tokens and registries. Confirm imports use the new alias and utility.
Config & build
apps/renderer/tsconfig.json, apps/renderer/vite.config.ts, biome.json
Adds #/* path alias and resolves # in Vite; sets exactOptionalPropertyTypes: false in tsconfig; relaxes biome a11y rule noSvgWithoutTitle.
Dependencies
apps/renderer/package.json
Adds runtime deps including @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge and reorders react-markdown. Verify lockfile and install.
Markup & scripts
apps/renderer/index.html, apps/desktop/scripts/smoke-test.mjs
Removes body Tailwind class in index.html. Small smoke-test script tweak: reorder imports from node:child_process and use template literal for error output. Minor script change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check❓ InconclusiveThe PR title 'light mode' is vague and does not clearly describe the actual scope of changes, which include comprehensive UI component library implementation, theming system, styling updates, and design system token migration across the renderer application.Consider a more descriptive title that captures the main work, such as 'Add design system UI components and light/dark theme support' or 'Implement base UI component library with theming.'
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch juliusmarminge/use-base-components

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/renderer/src/components/ChatView.tsx (1)

312-344: Consider using cn utility for conditional class handling.

The deeply nested ternaries for status indicator styling reduce readability. Using the cn utility with object syntax would make the phase-based styling clearer and more maintainable.

♻️ Suggested refactor using cn utility
+const statusContainerClasses: Record<string, string> = {+ running: "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100",+ connecting: "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100",+ ready: "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100",+ disconnected: "border-border bg-secondary text-muted-foreground",+};++const statusDotClasses: Record<string, string> = {+ running: "bg-sky-200",+ connecting: "bg-amber-200",+ ready: "bg-emerald-200",+ disconnected: "bg-muted-foreground/40",+};

Then use:

<divclassName={cn("inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px]",statusContainerClasses[phase]??statusContainerClasses.disconnected)}>

Based on learnings: "Use Tailwind CSS for styling - no inline styles, use cn for conditional classes".

apps/renderer/src/components/Sidebar.tsx (1)

138-152: Prefer Coss UI for the new icon-only theme toggle.

To keep styling and interaction patterns consistent with the rest of the app, consider swapping this raw <button> for the equivalent Coss UI icon/button primitive.

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeappBot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Add light/dark/system theme cycling and apply token-based theming across Sidebar, ChatView, and global styles to introduce light mode

Introduce a useTheme hook with localStorage persistence and a Sidebar header button to cycle system/light/dark; switch global and app styling to CSS variables and semantic Tailwind tokens; refactor ChatView and Sidebar UI to use shared UI primitives; remove thread status badges, editor/approval flows, and keyboard shortcuts; update model selection in new project flow.

📍Where to Start

Start with the useTheme hook in useTheme.ts and its integration in the Sidebar in Sidebar.tsx, then review theme token application in index.css.


Macroscope summarized 0a535ae.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/Sidebar.tsx`:
- Around line 225-230: The button currently uses the disabled prop which
conflicts with accessibility guidance and tooltips; instead remove the disabled
attribute on the button element, add aria-disabled={isPickingFolder} to reflect
state, keep/adjust the visual "disabled" classes conditionally when
isPickingFolder, and add a click guard at the top of handlePickFolder (or inline
in the onClick wrapper) that returns early if isPickingFolder so clicks are
ignored; ensure tooltip behavior is preserved by using aria-disabled rather than
disabled.
In `@apps/renderer/src/components/ui/combobox.tsx`:
- Around line 196-214: The SVG in the Combobox component uses an incorrect xmlns
value; update the xmlns on the SVG inside ComboboxPrimitive.ItemIndicator to the
correct "http://www.w3.org/2000/svg" string so the icon renders properly (look
for the SVG element in the ComboboxPrimitive.ItemIndicator within the
combobox.tsx component and change the xmlns attribute value).
In `@apps/renderer/src/components/ui/empty.tsx`:
- Around line 94-105: The component EmptyDescription is typed as
React.ComponentProps<"p"> but renders a <div>, causing a props/semantic
mismatch; fix it by making the types and markup consistent—either change the
rendered element to <p> (keep the same props type) or change the prop type to
React.ComponentProps<"div"> (keep the <div>); update the function signature
(EmptyDescription) accordingly and ensure spreading {...props}, className and
data-slot="empty-description" remain correct for the chosen element.
In `@apps/renderer/src/components/ui/scroll-area.tsx`:
- Around line 5-35: Replace the custom use of ScrollAreaPrimitive
(ScrollAreaPrimitive.Root, ScrollAreaPrimitive.Viewport,
ScrollAreaPrimitive.Corner) and the local ScrollBar components with the Coss UI
ScrollArea wrapper: install the package via the shadcn CLI (`pnpm dlx
shadcn@latest add `@coss/scroll-area``), import Coss's ScrollArea,
ScrollAreaViewport and Corner (or the exported equivalents) and re-create this
component as a thin wrapper named ScrollArea that forwards props and children
while preserving the scrollFade and scrollbarGutter booleans by mapping them to
equivalent className masks and gutter classes on the Coss component (apply the
same mask expressions and `data-has-overflow-*` gutter classes), and ensure
data-slot attributes (scroll-area-viewport, scroll-area-corner) remain on the
mapped children so existing consumers continue to work.
In `@apps/renderer/src/components/ui/select.tsx`:
- Around line 128-142: The SVG used inside SelectPrimitive.ItemIndicator has a
typo in its xmlns attribute; update the xmlns on the SVG element within
SelectPrimitive.ItemIndicator to the correct namespace
"http://www.w3.org/2000/svg" so the checkmark renders correctly across browsers
(locate the SVG in the SelectPrimitive.ItemIndicator block and replace the
incorrect "http://www.w3.org/1500/svg" value).
In `@apps/renderer/src/components/ui/skeleton.tsx`:
- Around line 1-3: The file uses React.ComponentProps<"div"> in the Skeleton
component but doesn't import the React namespace; add an import for React (e.g.,
add "import React from 'react'" or "import type React from 'react'") at the top
of apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.
🧹 Nitpick comments (6)
apps/renderer/src/components/ui/table.tsx (1)

36-47: Consider extracting long class strings for maintainability.

The class string on line 40 is quite long with many frame-conditional styles. While this is a common pattern in shadcn-style UI libraries, consider extracting the frame-specific styles into a separate variable or using Tailwind's @apply directive in a CSS file for better readability.

That said, this is a stylistic preference and the current implementation is functionally correct.

apps/renderer/src/components/ui/separator.tsx (1)

13-13: The CSS selector for vertical self-stretch is fragile and likely won't work as intended.

The selector not-[[class^='h-']]:not-[[class*='_h-']] checks whether the class attribute string starts with 'h-' or contains '_h-'. Since other classes (e.g., shrink-0) will appear first in the composed className, the [class^='h-'] condition will almost never match—even when a height utility like h-10 is present.

Consider simplifying by always applying self-stretch for vertical orientation and letting explicit height classes override it naturally, or document that consumers should use inline styles / CSS variables for custom heights:

♻️ Suggested simplification
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
apps/renderer/src/components/ui/toast.tsx (1)

220-250: Consider extracting shared toast content into a reusable component.

The toast content rendering here (icon, title, description, action button) is nearly identical to lines 128-158 in the Toasts component. Extracting this into a shared ToastContent component would reduce duplication and ensure consistency when making future changes.

♻️ Suggested extraction
// Shared component to use in both Toasts and AnchoredToastsfunctionToastContentBody({ toast }: {toast: Toast.ToastObject}){constIcon=toast.type
? TOAST_ICONS[toast.typeaskeyoftypeofTOAST_ICONS]
: null;return(<Toast.ContentclassName="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm ..."><divclassName="flex gap-2">{Icon&&(<divclassName="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"data-slot="toast-icon"><IconclassName="in-data-[type=loading]:animate-spin ..."/></div>)}<divclassName="flex flex-col gap-0.5"><Toast.TitleclassName="font-medium"data-slot="toast-title"/><Toast.DescriptionclassName="text-muted-foreground"data-slot="toast-description"/></div></div>{toast.actionProps&&(<Toast.ActionclassName={buttonVariants({size: "xs"})}data-slot="toast-action">{toast.actionProps.children}</Toast.Action>)}</Toast.Content>);}
apps/renderer/src/components/ui/kbd.tsx (1)

5-26: Prefer Coss UI primitives for consistency.

These custom Kbd/KbdGroup wrappers diverge from the guideline to use Coss UI components (installed via the ShadCN CLI) for consistent styling and Base UI accessibility alignment. If a Coss kbd component exists, consider swapping to it (or document why bespoke wrappers are required).

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling. Install new components using the ShadCN CLI (e.g., bunx --bun shadcnlatest add coss/button). Refer to Base UI docs for accessibility guidelines on primitives.

apps/renderer/src/components/ui/textarea.tsx (1)

7-10: Consider documenting or removing the number type for size.

The size prop accepts "sm" | "default" | "lg" | number, but the internal styling logic (lines 35-38) only handles the string literals. When a number is passed, it's set on data-size but no corresponding styles are applied.

If numeric sizes are intended for custom CSS handling via data-size, consider adding a brief comment documenting this. Otherwise, consider restricting the type to the supported string literals only.

apps/renderer/src/index.css (1)

227-242: Consider using CSS variables for scrollbar colors for consistency.

The scrollbar styling uses hardcoded rgba() values while the rest of the file uses CSS variables. For better theme consistency, consider using the defined tokens:

♻️ Optional: Use CSS variables for scrollbar colors
 ::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);+ background: var(--muted-foreground);+ opacity: 0.15;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 0, 0, 0.25);+ background: var(--muted-foreground);+ opacity: 0.25;
}
.dark ::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);+ background: var(--muted-foreground);+ opacity: 0.1;
}
.dark ::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.18);+ background: var(--muted-foreground);+ opacity: 0.18;
}

Note: Opacity on scrollbar thumbs may have limited browser support; the current approach works reliably.

Comment on lines 225 to 230
<button
type="button"
className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => void handlePickFolder()}
disabled={isPickingFolder}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid the disabled prop; use aria-disabled and a click guard instead.

The current disabled usage conflicts with the repo’s accessibility guidance and can break tooltips. Use styling plus a guard in the handler.

🛠️ Suggested fix
- className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"- onClick={() => void handlePickFolder()}- disabled={isPickingFolder}+ className={`mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary ${+ isPickingFolder ? "cursor-not-allowed opacity-60" : ""+ }`}+ aria-disabled={isPickingFolder}+ onClick={() => {+ if (isPickingFolder) return;+ void handlePickFolder();+ }}
Based on learnings: Avoid using disabled props on buttons as they harm accessibility and break tooltips.
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/Sidebar.tsx` around lines 225 - 230, The button
currently uses the disabled prop which conflicts with accessibility guidance and
tooltips; instead remove the disabled attribute on the button element, add
aria-disabled={isPickingFolder} to reflect state, keep/adjust the visual
"disabled" classes conditionally when isPickingFolder, and add a click guard at
the top of handlePickFolder (or inline in the onClick wrapper) that returns
early if isPickingFolder so clicks are ignored; ensure tooltip behavior is
preserved by using aria-disabled rather than disabled.

Comment on lines +196 to +214
<ComboboxPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</ComboboxPrimitive.ItemIndicator>
<div className="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

Same issue as in select.tsx: the xmlns attribute has http://www.w3.org/1500/svg instead of http://www.w3.org/2000/svg.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/combobox.tsx` around lines 196 - 214, The SVG
in the Combobox component uses an incorrect xmlns value; update the xmlns on the
SVG inside ComboboxPrimitive.ItemIndicator to the correct
"http://www.w3.org/2000/svg" string so the icon renders properly (look for the
SVG element in the ComboboxPrimitive.ItemIndicator within the combobox.tsx
component and change the xmlns attribute value).

Comment on lines +94 to +105
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: Component typed as <p> but renders <div>.

EmptyDescription accepts React.ComponentProps<"p"> but renders a <div>. This inconsistency could confuse consumers and allows <p>-specific props that won't apply to the rendered <div>.

💡 Proposed fix

Either change the rendered element to match the type:

-function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {+function EmptyDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div

Or render a <p> to match the type (if semantically appropriate):

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
- <div+ <p
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
- />+ />
);
}
📝 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.

Suggested change
functionEmptyDescription({ className, ...props}: React.ComponentProps<"p">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
functionEmptyDescription({ className, ...props}: React.ComponentProps<"div">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/empty.tsx` around lines 94 - 105, The
component EmptyDescription is typed as React.ComponentProps<"p"> but renders a
<div>, causing a props/semantic mismatch; fix it by making the types and markup
consistent—either change the rendered element to <p> (keep the same props type)
or change the prop type to React.ComponentProps<"div"> (keep the <div>); update
the function signature (EmptyDescription) accordingly and ensure spreading
{...props}, className and data-slot="empty-description" remain correct for the
chosen element.

Comment on lines +5 to +35
function ScrollArea({
className,
children,
scrollFade = false,
scrollbarGutter = false,
...props
}: ScrollAreaPrimitive.Root.Props & {
scrollFade?: boolean;
scrollbarGutter?: boolean;
}) {
return (
<ScrollAreaPrimitive.Root
className={cn("size-full min-h-0", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
scrollFade &&
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
scrollbarGutter &&
"data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
)}
data-slot="scroll-area-viewport"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation="vertical" />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Coss UI ScrollArea component documentation installation shadcn

💡 Result:

Coss UI – ScrollArea (shadcn CLI install + docs)

  • Docs:coss.com/ui/docs/components/scroll-area (coss.com)
  • Install (shadcn CLI):
    pnpm dlx shadcn@latest add `@coss/scroll-area`
    (coss.com)
  • Use:
    import{ScrollArea}from"@/components/ui/scroll-area"
    (coss.com)

If you meant the core shadcn/ui ScrollArea (Radix/Base UI), the install command is:

pnpm dlx shadcn@latest add scroll-area

(ui.shadcn.com)

Citations:


🏁 Script executed:

# Check for existing UI component usage patterns in the repository
find apps/renderer/src/components/ui -type f -name "*.tsx"| head -20

Repository: pingdotgg/codething-mvp

Length of output: 957


🏁 Script executed:

# Check package.json to see what UI libraries are currently in use
cat package.json | grep -A 20 '"dependencies"'| head -40

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Search for existing imports of UI components to understand patterns
rg "from.*coss|from.*shadcn" apps/renderer/src/components/ui --no-heading -A 1 | head -30

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Check if ScrollAreaPrimitive is imported from `@radix-ui` or another source
head -50 apps/renderer/src/components/ui/scroll-area.tsx

Repository: pingdotgg/codething-mvp

Length of output: 2171


Replace with Coss UI ScrollArea component for consistency with codebase guidelines.

Coss UI provides a ScrollArea component that should be installed via ShadCN CLI: pnpm dlx shadcn@latest add @coss/scroll-area``. The custom extensions (scrollFade, `scrollbarGutter`) can be preserved by wrapping the Coss UI component instead of using Base UI directly. This ensures alignment with the team's component catalog and styling standards.

🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/scroll-area.tsx` around lines 5 - 35, Replace
the custom use of ScrollAreaPrimitive (ScrollAreaPrimitive.Root,
ScrollAreaPrimitive.Viewport, ScrollAreaPrimitive.Corner) and the local
ScrollBar components with the Coss UI ScrollArea wrapper: install the package
via the shadcn CLI (`pnpm dlx shadcn@latest add `@coss/scroll-area``), import
Coss's ScrollArea, ScrollAreaViewport and Corner (or the exported equivalents)
and re-create this component as a thin wrapper named ScrollArea that forwards
props and children while preserving the scrollFade and scrollbarGutter booleans
by mapping them to equivalent className masks and gutter classes on the Coss
component (apply the same mask expressions and `data-has-overflow-*` gutter
classes), and ensure data-slot attributes (scroll-area-viewport,
scroll-area-corner) remain on the mapped children so existing consumers continue
to work.

Comment on lines +128 to +142
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

The xmlns attribute has a typo: http://www.w3.org/1500/svg should be http://www.w3.org/2000/svg. This invalid namespace may cause the checkmark SVG to fail to render in some browsers.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/select.tsx` around lines 128 - 142, The SVG
used inside SelectPrimitive.ItemIndicator has a typo in its xmlns attribute;
update the xmlns on the SVG element within SelectPrimitive.ItemIndicator to the
correct namespace "http://www.w3.org/2000/svg" so the checkmark renders
correctly across browsers (locate the SVG in the SelectPrimitive.ItemIndicator
block and replace the incorrect "http://www.w3.org/1500/svg" value).

Comment on lines +1 to +3
import { cn } from "#/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/renderer/src/components/ui/skeleton.tsx | head -20

Repository: pingdotgg/codething-mvp

Length of output: 723


🏁 Script executed:

find . -name "tsconfig.json" -type f | head -5

Repository: pingdotgg/codething-mvp

Length of output: 160


🏁 Script executed:

cat tsconfig.json 2>/dev/null || cat ./apps/renderer/tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -exec cat {} \;

Repository: pingdotgg/codething-mvp

Length of output: 388


Add React type import for ComponentProps reference.

Line 3 references React.ComponentProps<"div"> without importing the React namespace. While the modern JSX transform (jsx: "react-jsx") eliminates the need for React in JSX elements, explicit namespace references like React.ComponentProps still require the import and will cause a type-checking error.

+import type * as React from "react";
import { cn } from "#/lib/utils";
📝 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.

Suggested change
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
importtype*asReactfrom"react";
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/skeleton.tsx` around lines 1 - 3, The file
uses React.ComponentProps<"div"> in the Skeleton component but doesn't import
the React namespace; add an import for React (e.g., add "import React from
'react'" or "import type React from 'react'") at the top of
apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/ChatView.tsx`:
- Around line 478-482: The UI currently renders effort labels by capitalizing
the first character in the map over REASONING_OPTIONS (in the SelectItem
rendering), which turns values like "xhigh" into "Xhigh"; replace this logic by
defining a display-name mapping (e.g., a constant object like
REASONING_DISPLAY_NAMES keyed by the same values in REASONING_OPTIONS) and use
that map when rendering the SelectItem label and when comparing to
DEFAULT_REASONING, falling back to a nicer formatter (e.g., full word
replacements or Title Case) only if a key is missing; update the SelectItem
render to use REASONING_DISPLAY_NAMES[effort] || fallbackFormatter(effort).
🧹 Nitpick comments (1)
apps/renderer/src/components/ChatView.tsx (1)

451-456: Minor: Redundant dark: prefixes for transparent and semantic tokens.

bg-transparent is identical in both modes, and bg-accent is a semantic token that already adapts to the theme. The dark: prefixes are unnecessary here.

♻️ Simplified styling
 <SelectTrigger
size="sm"
- className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"+ className="w-auto min-w-0 border-0 shadow-none bg-transparent before:hidden data-[popup-open]:bg-accent"
>

Apply to both SelectTrigger instances (lines 451-456 and 471-476).

Also applies to: 471-476

Comment on lines +478 to +482
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
{effort.charAt(0).toUpperCase() + effort.slice(1)}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalization of "xhigh" produces awkward "Xhigh" in the UI.

The inline capitalization effort.charAt(0).toUpperCase() + effort.slice(1) transforms "xhigh" to "Xhigh", which isn't user-friendly. Consider a display name mapping for better readability.

💡 Suggested approach
+const EFFORT_LABELS: Record<string, string> = {+ xhigh: "Extra High",+ high: "High",+ medium: "Medium",+ low: "Low",+};+
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
- {effort.charAt(0).toUpperCase() + effort.slice(1)}+ {EFFORT_LABELS[effort] ?? effort}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
))}
📝 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.

Suggested change
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{effort.charAt(0).toUpperCase()+effort.slice(1)}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
constEFFORT_LABELS: Record<string,string>={
xhigh: "Extra High",
high: "High",
medium: "Medium",
low: "Low",
};
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{EFFORT_LABELS[effort]??effort}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ChatView.tsx` around lines 478 - 482, The UI
currently renders effort labels by capitalizing the first character in the map
over REASONING_OPTIONS (in the SelectItem rendering), which turns values like
"xhigh" into "Xhigh"; replace this logic by defining a display-name mapping
(e.g., a constant object like REASONING_DISPLAY_NAMES keyed by the same values
in REASONING_OPTIONS) and use that map when rendering the SelectItem label and
when comparing to DEFAULT_REASONING, falling back to a nicer formatter (e.g.,
full word replacements or Title Case) only if a key is missing; update the
SelectItem render to use REASONING_DISPLAY_NAMES[effort] ||
fallbackFormatter(effort).

juliusmarmingeand others added 3 commits February 9, 2026 01:12
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the juliusmarminge/use-base-components branch from 35deea6 to 0a535aeCompareFebruary 9, 2026 09:13
@t3dotgg
t3dotgg merged commit c51f252 into mainFeb 9, 2026
1 check passed
t3dotgg added a commit that referenced this pull request Feb 9, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
praveenperera pushed a commit to praveenperera/t3code that referenced this pull request Mar 23, 2026
nikrabaev added a commit to nikrabaev/t3code that referenced this pull request Apr 25, 2026
The original route URL /$environmentId/$weaveRunId collided with the
existing chat thread route /$environmentId/$threadId — TanStack Router
matches by URL pattern, not by route ID, so the chat route always won.
Opening any weave run dropped onto the chat route, which then ran its
'thread doesn't exist → redirect' effect in a tight loop and exhausted
React's update depth.
Add a literal 'weave' segment to the weave URL so the patterns are
distinct:
before: /$envId/$weaveRunId vs /$envId/$threadId
after : /$envId/weave/$weaveRunId vs /$envId/$threadId
The plan flagged this exact risk in §'Open design questions' pingdotgg#4 and
prescribed this fallback. Files renamed; consumers (ChatView dispatch,
WeaveBlueprintList node nav, WeaveRunSidebarItem Link, WeaveCreatedMarker
Link) updated. Route tree regenerated by Vite plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 14, 2026
Two cleanups flagged by thermo-review:
- Phase partition (Standards #1 + Quality #3b): the active/recent split
hardcoded the terminal-phase list twice as complementary filters. Extract
TERMINAL_PHASES + partitionIssuesByPhase into sandcastleView.ts so the two
buckets can't drift, with unit tests.
- Issue rows (Quality #3c): the Active and Recent sections hand-rolled the
same #number link-or-span + phaseLabel badge. Extract one SandcastleIssueRow
component with a variant prop; classNames preserved verbatim (no visual
change). Collapses ~80 lines to ~40.
Also replace as-never test casts (Quality pingdotgg#4) with a real RepositoryIdentity
fixture mirroring environmentGrouping.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 17, 2026
Gate computeRevertTurnCountByUserMessageId on the union of the span's
agent edit sets (ADR-0004 decision 4), not a single turn; normalize web
agent paths to repo-relative and match the checkpoint diff by equality
instead of a suffix heuristic. Review findings: spec gating, quality pingdotgg#4/pingdotgg#5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 3, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: contracts for blob store, mirror RPCs, and registry
M1 analysis pass recorded in the plan doc (deviations: settings-flag home,
reactor gating pattern, no server-side peer endpoint discovery, mirror RPC
transport, D4 credential handshake).
Contracts:
- WorkspaceProjectId branded id (D1)
- packages/contracts/src/roaming.ts: RoamingBlobRecord/manifest/conflict (D3),
registry payload, RoamingProjectShell, peer + mirror + enrollment RPC
schemas, HTTP path constants
- roaming:mirror auth scope; `roaming` server setting (default off)
- OrchestrationShellSnapshot.roamingProjects (decode-default []) + shell
stream variants; optional workspaceProjectId on project shapes and
project.meta-updated
- internal command project.roaming.enroll with decider/projector/engine
wiring (emits project.meta-updated; double-enroll rejected)
Test literals updated for the new snapshot field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Roaming M1 contracts: apply review findings
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: roaming_blobs migration + RoamingBlobStore
Migration 033 (roaming_blobs + roaming_blob_conflicts, PK (kind,key)) and a
SqlClient-backed RoamingBlobStore: writeLocal (version bump, sha256 of the
verbatim payload string, author environment id), applyRemote implementing the
D3 reconciliation rule (higher version wins; equal version + different hash
records a conflict carrying the full remote record and keeps local),
manifest/get/getMany/listConflicts, and a changes stream that emits only on
accepted writes. Reuses persistence error types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: accepted writes clear superseded conflict rows
codex review P2: a recorded equal-version conflict stayed in
roaming_blob_conflicts after a later accepted write moved the key past the
contested version, so listConflicts() kept surfacing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: apply independent review findings
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 20, 2026
ServerSettings.pushNotifications was hand-edited in settings.json with
the app stopped. It now has a "Phone push" section in Settings, General:
topic URL, click-through base URL, per-phase toggles, presence
suppression window, and a Send test button.
The write path already existed end to end (ServerSettingsPatch exposed
pushNotifications; server.updateSettings deep-merges), so the only new
plumbing is server.sendTestPushNotification: it fires a push with the
saved settings, bypasses presence suppression by design (the user is at
the machine clicking the button), and returns delivery problems as
{sent, detail} values rather than errors so the UI shows them inline.
Because splitPatch is shallow, the UI always writes the full
pushNotifications object.
Verification caveat, recorded in FORK_ROADMAP.md: in an isolated
browser-paired dev environment, server-settings writes from the web UI
silently never reach the server, for this section and for long-shipped
settings alike (optimistic update, no RPC, no error). Pre-existing
behavior, not a regression from this change; the Electron daily driver
is the real test. The send-test function is covered by focused tests
(delivery, click-header omission, rejection, timeout-free unreachable
host, empty topic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 21, 2026
…ttings writes
Two live bugs in the roadmap pingdotgg#4 ship:
- server.sendTestPushNotification was missing from RPC_REQUIRED_SCOPE, so
every call defected server-side ("has no declared authorization scope")
before reaching the handler. That map is a third registration point beyond
the contracts group and the ws handler; wsRpcScopes.test.ts now asserts it
covers every rpc in WsRpcGroup.
- Settings toggles persisted but never reflected in the UI when the config
projection was stalled (durable subscriptions resume late/never after a
reconnect — diagnosis written up under FORK_ROADMAP pingdotgg#5). useSettings.ts now
holds the settings object the server returns from updateSettings as an
acked overlay merged over the atom value, cleared when the projection
delivers a fresh value. This also stops stale full-object spreads from
clobbering earlier writes.
Verified in an isolated env: healthy toggle round-trip, toggle during the
post-reconnect stall window (reflects immediately, server state tracks UI),
send test returns "Delivered (HTTP 200)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
…tgg#4)
* feat(web): proxy the dev server at a Moatless backend
The fork's client talks to Moatless rather than a local t3 server, so the
dev server has to stand in as the API origin.
- T3CODE_PROXY_TARGET (alias MOATLESS_BASE_URL) selects the proxy target,
falling back to the VITE_WS_URL-derived one so stock `pnpm dev` is
unchanged. /api, /ws, /attachments and /.well-known all follow it.
- VITE_WS_URL / VITE_HTTP_URL are blanked when the override is active so
the client resolves to window.location.origin instead of reaching past
the proxy, which is what holds the credential.
- Authorization: Bearer is injected and cookie stripped only when the
target is not loopback, so a Moatless key can never reach a local t3
server.
- Credential-shaped VITE_* values are deleted from process.env before
Vite reads them, and a build-time assertion rejects any `define` value
containing an API-key prefix. Vite inlines the whole import.meta.env
object and Moatless sandboxes inject VITE_API_KEY=mvk_…, so without
this the key ships in the bundle.
- allowedHosts: true — a hosted sandbox's preview hostname is not
knowable ahead of time.
Two additions are specific to running inside a hosted sandbox:
T3CODE_PROXY_TARGET_OVERRIDE is read from repo env files only. loadRepoEnv
lets the ambient environment win over .env.local, which is right for
everything else it resolves, but a sandbox injects T3CODE_PROXY_TARGET
pointing at its own bundled server and a checkout needs to be able to say
"no, use mine" without editing the deployment.
Fast Refresh is turned off when NODE_ENV=production. @vitejs/plugin-react
enables the oxc Fast Refresh transform for every `serve` run but injects
its runtime preamble only when the config resolves as development, so a
sandbox that exports NODE_ENV=production process-wide gets modules full
of $RefreshReg$ calls with nothing to answer them and a blank page with
no server-side error. Forcing NODE_ENV=development instead trades that
for react/compiler-runtime's `c` export vanishing behind a lexer-opaque
conditional, so applying the plugin's own skipFastRefresh rule to the
transform is the smaller fix.
* docs: full spec for the t3code -> Moatless adapter
Records the architecture the fork settled on (a server-side adapter
rather than swapping client-side seams), the verified domain and message
mappings, the contract surface the adapter has to serve, the milestone
state including what M2 still owes, and the eleven measured Moatless API
gaps each workaround points back to.
* docs: record the M3 browser verification and the merged-repository case
The unit tests prove the projections decode; only a browser proves an
untouched t3 client accepts them. Records what the Playwright run against
the live adapter actually asserted, including the one 404 it tolerates
and why that is correct.
Also documents a case the run surfaced: two Moatless repositories can
point at the same git remote, and t3 groups the sidebar by repository
identity, so they render under one header. Left alone deliberately —
synthesizing a distinct canonicalKey would misreport identity that t3
also uses for worktree and VCS association.
* fix(web): don't pin the HMR socket to a wildcard bind address
Upstream pins hmr.protocol/host/clientPort so the socket connects reliably
inside Electron's BrowserWindow, whose window loads http://localhost:<port>
directly. A hosted sandbox sets HOST=0.0.0.0, which turned that into a literal
ws://0.0.0.0:5733/ in the served /@vite/client — an address no browser can dial,
and mixed-content-blocked once the page is served over an HTTPS preview
hostname. Unset, Vite's client derives the socket URL from the page's own
origin, which is right behind a proxy and for a plain local run alike, so pin
the endpoint only when HOST names a dialable address.
Also records in the plan doc why the preview URL 504s: the gateway's packets
never reach the pod (no socket on 5733 for the full 30s of an external request,
while the untouched t3 sidecar on 13773 fails the same way), so that one is
infrastructure, not this checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the root cause of the preview-URL 504
Traefik IngressRoutes and the sandbox Service are built per task from the
repository's declared server ports, but the NetworkPolicy that says who may
dial a sandbox takes its Traefik ingress ports from a fixed list in values.yaml
that stops at 8888. t3code uses 5733 and 13773, so the CNI drops the SYN and
Traefik times out into a 504.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: preview URL fixed — record which policy actually gated it
The chart's Traefik ingress rule names a namespace this cluster does not have,
so it could never match. The live gate was moatless-sandbox-preview-ingress, an
ArgoCD-managed policy carrying a copy of the same static port list. With its
ports block removed, the preview host serves / in 42ms and the shell endpoint in
54ms with 7 projects and 98 open threads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: spec the client/server contract surfaces
The contract lives in packages/contracts as 31 TypeScript modules with no
generated artifact, so the only way to see the whole surface was to read
rpc.ts and environmentHttp.ts and follow imports. Index it instead: all 70
WsRpcGroup methods with payload/success/scope, all 20 dispatchable commands,
all 23 EnvironmentHttpApi endpoints, the ServerConfig handshake and its
version-negotiation rules, and the subscription semantics a schema-correct
server can still break.
Also records what is deliberately outside the typed surfaces (raw HTTP
routes, the IPC/relay/MCP boundaries, the Schema.Unknown envelopes) so
"fully typed" is not overclaimed, and notes that WS_METHODS.projectsList /
projectsAdd / projectsRemove are dead constants with no Rpc.make.
Tables were generated by parsing the source and verified back against it:
method names, line numbers, stream flags, and required scopes all match
rpc.ts and ws.ts RPC_REQUIRED_SCOPE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* wip
* docs: map the client/server contract onto Moatless
Feature-by-feature walk of all three contract surfaces against the Moatless
backend: 23 HTTP endpoints, 70 RPC methods, and the 20-member command union.
Records which concepts are isomorphic, which only look alike, and what each
side has that the other cannot express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the preview mapping — tab vs. server process
T3's preview.* is the in-app browser tab (PreviewSessionSnapshot is
navStatus/canGoBack/viewport); Moatless's preview server is the process
serving the app. T3's process side is a ProjectScript in a terminal, found
by PortScanner. The declared-vs-discovered difference lands on
subscribeDiscoveredLocalServers, not preview.open.
Retallies the WS surface: 4 equivalent, 26 approximate, 40 absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the message-event claim — two events, not one
agent.message is the slim persisted log row (ADR-0003 / migration 0089);
message.upsert is a separate derived event carrying an assembled UiMessage,
relayed live and never persisted. Streaming assembled content is possible;
full tool detail is not, and the content-carrying event is the one that
cannot be replayed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: design the T3/Moatless convergence
Four moves, deliberately not all in the same direction: T3 adopts Moatless's
task+sandbox model, preview servers, and per-sandbox diagnostics scoping;
Moatless adopts T3's sequenced resumable streaming contract.
Records the governing decision (the deployment is the environment, the
sandbox is thread-scoped state), the two hard parts (unpersisted
message.upsert, bigserial as a cursor), and what each move costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: add local Moatless adapter workflow
* chore: preserve repository links
* feat(web): use Moatless auth for primary UI
* fix(web): allow Moatless cookie login after 401 session
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

light mode - #4

Merged
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components
Feb 9, 2026
Merged

light mode#4
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 9, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-02-08 at 22 05 13@2xCleanShot 2026-02-08 at 22 05 17@2x

Summary by CodeRabbit

  • New Features

    • Large library of reusable UI components added (accordions, dialogs, menus, inputs, selects, command palette, toasts, sheets, tables, etc.) for richer, consistent interfaces.
  • Theme

    • Dark theme support with system detection, cross-tab sync, and a theme toggle in the sidebar.
  • Style

    • Replaced hard-coded colors with semantic design tokens and global CSS variables for consistent light/dark theming and improved visuals.

@coderabbitai

coderabbitaiBot commented Feb 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds a large set of new UI components and utilities to the renderer, introduces a CSS-variable based theme (light/dark/system) and a useTheme hook, replaces hard-coded color tokens with semantic tokens across core renderer components, adds a cn utility and components.json, adjusts deps/config and a small smoke-test script tweak.

Changes

Cohort / File(s)Summary
UI component library
apps/renderer/src/components/ui/*
Adds 30+ new UI modules (accordion, alert, autocomplete, avatar, badge, button, card, checkbox, collapsible, combobox, command, dialog, empty, field, fieldset, form, input-group, input, kbd, label, menu, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, spinner, switch, table, tabs, textarea, toast). Each wraps @base-ui/react primitives, applies data-slot attributes, CVA variants and composed Tailwind classes. Review API shapes, typings, and consistent class/slot usage.
Core renderer components
apps/renderer/src/App.tsx, apps/renderer/src/components/ChatView.tsx, apps/renderer/src/components/ChatMarkdown.tsx, apps/renderer/src/components/DiffPanel.tsx, apps/renderer/src/components/Sidebar.tsx
Replaces hex color classes with semantic tokens; ChatView integrates new InputGroup, Select, Button components and removes previous textarea auto-resize logic; ChatMarkdown and DiffPanel styling tokens updated; Sidebar adds theme toggle and theme-aware styling. Validate behavioral changes where logic was removed (e.g., textarea resizing, model menu handling).
Theming & CSS
apps/renderer/src/index.css, apps/renderer/src/hooks/useTheme.ts
Introduces CSS variable theme system with light/dark variants and a .no-transitions helper; adds many CSS token definitions and dark overrides. Adds useTheme hook (light
Utilities & small libs
apps/renderer/src/lib/utils.ts, apps/renderer/components.json
Adds cn(...inputs) util combining clsx + twMerge. Adds components.json with UI framework config, aliases, Tailwind/tokens and registries. Confirm imports use the new alias and utility.
Config & build
apps/renderer/tsconfig.json, apps/renderer/vite.config.ts, biome.json
Adds #/* path alias and resolves # in Vite; sets exactOptionalPropertyTypes: false in tsconfig; relaxes biome a11y rule noSvgWithoutTitle.
Dependencies
apps/renderer/package.json
Adds runtime deps including @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge and reorders react-markdown. Verify lockfile and install.
Markup & scripts
apps/renderer/index.html, apps/desktop/scripts/smoke-test.mjs
Removes body Tailwind class in index.html. Small smoke-test script tweak: reorder imports from node:child_process and use template literal for error output. Minor script change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check❓ InconclusiveThe PR title 'light mode' is vague and does not clearly describe the actual scope of changes, which include comprehensive UI component library implementation, theming system, styling updates, and design system token migration across the renderer application.Consider a more descriptive title that captures the main work, such as 'Add design system UI components and light/dark theme support' or 'Implement base UI component library with theming.'
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch juliusmarminge/use-base-components

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/renderer/src/components/ChatView.tsx (1)

312-344: Consider using cn utility for conditional class handling.

The deeply nested ternaries for status indicator styling reduce readability. Using the cn utility with object syntax would make the phase-based styling clearer and more maintainable.

♻️ Suggested refactor using cn utility
+const statusContainerClasses: Record<string, string> = {+ running: "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100",+ connecting: "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100",+ ready: "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100",+ disconnected: "border-border bg-secondary text-muted-foreground",+};++const statusDotClasses: Record<string, string> = {+ running: "bg-sky-200",+ connecting: "bg-amber-200",+ ready: "bg-emerald-200",+ disconnected: "bg-muted-foreground/40",+};

Then use:

<divclassName={cn("inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px]",statusContainerClasses[phase]??statusContainerClasses.disconnected)}>

Based on learnings: "Use Tailwind CSS for styling - no inline styles, use cn for conditional classes".

apps/renderer/src/components/Sidebar.tsx (1)

138-152: Prefer Coss UI for the new icon-only theme toggle.

To keep styling and interaction patterns consistent with the rest of the app, consider swapping this raw <button> for the equivalent Coss UI icon/button primitive.

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeappBot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Add light/dark/system theme cycling and apply token-based theming across Sidebar, ChatView, and global styles to introduce light mode

Introduce a useTheme hook with localStorage persistence and a Sidebar header button to cycle system/light/dark; switch global and app styling to CSS variables and semantic Tailwind tokens; refactor ChatView and Sidebar UI to use shared UI primitives; remove thread status badges, editor/approval flows, and keyboard shortcuts; update model selection in new project flow.

📍Where to Start

Start with the useTheme hook in useTheme.ts and its integration in the Sidebar in Sidebar.tsx, then review theme token application in index.css.


Macroscope summarized 0a535ae.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/Sidebar.tsx`:
- Around line 225-230: The button currently uses the disabled prop which
conflicts with accessibility guidance and tooltips; instead remove the disabled
attribute on the button element, add aria-disabled={isPickingFolder} to reflect
state, keep/adjust the visual "disabled" classes conditionally when
isPickingFolder, and add a click guard at the top of handlePickFolder (or inline
in the onClick wrapper) that returns early if isPickingFolder so clicks are
ignored; ensure tooltip behavior is preserved by using aria-disabled rather than
disabled.
In `@apps/renderer/src/components/ui/combobox.tsx`:
- Around line 196-214: The SVG in the Combobox component uses an incorrect xmlns
value; update the xmlns on the SVG inside ComboboxPrimitive.ItemIndicator to the
correct "http://www.w3.org/2000/svg" string so the icon renders properly (look
for the SVG element in the ComboboxPrimitive.ItemIndicator within the
combobox.tsx component and change the xmlns attribute value).
In `@apps/renderer/src/components/ui/empty.tsx`:
- Around line 94-105: The component EmptyDescription is typed as
React.ComponentProps<"p"> but renders a <div>, causing a props/semantic
mismatch; fix it by making the types and markup consistent—either change the
rendered element to <p> (keep the same props type) or change the prop type to
React.ComponentProps<"div"> (keep the <div>); update the function signature
(EmptyDescription) accordingly and ensure spreading {...props}, className and
data-slot="empty-description" remain correct for the chosen element.
In `@apps/renderer/src/components/ui/scroll-area.tsx`:
- Around line 5-35: Replace the custom use of ScrollAreaPrimitive
(ScrollAreaPrimitive.Root, ScrollAreaPrimitive.Viewport,
ScrollAreaPrimitive.Corner) and the local ScrollBar components with the Coss UI
ScrollArea wrapper: install the package via the shadcn CLI (`pnpm dlx
shadcn@latest add `@coss/scroll-area``), import Coss's ScrollArea,
ScrollAreaViewport and Corner (or the exported equivalents) and re-create this
component as a thin wrapper named ScrollArea that forwards props and children
while preserving the scrollFade and scrollbarGutter booleans by mapping them to
equivalent className masks and gutter classes on the Coss component (apply the
same mask expressions and `data-has-overflow-*` gutter classes), and ensure
data-slot attributes (scroll-area-viewport, scroll-area-corner) remain on the
mapped children so existing consumers continue to work.
In `@apps/renderer/src/components/ui/select.tsx`:
- Around line 128-142: The SVG used inside SelectPrimitive.ItemIndicator has a
typo in its xmlns attribute; update the xmlns on the SVG element within
SelectPrimitive.ItemIndicator to the correct namespace
"http://www.w3.org/2000/svg" so the checkmark renders correctly across browsers
(locate the SVG in the SelectPrimitive.ItemIndicator block and replace the
incorrect "http://www.w3.org/1500/svg" value).
In `@apps/renderer/src/components/ui/skeleton.tsx`:
- Around line 1-3: The file uses React.ComponentProps<"div"> in the Skeleton
component but doesn't import the React namespace; add an import for React (e.g.,
add "import React from 'react'" or "import type React from 'react'") at the top
of apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.
🧹 Nitpick comments (6)
apps/renderer/src/components/ui/table.tsx (1)

36-47: Consider extracting long class strings for maintainability.

The class string on line 40 is quite long with many frame-conditional styles. While this is a common pattern in shadcn-style UI libraries, consider extracting the frame-specific styles into a separate variable or using Tailwind's @apply directive in a CSS file for better readability.

That said, this is a stylistic preference and the current implementation is functionally correct.

apps/renderer/src/components/ui/separator.tsx (1)

13-13: The CSS selector for vertical self-stretch is fragile and likely won't work as intended.

The selector not-[[class^='h-']]:not-[[class*='_h-']] checks whether the class attribute string starts with 'h-' or contains '_h-'. Since other classes (e.g., shrink-0) will appear first in the composed className, the [class^='h-'] condition will almost never match—even when a height utility like h-10 is present.

Consider simplifying by always applying self-stretch for vertical orientation and letting explicit height classes override it naturally, or document that consumers should use inline styles / CSS variables for custom heights:

♻️ Suggested simplification
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
apps/renderer/src/components/ui/toast.tsx (1)

220-250: Consider extracting shared toast content into a reusable component.

The toast content rendering here (icon, title, description, action button) is nearly identical to lines 128-158 in the Toasts component. Extracting this into a shared ToastContent component would reduce duplication and ensure consistency when making future changes.

♻️ Suggested extraction
// Shared component to use in both Toasts and AnchoredToastsfunctionToastContentBody({ toast }: {toast: Toast.ToastObject}){constIcon=toast.type
? TOAST_ICONS[toast.typeaskeyoftypeofTOAST_ICONS]
: null;return(<Toast.ContentclassName="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm ..."><divclassName="flex gap-2">{Icon&&(<divclassName="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"data-slot="toast-icon"><IconclassName="in-data-[type=loading]:animate-spin ..."/></div>)}<divclassName="flex flex-col gap-0.5"><Toast.TitleclassName="font-medium"data-slot="toast-title"/><Toast.DescriptionclassName="text-muted-foreground"data-slot="toast-description"/></div></div>{toast.actionProps&&(<Toast.ActionclassName={buttonVariants({size: "xs"})}data-slot="toast-action">{toast.actionProps.children}</Toast.Action>)}</Toast.Content>);}
apps/renderer/src/components/ui/kbd.tsx (1)

5-26: Prefer Coss UI primitives for consistency.

These custom Kbd/KbdGroup wrappers diverge from the guideline to use Coss UI components (installed via the ShadCN CLI) for consistent styling and Base UI accessibility alignment. If a Coss kbd component exists, consider swapping to it (or document why bespoke wrappers are required).

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling. Install new components using the ShadCN CLI (e.g., bunx --bun shadcnlatest add coss/button). Refer to Base UI docs for accessibility guidelines on primitives.

apps/renderer/src/components/ui/textarea.tsx (1)

7-10: Consider documenting or removing the number type for size.

The size prop accepts "sm" | "default" | "lg" | number, but the internal styling logic (lines 35-38) only handles the string literals. When a number is passed, it's set on data-size but no corresponding styles are applied.

If numeric sizes are intended for custom CSS handling via data-size, consider adding a brief comment documenting this. Otherwise, consider restricting the type to the supported string literals only.

apps/renderer/src/index.css (1)

227-242: Consider using CSS variables for scrollbar colors for consistency.

The scrollbar styling uses hardcoded rgba() values while the rest of the file uses CSS variables. For better theme consistency, consider using the defined tokens:

♻️ Optional: Use CSS variables for scrollbar colors
 ::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);+ background: var(--muted-foreground);+ opacity: 0.15;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 0, 0, 0.25);+ background: var(--muted-foreground);+ opacity: 0.25;
}
.dark ::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);+ background: var(--muted-foreground);+ opacity: 0.1;
}
.dark ::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.18);+ background: var(--muted-foreground);+ opacity: 0.18;
}

Note: Opacity on scrollbar thumbs may have limited browser support; the current approach works reliably.

Comment on lines 225 to 230
<button
type="button"
className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => void handlePickFolder()}
disabled={isPickingFolder}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid the disabled prop; use aria-disabled and a click guard instead.

The current disabled usage conflicts with the repo’s accessibility guidance and can break tooltips. Use styling plus a guard in the handler.

🛠️ Suggested fix
- className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"- onClick={() => void handlePickFolder()}- disabled={isPickingFolder}+ className={`mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary ${+ isPickingFolder ? "cursor-not-allowed opacity-60" : ""+ }`}+ aria-disabled={isPickingFolder}+ onClick={() => {+ if (isPickingFolder) return;+ void handlePickFolder();+ }}
Based on learnings: Avoid using disabled props on buttons as they harm accessibility and break tooltips.
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/Sidebar.tsx` around lines 225 - 230, The button
currently uses the disabled prop which conflicts with accessibility guidance and
tooltips; instead remove the disabled attribute on the button element, add
aria-disabled={isPickingFolder} to reflect state, keep/adjust the visual
"disabled" classes conditionally when isPickingFolder, and add a click guard at
the top of handlePickFolder (or inline in the onClick wrapper) that returns
early if isPickingFolder so clicks are ignored; ensure tooltip behavior is
preserved by using aria-disabled rather than disabled.

Comment on lines +196 to +214
<ComboboxPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</ComboboxPrimitive.ItemIndicator>
<div className="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

Same issue as in select.tsx: the xmlns attribute has http://www.w3.org/1500/svg instead of http://www.w3.org/2000/svg.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/combobox.tsx` around lines 196 - 214, The SVG
in the Combobox component uses an incorrect xmlns value; update the xmlns on the
SVG inside ComboboxPrimitive.ItemIndicator to the correct
"http://www.w3.org/2000/svg" string so the icon renders properly (look for the
SVG element in the ComboboxPrimitive.ItemIndicator within the combobox.tsx
component and change the xmlns attribute value).

Comment on lines +94 to +105
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: Component typed as <p> but renders <div>.

EmptyDescription accepts React.ComponentProps<"p"> but renders a <div>. This inconsistency could confuse consumers and allows <p>-specific props that won't apply to the rendered <div>.

💡 Proposed fix

Either change the rendered element to match the type:

-function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {+function EmptyDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div

Or render a <p> to match the type (if semantically appropriate):

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
- <div+ <p
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
- />+ />
);
}
📝 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.

Suggested change
functionEmptyDescription({ className, ...props}: React.ComponentProps<"p">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
functionEmptyDescription({ className, ...props}: React.ComponentProps<"div">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/empty.tsx` around lines 94 - 105, The
component EmptyDescription is typed as React.ComponentProps<"p"> but renders a
<div>, causing a props/semantic mismatch; fix it by making the types and markup
consistent—either change the rendered element to <p> (keep the same props type)
or change the prop type to React.ComponentProps<"div"> (keep the <div>); update
the function signature (EmptyDescription) accordingly and ensure spreading
{...props}, className and data-slot="empty-description" remain correct for the
chosen element.

Comment on lines +5 to +35
function ScrollArea({
className,
children,
scrollFade = false,
scrollbarGutter = false,
...props
}: ScrollAreaPrimitive.Root.Props & {
scrollFade?: boolean;
scrollbarGutter?: boolean;
}) {
return (
<ScrollAreaPrimitive.Root
className={cn("size-full min-h-0", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
scrollFade &&
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
scrollbarGutter &&
"data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
)}
data-slot="scroll-area-viewport"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation="vertical" />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Coss UI ScrollArea component documentation installation shadcn

💡 Result:

Coss UI – ScrollArea (shadcn CLI install + docs)

  • Docs:coss.com/ui/docs/components/scroll-area (coss.com)
  • Install (shadcn CLI):
    pnpm dlx shadcn@latest add `@coss/scroll-area`
    (coss.com)
  • Use:
    import{ScrollArea}from"@/components/ui/scroll-area"
    (coss.com)

If you meant the core shadcn/ui ScrollArea (Radix/Base UI), the install command is:

pnpm dlx shadcn@latest add scroll-area

(ui.shadcn.com)

Citations:


🏁 Script executed:

# Check for existing UI component usage patterns in the repository
find apps/renderer/src/components/ui -type f -name "*.tsx"| head -20

Repository: pingdotgg/codething-mvp

Length of output: 957


🏁 Script executed:

# Check package.json to see what UI libraries are currently in use
cat package.json | grep -A 20 '"dependencies"'| head -40

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Search for existing imports of UI components to understand patterns
rg "from.*coss|from.*shadcn" apps/renderer/src/components/ui --no-heading -A 1 | head -30

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Check if ScrollAreaPrimitive is imported from `@radix-ui` or another source
head -50 apps/renderer/src/components/ui/scroll-area.tsx

Repository: pingdotgg/codething-mvp

Length of output: 2171


Replace with Coss UI ScrollArea component for consistency with codebase guidelines.

Coss UI provides a ScrollArea component that should be installed via ShadCN CLI: pnpm dlx shadcn@latest add @coss/scroll-area``. The custom extensions (scrollFade, `scrollbarGutter`) can be preserved by wrapping the Coss UI component instead of using Base UI directly. This ensures alignment with the team's component catalog and styling standards.

🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/scroll-area.tsx` around lines 5 - 35, Replace
the custom use of ScrollAreaPrimitive (ScrollAreaPrimitive.Root,
ScrollAreaPrimitive.Viewport, ScrollAreaPrimitive.Corner) and the local
ScrollBar components with the Coss UI ScrollArea wrapper: install the package
via the shadcn CLI (`pnpm dlx shadcn@latest add `@coss/scroll-area``), import
Coss's ScrollArea, ScrollAreaViewport and Corner (or the exported equivalents)
and re-create this component as a thin wrapper named ScrollArea that forwards
props and children while preserving the scrollFade and scrollbarGutter booleans
by mapping them to equivalent className masks and gutter classes on the Coss
component (apply the same mask expressions and `data-has-overflow-*` gutter
classes), and ensure data-slot attributes (scroll-area-viewport,
scroll-area-corner) remain on the mapped children so existing consumers continue
to work.

Comment on lines +128 to +142
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

The xmlns attribute has a typo: http://www.w3.org/1500/svg should be http://www.w3.org/2000/svg. This invalid namespace may cause the checkmark SVG to fail to render in some browsers.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/select.tsx` around lines 128 - 142, The SVG
used inside SelectPrimitive.ItemIndicator has a typo in its xmlns attribute;
update the xmlns on the SVG element within SelectPrimitive.ItemIndicator to the
correct namespace "http://www.w3.org/2000/svg" so the checkmark renders
correctly across browsers (locate the SVG in the SelectPrimitive.ItemIndicator
block and replace the incorrect "http://www.w3.org/1500/svg" value).

Comment on lines +1 to +3
import { cn } from "#/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/renderer/src/components/ui/skeleton.tsx | head -20

Repository: pingdotgg/codething-mvp

Length of output: 723


🏁 Script executed:

find . -name "tsconfig.json" -type f | head -5

Repository: pingdotgg/codething-mvp

Length of output: 160


🏁 Script executed:

cat tsconfig.json 2>/dev/null || cat ./apps/renderer/tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -exec cat {} \;

Repository: pingdotgg/codething-mvp

Length of output: 388


Add React type import for ComponentProps reference.

Line 3 references React.ComponentProps<"div"> without importing the React namespace. While the modern JSX transform (jsx: "react-jsx") eliminates the need for React in JSX elements, explicit namespace references like React.ComponentProps still require the import and will cause a type-checking error.

+import type * as React from "react";
import { cn } from "#/lib/utils";
📝 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.

Suggested change
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
importtype*asReactfrom"react";
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/skeleton.tsx` around lines 1 - 3, The file
uses React.ComponentProps<"div"> in the Skeleton component but doesn't import
the React namespace; add an import for React (e.g., add "import React from
'react'" or "import type React from 'react'") at the top of
apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/ChatView.tsx`:
- Around line 478-482: The UI currently renders effort labels by capitalizing
the first character in the map over REASONING_OPTIONS (in the SelectItem
rendering), which turns values like "xhigh" into "Xhigh"; replace this logic by
defining a display-name mapping (e.g., a constant object like
REASONING_DISPLAY_NAMES keyed by the same values in REASONING_OPTIONS) and use
that map when rendering the SelectItem label and when comparing to
DEFAULT_REASONING, falling back to a nicer formatter (e.g., full word
replacements or Title Case) only if a key is missing; update the SelectItem
render to use REASONING_DISPLAY_NAMES[effort] || fallbackFormatter(effort).
🧹 Nitpick comments (1)
apps/renderer/src/components/ChatView.tsx (1)

451-456: Minor: Redundant dark: prefixes for transparent and semantic tokens.

bg-transparent is identical in both modes, and bg-accent is a semantic token that already adapts to the theme. The dark: prefixes are unnecessary here.

♻️ Simplified styling
 <SelectTrigger
size="sm"
- className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"+ className="w-auto min-w-0 border-0 shadow-none bg-transparent before:hidden data-[popup-open]:bg-accent"
>

Apply to both SelectTrigger instances (lines 451-456 and 471-476).

Also applies to: 471-476

Comment on lines +478 to +482
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
{effort.charAt(0).toUpperCase() + effort.slice(1)}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalization of "xhigh" produces awkward "Xhigh" in the UI.

The inline capitalization effort.charAt(0).toUpperCase() + effort.slice(1) transforms "xhigh" to "Xhigh", which isn't user-friendly. Consider a display name mapping for better readability.

💡 Suggested approach
+const EFFORT_LABELS: Record<string, string> = {+ xhigh: "Extra High",+ high: "High",+ medium: "Medium",+ low: "Low",+};+
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
- {effort.charAt(0).toUpperCase() + effort.slice(1)}+ {EFFORT_LABELS[effort] ?? effort}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
))}
📝 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.

Suggested change
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{effort.charAt(0).toUpperCase()+effort.slice(1)}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
constEFFORT_LABELS: Record<string,string>={
xhigh: "Extra High",
high: "High",
medium: "Medium",
low: "Low",
};
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{EFFORT_LABELS[effort]??effort}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ChatView.tsx` around lines 478 - 482, The UI
currently renders effort labels by capitalizing the first character in the map
over REASONING_OPTIONS (in the SelectItem rendering), which turns values like
"xhigh" into "Xhigh"; replace this logic by defining a display-name mapping
(e.g., a constant object like REASONING_DISPLAY_NAMES keyed by the same values
in REASONING_OPTIONS) and use that map when rendering the SelectItem label and
when comparing to DEFAULT_REASONING, falling back to a nicer formatter (e.g.,
full word replacements or Title Case) only if a key is missing; update the
SelectItem render to use REASONING_DISPLAY_NAMES[effort] ||
fallbackFormatter(effort).

juliusmarmingeand others added 3 commits February 9, 2026 01:12
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the juliusmarminge/use-base-components branch from 35deea6 to 0a535aeCompareFebruary 9, 2026 09:13
@t3dotgg
t3dotgg merged commit c51f252 into mainFeb 9, 2026
1 check passed
t3dotgg added a commit that referenced this pull request Feb 9, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
praveenperera pushed a commit to praveenperera/t3code that referenced this pull request Mar 23, 2026
nikrabaev added a commit to nikrabaev/t3code that referenced this pull request Apr 25, 2026
The original route URL /$environmentId/$weaveRunId collided with the
existing chat thread route /$environmentId/$threadId — TanStack Router
matches by URL pattern, not by route ID, so the chat route always won.
Opening any weave run dropped onto the chat route, which then ran its
'thread doesn't exist → redirect' effect in a tight loop and exhausted
React's update depth.
Add a literal 'weave' segment to the weave URL so the patterns are
distinct:
before: /$envId/$weaveRunId vs /$envId/$threadId
after : /$envId/weave/$weaveRunId vs /$envId/$threadId
The plan flagged this exact risk in §'Open design questions' pingdotgg#4 and
prescribed this fallback. Files renamed; consumers (ChatView dispatch,
WeaveBlueprintList node nav, WeaveRunSidebarItem Link, WeaveCreatedMarker
Link) updated. Route tree regenerated by Vite plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 14, 2026
Two cleanups flagged by thermo-review:
- Phase partition (Standards #1 + Quality #3b): the active/recent split
hardcoded the terminal-phase list twice as complementary filters. Extract
TERMINAL_PHASES + partitionIssuesByPhase into sandcastleView.ts so the two
buckets can't drift, with unit tests.
- Issue rows (Quality #3c): the Active and Recent sections hand-rolled the
same #number link-or-span + phaseLabel badge. Extract one SandcastleIssueRow
component with a variant prop; classNames preserved verbatim (no visual
change). Collapses ~80 lines to ~40.
Also replace as-never test casts (Quality pingdotgg#4) with a real RepositoryIdentity
fixture mirroring environmentGrouping.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 17, 2026
Gate computeRevertTurnCountByUserMessageId on the union of the span's
agent edit sets (ADR-0004 decision 4), not a single turn; normalize web
agent paths to repo-relative and match the checkpoint diff by equality
instead of a suffix heuristic. Review findings: spec gating, quality pingdotgg#4/pingdotgg#5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 3, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: contracts for blob store, mirror RPCs, and registry
M1 analysis pass recorded in the plan doc (deviations: settings-flag home,
reactor gating pattern, no server-side peer endpoint discovery, mirror RPC
transport, D4 credential handshake).
Contracts:
- WorkspaceProjectId branded id (D1)
- packages/contracts/src/roaming.ts: RoamingBlobRecord/manifest/conflict (D3),
registry payload, RoamingProjectShell, peer + mirror + enrollment RPC
schemas, HTTP path constants
- roaming:mirror auth scope; `roaming` server setting (default off)
- OrchestrationShellSnapshot.roamingProjects (decode-default []) + shell
stream variants; optional workspaceProjectId on project shapes and
project.meta-updated
- internal command project.roaming.enroll with decider/projector/engine
wiring (emits project.meta-updated; double-enroll rejected)
Test literals updated for the new snapshot field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Roaming M1 contracts: apply review findings
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: roaming_blobs migration + RoamingBlobStore
Migration 033 (roaming_blobs + roaming_blob_conflicts, PK (kind,key)) and a
SqlClient-backed RoamingBlobStore: writeLocal (version bump, sha256 of the
verbatim payload string, author environment id), applyRemote implementing the
D3 reconciliation rule (higher version wins; equal version + different hash
records a conflict carrying the full remote record and keeps local),
manifest/get/getMany/listConflicts, and a changes stream that emits only on
accepted writes. Reuses persistence error types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: accepted writes clear superseded conflict rows
codex review P2: a recorded equal-version conflict stayed in
roaming_blob_conflicts after a later accepted write moved the key past the
contested version, so listConflicts() kept surfacing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: apply independent review findings
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 20, 2026
ServerSettings.pushNotifications was hand-edited in settings.json with
the app stopped. It now has a "Phone push" section in Settings, General:
topic URL, click-through base URL, per-phase toggles, presence
suppression window, and a Send test button.
The write path already existed end to end (ServerSettingsPatch exposed
pushNotifications; server.updateSettings deep-merges), so the only new
plumbing is server.sendTestPushNotification: it fires a push with the
saved settings, bypasses presence suppression by design (the user is at
the machine clicking the button), and returns delivery problems as
{sent, detail} values rather than errors so the UI shows them inline.
Because splitPatch is shallow, the UI always writes the full
pushNotifications object.
Verification caveat, recorded in FORK_ROADMAP.md: in an isolated
browser-paired dev environment, server-settings writes from the web UI
silently never reach the server, for this section and for long-shipped
settings alike (optimistic update, no RPC, no error). Pre-existing
behavior, not a regression from this change; the Electron daily driver
is the real test. The send-test function is covered by focused tests
(delivery, click-header omission, rejection, timeout-free unreachable
host, empty topic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 21, 2026
…ttings writes
Two live bugs in the roadmap pingdotgg#4 ship:
- server.sendTestPushNotification was missing from RPC_REQUIRED_SCOPE, so
every call defected server-side ("has no declared authorization scope")
before reaching the handler. That map is a third registration point beyond
the contracts group and the ws handler; wsRpcScopes.test.ts now asserts it
covers every rpc in WsRpcGroup.
- Settings toggles persisted but never reflected in the UI when the config
projection was stalled (durable subscriptions resume late/never after a
reconnect — diagnosis written up under FORK_ROADMAP pingdotgg#5). useSettings.ts now
holds the settings object the server returns from updateSettings as an
acked overlay merged over the atom value, cleared when the projection
delivers a fresh value. This also stops stale full-object spreads from
clobbering earlier writes.
Verified in an isolated env: healthy toggle round-trip, toggle during the
post-reconnect stall window (reflects immediately, server state tracks UI),
send test returns "Delivered (HTTP 200)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
…tgg#4)
* feat(web): proxy the dev server at a Moatless backend
The fork's client talks to Moatless rather than a local t3 server, so the
dev server has to stand in as the API origin.
- T3CODE_PROXY_TARGET (alias MOATLESS_BASE_URL) selects the proxy target,
falling back to the VITE_WS_URL-derived one so stock `pnpm dev` is
unchanged. /api, /ws, /attachments and /.well-known all follow it.
- VITE_WS_URL / VITE_HTTP_URL are blanked when the override is active so
the client resolves to window.location.origin instead of reaching past
the proxy, which is what holds the credential.
- Authorization: Bearer is injected and cookie stripped only when the
target is not loopback, so a Moatless key can never reach a local t3
server.
- Credential-shaped VITE_* values are deleted from process.env before
Vite reads them, and a build-time assertion rejects any `define` value
containing an API-key prefix. Vite inlines the whole import.meta.env
object and Moatless sandboxes inject VITE_API_KEY=mvk_…, so without
this the key ships in the bundle.
- allowedHosts: true — a hosted sandbox's preview hostname is not
knowable ahead of time.
Two additions are specific to running inside a hosted sandbox:
T3CODE_PROXY_TARGET_OVERRIDE is read from repo env files only. loadRepoEnv
lets the ambient environment win over .env.local, which is right for
everything else it resolves, but a sandbox injects T3CODE_PROXY_TARGET
pointing at its own bundled server and a checkout needs to be able to say
"no, use mine" without editing the deployment.
Fast Refresh is turned off when NODE_ENV=production. @vitejs/plugin-react
enables the oxc Fast Refresh transform for every `serve` run but injects
its runtime preamble only when the config resolves as development, so a
sandbox that exports NODE_ENV=production process-wide gets modules full
of $RefreshReg$ calls with nothing to answer them and a blank page with
no server-side error. Forcing NODE_ENV=development instead trades that
for react/compiler-runtime's `c` export vanishing behind a lexer-opaque
conditional, so applying the plugin's own skipFastRefresh rule to the
transform is the smaller fix.
* docs: full spec for the t3code -> Moatless adapter
Records the architecture the fork settled on (a server-side adapter
rather than swapping client-side seams), the verified domain and message
mappings, the contract surface the adapter has to serve, the milestone
state including what M2 still owes, and the eleven measured Moatless API
gaps each workaround points back to.
* docs: record the M3 browser verification and the merged-repository case
The unit tests prove the projections decode; only a browser proves an
untouched t3 client accepts them. Records what the Playwright run against
the live adapter actually asserted, including the one 404 it tolerates
and why that is correct.
Also documents a case the run surfaced: two Moatless repositories can
point at the same git remote, and t3 groups the sidebar by repository
identity, so they render under one header. Left alone deliberately —
synthesizing a distinct canonicalKey would misreport identity that t3
also uses for worktree and VCS association.
* fix(web): don't pin the HMR socket to a wildcard bind address
Upstream pins hmr.protocol/host/clientPort so the socket connects reliably
inside Electron's BrowserWindow, whose window loads http://localhost:<port>
directly. A hosted sandbox sets HOST=0.0.0.0, which turned that into a literal
ws://0.0.0.0:5733/ in the served /@vite/client — an address no browser can dial,
and mixed-content-blocked once the page is served over an HTTPS preview
hostname. Unset, Vite's client derives the socket URL from the page's own
origin, which is right behind a proxy and for a plain local run alike, so pin
the endpoint only when HOST names a dialable address.
Also records in the plan doc why the preview URL 504s: the gateway's packets
never reach the pod (no socket on 5733 for the full 30s of an external request,
while the untouched t3 sidecar on 13773 fails the same way), so that one is
infrastructure, not this checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the root cause of the preview-URL 504
Traefik IngressRoutes and the sandbox Service are built per task from the
repository's declared server ports, but the NetworkPolicy that says who may
dial a sandbox takes its Traefik ingress ports from a fixed list in values.yaml
that stops at 8888. t3code uses 5733 and 13773, so the CNI drops the SYN and
Traefik times out into a 504.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: preview URL fixed — record which policy actually gated it
The chart's Traefik ingress rule names a namespace this cluster does not have,
so it could never match. The live gate was moatless-sandbox-preview-ingress, an
ArgoCD-managed policy carrying a copy of the same static port list. With its
ports block removed, the preview host serves / in 42ms and the shell endpoint in
54ms with 7 projects and 98 open threads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: spec the client/server contract surfaces
The contract lives in packages/contracts as 31 TypeScript modules with no
generated artifact, so the only way to see the whole surface was to read
rpc.ts and environmentHttp.ts and follow imports. Index it instead: all 70
WsRpcGroup methods with payload/success/scope, all 20 dispatchable commands,
all 23 EnvironmentHttpApi endpoints, the ServerConfig handshake and its
version-negotiation rules, and the subscription semantics a schema-correct
server can still break.
Also records what is deliberately outside the typed surfaces (raw HTTP
routes, the IPC/relay/MCP boundaries, the Schema.Unknown envelopes) so
"fully typed" is not overclaimed, and notes that WS_METHODS.projectsList /
projectsAdd / projectsRemove are dead constants with no Rpc.make.
Tables were generated by parsing the source and verified back against it:
method names, line numbers, stream flags, and required scopes all match
rpc.ts and ws.ts RPC_REQUIRED_SCOPE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* wip
* docs: map the client/server contract onto Moatless
Feature-by-feature walk of all three contract surfaces against the Moatless
backend: 23 HTTP endpoints, 70 RPC methods, and the 20-member command union.
Records which concepts are isomorphic, which only look alike, and what each
side has that the other cannot express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the preview mapping — tab vs. server process
T3's preview.* is the in-app browser tab (PreviewSessionSnapshot is
navStatus/canGoBack/viewport); Moatless's preview server is the process
serving the app. T3's process side is a ProjectScript in a terminal, found
by PortScanner. The declared-vs-discovered difference lands on
subscribeDiscoveredLocalServers, not preview.open.
Retallies the WS surface: 4 equivalent, 26 approximate, 40 absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the message-event claim — two events, not one
agent.message is the slim persisted log row (ADR-0003 / migration 0089);
message.upsert is a separate derived event carrying an assembled UiMessage,
relayed live and never persisted. Streaming assembled content is possible;
full tool detail is not, and the content-carrying event is the one that
cannot be replayed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: design the T3/Moatless convergence
Four moves, deliberately not all in the same direction: T3 adopts Moatless's
task+sandbox model, preview servers, and per-sandbox diagnostics scoping;
Moatless adopts T3's sequenced resumable streaming contract.
Records the governing decision (the deployment is the environment, the
sandbox is thread-scoped state), the two hard parts (unpersisted
message.upsert, bigserial as a cursor), and what each move costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: add local Moatless adapter workflow
* chore: preserve repository links
* feat(web): use Moatless auth for primary UI
* fix(web): allow Moatless cookie login after 401 session
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

light mode - #4

Merged
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components
Feb 9, 2026
Merged

light mode#4
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 9, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-02-08 at 22 05 13@2xCleanShot 2026-02-08 at 22 05 17@2x

Summary by CodeRabbit

  • New Features

    • Large library of reusable UI components added (accordions, dialogs, menus, inputs, selects, command palette, toasts, sheets, tables, etc.) for richer, consistent interfaces.
  • Theme

    • Dark theme support with system detection, cross-tab sync, and a theme toggle in the sidebar.
  • Style

    • Replaced hard-coded colors with semantic design tokens and global CSS variables for consistent light/dark theming and improved visuals.

@coderabbitai

coderabbitaiBot commented Feb 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds a large set of new UI components and utilities to the renderer, introduces a CSS-variable based theme (light/dark/system) and a useTheme hook, replaces hard-coded color tokens with semantic tokens across core renderer components, adds a cn utility and components.json, adjusts deps/config and a small smoke-test script tweak.

Changes

Cohort / File(s)Summary
UI component library
apps/renderer/src/components/ui/*
Adds 30+ new UI modules (accordion, alert, autocomplete, avatar, badge, button, card, checkbox, collapsible, combobox, command, dialog, empty, field, fieldset, form, input-group, input, kbd, label, menu, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, spinner, switch, table, tabs, textarea, toast). Each wraps @base-ui/react primitives, applies data-slot attributes, CVA variants and composed Tailwind classes. Review API shapes, typings, and consistent class/slot usage.
Core renderer components
apps/renderer/src/App.tsx, apps/renderer/src/components/ChatView.tsx, apps/renderer/src/components/ChatMarkdown.tsx, apps/renderer/src/components/DiffPanel.tsx, apps/renderer/src/components/Sidebar.tsx
Replaces hex color classes with semantic tokens; ChatView integrates new InputGroup, Select, Button components and removes previous textarea auto-resize logic; ChatMarkdown and DiffPanel styling tokens updated; Sidebar adds theme toggle and theme-aware styling. Validate behavioral changes where logic was removed (e.g., textarea resizing, model menu handling).
Theming & CSS
apps/renderer/src/index.css, apps/renderer/src/hooks/useTheme.ts
Introduces CSS variable theme system with light/dark variants and a .no-transitions helper; adds many CSS token definitions and dark overrides. Adds useTheme hook (light
Utilities & small libs
apps/renderer/src/lib/utils.ts, apps/renderer/components.json
Adds cn(...inputs) util combining clsx + twMerge. Adds components.json with UI framework config, aliases, Tailwind/tokens and registries. Confirm imports use the new alias and utility.
Config & build
apps/renderer/tsconfig.json, apps/renderer/vite.config.ts, biome.json
Adds #/* path alias and resolves # in Vite; sets exactOptionalPropertyTypes: false in tsconfig; relaxes biome a11y rule noSvgWithoutTitle.
Dependencies
apps/renderer/package.json
Adds runtime deps including @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge and reorders react-markdown. Verify lockfile and install.
Markup & scripts
apps/renderer/index.html, apps/desktop/scripts/smoke-test.mjs
Removes body Tailwind class in index.html. Small smoke-test script tweak: reorder imports from node:child_process and use template literal for error output. Minor script change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check❓ InconclusiveThe PR title 'light mode' is vague and does not clearly describe the actual scope of changes, which include comprehensive UI component library implementation, theming system, styling updates, and design system token migration across the renderer application.Consider a more descriptive title that captures the main work, such as 'Add design system UI components and light/dark theme support' or 'Implement base UI component library with theming.'
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch juliusmarminge/use-base-components

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/renderer/src/components/ChatView.tsx (1)

312-344: Consider using cn utility for conditional class handling.

The deeply nested ternaries for status indicator styling reduce readability. Using the cn utility with object syntax would make the phase-based styling clearer and more maintainable.

♻️ Suggested refactor using cn utility
+const statusContainerClasses: Record<string, string> = {+ running: "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100",+ connecting: "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100",+ ready: "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100",+ disconnected: "border-border bg-secondary text-muted-foreground",+};++const statusDotClasses: Record<string, string> = {+ running: "bg-sky-200",+ connecting: "bg-amber-200",+ ready: "bg-emerald-200",+ disconnected: "bg-muted-foreground/40",+};

Then use:

<divclassName={cn("inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px]",statusContainerClasses[phase]??statusContainerClasses.disconnected)}>

Based on learnings: "Use Tailwind CSS for styling - no inline styles, use cn for conditional classes".

apps/renderer/src/components/Sidebar.tsx (1)

138-152: Prefer Coss UI for the new icon-only theme toggle.

To keep styling and interaction patterns consistent with the rest of the app, consider swapping this raw <button> for the equivalent Coss UI icon/button primitive.

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeappBot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Add light/dark/system theme cycling and apply token-based theming across Sidebar, ChatView, and global styles to introduce light mode

Introduce a useTheme hook with localStorage persistence and a Sidebar header button to cycle system/light/dark; switch global and app styling to CSS variables and semantic Tailwind tokens; refactor ChatView and Sidebar UI to use shared UI primitives; remove thread status badges, editor/approval flows, and keyboard shortcuts; update model selection in new project flow.

📍Where to Start

Start with the useTheme hook in useTheme.ts and its integration in the Sidebar in Sidebar.tsx, then review theme token application in index.css.


Macroscope summarized 0a535ae.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/Sidebar.tsx`:
- Around line 225-230: The button currently uses the disabled prop which
conflicts with accessibility guidance and tooltips; instead remove the disabled
attribute on the button element, add aria-disabled={isPickingFolder} to reflect
state, keep/adjust the visual "disabled" classes conditionally when
isPickingFolder, and add a click guard at the top of handlePickFolder (or inline
in the onClick wrapper) that returns early if isPickingFolder so clicks are
ignored; ensure tooltip behavior is preserved by using aria-disabled rather than
disabled.
In `@apps/renderer/src/components/ui/combobox.tsx`:
- Around line 196-214: The SVG in the Combobox component uses an incorrect xmlns
value; update the xmlns on the SVG inside ComboboxPrimitive.ItemIndicator to the
correct "http://www.w3.org/2000/svg" string so the icon renders properly (look
for the SVG element in the ComboboxPrimitive.ItemIndicator within the
combobox.tsx component and change the xmlns attribute value).
In `@apps/renderer/src/components/ui/empty.tsx`:
- Around line 94-105: The component EmptyDescription is typed as
React.ComponentProps<"p"> but renders a <div>, causing a props/semantic
mismatch; fix it by making the types and markup consistent—either change the
rendered element to <p> (keep the same props type) or change the prop type to
React.ComponentProps<"div"> (keep the <div>); update the function signature
(EmptyDescription) accordingly and ensure spreading {...props}, className and
data-slot="empty-description" remain correct for the chosen element.
In `@apps/renderer/src/components/ui/scroll-area.tsx`:
- Around line 5-35: Replace the custom use of ScrollAreaPrimitive
(ScrollAreaPrimitive.Root, ScrollAreaPrimitive.Viewport,
ScrollAreaPrimitive.Corner) and the local ScrollBar components with the Coss UI
ScrollArea wrapper: install the package via the shadcn CLI (`pnpm dlx
shadcn@latest add `@coss/scroll-area``), import Coss's ScrollArea,
ScrollAreaViewport and Corner (or the exported equivalents) and re-create this
component as a thin wrapper named ScrollArea that forwards props and children
while preserving the scrollFade and scrollbarGutter booleans by mapping them to
equivalent className masks and gutter classes on the Coss component (apply the
same mask expressions and `data-has-overflow-*` gutter classes), and ensure
data-slot attributes (scroll-area-viewport, scroll-area-corner) remain on the
mapped children so existing consumers continue to work.
In `@apps/renderer/src/components/ui/select.tsx`:
- Around line 128-142: The SVG used inside SelectPrimitive.ItemIndicator has a
typo in its xmlns attribute; update the xmlns on the SVG element within
SelectPrimitive.ItemIndicator to the correct namespace
"http://www.w3.org/2000/svg" so the checkmark renders correctly across browsers
(locate the SVG in the SelectPrimitive.ItemIndicator block and replace the
incorrect "http://www.w3.org/1500/svg" value).
In `@apps/renderer/src/components/ui/skeleton.tsx`:
- Around line 1-3: The file uses React.ComponentProps<"div"> in the Skeleton
component but doesn't import the React namespace; add an import for React (e.g.,
add "import React from 'react'" or "import type React from 'react'") at the top
of apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.
🧹 Nitpick comments (6)
apps/renderer/src/components/ui/table.tsx (1)

36-47: Consider extracting long class strings for maintainability.

The class string on line 40 is quite long with many frame-conditional styles. While this is a common pattern in shadcn-style UI libraries, consider extracting the frame-specific styles into a separate variable or using Tailwind's @apply directive in a CSS file for better readability.

That said, this is a stylistic preference and the current implementation is functionally correct.

apps/renderer/src/components/ui/separator.tsx (1)

13-13: The CSS selector for vertical self-stretch is fragile and likely won't work as intended.

The selector not-[[class^='h-']]:not-[[class*='_h-']] checks whether the class attribute string starts with 'h-' or contains '_h-'. Since other classes (e.g., shrink-0) will appear first in the composed className, the [class^='h-'] condition will almost never match—even when a height utility like h-10 is present.

Consider simplifying by always applying self-stretch for vertical orientation and letting explicit height classes override it naturally, or document that consumers should use inline styles / CSS variables for custom heights:

♻️ Suggested simplification
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
apps/renderer/src/components/ui/toast.tsx (1)

220-250: Consider extracting shared toast content into a reusable component.

The toast content rendering here (icon, title, description, action button) is nearly identical to lines 128-158 in the Toasts component. Extracting this into a shared ToastContent component would reduce duplication and ensure consistency when making future changes.

♻️ Suggested extraction
// Shared component to use in both Toasts and AnchoredToastsfunctionToastContentBody({ toast }: {toast: Toast.ToastObject}){constIcon=toast.type
? TOAST_ICONS[toast.typeaskeyoftypeofTOAST_ICONS]
: null;return(<Toast.ContentclassName="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm ..."><divclassName="flex gap-2">{Icon&&(<divclassName="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"data-slot="toast-icon"><IconclassName="in-data-[type=loading]:animate-spin ..."/></div>)}<divclassName="flex flex-col gap-0.5"><Toast.TitleclassName="font-medium"data-slot="toast-title"/><Toast.DescriptionclassName="text-muted-foreground"data-slot="toast-description"/></div></div>{toast.actionProps&&(<Toast.ActionclassName={buttonVariants({size: "xs"})}data-slot="toast-action">{toast.actionProps.children}</Toast.Action>)}</Toast.Content>);}
apps/renderer/src/components/ui/kbd.tsx (1)

5-26: Prefer Coss UI primitives for consistency.

These custom Kbd/KbdGroup wrappers diverge from the guideline to use Coss UI components (installed via the ShadCN CLI) for consistent styling and Base UI accessibility alignment. If a Coss kbd component exists, consider swapping to it (or document why bespoke wrappers are required).

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling. Install new components using the ShadCN CLI (e.g., bunx --bun shadcnlatest add coss/button). Refer to Base UI docs for accessibility guidelines on primitives.

apps/renderer/src/components/ui/textarea.tsx (1)

7-10: Consider documenting or removing the number type for size.

The size prop accepts "sm" | "default" | "lg" | number, but the internal styling logic (lines 35-38) only handles the string literals. When a number is passed, it's set on data-size but no corresponding styles are applied.

If numeric sizes are intended for custom CSS handling via data-size, consider adding a brief comment documenting this. Otherwise, consider restricting the type to the supported string literals only.

apps/renderer/src/index.css (1)

227-242: Consider using CSS variables for scrollbar colors for consistency.

The scrollbar styling uses hardcoded rgba() values while the rest of the file uses CSS variables. For better theme consistency, consider using the defined tokens:

♻️ Optional: Use CSS variables for scrollbar colors
 ::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);+ background: var(--muted-foreground);+ opacity: 0.15;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 0, 0, 0.25);+ background: var(--muted-foreground);+ opacity: 0.25;
}
.dark ::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);+ background: var(--muted-foreground);+ opacity: 0.1;
}
.dark ::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.18);+ background: var(--muted-foreground);+ opacity: 0.18;
}

Note: Opacity on scrollbar thumbs may have limited browser support; the current approach works reliably.

Comment on lines 225 to 230
<button
type="button"
className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => void handlePickFolder()}
disabled={isPickingFolder}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid the disabled prop; use aria-disabled and a click guard instead.

The current disabled usage conflicts with the repo’s accessibility guidance and can break tooltips. Use styling plus a guard in the handler.

🛠️ Suggested fix
- className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"- onClick={() => void handlePickFolder()}- disabled={isPickingFolder}+ className={`mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary ${+ isPickingFolder ? "cursor-not-allowed opacity-60" : ""+ }`}+ aria-disabled={isPickingFolder}+ onClick={() => {+ if (isPickingFolder) return;+ void handlePickFolder();+ }}
Based on learnings: Avoid using disabled props on buttons as they harm accessibility and break tooltips.
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/Sidebar.tsx` around lines 225 - 230, The button
currently uses the disabled prop which conflicts with accessibility guidance and
tooltips; instead remove the disabled attribute on the button element, add
aria-disabled={isPickingFolder} to reflect state, keep/adjust the visual
"disabled" classes conditionally when isPickingFolder, and add a click guard at
the top of handlePickFolder (or inline in the onClick wrapper) that returns
early if isPickingFolder so clicks are ignored; ensure tooltip behavior is
preserved by using aria-disabled rather than disabled.

Comment on lines +196 to +214
<ComboboxPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</ComboboxPrimitive.ItemIndicator>
<div className="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

Same issue as in select.tsx: the xmlns attribute has http://www.w3.org/1500/svg instead of http://www.w3.org/2000/svg.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/combobox.tsx` around lines 196 - 214, The SVG
in the Combobox component uses an incorrect xmlns value; update the xmlns on the
SVG inside ComboboxPrimitive.ItemIndicator to the correct
"http://www.w3.org/2000/svg" string so the icon renders properly (look for the
SVG element in the ComboboxPrimitive.ItemIndicator within the combobox.tsx
component and change the xmlns attribute value).

Comment on lines +94 to +105
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: Component typed as <p> but renders <div>.

EmptyDescription accepts React.ComponentProps<"p"> but renders a <div>. This inconsistency could confuse consumers and allows <p>-specific props that won't apply to the rendered <div>.

💡 Proposed fix

Either change the rendered element to match the type:

-function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {+function EmptyDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div

Or render a <p> to match the type (if semantically appropriate):

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
- <div+ <p
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
- />+ />
);
}
📝 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.

Suggested change
functionEmptyDescription({ className, ...props}: React.ComponentProps<"p">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
functionEmptyDescription({ className, ...props}: React.ComponentProps<"div">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/empty.tsx` around lines 94 - 105, The
component EmptyDescription is typed as React.ComponentProps<"p"> but renders a
<div>, causing a props/semantic mismatch; fix it by making the types and markup
consistent—either change the rendered element to <p> (keep the same props type)
or change the prop type to React.ComponentProps<"div"> (keep the <div>); update
the function signature (EmptyDescription) accordingly and ensure spreading
{...props}, className and data-slot="empty-description" remain correct for the
chosen element.

Comment on lines +5 to +35
function ScrollArea({
className,
children,
scrollFade = false,
scrollbarGutter = false,
...props
}: ScrollAreaPrimitive.Root.Props & {
scrollFade?: boolean;
scrollbarGutter?: boolean;
}) {
return (
<ScrollAreaPrimitive.Root
className={cn("size-full min-h-0", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
scrollFade &&
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
scrollbarGutter &&
"data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
)}
data-slot="scroll-area-viewport"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation="vertical" />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Coss UI ScrollArea component documentation installation shadcn

💡 Result:

Coss UI – ScrollArea (shadcn CLI install + docs)

  • Docs:coss.com/ui/docs/components/scroll-area (coss.com)
  • Install (shadcn CLI):
    pnpm dlx shadcn@latest add `@coss/scroll-area`
    (coss.com)
  • Use:
    import{ScrollArea}from"@/components/ui/scroll-area"
    (coss.com)

If you meant the core shadcn/ui ScrollArea (Radix/Base UI), the install command is:

pnpm dlx shadcn@latest add scroll-area

(ui.shadcn.com)

Citations:


🏁 Script executed:

# Check for existing UI component usage patterns in the repository
find apps/renderer/src/components/ui -type f -name "*.tsx"| head -20

Repository: pingdotgg/codething-mvp

Length of output: 957


🏁 Script executed:

# Check package.json to see what UI libraries are currently in use
cat package.json | grep -A 20 '"dependencies"'| head -40

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Search for existing imports of UI components to understand patterns
rg "from.*coss|from.*shadcn" apps/renderer/src/components/ui --no-heading -A 1 | head -30

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Check if ScrollAreaPrimitive is imported from `@radix-ui` or another source
head -50 apps/renderer/src/components/ui/scroll-area.tsx

Repository: pingdotgg/codething-mvp

Length of output: 2171


Replace with Coss UI ScrollArea component for consistency with codebase guidelines.

Coss UI provides a ScrollArea component that should be installed via ShadCN CLI: pnpm dlx shadcn@latest add @coss/scroll-area``. The custom extensions (scrollFade, `scrollbarGutter`) can be preserved by wrapping the Coss UI component instead of using Base UI directly. This ensures alignment with the team's component catalog and styling standards.

🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/scroll-area.tsx` around lines 5 - 35, Replace
the custom use of ScrollAreaPrimitive (ScrollAreaPrimitive.Root,
ScrollAreaPrimitive.Viewport, ScrollAreaPrimitive.Corner) and the local
ScrollBar components with the Coss UI ScrollArea wrapper: install the package
via the shadcn CLI (`pnpm dlx shadcn@latest add `@coss/scroll-area``), import
Coss's ScrollArea, ScrollAreaViewport and Corner (or the exported equivalents)
and re-create this component as a thin wrapper named ScrollArea that forwards
props and children while preserving the scrollFade and scrollbarGutter booleans
by mapping them to equivalent className masks and gutter classes on the Coss
component (apply the same mask expressions and `data-has-overflow-*` gutter
classes), and ensure data-slot attributes (scroll-area-viewport,
scroll-area-corner) remain on the mapped children so existing consumers continue
to work.

Comment on lines +128 to +142
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

The xmlns attribute has a typo: http://www.w3.org/1500/svg should be http://www.w3.org/2000/svg. This invalid namespace may cause the checkmark SVG to fail to render in some browsers.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/select.tsx` around lines 128 - 142, The SVG
used inside SelectPrimitive.ItemIndicator has a typo in its xmlns attribute;
update the xmlns on the SVG element within SelectPrimitive.ItemIndicator to the
correct namespace "http://www.w3.org/2000/svg" so the checkmark renders
correctly across browsers (locate the SVG in the SelectPrimitive.ItemIndicator
block and replace the incorrect "http://www.w3.org/1500/svg" value).

Comment on lines +1 to +3
import { cn } from "#/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/renderer/src/components/ui/skeleton.tsx | head -20

Repository: pingdotgg/codething-mvp

Length of output: 723


🏁 Script executed:

find . -name "tsconfig.json" -type f | head -5

Repository: pingdotgg/codething-mvp

Length of output: 160


🏁 Script executed:

cat tsconfig.json 2>/dev/null || cat ./apps/renderer/tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -exec cat {} \;

Repository: pingdotgg/codething-mvp

Length of output: 388


Add React type import for ComponentProps reference.

Line 3 references React.ComponentProps<"div"> without importing the React namespace. While the modern JSX transform (jsx: "react-jsx") eliminates the need for React in JSX elements, explicit namespace references like React.ComponentProps still require the import and will cause a type-checking error.

+import type * as React from "react";
import { cn } from "#/lib/utils";
📝 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.

Suggested change
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
importtype*asReactfrom"react";
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/skeleton.tsx` around lines 1 - 3, The file
uses React.ComponentProps<"div"> in the Skeleton component but doesn't import
the React namespace; add an import for React (e.g., add "import React from
'react'" or "import type React from 'react'") at the top of
apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/ChatView.tsx`:
- Around line 478-482: The UI currently renders effort labels by capitalizing
the first character in the map over REASONING_OPTIONS (in the SelectItem
rendering), which turns values like "xhigh" into "Xhigh"; replace this logic by
defining a display-name mapping (e.g., a constant object like
REASONING_DISPLAY_NAMES keyed by the same values in REASONING_OPTIONS) and use
that map when rendering the SelectItem label and when comparing to
DEFAULT_REASONING, falling back to a nicer formatter (e.g., full word
replacements or Title Case) only if a key is missing; update the SelectItem
render to use REASONING_DISPLAY_NAMES[effort] || fallbackFormatter(effort).
🧹 Nitpick comments (1)
apps/renderer/src/components/ChatView.tsx (1)

451-456: Minor: Redundant dark: prefixes for transparent and semantic tokens.

bg-transparent is identical in both modes, and bg-accent is a semantic token that already adapts to the theme. The dark: prefixes are unnecessary here.

♻️ Simplified styling
 <SelectTrigger
size="sm"
- className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"+ className="w-auto min-w-0 border-0 shadow-none bg-transparent before:hidden data-[popup-open]:bg-accent"
>

Apply to both SelectTrigger instances (lines 451-456 and 471-476).

Also applies to: 471-476

Comment on lines +478 to +482
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
{effort.charAt(0).toUpperCase() + effort.slice(1)}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalization of "xhigh" produces awkward "Xhigh" in the UI.

The inline capitalization effort.charAt(0).toUpperCase() + effort.slice(1) transforms "xhigh" to "Xhigh", which isn't user-friendly. Consider a display name mapping for better readability.

💡 Suggested approach
+const EFFORT_LABELS: Record<string, string> = {+ xhigh: "Extra High",+ high: "High",+ medium: "Medium",+ low: "Low",+};+
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
- {effort.charAt(0).toUpperCase() + effort.slice(1)}+ {EFFORT_LABELS[effort] ?? effort}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
))}
📝 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.

Suggested change
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{effort.charAt(0).toUpperCase()+effort.slice(1)}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
constEFFORT_LABELS: Record<string,string>={
xhigh: "Extra High",
high: "High",
medium: "Medium",
low: "Low",
};
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{EFFORT_LABELS[effort]??effort}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ChatView.tsx` around lines 478 - 482, The UI
currently renders effort labels by capitalizing the first character in the map
over REASONING_OPTIONS (in the SelectItem rendering), which turns values like
"xhigh" into "Xhigh"; replace this logic by defining a display-name mapping
(e.g., a constant object like REASONING_DISPLAY_NAMES keyed by the same values
in REASONING_OPTIONS) and use that map when rendering the SelectItem label and
when comparing to DEFAULT_REASONING, falling back to a nicer formatter (e.g.,
full word replacements or Title Case) only if a key is missing; update the
SelectItem render to use REASONING_DISPLAY_NAMES[effort] ||
fallbackFormatter(effort).

juliusmarmingeand others added 3 commits February 9, 2026 01:12
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the juliusmarminge/use-base-components branch from 35deea6 to 0a535aeCompareFebruary 9, 2026 09:13
@t3dotgg
t3dotgg merged commit c51f252 into mainFeb 9, 2026
1 check passed
t3dotgg added a commit that referenced this pull request Feb 9, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
praveenperera pushed a commit to praveenperera/t3code that referenced this pull request Mar 23, 2026
nikrabaev added a commit to nikrabaev/t3code that referenced this pull request Apr 25, 2026
The original route URL /$environmentId/$weaveRunId collided with the
existing chat thread route /$environmentId/$threadId — TanStack Router
matches by URL pattern, not by route ID, so the chat route always won.
Opening any weave run dropped onto the chat route, which then ran its
'thread doesn't exist → redirect' effect in a tight loop and exhausted
React's update depth.
Add a literal 'weave' segment to the weave URL so the patterns are
distinct:
before: /$envId/$weaveRunId vs /$envId/$threadId
after : /$envId/weave/$weaveRunId vs /$envId/$threadId
The plan flagged this exact risk in §'Open design questions' pingdotgg#4 and
prescribed this fallback. Files renamed; consumers (ChatView dispatch,
WeaveBlueprintList node nav, WeaveRunSidebarItem Link, WeaveCreatedMarker
Link) updated. Route tree regenerated by Vite plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 14, 2026
Two cleanups flagged by thermo-review:
- Phase partition (Standards #1 + Quality #3b): the active/recent split
hardcoded the terminal-phase list twice as complementary filters. Extract
TERMINAL_PHASES + partitionIssuesByPhase into sandcastleView.ts so the two
buckets can't drift, with unit tests.
- Issue rows (Quality #3c): the Active and Recent sections hand-rolled the
same #number link-or-span + phaseLabel badge. Extract one SandcastleIssueRow
component with a variant prop; classNames preserved verbatim (no visual
change). Collapses ~80 lines to ~40.
Also replace as-never test casts (Quality pingdotgg#4) with a real RepositoryIdentity
fixture mirroring environmentGrouping.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 17, 2026
Gate computeRevertTurnCountByUserMessageId on the union of the span's
agent edit sets (ADR-0004 decision 4), not a single turn; normalize web
agent paths to repo-relative and match the checkpoint diff by equality
instead of a suffix heuristic. Review findings: spec gating, quality pingdotgg#4/pingdotgg#5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 3, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: contracts for blob store, mirror RPCs, and registry
M1 analysis pass recorded in the plan doc (deviations: settings-flag home,
reactor gating pattern, no server-side peer endpoint discovery, mirror RPC
transport, D4 credential handshake).
Contracts:
- WorkspaceProjectId branded id (D1)
- packages/contracts/src/roaming.ts: RoamingBlobRecord/manifest/conflict (D3),
registry payload, RoamingProjectShell, peer + mirror + enrollment RPC
schemas, HTTP path constants
- roaming:mirror auth scope; `roaming` server setting (default off)
- OrchestrationShellSnapshot.roamingProjects (decode-default []) + shell
stream variants; optional workspaceProjectId on project shapes and
project.meta-updated
- internal command project.roaming.enroll with decider/projector/engine
wiring (emits project.meta-updated; double-enroll rejected)
Test literals updated for the new snapshot field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Roaming M1 contracts: apply review findings
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: roaming_blobs migration + RoamingBlobStore
Migration 033 (roaming_blobs + roaming_blob_conflicts, PK (kind,key)) and a
SqlClient-backed RoamingBlobStore: writeLocal (version bump, sha256 of the
verbatim payload string, author environment id), applyRemote implementing the
D3 reconciliation rule (higher version wins; equal version + different hash
records a conflict carrying the full remote record and keeps local),
manifest/get/getMany/listConflicts, and a changes stream that emits only on
accepted writes. Reuses persistence error types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: accepted writes clear superseded conflict rows
codex review P2: a recorded equal-version conflict stayed in
roaming_blob_conflicts after a later accepted write moved the key past the
contested version, so listConflicts() kept surfacing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: apply independent review findings
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 20, 2026
ServerSettings.pushNotifications was hand-edited in settings.json with
the app stopped. It now has a "Phone push" section in Settings, General:
topic URL, click-through base URL, per-phase toggles, presence
suppression window, and a Send test button.
The write path already existed end to end (ServerSettingsPatch exposed
pushNotifications; server.updateSettings deep-merges), so the only new
plumbing is server.sendTestPushNotification: it fires a push with the
saved settings, bypasses presence suppression by design (the user is at
the machine clicking the button), and returns delivery problems as
{sent, detail} values rather than errors so the UI shows them inline.
Because splitPatch is shallow, the UI always writes the full
pushNotifications object.
Verification caveat, recorded in FORK_ROADMAP.md: in an isolated
browser-paired dev environment, server-settings writes from the web UI
silently never reach the server, for this section and for long-shipped
settings alike (optimistic update, no RPC, no error). Pre-existing
behavior, not a regression from this change; the Electron daily driver
is the real test. The send-test function is covered by focused tests
(delivery, click-header omission, rejection, timeout-free unreachable
host, empty topic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 21, 2026
…ttings writes
Two live bugs in the roadmap pingdotgg#4 ship:
- server.sendTestPushNotification was missing from RPC_REQUIRED_SCOPE, so
every call defected server-side ("has no declared authorization scope")
before reaching the handler. That map is a third registration point beyond
the contracts group and the ws handler; wsRpcScopes.test.ts now asserts it
covers every rpc in WsRpcGroup.
- Settings toggles persisted but never reflected in the UI when the config
projection was stalled (durable subscriptions resume late/never after a
reconnect — diagnosis written up under FORK_ROADMAP pingdotgg#5). useSettings.ts now
holds the settings object the server returns from updateSettings as an
acked overlay merged over the atom value, cleared when the projection
delivers a fresh value. This also stops stale full-object spreads from
clobbering earlier writes.
Verified in an isolated env: healthy toggle round-trip, toggle during the
post-reconnect stall window (reflects immediately, server state tracks UI),
send test returns "Delivered (HTTP 200)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
…tgg#4)
* feat(web): proxy the dev server at a Moatless backend
The fork's client talks to Moatless rather than a local t3 server, so the
dev server has to stand in as the API origin.
- T3CODE_PROXY_TARGET (alias MOATLESS_BASE_URL) selects the proxy target,
falling back to the VITE_WS_URL-derived one so stock `pnpm dev` is
unchanged. /api, /ws, /attachments and /.well-known all follow it.
- VITE_WS_URL / VITE_HTTP_URL are blanked when the override is active so
the client resolves to window.location.origin instead of reaching past
the proxy, which is what holds the credential.
- Authorization: Bearer is injected and cookie stripped only when the
target is not loopback, so a Moatless key can never reach a local t3
server.
- Credential-shaped VITE_* values are deleted from process.env before
Vite reads them, and a build-time assertion rejects any `define` value
containing an API-key prefix. Vite inlines the whole import.meta.env
object and Moatless sandboxes inject VITE_API_KEY=mvk_…, so without
this the key ships in the bundle.
- allowedHosts: true — a hosted sandbox's preview hostname is not
knowable ahead of time.
Two additions are specific to running inside a hosted sandbox:
T3CODE_PROXY_TARGET_OVERRIDE is read from repo env files only. loadRepoEnv
lets the ambient environment win over .env.local, which is right for
everything else it resolves, but a sandbox injects T3CODE_PROXY_TARGET
pointing at its own bundled server and a checkout needs to be able to say
"no, use mine" without editing the deployment.
Fast Refresh is turned off when NODE_ENV=production. @vitejs/plugin-react
enables the oxc Fast Refresh transform for every `serve` run but injects
its runtime preamble only when the config resolves as development, so a
sandbox that exports NODE_ENV=production process-wide gets modules full
of $RefreshReg$ calls with nothing to answer them and a blank page with
no server-side error. Forcing NODE_ENV=development instead trades that
for react/compiler-runtime's `c` export vanishing behind a lexer-opaque
conditional, so applying the plugin's own skipFastRefresh rule to the
transform is the smaller fix.
* docs: full spec for the t3code -> Moatless adapter
Records the architecture the fork settled on (a server-side adapter
rather than swapping client-side seams), the verified domain and message
mappings, the contract surface the adapter has to serve, the milestone
state including what M2 still owes, and the eleven measured Moatless API
gaps each workaround points back to.
* docs: record the M3 browser verification and the merged-repository case
The unit tests prove the projections decode; only a browser proves an
untouched t3 client accepts them. Records what the Playwright run against
the live adapter actually asserted, including the one 404 it tolerates
and why that is correct.
Also documents a case the run surfaced: two Moatless repositories can
point at the same git remote, and t3 groups the sidebar by repository
identity, so they render under one header. Left alone deliberately —
synthesizing a distinct canonicalKey would misreport identity that t3
also uses for worktree and VCS association.
* fix(web): don't pin the HMR socket to a wildcard bind address
Upstream pins hmr.protocol/host/clientPort so the socket connects reliably
inside Electron's BrowserWindow, whose window loads http://localhost:<port>
directly. A hosted sandbox sets HOST=0.0.0.0, which turned that into a literal
ws://0.0.0.0:5733/ in the served /@vite/client — an address no browser can dial,
and mixed-content-blocked once the page is served over an HTTPS preview
hostname. Unset, Vite's client derives the socket URL from the page's own
origin, which is right behind a proxy and for a plain local run alike, so pin
the endpoint only when HOST names a dialable address.
Also records in the plan doc why the preview URL 504s: the gateway's packets
never reach the pod (no socket on 5733 for the full 30s of an external request,
while the untouched t3 sidecar on 13773 fails the same way), so that one is
infrastructure, not this checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the root cause of the preview-URL 504
Traefik IngressRoutes and the sandbox Service are built per task from the
repository's declared server ports, but the NetworkPolicy that says who may
dial a sandbox takes its Traefik ingress ports from a fixed list in values.yaml
that stops at 8888. t3code uses 5733 and 13773, so the CNI drops the SYN and
Traefik times out into a 504.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: preview URL fixed — record which policy actually gated it
The chart's Traefik ingress rule names a namespace this cluster does not have,
so it could never match. The live gate was moatless-sandbox-preview-ingress, an
ArgoCD-managed policy carrying a copy of the same static port list. With its
ports block removed, the preview host serves / in 42ms and the shell endpoint in
54ms with 7 projects and 98 open threads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: spec the client/server contract surfaces
The contract lives in packages/contracts as 31 TypeScript modules with no
generated artifact, so the only way to see the whole surface was to read
rpc.ts and environmentHttp.ts and follow imports. Index it instead: all 70
WsRpcGroup methods with payload/success/scope, all 20 dispatchable commands,
all 23 EnvironmentHttpApi endpoints, the ServerConfig handshake and its
version-negotiation rules, and the subscription semantics a schema-correct
server can still break.
Also records what is deliberately outside the typed surfaces (raw HTTP
routes, the IPC/relay/MCP boundaries, the Schema.Unknown envelopes) so
"fully typed" is not overclaimed, and notes that WS_METHODS.projectsList /
projectsAdd / projectsRemove are dead constants with no Rpc.make.
Tables were generated by parsing the source and verified back against it:
method names, line numbers, stream flags, and required scopes all match
rpc.ts and ws.ts RPC_REQUIRED_SCOPE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* wip
* docs: map the client/server contract onto Moatless
Feature-by-feature walk of all three contract surfaces against the Moatless
backend: 23 HTTP endpoints, 70 RPC methods, and the 20-member command union.
Records which concepts are isomorphic, which only look alike, and what each
side has that the other cannot express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the preview mapping — tab vs. server process
T3's preview.* is the in-app browser tab (PreviewSessionSnapshot is
navStatus/canGoBack/viewport); Moatless's preview server is the process
serving the app. T3's process side is a ProjectScript in a terminal, found
by PortScanner. The declared-vs-discovered difference lands on
subscribeDiscoveredLocalServers, not preview.open.
Retallies the WS surface: 4 equivalent, 26 approximate, 40 absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the message-event claim — two events, not one
agent.message is the slim persisted log row (ADR-0003 / migration 0089);
message.upsert is a separate derived event carrying an assembled UiMessage,
relayed live and never persisted. Streaming assembled content is possible;
full tool detail is not, and the content-carrying event is the one that
cannot be replayed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: design the T3/Moatless convergence
Four moves, deliberately not all in the same direction: T3 adopts Moatless's
task+sandbox model, preview servers, and per-sandbox diagnostics scoping;
Moatless adopts T3's sequenced resumable streaming contract.
Records the governing decision (the deployment is the environment, the
sandbox is thread-scoped state), the two hard parts (unpersisted
message.upsert, bigserial as a cursor), and what each move costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: add local Moatless adapter workflow
* chore: preserve repository links
* feat(web): use Moatless auth for primary UI
* fix(web): allow Moatless cookie login after 401 session
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@t3dotgg
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

light mode - #4

Merged
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components
Feb 9, 2026
Merged

light mode#4
t3dotgg merged 3 commits into
mainfrom
juliusmarminge/use-base-components

Conversation

@juliusmarminge

@juliusmarmingejuliusmarminge commented Feb 9, 2026

Copy link
Copy Markdown
Member
CleanShot 2026-02-08 at 22 05 13@2xCleanShot 2026-02-08 at 22 05 17@2x

Summary by CodeRabbit

  • New Features

    • Large library of reusable UI components added (accordions, dialogs, menus, inputs, selects, command palette, toasts, sheets, tables, etc.) for richer, consistent interfaces.
  • Theme

    • Dark theme support with system detection, cross-tab sync, and a theme toggle in the sidebar.
  • Style

    • Replaced hard-coded colors with semantic design tokens and global CSS variables for consistent light/dark theming and improved visuals.

@coderabbitai

coderabbitaiBot commented Feb 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds a large set of new UI components and utilities to the renderer, introduces a CSS-variable based theme (light/dark/system) and a useTheme hook, replaces hard-coded color tokens with semantic tokens across core renderer components, adds a cn utility and components.json, adjusts deps/config and a small smoke-test script tweak.

Changes

Cohort / File(s)Summary
UI component library
apps/renderer/src/components/ui/*
Adds 30+ new UI modules (accordion, alert, autocomplete, avatar, badge, button, card, checkbox, collapsible, combobox, command, dialog, empty, field, fieldset, form, input-group, input, kbd, label, menu, popover, progress, radio-group, scroll-area, select, separator, sheet, skeleton, spinner, switch, table, tabs, textarea, toast). Each wraps @base-ui/react primitives, applies data-slot attributes, CVA variants and composed Tailwind classes. Review API shapes, typings, and consistent class/slot usage.
Core renderer components
apps/renderer/src/App.tsx, apps/renderer/src/components/ChatView.tsx, apps/renderer/src/components/ChatMarkdown.tsx, apps/renderer/src/components/DiffPanel.tsx, apps/renderer/src/components/Sidebar.tsx
Replaces hex color classes with semantic tokens; ChatView integrates new InputGroup, Select, Button components and removes previous textarea auto-resize logic; ChatMarkdown and DiffPanel styling tokens updated; Sidebar adds theme toggle and theme-aware styling. Validate behavioral changes where logic was removed (e.g., textarea resizing, model menu handling).
Theming & CSS
apps/renderer/src/index.css, apps/renderer/src/hooks/useTheme.ts
Introduces CSS variable theme system with light/dark variants and a .no-transitions helper; adds many CSS token definitions and dark overrides. Adds useTheme hook (light
Utilities & small libs
apps/renderer/src/lib/utils.ts, apps/renderer/components.json
Adds cn(...inputs) util combining clsx + twMerge. Adds components.json with UI framework config, aliases, Tailwind/tokens and registries. Confirm imports use the new alias and utility.
Config & build
apps/renderer/tsconfig.json, apps/renderer/vite.config.ts, biome.json
Adds #/* path alias and resolves # in Vite; sets exactOptionalPropertyTypes: false in tsconfig; relaxes biome a11y rule noSvgWithoutTitle.
Dependencies
apps/renderer/package.json
Adds runtime deps including @base-ui/react, class-variance-authority, clsx, lucide-react, tailwind-merge and reorders react-markdown. Verify lockfile and install.
Markup & scripts
apps/renderer/index.html, apps/desktop/scripts/smoke-test.mjs
Removes body Tailwind class in index.html. Small smoke-test script tweak: reorder imports from node:child_process and use template literal for error output. Minor script change.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 0.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check❓ InconclusiveThe PR title 'light mode' is vague and does not clearly describe the actual scope of changes, which include comprehensive UI component library implementation, theming system, styling updates, and design system token migration across the renderer application.Consider a more descriptive title that captures the main work, such as 'Add design system UI components and light/dark theme support' or 'Implement base UI component library with theming.'
✅ Passed checks (1 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch juliusmarminge/use-base-components

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/renderer/src/components/ChatView.tsx (1)

312-344: Consider using cn utility for conditional class handling.

The deeply nested ternaries for status indicator styling reduce readability. Using the cn utility with object syntax would make the phase-based styling clearer and more maintainable.

♻️ Suggested refactor using cn utility
+const statusContainerClasses: Record<string, string> = {+ running: "border-sky-400/35 bg-sky-500/8 text-sky-700 dark:text-sky-100",+ connecting: "border-amber-400/35 bg-amber-500/8 text-amber-700 dark:text-amber-100",+ ready: "border-emerald-400/35 bg-emerald-500/8 text-emerald-700 dark:text-emerald-100",+ disconnected: "border-border bg-secondary text-muted-foreground",+};++const statusDotClasses: Record<string, string> = {+ running: "bg-sky-200",+ connecting: "bg-amber-200",+ ready: "bg-emerald-200",+ disconnected: "bg-muted-foreground/40",+};

Then use:

<divclassName={cn("inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-[10px]",statusContainerClasses[phase]??statusContainerClasses.disconnected)}>

Based on learnings: "Use Tailwind CSS for styling - no inline styles, use cn for conditional classes".

apps/renderer/src/components/Sidebar.tsx (1)

138-152: Prefer Coss UI for the new icon-only theme toggle.

To keep styling and interaction patterns consistent with the rest of the app, consider swapping this raw <button> for the equivalent Coss UI icon/button primitive.

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling.


Comment @coderabbitai help to get the list of available commands and usage tips.

@macroscopeapp

macroscopeappBot commented Feb 9, 2026

Copy link
Copy Markdown
Contributor

Add light/dark/system theme cycling and apply token-based theming across Sidebar, ChatView, and global styles to introduce light mode

Introduce a useTheme hook with localStorage persistence and a Sidebar header button to cycle system/light/dark; switch global and app styling to CSS variables and semantic Tailwind tokens; refactor ChatView and Sidebar UI to use shared UI primitives; remove thread status badges, editor/approval flows, and keyboard shortcuts; update model selection in new project flow.

📍Where to Start

Start with the useTheme hook in useTheme.ts and its integration in the Sidebar in Sidebar.tsx, then review theme token application in index.css.


Macroscope summarized 0a535ae.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/Sidebar.tsx`:
- Around line 225-230: The button currently uses the disabled prop which
conflicts with accessibility guidance and tooltips; instead remove the disabled
attribute on the button element, add aria-disabled={isPickingFolder} to reflect
state, keep/adjust the visual "disabled" classes conditionally when
isPickingFolder, and add a click guard at the top of handlePickFolder (or inline
in the onClick wrapper) that returns early if isPickingFolder so clicks are
ignored; ensure tooltip behavior is preserved by using aria-disabled rather than
disabled.
In `@apps/renderer/src/components/ui/combobox.tsx`:
- Around line 196-214: The SVG in the Combobox component uses an incorrect xmlns
value; update the xmlns on the SVG inside ComboboxPrimitive.ItemIndicator to the
correct "http://www.w3.org/2000/svg" string so the icon renders properly (look
for the SVG element in the ComboboxPrimitive.ItemIndicator within the
combobox.tsx component and change the xmlns attribute value).
In `@apps/renderer/src/components/ui/empty.tsx`:
- Around line 94-105: The component EmptyDescription is typed as
React.ComponentProps<"p"> but renders a <div>, causing a props/semantic
mismatch; fix it by making the types and markup consistent—either change the
rendered element to <p> (keep the same props type) or change the prop type to
React.ComponentProps<"div"> (keep the <div>); update the function signature
(EmptyDescription) accordingly and ensure spreading {...props}, className and
data-slot="empty-description" remain correct for the chosen element.
In `@apps/renderer/src/components/ui/scroll-area.tsx`:
- Around line 5-35: Replace the custom use of ScrollAreaPrimitive
(ScrollAreaPrimitive.Root, ScrollAreaPrimitive.Viewport,
ScrollAreaPrimitive.Corner) and the local ScrollBar components with the Coss UI
ScrollArea wrapper: install the package via the shadcn CLI (`pnpm dlx
shadcn@latest add `@coss/scroll-area``), import Coss's ScrollArea,
ScrollAreaViewport and Corner (or the exported equivalents) and re-create this
component as a thin wrapper named ScrollArea that forwards props and children
while preserving the scrollFade and scrollbarGutter booleans by mapping them to
equivalent className masks and gutter classes on the Coss component (apply the
same mask expressions and `data-has-overflow-*` gutter classes), and ensure
data-slot attributes (scroll-area-viewport, scroll-area-corner) remain on the
mapped children so existing consumers continue to work.
In `@apps/renderer/src/components/ui/select.tsx`:
- Around line 128-142: The SVG used inside SelectPrimitive.ItemIndicator has a
typo in its xmlns attribute; update the xmlns on the SVG element within
SelectPrimitive.ItemIndicator to the correct namespace
"http://www.w3.org/2000/svg" so the checkmark renders correctly across browsers
(locate the SVG in the SelectPrimitive.ItemIndicator block and replace the
incorrect "http://www.w3.org/1500/svg" value).
In `@apps/renderer/src/components/ui/skeleton.tsx`:
- Around line 1-3: The file uses React.ComponentProps<"div"> in the Skeleton
component but doesn't import the React namespace; add an import for React (e.g.,
add "import React from 'react'" or "import type React from 'react'") at the top
of apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.
🧹 Nitpick comments (6)
apps/renderer/src/components/ui/table.tsx (1)

36-47: Consider extracting long class strings for maintainability.

The class string on line 40 is quite long with many frame-conditional styles. While this is a common pattern in shadcn-style UI libraries, consider extracting the frame-specific styles into a separate variable or using Tailwind's @apply directive in a CSS file for better readability.

That said, this is a stylistic preference and the current implementation is functionally correct.

apps/renderer/src/components/ui/separator.tsx (1)

13-13: The CSS selector for vertical self-stretch is fragile and likely won't work as intended.

The selector not-[[class^='h-']]:not-[[class*='_h-']] checks whether the class attribute string starts with 'h-' or contains '_h-'. Since other classes (e.g., shrink-0) will appear first in the composed className, the [class^='h-'] condition will almost never match—even when a height utility like h-10 is present.

Consider simplifying by always applying self-stretch for vertical orientation and letting explicit height classes override it naturally, or document that consumers should use inline styles / CSS variables for custom heights:

♻️ Suggested simplification
- "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
apps/renderer/src/components/ui/toast.tsx (1)

220-250: Consider extracting shared toast content into a reusable component.

The toast content rendering here (icon, title, description, action button) is nearly identical to lines 128-158 in the Toasts component. Extracting this into a shared ToastContent component would reduce duplication and ensure consistency when making future changes.

♻️ Suggested extraction
// Shared component to use in both Toasts and AnchoredToastsfunctionToastContentBody({ toast }: {toast: Toast.ToastObject}){constIcon=toast.type
? TOAST_ICONS[toast.typeaskeyoftypeofTOAST_ICONS]
: null;return(<Toast.ContentclassName="pointer-events-auto flex items-center justify-between gap-1.5 overflow-hidden px-3.5 py-3 text-sm ..."><divclassName="flex gap-2">{Icon&&(<divclassName="[&>svg]:h-lh [&>svg]:w-4 [&_svg]:pointer-events-none [&_svg]:shrink-0"data-slot="toast-icon"><IconclassName="in-data-[type=loading]:animate-spin ..."/></div>)}<divclassName="flex flex-col gap-0.5"><Toast.TitleclassName="font-medium"data-slot="toast-title"/><Toast.DescriptionclassName="text-muted-foreground"data-slot="toast-description"/></div></div>{toast.actionProps&&(<Toast.ActionclassName={buttonVariants({size: "xs"})}data-slot="toast-action">{toast.actionProps.children}</Toast.Action>)}</Toast.Content>);}
apps/renderer/src/components/ui/kbd.tsx (1)

5-26: Prefer Coss UI primitives for consistency.

These custom Kbd/KbdGroup wrappers diverge from the guideline to use Coss UI components (installed via the ShadCN CLI) for consistent styling and Base UI accessibility alignment. If a Coss kbd component exists, consider swapping to it (or document why bespoke wrappers are required).

Based on learnings: Applies to **/*.tsx : Use Coss UI components (https://coss.com/ui) for consistent styling. Install new components using the ShadCN CLI (e.g., bunx --bun shadcnlatest add coss/button). Refer to Base UI docs for accessibility guidelines on primitives.

apps/renderer/src/components/ui/textarea.tsx (1)

7-10: Consider documenting or removing the number type for size.

The size prop accepts "sm" | "default" | "lg" | number, but the internal styling logic (lines 35-38) only handles the string literals. When a number is passed, it's set on data-size but no corresponding styles are applied.

If numeric sizes are intended for custom CSS handling via data-size, consider adding a brief comment documenting this. Otherwise, consider restricting the type to the supported string literals only.

apps/renderer/src/index.css (1)

227-242: Consider using CSS variables for scrollbar colors for consistency.

The scrollbar styling uses hardcoded rgba() values while the rest of the file uses CSS variables. For better theme consistency, consider using the defined tokens:

♻️ Optional: Use CSS variables for scrollbar colors
 ::-webkit-scrollbar-thumb {
- background: rgba(0, 0, 0, 0.15);+ background: var(--muted-foreground);+ opacity: 0.15;
border-radius: 3px;
}
::-webkit-scrollbar-thumb:hover {
- background: rgba(0, 0, 0, 0.25);+ background: var(--muted-foreground);+ opacity: 0.25;
}
.dark ::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.1);+ background: var(--muted-foreground);+ opacity: 0.1;
}
.dark ::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.18);+ background: var(--muted-foreground);+ opacity: 0.18;
}

Note: Opacity on scrollbar thumbs may have limited browser support; the current approach works reliably.

Comment on lines 225 to 230
<button
type="button"
className="mb-2 flex w-full items-center justify-center rounded-md border border-white/[0.1] px-2 py-1.5 text-xs text-[#a0a0a0]/70 transition-colors duration-150 hover:bg-white/[0.04] disabled:cursor-not-allowed disabled:opacity-60"
className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"
onClick={() => void handlePickFolder()}
disabled={isPickingFolder}
>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid the disabled prop; use aria-disabled and a click guard instead.

The current disabled usage conflicts with the repo’s accessibility guidance and can break tooltips. Use styling plus a guard in the handler.

🛠️ Suggested fix
- className="mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-60"- onClick={() => void handlePickFolder()}- disabled={isPickingFolder}+ className={`mb-2 flex w-full items-center justify-center rounded-md border border-border px-2 py-1.5 text-xs text-muted-foreground transition-colors duration-150 hover:bg-secondary ${+ isPickingFolder ? "cursor-not-allowed opacity-60" : ""+ }`}+ aria-disabled={isPickingFolder}+ onClick={() => {+ if (isPickingFolder) return;+ void handlePickFolder();+ }}
Based on learnings: Avoid using disabled props on buttons as they harm accessibility and break tooltips.
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/Sidebar.tsx` around lines 225 - 230, The button
currently uses the disabled prop which conflicts with accessibility guidance and
tooltips; instead remove the disabled attribute on the button element, add
aria-disabled={isPickingFolder} to reflect state, keep/adjust the visual
"disabled" classes conditionally when isPickingFolder, and add a click guard at
the top of handlePickFolder (or inline in the onClick wrapper) that returns
early if isPickingFolder so clicks are ignored; ensure tooltip behavior is
preserved by using aria-disabled rather than disabled.

Comment on lines +196 to +214
<ComboboxPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</ComboboxPrimitive.ItemIndicator>
<div className="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

Same issue as in select.tsx: the xmlns attribute has http://www.w3.org/1500/svg instead of http://www.w3.org/2000/svg.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
<ComboboxPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</ComboboxPrimitive.ItemIndicator>
<divclassName="col-start-2">{children}</div>
</ComboboxPrimitive.Item>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/combobox.tsx` around lines 196 - 214, The SVG
in the Combobox component uses an incorrect xmlns value; update the xmlns on the
SVG inside ComboboxPrimitive.ItemIndicator to the correct
"http://www.w3.org/2000/svg" string so the icon renders properly (look for the
SVG element in the ComboboxPrimitive.ItemIndicator within the combobox.tsx
component and change the xmlns attribute value).

Comment on lines +94 to +105
function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Type mismatch: Component typed as <p> but renders <div>.

EmptyDescription accepts React.ComponentProps<"p"> but renders a <div>. This inconsistency could confuse consumers and allows <p>-specific props that won't apply to the rendered <div>.

💡 Proposed fix

Either change the rendered element to match the type:

-function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {+function EmptyDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div

Or render a <p> to match the type (if semantically appropriate):

 function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
return (
- <div+ <p
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
- />+ />
);
}
📝 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.

Suggested change
functionEmptyDescription({ className, ...props}: React.ComponentProps<"p">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
functionEmptyDescription({ className, ...props}: React.ComponentProps<"div">){
return(
<div
className={cn(
"text-muted-foreground text-sm [&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4 [[data-slot=empty-title]+&]:mt-1",
className,
)}
data-slot="empty-description"
{...props}
/>
);
}
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/empty.tsx` around lines 94 - 105, The
component EmptyDescription is typed as React.ComponentProps<"p"> but renders a
<div>, causing a props/semantic mismatch; fix it by making the types and markup
consistent—either change the rendered element to <p> (keep the same props type)
or change the prop type to React.ComponentProps<"div"> (keep the <div>); update
the function signature (EmptyDescription) accordingly and ensure spreading
{...props}, className and data-slot="empty-description" remain correct for the
chosen element.

Comment on lines +5 to +35
function ScrollArea({
className,
children,
scrollFade = false,
scrollbarGutter = false,
...props
}: ScrollAreaPrimitive.Root.Props & {
scrollFade?: boolean;
scrollbarGutter?: boolean;
}) {
return (
<ScrollAreaPrimitive.Root
className={cn("size-full min-h-0", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
className={cn(
"h-full overscroll-contain rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-x:overscroll-x-contain",
scrollFade &&
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
scrollbarGutter &&
"data-has-overflow-y:pe-2.5 data-has-overflow-x:pb-2.5",
)}
data-slot="scroll-area-viewport"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar orientation="vertical" />
<ScrollBar orientation="horizontal" />
<ScrollAreaPrimitive.Corner data-slot="scroll-area-corner" />
</ScrollAreaPrimitive.Root>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Coss UI ScrollArea component documentation installation shadcn

💡 Result:

Coss UI – ScrollArea (shadcn CLI install + docs)

  • Docs:coss.com/ui/docs/components/scroll-area (coss.com)
  • Install (shadcn CLI):
    pnpm dlx shadcn@latest add `@coss/scroll-area`
    (coss.com)
  • Use:
    import{ScrollArea}from"@/components/ui/scroll-area"
    (coss.com)

If you meant the core shadcn/ui ScrollArea (Radix/Base UI), the install command is:

pnpm dlx shadcn@latest add scroll-area

(ui.shadcn.com)

Citations:


🏁 Script executed:

# Check for existing UI component usage patterns in the repository
find apps/renderer/src/components/ui -type f -name "*.tsx"| head -20

Repository: pingdotgg/codething-mvp

Length of output: 957


🏁 Script executed:

# Check package.json to see what UI libraries are currently in use
cat package.json | grep -A 20 '"dependencies"'| head -40

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Search for existing imports of UI components to understand patterns
rg "from.*coss|from.*shadcn" apps/renderer/src/components/ui --no-heading -A 1 | head -30

Repository: pingdotgg/codething-mvp

Length of output: 49


🏁 Script executed:

# Check if ScrollAreaPrimitive is imported from `@radix-ui` or another source
head -50 apps/renderer/src/components/ui/scroll-area.tsx

Repository: pingdotgg/codething-mvp

Length of output: 2171


Replace with Coss UI ScrollArea component for consistency with codebase guidelines.

Coss UI provides a ScrollArea component that should be installed via ShadCN CLI: pnpm dlx shadcn@latest add @coss/scroll-area``. The custom extensions (scrollFade, `scrollbarGutter`) can be preserved by wrapping the Coss UI component instead of using Base UI directly. This ensures alignment with the team's component catalog and styling standards.

🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/scroll-area.tsx` around lines 5 - 35, Replace
the custom use of ScrollAreaPrimitive (ScrollAreaPrimitive.Root,
ScrollAreaPrimitive.Viewport, ScrollAreaPrimitive.Corner) and the local
ScrollBar components with the Coss UI ScrollArea wrapper: install the package
via the shadcn CLI (`pnpm dlx shadcn@latest add `@coss/scroll-area``), import
Coss's ScrollArea, ScrollAreaViewport and Corner (or the exported equivalents)
and re-create this component as a thin wrapper named ScrollArea that forwards
props and children while preserving the scrollFade and scrollbarGutter booleans
by mapping them to equivalent className masks and gutter classes on the Coss
component (apply the same mask expressions and `data-has-overflow-*` gutter
classes), and ensure data-slot attributes (scroll-area-viewport,
scroll-area-corner) remain on the mapped children so existing consumers continue
to work.

Comment on lines +128 to +142
<SelectPrimitive.ItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</SelectPrimitive.ItemIndicator>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Invalid SVG namespace will break rendering.

The xmlns attribute has a typo: http://www.w3.org/1500/svg should be http://www.w3.org/2000/svg. This invalid namespace may cause the checkmark SVG to fail to render in some browsers.

🐛 Proposed fix
 <svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
- xmlns="http://www.w3.org/1500/svg"+ xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
📝 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.

Suggested change
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/1500/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemIndicatorclassName="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<pathd="M5.252 12.7 10.2 18.63 18.748 5.37"/>
</svg>
</SelectPrimitive.ItemIndicator>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/select.tsx` around lines 128 - 142, The SVG
used inside SelectPrimitive.ItemIndicator has a typo in its xmlns attribute;
update the xmlns on the SVG element within SelectPrimitive.ItemIndicator to the
correct namespace "http://www.w3.org/2000/svg" so the checkmark renders
correctly across browsers (locate the SVG in the SelectPrimitive.ItemIndicator
block and replace the incorrect "http://www.w3.org/1500/svg" value).

Comment on lines +1 to +3
import { cn } from "#/lib/utils";

function Skeleton({ className, ...props }: React.ComponentProps<"div">) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/renderer/src/components/ui/skeleton.tsx | head -20

Repository: pingdotgg/codething-mvp

Length of output: 723


🏁 Script executed:

find . -name "tsconfig.json" -type f | head -5

Repository: pingdotgg/codething-mvp

Length of output: 160


🏁 Script executed:

cat tsconfig.json 2>/dev/null || cat ./apps/renderer/tsconfig.json 2>/dev/null || find . -name "tsconfig.json" -exec cat {} \;

Repository: pingdotgg/codething-mvp

Length of output: 388


Add React type import for ComponentProps reference.

Line 3 references React.ComponentProps<"div"> without importing the React namespace. While the modern JSX transform (jsx: "react-jsx") eliminates the need for React in JSX elements, explicit namespace references like React.ComponentProps still require the import and will cause a type-checking error.

+import type * as React from "react";
import { cn } from "#/lib/utils";
📝 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.

Suggested change
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
importtype*asReactfrom"react";
import{cn}from"#/lib/utils";
functionSkeleton({ className, ...props}: React.ComponentProps<"div">){
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ui/skeleton.tsx` around lines 1 - 3, The file
uses React.ComponentProps<"div"> in the Skeleton component but doesn't import
the React namespace; add an import for React (e.g., add "import React from
'react'" or "import type React from 'react'") at the top of
apps/renderer/src/components/ui/skeleton.tsx so the React.ComponentProps type
resolves for the Skeleton function signature.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/renderer/src/components/ChatView.tsx`:
- Around line 478-482: The UI currently renders effort labels by capitalizing
the first character in the map over REASONING_OPTIONS (in the SelectItem
rendering), which turns values like "xhigh" into "Xhigh"; replace this logic by
defining a display-name mapping (e.g., a constant object like
REASONING_DISPLAY_NAMES keyed by the same values in REASONING_OPTIONS) and use
that map when rendering the SelectItem label and when comparing to
DEFAULT_REASONING, falling back to a nicer formatter (e.g., full word
replacements or Title Case) only if a key is missing; update the SelectItem
render to use REASONING_DISPLAY_NAMES[effort] || fallbackFormatter(effort).
🧹 Nitpick comments (1)
apps/renderer/src/components/ChatView.tsx (1)

451-456: Minor: Redundant dark: prefixes for transparent and semantic tokens.

bg-transparent is identical in both modes, and bg-accent is a semantic token that already adapts to the theme. The dark: prefixes are unnecessary here.

♻️ Simplified styling
 <SelectTrigger
size="sm"
- className="w-auto min-w-0 border-0 shadow-none bg-transparent dark:bg-transparent before:hidden data-[popup-open]:bg-accent dark:data-[popup-open]:bg-accent"+ className="w-auto min-w-0 border-0 shadow-none bg-transparent before:hidden data-[popup-open]:bg-accent"
>

Apply to both SelectTrigger instances (lines 451-456 and 471-476).

Also applies to: 471-476

Comment on lines +478 to +482
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
{effort.charAt(0).toUpperCase() + effort.slice(1)}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Capitalization of "xhigh" produces awkward "Xhigh" in the UI.

The inline capitalization effort.charAt(0).toUpperCase() + effort.slice(1) transforms "xhigh" to "Xhigh", which isn't user-friendly. Consider a display name mapping for better readability.

💡 Suggested approach
+const EFFORT_LABELS: Record<string, string> = {+ xhigh: "Extra High",+ high: "High",+ medium: "Medium",+ low: "Low",+};+
{REASONING_OPTIONS.map((effort) => (
<SelectItem key={effort} value={effort}>
- {effort.charAt(0).toUpperCase() + effort.slice(1)}+ {EFFORT_LABELS[effort] ?? effort}
{effort === DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
))}
📝 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.

Suggested change
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{effort.charAt(0).toUpperCase()+effort.slice(1)}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
constEFFORT_LABELS: Record<string,string>={
xhigh: "Extra High",
high: "High",
medium: "Medium",
low: "Low",
};
{REASONING_OPTIONS.map((effort)=>(
<SelectItemkey={effort}value={effort}>
{EFFORT_LABELS[effort]??effort}
{effort===DEFAULT_REASONING ? " (default)" : ""}
</SelectItem>
🤖 Prompt for AI Agents
In `@apps/renderer/src/components/ChatView.tsx` around lines 478 - 482, The UI
currently renders effort labels by capitalizing the first character in the map
over REASONING_OPTIONS (in the SelectItem rendering), which turns values like
"xhigh" into "Xhigh"; replace this logic by defining a display-name mapping
(e.g., a constant object like REASONING_DISPLAY_NAMES keyed by the same values
in REASONING_OPTIONS) and use that map when rendering the SelectItem label and
when comparing to DEFAULT_REASONING, falling back to a nicer formatter (e.g.,
full word replacements or Title Case) only if a key is missing; update the
SelectItem render to use REASONING_DISPLAY_NAMES[effort] ||
fallbackFormatter(effort).

juliusmarmingeand others added 3 commits February 9, 2026 01:12
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@t3dotgg
t3dotggforce-pushed the juliusmarminge/use-base-components branch from 35deea6 to 0a535aeCompareFebruary 9, 2026 09:13
@t3dotgg
t3dotgg merged commit c51f252 into mainFeb 9, 2026
1 check passed
t3dotgg added a commit that referenced this pull request Feb 9, 2026
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
jjalangtry pushed a commit to jjalangtry/t3code that referenced this pull request Mar 16, 2026
praveenperera pushed a commit to praveenperera/t3code that referenced this pull request Mar 23, 2026
nikrabaev added a commit to nikrabaev/t3code that referenced this pull request Apr 25, 2026
The original route URL /$environmentId/$weaveRunId collided with the
existing chat thread route /$environmentId/$threadId — TanStack Router
matches by URL pattern, not by route ID, so the chat route always won.
Opening any weave run dropped onto the chat route, which then ran its
'thread doesn't exist → redirect' effect in a tight loop and exhausted
React's update depth.
Add a literal 'weave' segment to the weave URL so the patterns are
distinct:
before: /$envId/$weaveRunId vs /$envId/$threadId
after : /$envId/weave/$weaveRunId vs /$envId/$threadId
The plan flagged this exact risk in §'Open design questions' pingdotgg#4 and
prescribed this fallback. Files renamed; consumers (ChatView dispatch,
WeaveBlueprintList node nav, WeaveRunSidebarItem Link, WeaveCreatedMarker
Link) updated. Route tree regenerated by Vite plugin.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 14, 2026
Two cleanups flagged by thermo-review:
- Phase partition (Standards #1 + Quality #3b): the active/recent split
hardcoded the terminal-phase list twice as complementary filters. Extract
TERMINAL_PHASES + partitionIssuesByPhase into sandcastleView.ts so the two
buckets can't drift, with unit tests.
- Issue rows (Quality #3c): the Active and Recent sections hand-rolled the
same #number link-or-span + phaseLabel badge. Extract one SandcastleIssueRow
component with a variant prop; classNames preserved verbatim (no visual
change). Collapses ~80 lines to ~40.
Also replace as-never test casts (Quality pingdotgg#4) with a real RepositoryIdentity
fixture mirroring environmentGrouping.test.ts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ziyadakl pushed a commit to ziyadakl/t3code that referenced this pull request Jun 17, 2026
Gate computeRevertTurnCountByUserMessageId on the union of the span's
agent edit sets (ADR-0004 decision 4), not a single turn; normalize web
agent paths to repo-relative and match the checkpoint diff by equality
instead of a suffix heuristic. Review findings: spec gating, quality pingdotgg#4/pingdotgg#5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 3, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: contracts for blob store, mirror RPCs, and registry
M1 analysis pass recorded in the plan doc (deviations: settings-flag home,
reactor gating pattern, no server-side peer endpoint discovery, mirror RPC
transport, D4 credential handshake).
Contracts:
- WorkspaceProjectId branded id (D1)
- packages/contracts/src/roaming.ts: RoamingBlobRecord/manifest/conflict (D3),
registry payload, RoamingProjectShell, peer + mirror + enrollment RPC
schemas, HTTP path constants
- roaming:mirror auth scope; `roaming` server setting (default off)
- OrchestrationShellSnapshot.roamingProjects (decode-default []) + shell
stream variants; optional workspaceProjectId on project shapes and
project.meta-updated
- internal command project.roaming.enroll with decider/projector/engine
wiring (emits project.meta-updated; double-enroll rejected)
Test literals updated for the new snapshot field.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Roaming M1 contracts: apply review findings
- Contractual per-kind key derivation for the (kind, key) blob address;
workspaceProjectId documented as denormalized (opus review #1, codex P1)
- RoamingBlobConflict retains the full remote record so M2 resolution can
show both payloads (opus review #2)
- Doc notes: payload bytes authoritative for hashing; scalar-version
divergence limitation (opus review #3, #4)
- Plan doc: record the sharpened convention + the SQL-projection
persistence gap (codex P1) slated for the server enrollment PR
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
thanostourik referenced this pull request in thanostourik/t3code Jul 4, 2026
* Roaming M1: roaming_blobs migration + RoamingBlobStore
Migration 033 (roaming_blobs + roaming_blob_conflicts, PK (kind,key)) and a
SqlClient-backed RoamingBlobStore: writeLocal (version bump, sha256 of the
verbatim payload string, author environment id), applyRemote implementing the
D3 reconciliation rule (higher version wins; equal version + different hash
records a conflict carrying the full remote record and keeps local),
manifest/get/getMany/listConflicts, and a changes stream that emits only on
accepted writes. Reuses persistence error types.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: accepted writes clear superseded conflict rows
codex review P2: a recorded equal-version conflict stayed in
roaming_blob_conflicts after a later accepted write moved the key past the
contested version, so listConflicts() kept surfacing it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* RoamingBlobStore: apply independent review findings
- Serialize read-modify-write in writeLocal/applyRemote behind a semaphore:
interleaved fibers could both read before either wrote, losing updates and
defeating equal-version conflict detection (opus review #1)
- Verify ingested contentHash against the payload before any branch — a
mismatched record poisons reconciliation against every peer (opus #2)
- Replace the changes Stream with subscribeChanges (PubSub.subscribe, same
shape as ProviderInstanceRegistry): subscription is established on return,
fixing the racy stream test (opus #3) and documenting drop semantics (#4)
- Coverage: hash-mismatch rejection, concurrent same-version race, getMany
across kinds with same key, conflict replacement, idempotent-apply silence
(opus #5); precise error labels in writeLocal/applyRemote paths (opus #6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
* init components
* use them
* Fix select trigger styling in dark mode and add open state highlight
Remove dark mode background bleed on select triggers and add accent
background when popup is open.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
dcherrera pushed a commit to dcherrera/t3code that referenced this pull request Jul 7, 2026
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
juliusmarminge added a commit that referenced this pull request Jul 12, 2026
The native provider logs are the debugging ground truth, but SDK
rejections (query open, message stream, prompt offer, agent open,
run start/wait) raised errors without any log write — a failed turn
left the log ending mid-conversation with no explanation (audit plan
#4: neither of thread 721fc23c's failed turns was explainable from its
log). The Claude and Cursor runners now tap every fallible SDK boundary
and write a runner.error frame carrying the redacted cause chain.
App-verified: with a broken Claude binary the native log now records
`runner.error messages.stream | Claude Agent SDK query failed. ← Claude
Code native binary not found at ...` where it previously went silent.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 20, 2026
ServerSettings.pushNotifications was hand-edited in settings.json with
the app stopped. It now has a "Phone push" section in Settings, General:
topic URL, click-through base URL, per-phase toggles, presence
suppression window, and a Send test button.
The write path already existed end to end (ServerSettingsPatch exposed
pushNotifications; server.updateSettings deep-merges), so the only new
plumbing is server.sendTestPushNotification: it fires a push with the
saved settings, bypasses presence suppression by design (the user is at
the machine clicking the button), and returns delivery problems as
{sent, detail} values rather than errors so the UI shows them inline.
Because splitPatch is shallow, the UI always writes the full
pushNotifications object.
Verification caveat, recorded in FORK_ROADMAP.md: in an isolated
browser-paired dev environment, server-settings writes from the web UI
silently never reach the server, for this section and for long-shipped
settings alike (optimistic update, no RPC, no error). Pre-existing
behavior, not a regression from this change; the Electron daily driver
is the real test. The send-test function is covered by focused tests
(delivery, click-header omission, rejection, timeout-free unreachable
host, empty topic).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
phoenixtail26 added a commit to phoenixtail26/t3code that referenced this pull request Jul 21, 2026
…ttings writes
Two live bugs in the roadmap pingdotgg#4 ship:
- server.sendTestPushNotification was missing from RPC_REQUIRED_SCOPE, so
every call defected server-side ("has no declared authorization scope")
before reaching the handler. That map is a third registration point beyond
the contracts group and the ws handler; wsRpcScopes.test.ts now asserts it
covers every rpc in WsRpcGroup.
- Settings toggles persisted but never reflected in the UI when the config
projection was stalled (durable subscriptions resume late/never after a
reconnect — diagnosis written up under FORK_ROADMAP pingdotgg#5). useSettings.ts now
holds the settings object the server returns from updateSettings as an
acked overlay merged over the atom value, cleared when the projection
delivers a fresh value. This also stops stale full-object spreads from
clobbering earlier writes.
Verified in an isolated env: healthy toggle round-trip, toggle during the
post-reconnect stall window (reflects immediately, server state tracks UI),
send test returns "Delivered (HTTP 200)".
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
aorwall added a commit to aorwall/t3code that referenced this pull request Aug 2, 2026
…tgg#4)
* feat(web): proxy the dev server at a Moatless backend
The fork's client talks to Moatless rather than a local t3 server, so the
dev server has to stand in as the API origin.
- T3CODE_PROXY_TARGET (alias MOATLESS_BASE_URL) selects the proxy target,
falling back to the VITE_WS_URL-derived one so stock `pnpm dev` is
unchanged. /api, /ws, /attachments and /.well-known all follow it.
- VITE_WS_URL / VITE_HTTP_URL are blanked when the override is active so
the client resolves to window.location.origin instead of reaching past
the proxy, which is what holds the credential.
- Authorization: Bearer is injected and cookie stripped only when the
target is not loopback, so a Moatless key can never reach a local t3
server.
- Credential-shaped VITE_* values are deleted from process.env before
Vite reads them, and a build-time assertion rejects any `define` value
containing an API-key prefix. Vite inlines the whole import.meta.env
object and Moatless sandboxes inject VITE_API_KEY=mvk_…, so without
this the key ships in the bundle.
- allowedHosts: true — a hosted sandbox's preview hostname is not
knowable ahead of time.
Two additions are specific to running inside a hosted sandbox:
T3CODE_PROXY_TARGET_OVERRIDE is read from repo env files only. loadRepoEnv
lets the ambient environment win over .env.local, which is right for
everything else it resolves, but a sandbox injects T3CODE_PROXY_TARGET
pointing at its own bundled server and a checkout needs to be able to say
"no, use mine" without editing the deployment.
Fast Refresh is turned off when NODE_ENV=production. @vitejs/plugin-react
enables the oxc Fast Refresh transform for every `serve` run but injects
its runtime preamble only when the config resolves as development, so a
sandbox that exports NODE_ENV=production process-wide gets modules full
of $RefreshReg$ calls with nothing to answer them and a blank page with
no server-side error. Forcing NODE_ENV=development instead trades that
for react/compiler-runtime's `c` export vanishing behind a lexer-opaque
conditional, so applying the plugin's own skipFastRefresh rule to the
transform is the smaller fix.
* docs: full spec for the t3code -> Moatless adapter
Records the architecture the fork settled on (a server-side adapter
rather than swapping client-side seams), the verified domain and message
mappings, the contract surface the adapter has to serve, the milestone
state including what M2 still owes, and the eleven measured Moatless API
gaps each workaround points back to.
* docs: record the M3 browser verification and the merged-repository case
The unit tests prove the projections decode; only a browser proves an
untouched t3 client accepts them. Records what the Playwright run against
the live adapter actually asserted, including the one 404 it tolerates
and why that is correct.
Also documents a case the run surfaced: two Moatless repositories can
point at the same git remote, and t3 groups the sidebar by repository
identity, so they render under one header. Left alone deliberately —
synthesizing a distinct canonicalKey would misreport identity that t3
also uses for worktree and VCS association.
* fix(web): don't pin the HMR socket to a wildcard bind address
Upstream pins hmr.protocol/host/clientPort so the socket connects reliably
inside Electron's BrowserWindow, whose window loads http://localhost:<port>
directly. A hosted sandbox sets HOST=0.0.0.0, which turned that into a literal
ws://0.0.0.0:5733/ in the served /@vite/client — an address no browser can dial,
and mixed-content-blocked once the page is served over an HTTPS preview
hostname. Unset, Vite's client derives the socket URL from the page's own
origin, which is right behind a proxy and for a plain local run alike, so pin
the endpoint only when HOST names a dialable address.
Also records in the plan doc why the preview URL 504s: the gateway's packets
never reach the pod (no socket on 5733 for the full 30s of an external request,
while the untouched t3 sidecar on 13773 fails the same way), so that one is
infrastructure, not this checkout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: record the root cause of the preview-URL 504
Traefik IngressRoutes and the sandbox Service are built per task from the
repository's declared server ports, but the NetworkPolicy that says who may
dial a sandbox takes its Traefik ingress ports from a fixed list in values.yaml
that stops at 8888. t3code uses 5733 and 13773, so the CNI drops the SYN and
Traefik times out into a 504.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: preview URL fixed — record which policy actually gated it
The chart's Traefik ingress rule names a namespace this cluster does not have,
so it could never match. The live gate was moatless-sandbox-preview-ingress, an
ArgoCD-managed policy carrying a copy of the same static port list. With its
ports block removed, the preview host serves / in 42ms and the shell endpoint in
54ms with 7 projects and 98 open threads.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: spec the client/server contract surfaces
The contract lives in packages/contracts as 31 TypeScript modules with no
generated artifact, so the only way to see the whole surface was to read
rpc.ts and environmentHttp.ts and follow imports. Index it instead: all 70
WsRpcGroup methods with payload/success/scope, all 20 dispatchable commands,
all 23 EnvironmentHttpApi endpoints, the ServerConfig handshake and its
version-negotiation rules, and the subscription semantics a schema-correct
server can still break.
Also records what is deliberately outside the typed surfaces (raw HTTP
routes, the IPC/relay/MCP boundaries, the Schema.Unknown envelopes) so
"fully typed" is not overclaimed, and notes that WS_METHODS.projectsList /
projectsAdd / projectsRemove are dead constants with no Rpc.make.
Tables were generated by parsing the source and verified back against it:
method names, line numbers, stream flags, and required scopes all match
rpc.ts and ws.ts RPC_REQUIRED_SCOPE.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* wip
* docs: map the client/server contract onto Moatless
Feature-by-feature walk of all three contract surfaces against the Moatless
backend: 23 HTTP endpoints, 70 RPC methods, and the 20-member command union.
Records which concepts are isomorphic, which only look alike, and what each
side has that the other cannot express.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the preview mapping — tab vs. server process
T3's preview.* is the in-app browser tab (PreviewSessionSnapshot is
navStatus/canGoBack/viewport); Moatless's preview server is the process
serving the app. T3's process side is a ProjectScript in a terminal, found
by PortScanner. The declared-vs-discovered difference lands on
subscribeDiscoveredLocalServers, not preview.open.
Retallies the WS surface: 4 equivalent, 26 approximate, 40 absent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: correct the message-event claim — two events, not one
agent.message is the slim persisted log row (ADR-0003 / migration 0089);
message.upsert is a separate derived event carrying an assembled UiMessage,
relayed live and never persisted. Streaming assembled content is possible;
full tool detail is not, and the content-carrying event is the one that
cannot be replayed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: design the T3/Moatless convergence
Four moves, deliberately not all in the same direction: T3 adopts Moatless's
task+sandbox model, preview servers, and per-sandbox diagnostics scoping;
Moatless adopts T3's sequenced resumable streaming contract.
Records the governing decision (the deployment is the environment, the
sandbox is thread-scoped state), the two hard parts (unpersisted
message.upsert, bigserial as a cursor), and what each move costs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs: add local Moatless adapter workflow
* chore: preserve repository links
* feat(web): use Moatless auth for primary UI
* fix(web): allow Moatless cookie login after 401 session
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
NeilTheFisher pushed a commit to NeilTheFisher/t3code that referenced this pull request Aug 18, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@juliusmarminge@t3dotgg