feat(info): World Almanac — repo metadata in the Info tab (#60)#72
Conversation
| aria-selected={selected ? 'true' : 'false'} | ||
| class={selected ? 'pane-tab pane-tab--active' : 'pane-tab'} | ||
| onClick={() => onSelect(t.id)} | ||
| onKeyDown={(e) => { |
There was a problem hiding this comment.
I don't want these keyboard shortcuts.
There was a problem hiding this comment.
Removed the arrow-key navigation (and its tests) in 539e3a6.
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>
cbfbb7d to
0667f97
Compare
- 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>
| newest, | ||
| created(newest), | ||
| 'Most recently created file, by git history.' | ||
| ), |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Done in 70680bd — fileFact/dirFact/commitFact now take a single options object, and the Buildings facts are an array mapped through fileFact then compact-ed.
|
|
||
| /** 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; |
There was a problem hiding this comment.
should this be here and not in any utils?
There was a problem hiding this comment.
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.)
| label: 'Billboards', | ||
| primary: pluralize(media.length, 'billboard'), | ||
| tip: 'Image & video files — they render as billboard panels sized by aspect.', | ||
| } as AlmanacFact, |
There was a problem hiding this comment.
why can't this be a fileFact call?
There was a problem hiding this comment.
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.
| 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); |
There was a problem hiding this comment.
everything else has helper / utils functions... why doesn't this?
There was a problem hiding this comment.
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.
| label: 'Biggest neighborhood', | ||
| primary: biggest.path, | ||
| secondary: pluralize(biggest.descendants_file_count, 'building'), | ||
| mono: true, |
There was a problem hiding this comment.
what does mono mean or do?
There was a problem hiding this comment.
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.
| return { | ||
| key: 'forest', | ||
| title: 'Forest', | ||
| facts: [ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| if (commits.length === 0) { | ||
| return { | ||
| key: 'fireflies', | ||
| title: 'Fireflies', |
There was a problem hiding this comment.
add a tooltip to headers to explain waht a fireflie is, what a forest is, etc.
There was a problem hiding this comment.
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.
| <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} /> |
There was a problem hiding this comment.
can't this be active.Component directly?
There was a problem hiding this comment.
Yep — 534216c renders <active.Component manifest={manifest} /> directly and drops the Body local.
| <span class="almanac-sha">{repo.head_sha.slice(0, 7)}</span> {repo.head_subject} | ||
| </> | ||
| )} | ||
| {repo.dirty ? ' (uncommitted changes)' : ''} |
There was a problem hiding this comment.
since the city doesn't render dirty maybe don't display this? or can the city support rendering/updaing based on dirty files?
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>
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>
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>
…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>
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
PaneTabsprimitive (components/PaneTabs/) — horizontal subtabs inside a Pane body. Controlled, presentational, arrow-key navigable.Info tab →
Overview|ReadmesubtabsInfoPaneis now a thin shell hosting the two subtabs (Overview is the default).ReadmePaneis today's README logic extracted verbatim (no behavior change).WorldPanerenders the almanac.The almanac (
views/InfoPane/almanac.ts, pure + unit-tested)owner/reponame, founded date, branch, remote link, Latest commit (sha + subject, linked), and a language breakdown using the sharedExtensionBadge(file-type colors).Travel-guide behavior
selectPath/focusPath/focusCommit; adds a smallselectCommitto the picker for symmetry).modifiedis the last-commit date — an uncommitted working-tree edit won't change Freshest/Stalest until committed).Notes
--cc-font-monotoken (var(--cc-font-mono)→ a real monospace stack) surfaced by the language badges — affects mono text app-wide.Out of scope (tracked separately)
PaneTabs→ Reorganize Settings pane onto PaneTabs subtabs #69Testing
just test-appgreen (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
computeAlmanacis 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.RepoStatsschema (TypedDict + Pydantic), emitted at manifest-wrap, cache version → v8. Contract regenerated viajust gen-types; the drift guard verifies the hand-written types.Frontend
computeAlmanacmapsmanifest.statsleaders → facts (nowalk/pickFile/commit loops). Output is identical to the client-side version.computeFileStatsreadsmanifest.stats.fileLines/fileBytesinstead of re-walking the tree on every apply/live-update.On the rendering range (1:1 guarantee):
fileLines/fileBytesare documented as the building-size normalization range (non-zero), matching the oldcomputeFileStatsexactly so the world renders identically. Honest "smallest file" lives in thenarrowest/shortestleaders. The frontend also clamps the byte range to ≥1 (defense againstlog(0)→ NaN geometry).Scope notes
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-apigreen (250 tests); frontend--project=unitgreen (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)
.btn-icon); rows stretch as flex items so values truncate cleanly with no hardcoded widths.Intro card
An 8-year-old city of 312 buildings, mostly Python.(age vs. live clock, building count = non-media files, dominant language). Removes the old1 firefliesplural bug and fixes theA/Anarticle.name · N files (pct%)tooltip and a neutral tail for the long-tail types.Sidebar
useSignalEffectonCURRENT_SOURCEre-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=unitgreen (2373); tsc + eslint + prettier clean.