Skip to content

Per-author fireflies + co-author support, commit-pane perf, gem palette#50

Merged
thalida merged 28 commits into
mainfrom
worktree-per-author-fireflies
May 30, 2026
Merged

Per-author fireflies + co-author support, commit-pane perf, gem palette#50
thalida merged 28 commits into
mainfrom
worktree-per-author-fireflies

Conversation

@thalida

@thalida thalida commented May 30, 2026

Copy link
Copy Markdown
Owner

Summary

Three feature areas across 28 commits.

Per-author fireflies + co-author support

  • API parses Co-authored-by: trailers via git's native %(trailers) placeholder. CommitEntry.author: strauthors: list[str]; emails stripped (local-part fallback for email-only trailers).
  • Fireflies render one orb per distinct author of each commit (was one per commit), each in the author's color.
  • Sidebar commit pane stacks one row per author.
  • Top breadcrumb chip shows <primary> (+N) for multi-author commits.
  • Orbit rings (hover/select) fan out one ring per author orbit; hover uses a lighter pastel of the author's hue, selected uses the shared RAINBOW chase used by tree-canopy outlines.

Commit-pane perf + layout

  • Renders manifest-resident metadata (authors, sha, subject, date, files, same-day) synchronously on selection — kills the full-pane "Loading commit…" splash.
  • Lazy body sits at the BOTTOM of the pane in a mutable slot (loading / text / error / hidden). When the body resolves, nothing above moves.
  • Subject promoted to title position (top, font-xl, primary, weight 600); authors shrunk to secondary metadata (12px, smaller dot).
  • Same-day badge: Busy day: 24 commits; full date moves to a hover tooltip.
  • Body's nested max-height scrollbar removed — pane-body owns scrolling.

Gem palette + color utilities

  • Generic OKLCH/OKLab/HSL math consolidated into scene/utils/color/colors.ts. Adds oklchToHex for palette generation.
  • Gem face palette generated as an OKLCH rainbow at L=0.75, C=0.22 (vivid, perceptually-even hue spacing). User overrides via Controls unchanged.
  • Date formatting helpers consolidated into utils/dates.ts (formatRelativeAge, formatRelativeAgeShort, formatFullDate, formatShortDate). Drops private helpers in appFooter and commitPane.

Tests + docs

  • New fixture commit in api/tests/fixtures/sample-repo exercises co-authored trailers including a duplicate-primary-author edge case and an email-only trailer.
  • _build_authors_list direct unit tests + integration coverage on the multi-author commit.
  • New tests across fireflies, orbit rings, sidebar pane, top header chip, dates helpers.
  • README updated to document co-author parsing.

Test plan

  • Run just test — expect ~264 api tests + ~2010 app tests passing.
  • Load a real repo with Co-authored-by: commits (e.g. this repo). Click a multi-author tree:
    • N visibly distinct fireflies orbit the tree in author colors.
    • Top header chip shows <primary> (+N).
    • Sidebar lists every author, primary first.
    • Skeleton renders instantly; body text fades in below subject without bouncing other content.
    • Hover the tree — N orbit rings render, each in a lighter pastel of its author's color.
    • Select the tree — rings switch to the rainbow chase.

🤖 Generated with Claude Code

thalida and others added 28 commits May 30, 2026 11:44
Wire-format rename in preparation for Co-authored-by trailer parsing
and per-author fireflies. Cache version bump to 11 silently
invalidates old git-history caches.

No behavior change yet — every commit still has exactly one author
in authors[0]. Phase B will parse trailers.
Wire-format mirror of the api change. Most readers pull authors[0];
the top app-header breadcrumb chip surfaces co-author count via a
'<primary> (+N)' suffix when N > 0. Phase B+ will parse trailers
server-side and per-author fireflies will fan out client-side.
Uses git's native %(trailers:key=Co-authored-by,valueonly,separator)
placeholder — no body parsing on our side. Trailers field placed
before %s in the log format so the existing tabs-in-subject invariant
holds. Deduped per commit on the name string (primary author + each
unique co-author).

Adds a multi-author fixture commit with a Signed-off-by trailer
(ignored) and a duplicate primary-author trailer (deduped) to verify
both edge cases.
Privacy fix — _build_authors_list previously emitted the full email
when a trailer had no name (e.g. `<bot@host>`), contradicting the
"emails stripped" guarantee on CommitEntry and CommitDetailResponse.
Now keeps only the email local-part as the identity.

Adds direct unit tests for the helper (no test coverage existed
before — only integration coverage via the fixture).

