ConversationEmailRow.tsxfeat: v0 smart & conversation inboxApr 21, 2026, 11:30 PMConversationGroup.tsxfeat: v0 smart & conversation inboxApr 21, 2026, 11:30 PMConversationInboxLayout.tsxfix(artifacts): open the chat instead of reciting its id, and never leave a blank screen A LinkedIn chat opened from a context chip drew a card with the counterpart's name over the raw chat id — a screen with no message on it and no way forward. Nothing outside the inbox route could turn a chat id into the feed row the chat view renders from, so the panel had only the chip's kind and id, and drew those. `inbox.channelItem` reads one container by the provider's key and maps it through the feed's own normalizers, with none of the feed's list filters: done, snoozed and quarantined are rules about what belongs in a LIST, and you have already clicked this chat. One resolver — feed row, container read, then whatever opened it left behind — now backs every surface, so Slack, LinkedIn and WhatsApp are openable from all of them or from none. The label card is gone. The same audit turned up three more dead ends. The overlay hosts hide the whole page behind an artifact flag, so a branch that resolves to null is a white screen with nothing to click: `isThreadOpen` is derived from the artifact while the branch read `selectedThreadId`, which every chat-side writer leaves untouched. ActiveViewDisplay now takes each id off the artifact itself, and ArtifactDeadEnd is the floor under all of it. `isSlackThreadOpen` becomes `isChannelChatOpen` and covers all three channels — a LinkedIn artifact used to flip no flag at all, so /tasks and /inbox rendered straight past it. And the home panel says so when its one query fails, rather than staying blank for the session. Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 26, 2026, 11:36 PMConversationTaskRow.tsxfix(tasks): a tick on an already-resolved task stops looping (design: zach-sept10-bugs phase 1) Zach, 2026-09-10: "i keep checking this off and it comes back." It was a closed loop. The Overview renders Due Tasks off the crm.getConversation cache and filters status === 'todo', so a row the server has already resolved can still be on screen. Ticking it reached completeTask, which refused anything not `todo`; the tRPC route THREW that refusal; the client's onError rolled the row back — restoring the very row the user was clearing. Every attempt re-rendered it, so the tick could never converge. Measured: tasks ad0fd8a4 and 5fbee194 went status='deleted' at 17:07:32-34 UTC, and the screenshot 8 seconds later still lists both as open. Two halves, because they fail independently: - completeTask treats `deleted`/`agent_deleted` as a terminal SUCCESS, the way `done` always was, and reports which state via `alreadyResolved`. The status is never rewritten — a deleted task must not come back as done — and the draft cleanup does not re-run, since deleteTask already tidied it. - The client refetches before it restores, so a row that is genuinely gone stays gone. `staleTime: 0` on that refetch is load-bearing: this same function has already optimistically written `done` into that cache, so a fetch allowed to serve cached data would read back its own write and drop tasks that are still open. A test caught exactly that. ConversationTaskRow refreshed only conversationInbox.list, leaving the other three userTasks-carrying reads stale; it now invalidates all four like the main path, and refetches on error. Headless proof, all five statuses against the real service: pnpm --filter @zero/server task-complete-probe PASS todo → completed | done → done | deleted → deleted | agent_deleted → agent_deleted | recommended → refused Two pre-existing tests asserted the old contract; the deleted one was pinning the bug itself, and is rewritten to keep the half that still holds (no draft cleanup). Co-Authored-By: Claude Opus 5 (1M context) <<email>>Sep 10, 2026, 8:18 PM