Skip to content

feat(messages): show "<author> in #<channel>" for message links - #1969

Merged
kalvinnchau merged 6 commits into
mainfrom
link-messages-show-author
Nov 2, 2026
Merged

kalvinnchau merged 6 commits into
mainfrom
link-messages-show-author

Conversation

@matt2e

@matt2e matt2e commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Summary

A raw Buzz message or thread link now names the message's author. Before, it showed only the channel name (Alpha). Now it reads <author> in #<channel> (Alice Fixture in #Alpha) in both the visible label and the accessible name. The icon, styling, hover preview card, menu, click behaviour and selection copy (which still yields the raw URL) are unchanged.

Before and after

Captured with the real-app browser fixture behind tests/browser/buzz-links.spec.mjs (WebKit, 1280×800 at 2×). "Before" is the merge base 9818f778e. "After" is 76c5ffd75; the only later commit, 6576a15cc, changes a comment.

Before: the top-level, thread-reply and no-name links all read just "Alpha", so they can't be told apart

After: the top-level link reads "Fixture Reader in #Alpha" and the thread reply reads "Alice Fixture in #Alpha". The link whose author has no name still reads "Alpha" and the DM link still reads "dm-peer"

Dark theme

Before, dark: the same three identical "Alpha" labels

After, dark: author names appear only on the first two links. Icons, link colour and underline match the before shot

When the old label stays

The link keeps its previous channel presentation when:

  • the message is in a DM, a Solo chat or an unlisted channel;
  • the channel's membership is still loading from cache (it waits for the relay to confirm membership before reading);
  • the lookup cannot find or read the message (deleted, or not yet on the replica that answered), or no reader is free;
  • the link has custom (authored) link text;
  • the link is in a composer draft or preview, which never reads;
  • the author has no profile name, so the label never shows a key fragment. An agent with a library name is still named.

A workflow's message reads Workflow in #<channel>, matching its row header.

How the author is found

  1. Local timeline row first. If the channel's live timeline already holds the message as a message row, the author comes from that row and no reader is borrowed. Membership and huddle rows don't count; those links take the exact read. A cached window (restored from disk, or marked cached after a stream drop) skips this step and goes to the exact read.
  2. Otherwise, one bounded exact read. The link borrows the same bounded exact thread reader the hover preview uses, then disposes it as soon as the author is known. At most 4 lookups run at once, so open threads and previews keep their capacity, and their reads take background admission in the shared read scheduler: one label read runs at a time, and opening or previewing a conversation reads ahead of them. A lookup is cancelled when its last link unmounts.
  3. Remembered per session. A found author is kept for the session, so remounted (virtualized) links don't read again. It stays shown even if the message is later deleted.
  4. Retried without looping. A failed or unavailable read is retried when the live stream connects again, or after every link to that message has unmounted and one mounts again. Those are the only recovery paths; it never retries in a loop.

Names resolve through the existing identity names, profile names and workflow attribution. BuzzLinkPreview now shares its ready/stopped check with the lookup. docs/plugin-architecture.md describes the new label and the reader use.

Commits

  • 6ec71482f feat(messages): name the author in message links
  • 13cb363f4 fix(messages): keep message link authors named, local and retryable
  • 6c555884b fix(messages): re-read unavailable message links and trust only message rows
  • ca410f719 docs(messages): say a cached timeline window defers to the exact reader
  • 2f2a9d8f5 fix(messages): drop the message link's fragment guard the names view now owns
  • f61cd574f fix(messages): read message link authors in the background

Validation

Run locally during development:

  • typecheck, lint, format and design:check
  • src/features/conversation/linked-message.test.tsx (new unit tests) and the dependent Vitest files
  • src/features/relay/thread-target.test.ts: the new background-admission case fails when the thread view does not forward its priority (fail-before run), and threads, reader, thread-window and traffic.integration pass
  • browser tests buzz-links, nested-replies and navigation-thread-history in Chromium and WebKit

The branch was pushed with --no-verify because pre-push tests time out under load on the author's machine. Hosted CI is the broad check.

