fabro/apps/fabro-web/app/components
Bryan Helmkamp d17599898e
feat(web): show creator avatar on run "Created by" cell (#319)
## Summary

The run Overview tab's "Created by" cell rendered every user as a
colored circle with the first letter of their login. Reviewers and run
owners expected the same GitHub avatar shown on `/profile` and in the
top-right nav. The cell only had `login` to work with — the
`PrincipalUser` schema carried no avatar URL.

This threads an optional `avatar_url` through `UserPrincipal`
end-to-end: schema, server auth, and frontend. The avatar is captured at
action time from the request's auth context and persisted with the run's
`created_by` principal — a point-in-time snapshot, the same pattern as
audit logs and chat apps.

## What changed

- **`fabro-types`** — `UserPrincipal` gains `avatar_url: Option<String>`
with `#[serde(default, skip_serializing_if)]`, plus a
`Principal::user_with_avatar` constructor. The existing
`Principal::user` constructor is unchanged (sets `None`), so test
fixtures and CLI/replay call sites need no edits.
- **OpenAPI** — `PrincipalUser` gains an optional nullable `avatar_url`;
Rust (progenitor) and TypeScript clients regenerated.
- **`fabro-server`** — `auth_context_from_session` (cookie auth) and
`classify_user_token` (JWT auth) populate the principal's avatar from
the session/JWT, treating an empty string as `None`.
- **`fabro-web`** — the `run-summary-panel` "Created by" cell renders an
`<img>` when `avatar_url` is present, falling back to the initial circle
otherwise.

## Compatibility

The field is optional with serde defaults, so old persisted runs and
`RunEvent.actor` payloads deserialize unchanged — they show the
initial-circle fallback. No migration or backfill.

## Known gap

CLI-initiated runs (`fabro run ...`) still show the initial circle: the
CLI auth flow hardcodes an empty `avatar_url` in the JWT subject
(`cli_flow.rs:508`). Wiring the avatar through CLI login
(`~/.fabro/auth.json`, JWT claims, refresh-token chain) is a deliberate
follow-up. Web-initiated runs get the avatar today.

## Test plan

- `cargo nextest run --workspace` — 5,832 tests pass, including new
`principal.rs` and `principal_round_trip.rs` cases covering avatar
serialization and legacy-JSON (no-field) deserialization.
- `cd apps/fabro-web && bun test run-summary-panel` — 13 tests pass,
including a new case asserting the `<img>` renders with the avatar src.
- `bun run typecheck`, `cargo +nightly-2026-04-14 fmt --check --all`,
and `clippy --workspace --all-targets -- -D warnings` all clean.
- Manual: restart `fabro server`, create a run from the web UI, confirm
the real avatar renders on the Overview tab; confirm an older run falls
back to the initial circle.

---

[![Compound Engineering
v2.60.0](https://img.shields.io/badge/Compound_Engineering-v2.60.0-6366f1)](https://github.com/EveryInc/compound-engineering-plugin)
🤖 Generated with Claude Opus 4.7 (1M context, extended thinking) via
[Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 09:11:57 -04:00
..
chats feat(fabro-web): port /chats/new + /chats/:id from prototype (#289) 2026-05-16 17:10:16 -04:00
stage-renderers refactor(core): simplify reviewed run cleanup 2026-05-10 22:53:20 -04:00
auth-layout.tsx chore(web): namespace static images under /images and skip in HTTP logs 2026-04-30 08:57:14 -04:00
collapsible-file.tsx refactor(web): drop light mode, go dark-only 2026-04-20 09:27:08 -04:00
editable-run-title.test.tsx feat(web): inline-edit run titles in the run header 2026-05-09 11:54:41 -04:00
editable-run-title.tsx feat(web): inline-edit run titles in the run header 2026-05-09 11:54:41 -04:00
event-debug.tsx refactor(core): simplify reviewed run cleanup 2026-05-10 22:53:20 -04:00
graph-toolbar.tsx style(runs): tighten stages, graph toolbar, billing, and stage sidebar 2026-04-19 16:51:55 -04:00
icons.tsx feat(runs): link stored pull requests 2026-05-08 13:17:08 -07:00
inline-markdown.test.tsx feat(web): render inline Markdown in run titles 2026-05-08 18:30:51 -07:00
inline-markdown.tsx refactor: simplify reviewed run cleanup 2026-05-08 23:29:08 -04:00
interview-dock.test.tsx fix(api): type interview answer submissions 2026-05-08 09:18:35 -07:00
interview-dock.tsx fix(api): type interview answer submissions 2026-05-08 09:18:35 -07:00
pull-request-chip.test.tsx feat(pr): support GitHub pull request associations (#270) 2026-05-16 12:47:27 -04:00
pull-request-chip.tsx feat(pr): support GitHub pull request associations (#270) 2026-05-16 12:47:27 -04:00
run-summary-panel.test.tsx feat(web): show creator avatar on run "Created by" cell (#319) 2026-05-20 09:11:57 -04:00
run-summary-panel.tsx feat(web): show creator avatar on run "Created by" cell (#319) 2026-05-20 09:11:57 -04:00
settings-panel.tsx feat(server): add GET /api/v1/providers and /settings/models page (#321) 2026-05-20 09:05:00 -04:00
stage-sidebar.tsx feat(api): expose stage handlers on run stages 2026-05-08 13:36:02 -07:00
state.tsx feat(web): unify empty/error/loading states in one primitive 2026-04-19 17:22:57 -04:00
steer-bar.tsx feat(web): wire steer bar to interrupt and steer APIs 2026-05-07 22:38:18 -07:00
terminal-view.test.ts refactor(core): simplify reviewed run cleanup 2026-05-10 22:53:20 -04:00
terminal-view.tsx refactor(core): simplify reviewed run cleanup 2026-05-10 22:53:20 -04:00
toast.test.tsx feat(web): add run lifecycle actions 2026-04-20 08:43:30 -04:00
toast.tsx feat(web): add run lifecycle actions 2026-04-20 08:43:30 -04:00
tool-use.tsx style(run-stages): copy affordance, collapsed long output, sticky header 2026-04-19 17:27:25 -04:00
ui.tsx feat(web): add Delete action to archived run actions menu 2026-05-08 07:44:33 -07:00