Skip to content

Public pages & sharing

Batches aren’t secret. The public index at / lists every published batch. The admin lives at /admin behind your login. Everything else on this page is about what a stranger holding a bottle can see and do.

The public index and The Farm share one look

Section titled “The public index and The Farm share one look”

/ and /farm (see below) share the same page width and layout rhythm — one visual family, not two competing designs. The index still opens with its own hero: your site name as a heading, next to the logo, with the tagline underneath. Set an optional About block — Admin → Settings → Public page — and it renders under the tagline too, but unlike the tagline’s inline-only markdown, About supports full block markdown — paragraphs, lists, links — rendered in its own prose container. Leave it empty and nothing renders there. The persistent nav carries Batches / The Farm links on every public page regardless.

Set a Contact email in Admin → Settings → Public page and every public page’s footer grows a quiet “Questions, allergies, or just want to say hi? Write me.” line, mailto-linked to that address. It’s meant for the person holding the bottle, not for you to check constantly — but remember the address itself is plain text in the page source, visible to anyone who views it and to any bot that loads the page. Clear the field and the line goes away.

Mash follows your device’s light/dark preference by default, and every page — admin and bottle pages alike — has a small toggle to force light or dark for that browser. See the Tour for a dark-mode capture of the bottle page, and its mobile-width shots of the admin batch editor and the subscribers list.

The shelf at / opens with your tagline beside a pair of drawn vintage seed packets — scenery, not data — and the site’s name lives in the navigation bar on every public page, so it never moves as a visitor clicks around.

Each card on the shelf carries a small composition-by-weight bar under the batch name — the same math as the bottle page’s bar, shrunk down — so a visitor can tell a mostly- mango sauce from a mostly-pepper one before clicking in. A batch with no photo isn’t a blank box: it gets one of five drawings in the site’s own ink (the mash bottle, a ghost pepper, a garlic bulb, a ferment jar, a seedling), picked by the batch so neighbouring cards don’t repeat. Cards introduce themselves as they scroll into view — the bar draws, the chilis light — and all of it stays still for visitors who prefer reduced motion or browse with JavaScript off.

A scanned bottle whose batch was unpublished — or a mistyped address — lands on a proper dead-end page: a tipped-over bottle, a line of explanation, and a link back to the shelf. It still answers with a 404 (an unpublished batch never confirms it exists), but the person holding the bottle gets a page, not a bare “Not found”.

Scanning the QR (or opening the shared link) lands on the batch’s public page:

  • the name, the story, and the dates
  • the composition bar: what the batch is, by weight
  • the itemized ingredient list, with weights, heat, and seed links — recognized ingredients get a small drawn icon next to their name. Essentially any common fruit, vegetable, spice, or pantry staple is covered (peppers, alliums, berries, stone fruit, roots, greens, sugars, thickeners like xanthan gum, oils, and so on), with generic catch-alls so a fruit or root Mash has no specific drawing for still gets a sensible category icon; anything truly exotic keeps the plain colored dot. “Peppercorn” draws the spice icon, not the pepper one — a naming overlap the icon table deliberately resolves in the spice rack’s favor.
  • a “Contains:” allergen line, when anything is flagged — see below
  • the Scoville heat estimate
  • seed links, if you recorded where a pepper came from
  • harvest provenance, if you turned it on for this batch — see below
  • a thumbs up / thumbs down, and a comment box
  • a Want a bottle? request form — see below

The composition bar and heat gauge draw themselves in on first paint — a brief, one-time fill/light animation, not a loop. It’s skipped entirely if the browser has “reduce motion” turned on; the page renders in its final state immediately either way.

Ingredient quantities accept fractions the way you’d actually write them on a card: 1/2, 1 1/2, or a plain decimal like 0.75 all parse correctly. On the bottle page’s itemized list, common fractions (halves, thirds, quarters, eighths) render as a single glyph (½, , ¾, …) instead of the raw decimal, so “1 1/2 cup” shows as “1½ cup”. Anything that doesn’t land on one of those common fractions just shows the plain number. The printed label doesn’t carry quantities at all — see the next section for what actually prints there.