Also fixes a stale docstring on _serve_commit_detail that still
referenced the pre-rename `author` field.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the ORBS_PER_TREE=1 constant with iteration over
commit.authors. Each author gets a stable, per-(sha, author) seed
so multi-author commits emit visibly distinct orbits with
per-author colors. Co-authorship counts as full credit toward
the per-author scale tally.
- Fix test comment claiming Alice was "solo" on a co-authored commit
- Rename per-tree-orb-count test to clarify it now covers the
  single-author regression case (post-Phase-C the count isn't a
  general invariant)
- Restore the dropped inline comment explaining the orbit-radius
  multiplier
- Drop the stale ORBS_PER_TREE reference from renderLoop.ts's
  structural-key comment

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the single .commit-author block with a loop over
commit.authors. Primary author stays first; each row carries the
per-author color dot. Existing CSS for .commit-author renders the
rows as a stack (block-level divs).
- appHeader.ts: update stale comment to match the new (+N) format
- TODO.md: mark per-author fireflies + co-author support done
Before this fix, hover/select on a multi-author commit's tree showed
only ONE orbit ring even though Phase C renders multiple per-author
orbs. Root cause: orbitRings.ts keyed its placement lookup by
commitIndex with a single FireflyPlacement value, so last-write-wins
collapsed the N per-author orbs to one. The single ring built used
only the last author's orbitRadius/orbitTilt.

Fix: store FireflyPlacement[] per commitIndex, and let each slot
(hover + selected) own N meshes — one per author orb on the active
commit. Disposal walks the array.

Adds 5 multi-author tests (hover N rings, selected N rings,
hover+selected on different multi-author commits, clear-hover
disposes all N, distinct geometries per author).
Hover orbit rings now render in each author's pastel hue (OKLCH at
L=0.90, C=0.10 — pastel of the orb color) so a hovered multi-author
tree shows one tinted ring per author. Selected orbit rings render the
shared RAINBOW chase used by the tree-canopy outline (vertexColors on
the TubeGeometry, hue-per-tubular-segment, advanced every frame).

Drops the now-dead ORBIT_RING_HOVER_COLOR and ORBIT_RING_SELECTED_COLOR
knobs from FirefliesConfig and the Controls pane. Bumps the schema
version comment to v8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Was OKLCH (L=0.90, C=0.10) — too pastel; the hovered ring read as
generic-light instead of "author X's color, but lighter". Now L=0.84,
C=0.18 (same chroma as the orb, only +0.06 lift on lightness) so the
hue identity carries through.
…rs.ts

Three previously-separate modules (hsl.ts, oklch.ts, colorInterp.ts)
covered overlapping color-math territory. Merge into one colors.ts
with sections for each space:

- HSL string helpers (mirror hsl.glsl for shader parity)
- OKLab Cartesian conversion (out-params for hot paths)
- OKLCH polar wrapper (allocates; palette-generation use cases)
- linear sRGB → hex encoding
- interpolateOklch (perceptual color interpolation)

Update the three importers (authorColor.ts, treeRenderer.ts, gem.ts —
gem in next commit) and relocate colorInterp.test.ts next to its
module. Delete the old hsl.ts and colorInterp.ts.

The oklchToLinearRgb function previously inlined in authorColor.ts
is gone — it now lives in the shared module and reuses oklabToLinear
internally, removing the duplicate OKLab→linear-sRGB matrix.
Hand-picked pastel hex values gave perceptually-uneven transitions
(yellow read brighter than blue at the same lightness). Generate the
8 defaults from OKLCH at L=0.78, C=0.18 across evenly-spaced hues
instead — each face is the same perceptual lightness, so the
rainbow transitions evenly around the gem.

Matches the firefly orb palette parameters (also L=0.78, C=0.18) so
the gem reads visually consistent with the per-author orbs.

User overrides via Controls are unaffected — only the defaults change.
The four favicon triangles now use 4 of the 8 OKLCH-derived face
colors (hues 135°/225°/315°/45° at L=0.78, C=0.18) so the icon reads
as the same gem the user sees floating in the city. The previous
hand-picked #bfff33/#26e5ff/#9940ff/#ff338c were perceptually uneven
in the same way the old face palette was.

gem-simple.svg stays untouched — it's the monochrome variant for
inline use on colored backgrounds.
The previous quartet (135/225/315/45) put lime (NW) next to sky (NE)
on the top edge — both cool-greens-to-blues, too perceptually close
for a tiny icon. Replace with the other four of the 8-face palette
(90/0/180/270): gold + pink on top (both warm, but clearly distinct
hues), teal + periwinkle on bottom (the less-prominent diagonal).

