Skip to content

Release v2.7.0 - #60

Merged
Alex375 merged 28 commits into
mainfrom
dev
Sep 29, 2026
Merged

Alex375 merged 28 commits into
mainfrom
dev

Conversation

@Alex375

@Alex375 Alex375 commented Sep 29, 2026

Copy link
Copy Markdown
Owner

Release v2.7.0. Voir les commits de dev depuis la dernière release.

🤖 Generated with Claude Code

clousty8 and others added 28 commits September 28, 2026 10:07
…CRM d1ce0aa6)

A prompt that launches background work produces several `result`s: the main
answer, then one follow-up turn the CLI starts on its own per background task
that reports back (verified live, claude 2.1.283). Each follow-up restamped the
turn clock — the live counter restarted — and printed its own short footer.

- agent/runClock.ts: a RUN opens on the user's send, owns the background tasks
  first seen while it is the latest run, absorbs the follow-up turns they
  trigger, and ends when nothing of it runs any more.
- Thread: one timing line per run, under its latest result — main-answer time,
  then a live green "Background task running · 1m 12s" counted from the Enter,
  then "46s total" once the work is done. The working indicator, the sidebar
  row and the "Conversation ended" band read the run clock too.
- result.duration_api_ms is a cumulative per-session counter (restored across
  --resume): the assembler now reports each turn's own delta, unknown on the
  first turn of a resumed process.
- fmtDuration no longer prints "1m 60s".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… system tray (CRM 29ff1ebd)

A restyle of the existing title bar, same controls as before:
- The bar sits flush with the app (same ground, a hairline under it).
- View tabs are plain text; ONE underline sits under the current tab and slides to
  the next one (useTabUnderline in kit.tsx, observer-driven, no per-render layout).
- Title-bar buttons lose their frame: open = lit glyph + a dot underneath that grows
  in and out. Scoped to .wf-titlebar — the sidebar and Flight Deck keep the framed look.
- Voice session / mic / notification sound / Caffeinate share one pill (SystemTray),
  each still a one-click toggle, lit white when on and grey when off. Caffeinate gets a
  chevron to pick Light / Hard. Replaces VoiceToggle, SoundToggle and CaffeinateToggle.
- The stream / worktree / TOSSE task chips drop their frame when they sit in the bar.
- New Motion pref "Animate the title bar" (titleBarAnimations, ON by default); the OS
  reduce-motion setting always wins.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ch older versions

The side panel now drags to a width of its own (286–560px, persisted in
`tosse:editor` like `treeWidth`). The splitter travels inside the animated slot
so it slides in with the panel; the rendered width is capped by the room left
beside the conversation and the editor/terminal region, so a wide stored panel
never pushes the composer under its floor — it springs back when the room
returns. The docking test now measures the panel's FLOOR rather than a fixed
340, so a narrower window docks a narrow panel instead of floating a wide one.

The artifact tile was empty because the wire changed under us: the `Artifact`
tool no longer sends `favicon` (an emoji) but `icon` (a WORD — "sparkle",
"report", "code"). Checked against the transcripts on disk: `favicon` alone up
to 2026-09-14, both during a short overlap, `icon` alone from 2026-09-20. Both
are read now and a word is resolved back to a pictograph in `artifactIcon.ts`.
Emoji rather than a kit glyph because the vocabulary is open — a 70-glyph UI set
has nothing for "recipe", "calendar" or "map", so most artifacts would land back
on the generic fallback — and a conversation mixing both wires renders as one
list.

An artifact's version history is now reachable: the "vN" badge expands into a
row per publish (its real number, from the ack's "(Version N)" suffix, else its
position), and a row scrolls the thread to the card that published it. That card
is the ONLY place an older version's contents still exist — claude.ai serves the
latest, and each republish overwrites the same temp file — so nothing fabricates
a per-version link.

Opening an artifact preview now puts the side panel away and brings it back when
the preview closes (pref `artifactHidesSidePanel`, ON). Transient: `convPanelOpen`
keeps the user's own answer, so an artifact never OPENS a panel that was closed,
and asking for the panel outranks the preview.

Also: the composer popover passes `multiFile` through (a page published with
sibling files was previewed locally and rendered stripped of its stylesheet), and
shows real version numbers and failed publishes. New `?demo=artifacts` scenario —
two artifacts, one republished twice, both wires — for visual checks.

Verified: tsc clean, 2285/2285 vitest, `pnpm build` green, and all four
behaviours driven end to end in the browser mock (resize + both clamps, the
conversation floor with the editor open, the version jump flashing the right
card, the panel hiding and returning — docked and floating).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e, one generic mark

