Codebase due-diligence cleanup: issues #31–#38 - #47
Conversation
The home page, briefs index, and per-category brief pages each called `item.render()` for every entry and threaded the resulting `Content` component through, even though those list views only render card data derived from frontmatter (title/description/link). The `<Content />` component was never used, so every render did wasted markdown/MDX processing at build time. Drop the `Promise.all(... item.render())` passes and feed the collection entries directly into the card helpers. Sort/filter/slice behavior is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`getCategory` performed synchronous `existsSync` + `readFileSync` (and a YAML parse) on every call. Brief card generation calls it once per card, so a category's `category.yaml` was re-read and re-parsed many times per build. Memoize resolved category metadata in a module-level `Map`, keyed by slug + path, so each category's YAML is read and parsed at most once per build. Missing/invalid-YAML fallback to default metadata is preserved (the resolved default is what gets cached). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`readingTime` added an unconditional `+1` minute before rounding, so every post's estimate was inflated (e.g. a 200-word post read as "2 min read"). Switch to the standard formula `max(1, ceil(words / 200))` and normalize empty/whitespace-only content to "1 min read". This is also the first code in the repo to get unit coverage, so add a minimal Vitest harness: - add `vitest` (devDependency) and a `test:unit` script - `vitest.config.ts` scopes Vitest to `src/**/*.test.ts` so it doesn't collide with the Playwright `tests/*.spec.ts` suites - `src/lib/utils.test.ts` covers the reading-time edge cases called out in the issue (0/1/199/200/201/399/400 words, empty content, HTML stripping, round-up) - wire `npm run test:unit` into the shared CI workflow and the local `test:ci` aggregates Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`getProjectCardProps` accepted `CollectionEntry<"blog"> | CollectionEntry<"projects">` despite being the project helper, and duplicated `getBlogCardProps` verbatim. Narrow its input to `CollectionEntry<"projects">` and route both the blog and project helpers through a shared internal `getStandardCardProps` mapping. `getBriefCardProps` stays specialized because it adds the category-prefix behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`CallToAction.astro` was unused dead code left over from the accessible-astro-starter template. It imported `../assets/scss/base/mixins`, a path that does not exist in this project (there is no `src/assets` directory), so it would have failed to build the moment anything imported it. Its default copy/links also pointed at the upstream starter theme. Remove it rather than retrofit a component nothing references. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`dateRange` concatenated month and year with no separator ("Jan2020") and,
when `endDate` was omitted, produced "undefinedundefined" because the end
month/year were never assigned.
Rework it to format each endpoint as `MMM YYYY` (deterministic en-US
locale, matching `formatDate`) and join with " - ". The end label is the
provided string verbatim when a string is passed, a formatted `MMM YYYY`
when a Date is passed, and defaults to "Present" when omitted. Add tests
covering all three branches and asserting no `undefined` fragments appear.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
URL-valued frontmatter fields were typed as plain `z.string()`, so a malformed link in frontmatter sailed through validation and could emit a broken link into a generated page. Tighten the project `demoURL`/`repoURL` fields and the `ogImage` fields (blog/briefs/projects) to `z.string().url()`. Both are semantically absolute URLs — `demoURL`/`repoURL` are rendered as external links and OG images must be absolute per the Open Graph protocol — so the absolute-only policy `.url()` enforces is the intended one; relative paths are rejected. `.optional()` is preserved. All existing project content already uses absolute https URLs, so `astro check` and link validation still pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "fetch a collection, drop drafts, sort newest-first (optionally sliced)" pipeline was copy-pasted across every list page, every detail route's `getStaticPaths`, and the RSS endpoint. Any change to the draft/sort/slice policy had to be made in ~9 places. Add `src/lib/collections.ts` exposing: - `published(entries)` — drop drafts - `byDateDesc(entries)` — date-descending sort (non-mutating) - `getPublishedCollection(name, limit?)` — the common fetch+filter+sort, with an optional limit for homepage previews Route every consumer through these helpers. The home/blog/briefs/projects index pages and the blog/projects/briefs detail routes use `getPublishedCollection`; the per-category brief page and the RSS endpoint (which filter/merge before sorting) compose `published` + `byDateDesc`. Ordering, draft exclusion, and slice counts are unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Brings the issue #31-#38 work up to date with main, which had advanced with the April-2026 dependency refresh and tooling upgrades (eslint 10, typescript 6, cspell 10, @types/node 24, .nvmrc pin + CI node-version-file). Conflicts were limited to the test-harness additions from #33: - package.json: kept main's dependency set (including the dropped eslint-plugin-jsx-a11y and the @astrojs/check typescript override) and re-added the `vitest` devDependency + `test:unit`/`test:ci` script wiring. - package-lock.json: regenerated from main's lockfile with `npm install`. - .github/workflows/build.yml: auto-merged (main's node-version-file + the new "Run Unit Tests" step). Re-validated the full branch under the upgraded toolchain: lint, 15 vitest tests, tsc 6 typecheck (app + playwright), astro build (0 errors), cspell 10 source + HTML, and internal link validation all pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR Validation ✅All checks passed! Ready for review. Checks Performed:✓ Linting This is a complete dry-run of the deployment process, ensuring your changes will deploy successfully when merged. |
Code Review — Codebase Due-Diligence Cleanup (#31–#38)Overall this is a well-structured, focused PR. The atomic-commit-per-issue approach makes each change easy to reason about independently, and the test harness addition (Vitest for unit tests, scoped away from Playwright) is a solid foundation. Findings below, roughly ordered by severity. Issues
The file is flagged as modified (
If the memoization is genuinely absent, issue #32 is not addressed by this PR. If it is present but git cannot diff it, the binary encoding problem should be fixed before merge (e.g., normalize line endings, remove BOM).
Missing newline at end of The diff ends with Observations (non-blocking)
Test coverage — The 15 assertions cover the two fixed functions well.
SummaryThe binary diff on |
Works through GitHub issues #31–#38 from the codebase due-diligence pass — one focused, independently-verified commit per issue.
Commits (one per issue)
item.render()passes on the home / briefs / category list pages (the<Content />was never used)getCategory()so eachcategory.yamlis read/parsed at most once per build instead of once per brief cardreadingTimetomax(1, ceil(words / 200))(no more blanket +1 minute); add a Vitest harness + edge-case testsgetProjectCardPropstoCollectionEntry<"projects">and extract a sharedgetStandardCardPropsCallToAction.astro(imported a non-existent SCSS path)dateRangeformatting and open-ended end dates (MMM YYYY - Present); add branch testsdemoURL/repoURL/ogImageasz.string().url()in the content schemasrc/lib/collections.tsand route every page /getStaticPaths/ RSS through itTest infrastructure
The repo previously had no unit-test runner (only Playwright QA). #33/#36 add a minimal Vitest setup:
vitestdevDependency +test:unitscriptvitest.config.tsscopes Vitest tosrc/**/*.test.tsso it never collides with the Playwrighttests/*.spec.tssuitessrc/lib/utils.test.ts(15 assertions covering reading-time and date-range edge cases)build.ymlCI workflow (runs on PR validation and pre-deploy) and to the localtest:ciaggregatesValidation
npm run test:ci✅ — lint, 15 unit tests, source + HTML spellcheck,astro check(0 errors), internal link validationnpm run qa(Playwright, chromium) ✅ — 30/30 passing (content, navigation, accessibility, responsive)Closes #31
Closes #32
Closes #33
Closes #34
Closes #35
Closes #36
Closes #37
Closes #38
🤖 Generated with Claude Code