feat(ui,desktop): an Inbox row reads as two lines, the last word in its thread under its title

Merged
DA
2d ago
+579-88

Summary

PR 1 of 2 for the Inbox list rows in docs/proposals/inbox-threads.md §3–§4 (approved by Dean). PR 2, unread and marking read, stacks on this one and on the service's read state.

  • Two lines per row (tasks/inbox-list.tsx). Line one has the agent's tile, the title, and the time. Line two is a muted preview of the last word in the thread (inboxPreview in tasks/inbox-model.ts):
    • the latest message on the thread: Agent: Backfilled 41,208 rows…, Charles Pan: …, or You: … (Sent 2 files where a message carried only files);
    • the answer, by who gave it: You: Per 1,000, rounded up, You: Approved · Wait for Monday;
    • Withdrawn: <reason>;
    • on an open item nobody has written on yet, what it asks: Choice · 3 options, one recommended, Form · 4 fields, or To-do, Approval, Question, or Note, followed by · <first words of its context> where it has context.
  • Needs you is the orange attention mark (attention-mark.tsx) before the time, on the reader's own open items only (needsReader). It replaces the blue .inbox-row-dot and the unread weight that used to mean needs you. It also replaces #3198's hollow ring on a teammate's open item, which keeps the teammate's face in that place. With no unread state yet, every title reads at the body weight. The dot, the semibold title, and the brighter preview come back meaning unread in PR 2.
  • The Needs you filter's glyph wears its dot in the attention orange (--luke-color-attention-fill). The group counts already followed needsReader.
  • Group by task, Newest first, and Group by person all still work. A person's group still names each row's task on line one instead of a face. Home's Inbox and a person's page draw the same row, so they pick up the preview and the mark too.
  • The placeholder rows shown while the Inbox is first read (#3608) are now two lines too, so the items land where the placeholders stood.
  • @luke/ui ListRow gains metaAt: LIST_ROW_META.TITLE. It puts the metadata at the end of the title's line, runs the description across the full width under both, and aligns the leading mark with line one. The default, MIDDLE, leaves every other ListRow's markup unchanged.
  • The Factory's own rows still use .inbox-row-dot for "waiting". This PR doesn't touch them.

One judgement call: on an open item that has messages, the preview shows the latest message rather than what the item asks, because that message is the last word in the thread. The attention mark still says it needs you.

Screenshots

Rendered from the jsdom fixtures with the built apps/desktop/dist/renderer/styles.css, captured with Playwright.

DarkLight
Inbox list, darkInbox list, light

Group by person, where each row names its task on line one:

Group by person, dark

Evidence

  • pnpm check:affected: lint, knip, typecheck, and the tests pass. The one failure is src/main/app-menu.test.ts, which can't run in the sandbox because it has no Electron binary. discarded-effect ran out of memory alongside the test run and passes when run alone (No discarded Effect, Stream, or Layer in 2276 files.).
  • pnpm exec vitest run on desktop-inbox, inbox-model, desktop-home, person-page, and list-row: all pass.
  • Test count: +8.
    • inbox-model.test.ts: +4 (the preview's words for open, answered, withdrawn, and thread states).
    • desktop-inbox.test.ts: +2 (the two-line row and its preview in each state; the attention mark on your own open item, a teammate's face and no mark or ring on theirs, and no unread weight).
    • desktop-home.test.ts: +1 (Home's row reads as the Inbox's).
    • list-row.test.ts: +1 (metadata on the title's line reads after the title and before the description).
  • desktop-inbox.test.ts's needsYouMark now asserts the attention mark and its "Needs you" words, and fails if a row still wears .inbox-row-dot.

Invariants

  • ./scripts/check.sh green. (CI)
  • JSON Schema goldens unchanged (LUKE_UPDATE_FIXTURES not run, or diff explained line by line). (CI)
  • Every behavior change has, in this PR, the test that fails without it; no change-detector test added. (manual; root AGENTS.md "Testing")
  • Gateway envelope goldens unchanged. (CI)
  • No new as type assertion outside the allowlisted files. (CI via anti-slop)
  • No Effect.runPromise/runSync/runFork outside the runtime edges and the shims tools/anti-slop/effect-edges.json records. (CI via anti-slop/no-run-promise-outside-edges)
  • No new setTimeout/setInterval/new Promise/AbortController/fs.watch outside the files tools/anti-slop/effect-edges.json records. (CI via anti-slop/no-raw-async-primitives)
  • No Effect, Stream, or Layer written as a statement and dropped; a description a caller walks past runs nothing. (CI via pnpm discarded-effect)
  • Test count equals the pre-PR count or the delta is listed. (manual: vitest run --reporter=json)
  • Each hand-rolled file the PR replaces is deleted, or the part that remains is a permanent adaptor named in root AGENTS.md's "The permanent adaptors" with the reason it stays.
  • Every CLAUDE.md/AGENTS.md sentence naming a changed part is edited; root pair stays byte-identical. (CI for pair existence; manual for wording)
  • PRIVACY.md unchanged, or the change is a product decision called out in the PR body.
  • Package package.json deps match what the sources import by bare specifier; effect via catalog:. (CI)
  • Barrel/door rule: no Node-reaching Effect module (@effect/platform-node, effect/sql) behind a barrel the renderer or a web function opens. (CI: existing renderer node: grep plus a new grep for those specifiers in renderer bundles)

Notes

  • Blockers or follow-up verification: none. Session replay masks the preview's words like every other row's.

Made with Luke


Devin Review


Summary by cubic

Makes each Inbox row read as two lines, previewing the last word in its thread under its title, and moves "needs you" to an orange attention mark.

  • The preview shows the latest thread message by who wrote it, an answer by who gave it, "Withdrawn: ", or what an open item asks.
  • The attention mark stands on the reader's own open items only, replacing the blue dot, the unread weight, and the hollow ring a teammate's open item wore; the teammate's face keeps a hidden "Waiting on " for a screen reader.
  • The Needs you filter's glyph now wears its dot in the attention orange.
  • @luke/ui ListRow gains LIST_ROW_META.TITLE to end its metadata on the title's line and run the preview under both.