# One System content — rules for this website (keep this section)

This website's editable content is managed by One System (https://copilot.onesystemcrm.com) so the business owner
can change it by chat, with a preview and Undo. Follow these rules every time you build or change a page.

## 1. Editable content lives in `public/onesystem-content.json`, never typed into components

Everything a business owner might want to change goes in that file:

- headings, paragraphs, button labels, prices, opening hours, offers
- phone, email, address, WhatsApp/booking/social links
- images people may swap (and a description for each image)
- each page's Google title and description

Layout, styles, class names, components and logic stay in code. Only content goes in the file.

## 2. The file's shape

```json
{
  "version": 1,
  "pages": [
    { "path": "/", "name": "Home" },
    { "path": "/about", "name": "About" }
  ],
  "fields": {
    "home.hero.title": { "label": "Home — main heading", "type": "text", "page": "/", "value": "Fresh bread every morning" },
    "home.hero.text": { "label": "Home — text under the heading", "type": "paragraph", "page": "/", "value": "Baked in Austin since 1998." },
    "home.hero.button": { "label": "Home — button", "type": "text", "page": "/", "value": "See our menu" },
    "home.hero.button_link": { "label": "Home — button link", "type": "link", "page": "/", "value": "/menu" },
    "home.hero.image": { "label": "Home — main photo", "type": "image", "page": "/", "value": "/images/hero.jpg" },
    "home.hero.image_alt": { "label": "Home — main photo description", "type": "text", "page": "/", "value": "Loaves on the counter" },
    "contact.phone": { "label": "Phone number", "type": "phone", "page": "*", "value": "+1 512 555 0142" },
    "contact.email": { "label": "Email", "type": "email", "page": "*", "value": "hello@example.com" },
    "seo./.title": { "label": "Home — Google title", "type": "seo_title", "page": "/", "value": "Fresh Bread in Austin | Example Bakery" },
    "seo./.description": { "label": "Home — Google description", "type": "seo_description", "page": "/", "value": "Sourdough, pastries and coffee, baked fresh every morning." }
  }
}
```

- **Keys:** short and stable, with dots for sections (`home.hero.title`, `about.team.1.name`, `contact.phone`).
  Google title and description use `seo.<path>.title` and `seo.<path>.description`, for example `seo./about.title`.
  Never rename a key once the site is live: a renamed item starts over with the file's text, and the owner loses their edit.
- **Types:** `text` (one line, up to 300 characters), `paragraph` (plain text, line breaks allowed, up to 2,000),
  `link` (https://…, /page, #section, mailto: or tel:), `phone`, `email`, `image` (a path under `public/` like
  `/images/hero.jpg`, or an https:// address), `seo_title`, `seo_description`.
- **label:** what the owner sees, in plain words (“Home — main heading”, “Footer — opening hours”).
- **page:** the page path where it shows, or `"*"` for the header, footer and contact details shown on every page.
  List every page in `"pages"`.
- Values are plain text only: no HTML, no Markdown, no code.
- Items in a list (services, team members, prices) each get their own keys: `services.1.title`, `services.1.text`,
  `services.2.title`…

## 3. Show content only through the One System helper

The helper files are `onesystem/content.ts` and `onesystem/react.tsx` (from https://copilot.onesystemcrm.com/developers).

```tsx
const title = useContent("home.hero.title");
return <h1>{title}</h1>;
```

- Always render values as text: `{value}`. Never use `dangerouslySetInnerHTML`, `innerHTML`, `eval`, or put a value
  into `style`, a class name, a script or a URL that isn't a `link`/`image` item.
- Images the owner may swap live in `public/images/` (not imported from `src/`), referenced by the `image` item,
  with the `…_alt` item as the `alt` text.
- `paragraph` values may contain line breaks: show them with CSS `white-space: pre-line`.

## 4. Setup (once per project)

- **Site ID:** One System → Settings → Website → “Connect a site built with code” shows it (`os_` + 10 letters/digits).
  - Vite / React: `.env` → `VITE_ONESYSTEM_SITE=os_xxxxxxxxxx`, and in `index.html`:
    `<meta name="onesystem-site" content="%VITE_ONESYSTEM_SITE%">`
  - Next.js: `.env` → `NEXT_PUBLIC_ONESYSTEM_SITE=os_xxxxxxxxxx`, and in `app/layout.tsx` metadata:
    `other: { "onesystem-site": process.env.NEXT_PUBLIC_ONESYSTEM_SITE ?? "" }`
- **Vite / React** `src/main.tsx`:
  ```tsx
  import contentFile from "../public/onesystem-content.json";
  import { OneSystemContent } from "../onesystem/react";
  createRoot(document.getElementById("root")!).render(
    <OneSystemContent site={import.meta.env.VITE_ONESYSTEM_SITE} file={contentFile as never}><App /></OneSystemContent>
  );
  ```
  On each page component call `useSeo("/about")` so the Google title and description come from the file.
- **Next.js (App Router)** `app/layout.tsx`:
  ```tsx
  import contentFile from "@/public/onesystem-content.json";
  import { loadContent, type ContentFile } from "@/onesystem/content";
  import { OneSystemContent } from "@/onesystem/react";
  const file = contentFile as ContentFile;
  export const revalidate = 30;
  export default async function RootLayout({ children }: { children: React.ReactNode }) {
    const initial = await loadContent({ site: process.env.NEXT_PUBLIC_ONESYSTEM_SITE, file, init: { next: { revalidate: 30 } } });
    return <html lang="en"><body><OneSystemContent site={process.env.NEXT_PUBLIC_ONESYSTEM_SITE} file={file} initial={initial}>{children}</OneSystemContent></body></html>;
  }
  ```
  Server components can call `loadContent(...)` the same way; Google titles go in `generateMetadata` from
  `seo.<path>.title` / `seo.<path>.description`.
- Deploy as usual. Then in One System click **Check connection**.

## 5. When you change the site later

Add new items to `public/onesystem-content.json` and deploy. One System adds them; the owner's earlier edits stay.
Remove an item only when its element is removed from the site.
