Guestbook Build

How was the poetry guestbook implemented?

Original Poetry

Book

Above 768px, .desk-desktop shows an interactive animated 3D book volume.

Book.tsx drives open/close with GSAP (gsap + @gsap/react).

Poems

Poems are Markdown files that live under poetry/:

  • loadPoems() reads that folder at request time, splits on ## headings, and turns *emphasis* / **strong** into HTML. Each file becomes a Poem (id from the filename, sections for every heading).
  • pickPoemIndex() picks a random poem for the first paint, and a different one whenever the reader turns the page, ensuring no repeats in a row.
  • On each poem, a heart button calls togglePoemHeart allowing visitors to like poems.

Guestbook Functionality

Supabase Backend

The schema applied by running scripts/schema.sql enables row-level security on every public table — is_admin() is true only when the JWT has app_metadata.role = 'admin' (stamped from the allow-list at signup, not from editable user_metadata).

TableanonAdmin (authenticated + is_admin())
commentsINSERTSELECT, INSERT, UPDATE, DELETE
guestbook_settingsSELECTSELECT, UPDATE
admin_allowlistnonenone
poem_heartsnonenone
  • comments has no public SELECT, so emails stay on the private table. Visitors read the comments_public view instead (id, display_name, body, timestamps).
  • guestbook_settings is a singleton (id = 1) — no INSERT or DELETE policies. SELECT is granted to anon and authenticated.
  • admin_allowlist has RLS on with no policies, so anon / authenticated cannot read or write. service_role bypasses RLS (and still needs table grants). Auth's before-user-created hook reads it as supabase_auth_admin.
  • poem_hearts has no table policies. Likes go through the security-definer RPCs toggle_poem_heart and poem_heart_state. Public counts come from poem_heart_counts and poem_heart_total.

Next.js Frontend

CommentBubbles.tsx powers both the desktop inline guestbook and the mobile commenting overlay. Publicly displayed comments returns the comments_public table (id, display_name, body, timestamps — no email) via getPublicCommentsPage by newest first.

When users click Submit, the submitComment server action:

  1. Requires a display name and comment body.
  2. Enforces commentLengthError — name cap 128, email cap 254, and comment_length from settings for the body (default at 1000).
  3. Runs checkCommentProfanity (obscenity plus the admin allow-list).
  4. Consumes consumeCommentRateLimit (rate-limiter-flexible over DATABASE_URL, keyed by a hash of clientIp()). Burst and daily caps come from settings (default at 1 comment per 5 minutes, up to 2 per 24 hours).
  5. Inserts the submitted comment in the private comments table, then revalidatePath for /, /guestbook, and the admin path (so a later navigation or refresh is not served from a stale cache). CommentBubbles then calls getPublicCommentsPage again and writes that result into local state — the list updates in place, with no page reload.

Feature Flags

Flags are built on compile:

  • src/lib/flags.ts reads process.env.FLAG_* with envFlag (true / 1 vs false / 0 — unset uses the default). FLAG_COUNTER_URL and FLAG_COUNTER_DATE are not in this file — getUniqueVisitors() reads them at request time so Next does not bake them into the client bundle.
  • next.config.ts lists the boolean flag names under env so Next can inline them into client components.

FLAG_PUBLIC

Allows diversion of the guestbook to a public page instead of the landing with the poetry volume.

Used to deploy to the public version of the site at https://y2k-guestbook.vercel.app/:

  • Homepage is the full page guestbook instead of the landing with the poetry volume.
  • Favicon is dark green instead of coral.
  • GitHub "Releases" and "Report Issue" links point at y2k-guestbook instead of guestbook.
  • Documentation landing subtitle, live OpenAPI explorer, and {.public} / {.private} fences all follow this flag.
  • Documentation pages with public: false in their YAML frontmatter (like this one) do not display in the side navigation and do not exist on the public version of the site.

Accessibility

Along with checking pages with Chrome's Lighthouse accessibility audit, as part of pull request checks, a11y lint runs a spot check on all guestbook application pages to ensure they are somewhat accessible and any errors discovered are patched before deployment.