The icon-wire fix covered page artifacts, but a Claude Design canvas still showed
the blank tile: verified over all 16 `Artifact` calls of a real Design
conversation, NOT ONE carries `icon` or `favicon` — the tool's own contract says
`icon` is "ignored on an Artifact created from an Artifact type". There was
nothing in the input to resolve.

Its third source is its TYPE's name ("Design" → 🎨), run through the same table
so a future type needs no code. A type nobody named stays faceless rather than
guessing.

The fallback itself was also inconsistent: 🎨 hard-coded on four surfaces, the
kit glyph on the fifth — and 🎨 is exactly what a real Design artifact resolves
to, so "we were told nothing" and "this is a design" were indistinguishable.
<ArtifactFace> is now the one rendering, drawing the kit's `artifact` mark when
there is no emoji. It carries no size class on purpose: `.wf-ico.sm` would tie
the per-tile rules on specificity and leave the winner to stylesheet order.

Verified: tsc clean, 2289/2289 vitest, `pnpm build` green; in the browser mock
the Design demo now shows 🎨 in both the thread cards and the panel, and each
tile's generic-mark rule measured at its own size (17/14/15/30px, muted).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…text section

The swap read as two shoves because it WAS two. Measured in the browser: the
panel left on `340px→0` at 135ms/ease-out while the artifact arrived on
`0→578px` at 190ms/ease-in — opposite curves, different clocks. The
conversation's right edge reversed direction mid-flight and overshot its
destination by ~200px before settling.

`slidePlan` now takes an optional timing, and both halves of a hand-off pass
SWAP_TIMING (the entrance's: the arrival sets the pace). Which side needs the
override is not symmetric, and the asymmetry is the whole subtlety:
  - the panel LEAVING and the artifact LEAVING would each pick the short exit;
  - the panel RETURNING already picks exactly SWAP_TIMING, so it needs nothing.

⚠️ The first attempt detected the hand-off by diffing a ref during render, like
`docksChanged`. It silently answered false on the one render that mattered, and
the panel went on leaving at 135ms — the measurement caught it, the code review
would not have. Both sides now read STATE: the panel reads `convPanelPreempted`
(true for as long as it is put away), and the side region reads it through
`useFrozenWhile`, since the preemption is lifted in the very `set()` that
dismisses the artifact.

Verified in the browser mock, sampling the composer's right edge every frame:
0 direction reversals and 0 overshoot in both directions (was 1 and ~200px), and
both animations now report 190ms / cubic-bezier(0.32,0.72,0,1).

Also: the side panel gains a CONTEXT section — the fill bar, "29.8k / 1M", and
the same popover the composer's ring and the Flight Deck meter open (plan usage
+ « Compact context »), through the same backend-aware hook. It appears once
there is a token count, and withholds the bar until the window is known
(end of turn) rather than drawing a fill from a guess.

Verified: tsc clean, 2293/2293 vitest, `pnpm build` green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…CRM 2c0ba2b7)

- Remove the 1 Hz TickEvent thread left over from the scaffold: nothing
  listened to it, it only woke the app 86,400 times a day.
- Run the "Thinking…" accrual ticker only while a session is busy, with a
  final sweep that seals the open spells when the last one goes idle.
- Ambient machine health: probe with `ssh … true` (new
  `machine_reachability`) instead of the full server diagnosis (a dozen
  server-side commands, two of them Node.js start-ups) — same ssh options,
  timeout and failure classification, shared with `diagnose`.
- Never probe a machine a live session is attached to: its ssh link already
  proves reachability; file the attached link as the verdict instead, and
  record "reachable" on the link's attach edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…try deck

Design B, chosen from the four proposals: the panel becomes a sheet lifted off
the window (inset 8px, rounded, shadowed) whose sections are ISLANDS rather
than rows ruled apart. The title and its chord share one pill in the title
bar's tray voice; counts sit in soft capsules; artifacts are a hairline list;
the session footer is the last island, pinned. Same elements as before — only
the look moved. The drag splitter goes `quiet` (no rule at rest, accent on
hover): a standing line in the gap beside a self-framed sheet was a third edge.
SIDE_PANEL_MIN_PX 286 → 296 to count the sheet's insets.

