Skip to content

Implement automated ring.json generation #14

@AJaccP

Description

@AJaccP

🧠 Context

The webring embed widget fetches a small JSON file listing the ring members so it can compute prev/random/next. Today that file is a hand-written placeholder at public/ring.json (with a public/ring-README.txt explaining it's temporary). This ticket replaces it with a version generated at build time from the webring content collection, so the ring data is always derived from the real member entries — no hand-maintained list.

The generated file:

  • Includes only members with inRing === true (some members are listed in the directory but opt out of the ring).
  • Contains just the fields the widget needs: name and url.
  • Is ordered by graduation year ascending, with name as a stable tiebreak for members sharing a year (deterministic ordering matters — it defines the prev/next traversal).
  • Is emitted at the same path, /ring.json, with the same array-of-{name, url} shape as the placeholder — so anything already developed against the placeholder keeps working unchanged.

The graph visualization reads the webring collection directly at build; ring.json exists specifically as the external embed's data source. This ticket is independent of the embed work — that builds against whatever is served at /ring.json (placeholder or generated).

Files you'll touch:

  • src/pages/ring.json.ts (new — the static endpoint that generates the file)
  • public/ring.json (delete — replaced by the endpoint)
  • public/ring-README.txt (delete — no longer a placeholder to explain)

Don't touch:

  • The webring schema in src/content.config.ts.
  • webring.astro, RingGraph.astro, or other components.

🛠️ Implementation Plan

Note: Any code snippets may not work immediately, adjust as needed.

  1. Add a static JSON endpoint. Create src/pages/ring.json.ts. In a static build, an Astro endpoint at this path emits a real /ring.json file. Pull the collection, filter to inRing, sort, and project to the needed fields:

    import type { APIRoute } from 'astro';
    import { getCollection } from 'astro:content';
    
    export const GET: APIRoute = async () => {
      const members = await getCollection('webring');
    
      // Build `ring` from `members`:
      //   - .filter() to keep only inRing === true
      //   - .sort() by graduation year ascending, then name as a stable tiebreak
      //     (year difference, falling back to localeCompare on name)
      //   - .map() each down to just { name, url }
      // This order defines prev/next traversal in the embed — keep it deterministic.
      const ring = /* ... */;
    
      return new Response(JSON.stringify(ring, null, 2), {
        headers: { 'Content-Type': 'application/json' },
      });
    };
  2. Remove the placeholders. Delete public/ring.json and public/ring-README.txt. (Two files can't serve the same /ring.json path — the old static file must go so the generated endpoint owns it.)

  3. Verify. The generated file lands in the build output (dist/ring.json), not in public/public/ is an input folder, and the endpoint is served at the /ring.json URL (dynamically in pnpm dev, emitted to dist/ on pnpm build). Run pnpm build and confirm dist/ring.json exists, is valid JSON, contains only inRing: true members as { name, url }, and is ordered by grad year. Toggle a member's inRing to false in a content entry and rebuild to confirm they drop out. (If the site later sets a base path, the file is served under that base — that's the embed's concern, not this ticket's.)


✅ Acceptance Criteria

  • /ring.json is generated at build from the webring collection (no hand-maintained list).
  • Only inRing === true members appear, each as { name, url }.
  • Ordering is graduation year ascending, then name — deterministic.
  • The output path (/ring.json) and array shape match the old placeholder, so existing consumers keep working.
  • public/ring.json and public/ring-README.txt are deleted.
  • pnpm format:check, pnpm check, and pnpm build all pass.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Ready

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions