site: visible RSS pill on category and tag pages

Autodiscovery alone is invisible since browsers dropped feed-detection
UI — add a small RSS pill in the subheader (new feedHref prop on
BaseLayout) linking to the page's scoped feed.

Claude-Session: https://claude.ai/code/session_01WZaczDJjL3xZ3u5spsN5AL
This commit is contained in:
2026-07-12 18:41:20 -04:00
parent 86ea4c8873
commit 1aa1216a9d
5 changed files with 29 additions and 2 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
## July 2026 ## July 2026
- Feeds for everything: alongside the existing `/rss.xml`, add a JSON Feed 1.1 at `/feed.json`, per-category RSS at `/categories/<name>/rss.xml`, and per-tag RSS at `/tags/<name>/rss.xml` — all derived from a shared `src/lib/feed.ts` item source (reviews + posts + bundles, same visibility rules as the site). Category/tag pages advertise their scoped feed via `<link rel="alternate">` autodiscovery; a new `/feeds/` directory page (linked from the footer) lists every feed with entry counts; every RSS feed carries an `atom:link` self reference and renders as a readable page in browsers via `public/feed.xsl` - Feeds for everything: alongside the existing `/rss.xml`, add a JSON Feed 1.1 at `/feed.json`, per-category RSS at `/categories/<name>/rss.xml`, and per-tag RSS at `/tags/<name>/rss.xml` — all derived from a shared `src/lib/feed.ts` item source (reviews + posts + bundles, same visibility rules as the site). Category/tag pages advertise their scoped feed via `<link rel="alternate">` autodiscovery and show a visible RSS pill in the subheader (browsers dropped feed-detection UI years ago, so autodiscovery alone is invisible); a new `/feeds/` directory page (linked from the footer) lists every feed with entry counts; every RSS feed carries an `atom:link` self reference and renders as a readable page in browsers via `public/feed.xsl`
- Programmatic-reader pass: build-generated `/llms.txt` (llmstxt.org format — site description, feed/endpoint map, and every review, bundle, and post with one-line descriptions), `SearchAction` on the home-page `WebSite` JSON-LD wired to `/search/?q=`, and a "For robots" section on `/feeds/` documenting `search.json`, `llms.txt`, the sitemap, and JSON-LD coverage - Programmatic-reader pass: build-generated `/llms.txt` (llmstxt.org format — site description, feed/endpoint map, and every review, bundle, and post with one-line descriptions), `SearchAction` on the home-page `WebSite` JSON-LD wired to `/search/?q=`, and a "For robots" section on `/feeds/` documenting `search.json`, `llms.txt`, the sitemap, and JSON-LD coverage
- Autonomous pipeline overhaul: new `/daily-pipeline` skill chains capture → bundle → review graduation → pick-of-the-day → build → commit → push and ends with a digest; `daily-finds` gains a score-and-shortlist stage (up to 12 `shortlist: true` finds per run, top 1-3 flagged as review candidates), feed-first fetching (`fetch: "feed"` + `feedUrl` in sources.json), cadence-aware scanning, and authority to apply mechanical source maintenance itself; `build-bundle`/`cluster-bundles`/`build-review` gain unattended `auto` modes (publish + log instead of pausing for confirmation), and `build-bundle weekly` writes a date-based "week's catch" so shortlisted finds never sit unbundled for weeks - Autonomous pipeline overhaul: new `/daily-pipeline` skill chains capture → bundle → review graduation → pick-of-the-day → build → commit → push and ends with a digest; `daily-finds` gains a score-and-shortlist stage (up to 12 `shortlist: true` finds per run, top 1-3 flagged as review candidates), feed-first fetching (`fetch: "feed"` + `feedUrl` in sources.json), cadence-aware scanning, and authority to apply mechanical source maintenance itself; `build-bundle`/`cluster-bundles`/`build-review` gain unattended `auto` modes (publish + log instead of pausing for confirmation), and `build-bundle weekly` writes a date-based "week's catch" so shortlisted finds never sit unbundled for weeks
- Review heroes are now auto-discovered: `src/lib/hero.ts` globs `reviews/*/hero.<ext>` (images and `hero.mp4`), so a new review needs no registration — only legacy non-`hero`-named assets remain as explicit overrides; both hero globs also accept `.avif` - Review heroes are now auto-discovered: `src/lib/hero.ts` globs `reviews/*/hero.<ext>` (images and `hero.mp4`), so a new review needs no registration — only legacy non-`hero`-named assets remain as explicit overrides; both hero globs also accept `.avif`
+1 -1
View File
@@ -20,7 +20,7 @@ Built with [Astro](https://astro.build) + MDX.
- Auto-generated `/categories/<name>/` and `/tags/<name>/` listing pages — finds excluded by design - Auto-generated `/categories/<name>/` and `/tags/<name>/` listing pages — finds excluded by design
- **Amazon affiliate handling** in `src/lib/affiliate.ts`: outbound links to Amazon (`amazon.com`, `amzn.to`, `a.co`, `smile.amazon.com`) get the configured tracking ID appended automatically; the link gets `rel="sponsored"`, an "Affiliate" badge appears next to it, and a one-line disclosure is shown beneath. Finds and reviews also take an optional `amazonLink` frontmatter field (canonical `/dp/<ASIN>` form) rendered as an "Also on Amazon" link — the capture pipeline populates it when a shortlisted physical product has an exact Amazon match. Add additional marketplace IDs (e.g. `amazon.co.uk`) to the `AMAZON_TAGS` map when needed - **Amazon affiliate handling** in `src/lib/affiliate.ts`: outbound links to Amazon (`amazon.com`, `amzn.to`, `a.co`, `smile.amazon.com`) get the configured tracking ID appended automatically; the link gets `rel="sponsored"`, an "Affiliate" badge appears next to it, and a one-line disclosure is shown beneath. Finds and reviews also take an optional `amazonLink` frontmatter field (canonical `/dp/<ASIN>` form) rendered as an "Also on Amazon" link — the capture pipeline populates it when a shortlisted physical product has an exact Amazon match. Add additional marketplace IDs (e.g. `amazon.co.uk`) to the `AMAZON_TAGS` map when needed
- **SEO surface**: single `<SEO>` component owning canonical, Open Graph, Twitter Card, feed autodiscovery, and JSON-LD; per-page structured data (`Review`, `BlogPosting`, `Product`, `Article`, `CollectionPage`, `ItemList`, `BreadcrumbList`, `WebSite` with `SearchAction`, `Organization`); auto-generated `sitemap-index.xml` (excludes `/grid/` and `/index3/`); `robots.txt`; static OG image fallbacks under `/public/og/`; promoted finds canonical to their review - **SEO surface**: single `<SEO>` component owning canonical, Open Graph, Twitter Card, feed autodiscovery, and JSON-LD; per-page structured data (`Review`, `BlogPosting`, `Product`, `Article`, `CollectionPage`, `ItemList`, `BreadcrumbList`, `WebSite` with `SearchAction`, `Organization`); auto-generated `sitemap-index.xml` (excludes `/grid/` and `/index3/`); `robots.txt`; static OG image fallbacks under `/public/og/`; promoted finds canonical to their review
- **Feeds everywhere**: site-wide RSS 2.0 at `/rss.xml` and JSON Feed 1.1 at `/feed.json`, plus per-category (`/categories/<name>/rss.xml`) and per-tag (`/tags/<name>/rss.xml`) RSS feeds, all derived from a single item source in `src/lib/feed.ts`; category/tag pages advertise their scoped feed via `<link rel="alternate">`; a human-readable directory at `/feeds/` (footer-linked) lists every feed with counts; RSS renders as a readable page in browsers via `public/feed.xsl` - **Feeds everywhere**: site-wide RSS 2.0 at `/rss.xml` and JSON Feed 1.1 at `/feed.json`, plus per-category (`/categories/<name>/rss.xml`) and per-tag (`/tags/<name>/rss.xml`) RSS feeds, all derived from a single item source in `src/lib/feed.ts`; category/tag pages advertise their scoped feed via `<link rel="alternate">` and show a visible RSS pill in the subheader; a human-readable directory at `/feeds/` (footer-linked) lists every feed with counts; RSS renders as a readable page in browsers via `public/feed.xsl`
- **Programmatic surface**: build-generated `/llms.txt` (llmstxt.org format — site map with every review/bundle/post and one-line descriptions, plus feed and endpoint pointers) and the full-site JSON index at `/search.json`, both documented in the "For robots" section of `/feeds/` - **Programmatic surface**: build-generated `/llms.txt` (llmstxt.org format — site map with every review/bundle/post and one-line descriptions, plus feed and endpoint pointers) and the full-site JSON index at `/search.json`, both documented in the "For robots" section of `/feeds/`
- Custom 404 page with recent-reviews and navigation fallback - Custom 404 page with recent-reviews and navigation fallback
- Light/dark mode via `prefers-color-scheme` - Light/dark mode via `prefers-color-scheme`
+25
View File
@@ -22,6 +22,8 @@ interface Props {
jsonLd?: Record<string, unknown> | Record<string, unknown>[]; jsonLd?: Record<string, unknown> | Record<string, unknown>[];
/** Page-scoped feeds (category/tag) advertised alongside the site-wide ones. */ /** Page-scoped feeds (category/tag) advertised alongside the site-wide ones. */
feeds?: Array<{ title: string; href: string }>; feeds?: Array<{ title: string; href: string }>;
/** Visible RSS pill in the subheader pointing at this page's scoped feed. */
feedHref?: string;
/** Opt into the wider content tier on roomy viewports (grid/listing pages). */ /** Opt into the wider content tier on roomy viewports (grid/listing pages). */
wide?: boolean; wide?: boolean;
} }
@@ -43,6 +45,7 @@ const {
articleTags, articleTags,
jsonLd, jsonLd,
feeds, feeds,
feedHref,
wide, wide,
} = Astro.props; } = Astro.props;
const siteName = 'Unique'; const siteName = 'Unique';
@@ -113,6 +116,11 @@ const year = new Date().getFullYear();
Updated <time datetime={updatedDate.toISOString()}>{fmtLong.format(updatedDate)}</time> Updated <time datetime={updatedDate.toISOString()}>{fmtLong.format(updatedDate)}</time>
</p> </p>
)} )}
{feedHref && (
<p class="subheader-feed">
<a href={feedHref}>RSS</a>
</p>
)}
</div> </div>
)} )}
</header> </header>
@@ -305,6 +313,23 @@ const year = new Date().getFullYear();
letter-spacing: 0.08em; letter-spacing: 0.08em;
color: var(--muted); color: var(--muted);
} }
.subheader-feed { margin: 0.45rem 0 0; }
.subheader-feed a {
display: inline-block;
padding: 0.05rem 0.5rem;
font-family: ui-sans-serif, system-ui, -apple-system, 'Helvetica Neue', sans-serif;
font-size: 0.66rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
background: var(--rule);
color: var(--muted);
border-radius: 999px;
}
.subheader-feed a:hover {
color: var(--accent);
text-decoration: none;
}
@media (max-width: 640px) { @media (max-width: 640px) {
.masthead-row { .masthead-row {
+1
View File
@@ -61,6 +61,7 @@ const breadcrumbs = buildBreadcrumbs([
ogImage={ogImage} ogImage={ogImage}
jsonLd={[collectionPage, itemList, breadcrumbs]} jsonLd={[collectionPage, itemList, breadcrumbs]}
feeds={[{ title: `Unique — ${category} (RSS)`, href: `/categories/${category}/rss.xml` }]} feeds={[{ title: `Unique — ${category} (RSS)`, href: `/categories/${category}/rss.xml` }]}
feedHref={`/categories/${category}/rss.xml`}
wide wide
> >
<EntryGrid entries={entries} /> <EntryGrid entries={entries} />
+1
View File
@@ -61,6 +61,7 @@ const breadcrumbs = buildBreadcrumbs([
ogImage={ogImage} ogImage={ogImage}
jsonLd={[collectionPage, itemList, breadcrumbs]} jsonLd={[collectionPage, itemList, breadcrumbs]}
feeds={[{ title: `Unique — #${tag} (RSS)`, href: `/tags/${tag}/rss.xml` }]} feeds={[{ title: `Unique — #${tag} (RSS)`, href: `/tags/${tag}/rss.xml` }]}
feedHref={`/tags/${tag}/rss.xml`}
wide wide
> >
<EntryGrid entries={entries} /> <EntryGrid entries={entries} />