Skip to content

feat(info): World Almanac — repo metadata in the Info tab (#60)#72

Merged
thalida merged 65 commits into
mainfrom
feat/issue-60-world-metadata
Jun 26, 2026
Merged

feat(info): World Almanac — repo metadata in the Info tab (#60)#72
thalida merged 65 commits into
mainfrom
feat/issue-60-world-metadata

Conversation

@thalida

@thalida thalida commented Jun 19, 2026

Copy link
Copy Markdown
Owner

Closes #60.

Adds a World Almanac to the left-sidebar Info tab — a travel-guide of repo superlatives derived entirely client-side from the manifest (no backend changes). Built on a new reusable in-pane subtab primitive.

What's here

PaneTabs primitive (components/PaneTabs/) — horizontal subtabs inside a Pane body. Controlled, presentational, arrow-key navigable.

Info tab → Overview | Readme subtabs

  • InfoPane is now a thin shell hosting the two subtabs (Overview is the default).
  • ReadmePane is today's README logic extracted verbatim (no behavior change).
  • WorldPane renders the almanac.

The almanac (views/InfoPane/almanac.ts, pure + unit-tested)

  • Overview — concise owner/repo name, founded date, branch, remote link, Latest commit (sha + subject, linked), and a language breakdown using the shared ExtensionBadge (file-type colors).
  • Buildings (code files) — Newest/Oldest (created), Freshest/Stalest (last commit), Tallest/Shortest (lines), Widest/Narrowest (bytes).
  • Billboards (media files) — split out as their own class (they render as aspect-sized ad panels, not line-height buildings): count, largest by bytes, highest resolution.
  • Streets (dirs) — deepest alley, biggest neighborhood (root excluded).
  • Forest (commits → trees) — grandest/sparsest canopy, busiest day, longest streak.
  • Fireflies (authors) — count, most prolific author.

Travel-guide behavior

  • Each landmark row has a Focus button that selects + flies the camera to that building/tree (reuses selectPath/focusPath/focusCommit; adds a small selectCommit to the picker for symmetry).
  • The Forest section gates on the Trees layer — when trees are off it shows a hint instead of dead-click canopy rows.
  • Every row has a tooltip explaining its meaning and in-world encoding (the date rows clarify that modified is the last-commit date — an uncommitted working-tree edit won't change Freshest/Stalest until committed).
  • Paths truncate from the directory side so the filename stays whole; values are stacked (label over value) for a clean, scannable layout.

Notes

  • Fixed a pre-existing self-referential --cc-font-mono token (var(--cc-font-mono) → a real monospace stack) surfaced by the language badges — affects mono text app-wide.
  • Reactivity is covered by regression tests (signal change → Overview re-renders, including through the non-re-rendering shell), so live-update polls reflect without a manual refresh.

Out of scope (tracked separately)

Testing

just test-app green (2348 unit tests); tsc + eslint + prettier clean.

🤖 Generated with Claude Code


Update — almanac now computed server-side (folded in per review)

Per the discussion, the almanac stats are computed once on the backend during the scan and shipped in the manifest, instead of walking the tree in the browser. The client computeAlmanac is now a thin mapper.

Backend

  • api/services/stats.py::compute_repo_stats(tree, commits)RepoStats (pure, unit-tested): per-repo superlative leaders (oldest/newest/freshest/stalest/tallest/shortest/widest/narrowest building, largest/highest-res billboard, deepest alley, biggest neighborhood, grandest/sparsest canopy, busiest day, longest streak, author counts) + the building-size normalization ranges.
  • RepoStats schema (TypedDict + Pydantic), emitted at manifest-wrap, cache version → v8. Contract regenerated via just gen-types; the drift guard verifies the hand-written types.

Frontend

  • computeAlmanac maps manifest.stats leaders → facts (no walk/pickFile/commit loops). Output is identical to the client-side version.
  • The layout's computeFileStats reads manifest.stats.fileLines/fileBytes instead of re-walking the tree on every apply/live-update.

On the rendering range (1:1 guarantee): fileLines/fileBytes are documented as the building-size normalization range (non-zero), matching the old computeFileStats exactly so the world renders identically. Honest "smallest file" lives in the narrowest/shortest leaders. The frontend also clamps the byte range to ≥1 (defense against log(0) → NaN geometry).

Scope notes

  • Fireflies' per-author tally was evaluated for consolidation but left as-is — it's intrinsic to the fireflies' existing commit loop, not a redundant tree-walk; threading stats through wasn't worth the coupling/rendering risk.
  • A test-suite audit (golden bench/ guards aren't run by the default suite; fixture gaps; dupe check) is tracked separately in Audit the test suite: golden guards not run by default, fixture + dupe gaps #73.

Backend just test-api green (250 tests); frontend --project=unit green (2347); tsc + eslint + prettier + the manifest contract guard all clean.


Update — Overview redesign pass + Info leads the sidebar

A design pass on the Overview tab so it reads like a polished travel-guide rather than a stat dump, plus a small left-sidebar change so a loaded world greets you with it.

Section layout (rhythm + pairing)

  • Min↔max facts render as bound duos under one dimension label (Height over Shortest+Tallest, etc.) instead of eight loose rows; each section carries its world-layer icon + accent and an overview summary line ("315 fireflies · ~40 commits each") for a consistent opening beat.
  • Streets now reads by direct children: Biggest (most direct children) + Smallest (fewest direct children, ties broken by fewest descendants), with Deepest under its own header.
  • Fireflies shows Most active then Least active; a lone Billboard collapses to a single "Only" spotlight row instead of faking a broken pair.
  • Landmark rows keep an always-visible Focus affordance (faint → bright on row hover, matching .btn-icon); rows stretch as flex items so values truncate cleanly with no hardcoded widths.

Intro card

  • Flavor blurb replaces the counts-heavy sentence — An 8-year-old city of 312 buildings, mostly Python. (age vs. live clock, building count = non-media files, dominant language). Removes the old 1 fireflies plural bug and fixes the A/An article.
  • Language composition bar — a GitHub-style stacked bar whose segments use the same extension→hue as the legend chips, each with a name · N files (pct%) tooltip and a neutral tail for the long-tail types.
  • Latest is informational text; the commit hash is the one explicit link (accent + hover underline, matching the Remote link) to the commit on the remote — plain text when there's no remote. Meta values stretch to fill the right column.

Sidebar

  • Info leads the activity bar and is the default tab, and a useSignalEffect on CURRENT_SOURCE re-opens Info on every committed world load (cold-boot ?src= and source-picker switches both write it; live-reloads don't, so a manual tab change persists between loads). The panel's collapsed state is untouched.

Helpers: humanAge (date → "2-year-old"), languageLabelForExt (".ts" → "TypeScript").

Frontend --project=unit green (2373); tsc + eslint + prettier clean.

@thalida thalida linked an issue Jun 19, 2026 that may be closed by this pull request
aria-selected={selected ? 'true' : 'false'}
class={selected ? 'pane-tab pane-tab--active' : 'pane-tab'}
onClick={() => onSelect(t.id)}
onKeyDown={(e) => {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want these keyboard shortcuts.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the arrow-key navigation (and its tests) in 539e3a6.

Comment thread app/src/views/InfoPane/almanac.ts Outdated
Comment thread app/src/views/InfoPane/almanac.ts Outdated
Comment thread app/src/views/InfoPane/almanac.ts Outdated
Comment thread app/src/views/InfoPane/almanac.ts Outdated
Comment thread app/src/views/InfoPane/InfoPane.tsx Outdated
Comment thread app/src/views/InfoPane/InfoPane.tsx Outdated
Comment thread app/src/views/InfoPane/ReadmePane.tsx Outdated
Comment thread app/src/views/InfoPane/WorldPane.tsx Outdated
Comment thread app/src/views/InfoPane/WorldPane.tsx Outdated
thalida and others added 22 commits June 19, 2026 19:55
Pure computeAlmanac(manifest) function with TDD: overview totals/languages
from root DirNode fields, 8 building superlatives (tallest/shortest/widest/
narrowest/oldest/newest/brightest/most-faded) each with a LandmarkRef for
camera fly-to. 10/10 tests pass, tsc clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Controlled, presentational tab strip for below pane headers; arrow-key
navigable. Installs @testing-library/preact as a dev dep for the test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrites paneTabs.test.tsx to use raw preact render + flush() helper
(matching the nodeIcon.test.tsx convention) and restores package.json /
package-lock.json to remove the @testing-library/preact dependency
added in the previous commit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Move README fetch/render logic verbatim into ReadmePane (body-only, no
Pane chrome). InfoPane delegates to it — rendered DOM is identical.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renders the World Almanac (overview + superlative sections) in a body-only
pane. Landmark rows are buttons that call selectPath/focusPath (file/dir)
or selectCommit/focusCommit (commit) to fly the camera. Two unit tests
(empty state + landmark click) pass with TDD flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…andmark rows

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rewrites InfoPane as a PaneTabs shell hosting World (almanac, default)
and Readme subtabs; body-only panes delegate chrome to the parent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Restructure facts into primary (identifier, truncates) + secondary (metric),
  rendered as stacked rows (label over value) instead of justified pairs — long
  paths/shas truncate from the left so filenames stay visible.
- Show the actual created/edited date on the oldest/newest/brightest/faded rows.
- Gate the Forest section on TREES.ENABLED; show a help note when off, so its
  canopy landmarks can't be dead-clicked with the Trees layer hidden.
- Fix self-referential --cc-font-mono token (was var(--cc-font-mono)) with a
  real monospace stack — affected mono text app-wide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Intro:
- Title uses labelFromSource (owner/repo) instead of the full URL, which is
  still shown once as the clickable Remote link — no more duplication.
- Language breakdown uses the shared ExtensionBadge (file-type colors), matching
  the header + file pane instead of plain chips.
- Latest row is now short-sha + subject, clickable to the commit on the remote.
- Bigger, less-muted blurb so it reads as a summary, not help text.

Interactions:
- Replace whole-row click with an explicit Focus icon button per landmark row —
  clearer affordance, and non-landmark rows have none.
- Fix path truncation: split dir/filename (filename stays whole) instead of the
  direction:rtl hack that reordered leading dots (.github → trailing dot bug).

Buildings:
- Exclude media/binary files from the line-based Tallest/Shortest picks (they're
  sized by aspect, not lines, and a 0-line image was winning Shortest).
- Rename Brightest/Most faded → Freshest/Stalest (clearer; secondary shows the
  edit date).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Media files render as billboards (image/video ad panels) sized by aspect, not
lines — fundamentally different from code buildings. Give them their own
section with media-appropriate superlatives: count, largest by bytes, and
highest resolution (when pixel dimensions are known). Buildings is now
code-only; media no longer competes in any building superlative.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The free-floating counts read as unrelated to their badge; group each
extension badge with its count inside a subtle bordered pill so each language
reads as one unit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Rename the Overview subtab (was 'World', which read as the 3D world view in
  an app that is literally a 3D world).
- Align fact rows flush with their section title (drop the row inset that made
  them look indented/nested).
- Move the focus button onto the value line so it balances against the primary
  text instead of centering on the whole two-line block.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…re oldest)

Group the created/edited date superlatives together at the top (Newest, Oldest,
Freshest, Stalest), with newest before oldest, then the size-based picks.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Guard that the Overview re-renders when the MANIFEST signal changes — both
directly and through the InfoPane shell (whose parent does not re-render) —
i.e. live-update polls are reflected without a manual refresh.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each superlative row gets a title explaining what it means and its in-world
encoding. The date rows clarify that 'modified' is the last-commit date — an
uncommitted working-tree edit won't change Freshest/Stalest until committed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thalida
thalida force-pushed the feat/issue-60-world-metadata branch from cbfbb7d to 0667f97 Compare June 19, 2026 23:57
thalida and others added 2 commits June 19, 2026 20:10
- Remove PaneTabs arrow-key navigation (+ its tests).
- Extract generic helpers to utils: dateMs → utils/dates; formatCount +
  pluralize → new utils/format.ts (was fmtCount/pluralize in almanac).
- LandmarkRef.kind reuses the NodeKind enum instead of a bare string union.
- Set each fact's tooltip inline at creation; drop the label-keyed FACT_TIPS map.
- computeAlmanac always emits every section; empty ones carry a note empty-state
  (incl. the Trees-disabled Forest notice, now produced by compute, not the
  view). Takes treesEnabled as an arg.
- ExtensionBadge reads BUILDINGS/STREETS itself; dropped huePalette/asphaltColor
  props from all callers (PathBreadcrumbs, StreetPane, FilePreviewPane, Overview).
- Rename WorldPane → OverviewPane (file/component/css/test) to match the tab.
- InfoPane: InfoTab enum + a data-driven tabs array (id/label/icon/Component)
  rendered generically, replacing hardcoded id strings + the ternary.
- Simplify ReadmePane._findRootReadme (drop the unreachable bare-DirNode branch).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A queued or in-flight reactiveRebuild run() kept executing after dispose()
because dispose only stopped the effect — it never bumped the generation, so
isCurrent() still returned true. The trees decoration's deferred run (it awaits
rAF + setTimeout) would then resume against a torn-down scene, throw, and log
via the default onError. Under the parallel test suite that console.warn landed
during worker teardown → the intermittent 'Closing rpc while onUserConsoleLog
was pending' flake (seen in tests/city/initialFraming.test.ts).

dispose() now bumps the generation so pending/in-flight runs bail at their
isCurrent() checks. Adds a regression test for both the queued and in-flight
cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread app/src/views/InfoPane/almanac.ts Outdated
newest,
created(newest),
'Most recently created file, by git history.'
),

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe make these functions take objects instead? also it could be an array of objects, and then just loop over the array to generate each file fact / facts array.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 70680bdfileFact/dirFact/commitFact now take a single options object, and the Buildings facts are an array mapped through fileFact then compact-ed.

Comment thread app/src/views/InfoPane/almanac.ts Outdated

/** Pixel area of a media file, or NaN when its dimensions are unknown. */
function pixels(f: FileNode): number {
return f.media_width && f.media_height ? f.media_width * f.media_height : NaN;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be here and not in any utils?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That pixels() helper is gone — pixel area is computed on the backend now (api/services/stats.py:_media_pixels, feeding maxMediaPixelsFile). The almanac no longer derives it; it just reads the leader. (Part of the server-side stats move in this PR.)

Comment thread app/src/views/InfoPane/almanac.ts Outdated
label: 'Billboards',
primary: pluralize(media.length, 'billboard'),
tip: 'Image & video files — they render as billboard panels sized by aspect.',
} as AlmanacFact,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why can't this be a fileFact call?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a count summary (the total number of billboards) — there is no single file to fly to, so it cannot be a fileFact (those carry a path + landmark). But you are right it should not be hand-rolled: 70680bd adds a statFact helper for these plain count/date/name facts, used here and by busiest-day / streak / fireflies.

Comment thread app/src/views/InfoPane/almanac.ts Outdated
if (depth(d.path) > depth(deepest.path)) deepest = d;
if (d.descendants_file_count > biggest.descendants_file_count) biggest = d;
}
const deepestDepth = depth(deepest.path);

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

everything else has helper / utils functions... why doesn't this?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed — 70680bd adds statFact for the no-landmark summary facts (busiest day, longest streak, billboard/firefly counts, most-prolific author), so nothing hand-rolls a fact object anymore.

Comment thread app/src/views/InfoPane/almanac.ts Outdated
label: 'Biggest neighborhood',
primary: biggest.path,
secondary: pluralize(biggest.descendants_file_count, 'building'),
mono: true,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does mono mean or do?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mono meant "render the primary monospace + left-truncate" — it was set on exactly the path/sha facts, which are exactly the ones with a landmark. So it was redundant: dropped it in 70680bd and the renderer now derives monospace from landmark (a path/sha is a code identifier). One less flag to explain.

Comment thread app/src/views/InfoPane/almanac.ts Outdated
return {
key: 'forest',
title: 'Forest',
facts: [

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate out the utils/functions/methods that do the calculations from the ui fact rendering itself.
maybe the ui part can be a proper component(s)? and keep the logic seperate. right now this file conflates a few tasks.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the calculation this file used to do — the min/max loops over commits/dirs/files, pixel area, longest-streak — moved to the backend (api/services/stats.py) earlier in this PR, so almanac.ts is now a pure mapper: manifest.stats leaders → display facts. The UI (how a fact renders) is already a separate component layer in OverviewPane.tsx (FactRow / PrimaryValue). 70680bd finishes the tidy-up: object-arg builders, a statFact helper, section tooltips. If you still want the section/fact rendering broken into smaller standalone components, happy to — lmk.

Comment thread app/src/views/InfoPane/almanac.ts Outdated
if (commits.length === 0) {
return {
key: 'fireflies',
title: 'Fireflies',

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a tooltip to headers to explain waht a fireflie is, what a forest is, etc.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 70680bd — each section now carries a tip ("Each commit plants a tree…", "Each distinct commit author is a firefly…", etc.) rendered as the <h3> header tooltip.

Comment thread app/src/views/InfoPane/InfoPane.tsx Outdated
<Pane paneClass="info-pane" title="Info" onClose={onClose}>
<PaneTabs tabs={INFO_TABS} active={tab} onSelect={(id) => setTab(id as InfoTab)} />
<div class="pane-body info-body">
<Body manifest={manifest} />

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't this be active.Component directly?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep — 534216c renders <active.Component manifest={manifest} /> directly and drops the Body local.

Comment thread app/src/views/InfoPane/OverviewPane.tsx Outdated
<span class="almanac-sha">{repo.head_sha.slice(0, 7)}</span> {repo.head_subject}
</>
)}
{repo.dirty ? ' (uncommitted changes)' : ''}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since the city doesn't render dirty maybe don't display this? or can the city support rendering/updaing based on dirty files?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the "(uncommitted changes)" marker in 70680bd — the city does not render dirty state, so it was pointing at nothing. Filed #74 to explore actually visualizing uncommitted changes (then the almanac could resurface it, earned).

thalida and others added 2 commits June 19, 2026 20:56
Pure function over the in-memory tree + commits; no I/O. Returns file/dir/commit
leaders and min/max ranges so the frontend reads pre-computed stats instead of
re-walking the tree. 4 TDD tests covering media/text partitioning, dir depth
exclusions, commit leaders, streak, authors, and empty-tree edge cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
thalida and others added 4 commits June 19, 2026 23:13
F1 made layoutCity read building sizes from manifest.stats instead of
walking the tree, but the decoration benches passed a bare { tree } with no
stats — so every building collapsed to min-width, shifting the layout/bbox
and silently breaking the bit-identical golden guard (and skewing the
profiler). Add a fileStats(tree) helper and pass real file ranges, restoring
the original 74166740 baseline — confirming the whole branch (incl. the
commit-stats consolidation) is bit-identical to main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
compute_repo_stats did ~15 separate passes over the same file list (one
_pick/min/_range per superlative). Fold them into one pass over files, one
over dirs, one over commits — each updates every winner/range/count as it
goes. Output is bit-identical (test_stats unchanged, all first-seen ties
preserved via strict >/<). Addresses PR review (stats.py:171).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per PR review (manifest.py:198/204): the backend should expose what a stat
IS, not how the frontend renders it. Rename the world-metaphor field names to
metric names (the almanac maps them back to 'Tallest building' etc.):

  dateRanges: createdMin/Max, modifiedMin/Max → min/maxCreated, min/maxModified
  ranges:     fileLines/fileBytes            → lineCountRange/byteSizeRange
  files:      tallest/shortest/widest/narrowest/oldest/newest/freshest/stalest
              → max/minLinesFile, max/minBytesFile, old/newCreatedFile,
                new/oldModifiedFile; largest/sharpestMedia → maxMedia{Bytes,Pixels}File
  dirs:       biggest/deepestDir → maxFilesPerDir/maxDepthDir
  commits:    grandest/sparsestCommit → max/minFilesPerCommit
  day/streak: busiestDay → maxCommitsPerDay; longestStreakDays → maxCommitStreakDays

Cascades through Pydantic + TypedDict → gen-types → manifest.ts → almanac,
layout, buildings color, EMPTY_REPO_STATS + tests. Output values unchanged
(golden still 1:1).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address PR review on almanac.ts:
- fileFact/dirFact/commitFact take an options object; building facts are an
  array mapped through fileFact (almanac.ts:147)
- add statFact for plain count/summary facts so Billboards / busiest-day /
  streak / fireflies stop hand-rolling fact objects (almanac.ts:210, :272)
- drop the `mono` flag — it was exactly 'has a path/sha primary', i.e. 'has a
  landmark'; the renderer now derives monospace from `landmark` (almanac.ts:289)
- each section carries a `tip` explaining the layer, shown as the <h3> header
  tooltip (almanac.ts:303)
- drop the 'uncommitted changes' dirty marker from Overview (the city doesn't
  render dirty state) (OverviewPane.tsx:139)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thalida and others added 3 commits June 19, 2026 23:42
PR review (InfoPane.tsx:49): a member-expression component reference works in
JSX, so the intermediate `const Body = active.Component` is unnecessary.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A media-heavy repo (Infisical: ~2.6k images) fired one GET /api/file per
billboard, throttled to 4 at a time by the GPU semaphore — thousands of
serial round-trips that exhaust the browser's HTTP/1.1 connection pool.

Add POST /api/files: trust-checks each path exactly like GET /api/file and
returns {path: {mime, b64}} for many small images in one round trip (images
only, ≤8MB; videos still stream their poster). The frontend mediaBatch.ts
coalesces image paths requested at scene build into batches of 32, feeds the
bytes to the existing <img> decode path via object URLs, and falls back to
the individual GET for anything the batch omits. The fetch is no longer
slot-gated (only the decode + GPU upload is), so the coalescing window sees
every billboard at once: ~2.6k requests → ~82 batched requests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… cap

Trees are capped at TREE_MAX_CELLS (100k), but orbs = trees × authors-per-
commit, so heavy co-authorship on a Linux-scale repo can balloon orbs well
past that — each is an allocation in placeFireflies + an instance in the
renderer, on the main-thread decoration pass. Cap at 200k (orbs are lighter
than trees, so it sits above the tree cap and only bites the pathological
case). Golden unaffected (51,600 orbs << cap).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
thalida and others added 19 commits June 21, 2026 18:57
…lish

The almanac read as a uniform stack of identical label/value/metric rows — no
hierarchy, no grouping, no tie to the world it describes (the 'AI-generated'
look). Redesign:

- Pair min/max facts into bound duos: Buildings' 8 rows become 4 spectrums
  (Age, Last touched, Height, Footprint), Forest gets a Canopy duo. Each duo
  shares a dimension sub-label + an accent left-border, so the *contrast* is
  the unit you read.
- Drop the repeated noun ('Newest building' x8 → 'Newest' under a BUILDINGS
  header); the metaphor stays in the tooltip.
- Give each section its world-layer identity: a lucide icon + accent color
  (buildings/billboards/streets/forest/fireflies), so the panel reads as a
  legend for the city.
- One-line rows (label · value · right-aligned metric on a shared rail); the
  whole landmark row is now the button that flies the camera, with the focus
  glyph revealed on hover instead of a persistent icon per row.
- Hairline dividers between sections; bare formatted date as the metric (the
  dimension already says created vs modified).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Per feedback — the dimension label alone groups the pair; the accent bar read
as clutter. Rows now align flush under the label.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give each section the same opening rhythm: a summary line under the header
(count + one aggregate), then every fact under a dimension label.

- Overviews: Buildings '{n} buildings · ~{avg} lines each', Billboards
  '{n} billboards', Streets '{n} streets · ~{avg} files each', Forest '{n}
  trees · since {year}', Fireflies '{n} fireflies · ~{avg} commits each'. The
  per-section count facts (Billboards/Fireflies) fold into this line.
- Every fact now sits under a dimension header — the loose singles get grouped
  too: Forest → Activity (Busiest day / Longest streak), Streets → Standouts
  (Deepest / Biggest), Billboards → Standouts (Largest / Sharpest), Fireflies
  → Top contributor (Most active).
- Backend: add totalLines + codeBytes (non-media sums) to power the buildings
  average; the rest derive from existing counts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address defects I shouldn't have shipped, plus the requested content:

UI/UX (matched to the app's existing conventions, not reinvented):
- Focus glyph: was baseline-misaligned, hidden until hover, and tinted per
  section. Now centered (row is align-items:center), always visible, and one
  consistent interactive color (faint→primary, like .btn-icon) in every
  section — section accent stays for identity, not interaction.
- Fixed 76px label column so values align row-to-row; one value text size.
- Balanced the spacing around section dividers; consistent left rail.

Bug:
- '~NaN lines each' — totalLines needs the cache schema bumped (v10) so the
  live manifest re-scans; perEach also guards a non-finite total.

Content / balance:
- Forest overview is a duration ('… · 2 years of history') not 'since 2024'.
- Billboards now pairs Size (Smallest/Largest) + Resolution (Lowest/Highest)
  — adds min media leaders to the backend stats. Fireflies pairs Least/Most
  active. Every section now reads as labeled duos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tlight

- Drop the left indent on section content — it read as a stray inset; the
  header already groups. Overview/dimension/row text now sit flush with the
  section icon; the hover highlight bleeds into the gutter so text stays flush
  without losing breathing room.
- Every section (including the first) gets a top hairline, so the intro
  title-card is divided from the data sections like they divide from each
  other; spacing balanced around each divider.
- A lone billboard no longer renders as two one-item 'pairs' (Size→Largest,
  Resolution→Highest). It collapses to one spotlight row carrying both byte
  size and resolution. FactRow supports a label-less row for it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Fix the horizontal overflow I shipped: make .almanac-section and
  .almanac-duo flex columns so each fact row stretches to a definite width as a
  flex item — that's what lets the value truncate. No calc() width. The −8px
  margins keep their one job: bleeding the hover highlight into the gutter so
  text stays flush with the header (the look I'd had, now resting on a real
  layout instead of a hardcoded width).
- Language chips: add a trailing '+N more' (with a files/types tooltip) so the
  list accounts for the whole repo instead of an arbitrary top-6 cut-off.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The street 'Biggest' was keyed off total descendant files, which conflates a
deep subtree with a busy street. Make it the street with the most DIRECT
children (files + sub-dirs on that segment), and add a Smallest counterpart
(fewest direct children, ties broken by fewest descendants) so it's a real
duo like the other sections. Deepest stays as a standalone highlight.

Backend: DirLeader now carries direct children + descendants (not descendant
file count); maxFilesPerDir → maxChildrenDir + new minChildrenDir. Cache → v11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It rendered as a bare, label-less full-width row. Give it the same shape as
every other row — a 'Spotlight' group header and an 'Only' label — while
keeping the combined size · resolution metric.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rework the header to match the polish of the sections below:

- Flavor blurb (age + dominant language, e.g. "A 2-year-old city, mostly
  TypeScript.") replaces the counts-heavy sentence — which also removes the
  "1 fireflies" plural bug.
- Language composition bar: a GitHub-style stacked bar colored by the same
  extension hues as the legend chips, with a neutral tail for the long-tail
  file types. The chips below read as its legend.
- "Latest" becomes a live landmark: a two-line button (sha + subject, then
  relative age + branch chip) that flies the camera to the head commit's
  tree, consistent with the rest of the almanac.
- Founded shows the exact founding date; the relative age lives once, in the
  blurb (no duplicate "2 years").
- Fixed meta label column so Founded / Latest / Remote values share one rail.

New helpers: humanAge (date adjective phrase) and languageLabelForExt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
focusTree/selectByCommit no-op when the commit's tree doesn't exist, so a
clickable Latest button would be a dead end with the Trees layer disabled.
Render it as a plain row in that case — mirroring how the Forest section
gates its commit landmark rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each composition-bar segment now carries a tooltip ("TypeScript · 31 files
(40%)") so a bare colored sliver is identifiable on hover, and the trailing
tail names how many files across how many other types it folds in.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The meta dd cells sized to their content, so the Latest button (width:100%)
only spanned its text, not the full column. Give dd flex:1 so it fills the
row past the fixed label column — the button's click/hover area now spans the
whole right column and long values truncate at its edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pure age+language line read too terse. Weave the building count back in —
"An 8-year-old city of 312 buildings, mostly Python." — for enough texture to
set the scene without the per-layer counts (those live in each section).

Also fixes the article: humanAge yields "8-year-old", so the old "A " prefix
produced the ungrammatical "A 8-year-old". articleFor picks A/An by the spoken
sound of the leading number. Building count is non-media files, matching the
Buildings section. Each clause drops out when its input is absent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The whole-row camera-fly button was an invisible link — no affordance that it
was clickable. Make the Latest row plain informational text and turn the commit
hash into the one explicit link (accent-colored, hover underline, matching the
Remote link), pointing at the commit on the remote. Plain text when there's no
remote. Drops the camera-fly and its Trees-layer gating.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Move Info to the top of the left activity bar and make it the default tab, so
a freshly-loaded world greets you with its almanac. A useSignalEffect on
CURRENT_SOURCE re-opens Info on every committed load (cold-boot ?src= and user
source switches both write it; live-reloads don't, so a manual tab change
persists between loads). The panel's collapsed state is untouched — this only
sets which tab is active.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add DEFAULT_SIDEBAR_TAB to constants/ui.ts (next to ACTIVITY_BAR_TABS) and use
it for both the initial activeTab and the on-load switch, instead of repeating
SidebarTab.Info at each site.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thalida
thalida merged commit dd9fe8d into main Jun 26, 2026
1 check passed
@thalida
thalida deleted the feat/issue-60-world-metadata branch June 26, 2026 04: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.

Add metadata about the world

1 participant