Skip to content

Draw nothing more once an answer is out, and make pasted web addresses links - #885

Merged
lavaman131 merged 2 commits into
mainfrom
chat-after-answer-row
Nov 13, 2026
Merged

lavaman131 merged 2 commits into
mainfrom
chat-after-answer-row

Conversation

@lavaman131

Copy link
Copy Markdown
Collaborator

What was seen

Two things from a live check of a hosted deployment:

  1. After chopin finished an answer in Chat, a "Getting oriented" row appeared under the finished answer for 4–7 seconds, the answer's header said "Started at …", and Stop was still showing. Then it all went away. One chopin session each time.
  2. A web address pasted into Chat stayed plain text. Only Markdown links were links, so pasting a link to a section of the document left nothing to click.

Causes

  1. A quiet turn drawn as work. Since the reply now comes before the title job, the background job that keeps the document's title runs right after a reply. Its turn reached Chat like a reply's turn (chat:state with busy and a turn), with no message of its own, so Chat drew its empty working row with the default label "Getting oriented", and the composer offered Stop. The docs already said such jobs are "neither saved nor shown". A reply's own turn also stayed busy after its last word, while its after-answer check (the reply policy's "stream now, check after") ran, which kept Stop up for that time too.
  2. Bare addresses were left out on purpose. Chat read only three of GitHub's Markdown parts (tables, strikethrough, task lists); the comment recorded that a bare address "would become a link nobody wrote".

The fix

1. A turn says when nothing more of it is for people to watch (Chat.Turn.quiet, optional, so earlier clients ignore it).

  • The server sends a background job's turn quiet from its start.
  • It marks a reply's turn quiet once the reply's words are all out, while the check runs (publishPlannerReplies tells it).
  • A repair step the check starts (moving a question into a Decision card) clears it: that is work again, and it shows in place on the answer, then its summary line.
  • Chat and threads draw a quiet turn as the turn that ended: no working row, no "Started at", its tool summary kept.
  • Stop is offered for a quiet turn only once a message was refused because of it (planner-busy), so a person can still stop a job to ask now. The composer's Resume and refusal handling are unchanged.
  • The workspace's Chat tab ("Chat, chopin working" and its spinner) follows the same rule. The document's activity dot and the decisions' "being written up" state still follow every busy turn, because a background job is what writes a decision's prose.

2. A web address written out is a link, as in Slack.

  • Chat reads GitHub's literal links (micromark-extension-gfm-autolink-literal, mdast-util-gfm-autolink-literal, both already in the tree through remark-gfm, now pinned in the web app) when a message holds http:// or https://. Messages without one are parsed exactly as before.
  • Only http:// and https:// addresses become links. GitHub's www. and email literals are turned back into the text they were (keepWebAddresses in gfm.ts), and no other scheme (javascript:, data:) is linked.
  • Punctuation after an address stays the sentence's; code spans and blocks stay code.
  • The link is Chat's own link, so an address into the document or another of its documents (the document's URL plus #heading included) is followed in place, as Follow a link to a section in place, and open only external links in a new tab #866 made section links; only another site opens a new tab.

Evidence

Before and after, with a scripted Planner: the answer is out and the title job's turn is running.

Before After
Desktop before after
Phone before after

Before: "Started at", the "Getting oriented" row, Stop, the Chat tab's working spinner on the phone, and the address as plain text. After: the finished answer, Send, and the address as a link.

Checks run

  • New tests that fail on main (8634eef) and pass here:
    • reply-policy.test.ts: a reply's turn goes quiet after its last word, while the check runs; a repair step is work again and its end is quiet.
    • job-lifecycle.test.ts: a background job's turn is quiet.
    • invoke.test.ts: each turn goes quiet once at its own offset (the existing offset assertion now counts turns, not frames).
    • transcript-state.test.ts: a quiet job turn draws nothing; a quiet reply turn draws as ended with its tools; Stop rules.
    • gfm.test.tsx, markdown.test.ts: addresses link; www., email, other schemes and code stay text; the parts chosen by source still give the same tree as reading every part (6,000 generated sources and streamed prefixes, now including addresses).
    • e2e/chat-after-answer.e2e.ts (new, 4 tests): no row, no Stop, no "Started at" during the check and during a job; Stop once a send is refused; a repair shows in place; the phone's Chat tab. All 4 fail on main ("Getting oriented", Stop, "Chat, chopin working").
    • e2e/in-document-links.e2e.ts: a pasted address links, its trailing comma and full stop stay text, www. and code stay text, and the document's URL plus a section is followed in the same tab. Fails on main (no links).
  • After rebasing onto origin/main 8cb7e62 (the row-shade and image-viewer changes; one conflict in thread-panel.tsx, both sides kept):
    • bun run types, bun run ci: pass.
    • bun test apps/server/src/chat apps/web/src/chat apps/server/src/conversation-plan: 2,895 pass, 0 fail.
    • Playwright (--workers=2): chat-after-answer, in-document-links, chat-markdown, chat-threads, chat-thread-replies, harness, composer-send-stop, steering, responsive-activity, chat-phone, smoke: 104 passed.
    • bun run design:behavior: 104 passed; two assistant-ui-geometry tests timed out waiting for the audit page to load under load, and the file passes alone (20 passed).
    • bun run build: initial JS 259,958 B raw / 82,473 B gzip (budget 260,500 / 83,450); initial CSS 184,346 B raw / 32,665 B gzip (budget 185,000 / 33,000). This change adds nothing to either: the new parser parts load with Chat's Markdown.
  • Before the rebase, the same specs plus conversation-initiative and reaction-reader: 111 passed; after the Chat tab change, chat-after-answer, responsive-activity, smoke, chat-phone and composer-send-stop: 50 passed.
  • No colours changed. The link uses the existing Markdown link style (petrol ink, underlined).

