Skip to content

fix(code): consistent styling and links for thread status messages - #5118

Merged
Mason Daugherty (mdrxy) merged 6 commits into
mainfrom
mdrxy/code/thread-message-link-styling
Jul 29, 2026
Merged

fix(code): consistent styling and links for thread status messages#5118
Mason Daugherty (mdrxy) merged 6 commits into
mainfrom
mdrxy/code/thread-message-link-styling

Conversation

@mdrxy

@mdrxy Mason Daugherty (mdrxy) commented Jul 28, 2026

Copy link
Copy Markdown
Member

Thread status notes in the transcript (Resumed thread:, Started new thread:, Previous thread:) now all render with the same dim italic styling, and the mouse pointer changes to a hand when hovering a link inside any app message. Re-selecting the thread the session is already on now shows a transient toast instead of an inline chat message, matching the existing same-model behavior.


Several related inconsistencies, all visible in a single resume:

  • AppMessage applies dim italic only when it is handed a plain string; a pre-built Content is rendered as-is. Thread notes start life as plain strings and are upgraded to linked Content once the LangSmith URL resolves, so they visibly brightened relative to every other app message. _build_thread_message now styles each span dim italic, matching what AppMessage does for strings (and what other linked app messages such as /trace output already do explicitly).
  • AppMessage carries a text pointer in CSS with no hover handling, so an embedded link gave no cursor affordance. It now toggles the pointer on MouseMove/Leave using the existing event_targets_link helper, mirroring AssistantMessage. Link clicks still route through open_style_link, so URL safety checks are unchanged.
  • The Already on thread note was the odd one out: an inline transcript message for something that changed no state, where the equivalent Already using <model> no-op is a toast. It is now a toast too. Its dedup guard — don't stack an identical toast while the previous one is presumed still on-screen, but do re-announce once that has expired — is hoisted into _notify_unchanged_once and shared by both call sites. A successful thread switch re-arms the toast. The sibling Switched to thread directory message stays inline because it records a real state change.

Made by Open SWE

`AppMessage` dims plain-string messages but leaves pre-built `Content`
untouched, so thread notes brightened as soon as their LangSmith link
resolved. `_build_thread_message` now carries dim italic spans, the
"Already on thread" note resolves a link like its siblings, and hovering
an embedded link in any app message switches the mouse pointer.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
@github-actions github-actions Bot added dcode Related to `deepagents-code` fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: S 50-199 LOC labels Jul 28, 2026
Re-selecting the thread the session is already on is transient feedback,
not part of the conversation — the same reasoning that already makes the
same-model no-op a toast. Hoists that path's dedup guard into
`_notify_unchanged_once` so both share one implementation and neither can
stack duplicate toasts while the previous one is still on-screen.

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
@github-actions github-actions Bot added size: M 200-499 LOC and removed size: S 50-199 LOC labels Jul 28, 2026
…sage-link-styling

# Conflicts:
#	libs/code/deepagents_code/app.py
Correct `_last_thread_unchanged`'s docstring, which called the field the
same-model counterpart of `_last_model_unchanged` while its own summary
line calls it the same-thread toast.

Log the same-thread no-op resume. The `Already on thread` notice is a
toast, which is transient and suppressible, and `/threads -r` mounts the
user's command echo before dispatching -- so a no-op left the transcript
showing the command and nothing else, indistinguishable from a dropped
command. The same-model path already logs unconditionally; this mirrors
it, outside the dedup guard.

Document what `_notify_unchanged_once` actually guarantees: suppression
is time-based and never inspects live toast state, so "on-screen" is a
presumption -- a toast clicked away still suppresses. Its window matches
the toast lifetime only because `notify` is called without a `timeout`
override, which nothing else records. Note that callers own the record,
and give `markup=False` the reason it is load-bearing. The return type
drops `| None`: the suppression branch is guarded by `last is not None`,
so only the explicit re-arm sites clear a caller's field.

