Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/browser/components/AddSectionButton/AddSectionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,7 @@ export const AddSectionButton: React.FC<AddSectionButtonProps> = ({ onCreateSect

if (isCreating) {
return (
<div className="flex items-center gap-1 px-2 py-0.5">
<div className="flex h-5 w-5 shrink-0 items-center justify-center">
<Plus size={12} className="text-muted/60" />
</div>
<div className="flex items-center px-2 py-0.5">
<input
ref={inputRef}
type="text"
Expand All @@ -47,7 +44,7 @@ export const AddSectionButton: React.FC<AddSectionButtonProps> = ({ onCreateSect
}}
placeholder="Section name..."
data-testid="add-section-input"
className="bg-background/50 text-foreground min-w-0 flex-1 rounded border border-white/20 px-1.5 py-0.5 text-[11px] outline-none select-text"
className="bg-background/50 text-foreground ml-6 min-w-0 flex-1 rounded border border-white/20 px-1.5 py-0.5 text-[11px] outline-none select-text"
/>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -541,7 +541,7 @@ async function assertNestedConnectorContinuity(
);
for (const trunk of ancestorTrunks) {
await expect(trunk).toHaveAttribute("data-trunk-active", expectedTrunkActive);
await expect(trunk.style.left).toBe("20px");
await expect(trunk.style.left).toBe("16px");
}
}

Expand Down
25 changes: 19 additions & 6 deletions src/browser/components/AgentListItem/AgentListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ interface AgentListItemBaseProps {
projectPath: string;
isSelected: boolean;
depth?: number;
sectionId?: string;
}

/** Props for regular (persisted) workspace items */
Expand Down Expand Up @@ -122,7 +123,7 @@ const SHOW_INLINE_ACTIONS_ON_WIDE_TOUCH =
/** Calculate left padding based on nesting depth */
function getItemPaddingLeft(depth?: number): number {
const safeDepth = typeof depth === "number" && Number.isFinite(depth) ? Math.max(0, depth) : 0;
return 12 + Math.min(32, safeDepth) * 12;
return 8 + Math.min(32, safeDepth) * 12;
}

type VisualState = "active" | "idle" | "seen" | "hidden" | "error" | "question";
Expand Down Expand Up @@ -255,7 +256,7 @@ function ActionButtonWrapper(props: { children: React.ReactNode }) {
return (
<div
className={cn(
"relative order-last ml-auto mt-1 inline-flex h-4 w-4 shrink-0 items-center self-start"
"relative order-last ml-auto mt-1 inline-flex shrink-0 items-center gap-1 self-start"
)}
>
{/* Keep the kebab trigger aligned with the title row. */}
Expand All @@ -269,20 +270,31 @@ function ActionButtonWrapper(props: { children: React.ReactNode }) {
// ─────────────────────────────────────────────────────────────────────────────

function DraftAgentListItemInner(props: DraftAgentListItemProps) {
const { projectPath, isSelected, depth, draft } = props;
const { projectPath, isSelected, depth, sectionId, draft } = props;
const paddingLeft = getItemPaddingLeft(depth);
const hasPromptPreview = draft.promptPreview.length > 0;
const draftBorderStyle: React.CSSProperties = {
backgroundImage: [
"repeating-linear-gradient(to right, var(--color-border) 0 8px, transparent 8px 14px)",
"repeating-linear-gradient(to right, var(--color-border) 0 8px, transparent 8px 14px)",
"repeating-linear-gradient(to bottom, var(--color-border) 0 8px, transparent 8px 14px)",
].join(", "),
backgroundSize: "100% 1.5px, 100% 1.5px, 1.5px 100%",
backgroundPosition: "left top, left bottom, left top",
backgroundRepeat: "no-repeat",
};

const ctxMenu = useContextMenuPosition({ longPress: true });

return (
<div
className={cn(
LIST_ITEM_BASE_CLASSES,
"border-border cursor-pointer border-t border-b border-l border-dashed pl-1 hover:bg-surface-secondary [&:hover_button]:opacity-100",
sectionId != null ? "ml-8" : "ml-6.5",
"cursor-pointer pl-1 hover:bg-surface-secondary [&:hover_button]:opacity-100",
isSelected && "bg-surface-secondary"
)}
style={{ paddingLeft }}
style={{ paddingLeft, ...draftBorderStyle }}
onClick={() => {
if (ctxMenu.suppressClickIfLongPress()) return;
draft.onOpen();
Expand Down Expand Up @@ -602,7 +614,7 @@ function RegularAgentListItemInner(props: AgentListItemProps) {
!isSelected && visualState === "idle"
? "text-content-primary"
: !isSelected && visualState === "seen"
? "text-content-secondary"
? "text-content-tertiary"
: "text-content-primary";

const paddingLeft = getItemPaddingLeft(depth);
Expand Down Expand Up @@ -640,6 +652,7 @@ function RegularAgentListItemInner(props: AgentListItemProps) {
className={cn(
LIST_ITEM_BASE_CLASSES,
"group/row",
sectionId != null ? "ml-8" : "ml-6.5",
isDragging && "opacity-50",
isRemoving && "opacity-70",
// Keep hover styles enabled for initializing workspaces so the row feels interactive.
Expand Down
27 changes: 16 additions & 11 deletions src/browser/components/ProjectSidebar/ProjectSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ interface DraftAgentListItemWrapperProps {
draftId: string;
draftNumber: number;
isSelected: boolean;
sectionId?: string;
onOpen: () => void;
onDelete: () => void;
}
Expand Down Expand Up @@ -295,6 +296,7 @@ function DraftAgentListItemWrapper(props: DraftAgentListItemWrapperProps) {
variant="draft"
projectPath={props.projectPath}
isSelected={props.isSelected}
sectionId={props.sectionId}
draft={{
draftId: props.draftId,
draftNumber: props.draftNumber,
Expand Down Expand Up @@ -356,7 +358,7 @@ const ProjectDragLayer: React.FC = () => {
<div style={{ transform: `translate(${currentOffset.x + 10}px, ${currentOffset.y + 10}px)` }}>
<div className={cn(PROJECT_ITEM_BASE_CLASS, "w-fit max-w-64 rounded-sm shadow-lg")}>
<span className="text-secondary mr-2 flex h-5 w-5 shrink-0 items-center justify-center">
<ChevronRight size={12} />
<ChevronRight className="h-4 w-4" />
</span>
<div className="flex min-w-0 flex-1 items-center pr-2">
<span className="text-foreground truncate text-sm font-medium">{basename}</span>
Expand Down Expand Up @@ -1337,16 +1339,15 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
className="flex-1 overflow-x-hidden overflow-y-auto"
>
{multiProjectWorkspaces.length > 0 && (
<div className="border-hover border-b">
<div>
<div className={PROJECT_ITEM_BASE_CLASS}>
<button
onClick={() => toggleProject(MULTI_PROJECT_SIDEBAR_SECTION_ID)}
aria-label={`${isMultiProjectSectionExpanded ? "Collapse" : "Expand"} multi-project workspaces`}
className="text-secondary hover:bg-hover hover:border-border-light mr-1.5 flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded border border-transparent bg-transparent p-0 transition-all duration-200"
>
<ChevronRight
size={12}
className="transition-transform duration-200"
className="h-4 w-4 transition-transform duration-200"
style={{
transform: isMultiProjectSectionExpanded
? "rotate(90deg)"
Expand Down Expand Up @@ -1426,7 +1427,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
const isEditingProjectDisplayName = editingProjectPath === projectPath;

return (
<div key={projectPath} className="border-hover border-b">
<div key={projectPath}>
<DraggableProjectItem
projectPath={projectPath}
onReorder={handleReorder}
Expand Down Expand Up @@ -1473,8 +1474,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
className="text-secondary hover:bg-hover hover:border-border-light mr-1.5 flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded border border-transparent bg-transparent p-0 transition-all duration-200"
>
<ChevronRight
size={12}
className="transition-transform duration-200"
className="h-4 w-4 transition-transform duration-200"
style={{ transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)" }}
/>
</button>
Expand Down Expand Up @@ -1574,8 +1574,12 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
id={workspaceListId}
role="region"
aria-label={`Workspaces for ${projectName}`}
className="pt-1"
className="relative pt-1"
>
<div
aria-hidden="true"
className="bg-border pointer-events-none absolute top-1 bottom-0 left-4.5 w-px"
/>
{(() => {
// Archived workspaces are excluded from workspaceMetadata so won't appear here

Expand Down Expand Up @@ -1937,6 +1941,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
draftId={draft.draftId}
draftNumber={draftNumber}
isSelected={isSelected}
sectionId={sectionId ?? undefined}
onOpen={() =>
handleOpenWorkspaceDraft(
projectPath,
Expand Down Expand Up @@ -2177,7 +2182,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
: "rotate(0deg)",
}}
>
<ChevronRight size={12} />
<ChevronRight className="h-4 w-4" />
</span>
</button>
{isTierExpanded && (
Expand Down Expand Up @@ -2339,7 +2344,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
)
) : sectionDrafts.length === 0 ? (
<div className="text-muted px-3 py-2 text-center text-xs italic">
No workspaces in this section
No chats in this section
</div>
) : null}
</div>
Expand Down Expand Up @@ -2369,7 +2374,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
)
) : unsectionedDrafts.length === 0 ? (
<div className="text-muted px-3 py-2 text-center text-xs italic">
No unsectioned workspaces
No unsectioned chats
</div>
) : null}
</WorkspaceSectionDropZone>
Expand Down
19 changes: 7 additions & 12 deletions src/browser/components/SectionHeader/SectionHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,7 @@ export const SectionHeader: React.FC<SectionHeaderProps> = ({

return (
<div
className="group relative flex items-center gap-1 border-t border-white/5 px-2 py-1.5 select-none"
style={{
backgroundColor: `${sectionColor}10`,
borderLeftWidth: 3,
borderLeftColor: sectionColor,
}}
className="group relative ml-2 flex items-center gap-1 py-1.5 pr-2 pl-3 select-none"
data-section-id={section.id}
>
{/* Expand/Collapse Button */}
Expand All @@ -88,8 +83,7 @@ export const SectionHeader: React.FC<SectionHeaderProps> = ({
aria-expanded={isExpanded}
>
<ChevronRight
size={12}
className="transition-transform duration-200"
className="h-3 w-3 transition-transform duration-200"
style={{ transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)" }}
/>
</button>
Expand All @@ -116,7 +110,8 @@ export const SectionHeader: React.FC<SectionHeaderProps> = ({
<button
onClick={onToggleExpand}
onDoubleClick={() => setIsEditing(true)}
className="text-foreground min-w-0 flex-1 cursor-pointer truncate border-none bg-transparent p-0 text-left text-xs font-medium"
className="min-w-0 flex-1 cursor-pointer truncate border-none bg-transparent p-0 text-left text-xs font-medium"
style={{ color: sectionColor }}
>
{section.name}
<span className="text-muted ml-1.5 font-normal">({workspaceCount})</span>
Expand Down Expand Up @@ -218,18 +213,18 @@ export const SectionHeader: React.FC<SectionHeaderProps> = ({
</Tooltip>
</div>

{/* Add Workspace — always visible on touch devices */}
{/* Add Chat — always visible on touch devices */}
<Tooltip>
<TooltipTrigger asChild>
<button
onClick={onAddWorkspace}
className="text-secondary hover:text-foreground hover:bg-hover flex h-5 w-5 cursor-pointer items-center justify-center rounded border-none bg-transparent p-0 text-sm opacity-0 transition-[colors,opacity] group-hover:opacity-100 [@media(hover:none)_and_(pointer:coarse)]:opacity-100"
aria-label="New workspace in section"
aria-label="New chat in section"
>
+
</button>
</TooltipTrigger>
<TooltipContent>New workspace</TooltipContent>
<TooltipContent>New chat</TooltipContent>
</Tooltip>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion src/browser/stories/App.phoneViewports.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ export const IPhone16eSidebarWithSections: AppStory = {
// The actual visibility assertion (opacity via CSS media query) is
// validated by the Chromatic snapshot in touch mode — the Storybook
// test runner doesn't emulate pointer:coarse media queries.
within(sectionHeader as HTMLElement).getByLabelText("New workspace in section");
within(sectionHeader as HTMLElement).getByLabelText("New chat in section");
},
{ timeout: 10_000 }
);
Expand Down
6 changes: 5 additions & 1 deletion src/browser/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
--color-text-secondary: hsl(0 0% 42%);
--color-content-primary: hsl(0 0% 100%);
--color-content-secondary: hsla(240, 5%, 65%);
--color-content-tertiary: hsla(240, 5%, 34%);
--color-muted-foreground: hsl(0 0% 60%);
--color-secondary: hsl(0 0% 42%);
--color-surface-primary: hsla(240, 10%, 4%, 1);
Expand Down Expand Up @@ -410,13 +411,14 @@

--color-background: hsl(210 33% 98%);
--color-background-secondary: hsl(210 36% 95%);
--color-border: hsl(210 24% 82%);
--color-border: hsla(240, 5%, 26%);
--color-foreground: hsl(210 18% 16%);
--color-text: hsl(210 18% 16%);
--color-text-light: hsl(210 15% 28%);
--color-text-secondary: hsl(210 12% 42%);
--color-content-primary: hsl(240 10% 4%);
--color-content-secondary: hsla(240, 5%, 34%);
--color-content-tertiary: hsl(240, 4%, 46%);
--color-muted-foreground: hsl(210 14% 48%);
--color-secondary: hsl(210 18% 60%);
--color-surface-primary: hsla(0, 0%, 100%, 1);
Expand Down Expand Up @@ -675,6 +677,7 @@
--color-text-secondary: #6f6e69;
--color-content-primary: #100f0f;
--color-content-secondary: #575653;
--color-content-tertiary: #6f6e69;
--color-muted-foreground: #6f6e69;
--color-secondary: #6f6e69;
--color-surface-primary: #fffcf0;
Expand Down Expand Up @@ -905,6 +908,7 @@
--color-text-secondary: #575653;
--color-content-primary: #fffcf0;
--color-content-secondary: #878580;
--color-content-tertiary: #575653;
--color-muted-foreground: #878580;
--color-secondary: #575653;
--color-surface-primary: #100f0f;
Expand Down
4 changes: 1 addition & 3 deletions tests/ui/chat/sections.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,9 +265,7 @@ describeIntegration("Workspace Sections", () => {
const sectionHeader = view.container.querySelector(`[data-section-id="${sectionId}"]`);
expect(sectionHeader).not.toBeNull();

const addButton = sectionHeader!.querySelector(
'button[aria-label="New workspace in section"]'
);
const addButton = sectionHeader!.querySelector('button[aria-label="New chat in section"]');
expect(addButton).not.toBeNull();

// Click the add button - this should navigate to create page with section context
Expand Down
Loading