The ingredient panel — it prints on the label

Section titled “The ingredient panel — it prints on the label”

The label-legal “Ingredients:” line — descending-weight order, the ordering food labels are legally required to use — prints on the physical label itself (see Labels for how it auto-fits the die-cut). The bottle page doesn’t repeat that sentence: its itemized list already shows every ingredient with its weight, heat, and seed link, which is strictly more than the flat panel line says.

Flag an ingredient with one or more of the FDA’s big 9 allergens (milk, eggs, fish, shellfish, tree nuts, peanuts, wheat, soy, sesame) from the “⚠ contains” field on its row in the recipe editor, and every published batch that uses that ingredient — the one you made last month and the one you made last year — picks up a “Contains: …” line on its bottle page, bold, above the vote widget. Flag nothing and the line simply doesn’t appear; a bottle page never claims “contains none of the big 9,” because silence is honest and an affirmative none-claim is a liability.

This is deliberately the one place Mash breaks its own rule that “a batch is the truth” (see Concepts) — allergen flags live on the ingredient, not a frozen copy on each batch, so a correction after the fact fixes every bottle page that uses that ingredient at once, instead of leaving old pages wrong.

Every bottle page carries a small Want a bottle? form: name, email, and an optional note (capped at 500 characters, same as comments). That’s all it collects — the name and email exist only so you can arrange the handoff, and Mash never links a request to a vote, a comment, or anything else public; see Concepts for the full privacy posture. Submitting shows the same “got it” confirmation whether it’s a fresh ask or a repeat of one still open — the form never confirms or denies what’s already on file. The request itself lands in Admin → Community → Requests, where marking it done can email the requester automatically if mail is configured (see below); without mail configured it still works as a plain checklist. Reopening a completed request and completing it again sends another “bottle ready” email — handy when a handoff falls through, so reopen deliberately rather than as an undo you don’t mean.

Votes are anonymous. Mash stores a salted hash of the voter’s address (VOTE_SALT), never the address itself, and a repeat vote from the same person moves their vote rather than stacking a second one.

Comments are plain text, capped at 500 characters, and never public until you approve them in Admin → Feedback. There is no HTML anywhere in the public write path.

The Feedback page also lists votes that arrived without words, so a mis-tapped thumb can be deleted — deleting removes the vote from the tally (and frees that browser to vote again). A “with comments only” toggle narrows the list back to the moderation queue.

You can also check several comments at once and approve or hide them in one go; delete stays a per-row action on purpose, since it is the one irreversible option.

If you logged a harvest and linked it to a batch, a “Show harvest dates” toggle on the batch page controls whether it shows on the bottle page — a Picked line reading, say, “Carolina Reaper · picked July 9”. Only the ingredient name and the date; the weight and any note you logged stay in the admin. It’s off by default, per batch.

A public photo wall at /farm, switched on by a checkbox in Admin → Settings (the on/off toggle stays there). It fills itself: harvest photos, journal and hero photos from published batches, plus anything you upload directly in Admin → Farm (garden shots that have no harvest or batch to live on). Captions are generated from structured data — “Carolina Reaper harvest — July 9”, the batch’s name — never from your harvest or journal notes, which weren’t written for public eyes. Photos are grouped under a month stamp (“JULY 2026”) in reverse-chronological order, so the wall reads like a running field journal rather than a flat grid. Every tile carries a small cue for what clicking it does — “→ bottle” for a photo sourced from a batch, “⤢ view” for a photo you uploaded directly, which just opens the full-size image. Every photo has a Hide button in the admin, and batch-sourced photos link back to their bottle page. Switched off, /farm returns a plain 404 and no link to it renders anywhere.

The line under the “The Farm” heading is your own — set it in Admin → Farm as the Intro, rendered the same inline-markdown way as the site tagline (bold, italic, and links, no block elements). The photo upload form there shows the filename you picked once you’ve picked it, instead of leaving the button reading “Photo” with no feedback that anything happened.

