gui: fold Slack-origin sessions into Recent — drop the From Slack band

Mention-spawned sessions now list chronologically like any other session, in
both layouts; the row's origin icon (with the channel · workspace tooltip)
carries the provenance. The collapsed band hid fresh mentions below week-old
sessions. UX-DECISIONS §31 revised.
This commit is contained in:
Rohit C Prasad
2026-07-21 12:03:37 -07:00
parent d56a15bf44
commit 52afd9c694
3 changed files with 23 additions and 61 deletions
+12 -14
View File
@@ -7,12 +7,13 @@ import { test, expect } from "./fixtures";
test("session list caps at the peek count with Show more", async ({ page }) => {
await page.goto("/");
// Boot resumes a cowork session, so the Coworker accordion body is expanded.
// Boot resumes a cowork session, so the Coworker accordion body is expanded. The body holds
// 8 sessions (7 weekly plans + the Slack-origin one, §31 rev) against sessions_peek=5.
await expect(page.getByTitle("Weekly plan 1")).toBeVisible();
await expect(page.getByTitle("Weekly plan 5")).toBeVisible();
await expect(page.getByTitle("Weekly plan 6")).toHaveCount(0);
await page.getByRole("button", { name: "Show more (2)" }).click();
await page.getByRole("button", { name: "Show more (3)" }).click();
await expect(page.getByTitle("Weekly plan 6")).toBeVisible();
await expect(page.getByTitle("Weekly plan 7")).toBeVisible();
});
@@ -42,26 +43,23 @@ test("archive via the row menu is reversible via the Archived disclosure", async
await expect(page.getByTitle("Weekly plan 2")).toBeVisible();
});
test("mention-spawned sessions collapse under From Slack with the platform icon (§31)", async ({
test("mention-spawned sessions list in Recent with the platform icon — no From Slack band (§31 rev)", async ({
page,
}) => {
// Flat chronological layout — the launch default (personas off).
await page.route("**/v1/settings", (r) => r.fulfill({ json: { nav_layout: "flat" } }));
await page.goto("/");
await expect(page.getByTitle("Weekly plan 1")).toBeVisible();
// Collapsed by default with a count; the session row hidden until expanded…
const toggle = page.getByTestId("from-slack-toggle");
await expect(toggle).toContainText("From Slack (1)");
await expect(page.getByTitle("#general — check the deploy?")).toHaveCount(0);
await toggle.click();
// No collapsed band; the session sits directly in Recent, exactly once (its fixture
// timestamp sorts it past the peek cap, so expand first)…
await expect(page.getByTestId("from-slack-toggle")).toHaveCount(0);
await page.getByText(/Show \d+ more/).click();
const row = page.getByTitle("#general — check the deploy?");
await expect(row).toBeVisible();
// …wearing the Slack logo (hover-hidden cluster, so assert attachment not visibility)…
await expect(
page.getByTestId("from-slack-list").locator('[data-logo="slack"]'),
).toHaveCount(1);
// …and never duplicated into any other list.
await expect(page.getByTitle("#general — check the deploy?")).toHaveCount(1);
// …wearing the Slack logo (hover-hidden cluster, so assert attachment not visibility).
await expect(row.locator('[data-logo="slack"]')).toHaveCount(1);
});
test("pin via the row menu moves the session to the Pinned band and back", async ({ page }) => {
+6 -12
View File
@@ -179,7 +179,7 @@ describe("From Slack group (§31)", () => {
origin_label: "#general · T0AB",
};
it("mention-spawned sessions collapse under From Slack with the platform icon, out of Recent", async () => {
it("mention-spawned sessions list chronologically in Recent with the platform icon (no band)", async () => {
stubFetch([
{ match: "/v1/personas", method: "GET", json: PERSONAS },
{ match: "/v1/settings", method: "GET", json: { nav_layout: "flat" } },
@@ -187,20 +187,14 @@ describe("From Slack group (§31)", () => {
render(<Sidebar {...baseProps} sessions={[...SESSIONS, SLACK_SESSION]} />);
await screen.findByText("incident watch"); // flat Recent rendered
// Collapsed by default: the header shows a count, the row itself is hidden
const toggle = screen.getByTestId("from-slack-toggle");
expect(toggle.textContent).toContain("From Slack (1)");
expect(screen.queryByText("#general — check the deploy?")).toBeNull();
// …and the session does NOT duplicate into the chronological Recent list.
fireEvent.click(toggle);
// No collapsed band — the session sits directly in the Recent list, exactly once
expect(screen.queryByTestId("from-slack-toggle")).toBeNull();
const row = await screen.findByText("#general — check the deploy?");
expect(screen.getAllByText("#general — check the deploy?")).toHaveLength(1);
// The row wears the Slack logo, right-aligned in the indicator cluster.
const list = screen.getByTestId("from-slack-list");
expect(list.querySelector('[data-logo="slack"]')).toBeTruthy();
expect(row).toBeTruthy();
// wearing the Slack logo in the row's indicator cluster.
const cluster = row.closest(".group");
expect(cluster?.querySelector('[data-logo="slack"]')).toBeTruthy();
});
});
+5 -35
View File
@@ -339,15 +339,9 @@ export function Sidebar(props: Props) {
const pinnedSessions = props.sessions.filter(
(s) => s.pinned && !s.session_id.startsWith("__") && !s.archived,
);
// §31: mention-spawned sessions gather in one cross-persona "From Slack" group, collapsed by
// default (owner call 2026-07-13; no auto-archive — a future global setting). Pinned wins.
const slackSessions = props.sessions
.filter(
(s) =>
s.origin === "slack" && !s.archived && !s.pinned && !s.session_id.startsWith("__"),
)
.sort((a, b) => (b.updated_at || "").localeCompare(a.updated_at || ""));
const [slackOpen, setSlackOpen] = useState(false);
// §31 (revised 2026-07-21): mention-spawned sessions list chronologically in Recent like any
// other session — the OriginIcon in the row's indicator cluster marks where they came from.
// The separate collapsed "From Slack" band hid fresh mentions below week-old sessions.
// A row in the account menu (§26): closes the menu, then runs the destination.
const appMenuItem = (
icon: IconName,
@@ -411,9 +405,7 @@ export function Sidebar(props: Props) {
// EXCLUDED here: they live in the cross-persona Pinned band only, so they don't repeat inside the
// persona group / project list (matching the flat layout's Recent, which also drops pinned).
const all = props.sessions.filter((s) => s.agent === browseKey && !s.session_id.startsWith("__"));
// Origin (mention-spawned) sessions live in the cross-persona "From Slack" band, not the
// persona lists — pinned still wins (Pinned band), and archived keeps the persona disclosure.
const mine = all.filter((s) => !s.archived && !s.pinned && !s.origin);
const mine = all.filter((s) => !s.archived && !s.pinned);
const archived = all.filter((s) => s.archived);
// Only PROJECT-SCOPED personas group sessions by project (git-bound Code, project-bound Ops).
// Scratch/deliverable conversations are orphan (each has its own per-conversation scratch dir),
@@ -429,7 +421,7 @@ export function Sidebar(props: Props) {
// Recent = every non-pinned, non-archived, real session across ALL personas, newest first
// (by updated_at; missing timestamps keep store order), search-filtered. Drives the flat layout.
const recentSessions = [...props.sessions]
.filter((s) => !s.archived && !s.session_id.startsWith("__") && !s.pinned && !s.origin)
.filter((s) => !s.archived && !s.session_id.startsWith("__") && !s.pinned)
.filter((s) => personaVisible(s.agent))
.filter(matches)
.sort((a, b) => (b.updated_at || "").localeCompare(a.updated_at || ""));
@@ -705,27 +697,6 @@ export function Sidebar(props: Props) {
</div>
) : null;
// §31: the collapsed cross-persona "From Slack" band — mention-spawned sessions, both layouts.
// Disclosure shape mirrors the persona body's Archived row.
const fromSlackBand = () =>
slackSessions.length > 0 ? (
<div>
<button
className="w-full flex items-center gap-1.5 px-1.5 py-1 rounded text-[10.5px] uppercase tracking-[0.07em] text-faint font-semibold hover:text-muted"
onClick={() => setSlackOpen((v) => !v)}
data-testid="from-slack-toggle"
>
<Icon name={slackOpen ? "chevronDown" : "chevronRight"} size={12} className="shrink-0" />
From Slack ({slackSessions.length})
</button>
{slackOpen && (
<div className="space-y-0.5 mt-0.5" data-testid="from-slack-list">
{slackSessions.map((s) => cardRow(s))}
</div>
)}
</div>
) : null;
// RECENT header with the group/filter control (§20) — the group toggle moved off the brand bar.
// "Group by" flips the persona accordion ↔ chronological list; "Filter by coworker" narrows to
// the checked personas (none checked = all shown).
@@ -1075,7 +1046,6 @@ export function Sidebar(props: Props) {
<div className="space-y-4">
{pinnedBand()}
{scheduledBand()}
{fromSlackBand()}
<div>
{recentHeader()}
{layout === "grouped" ? (