Known trade-offs

  • One read per distinct unloaded message. Each distinct linked message that isn't in its channel's live timeline costs one exact read per session.
  • The label widens when the author arrives. The link first shows the channel name, then grows to <author> in #<channel>.
  • A found author persists after deletion. Once found, the author stays shown for the session even if the message is later deleted. Only a message the lookup never found keeps the channel name.
  • Thread-panel replies still read. A reply held only by an open thread panel still takes the exact read. Reusing the panel's copy would need a lookup in the FOUNDATION relay session, which is left for explicit guidance.

🤖 Generated with Claude Code

@matt2e
matt2e requested a review from a team as a code owner October 30, 2026 11:54

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 The new fallback-name assertion fails against current main and blocks required CI. Please rebase and update the test and redundant fragment filtering. Two documentation contracts also need correction.

Comment thread docs/plugin-architecture.md Resolved

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account. Reviewed head 6576a15 against target base 36a283b (merge-base 9818f77).

One new P2 is inline: passive author lookups occupy foreground transport capacity and can delay opening another thread. I also corroborated the existing P2 fallback-name test finding: the target base excludes fallback profile names, so the new assertion at linked-message.test.tsx:252 contradicts that contract. The hosted JavaScript (2/3) log reports this exact assertion failure. Please update that assertion while preserving the unnamed-channel and named-agent behavior checks; no duplicate inline thread added.

The existing documentation note is a useful optional clarification of deletion and reconnect behavior; this review does not request changing the documented session-cache tradeoff. The public description, four attached fixture screenshots and commit messages showed no concrete disclosure issue.

Rocket, Mantis and Groot completed their independent source lanes; Star Lord checked and integrated the findings.

Validation limits: source-only review; no local tests, builds, app launches, live-relay requests or rendered/accessibility/performance validation. One hosted CI snapshot showed CI required failing (JavaScript 2/3, both Vitest flake checks and WebKit 8/12); the WebKit failure concerns closing an active thread, and its relationship to this change remains unestablished. No approval or merge authorization.

@matt2e
matt2e force-pushed the link-messages-show-author branch from 6576a15 to 2a00e50 Compare October 31, 2026 00:33

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account. Follow-up reviewed at head 2a00e50 against target base fb78d21 (merge-base 0944c1d), with independent Rocket, Mantis and Groot coverage.

No new actionable findings in the names-view cleanup. The prior four feature commits are patch-equivalent after rebase; the new two-file commit correctly delegates synthetic-name suppression to the shared names service and updates the fixture expectation. The service still subscribes to profile changes, so removing the local subscription preserves later name updates.

The existing P2 reader-admission finding remains unresolved at this head. Passive author lookups still enter the shared three-slot transport scheduler as foreground work and can delay an unrelated thread opening. That finding and its requested repair remain in the original thread; no duplicate inline finding is added here.

Reviewed the public description, all four attached screenshots and feature commit messages; no new disclosure finding. This is a bounded source-only follow-up, not fresh runtime acceptance of the whole rebased tree. No tests, builds or app execution were performed, and current CI, native behavior, keyboard/theme rendering and latency were not verified. This COMMENT is not approval or merge authorization.

@matt2e
matt2e force-pushed the link-messages-show-author branch from 2a00e50 to a3c3079 Compare November 1, 2026 23:14
matt2e and others added 6 commits November 2, 2026 11:13
A raw Buzz message or thread link now reads "<icon> <author> in
#<channel>" instead of only the channel name, in both its visible label
and its accessible name. The icon, styling, preview card, menu and click
behaviour are unchanged, and selection copy still yields the raw URL.

The author comes from the same bounded exact thread reader the hover
preview uses; the session exposes no other single-message read. A
session-scoped lookup borrows one reader per linked message, disposes it
as soon as the author is known, remembers the result for the session,
runs at most four at once so open threads and previews keep their
capacity, and cancels a lookup when its last link unmounts. Names
resolve through the existing identity names, profile names and
workflow attribution. The preview now shares its ready/stopped check.

The previous channel presentation stays while the author is unknown or
unnamed, when the message is unavailable or no reader is free, and for
DMs, Solo chats, unlisted channels, authored labels, and composer
drafts and previews, which never read.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
Addresses the review of 1c81e00.

