Skip to content

PRD 12 — Website builder (online store)

Status: draft · Priority tier: P1 · Size: L · Depends on: auth/user (existing), PRD 01 (locations, staff), PRD 02/04/05 (classes, appointments, courses), PRD 06 (packages, memberships), PRD 11 (contact form), media storage

1. Summary

A hosted public site per studio with a theme and section customizer, content pages, navigation menus, blog, FAQ, legal documents, domain, and tracking IDs. The site renders live booking data (timetable, packages, memberships, locations, team) and sends visitors into booking/login. This PRD covers the admin side and the public rendering contract; the booking/checkout flow itself belongs to other PRDs.

2. Problem & goals

Studios need a bookable web presence without a developer. Goals:

  • New studio has a published, correct-looking site with zero configuration (default theme + system pages).
  • Owner can change brand colours, fonts, logo and the homepage text and publish in <= 6 clicks.
  • Public pages: LCP <= 2.5 s on 4G for the home page; indexable (SEO fields, sitemap, canonical).
  • No customer-visible change until Publish (draft vs live separation). Non-goals: arbitrary HTML/JS layout editing, section reordering/adding (v1 sections are fixed), e-commerce beyond packages/memberships, multi-language sites, our own domain registrar.

3. Users & roles

Owner/admin (everything), staff with website permission (content only), visitor/customer (public site). Front desk: none.

4. Scope

In (v1)

  • must: themes: one live theme + library of drafts; publish, customize; at least 1 built-in theme.
  • must: customizer with sections Header, Hero, About, Appointments, Events, CTA banner, Classes, Locations, Blogs, Footer; theme settings colours, typography, social links, favicon.
  • must: pages (system fixed set + custom), custom page editor, SEO, visibility.
  • must: navigation Main + Footer, nesting, reorder, kebab actions.
  • must: platform subdomain <slug>.<our domain> as primary domain.
  • must: blog posts + blog types; FAQ; legal docs incl. Terms flag.
  • should: custom CSS; preferences (tracking IDs, site verification); preview (desktop/mobile); scheduled visibility; blog bulk actions; custom domain connect with DNS instructions; primary-domain change and redirect toggle.
  • could: blog sub type, tags, featured pin; page author; theme preview from library; image crop 16:9. Later (v2+)

  • Additional themes, section layout variants, section show/hide and reorder, per-section item count, extra pages per theme, floating WhatsApp button, reviews display, multi-language. Out

  • Automated DNS/SSL provisioning at launch is infrastructure-heavy: ship manual DNS instructions + verification first; auto-cert via a managed proxy is a decision (open question).

  • Copying the original's theme designs, names ("Lavander", "Alpha") or copy text.

5. User stories

  • US-12-01 As an owner, I want a ready site on day one so that customers can book immediately.
  • US-12-02 As an owner, I want to set colours, fonts and logo so that the site matches my brand.
  • US-12-03 As an owner, I want to edit headline text, buttons and featured items per section so that the home page says what I want.
  • US-12-04 As an owner, I want to preview before publishing so that customers never see half-done edits.
  • US-12-05 As an owner, I want custom pages (e.g. Pricing FAQ, Teacher training) with SEO so that I appear in search.
  • US-12-06 As an owner, I want to arrange the menu, including submenus, so that visitors find things.
  • US-12-07 As an owner, I want to hide a menu item or page without deleting it.
  • US-12-08 As an owner, I want blog posts grouped by type so that the site has fresh content.
  • US-12-09 As an owner, I want FAQ topics and legal documents so that policies and answers are public.
  • US-12-10 As an owner, I want to use my own domain so that the site is mine.
  • US-12-11 As a marketer, I want to paste Meta/GA4/GTM/TikTok IDs so that tracking works without code.
  • US-12-12 As a visitor, I want a fast, mobile-friendly site that lets me book or buy.

6. Functional requirements

Themes and customizer