Judgement calls

  • One flag for both cases. A background job and a reply's after-answer check are the same thing to a reader: nothing more to watch. One optional quiet on the turn covers both, keeps the turn's identity (so completed work is not counted twice), and lets a repair step become visible again on the same answer.
  • Stop for a quiet turn only after a refusal. A person's message to chopin during a background job is still refused (planner-busy, "Stop it to ask now."), so Stop must be there then. Letting a person's message stop the job instead is a better experience, but changes the server's scheduling; it is left for a follow-up.
  • http(s) only. The request named http(s) addresses. www.example.com and email addresses stay text: an email next to @mentions is easy to misread, and nothing becomes a link that was not written as an address.
  • No new styling. The link and the composer look exactly as they did; only what is drawn changed.

…ly for work people can watch

After chopin finished an answer, a "Getting oriented" row appeared under it for a few seconds with
Stop still showing, then went away. That row was a background job's turn: keeping the document's
title, which now runs right after a reply. Its turn reached Chat like a reply's, with no message of
its own, so Chat drew its empty working row. A reply's own turn also stayed busy after its last
word while its after-answer check ran, which kept Stop up.

A turn now says when nothing more of it is for people to watch (`Turn.quiet`, optional, so earlier
clients ignore it). The server sends a background job's turn quiet from its start, and marks a
reply's turn quiet once its words are all out, while the check runs. A repair step the check starts
clears it, since that is work again, shown in place on the answer. Chat and threads draw a quiet
turn as the turn that ended: no working row and no "Started at", its tools kept in the summary.
Stop is offered for a quiet turn only once a message was refused because of it, so the person can
still stop a job to ask now. The workspace's Chat tab says chopin is working only for a turn people
watch, while the document's activity and its decisions' write-up still follow every busy turn, since
a job is what writes a decision's prose.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun test apps/server/src/chat apps/web/src/chat apps/server/src/conversation-plan (2,897 pass after the invoke fix)
Assistant-verification: new unit tests fail on 8634eef (reply-policy quiet, job quiet, invoke offsets, transcript-state)
Assistant-verification: e2e chat-after-answer: 4/4 pass here, 4/4 fail on 8634eef ("Getting oriented", Stop, "Chat, chopin working")
Assistant-verification: e2e chat-after-answer, responsive-activity, smoke, chat-phone, composer-send-stop: 50 passed after the Chat tab change
Assistant-verification: e2e chat-after-answer, in-document-links, chat-markdown, chat-threads, chat-thread-replies, harness, composer-send-stop, steering, conversation-initiative, smoke, reaction-reader, responsive-activity: 111 passed
Assistant-verification: bun run types; bun run ci
User-preference: Things should be polished like the Slack UX
Co-authored-by: Alex Lavaee <lavaman131@github.com>
A bare address in a Chat message stayed plain text: only Markdown links were links, so pasting a
link to a section of the document left nothing to click. Chat now reads GitHub's literal links for
addresses written with http:// or https://, in messages, threads and chopin's replies, on the same
terms as the other GitHub parts it reads: only when the text holds such an address. The punctuation
after an address stays the sentence's, code spans and blocks stay code, and the link goes through
Chat's own link, so one into the document or another of its documents is followed in place (#866)
and only another site opens a new tab. GitHub's www. and email literals are turned back into the
text they were, so nothing becomes a link that was not written as an address, and no other scheme
is ever linked.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline (subagent)
Assistant-verification: bun test apps/web/src/chat/gfm.test.tsx apps/web/src/chat/markdown.test.ts apps/web/src/chat/message-copy.test.ts (new tests fail on 8634eef)
Assistant-verification: e2e in-document-links: the new pasted-address test passes here and fails on 8634eef; the file's other tests pass on both
Assistant-verification: bun run types; bun run ci; bun run build (initial JS 259,958 B raw / 82,480 B gzip, CSS 184,551 B raw / 32,672 B gzip, unchanged)
User-preference: Things should be polished like the Slack UX
Co-authored-by: Alex Lavaee <lavaman131@github.com>
@lavaman131
lavaman131 merged commit 74772ee into main Nov 13, 2026
24 checks passed
@lavaman131
lavaman131 deleted the chat-after-answer-row branch November 13, 2026 00:55
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