Skip to content

feat(nav): honor frontmatter order and description, add prev/next links - #129

Merged
llbbl merged 4 commits into
mainfrom
feat/nav-ordering
Sep 10, 2026
Merged

llbbl merged 4 commits into
mainfrom
feat/nav-ordering

Conversation

@llbbl

@llbbl llbbl commented Sep 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Navigation was purely alphabetical (by title) with no way to control article or folder order, and the real frontmatter description was discarded, so every meta description and og:description was the title repeated. There was also no prev/next navigation within a folder.
  • Two nullable columns (sort_order and description) now persist this data in the search table, populated by a post-index pass and read through one explicit-column query, with folder sequence controlled from a single config file.
  • Article pages now emit the real description and prev/next links within their folder.

Problem

Four separate gaps: alphabetical article order with no order frontmatter, incidental folder order (whatever order a folder's first article happened to sort into), description discarded by libsql-search so every <meta name="description"> and og:description was the title repeated, and no prev/next links.

Where the data lives

The libsql-search table has no description or order column, so this project persists both. Two nullable columns (sort_order — order is a SQL keyword — and description) are written by a post-index pass that is a sibling of the existing keyword-index rebuild, and read by one query that selects an explicit column list rather than SELECT * so article bodies are not dragged into the build. Nothing reads ./content at render time, which keeps one source of truth and works where content is not colocated with the server.

Upgrade path

CREATE TABLE IF NOT EXISTS will not add a column to an existing table, so ensureNavColumns does a PRAGMA table_info check plus ALTER TABLE ADD COLUMN. Verified against three real pre-existing database states: a populated pre-migration table, an index:local run with no prior db:init, and the repo's own local.db created before this branch. Idempotent, handles a partial earlier migration, and throws rather than silently no-op'ing when the table is absent.

Sort semantics

Within a folder, order ascending, then title, then slug. Slug is UNIQUE so the comparator is a total order and builds are deterministic. An article without order sorts after every article that has one; order: 0 counts as declared, not absent. localeCompare is pinned to 'en' so a build machine's locale cannot change page order. Folders follow src/config/nav.ts; unlisted folders sort after listed ones, alphabetically, and listing every folder is not required.

Fail-loud on a stale database

The new query selects the new columns, so a database that never ran db:init/index now fails at build time rather than serving pages in the wrong order. All three page routes are prerendered and neither API endpoint touches these columns, so there is no runtime path that can 500 — a stale deployment simply cannot be built. The error now names the commands to run instead of only the missing column.

Review findings applied

  • gray-matter moved from devDependencies to dependencies — it was imported by a shipped script, and pnpm install --prod leaves it unresolvable at the project root, so pnpm install --prod && pnpm index on a deploy host would crash with ERR_MODULE_NOT_FOUND. Being a transitive dependency of libsql-search makes this worse rather than better under pnpm's strict layout, since it is symlinked only into that package's own tree. The move costs three lockfile lines and no new package.
  • The post-index pass now reports rows matched rather than files read, and warns on a shortfall, so a slug divergence can no longer print a confident success line while the sidebar silently falls back to title order.
  • The file walk now honors the indexer's own exclude list.
  • The file list is sorted so duplicate-slug resolution is deterministic, matching the library.

Test gaps closed

A test that asserted the old files-read count is inverted to assert rows matched. The shipped folder configuration is now pinned — proven load-bearing, emptying src/config/nav.ts fails it, where previously the whole suite passed.

Verification

428 tests pass, lint and tsc --noEmit clean, credential-free build and smoke pass. Built output confirms the sidebar renders Getting Started → Features → Theme where alphabetical would give Features first, and both meta description tags carry the real frontmatter text.

Worth knowing

Prev/next does not appear in the shipped demo because each of the three folders holds a single article. All four boundary cases were verified with temporary fixtures — first has no prev, last has no next, single-article folder emits no nav element, and links never cross a folder boundary — then the fixtures were removed. Shipping a second article in one folder would make the feature visible to anyone evaluating the theme.

Closes #106

Navigation was derived entirely from the database with no ordering
controls: articles sorted alphabetically by title, folders appeared in
whatever order their first article happened to sort into, and every
page's meta description was its own title repeated because
libsql-search parses frontmatter description into the embedding text
and then discards it.

Two nullable columns now carry sort_order and description, added by a
PRAGMA-guarded ALTER so existing deployments upgrade without a manual
migration, populated by a post-index pass and read through one query.
Folder sequence lives in a single config file. Article pages emit the
real description and prev/next links within their folder.
@codecov

codecov Bot commented Sep 10, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.09%. Comparing base (9cd11b0) to head (5c4852f).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #129      +/-   ##
==========================================
+ Coverage   94.43%   95.09%   +0.65%     
==========================================
  Files          19       22       +3     
  Lines         539      611      +72     
  Branches      144      170      +26     
==========================================
+ Hits          509      581      +72     
  Misses         12       12              
  Partials       18       18              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

codecov/patch and codecov/project flagged the catch that translates a
missing-column error into one naming the commands to run. Added tests
against a real table in the stock libsql-search shape asserting the
message names both commands, that the underlying error is kept as the
cause, and that an unrelated error is rethrown unchanged rather than
relabelled. File line coverage goes from 83.33% to 100%.
codecov/patch reported no missing lines but seven partial branches.
Chasing them found that parseOrder's bigint arm cannot execute — the
libSQL client's default intMode throws on an integer too wide for a
JS number rather than returning a bigint, and SQLite stores neither
NaN nor Infinity, so the finite check was unreachable too. The
comment claiming the column arrives as number or bigint was wrong.
That branch is removed rather than tested.

The remaining arms are now covered: tags degrading to an empty array
for unparseable JSON, a JSON scalar, and NULL; a whitespace-only
description reading as absent; articles with no folder grouping
under the root folder as neighbors of each other; an unknown slug
yielding no neighbors; and unordered-versus-ordered comparison in
both directions.
@llbbl
llbbl merged commit a041615 into main Sep 10, 2026
6 checks passed
@llbbl
llbbl deleted the feat/nav-ordering branch September 10, 2026 12:48
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.

feat(nav): frontmatter order/description, configurable folder order, prev/next links

1 participant