Still 4 of the 8 OKLCH face colors, so the icon still reads as "the
gem palette" — just a different sample.
…vicon sizes

Without strokes between the 4 triangles, anti-aliasing at 16-24px
favicon scales blended adjacent fills into a muddy gradient — even
with high-contrast colors the user couldn't tell faces apart.

Add the same white stroke pattern gem-simple.svg already uses
(stroke-width=2, stroke-linejoin=round). Matches the in-app gem's
EDGE_COLOR (#ffffff) so the icon and the 3D gem render with the
same "outlined faces" look.
…order

Four primary-color faces with high lightness AND hue separation so
they stay distinct at 16-24px favicon sizes without needing a stroke.
Picks 4 of the 8 OKLCH face hues — 0°/90°/180°/270° (pink/gold/teal/
periwinkle) — and arranges them clockwise around the diamond as
warm/cool/warm/cool so every adjacent edge has a temperature jump.
Same uniform lightness as the in-app gem, so the favicon literally
samples the gem palette.
Keep the in-scene gem palette OKLCH-derived (config/components/gem.ts
still generates faces via oklchToHex). Only the favicon goes back to
its hand-picked quartet — those colors hold up better at 16-24px
favicon sizes than any uniform-lightness 4-of-8 subset of the OKLCH
palette did without strokes.

Also picks up a stale import path in colorInterp.test.ts that was
missed in the consolidation commit.
Faces are large solid areas, so a moderate chroma bump reads as
noticeably more saturated without crossing into neon. The fireflies
stay at their original L/C because they're tiny moving dots whose
color already gets pushed by the additive bloom pass.
Render every piece of manifest-resident metadata (authors, sha, date,
files, same-day, subject) synchronously on setCommit. Only the
lazy-fetched body waits for /api/commit. The full-pane 'Loading
commit…' blanking state goes away — clicking a commit tree now
displays the skeleton instantly, and the body fades in below.

Layout reorder so the lazy region sits at the BOTTOM of the pane:
authors → meta (age · files) → same-day → no-remote → subject →
body slot. When the body resolves, nothing above moves.

The body slot has four states (loading / text / error / hidden)
and only this slot mutates after the skeleton renders. Errors no
longer blank the rest of the pane.

Subject bumps to font-xl / text-primary / weight 600 — it's now the
title of the message block at the bottom. Body's nested max-height
scrollbar is gone; the pane-body owns scrolling.
- Move _bodySlotEl + BodyState declarations to precede _renderEmpty
  (was previously order-fragile — _renderEmpty wrote to _bodySlotEl
  before its declaration line)
- Update CSS comments that still referenced the old "footer" layout
  (.commit-meta and friends now sit ABOVE the message, not below it)
- Drop the empty .commit-message-body-slot and .commit-message-body-slot
  --error CSS rules — base class has no styles, error modifier is a
  JS-side semantic marker only

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reorder: commit title (subject) moves to the top of the pane so it's
the first thing the eye lands on. Authors drop one type-step (xl→lg,
14px→12px) and the author dot shrinks from 10px to 8px so they read
as secondary metadata below the title.

New order: subject → authors → meta → same-day → no-remote → body slot.
- .commit-age now reads just "1 month ago" (was "committed 1 month ago")
- Stacked author rows tighten from ~12px to 4px effective gap. The
  parent flex gap stays at 8px for between-section breathing room;
  consecutive .commit-author rows pull up with a small negative margin.
Visible text drops "that day" ("Busy day — 24 commits that day" →
"Busy day: 24 commits") and moves the date into the hover tooltip:
"24 commits on March 12, 2026". The tooltip uses toLocaleDateString
with a manual YMD parse so the date doesn't shift across timezones
the way `new Date('YYYY-MM-DD')` would.
Move formatRelativeAge from views/widgets/ to utils/ and add three
sibling helpers used elsewhere:
- formatRelativeAgeShort (was appFooter's private _relativeTime)
- formatShortDate (was appFooter's private _formatDate, sans the
  null-handling — call sites already null-guard)
- formatFullDate (was commitPane's private _formatFullDate)

appFooter and commitPane drop their private copies; inputHandlers
updates its import path. Test file moves alongside the source.
@thalida thalida merged commit 4ca4d9b into main May 30, 2026
1 check passed
@thalida thalida deleted the worktree-per-author-fireflies branch May 30, 2026 16:52
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.

1 participant