Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 417afda560d3dfc9ce24d83f21a98ea126696598
parent ab9bfa2063bfb6df0b1d8125e9ba1f854eed410b
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Wed, 30 Sep 2026 09:43:42 -0400

plans: slice SG — the parent's four rulings applied (the cap at the newest 10,000 commits by stagit -l, read in stagit.c and measured; the render cache in the XDG cache dir; the diff tokens and the themeConfig re-export stand), the merge of main with slice DT and doctor's drive-health line; the re-gates (common 2,341, the three source specs 12/12, source publish and --check); FACTS for the cap, the cache and doctor, anchors refreshed

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mplans/FACTS.md | 67++++++++++++++++++++++++++++++++++++++++++-------------------------
Mplans/release-15.md | 73++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
2 files changed, 104 insertions(+), 36 deletions(-)

diff --git a/plans/FACTS.md b/plans/FACTS.md @@ -7782,66 +7782,83 @@ source mirror (homepage)". Anchors are at the branch. - **stagit** (codemadness.org, C over libgit2; built from `519cae2f`, "bump version to 1.3", 2026-09-14, against the system libgit2 1.9.7) renders the scrubbed mirror into `/source/git/`. It is operator-installed like git-filter-repo: never vendored. `resolveStagit` - (`publish/sourceHistory.ts:104`): `paths.stagitBin` (`STAGIT_BIN`, else `"stagit"`, `lib/paths.ts:304`) + (`publish/sourceHistory.ts:115`): `paths.stagitBin` (`STAGIT_BIN`, else `"stagit"`, `lib/paths.ts:314`) — a name with a slash is that file or nothing; a bare name is PATH, then `~/.local/bin/<name>`. - It has no version flag: `stagitIdentity` (`:118`) is the first 12 hex of its binary's sha256, + It has no version flag: `stagitIdentity` (`:129`) is the first 12 hex of its binary's sha256, never the path (the manifest is published, and a home-dir path is a denied literal). - **What stagit does** (read in `stagit.c`): it writes into its CWD; the repository's NAME is its directory's basename minus `.git` — so the step's scratch clone is `<scratch>/archilyzer.git` - (`source.ts:792`), no longer `bare`; `description` (read with `fgets`, the newline KEPT — write it + (`source.ts:831`), no longer `bare`; `description` (read with `fgets`, the newline KEPT — write it without one) and `url` (newline stripped) come from the repository directory; the header links `<relpath>file/README.md.html` and `LICENSE` when HEAD has them, and the logo `<a href="../<relpath>">` lands on `/source/` from every depth; `file/` pages are written for HEAD's whole tree on EVERY run; every text from the repository is xmlencoded (`"` → `&quot;`), so no page text can fake an - `href="…"`; a diff's paths are percent-encoded (`/` and `,-.` kept); a commit over 1,000 files or + `href="…"`; `-l N` keeps the NEWEST N log lines (the revwalk is newest first) and ends "M more + commits remaining, fetch the repository", but still writes a page for EVERY commit (it only skips + the diffstat of a page that exists); `-c` with `-l` is a usage error; a diff's paths are percent-encoded (`/` and `,-.` kept); a commit over 1,000 files or 100,000 added or deleted lines prints "Diff is too large, output suppressed"; `-c <file>` walks from HEAD to the commit the cache file names, KEEPS every `commit/<sha>.html` already in the CWD, and appends the cached log lines — so the cache is the whole output directory, and a cache whose commit is not an ancestor duplicates the log. Its temp cache file (`cache.XXXXXXXXXXXX`) is made in the CWD. -- **The step** (`source.ts`, step 12b, `:920`) runs after the object audit and the mirror's staging, - before the manifest and the file audit: `stageHistoryPages` (`:1070`) → `renderHistory` - (`sourceHistory.ts:454`). **An allowlist is staged** (`TOP_FILES`, `COMMIT_PAGE`, `:76`): `log.html`, +- **The cap** (`SOURCE_HISTORY_MAX_COMMITS`, 10,000; ruled 2026-09-30): up to it, `-c`; past it, + `-l 10000`. Either way the pages published are exactly the commits the log links + (`loggedCommits`: every `href="commit/<sha>.html"` in log.html), each of which must exist, and the + first must be the head. The manifest's `history.total` is every commit, `commits` those with a + page; `/source/` says "the latest N of M" when they differ. Past the cap each publish computes N + diffstats (no `-c`): 8.75 s at 1,834 commits with every page present, against 0.58 s with `-c`. + The oldest published page's parent link is then a 404. +- **The step** (`source.ts`, step 12b) runs after the object audit and the mirror's staging, + before the manifest and the file audit: `stageHistoryPages` (`:1109`) → `renderHistory` + (`sourceHistory.ts:497`). **An allowlist is staged** (`TOP_FILES`, `COMMIT_PAGE`, `:87`): `log.html`, `files.html`, `refs.html`, `atom.xml`, `tags.xml`, `commit/<40 hex>.html`, plus `style.css`. Never `file/`, never a leftover. Pages are rewritten byte for byte (latin1 in and out; every injection is ASCII: the back link says `&middot;`). -- **The post-pass** `rewriteHistoryPage` (`:285`), pages only: `<script>` + the homepage's pre-paint - script (`homepageThemeScript`, `:70` = `buildThemeScript({ defaultBase: HOMEPAGE_DEFAULT_BASE })`) - before `</head>`; `HISTORY_BACK_LINK` (`:67`) after `<body>`; `href="(../)*file/<p>.html"` → +- **The post-pass** `rewriteHistoryPage` (`:296`), pages only: `<script>` + the homepage's pre-paint + script (`homepageThemeScript`, `:81` = `buildThemeScript({ defaultBase: HOMEPAGE_DEFAULT_BASE })`) + before `</head>`; `HISTORY_BACK_LINK` (`:78`) after `<body>`; `href="(../)*file/<p>.html"` → `href="$1../tree/<p>"`; `logo.png`/`favicon.png` → `/icons/icon-32.png` (a route the homepage renders at build). The theme config moved to `lib/themeConfig.ts` for this (publish may not import components/; `components/themeConfig.ts` re-exports it); `HOMEPAGE_DEFAULT_BASE` (`:62`) is what the homepage layout passes. The attribute the script sets is `data-base`, not `data-theme`. -- **`style.css`** is `historyStylesheet` (`:197`) over `common/styles/tokens.css`: the light block +- **`style.css`** is `historyStylesheet` (`:208`) over `common/styles/tokens.css`: the light block (`:root, html[data-base="light"]`), the dark block for `html[data-base="dark"]` AND for `:root:not([data-base])` under `prefers-color-scheme: dark` (no JS); `HISTORY_TOKENS` and whatever they reach through `var()`. Diff `+` lines are `--success`, `−` lines `--destructive`; there is no chart-safe red in the tokens (the chart palette is blue, green, violet, amber, magenta, rust). -- **Never a failed build over the history.** No stagit: one line with the install (`source.ts:931`). +- **Never a failed build over the history.** No stagit: one line with the install (`source.ts:970`). A render that fails (`HistoryProblem`), a stagit timeout (600 s), unreadable tokens, more files than the step's 15,000 in all, or a page over 24 MiB: a `[source] WARNING: …` and no history. A cancel still cancels; any other error is a crash as before. -- **The manifest's `history`** (`lib/sourceManifest.ts:88`, parsed at `:137`, all-or-nothing): +- **The manifest's `history`** (`lib/sourceManifest.ts:88`, parsed at `:140`, all-or-nothing): `href`, `commits` (`git rev-list --count`), `head`, `files`, `bytes`, `sha256` (`historyDigest`, - `source.ts:500`: `sourceDigest`'s walk over `source/git/**` alone), `tool`. `sourceDigest` (`:484`) + `source.ts:505`: `sourceDigest`'s walk over `source/git/**` alone), `tool`. `sourceDigest` (`:489`) now walks `source/git` too. The state (`.source-publish.json`) gains `stagit` and `history: {files, digest} | null`; the skip needs both to match, and a stagit present with no history never skips. `SOURCE_STEP_VERSION` is 4 (`:109`). -- **The deploy check** (`publishedSourceProblem`, `:1204`) names the history on its own (`:1286`): +- **The deploy check** (`publishedSourceProblem`, `:1244`) names the history on its own (`:1326`): `out/source/git`'s digest must be the state's (null when none) and the manifest's. -- **The render cache** is `<scratch root>/archilyzer-source-history/` (`historyCacheDir`, `:556`): - `key.json`, `stagit.cache`, `out/`, and `lock` while held. `holdHistoryCache` (`:408`): the lock +- **The render cache** is `${XDG_CACHE_HOME:-~/.cache}/archilyzer/source-history/` + (`getPaths().sourceHistoryCacheDir`; an empty `XDG_CACHE_HOME` is unset; ruled 2026-09-30): + `key.json`, `stagit.cache`, `out/`, and `lock` while held. `historyCacheFor` (`source.ts`) gives + none for `--check` and none, with a line, inside the checkout or the public dir. `holdHistoryCache` (`sourceHistory.ts:443`): the lock names the holder's pid; a live holder → render without the cache; a dead one → emptied and taken. - Usable only when the key (`historyCacheKey`, `:565`: rules hash, filter-repo, stagit, description, - clone URL, base URL) matches, `out/log.html` exists, and the cache's commit is an ancestor of the head - (`git merge-base --is-ancestor`). The key is removed before a render and written after it. A cached - render whose commit pages ≠ commits is rendered again from nothing, once. `--force` renders - afresh; `--check` renders in its own scratch; a refusal removes the cache (`source.ts:676`). + Its pages are kept when the key (`historyCacheKey`: rules hash, filter-repo, stagit, description, + clone URL, base URL) matches and `out/log.html` exists; its `-c` file only when the commit it names + is an ancestor of the head (`git merge-base --is-ancestor`), else that file alone goes. A page of a + commit no longer in history is never published (only the log's commits are). The key is removed + before a render and written after it. A cached render whose log names a page that is not there + is rendered again from nothing, once. `--force` renders + afresh; `--check` renders in its own scratch; a refusal removes the cache (`source.ts:713`). - **Measured (2026-09-30, `main` `6b8aa450` → mirror `8c924ca31701`):** 1,872 commits; 1,878 files, 141,299,532 bytes; the largest `commit/7ddfc955….html`, 5,017,646 bytes; stagit 8 s of a 32 s publish (every page); the whole publish 4,396 files, `homepage/out` 4,576. On the published mirror - at 1,834 commits: 8.0 s every page, 2.0 s from the cache with nothing new. The cache is 138 MB — on - this machine's tmpfs `/tmp`, memory. One file per commit: at about 100 commits a day (September - 2026) the step's 15,000 is some three months away, and the history is what is dropped then. + at 1,834 commits: 8.0 s every page, 2.0 s from the cache with nothing new. The cache is 138 MB. + At `main` `4dfe21e5`: 1,882 commits, 1,888 files, 4,409 staged in all. One file per commit: the cap + keeps the history at 10,006 files at most; the 15,000-file drop is the last resort. +- **Doctor** (`bin/doctor.ts`): the stagit line ends `; cache: <path>, <size>`. Since the merge of + slice DT it reads the settings before the corpus and applies `settings.storage.health` + (`applyHealthTimings`) before it inspects any drive, and prints the timings in force on a + `drive health` line. - **The homepage** believes `history` only beside `source/git/log.html` (`homepage/app/lib/source.ts:43`; without it the manifest comes back without the block). Its e2e reads a fixture publish from `E2E_SOURCE_PUBLIC_DIR` while it holds a manifest (`sourcePublicDir`, `:69`; never in a production diff --git a/plans/release-15.md b/plans/release-15.md @@ -21,7 +21,7 @@ prompt carries its ruling, and this record carries what was built. Rules: | UT | `r15/umtool-trace` | umtool's build stops tracing the whole `umtool/` folder | per its prompt | | SS | `r15/site-scope` | The editor's site picker paints the stored site at once: the selection is a cookie | `editor/app/lib/activeSite{,Server,Actions}.ts` + `activeSite.test.ts`, `editor/app/components/SiteScope{Provider,Select}.tsx`, `editor/app/layout.tsx`, the scope lines of `editor/app/page.tsx` and `editor/app/channels/page.tsx`, a comment in `editor/next.config.ts`, `editor/e2e/site-scope.spec.ts`; records: `plans/FACTS.md` | | DT | `r15/drive-timings` | The drive-health timings are settings (`settings.storage.health`), edited on `/storage` | new `common/lib/storageHealthTimings.ts` + test; `lib/{storageHealth,storageVolumes,channelMedia,storageLocations,settingsSchema,settingsDocs}.ts`, `controller/storageWatch.ts`, the `index` and `build stats` bins, `SETTINGS.md`; `/storage` (a form, its action and parse), the stall wording on `/channels` and the videos pages; `storage-locations.spec.ts`; records: `plans/FACTS.md` | -| SG | `r15/stagit` | The source's history and diffs on the homepage, rendered by stagit at `/source/git/` | new `common/publish/sourceHistory.ts` + test; `common/publish/source.ts` (step 12b, the key, the digest, the deploy check) + test; `common/lib/{sourceManifest,paths,envVars}.ts`, `common/lib/themeConfig.ts` (moved from `components/`, which re-exports it); `common/bin/doctor.ts`; `homepage/app/{source/page.tsx,lib/source.ts,layout.tsx}`, `homepage/e2e/{source,source-history}.spec.ts`, `homepage/e2e/fixture-source.ts`, `homepage/playwright.config.ts`; `ENVIRONMENT.md`, `PUBLISH.md`; records: `plans/FACTS.md` | +| SG | `r15/stagit` | The source's history and diffs on the homepage, rendered by stagit at `/source/git/` | new `common/publish/sourceHistory.ts` + test, `common/publish/__fixtures__/fakeStagit.ts`; `common/publish/source.ts` (step 12b, the key, the digest, the deploy check) + test; `common/lib/{sourceManifest,paths,envVars}.ts`, `common/lib/themeConfig.ts` (moved from `components/`, which re-exports it); `common/bin/doctor.ts`; `homepage/app/{source/page.tsx,lib/source.ts,layout.tsx}`, `homepage/e2e/{source,source-history}.spec.ts`, `homepage/e2e/fixture-source.ts`, `homepage/playwright.config.ts`; `ENVIRONMENT.md`, `PUBLISH.md`; records: `plans/FACTS.md` | **Order:** IG → DS. DS adds a health gate inside `inspectChannelMedia`, which IG's hold calls through its public signature. UT is independent. The shared files are `editor/CHANGELOG.md`'s @@ -1438,11 +1438,9 @@ against libgit2 1.9.7. The binary was copied to `~/.local/bin/stagit` (sha256 `8 #### Found and left -- **The file limit is months away, not years.** There is one file per commit, and main gained about - 100 commits a day in September 2026. The publish is 4,396 files, and the step refuses at 15,000: - about 10,600 commits of headroom. When the history would cross it, the history is dropped with a - WARNING and the rest is published. The choices then (raise `MAX_FILES` toward Pages' 20,000, or - publish only recent commits' pages) are a later ruling. +- ~~**The file limit is months away, not years.**~~ **Ruled and applied below: the history is capped + at the newest 10,000 commits**, so it is at most 10,006 files; the 15,000-file drop stays as the + last resort. - **`/source/git/` has no index page**, so the directory itself is a 404; `/source/` links `log.html`. A copy of `log.html` as `index.html` would double 0.6 MB. - **No `_headers` rule for `/source/git/*`.** The pages are HTML by extension, and stagit encodes @@ -1457,23 +1455,76 @@ against libgit2 1.9.7. The binary was copied to `~/.local/bin/stagit` (sha256 `8 | What I assumed | The alternative | |---|---| -| Diff lines use `--success` and `--destructive`, with no new tokens: the chart palette has no red (it is categorical: blue, green, violet, amber, magenta, rust); both are text-grade on both grounds; every changed line keeps its `+`/`−`, so hue is never the only signal. | Two new tokens (`--diff-add`, `--diff-del`), validated as a pair. A green/red pair cannot be told apart by hue for every colour-blind reader, so the sign column carries it either way. | -| A failed render, a timeout, unreadable tokens or pages over the limits are a WARNING, with no history. | Refuse the publish: louder, but it takes the mirror down with the history. | +| **Ruled (2026-09-30): stands.** Diff lines use `--success` and `--destructive`, with no new tokens: the chart palette has no red (it is categorical: blue, green, violet, amber, magenta, rust); both are text-grade on both grounds; every changed line keeps its `+`/`−`, so hue is never the only signal. | Two new tokens (`--diff-add`, `--diff-del`), validated as a pair. A green/red pair cannot be told apart by hue for every colour-blind reader, so the sign column carries it either way. | +| A failed render, a timeout, unreadable tokens or pages over the limits are a WARNING, with no history. **Ruled: the history is capped by commit count instead (below); the file-limit drop stays only as the last resort.** | Refuse the publish: louder, but it takes the mirror down with the history. | +| **Ruled (2026-09-30): the `components/themeConfig.ts` re-export stands**; its importers move to `lib/themeConfig` when a slice next touches them. | Move the 11 importers now. | | stagit's logo and favicon point at `/icons/icon-32.png`. | Leave them broken, or drop the `<img>`. The ruling said "nothing else is injected"; this is a link rewrite, like `file/`. | | The History block's head is 12 characters, linking to its commit page. The full sha is the Mirror head fact just above. | The full 40-character sha, as text. | -| The cache lives under the scratch root, a tmpfs `/tmp` by default here (138 MB of memory). | A cache dir of its own, on disk. | +| ~~The cache lives under the scratch root, a tmpfs `/tmp` by default here (138 MB of memory).~~ **Ruled (2026-09-30): `${XDG_CACHE_HOME:-~/.cache}/archilyzer/source-history/`** — applied, below. | — | | `--force` empties the cache. | Keep it, and force only the publish. | -| `--check` renders without the cache (8 s rather than 2 s): it writes nothing outside its own scratch. | Use the cache. | +| `--check` renders without the cache (8 s rather than 2 s): it writes nothing outside its own scratch. **Ruled: stays cache-less.** | Use the cache. | | The e2e shows the page with and without a history from a fixture publish, through a non-production-only `E2E_SOURCE_PUBLIC_DIR`. The real pages are walked by `source.spec` when the checkout has a history. | Only the checkout's real state, as the other source specs do. | **For the rollout** (the parent's): - **Install stagit on the publishing machine**, the same way (`~/.local/bin/stagit`). - **Rebuild and restart the editor** before any `/sites` Homepage job; its built bundle runs the old step. -- The first build publishes the source again (step 4), with the history. +- The first build publishes the source again (step 4), with the history, and makes the render + cache at `~/.cache/archilyzer/source-history` (about 140 MB on disk). - A review should read one live page after the deploy; the preview's `_headers` do not touch `/source/git/`. +#### The parent's rulings, applied (2026-09-30), and the merge of `main` with slice DT + +| Ruling | What was done | +|---|---| +| 1. The history is capped by commit count: stagit `-l <SOURCE_HISTORY_MAX_COMMITS>`, 10,000, the newest first; `/source/` says "the latest N of M"; the 15,000-file drop only as the last resort | `d8c9e5fe`, `4409ab9d`. **Read in `stagit.c` and measured:** `-l N` keeps the NEWEST N log lines (the revwalk is newest first) and ends "M more commits remaining, fetch the repository". But it still writes a page for EVERY commit (only the diffstat of a page that exists is skipped), and stagit refuses `-c` with `-l` (usage error). So: up to the cap, `-c` as before; past it, `-l 10000`. The pages published are, in both, exactly the commits the log links (`loggedCommits`), and every one must be there. The manifest's `history` gains `total` (`commits` is how many have a page). Measured on the published mirror (1,834 commits, pages present): `-l 10000` 8.75 s, `-c` 0.58 s. Past the cap every publish computes 10,000 diffstats (~5 ms each, ~48 s). | +| 2. The render cache is `~/.cache/archilyzer/source-history/` (`XDG_CACHE_HOME` honoured), made with a recursive mkdir, never inside the repo; `--check` stays cache-less; PUBLISH.md and doctor ("cache: <path>, <size>") | `d8c9e5fe`, `68670703`, `2acbd080`. `getPaths().sourceHistoryCacheDir`, from `XDG_CACHE_HOME` (empty = unset) else `~/.cache` (`XDG_CACHE_HOME` declared, `ENVIRONMENT.md`). `historyCacheFor` gives none for `--check` and none — with a line — inside the checkout or the public dir. Its pages are kept by key; its `-c` log lines only when they end at an ancestor (the pages stay otherwise). Doctor's stagit line ends `; cache: <path>, <size>` ("none yet"). The old `/tmp` cache was removed. | +| 3. `--success` / `--destructive` stand | Recorded (the decisions table). | +| 4. The `components/themeConfig.ts` re-export stands; importers move when a slice next touches them | Recorded (the decisions table). | +| After DT merged: merge `main`; doctor's `applyHealthTimings(...)` line (DT's Found and left) | `2b2b5305` merges `main` `4dfe21e5`; the conflicts were `release-15.md` (DT's row, then SG's; DT's section, then SG's, before the Rollout) and the editor changelog (DT's bullet, then SG's). `53c1849d`: doctor reads the settings before the corpus and applies `settings.storage.health` before it inspects any drive, as the index and stats bins do; a "drive health" line in the corpus block names the timings in force and whether they are the defaults. | + +| Commit | What | +|---|---| +| `d8c9e5fe` | `common:` the cap (`SOURCE_HISTORY_MAX_COMMITS`, `-l`, `loggedCommits`, `history.total`); the cache in the XDG cache dir (`sourceHistoryCacheDir`, `historyCacheFor`, `XDG_CACHE_HOME`); one fake stagit (`__fixtures__/fakeStagit.ts`, `-c` and `-l` as `stagit.c` has them); tests +4 (the cap twice, `loggedCommits`, the cache's place) and the cache test rewritten. | +| `68670703` | `common:` doctor's stagit line ends with the cache's path and size. | +| `4409ab9d` | `homepage:` "the latest N of M commits"; the fixture can be capped (+1 e2e); `source.spec` checks the log lists exactly the commits with a page. | +| `2acbd080` | `docs:` PUBLISH.md — the cap, the cache's place, doctor's line. | +| `44ddb6b2` | `changelog:` the cap and the cache's place in both `[Unreleased]` bullets. | +| `2b2b5305` | Merge `main` (DT). | +| `53c1849d` | `common:` doctor applies the drive-health timings, and prints them (+1 test). | +| this commit | `plans:` this subsection, the ruled rows, FACTS. | + +**Re-gates** (after the merge, at `53c1849d`; logs `$T/sg-*-2.log`, `sg-tsc-{5,6,7}.log`, `sg-pub2.log`): +- **tsc** clean before each commit and on the merge. +- **common 2,341/2,341**, 0 skipped: SG's 22 over `main` `4dfe21e5`'s (2,319 by difference; `sourceHistory` 13, + `source` +6, `sourceManifest` +2, doctor +1). +- **homepage unit 22/22**; **test:scripts 195 + 1 skipped**; `docs env --check` 0, + `docs files --check` 0. +- **The three source specs** (`source`, `source-history`, `downloads`; `E2E_EXPECT_SOURCE=1`, over + the worktree's fresh publish): **12 passed, 0 failed, 17.5 s** (+1: the capped block). +- **`source publish`** (main `4dfe21e5`) in the worktree: `[source] history: stagit (sha256 + 898752011b07) — 1882 commits, 1882 pages rendered; 1888 files, 135.3 MB, the largest + git/commit/7ddfc955….html 4.8 MB`; `audit clean: 23,128 objects (1,882 commits), 4,409 staged + files against 10 denied literals; gitleaks clean`; published as `f81edb46f7e3`, 4,409 files. The + cache was made at `~/.cache/archilyzer/source-history` (138 MB, mode 700). +- **`source publish --check`**: `check passed — would publish main 4dfe21e5714a as f81edb46f7e3: 4409 + files, 210.3 MB (…, history 1882 commits in 1888 files) …; nothing written (32 s)`. The cache's + key was untouched. +- **doctor**: `ok stagit ~/.local/bin/stagit; cache: ~/.cache/archilyzer/source-history, 134.1 MB` + and `-- drive health a read may take 3 s, a check every 15 s (3 s each), a stall clears on a + clean check twice in a row, 4 reads in flight per drive — the defaults`. +- Not re-run after the rulings: the full homepage suite (103 before), the builds. The rulings change + `source.ts`, `sourceHistory.ts`, doctor and one page component; the source specs cover the page. + +**Found and left, from the rulings:** +- **Past the cap, the oldest published commit page links its parent's page, which is not + published** (a 404). Every other link resolves. +- **Past the cap the render is not incremental**: stagit cannot combine `-l` with `-c`, so each + publish computes 10,000 diffstats (about 48 s by the per-commit rate measured here). The pages + themselves are still kept. + + ## Rollout Release 15 is slices IG (`r15/index-hold`, merged `ccf90892`), UT (`r15/umtool-trace`, `07c991be`),