ID requirement pri evidence
FR-12-01 Theme list: "Current theme" card (name, thumbnail, Customize) and "My library" cards (name, last edited, Publish, Customize, kebab > Preview) must S95
FR-12-02 New business gets a copy of the default theme, published must derived
FR-12-03 Publish from library asks confirm ("replaces current theme"), then swaps live theme atomically; previous live theme moves to library must S97
FR-12-04 Customizer shell: section list, Theme settings pinned at bottom, live preview iframe, page switcher (Home, Classes, Packages, Membership, Timetable, Locate us, Team, Blog, Contact), desktop/mobile toggle, Save (disabled until change), Publish Theme, exit link must S96, S98, S99
FR-12-05 Editing the live theme: Save writes a draft; Publish makes it live (draft-vs-live separation). Assumption; original behaviour unobserved must S96; open Q
FR-12-06 Section panels share the shape: Title (text), Description (textarea, 250 soft counter), Buttons (2: label, link, outline flag); blank label hides the button must S210-S217
FR-12-07 Button link = one of the fixed system pages, any custom page, or custom URL must S210, S104c
FR-12-08 Header: logo image (JPG/PNG/GIF <= 10 MB), background and text colour must S96a
FR-12-09 Hero: single image, overlay toggle + overlay colour, Title/Description/Buttons must S210
FR-12-10 About and CTA banner: section image + Title/Description/Buttons must S211, S214
FR-12-11 Appointments, Events (courses), Classes: Title/Description/Buttons + 3 item pickers each (None or a record) must S212, S213, S215
FR-12-12 Locations: Title/Description/Buttons; shows all locations automatically must S216
FR-12-13 Blogs: Title/Buttons + 4 post pickers; no description should S217
FR-12-14 Footer: background and text colour; shows menu "Footer", social links, legal links must S96b
FR-12-15 Colours: primary background; secondary background + text; solid button text + background; outlined button/link colour (hex validated, swatch) must S219
FR-12-16 Typography: header font and body font from a curated list (v1: about 12-32 open-licence fonts, self-hosted) should S220, S220b
FR-12-17 Social links: Facebook, X/Twitter, Instagram, YouTube, TikTok (URL validated; empty hidden) should S221
FR-12-18 Favicon upload (JPG/PNG/GIF <= 10 MB, converted to proper icon sizes) should S222
FR-12-19 Custom CSS (one textarea, applies site-wide, sanitised, size cap 20 KB, loaded after theme CSS) could S223
FR-12-20 Empty item picker slot = section renders only filled items; section with no content and no items is hidden must S212 (derived)
FR-12-21 Library theme preview opens site in preview mode with that theme could S95
FR-12-22 Section group "hide" (eye) per element: Title and similar hide on site could S210; unverified
### Pages
ID requirement pri evidence
--- --- --- ---
FR-12-30 Pages list: search by title, status filter, sort by title/updated/status; columns title, slug, type, updated, status must S100
FR-12-31 System pages (Home, Timetable, Locate us, Contact us, Team, Class, Course, Package, Membership, Appointment, Blog) always exist, not deletable/editable except SEO must S100, S100a
FR-12-32 Row menu: system = SEO settings; custom = Edit, SEO, Show/Hide (instant), Delete (confirm) must S100a, S100g
FR-12-33 Custom page: title (required), slug auto from title, unique per site, editable; featured image; rich text; author; Preview; Save & Exit must S101
FR-12-34 Rich text: headings, bold/italic/underline/strike, lists, quote, alignment, link, image, video embed (allow-listed hosts); HTML sanitised server side must S101c
FR-12-35 Visibility: visible now, hidden, scheduled (date+time, site time zone) should S101b
FR-12-36 SEO drawer for every page: title (65), description (320), social image (<= 1 MB), noindex, SERP preview; slug read-only for system pages should S100f, S101d
FR-12-37 Hidden/scheduled pages return 404 publicly and are excluded from sitemap must derived
### Navigation
ID requirement pri evidence
--- --- --- ---
FR-12-40 Two menus: Main, Footer; collapsible groups must S103
FR-12-41 Item: name, link (page or custom URL), read-only slug, visible flag; one nesting level; drag reorder and re-parent must (nest: should) S103, S104, S225
FR-12-42 Add/Edit drawer; Add disabled until name and link valid must S104, S225
FR-12-43 Kebab actions: Edit, Visible toggle (instant), Rename, Copy link, View live page, Delete (confirm) must S224
FR-12-44 Home item flagged "HomePage" and not deletable should S103
FR-12-45 Item linking to a hidden/deleted page shows a warning and is not rendered publicly must derived
FR-12-46 Hidden parent hides its children publicly (assumption; original unclear) should S224; open Q
### Domains
ID requirement pri evidence
--- --- --- ---
FR-12-50 Each site has a platform subdomain from the business slug, status Connected, primary by default must S106
FR-12-51 Connect existing domain: enter domain, show DNS records (CNAME/A + TXT), Verify, status Pending/Connected/Failed should S107
FR-12-52 Change primary domain (confirm); other domains redirect to primary; toggle redirect off (confirm warning) could S107b, S107c
FR-12-53 TLS certificate issued automatically for connected domains should infra; open Q
### Blog
ID requirement pri evidence
--- --- --- ---
FR-12-60 Blog types CRUD (title); list with search, sort, "View posts"; delete warns that posts of that type are deleted too (cascade) must S109-S109e
FR-12-61 Post: title, feature image (16:9 crop, <= 3 MB), rich text, author, type (required), visibility (visible/hidden/scheduled), SEO must S110
FR-12-62 Optional sub type, tags, "pin as featured" could S110
FR-12-63 Posts list: search, filters (status, type, tag, author), rows per page, select-all and bulk set visible/hidden/delete with confirm should S108
FR-12-64 Public blog page lists visible posts newest first, featured pinned first, filter by type must derived
FR-12-65 Feature image required in original; we make it optional with a placeholder should S110 (deviation)
### FAQ and Legal
ID requirement pri evidence
--- --- --- ---
FR-12-70 FAQ topics: title, ordered Q&A (question, answer rich text, 250 char limit), add/remove Q&A, drag reorder topics and items must S111, S112
FR-12-71 Topic list with search; delete confirms that all Q&As go with it must S111d
FR-12-72 Legal docs: title, rich text, status active/draft, updated by/at, search/filter must S113, S114
FR-12-73 Exactly one doc may be "Terms & Conditions"; setting another clears the previous; the checkout and signup consent link to it should S114, S113c
FR-12-74 Privacy policy doc reference (flag) for footer/consent links could derived
### Preferences
ID requirement pri evidence
--- --- --- ---
FR-12-80 Fields: Meta Pixel ID, GA4 ID, GTM ID, TikTok Pixel ID, Meta domain verification, Google site verification; one Save; format validation (e.g. G-XXXX, GTM-XXXX, digits) should S115
FR-12-81 Public site injects only validated IDs through our own templates (never raw script text) must S115; security
### Public rendering
ID requirement pri evidence
--- --- --- ---
FR-12-90 Server-rendered pages from published theme + live data; page-level meta, OpenGraph, canonical, sitemap.xml, robots must S100f
FR-12-91 Header shows main menu, Login/Sign up (existing auth) and My bookings must S96
FR-12-92 Mobile-first responsive; theme tokens exposed as CSS variables must derived
FR-12-93 Uploaded images resized/served via CDN-style variants; alt text field for images should derived

