theam / theam/facility

Assignee avatars on the story board (follow-up to #101)

Open
#174 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
71
Forks
64
Avg merge
15h 38m
Merged PRs (30d)
66

Description

Follow-up to #101, split out as promised in my comment there and in the body of #171.

#171 puts the assignee on the board as text — @login on the story row and on the story header, with +N when there is more than one. #101 also asks for the avatar. I left it out of #171 on purpose, because it turned out to be a different size of job than the good first issue label suggests, and I would rather that be visible than buried in a rendering PR.

What #101 assumed, and what is actually there

Three things, checked against main at ae68401:

  1. There is no avatar component to reuse. packages/ui exports Button, ButtonLink, cx, Field, Select, TextArea, TextInput, Cell, CellAccent, HairlineGrid, Metric, Callout, Divider, Eyebrow, LegendChip, NumeralAnchor, PillTag, StatusDot, toneFor and Terminal. None of them is an avatar.

  2. The initial-letter fallback does not exist yet. #101 says "the initial-letter fallback the topbar already uses". apps/web/components/shell/topbar.tsx renders an image when me.principal.avatarUrl is set and nothing at all when it is not. So the exact gap the fallback is meant to close is currently open in the one place #101 believes it is closed. A user whose GitHub identity has no avatar sees an empty space today.

  3. The wire carries logins, not avatar URLs. assignees is string[] on both PipelineStorySchema and StoryDetailSchema. The only avatarUrl fields that reach the browser belong to the viewer's own principal and to org member rows. So an assignee's avatar has to be derived from the login, exactly as #101 suggests with https://github.com/{login}.png?size=40 — that part holds up.

The render sites also straddle the client/server boundary: the story row is a client component, the story header and the topbar are server components. One primitive has to serve all three.

What I plan to build

I have shaped this and would rather state the decisions than ask open questions — please push back on any of them.

  • One avatar, not a stack. It goes immediately left of @login; +N stays as it is. A stack reads faster but costs horizontal space on a row built for density, and it would change the same presenter and the same test file that #171 touches most.

  • A shared Avatar in packages/ui, used by the story row, the story header and the topbar. Retrofitting the topbar is the point, not scope creep: without it, item 2 above stays exactly where it is.

  • The fallback is the initial letter on one neutral background. No colour derived from the login. The design tokens in packages/ui (agent, human, ok, bad, info, machine, muted) are a status palette, so hashing a login into them would hand someone the colour of "failed" or "running".

  • The remote image is drawn as a CSS background-image layered over the letter, not as an <img>. This is the part worth reviewing closely, because it is the one that answers #101's own question about deployments that must not reach github.com.

    I tested this in Chromium, Firefox and WebKit, with the connection to github.com aborted and with a login that 404s. An <img> that fails to load makes every engine paint its own broken-image glyph on top of the letter; alt="" does not suppress it and neither does color: transparent. A background image that fails to load paints nothing, so the letter underneath is untouched — a failed avatar is pixel-identical to no avatar at all, in all three engines.

    That means a deployment whose browsers cannot reach github.com degrades to the initial letter with no configuration: no env var, no NEXT_PUBLIC_* convention, no .env.example, Dockerfile, compose or self-host docs change. It just works.

  • No next.config.ts change and no next/image. apps/web/next.config.ts has no images key at all today, so the optimiser route would mean adding remotePatterns and a caching story. The background-image approach needs neither, and it keeps packages/ui free of a framework dependency.

  • No API, database or sync change.

Considered and not doing

Serving avatars through the API so the browser never talks to github.com. This is the only approach that keeps github.com off the client without a fallback, and I took it seriously. It costs a new route plus one server-side fetch per assignee per board render, which is a great deal larger than the carve-out #101 describes. Happy to be overruled if you would rather have it.

One trade-off, stated up front

The topbar's image tag currently carries referrerPolicy="no-referrer", so github.com gets no Referer today. A CSS background image sends the document's referrer, and referrer policy is a property of the fetch initiator — CSS gives no way to set one. I measured this: a pseudo-element, a child element, an inline style, an external stylesheet carrying referrerpolicy="no-referrer", and a custom-property indirection all send the origin, in all three engines. The only levers are document-wide (<meta name="referrer"> or a Referrer-Policy header), which would change every other request the app makes.

So after this change, github.com learns the deployment's origin when it serves an avatar. It learns nothing else — the browser connects from the user's own machine either way. If that trade is not acceptable, the API-proxy approach above is the alternative, and I would rather know now than at review.

Also worth knowing: https://github.com/{login}.png?size=40 302s to https://avatars.githubusercontent.com/u/{id}?v=4. Two requests, two hosts — relevant only if a Content-Security-Policy is ever added. There is none today.

Possible follow-up, not built here

A "turn remote avatars off" switch, for a deployment that wants the letter fallback everywhere by policy rather than by network reachability. The behaviour above does not need one, so I have not built it blind. Say the word if you want it.

Scope

apps/web and packages/ui only. Not touching apps/web/components/project/delivery-intelligence.tsx, which renders assignees with a different grammar on purpose and which #171 also left alone.

I have this shaped and am ready to open a PR. It builds on #171, so it will be stacked on that branch and will read oddly until #171 lands — I will say so in the PR body.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with apps/web/components/shell/topbar.tsx and the assignees fields in PipelineStorySchema and StoryDetailSchema, then inspect the story row and header entry points. Check the shared components exported from packages/ui before implementing the Avatar across the stated apps/web and packages/ui scope. Done means avatars show the remote image when available and the initial-letter fallback when it fails or is absent, without API or configuration changes.

Written by the indexing model from the issue text.

Assessment

Tech stack
next.js, typescript
Domain
frontend
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.