Telemetry deck (`conversationTelemetry`, OFF by default, Settings → Display →
Thread): Alexandre wanted a loud, "Palantir-like" mode for the fun of watching
an agent work — gauges, counters, things that move — on one condition: that it
means something. So NOTHING on it moves on its own; every instrument reads a
real signal (telemetry.ts):
  - a context gauge (the plain panel's bar, same popover), needle with overshoot,
    a sweep circling it only while a turn is in flight;
  - the run's clock (live, then frozen on the run's length) + a status lamp that
    names what the agent is doing, in priority order: awaiting permission >
    retrying > the tool running > streaming > thinking > background ops;
  - one rolling counter per tool family (read/edit/shell/search/agent/web) with
    their failures;
  - calls per 5 s over two minutes — ARRIVALS only, so a conversation reloaded
    from disk is not drawn as one giant spike at load time;
  - files touched, turns, API-equivalent cost, model time, background ops,
    sub-agents; and a feed of the latest calls (running / ok / error).
Numbers are memoised on the store references that move when a call lands, not
on streamed tokens; the timers are a 1 Hz clock mounted only while a run is
live and the 5 s bucket; motion is CSS on transform/opacity and stops under
the OS reduce-motion setting. The popover behind the gauge and the bar is now
one component (ContextUsageMenu).

Verified in the browser mock on the background and workflow demos: counts
match the calls, the lamp reads "background ops" with Bg ops = 1 while the
background task runs, the clock ticks then freezes on the run's length.
tsc clean, 2492/2492 vitest, `pnpm build` green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…CRM 2c0ba2b7)

The CRM's edge ends an idle SSE stream every ~12 s, and every reconnection
owed a (throttled) refetch — a sweep of the TOSSE queries every 60 s for as
long as the window stayed visible. Only a real outage (the channel left
`live`) can hide changes worth refetching; a recycle hides ~200 ms. The
proper fix — a CRM keepalive under the edge's 12 s idle timeout — is being
shipped in CRM_max.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e for remote agents (CRM 2c0ba2b7)

- ssh keepalive 5 s → 10 s per remote conversation (a cut is noticed in
  ~30 s instead of ~15 s — decided 28/09): a third of the radio wakeups
  at rest.
- Reconnect backoff: 1 s doubling to 30 s for the first 10 attempts of an
  outage, then up to 5 min — a long outage no longer forks ssh every 30 s
  per conversation forever. Cut short by the new `reconnect_remote_sessions`
  (fired on `online`, window focus/visibility while a link is down) and by
  a send during the outage.
- Caffeinate (Light) only counts LOCAL agents: a remote agent keeps working
  on its server under flightdeckd while the Mac sleeps, and reattaches with
  replay on wake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… come and go (CRM 01393dfa)

The floating side panel now sits at the top right, only as tall as what it
holds, and grows or shrinks with a height transition when a TOSSE task, a
goal, todos or an artifact arrive or leave; past the window's bottom it caps
and scrolls. Pref sidePanelFitContent (ON) — off restores the full-height
column.

Heights are read and written in a ResizeObserver (before paint, never through
React state); only a CONTENT change animates — first placement, window resize
and width drags follow instantly (fitHeightStep, tested).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed needles, an oscilloscope

Alexandre wanted the deck "more live": timers down to the millisecond, a timer
per tool, gauges that move fast while the agent runs. All of it still reads a
real signal (the rule the deck exists under), now at 60 fps:

  - the run clock and a STATE timer in ms — how long the agent has been in its
    current state, from the store's own stamps where it has one (the running
    call's start, the thinking spell, the moment it blocked on the user);
  - an IN-FLIGHT board: every running call with its own ms timer and a latency
    bar against the median of ITS family in this conversation (median at a
    third, amber when slower, red past 3×; no median yet → a stripe that only
    says "running", never a fake fill); the feed then shows each call's frozen
    duration — all from `toolStartedAt`/`toolDurations`, which the store
    already measured live and never stamps on replayed history;
  - four eased gauges: streaming rate (≈ tok/s, estimated from characters and
    labelled so), calls per minute, calls in parallel, turn load (this turn
    against the median turn — "—" before any turn has completed);
  - an oscilloscope of the streaming rate over 15 s;
  - the histogram at one bar a second over the last minute.

Cost: ONE requestAnimationFrame loop writes straight into DOM nodes (no React
render per frame) and reads the store with getState(). It runs while the run
is live and until the needles settle, then stops itself — measured: 0 frames
over 4 s once idle, ~30 fps in headless Chromium while busy. Reduce-motion →
a 1 Hz tick, no easing.

Found and fixed by measuring in the browser:
  - a closing histogram bucket lost its count: `setDone(p => closeBucket(p,
    liveRef.current))` read a ref inside an updater. One `{done, live}` state,
    closed by a single pure transition.
  - the loop stopped during background work although the run was still live,
    freezing its clock. `live` now follows the run itself.
  - `liveInFlight`: a session that dies mid-call never delivers the result, so
    its call would count up forever and keep the loop awake; nothing runs →
    nothing in flight. The lamp reads a running call and streaming text as the
    facts they are, even before the session reports itself busy.