A link's author never shows as a key fragment. A profile with no name
(nameIsFallback) keeps the channel label even when the identity names
view hands back its fragment, while an agent's library name still names
it. The unit fixture now resolves through the app's own names view and
agent directory, which return that fragment.

A link in a channel whose membership is still cached waits for the
relay to confirm it before reading. A deleted or unavailable message
keeps the channel label and is remembered for the session, so
virtualized remounts do not read it again. Any other failed read is
retried when the live stream connects again, the session's own retry
signal, or once every link to that message has unmounted; it never
retries in a loop.

A message its channel's timeline already holds takes its author from
that row, without borrowing a reader.

A workflow's message reads "Workflow in #<channel>", the name its row
header shows, in both the visible label and the accessible name.

Replies held only by an open thread panel still take the exact read:
reaching them needs a lookup in the FOUNDATION relay session, which is
left for explicit guidance.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…ge rows

Addresses the review of 6737fb8.

An unavailable message is no longer remembered for the session. A read
can be served by a replica that lags behind a message just posted, so an
unavailable result now takes the same path as any other failed read: the
link keeps the channel label, and the message is read again once every
link to it has unmounted or the live stream connects again. It never
retries in a loop. The separate set of missing messages is gone.

The channel's timeline names an author only from a message row, as the
exact reader does. Membership and huddle rows are read instead, and a
timeline restored from disk is not trusted until it is revalidated. An
author already found stays shown for the session, even once its message
is deleted; the doc comment now says so.

A lookup's cleanup and refresh now check that it is still the running
lookup for its key, not just that some lookup holds the key, so they do
not depend on lookups starting in a later microtask.

New unit tests cover an unavailable message that a remount reads again
and finds, a failed read retried once every link has unmounted while
the stream stays connected, and the system-row and restored-timeline
skips.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
Addresses the review of 76c5ffd.

The comment on the linked-message timeline shortcut said a window
restored from disk waits until it is revalidated. It does not wait: a
cached window, whether restored from disk or marked cached after a
stream drop, skips the local match and the exact reader decides. The
comment now says so. Behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
…now owns

Rebasing onto main brought in #1927, which makes every identity-names
resolver drop fallback (key-fragment) profile names. The link's own
fragment comparison is dead, and the test premise that the names view
hands back the fragment no longer holds.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
Addresses the review of a3c3079.

A link's author lookup borrowed an exact thread reader whose reads took
foreground admission, so three slow lookups could hold every transport
slot and queue the read of a thread the person then opened. The thread
view now takes a read priority, applied by its retrying read wrapper to
every read it makes, and the session's thread() forwards it. Link
lookups ask for background admission: the shared scheduler runs one
background read at a time and starts a foreground read ahead of it, so
opening or previewing a conversation reads first. The exact reader's
access, cap and admission checks are unchanged, and a lookup that shares
a filter with an opened thread still shares and promotes that read.

A new thread-target test holds three background label reads and shows
an opened thread's target, overlay and root reads start at once; it
fails when the priority is not forwarded.

The plugin-architecture doc now says an author once found stays shown
for the session even after its message is deleted, that a message the
lookup cannot find or read keeps the channel name, and that it is read
again when a link to it mounts again or the live stream connects again.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Matt Toohey <[email protected]>
@matt2e
matt2e force-pushed the link-messages-show-author branch from a3c3079 to f61cd57 Compare November 2, 2026 00:23

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source follow-up via Wes’s account, integrating Rocket, Mantis and Groot reviews.

No required changes found. The prior P2 about passive message-author lookups consuming foreground read capacity is resolved: linked-message.ts requests background priority, session.ts forwards it, and threads.ts applies it to both read wrappers and their retries. The existing scheduler limits background work to one slot and prioritizes queued foreground work; preview callers retain foreground defaults. Access checks, shared-read cancellation and cleanup remain intact.

Reviewed head f61cd57 against target base 76803df (merge base e7ae7b1). Range-diff confirms the five earlier feature patches are unchanged from reviewed head 2a00e50; this follow-up covers the six-file repair and its relevant callers.