Replace two tests that could not fail:

- `test_hovering_text_keeps_text_pointer` asserted `pointer == "text"`,
  already the CSS default on a fresh widget. Deleting the handler's
  entire `else "text"` arm left it green. The new test covers what that
  arm exists for -- moving off a link onto plain text without leaving
  the widget, where `on_leave` cannot help and the hand cursor would
  otherwise stick.
- `test_linked_content_matches_plain_app_message_styling` hardcoded the
  same `dim`/`italic` literals as the implementation, so a change to
  `AppMessage` could break the parity it names while staying green. It
  now reads the expected style off a real `AppMessage`.

Make the re-arm test behavioral. It asserted private state directly and
passed even with the helper broken; it now pins the clock and asserts
two toasts across an A -> B -> A round trip, mirroring the same-model
counterpart.
@mdrxy
Mason Daugherty (mdrxy) marked this pull request as ready for review July 29, 2026 20:59
Mason Daugherty (mdrxy) and others added 2 commits July 29, 2026 16:59
…nter

Three fixes in the code paths this branch already touches, each of which
was invisible rather than wrong.

`_build_thread_message` swallowed every LangSmith URL failure without a
trace. Unlinked thread IDs are the only symptom, and they look identical
to tracing simply not being configured -- so a rejected
`LANGSMITH_API_KEY`, a changed payload shape, or a network failure left
the user with no diagnostic and no reason to suspect misconfiguration.
Log the cause at debug with `exc_info`. The `except (TimeoutError,
Exception)` tuple collapses to `except Exception`, which is all it ever
meant (`TimeoutError` inherits from `OSError`); its `noqa: BLE001` is
dropped because the rule was flagging the missing log, not the breadth
of the catch.

`_resume_thread` set `previous_thread_id` once the switch was materially
complete, but `_run_session_start_hook` runs after that and can raise --
it awaits `on_session_start` and `_mount_message` with no guard. Rollback
restored `thread_id` and `_lc_thread_id` but not the back-pointer, so a
raise there left `previous == current`: a later bare `/threads -r`
resolved to the thread already active and reported the no-op, with
nowhere to step back to. Capture and restore it alongside the other
rollback state. The comment claiming the assignment sits after "the last
statement that can raise" is corrected -- it never did.

Add the one test that verifies Textual actually delivers hover events to
these widgets. There are ten `on_mouse_move` handlers across seven widget
files and every test for them calls the handler directly with a stand-in
event, so nothing anywhere proved `MouseMove` reaches the widget or that
`event.style` is populated at the hovered offset. One `pilot.hover` test
over a real OSC 8 span covers the assumption the whole family shares.
@mdrxy
Mason Daugherty (mdrxy) merged commit 9ad8dde into main Jul 29, 2026
69 checks passed
@mdrxy
Mason Daugherty (mdrxy) deleted the mdrxy/code/thread-message-link-styling branch July 29, 2026 21:15
Marcelo5444 pushed a commit to Marcelo5444/deepagents that referenced this pull request Jul 30, 2026
…angchain-ai#5118)

Thread status notes in the transcript (`Resumed thread:`, `Started new
thread:`, `Previous thread:`) now all render with the same dim italic
styling, and the mouse pointer changes to a hand when hovering a link
inside any app message. Re-selecting the thread the session is already
on now shows a transient toast instead of an inline chat message,
matching the existing same-model behavior.

---

Several related inconsistencies, all visible in a single resume:

