diff --git a/AGENTS.md b/AGENTS.md index 5c32a4fc9..37d83ae9c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,6 +10,7 @@ - Keep agent, model, and thinking controls in the composer footer via `PromptContextControls`; adapt that footer with the named `prompt-composer` container rather than viewport-only breakpoints. - Session rows keep actions inline until their measured title, badges, and controls no longer fit. Keep responsive action styles in `styles/components/session-row-actions.css`; hidden inline controls remain measurable but inert, and an open overflow menu stays mounted until dismissal. - Session hierarchy geometry lives in `styles/components/session-tree.css`; connector axes follow the parent expander at every depth, including selection mode, RTL and touch layouts. +- Session search/filter mode uses flat per-session results with an optional subsession switch; filters, sorting, worktree badges and selection use each result's own identity. Normal browsing retains the session hierarchy. - Never use rounded corners in UI styling; keep corners square unless the user explicitly requests otherwise for a specific change. - Explicit round exceptions: Yolo and MCP switches (shared `styles/components/switches.css` geometry), overlay drawer navigation buttons, and floating message scroll buttons. Other chrome remains square. - Tags and numeric/context/token labels also use rounded geometry via `--chip-radius` (`--pill-radius` is an alias). Register badge variants in `styles/components/badges.css`; use `.badge-shape` for utility-styled labels rather than adding a local radius. diff --git a/dev-docs/WORKTREE_SESSION_PLACEMENT.md b/dev-docs/WORKTREE_SESSION_PLACEMENT.md index ab1052d38..d816c06e2 100644 --- a/dev-docs/WORKTREE_SESSION_PLACEMENT.md +++ b/dev-docs/WORKTREE_SESSION_PLACEMENT.md @@ -174,6 +174,28 @@ authorized workspace spelling before changing UI state. ### Final native/UI integration (2026-09-17) +Follow-up: search/filter mode presents independent flat rows. The "Show subsessions" +switch (off by default) includes children without requiring their parents in the +results. Text and worktree filters must match the same session; activity, names and +worktree labels also sort each session independently. Selection targets only the +displayed matches. Server search results no longer require ancestor hydration. +Closing search restores normal hierarchy and ignores its worktree filter. This +supersedes the temporary root-only worktree-filter fix in `3921ba08`. + +The real-component browser regression covers the switch, cross-worktree children, +text plus directory filters, independent badges, bulk selection, direct child +navigation, results without loaded ancestors and returning to normal hierarchy. + +The rebuilt UI was also loaded in the installed Windows Tauri renderer. An +existing child, "Analyze automation update", was verified through native reads +in `D:\CodeNomad` while its parent remained in `pr649-final`. With its title and +the Workspace filter selected, the child appeared alone only when subsessions +were enabled. Selecting the parent's checkout hid it; closing search restored +the hierarchy and preserved the active parent conversation. A first desktop wait +of 30 seconds expired; the completed repeat used a 90-second bound. Directory +search still traverses the local worktree catalogue, so this is not a latency +guarantee. No native session locations were changed by this check. + Merged `dev@e47e01c6` (PR #697) into this branch. Discovery and canonical `server.status()` adaptation are now inherited from that independently merged change. The native worktree/family fixture passed again against isolated official diff --git a/packages/ui/src/components/session-list.tsx b/packages/ui/src/components/session-list.tsx index c86414cc1..22ec22802 100644 --- a/packages/ui/src/components/session-list.tsx +++ b/packages/ui/src/components/session-list.tsx @@ -32,11 +32,11 @@ import { clearSessionSearch, fetchSessions, getSessionSearchQuery, - getSessionSearchThreads, + getSessionSearchSessions, isSessionSearchLoading, } from "../stores/sessions" import { getGitRepoStatus, getWorktreeSlugForParentSession, getWorktrees } from "../stores/worktrees" -import { collectSessionThreadIds, findSessionThread, flattenVisibleSessionThreads, projectSessionFamilies, sortSessionIdsDeepestFirst, type SessionFamilySort } from "../stores/session-tree" +import { collectSessionThreadIds, findSessionThread, flattenVisibleSessionThreads, projectSessionFamilies, projectSessionSearchResults, sortSessionIdsDeepestFirst, type SessionFamilySort } from "../stores/session-tree" import { normalizeSessionDirectory } from "../stores/session-list-options" import { getLogger } from "../lib/logger" import { copyToClipboard } from "../lib/clipboard" @@ -72,6 +72,7 @@ const SessionList: Component = (props) => { const [filterQuery, setFilterQuery] = createSignal("") const [sortBy, setSortBy] = createSignal("activity") const [worktreeDirectory, setWorktreeDirectory] = createSignal("") + const [includeSubsessions, setIncludeSubsessions] = createSignal(false) const normalizedQuery = createMemo(() => (props.enableFilterBar ? filterQuery().trim().toLowerCase() : "")) let failedSortExhaustion: string | undefined @@ -133,7 +134,7 @@ const SessionList: Component = (props) => { createEffect(() => { const sort = sortBy() const key = `${props.instanceId}:${sort}` - if (sort === "activity") { + if (sort === "activity" && !props.enableFilterBar) { failedSortExhaustion = undefined return } @@ -222,20 +223,26 @@ const SessionList: Component = (props) => { const filteredThreads = createMemo(() => { const query = normalizedQuery() - const searchThreads = query && getSessionSearchQuery(props.instanceId) === query && !isSessionSearchLoading(props.instanceId) - ? getSessionSearchThreads(props.instanceId) - : props.threads + const hasSearchResults = query && getSessionSearchQuery(props.instanceId) === query && !isSessionSearchLoading(props.instanceId) const worktrees = getWorktrees(props.instanceId) const getWorktreeLabel = (directory: string) => { const normalized = normalizeSessionDirectory(directory) const worktree = worktrees.find((candidate) => normalizeSessionDirectory(candidate.serviceDirectory ?? candidate.directory) === normalized) return worktree?.kind === "root" ? t("sessionList.worktree.workspace") : worktree?.label ?? worktree?.slug ?? directory } - return projectSessionFamilies(searchThreads, { + if (!props.enableFilterBar) return projectSessionFamilies(props.threads, { sort: sortBy(), getWorktreeLabel }) + const instanceSessions = sessionStateSessions().get(props.instanceId) + const candidates = hasSearchResults ? getSessionSearchSessions(props.instanceId) + : collectSessionThreadIds(props.threads).flatMap(id => { + const session = instanceSessions?.get(id) + return session ? [session] : [] + }) + return projectSessionSearchResults(candidates, { sort: sortBy(), worktreeDirectory: worktreeDirectory(), + includeSubsessions: includeSubsessions(), getWorktreeLabel, - ...(query && searchThreads === props.threads + ...(query && !hasSearchResults ? { matchesSession: (session) => sessionMatchesQuery(session.id, query) } : {}), }) @@ -557,6 +564,7 @@ const SessionList: Component = (props) => { }> = (rowProps) => { const sessionId = () => rowProps.session.id const isChild = () => rowProps.depth > 0 + const isSubsession = () => Boolean(rowProps.session.parentId) const worktreeSlug = createMemo(() => { if (isChild()) return "" @@ -686,7 +694,7 @@ const SessionList: Component = (props) => { return (
= (props) => { title={title()} aria-current={isActive() ? "true" : undefined} > -
+ + 0}>