Skip to content

fix(room): report a failed run beside its reply with the run's own notice - #611

Merged
91jaeminjo merged 1 commit into
mainfrom
refactor/602-failure-notice-beside-reply
Oct 9, 2026
Merged

91jaeminjo merged 1 commit into
mainfrom
refactor/602-failure-notice-beside-reply

Conversation

@91jaeminjo

Copy link
Copy Markdown
Collaborator

Summary

PR 1 of #602 (thread replay parity): failure notice beside the reply.

A run that failed after replying used to show a red ErrorMessage row under
the reply. That row knew only the error text, so it offered no way to view or
add the run's note, and it read as a fresh error on every reload. Now the run
reports itself with its own outcome tile, the same muted notice every other
outcome uses:

[N events] [Thinking]          band, on the reply
Assistant
  <reply>
ⓘ Run failed: <detail>        the run's own outcome tile
  View or add a note
  <time>
  [copy]

Red stays where it means "this just happened": the live, dismissible banner
above the input, unchanged.

Changes

  • lay_out_timeline.dart: _guaranteeATilePerRun places the run's
    NoResponseTile after the reply instead of minting an ErrorMessage, and
    returns the ids it placed beside a reply. RenderedTile gains
    besideReply. _placeBands uses one hostsBand rule, so a tile beside a
    reply never hosts a band, and a band keyed by its id reads as unclaimed and
    goes to the run's last response.
  • NoResponseTileWidget / MessageTile: take besideReply (required, one
    caller each). Beside a reply the tile shows only the reason, the note
    button, the time and the copy button, with no second "Assistant" label.
    With no error text it says "Run failed", because "Run failed without a
    response" would contradict the reply above it. Whitespace-only text
    counts as no text.
  • Every failed notice with error text, beside a reply or standing in for
    its run, has a copy button that copies the error text without the
    "Run failed:" label.
  • runErrorMessageId and the code that minted it are removed.
    ErrorMessage remains for the pre-run RUN_ERROR only.
  • Doc comments that described the old row are corrected
    (run_ending.dart _park, the NoResponseTile class doc, layout docs).

Breaking / behaviour notes

  • Library consumers: runErrorMessageId is no longer exported from
    soliplex_client or soliplex_agent (CHANGELOG).
  • A thread whose last seen message was the old red row misses the "New
    messages" divider once after updating: its saved position names
    run-error-<run>, which no tile carries any more. New positions are
    saved from the same layout the screen draws, so the divider works for
    them (CHANGELOG).

Tests

  • Red on main first: the layout puts the notice after the reply, not an
    ErrorMessage. The timeline renders the failure through the notice
    under a single author label.
  • Band placement uses the shape both live and reload produce. I confirmed
    that shape against the real producers: a whitespace response after a
    tool result, then a failure.
  • The owed outcome (a run with a reply and work no message spoke for)
    stays off the beside path and keeps its band.
  • Widget tests cover:
    • the beside notice hiding band, phase, thinking and author;
    • the wording with and without detail;
    • the copy button in both placements.
  • Updated the tests the plan lists:
    • lay_out_timeline_test;
    • derived_timeline_integration_test (both failure tests);
    • live_reload_parity_test (record destructuring);
    • message_tile_test and no_response_tile_widget_test (new
      parameter).
  • Removed the tests in this change that repeated another test's input or
    passed without the change. Each of the rules above was checked by
    breaking it in production code; a test went red every time.

Test Plan

  • flutter analyze --fatal-infos: no issues
  • App tests (--exclude-tags golden): 2961 pass
  • soliplex_client 1848 and soliplex_agent 574 pass
    (--exclude-tags integration)
  • markdownlint on CHANGELOG.md
  • Driven in Chrome against the fault-injection backend, live and after
    switching threads and back:
    • runerror: band on the reply, muted notice, note button, copy (copies
      "Injected failure"), red banner live.
    • Whitespace-only error text: "Run failed", no copy button.
    • cutoff: live, the partial reply plus the notice beside it. Reload
      still drops the partial reply (unchanged; PR 4 of Thread-history replay duplicates the live run path's citation and drop handling #602), so the
      notice stands in with band, note and copy.
    • Normal run: no notice.
    • RAG in the local chat room: reply, band and 2 sources identical
      live and reopened.

Found while testing, not changed here

  • ag_ui's decoder rejects a RUN_ERROR with an empty message
    (requireNonEmpty), so such a run reads as cut off, not failed.
  • The red banner renders whitespace-only error text as a blank line.
  • An undated RUN_ERROR notice has no time live, and on reload is dated at
    run start (before its reply). PRs 4–5 of Thread-history replay duplicates the live run path's citation and drop handling #602 fix this.
  • In the timeline, each tile's text merges into one semantics node labelled
    "Copy". This applies to replies and the pre-run row as well, so it is not
    new here.
  • thread_view_state_test's "a send from a disposed view does not throw"
    still prints its known "read after disposed" warning.

Related Issues

Part of #602

🤖 Generated with Claude Code

…tice

A run that failed after replying showed a red ErrorMessage row under the
reply. The row knew only the error text, so it offered no way to view or
add the run's note, and it looked like a fresh error on every reload.

Layout now places the run's own NoResponseTile after the reply, marked
besideReply on RenderedTile. That tile hosts no band (the reply keeps the
run's work), and a band keyed by its id reads as unclaimed and goes to the
run's last response. NoResponseTileWidget shows only the reason, "View or
add a note", the time and a copy button for it, with no second
"Assistant" label. With no error text it says "Run failed" rather than
"Run failed without a response", which the reply above would contradict.

Every failed notice with error text now has a copy button for that text.
Error text that is only whitespace counts as none. ErrorMessage remains
for the pre-run RUN_ERROR only; runErrorMessageId is removed from
soliplex_client (and so from soliplex_agent, which re-exports it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@91jaeminjo
91jaeminjo merged commit eb83998 into main Oct 9, 2026
6 checks passed
@91jaeminjo
91jaeminjo deleted the refactor/602-failure-notice-beside-reply branch October 9, 2026 23:16
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