feat: exclude 404 pages from summary totals and the directory tree - #256
Merged
Conversation
…tree No page exists behind a 404 URL, so status = 404 rows now leave every page/content total (totalPages / internalPages / externalPages / internalContents / externalContents), the contentTypeDistribution, and the directory tree (nodes, counts, and viewer_directory_pages memberships alike), regardless of provenance. 404s stay visible only in statusDistribution, split into two rows: the plain 404 row keeps fix-target broken pages, while source = 'inventory-seed' rows (crawl --inventory input mistakes) move to a trailing row marked inventorySeed: true, ordered after every regular status but before the null-status trailer. metadataFulfillment applies a deliberately asymmetric, narrower rule: only inventory-seed 404s leave the denominator — a fix-target 404 still owes its metadata, so it keeps dragging the rates until fixed. Bump VIEWER_READ_MODEL_SCHEMA_VERSION to 24 so read models built with 404-inclusive counts are treated as stale and rebuilt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… histogram The status distribution can now carry two status = 404 rows (fix-target pages and crawl --inventory input mistakes — see StatusCount.inventorySeed), so keying rows on status alone would collide in React's reconciliation and label them indistinguishably. Extract the key/label derivation into the pure buildStatusRowDescriptor helper (unit-tested, one inventorySeed check drives both) and render the seed row as "404 (inventory-seed)" after the regular status rows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… description MCP clients read this description to interpret the returned counts: every page/content total and the Content-Type distribution exclude status = 404 rows, 404s surface only in the status distribution (with inventory-seed input mistakes split into their own trailing row), and the metadata denominator drops only the inventory-seed 404s. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The rule spans two features (summary totals and the directory tree share it, statusDistribution is the only surface, inventory-seed splits into its own row, and the fulfillment denominator is deliberately asymmetric), so future count-bearing features need one indexed place to find it. Details stay in the get-summary.ts / build-directory-tree-rows.ts JSDoc per the index-only ARCHITECTURE.md policy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A 404 URL has no page behind it, so 404 rows no longer inflate any count. Inventory-driven crawls can seed tens of thousands of URLs that turn out not to exist, and until now those input mistakes dominated every summary number and the directory tree.
query
getSummaryexcludesstatus = 404rows (regardless ofsource) fromtotalPages/internalPages/externalPages/internalContents/externalContentsandcontentTypeDistribution.statusDistributionkeeps 404s, now split into two rows: the plain404row counts fix-target broken pages (crawled/inventory-discovered), whilesource = 'inventory-seed'rows (input mistakes fromcrawl --inventorylists) move to a trailing row markedinventorySeed: true— ordered after every regular status, before thenull-status trailer. Ordering lives in a single tier-based comparator.metadataFulfillmentapplies a deliberately asymmetric, narrower rule: only inventory-seed 404s leave the denominator. A fix-target 404 still owes its metadata, so it keeps dragging the rates until fixed.buildDirectoryTreeRowsdrops 404 rows before host qualification: no counts, noviewer_directory_pagesmembership, no node for a directory whose pages are all 404s, no tree for a host whose internal rows are all 404s.DirectoryTreeSourceRowgainsstatusfor this.VIEWER_READ_MODEL_SCHEMA_VERSIONbumped to 24 so read models built with 404-inclusive counts are treated as stale (live fallback /[]untilviewer-build, per the existing mechanism).sourceis typed asPageSourceso a literal typo cannot silently never-match.viewer
404 (inventory-seed). Key/label derivation is extracted into the pure, unit-testedbuildStatusRowDescriptor(the two 404 rows sharestatus, so keying on status alone would collide in React's reconciliation).docs
get_summarytool description documents the exclusion rules for MCP clients interpreting the counts.Behavior notes
getSummary/ the read model, so viewer, MCP (open_archive/get_summary), andnitpicker query summaryall change together.StatusCountgains the optionalinventorySeed?: truefield (v0.x, no compatibility shim).Tests
getSummary: dedicated 404-exclusion describe (totals, provenance split with hardcoded literals, asymmetric fulfillment denominator pinned as 1/2 vs 1/1 vs 1/3), plus a null-status ordering describe pinning[numeric…, seed, null].buildDirectoryTreeRows: unit tests for full exclusion, all-404 directories, host qualification, and NULL-status passthrough;buildViewerReadModelintegration test proves the exclusion end-to-end (SELECT → mapping → builder) and that the page stays a normalviewer_pagesrow.getViewerSummary: round-trip test provinginventorySeedsurvivesviewer_summary.status_jsonpersistence.buildStatusRowDescriptor: hardcoded key/label expectations including the collision-avoidance suffix.yarn lint/yarn lint:eslint:check/yarn build/yarn testall green.🤖 Generated with Claude Code