Skip to content

Index the resources in search, and give the landing page a filterable table - #8

Merged
fritzhand merged 1 commit into
mainfrom
claude/startup-resources-index
Jul 29, 2026
Merged

Index the resources in search, and give the landing page a filterable table#8
fritzhand merged 1 commit into
mainfrom
claude/startup-resources-index

Conversation

@fritzhand

Copy link
Copy Markdown
Owner

Follows PR #7, which brought the 653 resources onto main. This makes them findable.

Search

All 653 resources are now findable by title — the index goes from 79 entries to 732.

Entries are deliberately lean: no description field, because 653 of them would treble a file every visitor downloads for a line most people never read. The publisher goes in the short label, where it disambiguates near-identical titles cheaply.

That index is 22 KB gzipped, and every page was blocking on it whether or not anyone searched. It is now fetched on first open of the search dialog, with a prefetch hint so the browser can pull it while idle. Verified in a browser: window.SEARCH_INDEX is undefined on load, and holds 732 entries a moment after the dialog opens. If the fetch fails there is simply nothing to search rather than a broken dialog.

Because the index is generated rather than checked in, asset() cannot hash it from disk — so it is built before rendering and its hash seeded from the bytes that will be written. That meant moving the carve helpers and the resource load ahead of it too: the search index publishes section summaries and has to apply the same sensitivity test the stack table does.

The landing page

All 653 in one table. Filter by words, narrow by group chips or a topic select, sort any column.

  • mountFilterGrid drove chips only, so it now drives selects as well — fifty topics as a chip row would be longer than the table it filters.
  • Sorting collates numerically, so "10 Ways" does not sort ahead of "2 Ways".
  • An empty publisher sorts last in both directions, rather than floating a block of blanks to the top when the direction flips.

One source of truth

web/resources.json now holds the 653 records. The nine markdown pages stay committed so they read on GitHub, and the build reads the same file for the search index and the table — one file behind both, so they cannot drift.

Three bugs found by looking, not by reasoning

Symptom Cause
Search icon rendered as a full-width circle The filter bar used a class I invented; .field styling is scoped to .toolbar
Sort button on the first column appeared dead It re-applied ascending, because the JS did not adopt the order the markup already declared
Sticky header sat 58px below the table top .table-wrap sets overflow-x, making it a scroll container, so sticky resolved against the container rather than the page. I then reintroduced the same bug with overflow: hidden for a border radius, and removed it again.

Verification

Driven in a real browser rather than assumed:

rows=653
filter 'canvas'  -> 16   count='Showing 16 of 653 resources'
chip legal       -> 52
topic select     -> 31
sort title       -> asc/desc flip confirmed, aria-sort updates
sort publisher   -> blanks last in both directions
search index loaded eagerly? false
after opening search: 732 entries

node web/build.mjs clean: 71 pages, 732 search entries.


Generated by Claude Code

… table

web/resources.json is now the source of truth for the 653 resources. The nine
markdown pages stay committed so they read on GitHub, and the build reads the
same file for the search index and the table -- one file behind both, so they
cannot drift apart.

Search. Every resource is now findable by title, taking the index from 79
entries to 732. Entries are deliberately lean: no description field, because
653 of them would treble a file every visitor downloads for a line most people
never read. The publisher goes in the short label, where it disambiguates
near-identical titles cheaply.

That index is 22 KB gzipped, and every page used to block on it whether or not
anyone searched. It is fetched on first open of the search dialog instead, with
a prefetch hint so the browser can pull it while idle. Verified in a browser:
window.SEARCH_INDEX is undefined on load and holds 732 entries a moment after
the dialog opens. If the fetch fails there is nothing to search rather than a
broken dialog.

Because the file is generated rather than checked in, asset() cannot hash it
from disk, so the index is built before rendering and its hash seeded from the
bytes that will be written. That meant moving the carve helpers and the
resource load ahead of it too -- the search index publishes section summaries
and has to apply the same sensitivity test the stack table does.

The landing page. All 653 in one table: filter by words, narrow by group chips
or a topic select, sort any column. mountFilterGrid drove chips only, so it now
also drives selects -- fifty topics as a chip row would be longer than the
table it filters. Sorting collates numerically so "10 Ways" does not sort ahead
of "2 Ways", and an empty publisher sorts last in both directions rather than
floating a block of blanks to the top when the direction flips.

Three things caught by looking at it rather than by reasoning about it: the
filter bar used an invented class with no CSS behind it, so the search icon
rendered as a full-width circle; the sort button on the pre-sorted column
re-applied the same direction and appeared dead until it adopted the order the
markup declares; and the sticky header sat 58px below the top of the table
because .table-wrap sets overflow-x, which makes it a scroll container and
resolves sticky against the container rather than the page.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@fritzhand, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 30aa316b-1ee7-4ec1-8d32-b9a4715c92d5

📥 Commits

Reviewing files that changed from the base of the PR and between 59babf5 and 49edabf.

📒 Files selected for processing (4)
  • web/assets/site.css
  • web/assets/site.js
  • web/build.mjs
  • web/resources.json

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@fritzhand
fritzhand merged commit 6e314cb into main Jul 29, 2026
5 checks passed
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.

2 participants