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.
Contact line
Section titled “Contact line”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.
Dark mode
Section titled “Dark mode”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 public index
Section titled “The public index”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.
When a link goes nowhere
Section titled “When a link goes nowhere”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”.
The bottle page
Section titled “The bottle page”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.
Fractional quantities
Section titled “Fractional quantities”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.
The Contains line
Section titled “The Contains line”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.
Want a bottle?
Section titled “Want a bottle?”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 and feedback
Section titled “Comments and feedback”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.
Harvest provenance
Section titled “Harvest provenance”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.
The Farm (off by default)
Section titled “The Farm (off by default)”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.
Public navigation
Section titled “Public navigation”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.
Publish and unpublish
Section titled “Publish and unpublish”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.
Writing a story or a method
Section titled “Writing a story or a method”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.
Search engines and link previews
Section titled “Search engines and link previews”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.
Search-engine visibility
Section titled “Search-engine visibility”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.
Analytics (off by default)
Section titled “Analytics (off by default)”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.
Announcement email (off by default)
Section titled “Announcement email (off by default)”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.
“Made with Mash”
Section titled ““Made with Mash””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.