7. Screens

our screen modelled on states
Theme list S95, S97 filled, publish confirm, publishing, error
Customizer (shell, section panels, theme settings, font picker) S96-S99, S210-S223 loading preview, clean, dirty, saving, save error, preview load error
Pages list + SEO drawer + delete confirm S100-S100g empty custom, filled, filter-empty
Page add/edit S101-S102 empty (Add disabled), filled, scheduled, error
Navigation + add/edit drawer S103, S104, S224, S225 filled, empty menu, drag, link-broken warning
Domains, connect, change primary S106, S107 connected, pending, failed
Blog posts, types, post form S108-S110g empty, filled, selected (bulk), crop dialog
FAQ list/form S111, S112 empty, filled
Legal list/form S113, S114 empty, filled
Preferences S115 pristine, dirty, invalid ID
Sub-folders (e.g. theme/, content/) can be split out at implementation if the PRD grows; kept single-file for now.

8. Flows

  • Customize and publish: Theme > Customize > section > edit > Save > Publish Theme (6 clicks; original ~same).
  • Switch theme: Publish on library card > confirm (3 clicks).
  • Custom page: Pages > Add > title + content > Add (4 clicks + typing); then Navigation > Add item > pick page (4 clicks). Improvement: after creating a page offer "Add to menu" (1 click).
  • Hide menu item: kebab > Visible (2 clicks).
  • First blog post: Types > Add type > back > Add blog (<= 12 clicks original; target 8 by allowing inline "create type" in the post form).
  • Edge: deleting a page referenced by menu/buttons shows impact and clears/flags links; two admins editing the same theme: last save wins with "changed by someone else" warning (could).