Optional wording only: in docs/plugin-architecture.md, “A live link’s lookup reads take background admission” would distinguish passive lookups more explicitly from foreground hover/focus previews.

Source-only review: no tests, builds or app runs performed. The new service-level regression test was inspected, not executed; this follow-up adds/removes no browser cases. A one-time hosted snapshot showed CI required successful and Windows native validation skipped. Real relay latency, cold/warm opening performance, native behavior, rendered focus and assistive-technology behavior remain unverified. All four PR images and the public description were inspected; the images show older fixture captures, not current runtime acceptance. This COMMENT is not approval or merge authorization.

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Reviewed f61cd57. The passive-read scheduling issue is fixed: held background lookups no longer block unrelated thread opening. No actionable findings remain from our review; targeted tests and Chromium/WebKit workflow checks passed.

@kalvinnchau
kalvinnchau merged commit 7f793e9 into main Nov 2, 2026
44 checks passed
@kalvinnchau
kalvinnchau deleted the link-messages-show-author branch November 2, 2026 16:15
matt2e added a commit that referenced this pull request Nov 5, 2026
…2607)

## Problem

The PR flake check repeated only the tests in files a pull request
edited. If a product change made an unedited test racy, the check
selected nothing. #2000 repeated one emoji journey and skipped the seven
other specs that load `emoji.html`. Five of its flaky journeys were in
those specs.

## What changes

`scripts/flake-check.mjs select` now selects in two tiers:

- **Tier 1, edited tests.** This is exactly the old rule: same
repetitions, both engines. The budget never trims it, even when tier 1
alone goes over.
- **Tier 2, related tests**, run as whole files:
- Browser specs that import a changed helper, or that open a
`tests/fixtures/*.html` page whose module graph holds a changed source
file, stylesheet or patched package. One rolldown build of every spec
and fixture page supplies the graph and writes nothing (1,027 modules in
about 1 s).
- Vitest files related to the changed modules through Vitest's module
graph. This is the graph the pre-push `vitest related` uses.

Picks are ranked by specificity: the smallest route that holds a changed
file comes first, and the cheaper test wins a tie. A test named after a
changed module ranks ahead of that order (`ComposerAttachments.tsx` →
`composer-attachments.spec.mjs`). The step summary marks those rows as a
naming heuristic, not graph evidence. Tier 2 adds picks in rank order
while each lane's predicted wall time (`flake_browser` and
`flake_vitest`) stays within **9 minutes** (`LANE_BUDGET_MINUTES`;
`--budget=<minutes>` overrides it). The 40-journey and 20-Vitest-file
caps still apply, and the pick that crosses a cap still runs.

**Cost table.** `scripts/flake-check-costs.json` is checked in and
written by the new `flake-check costs [run-id…]` subcommand. By default
it reads the latest three green `main` push runs. It reads the required
lanes' Playwright and Vitest reports and stores median passing seconds
per journey title as [Chromium, WebKit], plus a median per Vitest file.
A test missing from the table falls back to its file's median, then to
the engine-wide median or the median of all Vitest files. I checked in
the table instead of fetching timings in CI for three reasons: `select`
stays reproducible from the commit, `flake_select` needs no token or
network, and the artifacts are about 100 MB per run and expire after 7
days. The current table comes from runs 41990511962, 41980968364 and
41979442279 (Nov 5): 1,049 journeys and 964 Vitest files in 181 KB.

**Lane model.** I fit the model to hosted PR runs from Nov 4–5. The
median predicted/actual ratio is 0.95 for the browser lane and 1.02 for
Vitest.

- Browser shard: 105 s of overhead + 1.11 × the shard's summed test time
× 10 repetitions / 2 workers, then × 1.22 for imbalance when there is
more than one shard.
- Vitest batch: 26 s + 5 × (5.7 s + 0.72 × max(sum / 2, slowest file)).

The step summary gains a Tier column. It also shows each lane's
predicted time against the budget, with the edited-only time beside it,
and the related picks dropped for time and for the cap.

`docs/contributing.md` describes the tiers and the budget. It also
corrects "at most eight" shards to sixteen (`MAX_SHARDS`).

