summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-21 22:37:25 +0900
committerAdam Malczewski <[email protected]>2026-06-21 22:37:25 +0900
commitf518ae6f98681225d921c66985f1115d20119f00 (patch)
tree442649805ef89cd082c6d99f5b935fcbc46df963
parent6cdbc1a645a625a7419b91ddada7ed06f475bed4 (diff)
downloaddispatch-f518ae6f98681225d921c66985f1115d20119f00.tar.gz
dispatch-f518ae6f98681225d921c66985f1115d20119f00.zip
docs: update cache-rate FE handoff for providers that don't report cache
Distinguish 'provider doesn't report cache' (undefined → N/A) from 'provider reports 0 cache hits' (0 → genuine miss). Umans doesn't report cache tokens at all; showing 0% in that case is misleading.
-rw-r--r--frontend-cache-rate-handoff.md26
-rw-r--r--frontend-conversation-list-handoff.md100
2 files changed, 126 insertions, 0 deletions
diff --git a/frontend-cache-rate-handoff.md b/frontend-cache-rate-handoff.md
index 158e199..89ab8e4 100644
--- a/frontend-cache-rate-handoff.md
+++ b/frontend-cache-rate-handoff.md
@@ -83,6 +83,32 @@ old session-cumulative-only panel could show.
breakpoints), and short prompts below the provider's cache threshold simply won't be cached —
`cacheReadTokens: 0` is a real "miss", not missing data. Cache reads grow as a conversation's
resent prefix gets large enough.
+- **Provider doesn't report cache at all — distinguish from 0.** Some providers (e.g.
+ **Umans**) never include `cache_read_tokens` / `cache_write_tokens` in their usage
+ payload. In that case `cacheReadTokens` is `undefined` — the provider can't tell you
+ whether cache was hit or missed. This is **different from `cacheReadTokens: 0`**,
+ which means "cache was checked and there were 0 hits" (a real miss).
+
+ The FE should distinguish these three states:
+
+ | `cacheReadTokens` | Meaning | FE display |
+ |---|---|---|
+ | `undefined` | Provider doesn't report cache | Hide cache panel, or show "N/A" |
+ | `0` | Provider reports cache; this request had 0 hits | Show "0%" (genuine miss) |
+ | `> 0` | Cache hit | Show percentage |
+
+ ```ts
+ function cacheDisplay(u: Usage): { kind: "not-reported" } | { kind: "reported"; hitPct: number } {
+ if (u.cacheReadTokens === undefined) return { kind: "not-reported" };
+ const read = u.cacheReadTokens;
+ const hitRate = u.inputTokens > 0 ? read / u.inputTokens : 0;
+ return { kind: "reported", hitPct: Math.round(hitRate * 100) };
+ }
+ ```
+
+ When `kind === "not-reported"`, do NOT show "0%" — that's misleading. Either hide the
+ cache panel entirely or show "Cache: not reported". This also applies to `cacheWriteTokens`
+ (if `undefined`, don't show a write row).
## Worked example (real numbers, captured live against OpenCode Go flash)
| Turn | inputTokens | cacheReadTokens | hit % |
diff --git a/frontend-conversation-list-handoff.md b/frontend-conversation-list-handoff.md
new file mode 100644
index 0000000..16e90cb
--- /dev/null
+++ b/frontend-conversation-list-handoff.md
@@ -0,0 +1,100 @@
+# FE handoff — conversation list, title, and open tab
+
+Courier this to `../dispatch-web`. All changes are ADDITIVE — nothing existing breaks.
+
+## What shipped (backend)
+
+Three new features for conversation management:
+
+1. **Conversation list** — `GET /conversations` returns all known conversations with
+ metadata (id, title, createdAt, lastActivityAt). The backend auto-tracks metadata
+ on every message append; title defaults to the first user message (truncated 80 chars).
+
+2. **Conversation title** — `GET/PUT /conversations/:id/title` lets the FE read and
+ set a human-readable title for any conversation.
+
+3. **Open tab signal** — `POST /conversations/:id/open` broadcasts a `conversation.open`
+ WS message to all connected clients (e.g. when the CLI uses `--open`). See also
+ `frontend-conversation-open-handoff.md` for the WS message details.
+
+No version bumps needed — all types are already in `@dispatch/transport-contract` `0.13.0`
+and `@dispatch/wire` `0.9.0`.
+
+## `GET /conversations` — conversation list
+
+Returns all conversations sorted by `lastActivityAt` descending (most recent first).
+
+- Optional `?q=<prefix>` query param filters by conversation ID prefix (short-ID
+ resolution — used by the CLI; the FE can ignore it or use it for search).
+- 200 response: `ConversationListResponse`
+
+```ts
+interface ConversationListResponse {
+ readonly conversations: readonly ConversationMeta[];
+}
+
+interface ConversationMeta {
+ readonly id: string;
+ readonly createdAt: number; // epoch-ms
+ readonly lastActivityAt: number; // epoch-ms
+ readonly title: string;
+}
+```
+
+**FE use case:** render a conversation sidebar/picker showing title + relative time.
+Click a conversation to open it (load its history via the existing `GET /conversations/:id`).
+
+## `GET /conversations/:id/title` — read title
+
+- 200 response: `TitleResponse { conversationId, title }`
+
+## `PUT /conversations/:id/title` — set title
+
+- Body: `SetTitleRequest { title: string }` (non-empty after trim, else 400)
+- 200 response: `TitleResponse { conversationId, title }`
+
+**FE use case:** let the user rename a conversation. The title is also auto-set from
+the first user message, so a newly created conversation already has a title.
+
+## `GET /conversations/:id/last` — blocking last message
+
+Blocks server-side until any in-flight turn settles, then returns the last AI text
+message. Mainly for CLI use (`dispatch read <id>`), but the FE could use it for
+notifications or previews.
+
+- 200 response: `LastMessageResponse { conversationId, content, turnId? }`
+- `content` is empty string if the conversation has no assistant message.
+- Unknown conversation → `content: ""` (200, not an error).
+
+## `POST /conversations/:id/open` — signal frontend
+
+Calls the backend to broadcast `conversation.open` to all connected WS clients. See
+`frontend-conversation-open-handoff.md` for the WS message format and FE handling.
+
+- 200 response: `OpenConversationResponse { conversationId }`
+
+## What the FE needs to do
+
+1. **Bump pinned deps:** `@dispatch/wire` → `0.9.0`, `@dispatch/transport-contract`
+ → `0.13.0`.
+
+2. **Conversation sidebar/picker:** call `GET /conversations` on load (and periodically
+ or on focus) to show a list of conversations. Each entry shows `title` + relative
+ time from `lastActivityAt`. Click to open → load history via `GET /conversations/:id`.
+
+3. **Title editing:** add an inline edit affordance on the conversation title.
+ `PUT /conversations/:id/title` with `{ title }` to update.
+
+4. **Handle `conversation.open` WS message:** when a `"conversation.open"` message
+ arrives, open (or focus) a tab for that `conversationId`. See
+ `frontend-conversation-open-handoff.md`.
+
+## Notes
+
+- Conversations are **in-memory only** on the backend — the list resets on server
+ restart. New conversations appear as users chat; old ones may disappear after a
+ restart.
+- The title is auto-set from the first user message (truncated 80 chars). Users can
+ override it via `PUT /conversations/:id/title`.
+- `createdAt` is set on the first message append; `lastActivityAt` updates on every
+ append.