feat(web): board cards show where a thread came from - #390
Merged
Conversation
Both sidebars and the mobile board mark a thread's source and participants with ThreadIdentityMark; the web board did not, so a card gave no way to tell a Discord- or Jira-started thread from a local one, or to see who else is on it, without opening the thread. The mark now trails the card title, matching where the sidebars and the mobile board put it. The title keeps its two-line clamp by becoming the flexible child of that row rather than spanning it, so nothing about the existing layout moves when a thread has no source — the glyph renders nothing at all in that case. The fork's surface guard now covers the board card too, next to the sidebars it already checks. Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
ThreadIdentityMark already falls back to the first participant's channel when no origin channel is given, so restating it at the call site was a second copy of the same rule that could drift. Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
BoardCardDragOverlay is aria-hidden and its test asserts it holds no focusable
controls, but ParticipantStack always renders tabIndex={0}. Dragging a thread
that has participants therefore put a tab stop inside a clone that assistive
technology cannot see. The existing overlay test passed only because its
fixture had no participants — the exact shape of a test that agrees with itself.
ParticipantStack and ThreadIdentityMark take an `interactive` flag, and the
board card passes its existing rendering.interactive through, so the mark is
inert in the clone and unchanged on a real card.
The tests now assert the component's own contract (source-channel-glyph and
participant-stack testids, and the aria-label) rather than the Δ character,
which would have gone quiet on any glyph redesign and said nothing about other
channels. Added the participant-only fallback case and a participant-bearing
overlay case; the latter fails without this fix, which is the point.
Found by the gpt-5.6-sol adversarial review.
Co-authored-by: Patrick Roza <42661+patroza@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Board cards now show where a thread came from.
Both sidebars and the mobile board already mark a thread's source and participants with
ThreadIdentityMark. The web board did not — so a card gave no way to tell a Discord- orJira-started thread from a local one, or to see who else is on it, without opening the thread. This
was a web/mobile parity gap rather than a new idea, so the placement is copied rather than invented:
the mark trails the card title, exactly where
Sidebar.tsx,LegacySidebar.tsxandBoardScreen.tsxput it.Layout
The title was
line-clamp-2 w-full. It becomes the flexible child of a new row(
flex min-w-0 items-start gap-1.5) with the mark as ashrink-0sibling, so the two-line clamp andtruncation are unchanged and a long title cannot push the mark out of the card.
A thread with no source costs nothing:
SourceChannelGlyphreturnsnullfor an empty channel,so the row renders exactly as before — no reserved space, no height change. That is asserted, not
assumed.
Adversarial review
Both reviewers called the change sound. One real bug between them, plus test-strength findings — all
fixed in
0bb062b5f.BoardCardDragOverlayisaria-hiddenand its test asserts it holds no focusable controls, butParticipantStackalways renderstabIndex={0}. Dragging a thread with participants put a tab stop inside a clone assistive technology cannot seeParticipantStack/ThreadIdentityMarktake aninteractiveflag and the card passes its existingrendering.interactivethrough. The existing overlay test passed only because its fixture had no participants — a test agreeing with itself. The new participant-bearing overlay case fails without the fix; I verified that by reverting the prop rather than assumingΔcharacter, which would go quiet on any glyph redesign and says nothing about non-Discord channels; no participant-path or overlay coveragesource-channel-glyph/participant-stacktestids and thearia-label), plus a participant-only fallback case and the overlay caseThreadIdentityMarkalready applies1d8f0eb50ParticipantStackbehaviour, identical in both sidebars; the board only makes it denser. Worth fixing in the component, not smuggled into this changeO(visible cards), no per-card network, and claim maps change rarely. Channel-only threads add no subscription at allBoardCardtests carry the behaviourBoth independently confirmed what I most wanted checked: the two-line clamp and truncation still
hold, a card with no source is byte-identical to before (the glyph renders
null, so the flexgap reserves nothing), the effective channel matches the sidebar's derivation,
memois not broken,and the fields really are populated in production — traced from
useThreadShells()throughOrchestrationThreadShellto the persistence query.Verification
vp buildof the web app passes.CodexTextGenerationlaunch-args onethat reproduces on unmerged
fork/dev.BoardCardtests: the Discord glyph appears for a thread with a source, and is absentfor one without — the pair is what makes the first assertion mean something.
OrchestrationThreadShellcarriesoriginSourceandparticipantSummaries, and it is the sameuseThreadShells()source thesidebar already renders the mark from.
forkSurfaceExistencenow guards the board card alongside the sidebars it already checked.Note
The web unit suite has a pre-existing intermittent failure in
browserHistoryStore.test.ts(~1 run in 3). It is not from this branch: I reproduced it onclean
fork/devand again onf067b34a1, before any of my recent work. Unrelated to the board,and worth its own look.
Co-authored by @patroza
opened by Patrick Roza in chat thread Discord · Discord · T3