Skip to content

feat: deterministic plugin cover image via README order#202

Open
marekh19 wants to merge 2 commits into
codicocodes:mainfrom
marekh19:feat/deterministic-cover-by-readme-order
Open

feat: deterministic plugin cover image via README order#202
marekh19 wants to merge 2 commits into
codicocodes:mainfrom
marekh19:feat/deterministic-cover-by-readme-order

Conversation

@marekh19

@marekh19 marekh19 commented Jul 18, 2026

Copy link
Copy Markdown

👋 First off — I'm a big fan of dotfyle and really appreciate everything you've built for the Neovim community. This one started as a small personal itch: my colorscheme's thumbnail on dotfyle was showing the wrong image, and digging into why turned into this PR.

What

Plugin cover image is random when a README has multiple images. Media is ordered only by orderBy: { thumbnail: 'desc' } — with no thumbnail set every row ties, so Postgres returns them in arbitrary heap order and the cover (and og:image) is whatever lands at index 0.

Adds a position column set to each image's index in the parser's output during sync, and orders by it. Cover now defaults to the first image the parser emits (see caveat below). The manual thumbnail flag still wins.

Fixes #186. Also addresses the ordering half of #136.

Why

How

  • Media.position Int? — nullable, additive migration.
  • syncMedia writes the parser index, deduped by URL keeping the first occurrence.
  • One shared mediaCoverOrderBy = [{ thumbnail: desc }, { position: asc, nulls: last }, { id: asc }], used by the plugin page, RSS feed, and the media endpoint.
  • Drive-by: syncMedia upserts were wrapped in an extra array (Promise.all([arr.map(...)])) and never awaited — fixed.

Ordering caveat — group order, not strict document order

position follows the order findMediaUrls emits URLs, which groups by source type (absolute githubusercontent > /assets uploads > relative markdown > relative HTML) and is document-ordered only within a group. So the cover is the first image of the highest-priority group present — e.g. a markdown screenshot can outrank an <img> banner placed above it.

Kept as-is on purpose. This preserves the parser's existing emit order, which predates test coverage and has no documented rationale (matcher flagged for refactor in #97). The real win here is determinism — same cover every sync, authors can influence it by ordering their README — not literal document order. True document order is a small follow-up (sort parser matches on their string index); happy to add it if you'd prefer that behavior.

Self-heals existing plugins, no stale-media delete

Rows dropped from a README but never re-synced keep position = null and sort last, so the correct cover surfaces on the next weekly sync without deleting anything. This deliberately avoids the deleteMany path from #140 that crashed the seeder. Stale-media cleanup (#128) stays a separate issue.

Not included

Test

sync.spec.ts — drives the real syncMedia + real parser against a multi-image README, stubbing only fetch and the DB upsert (no DB, no network, matches the existing vi.mock test style). Asserts: first README image → position 0, repeats collapse to one row keeping first position, relative paths resolve to raw URLs, and the thumbnail flag is never written so a manual cover choice survives re-sync.

parser.spec.ts — adds a case pinning the group-priority emit order (an absolute URL is emitted before a relative one that appears earlier in the README), so the caveat above is locked in and a future rework has to update it consciously.

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.

oasis.nvim has zero images and doesn't show up as a color scheme after being posted manually to dotfyle

1 participant