tsc clean, 2514/2514 vitest, `pnpm build` green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… fixed in the CRM

CRM_max bd6705f (keepalive 10 s) + a8456e5 (server.timeout(req, 0) on
/api/v1/sse): production shows no more stream endings. Correct the comments
that blamed the Railway edge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…call time up front

Alexandre's round of feedback on the live deck:

- COMPACT: the deck fits a 13-inch MacBook Pro — measured at 519px (was ~780)
  in a 1440×820 window, the panel's footer still in view. One line of key
  figures, the six family counters on one row, the oscilloscope and the
  histogram as two thin strips side by side with their labels, a 4-line feed.
- COST and AVG CALL promoted to the key-figures line (with CALLS and MODEL);
  each family counter now shows its own average call time.
- HISTOGRAM: every tick re-animated every bar ("all the peaks go down and up").
  Bars were keyed by POSITION, so a tick handed each DOM bar its neighbour's
  value. They are now keyed by the second they count (histogramBars): a tick
  only moves them left, and only the live bar has a height transition —
  measured: 0 closed bars with a transition.
- IN FLIGHT: most calls finish in well under a second, so the board flickered
  rows in and out too fast to read. It is now a LONG CALLS board: a call joins
  it once it has run 40 s (LONG_CALL_MS, Alexandre's figure), and the board is
  hidden while none qualifies. Visibility is toggled by the frame loop through
  a data attribute React never writes, so a re-render cannot undo it. Every
  call still counts on the gauges, the counters and the feed.
- COST OF A JOKE: off (the default), the deck is not mounted — measured: 0
  frames and 0 timers over 4 s idle. On, the loop is capped at 30 fps, pauses
  while the window is hidden, stops once settled; the histogram's 1 s tick
  stops once its minute has emptied.

tsc clean, 2516/2516 vitest, `pnpm build` green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pause without a key (CRM 2c0ba2b7)

- Window-preserving silence gate: while Silero heard no speech in the last
  16 scored steps a fire is impossible, so mel + embedding + classifier are
  skipped; ~2.2 s of raw audio is kept and the window is backfilled when
  speech starts. Decisions are bit-for-bit identical to the previous engine
  (frozen as a test-only reference: all phrases, 6 scenarios, 3 callback
  sizes, gated/ungated). Silence: 15.9 → 2.27 ms CPU per audio second.
- Mic callbacks reuse their buffers and hand the worker one batch per 80 ms
  step (~94 → 12.5 wakeups/s); the worker blocks on one channel (no 100 ms
  poll) and runs at UTILITY QoS; a lost mic stops it with a reason.
- Incremental mel NOT done: the mel model floors at 80 dB below the max of
  its whole input, so streaming it changes the features (pinned by a test).
- Bug: without an OpenAI key the wake word is PAUSED (mic off, choice kept)
  at boot, on config change and when the key is cleared; resumed when a key
  is saved. Switching it (and "Record false triggers") OFF is never blocked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… pause (CRM 2c0ba2b7)

The window-preserving silence gate backfilled up to 17 embeddings at speech
onset (17-22 ms on a P-core, unmeasured on E-cores) — the one change that
needs a real microphone to validate, and none is available. The engine is
back to origin/dev's per-step pipeline: mel + embedding + classifier on every
80 ms step (only `CHUNK` became `pub`, for the capture batcher).

Removed: the gate, its raw-audio ring, backfill / recompute-skipped-score /
self-disable paths, the frozen reference engine and every test, bench and
constant that only existed for them.

Kept: mic batching + buffer reuse, the blocking worker wait + lost-mic stop,
UTILITY QoS, the no-OpenAI-key pause and never-block-OFF toggles. Comments now
say what is true: the VAD vetoes fires, it does not gate work; there is no
silence-based battery guard. New test: batching per step changes no decision
(fires and debug candidates, bit-identical scores) vs per-callback feeding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…M 01393dfa)

