feat(ui,desktop): an Inbox row reads as two lines, the last word in its thread under its title
Merged
DA
2d ago+579+579+012345678901234567890123456789-88-88-01234567890123456789
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 (inboxPreviewintasks/inbox-model.ts):- the latest message on the thread:
Agent: Backfilled 41,208 rows…,Charles Pan: …, orYou: …(Sent 2 fileswhere 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, orTo-do,Approval,Question, orNote, followed by· <first words of its context>where it has context.
- the latest message on the thread:
- 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-dotand 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 followedneedsReader. - 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/uiListRow gainsmetaAt: 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-dotfor "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.
| Dark | Light |
|---|---|
![]() | ![]() |
Group by person, where each row names its task on line one:

Evidence
pnpm check:affected: lint, knip, typecheck, and the tests pass. The one failure issrc/main/app-menu.test.ts, which can't run in the sandbox because it has no Electron binary.discarded-effectran out of memory alongside the test run and passes when run alone (No discarded Effect, Stream, or Layer in 2276 files.).pnpm exec vitest runondesktop-inbox,inbox-model,desktop-home,person-page, andlist-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'sneedsYouMarknow asserts the attention mark and its "Needs you" words, and fails if a row still wears.inbox-row-dot.
Invariants
-
./scripts/check.shgreen. (CI) - JSON Schema goldens unchanged (
LUKE_UPDATE_FIXTURESnot 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
astype assertion outside the allowlisted files. (CI via anti-slop) - No
Effect.runPromise/runSync/runForkoutside the runtime edges and the shimstools/anti-slop/effect-edges.jsonrecords. (CI viaanti-slop/no-run-promise-outside-edges) - No new
setTimeout/setInterval/new Promise/AbortController/fs.watchoutside the filestools/anti-slop/effect-edges.jsonrecords. (CI viaanti-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.mdsentence naming a changed part is edited; root pair stays byte-identical. (CI for pair existence; manual for wording) -
PRIVACY.mdunchanged, or the change is a product decision called out in the PR body. - Package
package.jsondeps match what the sources import by bare specifier;effectviacatalog:. (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 renderernode: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
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/uiListRow gainsLIST_ROW_META.TITLEto end its metadata on the title's line and run the preview under both.