## For reviewers: the budget takes the longest fitting prefix

Tier 2 does **not** stop at the first pick that would go over the
budget. It takes the longest rank-order prefix whose predicted lane
fits. Browser shards grow with the selection (one shard per five
journeys), so a longer prefix can predict a *shorter* lane than a
shorter prefix. In the 248-PR replay, stopping at the first overrun
would have trimmed browser picks in 45 PRs. In 44 of those, the cap-only
selection already fit, and 40 predicted a slower lane after trimming.
#2000 would have kept 2 related journeys at 8.6 minutes instead of 46 at
6.2, and lost both of its flaky specs. Ranks are never skipped. The
Vitest lane only grows with each pick, so both rules give the same
result there. A unit test covers the non-monotonic case, and that test
fails under a first-overrun loop.

## Validation

Replays of `select <merge>^1 <merge>` in detached checkouts of each
merge commit. Selection before this PR → after:

| PR | Edited Vitest / journeys | Related journeys | Related Vitest |
Browser lane (min) | Vitest lane (min) | Flaky targets kept |
| --- | --- | --- | --- | --- | --- | --- |
| #2000 0944c1d | 1 / 1 | 0 → 46 (12 specs, incl. reactions-polish,
message-overlays) | 0 → 4 | 6.2 | 1.5 | yes |
| #1361 5992e69 | 2 / 0 | 0 → 40 (incl. composer-attachments, by name)
| 0 → 0 (budget) | 8.0 | 9.4 (edited alone 9.4) | yes |
| #2291 83b72a3 | 2 / 153 | 0 → 0 (budget) | 0 → 18 | 15.3 (edited
alone 15.3) | 6.6 | n/a |
| #1910 e7ae7b1 | 3 / 0 | 0 → 53 | 0 → 10 | 5.7 | 9.0 | n/a |

Lane times are this PR's predictions. Without the budget (the first
commit alone), the Vitest lane would have been 9.8 min for #2000, 16.3
min for #1910 and 6.6 min for #2291, and #2291's browser lane 18.3 min.
`--budget=Infinity` reproduces the first commit's output byte for byte
on all four source PRs. In the first commit's replay, a spec-only PR
(#2557) and a docs-only PR (#1694) selected exactly what the old rule
selected.

- `node --test tests/integration/flake-check.test.mjs`: 20 pass. Biome
is clean, including the table.
- Local `select` time on a laptop: #2000 takes 49 s with a cold Vitest
cache and 6 s warm (13 s before); #1361 takes 63 s cold and 8 s warm (19
s before). CI runs cold, within the select job's 5-minute limit. These
are local measurements, not hosted ones.

## Cost and known gaps

- **Runner-minutes grow about 7.5×.** In a replay of the 248 PRs merged
Nov 2–4, flake runner-minutes per run averaged about 11.4 under the old
rule and about 84 with related selection. Almost every source PR fills
the 40/20 caps, and the caps are what bound the cost. The budget limits
wall time only: against the first commit it moves the mean from 84.6 to
84.0 and the max from 381 to 319 (#2291). Lane wall time stays about 6–7
minutes for most PRs, and the highest predicted lanes fall from 16.7 to
13.7 minutes (browser) and from 16.3 to 10.4 (Vitest). All 11 Vitest
lanes still over 9 minutes are over on edited tests alone. The lanes
stay advisory, and repetitions, timeouts and workers are unchanged.
- **Not yet run in hosted CI.** This PR's own run will be the first.
- **Cost table drift.** Timings go stale until someone reruns
`flake-check costs`.
- **Model error.** Predicted/actual ranges from 0.73 to 1.23 (p10–p90)
for the browser lane and from 0.75 to 1.58 for Vitest. A Vitest
`file:line` filter is charged for its whole file.
- **Full-app journeys.** Journeys that load the full app rather than a
fixture page are reached only through helpers. This misses #1729, #2108
and #1969, which a coverage-map follow-up would address. A patched
package reaches browser specs but not Vitest files.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Signed-off-by: Matt Toohey <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
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.

3 participants