mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
Track readable content revisions and durable read cursors in the hub. Acknowledge only rendered foreground tails, persist offline read operations, and preserve newer manual unread choices across devices. Migrate existing history to a read baseline and cover pagination receipts with shared native conformance fixtures.
89 lines
4.4 KiB
Markdown
89 lines
4.4 KiB
Markdown
# Session read state
|
||
|
||
Sources: `shared/src/sessionReadState.ts`, `shared/src/messages.ts`,
|
||
`hub/src/store/sessionReadState.ts`, `hub/src/sync/messageService.ts`.
|
||
|
||
Read state is shared by Web, iOS and Android within one Hub and JWT namespace.
|
||
CLI terminal activity does not acknowledge reading. `updatedAt`, message `seq`,
|
||
permission state and transport heartbeat are independent of unread status.
|
||
|
||
## Revisions and unread
|
||
|
||
Session, SessionSummary and session SSE patches carry:
|
||
|
||
```ts
|
||
contentRevision: number
|
||
readState: { readRevision: number; manualUnread: boolean; version: number }
|
||
```
|
||
|
||
The hub increments `contentRevision` atomically with a new readable assistant
|
||
message: answer text, rendered media or an error. User prompts, reasoning, tool
|
||
traffic, usage, metadata and heartbeats do not advance it. Imported/copied
|
||
history starts at zero; live delayed output counts. A replay with the same
|
||
`localId` keeps its original revision. Meaningful message rows carry their
|
||
`contentRevision` in REST and SSE.
|
||
|
||
Unread is `manualUnread || (!(active && thinking) && contentRevision > readRevision)`.
|
||
Running suppresses the ordinary dot without discarding unseen content. Input and
|
||
permission attention remain independent. Selecting a session does not make it read.
|
||
Clients merge revisions monotonically and use the highest read-state `version`
|
||
for the manual flag, including when REST and SSE arrive out of order.
|
||
|
||
## Read acknowledgement
|
||
|
||
`PUT /api/sessions/:id/read-state` accepts one of:
|
||
|
||
```json
|
||
{"type":"read","readRevision":12,"expectedVersion":4}
|
||
```
|
||
|
||
```json
|
||
{"type":"unread","expectedVersion":4}
|
||
```
|
||
|
||
The response includes `{status, contentRevision, readState}`. Counters must be
|
||
nonnegative safe integers. An invalid/future revision returns 400. Manual unread
|
||
uses compare-and-set: a mismatched version returns 409 with authoritative state.
|
||
Read requests advance by maximum; they clear manual unread only when
|
||
`expectedVersion` matches. Thus an older automatic read cannot erase a newer
|
||
manual choice, and an acknowledgement of revision 12 cannot consume revision 13.
|
||
An old session ID resolving to a replacement returns 409: revisions cannot be
|
||
replayed against a different session. Namespace access is checked before mutation.
|
||
Read writes emit `session-updated` patches without advancing `updatedAt`.
|
||
|
||
Automatic reads require a visible foreground chat, successful message loading,
|
||
and the latest tail laid out on screen. Background, history browsing, hidden
|
||
inspectors/terminals, failed loads and list navigation cannot acknowledge reading.
|
||
A read-state-only update is not a new viewing action. Explicit “mark all read”
|
||
uses the content revisions observed in that list action.
|
||
|
||
Latest message pages carry `page.readThroughRevision`. A completed forward
|
||
catch-up carries it only if its snapshot still reaches the current head. Before
|
||
pages and partial/bounded catch-ups cannot certify a later head. Clients retain
|
||
the receipt with the corresponding message window; they can advance it with
|
||
rendered incoming message revisions after initial tail synchronization. Never
|
||
substitute the summary’s head revision for a rendered window receipt.
|
||
|
||
## Offline and lifecycle
|
||
|
||
Persist the exact observed revision and expected version in a durable outbox
|
||
before presenting a successful local read. Retry transient failures, including
|
||
after restart/foregrounding. Each operation has an ID so an old response cannot
|
||
remove a newer queued operation. Authoritative 400/403/404/409 rejection rolls
|
||
back the optimistic mark; do not blindly rebase onto a newer manual choice.
|
||
Storage keys include Hub origin and JWT namespace; never derive namespace by
|
||
splitting the pairing access token. Switching Hub must finish pending disk writes.
|
||
|
||
## Upgrade and history changes
|
||
|
||
Database v30 adds persistent read fields with zero defaults. Existing history
|
||
becomes a read baseline once; old client timestamp/manual-marker files are ignored.
|
||
This clears old unread dots on upgrade, while pending approvals/input remain.
|
||
Update Hub and clients together; clients do not fall back to timestamp-based reads.
|
||
|
||
Fork/import copies are baseline history. A session merge translates unread source
|
||
revisions above the destination head, transfers manual unread, and retires the
|
||
source state. Client windows seeded across IDs discard old message revisions and
|
||
fetch a fresh tail. Rewind never reuses revisions; if all unread output is removed,
|
||
it retires that output while preserving an explicit manual-unread choice.
|