Skip to content

feat(timeline): tool calls read as the web showed them, on every surface - #944

Merged
olafura merged 25 commits into
mainfrom
timeline-tool-calls-backlog
Nov 3, 2026
Merged

olafura merged 25 commits into
mainfrom
timeline-tool-calls-backlog

Conversation

@olafura

@olafura olafura commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

65 scenarios in features/timeline/tool-calls.feature were still @backlog or @backlog-<surface>: how tool calls read in a thread's timeline, as the web client and the old mobile app showed them, plus upstream pingdotgg/t3code#2829 and #13386. This makes all of them pass on the surfaces they name and takes their tags off; the file has no backlog left.

What changed

  • MC: a finished command whose output says it failed is marked failed (CommandOutput.failed?/1, reading at most the first 32 KB). A running tool's progress keeps the agent's message whole (#13386; the MC already did, so this adds the steps).
  • Desktop and phone (shared TimelineModel, TimelineSummary, Timeline.qml):
    • A finished turn's calls are summarised by the kind of work, and commands are named by their program.
    • HAL-C2's own tools get display names.
    • Each call has a details panel, and the changed-files card shows counts.
    • A call made through a website, an integration or an app shows that one's icon, ported from the old mobile app. App icons are signed like attachments, and only the rows that show one redraw.
  • Phone:
    • The timeline reads as its activity log: one live row while a turn runs, and calls that never reported an end are dropped once it settles.
    • Long-pressing a row copies it.
    • A turn's diff opens in the thread screen.
  • TUI: summaries, command labels and HAL-C2 tool names to match. A lone call reads as itself, as the web showed it.

Verified

Surface Run Result
Desktop tst_Features, every timeline glob 304 passed
Phone tst_Features, tool-calls.feature 141 passed
TUI tool-calls.feature 83 passed
TUI full CI set 1402 passed, 1 failed (an existing failure in the plugin-disabling scenario)
MC timeline/*.feature passes
MC turn_writer tests, format, compile with warnings as errors pass
  • Property tests: tst_TimelineModelProp gained the phone's brief mode and icon signing. Each property was checked to fail when its fix is reverted.
  • QML tests: desktop 787 passed, 1 failed (the existing test_sensibleTitleAndSize). Phone 105 passed.

Known failures left: the phone's full feature run fails 8 scenarios that were failing before this branch: #561, #230, #722 and the new #938. The phone's QML test entry uses the wrong test runner on some machines: #939.

Merged with main: main's phone fold mode (TimelineModel::Folds::Phone) now carries this branch's brief rules: one live row for a running turn, and unfinished calls, workspace preparation and already-carded delegations left out once a turn settles. The separate brief flag is gone. The phone's diff review and copy notice come from main, one implementation each.

Review fixes:

  • A command's output marks it failed only on a line that reads as a diagnostic, and the check reads its first and last 16 KB.
  • Signing a workspace image redraws only the rows that show it.

Done by Claude Opus 5.5 in Claude Code, running in HAL-C2.

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

olafura and others added 15 commits November 2, 2026 22:36
Upstream T3 Code closed the assistant's text segment on every tool update,
so a background command reporting progress cut a table in two (#13386).
The MC keys streamed text by message in every adapter (ACP, Codex,
Claude, Pi, Muse) and never closes it on a tool update, so the scenario
passes as it stands; it now runs on the MC against the ACP fake.

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: adeebahmad01 <52380344+adeebahmad01@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Some providers report a command completed when its output says the file
or command was missing, or that it exited non-zero. Clients never get a
command's output, so the MC reads it as the command finishes and sets
outputIndicatesFailure, which the wire already forwards.

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A tool call's details now show what the call did, by type:
- a command's exit, as "Process exited with code N"
- a file search's matches
- a web search's results, linked only when the address is http(s)
- a to-do list's done and open steps
- a changed file's relative path, counts, renames and "Open diff"
- an image the agent viewed, signed as a workspace file only when it is
  inside the workspace

An answered question previews its answer on the call's row, and a command
echoed back as its output is not shown twice.

The changed-files card hides zero counts. Right-clicking a file offers the
file menu, but only when the workspace can be located. A long open group
scrolls inside its own window: it reopens where the user left it, and
follows new calls only while the user is at its end.

Untags 15 @desktop scenarios and 4 @desktop @mobile ones (now
@backlog-mobile) in features/timeline/tool-calls.feature.

Co-authored-by: Alex Southwell <saphid@gmail.com>
Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com>
Co-authored-by: Craig <11655707+CodeZeno@users.noreply.github.com>
Co-authored-by: Henry Zhang <113233555+caezium@users.noreply.github.com>
Co-authored-by: Jake Leventhal <jakeleventhal@me.com>
Co-authored-by: Jono Kemball <Noojuno@users.noreply.github.com>
Co-authored-by: Julius Marminge <51714798+juliusmarminge@users.noreply.github.com>
Co-authored-by: maria <maria@kuuro.net>
Co-authored-by: Mark Griffin <mrmg@deflexion.net>
Co-authored-by: Mike Olson <mwolson@member.fsf.org>
Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>
Co-authored-by: ss <69873514+sandersonstabo@users.noreply.github.com>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
Co-authored-by: xxashxx-svg <xxanshxx9@gmail.com>
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-authored-by: Ymit24 <christianryansmith@gmail.com>
Co-authored-by: Yukun Shan <92423096+nateEc@users.noreply.github.com>
Co-authored-by: Zortos <zortosdev@proton.me>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… agent did

A settled turn's group of calls now collapses to one sentence (commands and
file changes first, at most two kinds named, the rest counted, failed calls
apart, a ✗ when one failed) and opens into each call: a command as it was
typed with its exit code, a file relative to the workspace. HAL-C2's own
tools are named for what they do, and a completed command whose output says
it failed, or a HAL-C2 tool that answered with an error, shows as failed.
Groups in a stopped or failed turn start open.

Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com>
Co-authored-by: Jake Leventhal <jakeleventhal@me.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: maria <maria@kuuro.net>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A provider can report a command as completed even though its output says it
failed (a non-zero exit code, "command failed"). The MC drops command output
from the wire, so clients cannot tell. TurnWriter now sets
outputIndicatesFailure on the item when CommandOutput.failed?/1 reads a failure
from the output, as the legacy toolOutput helper did.

Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… did

Activity group summaries now cover HAL-C2's own tools (display names, every
provider's name prefix, other servers left alone), integrations and named
sources, thoughts, the "other actions" count, "Tried to" for all-failed
groups, and failures read from a command's output or a HAL-C2 tool's error
answer. A running turn's last command reads by the program it runs ("Running
git", "Ran npm", "Failed cargo") through shells and set-up wrappers, with the
raw command kept in the call's details. The live verb is folded into the
TimelineModel property so a settle redraws only when the label changes.

Untags the desktop summary and command-naming scenarios in
features/timeline/tool-calls.feature.

Co-authored-by: Bilal Bakr <62337003+Bil0000@users.noreply.github.com>
Co-authored-by: Jake Leventhal <jakeleventhal@me.com>
Co-authored-by: Jono Kemball <Noojuno@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: maria <maria@kuuro.net>
Co-authored-by: Mike Olson <mwolson@member.fsf.org>
Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: Utkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com>
Co-authored-by: xxashxx-svg <xxanshxx9@gmail.com>
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-authored-by: Yukun Shan <92423096+nateEc@users.noreply.github.com>
Co-authored-by: Zortos <zortosdev@proton.me>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…backlog

# Conflicts:
#	apps/server-ex/lib/hal_c2/orchestration/turn_writer.ex
#	features/timeline/tool-calls.feature
A finished turn's group holding one call showed a summary of it ("Ran 1
command") on the TUI, where the web and the desktop show the call's own
line. Only groups of two or more items are summarised now; a lone call is
its row again, its failure marked on it.

The two summary outlines exercised the singular wording through lone
calls, so each group now also holds a thought, which a summary does not
count, and both outlines pass on the desktop and the TUI.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Both merged desktop branches defined "the user opens the call's details",
so every scenario using it was ambiguous. The details steps keep it and
open a settled turn's fold first. A running turn's latest command now
reads by its program ("Ran npm"), so "the details show the command once"
no longer expects the line to read the command itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The phone drew a thread's tool calls exactly as the desktop does, so none of
tool-calls.feature ran on it, and the activity log the phone had always had
(one live row while a turn runs, long press to copy, failures and markers on
their own rows, no lost or carded calls) was gone.

TimelineModel gains a brief mode, which ThreadStore sets on every timeline
when the phone layout shows (MobileApp's layout.desktop interceptor). In it,
a running turn ends in a Thinking row unless its latest call is running or
succeeded, a settled turn drops calls that never ended, the workspace's
preparation is left out, and a delegation whose subagent has a card is not
listed again. The Timeline brick copies a call or a failure on a long press
and says "Copied" for a moment, and the failure row always shows its time
there. The desktop keeps its behaviour by default.

The phone now runs timeline/tool-calls.feature through ToolCallSteps.cpp,
which drives the real phone screen against the fake MC.

Co-authored-by: Alex Southwell <saphid@gmail.com>
Co-authored-by: Chris Deeming <chris@xenforo.com>
Co-authored-by: Gabriel De Andrade <30420087+gabrielelpidio@users.noreply.github.com>
Co-authored-by: James C <134711311+Exotic209093@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: Julius Marminge <julius@mac.lan>
Co-authored-by: maria <maria@kuuro.net>
Co-authored-by: maria-rcks <maria@kuuro.net>
Co-authored-by: Mina Yacoub <56601613+myacoub91@users.noreply.github.com>
Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: Wout Stiens <71498452+StiensWout@users.noreply.github.com>
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The phone's activity log (TimelineModel::brief) now runs under the same
command model as the desktop timeline: a settled turn leaves out calls
that never ended, and a running latest turn ends in "Thinking" unless its
last call is running or went well, or its reply is streaming.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ff opens

The phone's work-group summaries had no steps for the kinds of work and
HAL-C2's own tools, and a turn's changes or one changed file had nowhere to
open on the phone.

The Diff tab now takes the phone's thread screen like the Files tab does,
and Back closes it. The phone's steps cover every kind of work the desktop's
do, a group led by a thought, and opening a turn's diff or a single file's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pp's own icon

The MC names each call's own icon (a website, an integration's themed logo, an
app on the environment's machine), but the Qt clients only ever drew the
kind's generic icon.

TimelineModel now gives each call an icon address for each appearance, chosen
as the old mobile app did: the provider's favicon, a site whose favicon is
illegible in that appearance, then the origin's favicon.ico. An app's icon is
signed through assets.createUrl like an attachment, and only the work rows
showing it redraw when it lands. The timeline draws the call's own icon over
the kind's once it loads, and keeps the kind's when it cannot load. The phone's
scenario now runs green, and the timeline property signs the app's icon among
its commands.

Co-authored-by: Alex Southwell <saphid@gmail.com>
Co-authored-by: Chris Deeming <chris@xenforo.com>
Co-authored-by: Felipe Franco <67972456+fe-franco@users.noreply.github.com>
Co-authored-by: Gabriel De Andrade <30420087+gabrielelpidio@users.noreply.github.com>
Co-authored-by: James C <134711311+Exotic209093@users.noreply.github.com>
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: maria <maria@kuuro.net>
Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>
Co-authored-by: Theo Browne <me@t3.gg>
Co-authored-by: Yash Singh <saiansh2525@gmail.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 findings: 1 medium, 2 low.

This takes the last 65 tool-call scenarios off backlog across the MC, the shared Qt timeline, the phone's brief mode and the TUI. It matches the description, and the brief mode, the icon signing and the HAL-C2 tool inventory read correctly. The biggest risk is the MC's new outputIndicatesFailure heuristic. The MC writes it into the event log for good. It reads only the first 32 KB, so it misses an exit code printed at the end of a long output. It also marks a successful cat or grep as failed when the text it prints contains a failure phrase.

Not run

  • Desktop and phone tst_Features, and the tst_TimelineModelProp property test: they need a full Qt build; the PR reports them passing, and I reviewed the code by reading it

Blocked on the reviewing machine (a setup problem there, not in this change)

  • mise exec -- mix test test/hal_c2/turn_writer_test.exs: (Mix) Can't continue due to errors on dependencies: bandit, slack_elixir, tz and others aren't available ("run mix deps.get") in this review checkout
Ran 2 checks
  • bun test src/toolSummary.test.ts (apps/tui): 11 pass, 0 fail
  • elixir -r lib/hal_c2/orchestration/command_output.ex: failed? on 63 KB of output ending in 'Exit code 1', and on a source line containing 'ENOENT': false for the long output (trailer missed), true for the ENOENT line (false positive)

Findings (3)

  • 🟡 Medium: apps/server-ex/lib/hal_c2/orchestration/command_output.ex:20 — The comment says a long output shows how it ended near its start, but trailers such as `<e…
  • 🔵 Low: apps/server-ex/lib/hal_c2/orchestration/command_output.ex:9 — The @failures patterns match any output that merely contains the words. A completed `cat…
  • 🔵 Low: apps/desktop-qt/src/native/TimelineModel.cpp:1505 — Each time a workspace-file: image is signed, every work row redraws, not just the rows t…

The phone's timeline has one mode: main's TimelineModel::Folds::Phone now
also carries what this branch's `brief` did (a running turn ends in one
live row, a settled turn drops calls that never ended, the workspace's
preparation and carded delegations are left out), and the `brief` flag and
ThreadStore::setBriefTimelines are gone. The phone's diff, copy and
setEntity/changeEntity changes take main's one implementation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No findings.

Since the last review the only new commit is the merge with main. It folds this branch's phone "brief" mode into main's TimelineModel::Folds::Phone, and the merge holds together. In project() the brief rules are now gated on phone(): settled turns drop calls that never ended, carded delegations and workspace preparation are hidden, and a running turn ends in one live row. Main's failed-turn rule (one call per row) and its keep-the-first-reply rule are kept alongside. ThreadStore::setFolds and MobileApp's layout interceptor replace setBriefTimelines. tst_TimelineModelProp folds the separate brief() property into the main property with a Layout command, so both modes and the switch between them are now exercised. Hold-to-copy still goes through Shell.setClipboardText, so copy() now returning a string does not affect it. Nothing references brief any more.

None of my three earlier findings is fixed; the code is the same:

  • CommandOutput.failed?/1 reads only the first 32 KB, so an exit-code trailer at the end of a long output is missed.
  • Its patterns match any output that merely contains words like ENOENT, and the result is written to the event log.
  • setAttachmentUrl still redraws every work row for each workspace-file: signing.

The first two are the biggest risk, because the wrong outputIndicatesFailure is persisted.

Not run

  • mise prop:desktop tst_TimelineModelProp and the desktop/phone tst_Features runs after the merge: A full Qt build is needed; the merge was checked by reading the model next to its RapidCheck spec, which match.
  • mix features timeline/*.feature: The MC files in this PR are unchanged since the last review; turn_writer.ex changed only through main's commits.
Ran 2 checks
  • git diff 2c05b094a HEAD restricted to the PR's files: The only change is the merge with main; command_output.ex is unchanged, and so is the setAttachmentUrl branch for workspace-file images.
  • grep for brief/setBrief/briefChanged in apps/desktop-qt and apps/mobile-qt: Nothing left over from the rename.

…at both ends

A long output's exit code sits at its end, which the first 32 KB missed, and a
file that merely mentioned ENOENT or "command not found" marked a successful
read failed, for good, in the event log. The check now reads the first and last
16 KB and counts a missing file or command only on a line that starts with it
or with a program's name and a colon.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 finding: 1 medium.

The new commit narrows CommandOutput.failed?/1: a missing-file or missing-command phrase now counts only at the start of a line or after a program: prefix, and a long output is read at both ends, so an exit code at the end is still seen. My previous review listed no open findings, so there is nothing to carry over. Reading both ends is correct. The new prefix only allows a bare word, though, so path-prefixed shell errors such as /bin/bash: line 1: foo: command not found and /usr/bin/env: 'python': No such file or directory no longer mark the command failed.

Not run

  • mise exec -- mix test test/hal_c2/turn_writer_test.exs: The review checkout has no fetched Hex deps (mix run stopped with "run mix deps.get"), and fetching them was outside the review's scope.
Ran 1 check
  • elixir $TMPDIR/t2.exs (the new @missing regex on sample lines): /bin/bash: line 1: … command not found, ./run.sh: … and /usr/bin/env-style lines do not match; bash: line 1: …, Makefile:12: no such file and note: … ENOENT do

`/bin/bash: line 1: foo: command not found` and `/usr/bin/env: 'python': No such
file or directory` read as a success, since only a bare program name was allowed
before the colon. A program's path is allowed now, and the colon must be followed
by a space, which keeps out a grep match's `path:line:` (`Makefile:12: …`).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 finding: 1 medium.

The new commit lets a diagnostic that names a program by its path (/bin/bash: line 1: …, ./run.sh: …, /usr/bin/env: …) fail a finished command, which fixes my previous finding. The new prefix now accepts / and ., though, so a grep or rg match printed without line numbers and followed by a space now reads as a failure. That is the false positive the old prefix kept out.

Blocked on the reviewing machine (a setup problem there, not in this change)

  • mise exec -- mix test test/hal_c2/turn_writer_test.exs: deps missing in this checkout: "the dependency is not available, run mix deps.get" (e.g. tz)
Ran 2 checks
  • git diff 888dc64d7 HEAD: one commit: the @missing regex prefix changed, plus 3 new failure cases and 1 new success case in turn_writer_test.exs
  • elixir -e (new @missing regex against grep-style lines): lib/foo.ex: {:error, :enoent} -> :ok and src/a.ts: // no such file here match (would fail the command); lib/foo.ex:12: enoent does not

`grep -r` and piped `rg` print `path: content`, which the path prefix read as a
diagnostic. A path counts now only when what follows it reads as a shell's or
env's report (`line 1:`, `'python':`); a bare program name counts as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

1 finding: 1 low.

The new commit narrows CommandOutput's diagnostic prefix. It now takes either a bare program name followed by : or a path followed by : line N: or a quoted name, so search output like path:content from grep without -n, or from piped rg, no longer marks a command failed. That addresses my previous finding. The main risk left is at the edges of the prefix rule: grep matches in extensionless top-level files can still count as failures, and older bash's /bin/bash: foo: command not found now goes unnoticed. Neither should block the merge.

Blocked on the reviewing machine (a setup problem there, not in this change)

  • mise exec -- mix test test/hal_c2/turn_writer_test.exs: the dependency is not available, run "mix deps.get" (tz missing from this review checkout); I did not fetch deps
Ran 2 checks
  • git diff 3b40f466a HEAD: one commit: the regex change in command_output.ex plus two new negative cases in turn_writer_test.exs
  • elixir script matching the new @missing regex against sample lines: lib/foo.ex: {:error, :enoent} → no match (the fix works); /bin/bash: line 1: ... and /usr/bin/env: 'python': ... → match; Makefile: # no such file here → match (false positive); /bin/bash: foo: command not found → no match (missed)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

No findings.

The only new commit since my last review (4549da8) changes no code. It extends the comment above @missing in command_output.ex to name the two edges I raised earlier: a hit in an extensionless file at the search root, like Makefile: # no such file, still marks the command failed, and pre-5.1 /bin/bash: foo: command not found does not. My earlier finding is settled by accepting both edges and documenting them. The comment matches the regex, so both trade-offs are now explicit and nothing new is open.

Not run

  • mix test for turn_writer and command_output: The new commit only changes a comment, so the tests have nothing new to cover.
Ran 1 check
  • git diff 72f7f0bd9a7a9cd688e42331437868fa1586cbcc HEAD: One file changed: a 4-line comment edit in apps/server-ex/lib/hal_c2/orchestration/command_output.ex. The regex and the code are unchanged.

The result's tap emitted linkActivated with one argument and its step spied
on a one-argument signal the brick does not have, so the scenario could not
see the link open. It passes `external` (false) as every other link does,
and the step listens for the signal the brick declares.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

No findings.

Since the last review, one commit landed: tapping a web result now emits the brick's two-argument linkActivated(link, false), the same way every other link in the timeline does. Its step now spies on linkActivated(QString, bool). Before this, the one-argument call didn't match the signal the brick declares, so the scenario could never see the link open. The fix is correct and consistent with the brick's other link paths. The previous review raised no findings, so nothing carries over, and the new commit introduces no new risk.

Not run

  • tst_Features for the timeline details web-result scenario: needs a desktop-qt native build. The change is a two-line signature match that reading the code settles.
Ran 2 checks
  • git diff 4549da83e..HEAD: one commit: Timeline.qml:1832 and TimelineDetailsSteps.cpp:267
  • grep linkActivated( in desktop-qt and mobile-qt QML and tests: every call to root.linkActivated in Timeline.qml and Markdown.qml now passes two arguments, matching signal linkActivated(string link, bool external) at Timeline.qml:74. The only QSignalSpy on it uses the (QString, bool) signature.

Main's prop test starts runs in "starting"; the model gave only running
turns the live thinking row, while TimelineModel (as the web) counts a
starting session as working.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

No findings.

Since the last review there is one test-only commit. In the property model, the phone's live "Thinking" row now shows for a latest turn that is starting as well as running. That matches TimelineModel.cpp:1327, where kRunning includes starting, and it matches main's prop generator, which starts runs as starting. The previous review had no findings, so there is nothing to follow up. The remaining gap is small: kRunning also counts preparing and waiting, which the model doesn't handle, but the generator never produces those statuses today, so the property can't diverge from the code yet.

Not run

  • mise prop:desktop TimelineModelProp: This is a two-line change to the model predicate, and reading it against the implementation's status set settled it. Building the Qt prop suite wasn't needed for that.
Ran 2 checks
  • git diff 48eeb0e64 HEAD: One file changed: apps/desktop-qt/tests/prop/tst_TimelineModelProp.cpp, 4 lines added and 3 removed
  • grep starting/kRunning in TimelineModel.cpp and tst_TimelineModelProp.cpp: The implementation's kRunning set covers preparing/starting/running/waiting. The prop generator only produces starting/running, and the model now accepts both.

… show it

Every work row redrew for each signing, and asked again for any image whose
signing had failed. Rows now redraw only when one of their calls shows the
image and its address changed, as an app's icon already did.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

No findings.

The new commit (ee67940) brings workspace-image signing into line with app-icon signing. A signed workspace-file: address now redraws only the work rows that have an entry with that imageId, and only when the address or its expiry changed. Before, it redrew every work row, and the QML then asked again for images whose signing had failed. The key it matches on is the one entryFor writes (TimelineModel.cpp:1745), so the change is correct. My previous review had no findings, so nothing carries over. The one remaining gap is small: the new SignImage branch of the property test only checks that an image no call viewed redraws nothing. Nothing in the property model checks the other direction, that a row showing the image does redraw.

Not run

  • mise prop:desktop tst_TimelineModelProp: the change is small and a read settles it; the author reports running the property suite
Ran 2 checks
  • git diff 3807633b4 HEAD: one commit, TimelineModel.cpp setAttachmentUrl and the tst_TimelineModelProp SignIcon command
  • grep imageId / workspace-file: in TimelineModel.cpp: entries write imageId = "workspace-file:" + path, the same key setAttachmentUrl now matches on

The merge left StreamingSteps and ToolCallSteps each defining "the user is
looking at a thread in", "it reads" and "the turn completes", so every
phone timeline scenario stopped on an ambiguous step. Main's background
step is the only one now; "it reads" and "the turn completes" ask
StreamingSteps first, as FilesSteps asks ToolCallSteps for a viewed image.
A failed turn stays open on the phone, so the copy scenario opens a fold
only when there is one.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

No findings.

This PR takes the tool-call scenarios off the backlog on the MC, desktop, phone and TUI. The new commit (f72a018) removes the step definitions the phone's StreamingSteps and ToolCallSteps both defined: "the user is looking at a thread in", "it reads" and "the turn completes". StreamingSteps now owns the first. For the other two, StreamingSteps handles its own turn and leaves the rest to ToolCallSteps: "it reads" goes to StreamingSteps once a duration was shown, and "the turn completes" once a run was started. My previous review had no open findings. The remaining risk is small: tool-calls scenarios now open their thread through StreamingSteps' setup, which also pins the clock and locale and sets a stream handle. The PR's phone run table may predate this commit, so rerun tool-calls.feature and streaming.feature on the phone before merging.

Not run

  • Phone tst_Features for timeline/streaming.feature and timeline/tool-calls.feature at f72a018: Building and running the Qt feature tests was out of scope for this incremental test-only review. The author's run table may predate this commit.
Ran 2 checks
  • git diff ee67940817b8c6dc1857275757c9d1f3373a96cd HEAD: one test-only commit touching apps/mobile-qt/tests/features/StreamingSteps.cpp and ToolCallSteps.cpp
  • grep 'the turn completes' / 'it reads' across features/: In tool-calls.feature, the one scenario that uses "the turn completes" (changed files) never calls startRun, so Turn.run stays empty and that step falls through to ToolCallSteps. The "it reads" scenarios in tool-calls never set durationShown, so they also fall through.

…acklog

# Conflicts:
#	apps/mobile-qt/src/MobileApp.cpp

@olafura olafura left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved

No findings.

Since f72a018 the only new commit is the merge of origin/main (135d4b0). The one conflict, in apps/mobile-qt/src/MobileApp.cpp, is resolved correctly. It keeps main's TerminalController::setSingleScreen (and drops the old setDrawn call) and the branch's ThreadStore::setFolds(Folds::Phone/Desktop), both at startup and in the layout.desktop interceptor, and the comment now covers both. My previous review raised no findings, so nothing is carried over. The biggest remaining risk is untested: main's Run-from-code-block steps in StreamingSteps.cpp now sit next to the branch's ToolCallSteps and Phone fold mode, and I couldn't build or run the phone feature suite here. I checked by reading that their step patterns don't overlap and their helpers live in separate anonymous namespaces. The PR description's note that the brief/fold reconciliation is still to come in the merge is now stale; it is done.

Not run

  • mobile tst_Features on timeline/streaming.feature and timeline/tool-calls.feature after the merge: this checkout has no Qt build directory, and building the phone app was out of scope for re-reviewing a merge commit
Ran 3 checks
  • git show --cc 135d4b067: one conflicted hunk in MobileApp.cpp: it keeps main's setSingleScreen and the branch's setFolds, in both places they are called
  • comm of files changed on both sides since the merge base: 5 files; I read the 4 that merged without a conflict (ThreadScreen.qml, FilesSteps.cpp, StreamingSteps.cpp, tst_Features.cpp). Each carries only main's changes, and none clashes with the branch
  • grep step patterns and namespaces in StreamingSteps.cpp / ToolCallSteps.cpp / TerminalSteps.cpp: main's new code-block Run steps have no ambiguous overlap with the tool-call steps; the two files' collect helpers are each in their own anonymous namespace

@olafura
olafura merged commit 0530fd9 into main Nov 3, 2026
15 checks passed
@olafura
olafura deleted the timeline-tool-calls-backlog branch November 3, 2026 21:34
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.

1 participant