Every public page carries a slim navigation bar: your site title (linking home) plus Batches and — while the gallery is on — The Farm, with the theme toggle at the far end. Someone who scanned a bottle’s QR code can browse from that bottle to everything else you’ve published. On the index itself the bar drops the site title — the page opens with the full logo-and-title header right below it, and one masthead is enough.

Only published batches exist, as far as the public is concerned. An unpublished batch returns a plain 404 (not a 403, which would confirm the URL means something) and stays off the public index. Batch URLs contain a random 8-character token, so they can’t be enumerated.

The batch story field and the recipe method field are progressively enhanced with a small, self-hosted markdown editor (no external CDN, nothing loaded off-domain) — a toolbar for bold, italic, headings, links, and lists, plus a preview toggle. It’s admin-only; nothing about it ships on public pages. With JavaScript disabled, both fields fall back to a plain textarea and still submit fine — the editor is a convenience layer over the same field, not a requirement.

Every public page carries a <meta name="description">, Open Graph and Twitter card tags, and a canonical URL, so sharing a link — in Slack, iMessage, wherever — shows a proper title, description, and preview instead of a bare URL. The shelf at / also embeds a JSON-LD ItemList of the published batches on that page, and paginates at 24 batches per page once you have more than that.

Two pages are indexable: the public shelf at / and — while the gallery is on — The Farm. Everything else, individual bottle pages included, serves noindex, nofollow by design: a bottle page is for the person holding the bottle (or someone you shared the link with), not for search engines to crawl. New public pages inherit the noindex default unless they deliberately opt in.

Mash also serves a minimal robots.txt that allows everything — the per-page robots meta tags are what actually keep private pages out of search. Deliberately, it lists no Disallow paths: naming /admin there would advertise where the admin lives. There’s no sitemap either; two indexable URLs don’t need one. Rich link previews work everywhere regardless — noindex only affects search crawlers, not unfurls.

Mash phones nobody unless you ask it to. Point ANALYTICS_SCRIPT_URL at any snippet-style analytics script (Umami and Plausible attributes are supported via ANALYTICS_WEBSITE_ID and ANALYTICS_DOMAIN) and it loads only on public bottle pages, never in the admin. QR scans arrive with ?s=qr, so you can tell scans from clicks; the copyable share URL stays clean.

If you set SMTP_URL and MAIL_FROM, a batch’s admin page gets a one-time Announce button that sends a branded announcement email to subscribers. The announcement is a short email: your intro line (editable in Settings), the batch’s name, its heat and lot code, the batch’s story if it has one, and a link to the batch page — plus a one-click unsubscribe link in the footer and in the mail headers. Every email also carries a plain-text version for text-only mail clients.

Joining the list is double opt-in, and both ends of it are click-to-confirm: the confirmation link and the unsubscribe link each land on a page with a single button rather than acting on the visit itself. Corporate mail scanners prefetch every link in an email — without the button, a scanner could confirm a subscription nobody asked for or silently unsubscribe a reader. Mail clients that support one-click unsubscribe (RFC 8058) skip the page entirely via the mail headers.

The signup form is also quietly hardened against abuse: submissions that fill a hidden honeypot field are dropped without a trace, repeat signups for the same address re-send the confirmation email at most once per hour, and addresses that never confirm are deleted after seven days. None of this needs configuration, and none of it changes what a real subscriber sees — sign up, click the link in the email, done.

Admin → Subscribers also has a Send an announcement panel (visible once mail is configured): a subject line and a markdown body, queued to every confirmed subscriber — pending, unconfirmed addresses never receive anything. It’s for anything that isn’t a specific batch (a season update, a new feature, a reminder the shelf has moved) — the per-batch Announce button above is still the way to tell subscribers about one bottle. Every broadcast carries the same unsubscribe link and headers as a batch announcement.

Every public page carries one small, low-contrast line at the bottom linking back to this docs site — the only advertising Mash does for itself. It’s a single line in src/layouts/Public.astro; if you’d rather it weren’t there, delete it. It never appears in the admin.