Skip to content

fix: clear unread marker when messages are read - #294

Merged
Astro-Han merged 10 commits into
mainfrom
opencode/ui-polish
Jun 26, 2026
Merged

fix: clear unread marker when messages are read#294
Astro-Han merged 10 commits into
mainfrom
opencode/ui-polish

Conversation

@Astro-Han

@Astro-HanAstro-Han commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Clear a session unread marker persistently when the renderer reads that session messages.
  • Keep SessionManager.getMessages() as a pure query so thread search and the open gateway do not mark sessions read.
  • Optimistically clear the renderer sidebar row after active-session message loads so the unread dot disappears immediately.
  • Preserve local read state across stale sessions:list responses and stale local read results while allowing newer messages to become unread again.
  • Apply latest-wins ordering to concurrent session-list refreshes so older list responses cannot hide newer unread output.
  • Mark sessions read atomically only through the effective message timestamp actually read, so output appended before or during mark-read remains unread.
  • Delay final message refresh broadcasts until after runtime iterator drain/finalize, so active sessions can clear the final hasUnread: true write.

Why

Unread dots should mean there is agent output the user has not viewed yet. Opening a session is the read boundary; background reads from search or the open gateway must not mutate persisted unread state. Final refreshes must happen after runtime finalization, and older list/read responses or non-atomic persisted writes must not overwrite newer unread state.

Scope

Changed:

  • Added store-level markSessionReadThrough(sessionId, readThroughTs) so the latest header and visible message timestamps are read and conditionally written inside one queued storage operation.
  • Added SessionManager.markSessionRead(sessionId, readThroughTs) for the explicit persisted read boundary.
  • Wired sessions:readMessages IPC to mark the session read after a successful message read, using the max ts returned by that read and without swallowing persistence failures.
  • Updated renderer local session state after active-session message reads.
  • Added renderer read-boundary tracking so late stale list responses and late stale read results cannot clear unread for newer messages.
  • Added latest-wins ordering for concurrent session-list refreshes.
  • Kept shared getMessages() pure for thread search/open gateway reads.
  • Moved final message-appended refresh broadcasts in the normal stream path to after iterator drain.
  • Kept bot permission handoff replies immediate, while terminal bot errors drain before returning.
  • Removed the unused isFinalSessionEvent() helper and narrowed brittle renderer source-shape assertions.
  • Avoided unnecessary session-list array copies when read overrides do not change anything.
  • Added runtime, storage, and desktop behavior coverage for mark-read behavior, atomic persisted read-through, stale header timestamp handling, read-boundary isolation, stale-list ordering, stale local-read ordering, latest-wins list refreshes, and final-refresh ordering.

Not included:

Verification

  • apps/desktop$ npx tsx --test src/main/__tests__/permission-response-ipc-boundary.test.ts
  • apps/desktop$ npx tsx --test src/main/__tests__/session-message-lifecycle-contract.test.ts
  • apps/desktop$ npx tsx --test src/main/__tests__/session-read-state.test.ts
  • npx tsx --test apps/desktop/src/main/__tests__/session-open-routing-contract.test.ts
  • npx tsx --test packages/runtime/src/__tests__/session-manager.test.ts
  • npx tsx --test packages/runtime/src/__tests__/runtime-event-read-model.test.ts
  • npx tsx --test packages/storage/src/__tests__/session-store.test.ts
  • npm --workspace @maka/runtime run build
  • npm --workspace @maka/storage run build
  • npm run typecheck --workspaces --if-present
  • git diff --check

User-facing impact

Opening a session now clears its unread marker and persists that state across restarts only for messages actually read. Background search and open-gateway reads keep unread state intact. Active sessions should not show stale unread markers, and background/newer output should not be hidden by stale header timestamps, older session-list, local-read, or persisted mark-read races. No CHANGELOG.md, docs, breaking changes, or migrations.

Reviewer notes

  • Claude review found one P1 boundary issue in the initial implementation: getMessages() was clearing unread for shared read paths. Fixed by moving persisted mark-read to sessions:readMessages IPC.
  • Claude re-review found no P0/P1/P2 issues.
  • Fresh-eye subagent review found a finalization race where active sessions could be written back to unread after an early final refresh. Fixed by broadcasting final message-appended after iterator drain/finalize.
  • Fresh-eye subagent follow-up found bot permission handoff would regress if it waited for drain. Fixed by preserving immediate permission handoff replies while still draining terminal bot errors before returning.
  • Fresh-eye final gate review found no P0/P1/P2 issues.
  • Review follow-up fixed a stale-list read-boundary race, stopped swallowing mark-read persistence failures, and removed a dead final-event helper.
  • Follow-up fixed concurrent sessions.list() latest-wins ordering, added mark-read read-through protection, and replaced brittle renderer source-shape assertions with behavior tests for the list refresh race.
  • Follow-up fixed the renderer local-read race where an older readMessages() result could clear unread for newer output.
  • Follow-up fixed the persisted mark-read race by moving read-through comparison into the storage queued write, and handled the P3 no-op list copy.
  • Latest review follow-up aligned storage mark-read with sidebar summary timestamp derivation for stale headers.
  • GitHub reports no checks for this branch.

@Astro-HanAstro-Han changed the title Clear unread marker when messages are readfix: clear unread marker when messages are readJun 26, 2026
@Astro-Han
Astro-Han merged commit 3aafb0d into mainJun 26, 2026
@Astro-Han
Astro-Han deleted the opencode/ui-polish branch June 26, 2026 08:55
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Astro-Han