mirror of
https://github.com/andrewyng/openworker.git
synced 2026-09-03 04:49:26 +00:00
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:
@@ -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 }) => {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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" ? (
|
||||
|
||||
Reference in New Issue
Block a user