9. Data model (guesses marked)

  • site(id, business_id, slug, primary_domain_id, live_theme_id).
  • theme(id, site_id, name, base_template, settings jsonb, sections jsonb, status live|library, draft jsonb?, updated_at): settings = colours (6), fonts (2), social (5), favicon_asset, custom_css; sections keyed by section name with title, description, buttons[2]{label, link, outline}, image_asset, overlay{on, colour}, item_ids[]. JSON is validated by Go types (guess: JSONB vs relational).
  • site_page(id, site_id, kind system|custom, system_key, title, slug unique(site), content_html, featured_asset, status visible|hidden|scheduled, publish_at, author_id, seo_title, seo_description, seo_asset, noindex, updated_at).
  • menu(id, site_id, key main|footer), menu_item(id, menu_id, parent_id, position, name, page_id null, url null, visible).
  • domain(id, site_id, host unique, kind platform|custom, status, verification_token, is_primary, redirect_to_primary, created_at).
  • blog_type(id, site_id, title), blog_post(id, site_id, type_id, title, slug, image_asset, content_html, status, publish_at, author_id, sub_type, tags text[], featured, seo_*, updated_at).
  • faq_topic(id, site_id, title, position), faq_item(id, topic_id, position, question, answer).
  • legal_doc(id, site_id, title, content_html, status, is_terms, updated_by, updated_at).
  • site_preference(site_id, meta_pixel, ga4, gtm, tiktok, meta_verification, google_verification).
  • Assets reference a shared media table (owned by a platform PRD). Domain packages singular: site, page, blog, faq, legal (or one site package; decide at build).

10. Business rules & edge cases

  • Rich text is sanitised on save with an allow-list; custom CSS stripped of @import/url(javascript:); no custom JS ever.
  • Slugs: lowercase, hyphenated, unique per site, reserved words blocked (system routes, admin, api).
  • Deleting a blog type deletes its posts (explicit confirm showing count) vs safer reassign; we choose confirm with count (original cascades).
  • Terms flag unique per site.
  • Scheduled items publish via a job or evaluated at read time (read-time compare, no job needed).
  • Contrast: warn when button text vs background contrast < 4.5:1.
  • Item pickers show only active, publicly bookable items; a picked item later deleted is silently skipped.
  • Changing the business slug changes the platform subdomain; old one redirects for 90 days (open Q).
  • A site with no classes still renders without empty sections.

11. Permissions

action owner admin staff front desk
Theme, domains, preferences, custom CSS edit edit none none
Pages, nav, blog, FAQ, legal edit edit edit if "Online store" granted none
View all all granted none
All admin routes in use: [auth] group; public site routes are deliberately public and read-only.

12. Acceptance criteria

  • Given a new business, when the public URL is opened, then the home page renders with default theme and all 11 system pages are reachable.
  • Given an edited theme not yet published, when a visitor loads the site, then they see the old version.
  • Given a library theme, when Publish is confirmed, then it is live and the previous live theme appears in the library.
  • Given a button with blank label, then it does not render.
  • Given an invalid hex colour, then Save is blocked and the field shows the error.
  • Given a custom page set to hidden, when its URL is visited, then 404 and it is absent from sitemap.xml.
  • Given a page scheduled for tomorrow, then it is not visible today and visible after that time.
  • Given a system page, then the menu offers only SEO settings and no delete.
  • Given a SEO title of 66 chars, then input is limited/blocked at 65.
  • Given a menu item hidden, then it is greyed in admin and absent publicly.
  • Given a blog type with 3 posts, when deleting it, then the confirm states 3 posts will be deleted; after confirm, none remain.
  • Given two legal docs, when doc B is set as Terms, then doc A's flag is cleared.
  • Given GTM ID abc, then Save fails with a format error; given GTM-ABC123, the public pages include the GTM snippet.
  • Given a pasted <script> in rich text, then it is stripped on save.
  • Given a connected custom domain with redirect on, when the platform subdomain is requested, then it 301s to the primary.

13. Dependencies & open questions

  • Depends on classes/appointments/courses/packages/memberships/locations/staff data for sections and system pages; auth for login CTA; media storage; notifications for contact-form submissions (Contact us page form; staff event "new contact form submission").
  • Open: draft vs live semantics when editing the live theme (original unobserved); hero gallery behaviour; eye "hide" effect; Rename/Delete nav dialogs; domain verification and TLS strategy; hosting model (shared renderer by Host header assumed); theme count at launch; plan gating of custom domain, custom CSS and extra themes (pricing tier: TBD); whether hidden parent hides children; blog comment/RSS (not observed); legal doc versioning; storefront checkout belongs to which PRD; slug-change redirects; rate/size limits for media.
  • Recon gaps: sections' real output on the public site (part 07 only covers guest pages), Save/Publish network behaviour, domain wizard beyond step 1.

14. Evidence

See evidence/evidence.md.