Skip to content
Merged
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
2 changes: 1 addition & 1 deletion FEATURE_MATRIX.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ OMP authority: `packages/coding-agent/src/session/agent-session.ts`, `session-ma

| Capability | Source command/state | Desktop behavior | T3 reference | Priority |
|---|---|---|---|---|
| List/recent/search/filter | session store and metadata | Virtualized left rail; project/host grouping; running/waiting/failed/unread badges; fuzzy search | `Sidebar.tsx`, sidebar logic/tests | Launch |
| List/recent/search/filter | session store and metadata | Bounded left rail; folder or flat organization; Priority, Last updated, or Manual order; title/folder/host search; attention/running/unread/error filters; pinned shortcuts; running/waiting/failed/unread badges | `Sidebar.tsx`, sidebar logic/tests | Launch |
| New session | `/new` | Create in selected project/host; model/profile defaults visible before first prompt | draft routes and composer draft store | Launch |
| Fast switch and tabs | session IDs and snapshots | One-click/keyboard switch; preserve draft, scroll anchor, panel widths/tabs, terminal focus; no white flash | T3 routes, `composerDraftStore`, `rightPanelStore`, terminal store | Launch |
| Resume | `/resume` | Open existing session by stable ID/path; recover moved/missing files with explicit error | thread routing and reconnect supervisor | Launch |
Expand Down
15 changes: 14 additions & 1 deletion apps/site/src/docs/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,20 @@ const localSessions: DocTopic = {
},
{
kind: "note",
text: "Removing a shortcut is view state for that T4 Code client, so desktop and phone can differ. Archived sessions remain visible, and their folder menu can restore the shortcut. A new or restored Current session always makes the folder visible. T4 Code still does not alias, pin, reorder, delete, or rename the underlying folder.",
text: "Removing, pinning, or manually ordering shortcuts is view state for that T4 Code client, so desktop and phone can differ. Archived sessions remain visible, and their folder menu can restore a removed shortcut. A new or restored Current session always makes the folder visible. T4 Code never moves, deletes, or renames the underlying folder.",
},
{ kind: "h2", id: "local-sessions-organize", text: "Organize a large session library" },
{
kind: "p",
text: "Use the rail's organization menu to group sessions by working folder or combine them into one list. Sort by **Priority**, **Last updated**, or **Manual order**. Priority puts approval requests, questions, running work, unread completions, errors, and plans ahead of ordinary recent work in that order.",
},
{
kind: "p",
text: "The rail search matches session titles, models, working folders, and host names. Quick filters narrow the list to Attention, Running, Unread, or Errors. Search and filters reset when T4 Code restarts so an old filter never makes work look missing.",
},
{
kind: "p",
text: "Pin a session or working folder from its action menu to create a shortcut in the Pinned section. In Manual order, the same menus provide Move up and Move down actions. Large folders initially draw a bounded set of rows; **Show more** reveals the next set without forcing the entire library into the page at once.",
},
{
kind: "h2",
Expand Down
115 changes: 109 additions & 6 deletions apps/web/src/components/AppShell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export function AppShell() {
const railOverlayOpen = useWorkspace((state) => state.railOverlayOpen);
const focusMode = useWorkspace((state) => state.focusMode);
const sessionListView = useWorkspace((state) => state.sessionListView);
const railSort = useWorkspace((state) => state.railSort);
const railQuery = useWorkspace((state) => state.railQuery);
const railFilter = useWorkspace((state) => state.railFilter);
const projectManualOrder = useWorkspace((state) => state.projectManualOrder);
const sessionManualOrderByProjectId = useWorkspace(
(state) => state.sessionManualOrderByProjectId,
);
const projectExpandedById = useWorkspace((state) => state.projectExpandedById);
const dismissedEmptyProjectIds = useWorkspace((state) => state.dismissedEmptyProjectIds);
const lastVisitedAtBySessionId = useWorkspace((state) => state.lastVisitedAtBySessionId);
Expand All @@ -52,12 +59,95 @@ export function AppShell() {
lastVisitedAtBySessionId,
"current",
dismissedEmptyProjectIds,
{
filter: railFilter,
query: railQuery,
sort: railSort,
projectManualOrder,
sessionManualOrderByProjectId,
},
),
[shellData, projectExpandedById, lastVisitedAtBySessionId, dismissedEmptyProjectIds],
[
shellData,
projectExpandedById,
lastVisitedAtBySessionId,
dismissedEmptyProjectIds,
railFilter,
railQuery,
railSort,
projectManualOrder,
sessionManualOrderByProjectId,
],
);
const archivedGroups = useMemo(
() => buildProjectGroups(shellData, projectExpandedById, lastVisitedAtBySessionId, "archived"),
[shellData, projectExpandedById, lastVisitedAtBySessionId],
() =>
buildProjectGroups(
shellData,
projectExpandedById,
lastVisitedAtBySessionId,
"archived",
{},
{
filter: railFilter,
query: railQuery,
sort: railSort,
projectManualOrder,
sessionManualOrderByProjectId,
},
),
[
shellData,
projectExpandedById,
lastVisitedAtBySessionId,
railFilter,
railQuery,
railSort,
projectManualOrder,
sessionManualOrderByProjectId,
],
);
const allCurrentGroups = useMemo(
() =>
buildProjectGroups(
shellData,
projectExpandedById,
lastVisitedAtBySessionId,
"current",
dismissedEmptyProjectIds,
{ sort: railSort, projectManualOrder, sessionManualOrderByProjectId },
Comment thread
wolfiesch marked this conversation as resolved.
),
[
shellData,
projectExpandedById,
lastVisitedAtBySessionId,
dismissedEmptyProjectIds,
railSort,
projectManualOrder,
sessionManualOrderByProjectId,
],
);
const allArchivedGroups = useMemo(
() =>
buildProjectGroups(
shellData,
projectExpandedById,
lastVisitedAtBySessionId,
"archived",
{},
{ sort: railSort, projectManualOrder, sessionManualOrderByProjectId },
),
[
shellData,
projectExpandedById,
lastVisitedAtBySessionId,
railSort,
projectManualOrder,
sessionManualOrderByProjectId,
],
);
const allSessionGroups = useMemo(
() => [...allCurrentGroups, ...allArchivedGroups],
[allArchivedGroups, allCurrentGroups],
);
const groups = sessionListView === "archived" ? archivedGroups : currentGroups;
const currentCount = shellData.sessions.filter(
Expand Down Expand Up @@ -137,7 +227,9 @@ export function AppShell() {
} else if (action.kind === "toggle-terminal") {
const activeId = state.activeSessionId;
const activeSession =
activeId === null ? undefined : getShellData().sessions.find((session) => session.id === activeId);
activeId === null
? undefined
: getShellData().sessions.find((session) => session.id === activeId);
if (activeId !== null && activeSession?.archivedAt === undefined) {
const view = selectSessionView(state, activeId);
if (state.focusMode) {
Expand All @@ -159,6 +251,13 @@ export function AppShell() {
state.lastVisitedAtBySessionId,
state.sessionListView,
state.dismissedEmptyProjectIds,
{
filter: state.railFilter,
query: state.railQuery,
sort: state.railSort,
projectManualOrder: state.projectManualOrder,
sessionManualOrderByProjectId: state.sessionManualOrderByProjectId,
},
),
);
const sessionId = visible[action.index];
Expand Down Expand Up @@ -220,7 +319,7 @@ export function AppShell() {
<div className="h-full" style={{ width: RAIL_COLLAPSED_WIDTH }}>
<CollapsedRail
attentionCount={attentionCount}
groups={currentGroups}
groups={allCurrentGroups}
onExpand={(projectId) => {
const state = workspaceStore.getState();
state.setRailCollapsed(false);
Expand All @@ -231,12 +330,14 @@ export function AppShell() {
) : (
<div className="flex h-full flex-col" style={{ width: effectiveRailWidth }}>
<Rail
allGroups={allCurrentGroups}
attentionCount={attentionCount}
archivedCount={archivedCount}
currentCount={currentCount}
groups={groups}
hiddenEmptyProjectIds={hiddenEmptyProjectIds}
nowMs={nowMs}
pinnedSessionGroups={allSessionGroups}
view={sessionListView}
/>
</div>
Expand Down Expand Up @@ -285,20 +386,22 @@ export function AppShell() {
</div>
<div className="min-h-0 flex-1">
<Rail
allGroups={allCurrentGroups}
attentionCount={attentionCount}
archivedCount={archivedCount}
currentCount={currentCount}
groups={groups}
hiddenEmptyProjectIds={hiddenEmptyProjectIds}
nowMs={nowMs}
pinnedSessionGroups={allSessionGroups}
view={sessionListView}
/>
</div>
</SheetPopup>
</Sheet>
)}

<CommandPalette groups={currentGroups} />
<CommandPalette groups={allCurrentGroups} />
</div>
);
}
Loading
Loading