feat(web): show story assignees on the board - #171
Conversation
storyOwner() picks the first assignee (GitHub's order, not sorted) and reports how many are left over, returning null when a story has no assignees so no call site can render an "unassigned" placeholder by accident. issue-row.tsx renders "@login" plus "+N" between the label chips and the relative-time stamp, and nothing when the story is unassigned.
Render the story's lead assignee beside the label chips in the story header, using the same @login (+N) grammar already used on the Backlog row. The header now reads story.assignees from StoryDetail, which previously arrived from the API and was dropped on the floor.
Adds ownedBy() and boardHref() as pure helpers in lib/pipeline.ts, and uses boardHref for all four board filter chips (all, stage, status clear, mine) instead of hand-built URL strings. The mine chip narrows each stage's stories to the signed-in viewer's GitHub login, composes with the existing stage/status filters, and only renders when the viewer has a GitHub login to match against. Stage chip counts and the active-open-stories subtitle now read from the mine-scoped stories so they never go stale relative to what's shown.
mineOn previously read straight from the mine=1 query param, so a viewer with no GitHub login (a key principal, or any user whose principal.githubLogin is unset) who arrived at ?mine=1 via a shared link, bookmark, or browser history landed on a board with every story filtered out by ownedBy(), no mine chip to undo it (it only renders when a login exists), and no other chip to recover with, since all four preserve mine. Lift the derivation into mineFilterOn(mine, login) in lib/pipeline.ts, which is false whenever the viewer has no login to match against regardless of the raw query param. The board now renders normally for such a viewer even with ?mine=1 in the URL, and every chip link emits a clean, mine-free href.
|
The avatar follow-up is now filed as #174, as this PR's body said it would be. It also records one thing that came out of shaping it: #174 will change the topbar, which today carries This PR is unchanged and does not depend on #174. #174 will be stacked on this branch. |
|
The avatar follow-up is now open as #175, implementing #174. One thing worth knowing while you review this PR: #175 is cut from this branch, and That means the two are ordered, not parallel: merge this one first and I will rebase |
Addresses #101 — the row, the story header, and the mine filter. Avatars are
deliberately left out and filed separately; see Not in this PR below.
apps/webonly. No new dependency, no change to anypackage.json, no API orschema change —
assigneeswas already on both responses the web fetches.What you get
On the row (
components/issues/issue-row.tsx) — between the label chips andthe relative time, in the same dim mono as the time. One assignee, several, and
none:
Unassigned rows render nothing at all — no placeholder, no reserved column.
In the story header (
stories/[number]/page.tsx) — same grammar at headerscale, after the last label chip and before the
issue #N ↗link:The owner takes the labels'
--dimrather than the link's brighter--mut: it isa read-only fact, not somewhere to click. The difference is visible in the first
of those two shots.
A
minechip on the Stories board, beside the stage chips, matchingme.principal.githubLogincase-insensitively againstassignees:The story dropped there is assigned — to someone else. The filter selects your
stories rather than dropping unowned ones.
Where the rules live
Four helpers in
lib/pipeline.ts, covered intest/pipeline-story.test.ts— thefile the issue points at:
storyOwner(assignees)nullwhen there are none, so nothing renders.ownedBy(assignees, login)falsefor any list when there is no login.boardHref(projectId, filter)mineFilterOn(mine, login)The row and the header both render
@{login}plus` +${extra}`whenextra > 0; only the size class differs.Two behaviour changes on the board, called out so they are not mistaken for
refactors:
maintained
countfield; with a filter in play it would have disagreed with therows beneath it. Both the number and its colour tone now come from
stories.length.boardHref. The chips previously built theirown template literals, and
allin particular would have discarded the wholequery string. It now clears the stage while preserving a filter the user did not
ask to clear.
The login-less viewer
mineFilterOnexists becausemine === "1"alone is not safe. A viewer with nogithubLogin— an API-key principal, or a user with no linked GitHub identity —who arrives at
?mine=1from a shared link, a bookmark or browser history wouldmatch no story, so every stage empties; the
minechip is not rendered for them,so there is no control to switch it off; and every chip preserves the parameter,
so none of them recovers the board. That is a blank board escapable only by
hand-editing the URL — an invisible dead control, worse than a disabled chip.
mineFilterOnmakes the filter inert for such a viewer, and the recovery path hasits own test. The chip itself is absent rather than disabled:
The status filter is deliberately still validated against the unfiltered story
set, so a status never silently drops out of the URL when
mineempties theboard. There is a comment in the page saying so.
Not in this PR
Avatars. The issue proposes
https://github.com/{login}.png?size=40with thetopbar's initial-letter fallback for deployments that must not let the browser
reach github.com. That is a self-contained piece of work carrying its own egress
question, so bundling it here would have made both harder to review.
It is being written up now as its own issue, and I will link it from #101 and from
this thread as soon as it is filed. Nothing in this PR blocks it: the avatar has a
natural home right next to
@{login}, andstoryOwneralready hands the call sitethe lead assignee and the count of the rest.
Testing
apps/websuite: 11 files, 80 tests. No new test file and no new testdependency — the presentation rules are pinned in the existing suite, the way
storyHrefand the CI grammar already are.pnpm verifyrun on this branch, green.Verified by hand on a live instance against GitHub-synced issues, which is where
the screenshots above come from: the row, the header,
minenarrowing the board,allclearing the stage while keepingmine, the status×dropping only thestatus, the chip being absent for a login-less viewer, and that viewer recovering
from a stray
?mine=1.One limit worth stating:
+Nwas exercised live at N=1 rather than N=2, becauseGitHub only assigns users who can reach the repository and the test instance had
two accounts. The count itself is covered by unit test.