With both side bars open, the thread text and the composer ran edge to edge
with 26px to spare. The reading column now keeps a responsive margin
(--cv-gutter: up to 56px, back to 26px only as the pane nears its floor, so
the composer still gets its 500px there), and its cap is a setting:
Display > Thread > Conversation width, 560-1080px in 40px steps, 760 by
default (was a fixed 840).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The deck's context gauge said how full the window is, not what fills it. The
state now carries the fill's breakdown (`context_usage`: cache read / cache
write / fresh input, plus the call's output) from the same root call that
sets `context_tokens` — live (message_start + message_delta, then the
result's last iteration), cold from the transcript, and from Codex's
`last` usage (cached tokens counted INSIDE input there, not on top). Each
turn_result carries what the turn consumed (`result.usage`).

The deck shows both as two stacked bars under one legend (Cached · Caching ·
Input · Output): the last call, and the session total over the turns the app
watched finish. Codex reports no per-turn usage, so its session row reads
"—" rather than a made-up zero.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…matic margin (CRM 01393dfa)

Per Alexandre: the app keeps today's look by default. sidePanelFitContent
now defaults OFF (full-height column) and conversationWidth to 840 — the
column's historical width. The responsive --cv-gutter is gone (back to the
fixed 26px); narrowing the width setting is what buys the margin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings the energy-audit work (machine_reachability, attachedMachineIds,
reconnect_remote_sessions, the SSE and wake-word fixes) the side panel's
upcoming Machine widget builds on.

- bindings.ts: dev dropped TickEvent, this branch added TokenUsage — both kept
  (regenerated from the merged Rust, identical).
- machineHealth.test.ts: dev's new neutral SessionStatePayload gains the
  branch's `context_usage` field.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…50f55614)

A review row raised while background work still runs (the re-alert on
background Bash finish edge) now keeps its blue and gets the green bg row's
violet wash on its right end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The conversation panel becomes a set of widgets the user switches on and
off, reorders and folds, from a Customize view in the panel itself (presets,
drag-to-reorder cards, a "collapsible sections" switch, reset). Everything
applies as you go; the layout persists in localStorage (tosse:sidepanel),
repaired against the catalogue on load. A widget switched off is never
mounted, and gives its old surface back (header chip, composer chip, todo
bar). Sections fold with a grid-row height animation and unmount their body
once folded; the telemetry deck folds to its head.

New widgets, each reading a real signal ("—" when unknown):
- Stats: average turn, files touched, calls, and the session's TOTAL tokens
  — the latest result.modelUsage (main loop + every sub-agent + workflow
  agents), seeded on reload from the transcript's last cost-state line.
  Fixes the deck's cost, which summed a cumulative figure.
- Git status: branch, sync with its upstream (incl. a gone upstream),
  staged / modified / untracked / conflicts; read-only git calls now pass
  --no-optional-locks, and git views refresh at each turn end.
- Plan usage: 5-hour / weekly / model windows with reset times, read from
  the shared cache (no new poll, no Keychain read on mount).
- Linked conversations: the conversations this one messaged or created,
  with live status and jumps to the matching message.
- Machine (bottom row): this Mac by name (new local_machine_name IPC) or the
  paired server with its reachability and link state.

Settings: the telemetry switch now drives its widget; a new "Conversation
panel widgets" row opens the Customize view.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d, fallbacks

From the multi-agent review of the widget stack:
- History replays in ONE store commit (applyItems): one set per item made
  every subscriber re-derive per item, and the Stats / Linked widgets turned
  opening a long conversation quadratic.
- The panel-closed summary line stands in for the goal and the todos each on
  its own: with the Todo widget off, an active /goal was shown nowhere.
- Customize: the card is its own dnd-kit activator, so Space / Enter on a
  switch toggles it instead of starting a keyboard drag; drag and drop
  animations follow the panel-animations switch; presets are aria-pressed
  buttons, not a radiogroup without arrow keys.
- Closing the panel leaves the Customize view; folding the telemetry deck
  from the keyboard keeps focus; no empty footer island; "every section is
  switched off" wording.
- Stats goes two by two at the panel's narrowest; its no-record tips name a
  rewind or an early fork among the causes.
- The git fs refresh matches the real paths FSEvents reports (/private/tmp,
  case-insensitive APFS).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ight

# Conflicts:
#	src/features/conversation/ConversationSidePanel.module.css
#	src/features/conversation/ConversationSidePanel.tsx
#	src/features/settings/SettingsPanel.tsx
#	src/store/display.ts
… Thread split into cards (CRM f065d069)

- The panel starts on Essentials (task, goal, todos, artifacts, stream). Standard is
  now task, goal, todos, artifacts, linked, stats, git + the three bottom rows.
- A stored layout still on the untouched v1 default (everything but telemetry) moves
  to Essentials once (layout v2); a layout the user chose keeps its visibility.
- Settings: the telemetry deck switch is gone (it is a panel widget, chosen in
  Customize). Display → Thread splits its 13-row card into Reading / Navigation &
  controls / Side panel / Artifacts; Appearance splits the workflow toggles into
  their own Workflows card. Search index follows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Alex375
Alex375 merged commit ed4a4c5 into main Sep 29, 2026
3 checks passed
Sign up for free to 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.

2 participants