feat(ui): sidebar redesign — project zones, grouping modes, full-page surfaces (#2480)
* checkpoint: flatten sidebar core (flat sessions, zones, single mode, folders flat) * checkpoint: sidebar nav + scheduled/archive full-page surfaces, recent zone header * checkpoint: worktrees management surface via project menu * checkpoint: docs, i18n, validation for sidebar redesign * checkpoint: unified row/zone geometry, recent backfill, tooltips everywhere, primary spinner * checkpoint: branch icon marker, date moved to rich tooltip, recent back to pure time window * checkpoint: PR state on branch markers + tooltip, no reserved right space, aligned show-more, instant tooltips * checkpoint: reserve hover-action space so title text is not overlapped * checkpoint: tighten hover-action reserve * checkpoint: color-only unread emphasis to avoid title reflow * checkpoint: drop new-subfolder action, folder actions overlay on hover * checkpoint: fix sticky project headers (sticky on trigger div), stuck elevation * checkpoint: full-bleed semibold zone headers, no top scroll fade * checkpoint: headers without background tint (typography-only emphasis) * checkpoint: recent header flush with scroll top (no pre-stick bump) * checkpoint: shared tooltip provider with grouping (instant handoff between rows) * checkpoint: blur pointer-click focus so hover chrome hides on mouse-leave * checkpoint: tooltip closeDelay bridges inter-row gap * checkpoint: nav above controls, merged view dropdown, project-scoped bulk selection, cross-worktree folders * checkpoint: folder header tooltip with full path name * checkpoint: true page surfaces (hidden chat, header title, close-on-select), multirun page, run-now jump, folders on top, controls row polish * checkpoint: rename mode — dual-instance outside-click fix, no vertical shift * checkpoint: session grouping mode toggle (by-worktree default, flat option) * checkpoint: worktree header — hover padding reserve + delete worktree action * checkpoint: frosted sticky header backing under desktop vibrancy * checkpoint: align worktree sub-header with project header icon column * checkpoint: restore worktree group DnD reorder; dense vibrancy header tint (Chromium mask+backdrop-filter) * checkpoint: vibrancy — drop scroller mask so backdrop-filter samples rows (Chromium backdrop-root limitation) * checkpoint: vibrancy headers use opaque sidebar tone (Electron transparent-window backdrop-filter bug) * checkpoint: nav collapsed to one row (New session + surface icons), mirrors Add project row * checkpoint: raise sticky zone headers above row action layers (z-20) * checkpoint: New session as full-width CTA + single quiet toolbar row * checkpoint: New session row back to quiet text form above the toolbar * checkpoint: align toolbar left icon with New session icon column * checkpoint: hoverless flat header controls (color-only hover states) * checkpoint: sticky project headers toggle in view dropdown (default on) * checkpoint: full-page surfaces adapted — archive directory filter panel, scheduled master-detail, multirun without duplicate title bar * checkpoint: multirun joins mutually exclusive surface set * checkpoint: surfaces leave via navigation only (no close/cancel buttons), robust close-on-new-session, drop dead MultiRunWindow * checkpoint: no scheduled header description, aligned empty-worktree note, collapse/expand covers worktree groups * checkpoint: overlay scrollbar above sticky zone headers * checkpoint: archive delete icons reveal on hover with padding shift * checkpoint: new worktrees surface at top of the worktree list * checkpoint: no grab cursor on worktree headers * checkpoint: worktrees page list-only with inline action, no worktrees in edit dialog, drop menu ellipsis * checkpoint: worktrees page uses full content width * checkpoint: worktrees header — 'in' instead of em dash, no description * checkpoint: drop legacy OpenCode badge from worktree list, tooltip without OpenCode mention * review: bound pr summary cache
This commit is contained in:
committed by
GitHub
parent
5787ea5d49
commit
1291cde5c2
@@ -1,11 +1,4 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
@@ -17,6 +10,7 @@ import { useUIStore } from '@/stores/useUIStore';
|
||||
import { formatTimeForPreference } from '@/lib/timeFormat';
|
||||
import type { TimeFormatPreference } from '@/stores/useUIStore';
|
||||
import { useProjectsStore } from '@/stores/useProjectsStore';
|
||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||
import { useDirectoryStore } from '@/stores/useDirectoryStore';
|
||||
import { refreshGlobalSessions } from '@/stores/useGlobalSessionsStore';
|
||||
import { subscribeOpenchamberEvents } from '@/lib/openchamberEvents';
|
||||
@@ -360,18 +354,25 @@ export function ScheduledTasksDialog() {
|
||||
}
|
||||
setMutatingTaskID(task.id);
|
||||
try {
|
||||
await runScheduledTaskNow(selectedProjectID, task.id);
|
||||
const { sessionId } = await runScheduledTaskNow(selectedProjectID, task.id);
|
||||
await Promise.all([
|
||||
reloadTasks(selectedProjectID, { silent: true }),
|
||||
refreshGlobalSessions(),
|
||||
]);
|
||||
toast.success(t('sessions.scheduledTasks.dialog.toast.started'));
|
||||
if (sessionId) {
|
||||
// Jump straight into the started session; selecting it also closes
|
||||
// this surface (MainLayout closes surfaces on session selection).
|
||||
const project = projects.find((entry) => entry.id === selectedProjectID);
|
||||
useSessionUIStore.getState().setCurrentSession(sessionId, project?.path ?? null);
|
||||
useUIStore.getState().setActiveMainTab('chat');
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(error instanceof Error ? error.message : t('sessions.scheduledTasks.dialog.toast.runFailed'));
|
||||
} finally {
|
||||
setMutatingTaskID(null);
|
||||
}
|
||||
}, [selectedProjectID, reloadTasks, t]);
|
||||
}, [selectedProjectID, projects, reloadTasks, t]);
|
||||
|
||||
const projectSelector = (
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
@@ -412,19 +413,16 @@ export function ScheduledTasksDialog() {
|
||||
setEditorOpen(true);
|
||||
};
|
||||
|
||||
const tasksContent = (
|
||||
<div className="space-y-4">
|
||||
{!isMobile ? (
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-end sm:justify-between">
|
||||
{projectSelector}
|
||||
<Button onClick={openNewTaskEditor} disabled={!selectedProjectID}>
|
||||
<Icon name="add" className="mr-1 h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.newTask')}
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
projectSelector
|
||||
)}
|
||||
const selectProject = (nextProjectID: string) => {
|
||||
setSelectedProjectID(nextProjectID);
|
||||
if (nextProjectID) {
|
||||
void reloadTasks(nextProjectID);
|
||||
} else {
|
||||
setTasks([]);
|
||||
}
|
||||
};
|
||||
|
||||
const tasksList = (
|
||||
<div className="min-h-[280px]">
|
||||
{loading ? (
|
||||
<div className="flex items-center gap-2 typography-meta text-muted-foreground">
|
||||
@@ -579,6 +577,12 @@ export function ScheduledTasksDialog() {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const tasksContent = (
|
||||
<div className="space-y-4">
|
||||
{projectSelector}
|
||||
{tasksList}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -613,18 +617,53 @@ export function ScheduledTasksDialog() {
|
||||
>
|
||||
{tasksContent}
|
||||
</MobileOverlayPanel>
|
||||
) : (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogContent className="max-h-[85vh] max-w-2xl overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('sessions.scheduledTasks.dialog.title')}</DialogTitle>
|
||||
<DialogDescription>{t('sessions.scheduledTasks.dialog.description')}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{tasksContent}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
) : open ? (
|
||||
// Full-page surface replacing the chat area (mounted inside <main>).
|
||||
// Master-detail: a scrollable project filter panel at the left, the
|
||||
// selected project's tasks at the right. The app Header shows the
|
||||
// surface title, so the page itself only carries the close affordance.
|
||||
<div className="absolute inset-0 z-10 flex flex-col bg-background">
|
||||
<div className="flex min-h-0 flex-1">
|
||||
<div className="flex w-60 flex-shrink-0 flex-col border-r border-border/50">
|
||||
<div className="flex-1 space-y-0.5 overflow-y-auto p-2">
|
||||
{projects.length === 0 ? (
|
||||
<div className="px-2 py-2 typography-meta text-muted-foreground">
|
||||
{t('sessions.scheduledTasks.dialog.project.empty')}
|
||||
</div>
|
||||
) : projects.map((project) => (
|
||||
<button
|
||||
key={project.id}
|
||||
type="button"
|
||||
onClick={() => selectProject(project.id)}
|
||||
className={cn(
|
||||
'flex w-full min-w-0 items-center rounded-md px-2 py-1.5 text-left typography-ui-label focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50',
|
||||
selectedProjectID === project.id
|
||||
? 'bg-interactive-selection text-foreground'
|
||||
: 'text-muted-foreground hover:bg-interactive-hover/50 hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{renderProjectLabel(project)}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
{/* Pages have no close button: you leave by picking a session,
|
||||
a draft, or another surface in the sidebar. */}
|
||||
<div className="flex items-center px-6 pt-3">
|
||||
<Button size="sm" onClick={openNewTaskEditor} disabled={!selectedProjectID}>
|
||||
<Icon name="add" className="mr-1 h-4 w-4" /> {t('sessions.scheduledTasks.dialog.actions.newTask')}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="min-h-0 flex-1 overflow-y-auto px-6 py-4">
|
||||
<div className="mx-auto w-full max-w-3xl">
|
||||
{tasksList}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<ScheduledTaskEditorDialog
|
||||
open={editorOpen}
|
||||
|
||||
Reference in New Issue
Block a user