Skip to content

Commit bbe7dbc

Browse files
radroidclaude
andcommitted
docs(t3x): design + plan for per-thread auto-resume UI (floating card)
Implementation-ready handoff plan: status (rides existing timeline activities, ~free), per-thread on/off toggle + resume-message box persisted via a fork-owned raw HTTP route (zero @t3tools/contracts edits), and a floating-card overlay under apps/web/src/t3x/. Total upstream footprint: 2 additive mount lines (server.ts + the cold chat route file). Phased with verification + guardrails. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_018UdKR8xwjujkyNiQwv3qxJ
1 parent fa79164 commit bbe7dbc

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# t3x — Per-thread auto-resume UI (design + implementation plan)
2+
3+
**Date:** 2026-07-24
4+
**Fork:** `radroid/t3code`
5+
**Parent feature:** `2026-07-23-usage-limit-auto-resume-design.md` (the headless server feature this adds UI to)
6+
**For:** a fresh implementation agent. Self-contained. Branch: `t3x/autoresume-ui` off `main` (merge `t3x/setup-and-auto-resume` into `main` first).
7+
8+
## Goal
9+
10+
Give each thread a small, visible control for the existing (headless) auto-resume feature:
11+
12+
1. **Status** — is this loop going to be auto-resumed, and when ("next attempt ~3:47 PM").
13+
2. **On/off toggle** — enable/disable auto-resume for _this thread_ (default on).
14+
3. **Resume-message textbox** — the text sent on resume for this thread (default `"continue"`; already stored as `overridePrompt`).
15+
16+
Placement (decided): a **floating card** overlaying the thread view — lowest merge-conflict, self-positioning, no upstream layout cooperation.
17+
18+
## Guiding constraint (this is a fork)
19+
20+
Keep the upstream footprint at a **fixed, tiny seam** and touch **zero** `@t3tools/contracts` code and no hot chat files' logic. Target total upstream footprint:
21+
22+
- **+1 line** (+1 import) in `apps/server/src/server.ts` (mount the t3x HTTP route).
23+
- **+1 line** (+1 import) in `apps/web/src/routes/_chat.$environmentId.$threadId.tsx` (mount the overlay).
24+
- Everything else is **new files** under `apps/server/src/t3x/` and a new `apps/web/src/t3x/`.
25+
26+
> Every concrete file path / line number / symbol below came from a codebase exploration and is a strong lead, **but the implementing agent MUST re-confirm each against the current code before editing** (upstream churn moves line numbers).
27+
28+
## Architecture (3 pieces)
29+
30+
### A. Status — mostly free
31+
32+
The reactor already appends per-thread timeline activities (`t3x.auto-resume.scheduled|resumed|cancelled|capped` via `appendActivity` → `thread.activity.append` in `apps/server/src/t3x/autoResume/Reactor.ts`), and the web already renders arbitrary activity kinds in the work-log (per exploration: `apps/web/src/.../session-logic.ts` `deriveWorkLogEntries` uses `activity.summary` and does not filter our kinds — **verify**). So a baseline status already surfaces in the timeline with no change.
33+
The floating card additionally shows a **live** status ("auto-resume: on · next attempt ~3:47 PM") read from the GET endpoint below (returns the current pending, if any). Enrich wording only in our own `Reactor.ts` if desired — no seam.
34+
35+
### B. Persistence — a fork-owned raw HTTP route (NOT an RPC/contracts change)
36+
37+
Adding a WS-RPC method would force edits to `@t3tools/contracts` (`WsRpcGroup`, `WS_METHODS`), `apps/server/src/ws.ts` (churn 36/60d) and its scope map — high conflict. Instead:
38+
39+
- **New file `apps/server/src/t3x/autoResume/http.ts`** exposing a raw `HttpRouter` with:
40+
- `GET /api/t3x/auto-resume?threadId=…` → `{ enabled, overridePrompt, pending: { resumeAtMs, reason } | null }`
41+
- `POST /api/t3x/auto-resume` body `{ threadId, enabled?, overridePrompt? }` → writes to `AutoResumeStore`.
42+
- **Auth:** copy the existing authenticated-raw-route pattern — mirror `otlpTracesProxyRouteLayer` / `authenticateRawRouteWithScope` in `server.ts` (same Bearer token + scope the web already holds). **Do not** create an unauthenticated endpoint. Confirm the exact helper name and required scope from the otlp route and reuse it verbatim.
43+
- **Mount:** one additive line in `server.ts`'s `makeRoutesLayer` `mergeAll`, beside `otlpTracesProxyRouteLayer` (+1 import). This is the only server seam.
44+
- **Sharing the store:** the route handler and the reactor must use the **same** `AutoResumeStore` instance. Wire `AutoResumeStoreLive` so it's visible to both the routes layer and the reactor (exploration flagged flipping `provide`→`provideMerge` for the store in `apps/server/src/t3x/index.ts` — confirm the layer graph and make the store a shared dependency, not two instances).
45+
46+
### C. The control UI — new `apps/web/src/t3x/`
47+
48+
- **New component** `apps/web/src/t3x/AutoResumeOverlay.tsx` — a floating/absolute-positioned card. Reuses existing primitives: `ui/switch` (toggle) and `ui/textarea` (resume message). Shows the live status line.
49+
- **Data:** an authenticated `fetch` to the route in (B), using the connection's `{ httpBaseUrl, httpAuthorization }` (find how the web already resolves these — likely an atom/context used by existing HTTP calls / `LocalApi`; reuse it). Debounce the textbox write; optimistic toggle.
50+
- **Mount (the one web seam):** in `apps/web/src/routes/_chat.$environmentId.$threadId.tsx`, add one import and render `<AutoResumeOverlay threadRef={threadRef} />` as a sibling of `<ChatView>` inside `<SidebarInset>` (~line 66; `threadRef` is already resolved there). This route is the server-thread view; the draft route is correctly excluded.
51+
52+
### D. State + gating (fork-owned, zero seam)
53+
54+
- **`apps/server/src/t3x/autoResume/state.ts`:** add `enabled` to `ThreadRecord` as **optional with default true** (`Schema.optionalWith(Schema.Boolean, { default: () => true })` or equivalent) so pre-existing state files still decode. Add a `setEnabled(threadId, enabled)` mutation and a `setOverridePrompt(threadId, text)` mutation (the store already carries `overridePrompt`; add the setter).
55+
- **`apps/server/src/t3x/autoResume/Reactor.ts`:** gate on `enabled`. In detection (`onRuntimeEvent`) skip scheduling when the thread is disabled; in `fireOne` treat disabled as a cancel reason (clear pending, optional activity note). Simplest: check `record.enabled !== false` in detection, and re-check in `fireOne` against a fresh read.
56+
57+
## Data flow
58+
59+
```
60+
toggle/textbox → fetch POST /api/t3x/auto-resume → AutoResumeStore.setEnabled / setOverridePrompt (durable JSON)
61+
reactor detection/fireOne → reads enabled + overridePrompt from the same store
62+
card status ← fetch GET /api/t3x/auto-resume ← AutoResumeStore (enabled, overridePrompt, current pending)
63+
timeline status ← existing activity notes (no change)
64+
```
65+
66+
## Implementation phases
67+
68+
**Phase 1 — server state + gating (no UI yet).**
69+
70+
- Add `enabled` (optional, default true) + `setEnabled`/`setOverridePrompt` to `state.ts`.
71+
- Gate detection + `fireOne` on `enabled` in `Reactor.ts`.
72+
- Tests: extend `state.test.ts` (enabled round-trips, defaults true, old file without the field decodes to enabled=true); extend `Reactor.test.ts` (disabled thread neither schedules nor fires).
73+
- Verify: `pnpm run test src/t3x` green; `pnpm run typecheck` clean.
74+
75+
**Phase 2 — the HTTP route.**
76+
77+
- `http.ts` GET/POST over the shared `AutoResumeStore`, authenticated like `otlpTracesProxyRouteLayer`.
78+
- Share the store instance; mount via the single `server.ts` line (+import).
79+
- Test: a route-level test (auth required; GET/POST round-trip through a real store). Confirm an unauthenticated request is rejected.
80+
- Verify: typecheck + tests; `curl` the route against a locally booted server with the bearer token (see the parent conversation's headless-boot recipe) and confirm read/write.
81+
82+
**Phase 3 — the web overlay.**
83+
84+
- `apps/web/src/t3x/AutoResumeOverlay.tsx` + the single mount line in the chat route file.
85+
- Resolve `{ httpBaseUrl, httpAuthorization }` the same way existing web HTTP calls do; debounce textbox; optimistic toggle; render live status.
86+
- Verify: `pnpm run typecheck`; web lint; manual check in a locally built app — toggle persists across reload, textbox persists, status reflects a scheduled resume.
87+
88+
**Phase 4 — docs + seam ledger.**
89+
90+
- Update `docs/t3x/SEAMS.md`: record the 2 new seams (server.ts route mount; web route-file overlay mount) and confirm contracts untouched.
91+
- Update the parent auto-resume spec's B7 ("zero UI") to point here.
92+
93+
## Guardrails (do not violate)
94+
95+
- **Zero edits to `@t3tools/contracts`.** No new RPC method, no settings-schema field.
96+
- **No logic edits to hot chat files** (`ChatView.tsx` 51/60d, `ChatComposer` 30, `ws.ts` 36). The only upstream edits are the two additive mount lines named above.
97+
- New code lives under `apps/server/src/t3x/` and `apps/web/src/t3x/` only.
98+
- Follow Effect v4 conventions already used in `t3x/autoResume/*` (`Effect.catch`/`catchCause`, `Context.Service`, no `new Date()`/`Math.random`).
99+
100+
## Out of scope (v1)
101+
102+
- Global defaults UI (a `/settings` section). Low-conflict to add later (new route file + one nav line) but only holds global defaults, not per-thread state — skip unless wanted.
103+
- Manual "resume now" / "cancel scheduled resume" buttons — easy follow-ups on the same route; not required for this ask.

0 commit comments

Comments
 (0)