- `AppMessage` applies `dim italic` only when it is handed a plain
string; a pre-built `Content` is rendered as-is. Thread notes start life
as plain strings and are upgraded to linked `Content` once the LangSmith
URL resolves, so they visibly brightened relative to every other app
message. `_build_thread_message` now styles each span `dim italic`,
matching what `AppMessage` does for strings (and what other linked app
messages such as `/trace` output already do explicitly).
- `AppMessage` carries a `text` pointer in CSS with no hover handling,
so an embedded link gave no cursor affordance. It now toggles the
pointer on `MouseMove`/`Leave` using the existing `event_targets_link`
helper, mirroring `AssistantMessage`. Link clicks still route through
`open_style_link`, so URL safety checks are unchanged.
- The `Already on thread` note was the odd one out: an inline transcript
message for something that changed no state, where the equivalent
`Already using <model>` no-op is a toast. It is now a toast too. Its
dedup guard — don't stack an identical toast while the previous one is
presumed still on-screen, but do re-announce once that has expired — is
hoisted into `_notify_unchanged_once` and shared by both call sites. A
successful thread switch re-arms the toast. The sibling `Switched to
thread directory` message stays inline because it records a real state
change.

Made by [Open
SWE](https://openswe.vercel.app/agents/4b51fe78-171c-1374-8355-489bab7e616d)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy) pushed a commit that referenced this pull request Jul 30, 2026
> [!CAUTION]
> Merging this PR will automatically publish to **PyPI** and create a
**GitHub release**.

For the full release process, see
[`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md).

---

_Release notes preview: keep this section in sync with the package
`CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`,
not this PR description — keep them aligned anyway so the PR stays an
accurate historical record for reviewers and anyone returning later._

---


##
[0.1.50](deepagents-code==0.1.49...deepagents-code==0.1.50)
(2026-07-30)

### Highlights

- Added project hooks workspace trust and expanded Hooks v2 support with
client and server lifecycle events plus runtime feedback
([#5105](#5105),
[#5104](#5104),
[#4997](#4997),
[#5045](#5045)).
- Added an option to mute the “YOLO is active” toast
([#5103](#5103)).
- Made the splash screen `thread` ID clickable to copy it
([#5173](#5173)).
- Show `ask_user` answers directly on the answered tool row
([#5100](#5100)).
- Show a toast when submitting an empty required `ask_user` answer
([#5095](#5095)).
- Added thread message counts to the Debug Console
([#5117](#5117)).

### Fixes and improvements

- Gated Hooks v2 behind `DEEPAGENTS_CODE_EXPERIMENTAL` and improved hook
resume stability across identity and Command tool results
([#5146](#5146),
[#5176](#5176)).
- Kept server hook state out of task results
([#5164](#5164)).
- Stopped duplicate Auto transcript events during interrupt replay
([#5157](#5157)).
- Kept `/update` and `/install --package` prompts responsive
([#5127](#5127)).
- Refreshed the `/threads` cache after each turn
([#5174](#5174)).
- Anchored toasts above the chat input and added a toast when media is
dropped into a free-text question
([#5101](#5101),
[#5099](#5099)).
- Improved thread status message styling and links
([#5118](#5118)).
- Made resume hints echo the launched command name
([#5119](#5119)).
- Scoped selection copy to the clicked screen
([#5140](#5140)).
- Ignored mouse hits on detached widgets
([#5114](#5114)).

_End release notes preview._

---

> [!NOTE]
> A **New Contributors** section is appended to the GitHub release notes
automatically at publish time (see [Release
Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline),
step 2).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com>
Co-authored-by: Johannes du Plessis <johannes@langchain.dev>
Mason Daugherty (mdrxy) added a commit that referenced this pull request Jul 30, 2026
…5118)

Thread status notes in the transcript (`Resumed thread:`, `Started new
thread:`, `Previous thread:`) now all render with the same dim italic
styling, and the mouse pointer changes to a hand when hovering a link
inside any app message. Re-selecting the thread the session is already
on now shows a transient toast instead of an inline chat message,
matching the existing same-model behavior.

---

Several related inconsistencies, all visible in a single resume:

- `AppMessage` applies `dim italic` only when it is handed a plain
string; a pre-built `Content` is rendered as-is. Thread notes start life
as plain strings and are upgraded to linked `Content` once the LangSmith
URL resolves, so they visibly brightened relative to every other app
message. `_build_thread_message` now styles each span `dim italic`,
matching what `AppMessage` does for strings (and what other linked app
messages such as `/trace` output already do explicitly).
- `AppMessage` carries a `text` pointer in CSS with no hover handling,
so an embedded link gave no cursor affordance. It now toggles the
pointer on `MouseMove`/`Leave` using the existing `event_targets_link`
helper, mirroring `AssistantMessage`. Link clicks still route through
`open_style_link`, so URL safety checks are unchanged.
- The `Already on thread` note was the odd one out: an inline transcript
message for something that changed no state, where the equivalent
`Already using <model>` no-op is a toast. It is now a toast too. Its
dedup guard — don't stack an identical toast while the previous one is
presumed still on-screen, but do re-announce once that has expired — is
hoisted into `_notify_unchanged_once` and shared by both call sites. A
successful thread switch re-arms the toast. The sibling `Switched to
thread directory` message stays inline because it records a real state
change.

Made by [Open
SWE](https://openswe.vercel.app/agents/4b51fe78-171c-1374-8355-489bab7e616d)

---------

Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
Mason Daugherty (mdrxy) pushed a commit that referenced this pull request Jul 30, 2026
> [!CAUTION]
> Merging this PR will automatically publish to **PyPI** and create a
**GitHub release**.

For the full release process, see
[`.github/RELEASING.md`](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md).

---

_Release notes preview: keep this section in sync with the package
`CHANGELOG.md`. Publish reads the merged CHANGELOG via `release.yml`,
not this PR description — keep them aligned anyway so the PR stays an
accurate historical record for reviewers and anyone returning later._

---


##
[0.1.50](deepagents-code==0.1.49...deepagents-code==0.1.50)
(2026-07-30)

### Highlights

- Added project hooks workspace trust and expanded Hooks v2 support with
client and server lifecycle events plus runtime feedback
([#5105](#5105),
[#5104](#5104),
[#4997](#4997),
[#5045](#5045)).
- Added an option to mute the “YOLO is active” toast
([#5103](#5103)).
- Made the splash screen `thread` ID clickable to copy it
([#5173](#5173)).
- Show `ask_user` answers directly on the answered tool row
([#5100](#5100)).
- Show a toast when submitting an empty required `ask_user` answer
([#5095](#5095)).
- Added thread message counts to the Debug Console
([#5117](#5117)).

### Fixes and improvements

- Gated Hooks v2 behind `DEEPAGENTS_CODE_EXPERIMENTAL` and improved hook
resume stability across identity and Command tool results
([#5146](#5146),
[#5176](#5176)).
- Kept server hook state out of task results
([#5164](#5164)).
- Stopped duplicate Auto transcript events during interrupt replay
([#5157](#5157)).
- Kept `/update` and `/install --package` prompts responsive
([#5127](#5127)).
- Refreshed the `/threads` cache after each turn
([#5174](#5174)).
- Anchored toasts above the chat input and added a toast when media is
dropped into a free-text question
([#5101](#5101),
[#5099](#5099)).
- Improved thread status message styling and links
([#5118](#5118)).
- Made resume hints echo the launched command name
([#5119](#5119)).
- Scoped selection copy to the clicked screen
([#5140](#5140)).
- Ignored mouse hits on detached widgets
([#5114](#5114)).

_End release notes preview._

---

> [!NOTE]
> A **New Contributors** section is appended to the GitHub release notes
automatically at publish time (see [Release
Pipeline](https://github.com/langchain-ai/deepagents/blob/main/.github/RELEASING.md#release-pipeline),
step 2).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: langchain-oss-automated-triage[bot] <248757908+langchain-oss-automated-triage[bot]@users.noreply.github.com>
Co-authored-by: Johannes du Plessis <johannes@langchain.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dcode Related to `deepagents-code` fix A bug fix (PATCH) internal User is a member of the `langchain-ai` GitHub organization size: M 200-499 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant