Skip to content

Commit f16fd00

Browse files
ndemiancclaude
andcommitted
docs(sessions): minimalist card + seal/switch ledger; lock auto-archive
Three refinements from review: - Session card: minimal at rest (title + one quiet line = primary file · time), state SILENT when done (a 2px left-edge only when noteworthy), pin only when pinned. Model/turns/sparkline/all-files/actions move to a hover/focus expand (progressive disclosure). 'Minimal' = fewer things visible at once, not fewer features. Compact mode goes further to one line. - New Chat / seal: a full state ledger — what the live window holds and where each piece goes on seal (transcript flush, kept edits, confirm-then-abort a running agent, reap background/MCP, live-only checkpoints, PRESERVED composer draft, pointer move). Seal vs Resume vs Compact kept distinct. - Lifecycle: auto-archive DECIDED — on at 30 days (pinned exempt, never deletes). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8ab6462 commit f16fd00

1 file changed

Lines changed: 67 additions & 25 deletions

File tree

docs/levelcode-sessions-experience.md

Lines changed: 67 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -109,44 +109,57 @@ Reached by the **clock icon** in the chat header, `⇧⌘H`, or `LevelCode: Chat
109109
├────────────────────────────────────────────────┤
110110
│ TODAY │ ← time buckets, mono uppercase
111111
│ ┌──────────────────────────────────────────┐ │
112-
│ │ Idempotent refunds via Redis keys ★ │ │ ← session card (see 4.2)
113-
│ │ 2h ago · Opus 5 · 41 turns · ⣀⣠⣴⣶ ▏ │ │
114-
│ │ refund.rb redis_lock.rb +3 · ✓ done │ │
112+
│ │ Idempotent refunds via Redis keys ★ │ │ ← minimal card (see 4.2)
113+
│ │ refund.rb · 2h │ │
115114
│ └──────────────────────────────────────────┘ │
116-
┌──────────────────────────────────────────┐ │
115+
┌──────────────────────────────────────────┐ │ ← 2px left-edge = interrupted (only marker)
117116
│ │ Debug the flaky payment webhook test │ │
118-
│ │ 5h ago · Kimi K2.7 · 12 turns · ⣀⣄⡀ │ │
119-
│ │ webhook_spec.rb · ⚠ interrupted │ │
117+
│ │ webhook_spec.rb · 5h │ │
120118
│ └──────────────────────────────────────────┘ │
121119
│ YESTERDAY │
122120
│ … │
123121
└────────────────────────────────────────────────┘
124122
```
125123

126124
- **Time buckets:** Today / Yesterday / This week / Earlier (then by month). Sticky bucket headers on scroll. This is how a human actually remembers work ("that was Tuesday"), and it beats a flat reverse-chron list.
127-
- **Density:** default is the rich card above. A **compact** toggle (`⌘\`) collapses to a single mono line (title · time · turns) for users who prefer Copilot-terseness — respecting that some people want the dropdown. The preference persists.
125+
- **Density:** the default is the calm two-line card (§4.2); hover/focus expands it to the rich detail, and a **compact** toggle (`⌘\`) drops it to one mono line for Copilot-terseness. Density is a spectrum the user sets; the preference persists.
128126
- **Virtualized** list (§8) so 5,000 sessions scroll at 60fps.
129127
- The panel **does not steal the conversation** — it slides over it from the right at `--mo-base`, and the conversation stays warm underneath (dimmed 40%), so dismissing feels like closing a drawer, not navigating away.
130128

131-
### 4.2 The session card — information design
129+
### 4.2 The session card — minimal at rest, rich on demand
130+
131+
The first draft of this card showed everything at once — title, model, turns, a sparkline, file chips, a state pill. That is *dense*, and dense is noise: on a list of a hundred sessions you scan **titles**, not sparklines. **Awwwards-minimal is not fewer features — it is fewer things visible at once.** So the card has two states, and the richness lives in the second.
132+
133+
**At rest — two lines, one signal, silent when nothing's wrong:**
134+
```
135+
┌────────────────────────────────────────────────┐
136+
│ Idempotent refunds via Redis keys ★ │ title owns the card · ★ only when pinned
137+
│ refund.rb · 2h │ one quiet line: primary file it edited · when
138+
└────────────────────────────────────────────────┘
139+
⚠│ Debug the flaky payment webhook test │ a 2px left-edge is the ONLY state marker,
140+
│ webhook_spec.rb · 5h │ and only when noteworthy (here: interrupted)
141+
```
142+
- **Title owns the card** — 14px/500, one line, ellipsis, full title on hover. It is 90% of recognition; give it the room.
143+
- **One quiet meta line** — muted mono: the single highest-signal cue, **the primary file it edited**, + relative time (`refund.rb · 2h`). A chat-only session with no edits reads just `2h ago`. No model, no turn count, no sparkline, no chip row at rest.
144+
- **State is silent when normal.** A `done` session shows *nothing* — "done" is the default, and a `✓ done` on every row is pure noise. Only a noteworthy run-state surfaces, as a **2px colored left-edge**: `` interrupted (`--cc-warn`), `` error (`--cc-danger`), `` resumed-from-summary (accent). **Absence means it finished.** One move, and ~90% of cards lose a chip.
145+
- **Pin (★)** appears only when pinned — a small accent star, right-aligned; otherwise absent.
132146

133-
This is the piece Cursor doesn't have. Every field earns its place by helping you recognize the session you want:
147+
Calmer than Cursor *and* more useful than Copilot: Copilot gives you title + time, full stop; we add the one cue a developer recognizes work by — *the file it changed* — and a state edge that speaks only when something is off.
134148

149+
**On hover / focus / selection — the card earns its detail:**
150+
It expands (`--mo-base`) into a third zone, so nothing is lost, only deferred to the moment you're choosing:
135151
```
136152
┌────────────────────────────────────────────────┐
137-
│ Idempotent refunds via Redis keys ★ │ ← title (14/600, 1 line, ellipsis); star = pinned
138-
│ 2h ago · Opus 5 · 41 turns · ⣀⣠⣴⣶⣶⣦⣀ ▏ │ ← meta row (11.5 mono): when · model · size · activity sparkline
139-
│ refund.rb · redis_lock.rb · +3 ✓ done │ ← files-touched chips (max 2 + overflow) · state pill
153+
│ Idempotent refunds via Redis keys ★ │
154+
│ refund.rb · redis_lock.rb · +3 │ all files it edited
155+
│ Opus 5 · 41 turns · ⣀⣠⣴⣶⣦⣀ ✓ done │ model · size · sparkline · full state
156+
│ [Resume ⏎] [Done d] [Rename] [Fork] [⋯] │ inline actions — no "…" menu
140157
└────────────────────────────────────────────────┘
141-
hover ▸ reveals: [Resume ⏎] [Rename] [Fork] [Export] [Delete]
142158
```
159+
- model, turn count, the **activity sparkline** (drawn-in on expand, §5), all files, the full state, and inline actions (`⏎ d r f e ⌫`). The sparkline still tells you *shape* — a long ramp vs a quick spike vs a cliff — but only when you're looking at that one card, not on every row.
160+
- Mouse: hover-intent (120ms) so a fast scroll-by doesn't flicker. Keyboard: the focused card *is* the expanded one.
143161

144-
- **Title** — auto-generated (cheap-lane, per spine §Titles) or user-renamed. One line, `text-overflow: ellipsis`, full title on hover title-attr.
145-
- **Activity sparkline** — a ~28px Unicode/`<canvas>` micro-bar of tool-calls-per-turn across the session. It tells you *shape* at a glance: a long ramp (big feature), a short spike (quick fix), a flat line then a cliff (interrupted). No incumbent shows this; it's cheap (the data is already in the JSONL) and instantly legible.
146-
- **Files-touched chips** — the top 2 files this session *edited* (not read), + `+N` overflow. This is the single most useful recognition cue for developers ("the one that touched `refund.rb`") and is impossible in Cursor's title-only search. Derived from the `agent` edit events already stored.
147-
- **State pill**`✓ done` (`--cc-ok`), `⚠ interrupted` (`--cc-warn`), `✕ error` (`--cc-danger`), `↻ resumed-from-summary` (accent). This is the honesty layer as UI. A session that ended mid-run is *marked*, so History never lies about what finished.
148-
- **Pin (★)** — pinned sessions float to a "Pinned" bucket above Today. For the handful of chats you return to (a long-running refactor, a design thread).
149-
- **Hover/focus actions** — appear inline (not a `` menu): Resume (primary, ⏎), Rename, **Fork** (see §6), Export as Markdown, Delete. Keyboard: arrow to a card, actions are on `` (resume), `r`, `f`, `e`, ``.
162+
**Compact mode** (`⌘\`) goes further the other way — one mono row of `title · time` — for people who want Copilot-terseness. Density is a spectrum the user sets; the *default* is this calm two-line card, not the dense original.
150163

151164
### 4.3 The switcher — keyboard-first resume
152165

@@ -198,10 +211,39 @@ Resuming is where LevelCode earns the award, because it is the one moment that i
198211

199212
- **What died is said.** If the session had background commands, MCP servers, or a running agent when it was last open, resume renders a quiet **"since you left"** strip: *"3 background processes and 1 MCP server were stopped when this chat was last closed. The agent will re-establish anything it needs."* — turning the spine's honesty events into UI. No incumbent does this; it is the difference between resuming a *state* and resuming a *transcript*.
200213

201-
### 4.6 New Chat — seal & rotate
214+
### 4.6 New Chat — what "seal" saves when the window goes inactive
215+
216+
New Chat is not "clear the screen." It **seals** the current session — a precise finalization — then opens a fresh one, and the window you were in becomes an inactive History entry with *nothing lost that mattered*. "What happens to my work when I switch?" is the question that decides whether people trust New Chat at all, so seal is specified exactly.
217+
218+
**The state ledger — what the live window holds, and where each piece goes on seal:**
219+
220+
| Live-window state | On New Chat (seal) | On resume |
221+
|---|---|---|
222+
| Transcript (user / assistant / agent turns) | already appended per-turn; seal **force-flushes** the last one | replayed verbatim (or summarized to fit, §4.5) |
223+
| Applied file edits (Keep/Undo review) | **kept on disk**; review finalized; session records the era | already in your working tree; era noted |
224+
| A running agent turn | **confirm first**, then abort → recorded `interrupted` | shown as ``; never auto-resumed |
225+
| Background commands / MCP servers | **reaped** (killed) → recorded `stopped` | "since you left" strip; agent re-establishes deliberately |
226+
| Checkpoints (per-turn file-restore) | boundaries recorded; **file-restore is live-only** | shown as markers (no cross-seal rewind — yet) |
227+
| Composer draft (typed, unsent) | **saved** as a per-session `draft` (not a turn) | **restored into the input box** |
228+
| Live-session pointer (`workspaceState`) | **moves to the new session** | a window reload re-opens the right one |
229+
| Model / provider mode | unchanged (these are global, not per-session) | unchanged |
230+
231+
Reading the table top to bottom is the whole design, but three rows deserve their reasoning:
232+
233+
- **A running agent → confirm, don't silently abort.** A mid-run agent may be halfway through editing files, so New Chat asks: *"A run is in progress. Start a new chat? The run stops and this chat is saved to History."* Only on confirm does it abort; the abort is an `interrupted` event, so the sealed card shows `` and resume tells the truth. This is the one place seal pauses to ask — everywhere else it is silent and instant.
234+
- **Background processes → reaped, never orphaned, never auto-restarted.** Killing them matches today's `newChat()` and avoids leaking processes across sessions; recording them as events is what lets the resumed session's "since you left" strip (§4.5) tell the agent what died, so it re-establishes state on purpose rather than inheriting a half-alive environment.
235+
- **The composer draft → preserved.** Half-typed text is the cheapest thing to lose and the most annoying; it rides the session file as a non-transcript `draft` and comes back on resume. Small touch, disproportionate goodwill.
236+
237+
**The new session starts clean — deliberately.** Empty transcript, fresh checkpoint sequence, no inherited background state. It keeps only what is *global* (model, provider mode), never per-session context. New Chat is a clean slate **by definition**; if you want continuity you **Resume**, you don't New Chat.
238+
239+
**Three verbs, kept distinct** — the mental model that prevents the "where did my chat go?" panic:
240+
- **New Chat / Seal***this* session freezes into History; a *fresh* one opens.
241+
- **Resume** → a History session becomes the live one again (§4.5).
242+
- **Compact** → the *current* session summarizes its own head to keep going *in place* (context management, same session); it does **not** start a new session and does **not** touch History. Seal ≠ Resume ≠ Compact, and the UI labels them so.
243+
244+
**The gesture (taught once).** The sealed session doesn't just vanish — its card **flies to the History icon** (`--mo-base` translate+scale to the clock; the icon pulses once). First time only, a one-line toast: *"Saved to History (⇧⌘H)."* Dismiss = never shown again. One gesture teaches the whole feature, then gets out of the way.
202245

203-
- `⌘N` (in-panel) / the `+` in the header. The current session **seals** (final index write, async title if untitled) and a fresh one opens.
204-
- **The affordance, taught once:** the sealed session doesn't just vanish — its card **flies to the History icon** (a `--mo-base` translate+scale to the clock, then the icon pulses once). First time only, a one-line toast: *"Saved to History (⇧⌘H)."* Dismiss = never shown again. This teaches the entire feature in one gesture, then gets out of the way.
246+
**Multi-window.** Two windows on one project each own their *own* live session; New Chat in one never touches the other. A session file is owned by one window at a time (lockfile beside it, 30s stale timeout, spine §UI); resuming a session already live elsewhere offers a read-only *"open a copy?"* — no merge semantics, ever.
205247

206248
### 4.7 Empty & edge states — where character lives
207249

@@ -246,21 +288,21 @@ A session can be run-state `interrupted` yet lifecycle `done` (you gave up on it
246288

247289
**UI:** the panel shows **Active** by default; a scope pill toggles **Archive** (with its count — *"142 archived"*) and **Trash**. Archived cards render dimmed and re-activate inline. Card actions become **Resume · Done · Rename · Fork · Export · Delete** (`⏎ · d · r · f · e · ⌫`). The run-state pill and a small lifecycle glyph (active / archived) stay visually distinct.
248290

249-
**The one call that is genuinely yours:** whether auto-archive ships **on at 30 days** (my recommendation — it is the Arc magic that keeps the list alive for free) or **opt-in, off** (more conservative). And the verb: I lean **"Done"** (the decluttering framing users reach for) with *archive* as the mechanism underneath.
291+
**Decided:** auto-archive ships **on, at 30 days** the Arc magic that keeps History alive for free (`sessions.autoArchiveDays: 30`; pinned exempt; never deletes). The button reads **"Done"**, with *archive* as the mechanism underneath — still a one-line change if a later usability pass prefers "Archive" on the label.
250292

251293
## 5. Signature interactions & motion (the differentiators)
252294

253295
Each is specific, cheap, and reduced-motion-safe:
254296

255297
1. **Card focus lift** (`--mo-fast`): border → `--cc-accent`, `translateY(-1px)`, soft shadow in. Communicates focusability; the whole list is arrow-navigable.
256298
2. **Resume morph** (`--mo-morph`): the shared-element card→conversation transition (§4.5). The single most memorable moment; the thing a juror screenshots.
257-
3. **Sparkline draw-in** (`--mo-base`, staggered 8ms/bar on first paint of a card entering the viewport): the activity bars grow from baseline. Ambient life, not noise; only on enter, never on scroll-back.
299+
3. **Sparkline draw-in** (`--mo-base`, staggered 8ms/bar **on hover/focus expand**, §4.2): when a card opens to its rich detail the activity bars grow from baseline. It never draws on scroll — the resting row has no sparkline to animate — so a fast scroll stays calm.
258300
4. **Filter reflow** (`--mo-base`): cards that leave fade+collapse; remaining cards ease to new positions (FLIP). Turns filtering from a repaint into a legible rearrangement.
259301
5. **Seal-to-history fly** (§4.6): the New Chat teaching moment.
260302
6. **Search match shimmer** (`--mo-fast`): matched substrings get an accent underline that draws left-to-right — shows *why* a result matched.
261303
7. **Portal drift** (`7s`, empty state + switcher glyph only): the brand's ambient signature, used with extreme restraint.
262304

263-
Global rule: **nothing animates on scroll** except the one-time sparkline draw-in; scrolling a history of your own work must feel like paper, not a parallax site. Restraint is the awwwards move here, not maximalism.
305+
Global rule: **nothing animates on scroll at all** the sparkline lives in the hover-expanded card, not the resting row, so scrolling a history of your own work feels like paper, not a parallax site. Restraint is the awwwards move here, not maximalism.
264306

265307
---
266308

0 commit comments

Comments
 (0)