# Juniper Lane Coffee Roasters · Back Office

> Public LivePage template `livepage-app` · Scene: Tools & mini apps · Industries: Food & drink / Retail
> Anyone and any agent may read and adapt it; no account needed. Example content is fictional.
> Live page: https://space.24haowan.com/s/4Vgca4HGNT4r · Detail: https://getlivepage.com/examples/livepage-app

## What it is

Shop back office · A small app people read and write

## When to use it

What you need is not just a document but a small app used every day: sales numbers, products and stock, order intake, a project board, check-ins, polls. The data lives in tables, so editing a table changes the page, and readers can write back. If you only collect sign-ups once, pick sample-event-signup.

## What the user would say

Help me build a back-office page for our small shop that we use every day, and send it as a web link. At the top, the shop name and a "Refresh data" button. The first block is sales: four numbers (order value this month, order count, waiting to ship, open to-dos) plus a bar chart of sales for the last 7 days (with an average line), and below it a to-do list filterable by status. The second block is products and stock (available = stock minus unshipped orders), a table of recent orders, and a small form where customers register what they want. The third block is this week's projects and their status. The fourth block is shop check-ins: one row per task, the last 7 days as boxes on the right, and a "Done" button. Last, a new-product poll for regular customers that shows vote bars. Keep all the data in data tables, so later I only say which number to change and the page does not need to be republished. On a phone, the charts use their own set of axes.

If you don't have the LivePage connector (MCP) yet, read https://getlivepage.com/for-agents.md first and follow it to connect, or publish without signing in.

## How to make your own

1. Read the entry file https://getlivepage.com/examples/livepage-app/source/index.html (and the other files below if it references them).
2. Keep the structure, layout and interactions; replace ALL content with the user's own. Company names, people and numbers in the template are fictional.
3. Deliver one self-contained HTML file (styles inline, no external fonts/scripts/CDNs) that looks right at 375px and 1440px wide, with dark mode if the template has it.
4. To turn it into a link that opens inside WeChat and keeps updating: connect LivePage (https://getlivepage.com/for-agents.md) and publish; without an account the user can upload the file at https://getlivepage.com/quick.
5. No LivePage tools but you can send HTTPS requests: POST the finished HTML (Content-Type: text/html) to https://getlivepage.com/api/quick?client=template-livepage-app — it returns a preview link, a 7-day share step and a claim link the user can keep after signing in.

## Page structure

- Today
- Sales
- Products & orders
- Projects
- Check-ins
- Bean poll
- How it updates
- Make your own

## Files (public, plain text)

- https://getlivepage.com/examples/livepage-app/source/index.html (entry)
- https://getlivepage.com/examples/livepage-app/source/_data/checkins.json
- https://getlivepage.com/examples/livepage-app/source/_data/daily_sales.json
- https://getlivepage.com/examples/livepage-app/source/_data/habits.json
- https://getlivepage.com/examples/livepage-app/source/_data/order_intake.json
- https://getlivepage.com/examples/livepage-app/source/_data/orders.json
- https://getlivepage.com/examples/livepage-app/source/_data/products.json
- https://getlivepage.com/examples/livepage-app/source/_data/signup.json
- https://getlivepage.com/examples/livepage-app/source/_data/todos.json
- https://getlivepage.com/examples/livepage-app/source/_data/weekly_board.json

## Once published on LivePage

- 9 data tables (the page fetches ./_data/<table>.json)
- Readers write back to three tables through native forms (order requests / check-ins / poll)
- Edit a table without republishing; the link stays the same

## Prepare before publishing

- Data tables orders / daily_sales / todos / products / weekly_board / habits (read-only, append_open=false): the shop updates them by editing the tables each day
- Data tables order_intake (columns sku / qty / name / note), checkins (columns habit / name / day) and signup (columns choice / name / note), all append_open=true: the three forms write here

## Template README

English edition of `core/space/examples/livepage-app/` (EPIC #11418 · R1 #11427). Same sections, same nine table names and columns, same page logic; the story is re-set for an international reader (fictional English brand, names from several origins, prices in $, Slack / email / WhatsApp as the channels, neutral holidays) rather than translated word for word — the owner's 2026-10-05 rule for the English samples. The Chinese pack is the source for structure — change a section or a table on one side, change the other (`core/space/examples/QUALITY.md` §9). Sample content — all names and figures are fictional (the roastery, the people, the products and every number are made up).

This directory is the **source of the publish pack**: a single-file `index.html` (styles and script inline, sticky top tabs that follow the scroll) + `manifest.json` + `_data/` sample data.
Published as a separate English page in the official workspace (title in English, public, `allow_copy`, not indexed); the link is `en.live_url` in `../../livepage-app/gallery.json`.

## What it demonstrates

**A page that is a small app, not a document: nine data tables feed it, three of them take rows back from readers, and none of it is republished when a number changes.**

- Reads are `fetch('./_data/<table>.json')` (the revision number comes back in the `X-Space-Revision` header). The page combines tables in the plainest way:
  available stock = `products.stock − qty of orders still "To ship"`; the sales chart is `daily_sales` with an average line; the check-in grid is `checkins` counted per habit per day for the last 7 days; the poll is `signup` counted per `choice`.
- Writes are three plain forms `<form action="./_data/<table>" method="post">` (`order_intake` / `checkins` / `signup`): they submit without script (the server answers 303 back to this page with `?space_submitted=<table>`);
  with script they become a `fetch` POST of one JSON row and show the result in place. The check-in form has one submit button per habit (`name="habit"`), all in the same form.
- Errors are explained by the response body's `error.code` (`append_closed` / `append_rate_limited` / `forbidden` / `table_not_found` / `moderation_unavailable`); opened by double-clicking the file, `fetch` fails and the page says to open it from its link.
- The "Refresh data" button reacts on the spot (grey out + "Loading…" + "Updated" flash + no stacked requests): the nine tables usually come back identical, so the redraw alone is invisible.
- Command-line and SDK snippets stay inside `<details class="code-fold">`; the body never shows a `<pre>`; no internal table name appears in a section heading; no copy-prompt on the page — copying goes through the agent's `copy_proposal` (`QUALITY.md` §6.1).

## Nine tables (attached to the page; `_data/` in the pack is the same-named fallback)

| Table | Columns | Reader append `append_open` | How the page uses it |
|---|---|---|---|
| `orders` | `order_no`, `sku`, `qty`, `amount`, `status`, `customer`, `created` | off | the four sales figures (value / count / waiting to ship), recent orders; joined with `products` for available stock |
| `daily_sales` | `day`, `amount` | off | 7-day sales bar chart |
| `todos` | `task`, `owner`, `due`, `status` | off | to-do list, filterable by status |
| `products` | `sku`, `name`, `price`, `stock`, `category`, `emoji` | off | product list, filterable by category (`emoji` is no longer shown); available = `stock − qty of orders "To ship"` |
| `order_intake` | `sku`, `qty`, `name`, `note` | **on** | the "Register what you want" form writes one row; recent requests listed |
| `weekly_board` | `project`, `owner`, `status`, `progress`, `updated` | off | project board, filterable by status |
| `habits` | `habit`, `target` | off | the habit definitions for the check-in wall |
| `checkins` | `habit`, `name`, `day` | **on** | one "Done" button per habit (`name=habit`) writes one row; counted per day into the 7-day grid |
| `signup` | `choice`, `name`, `note` | **on** | the new-bean poll; rows from `GET` already include reviewed reader rows, counted per `choice` |

Status words are the English values stored in the tables and matched by the page: `orders.status` ∈ `Done` / `Shipped` / `To ship` / `Cancelled`; `todos.status` ∈ `To do` / `In progress` / `Done`; `weekly_board.status` ∈ `In progress` / `At risk` / `Done` (`Blocked` is also coloured). Categories: `Coffee` / `Merch` / `Snacks`. Only the example rows differ from the Chinese pack; table names, columns and `append_open` are identical.
Create the tables before you publish (rows below are the pack's `_data/`; `checkins` days are relative — move them to the current week before publishing, the grid draws "today back 7 days"):

```
set_data proposal_id=<id> table="orders" revision=0 append_open=false rows=<_data/orders.json>
set_data proposal_id=<id> table="daily_sales" revision=0 append_open=false rows=<_data/daily_sales.json>
set_data proposal_id=<id> table="todos" revision=0 append_open=false rows=<_data/todos.json>
set_data proposal_id=<id> table="products" revision=0 append_open=false rows=<_data/products.json>
set_data proposal_id=<id> table="weekly_board" revision=0 append_open=false rows=<_data/weekly_board.json>
set_data proposal_id=<id> table="habits" revision=0 append_open=false rows=<_data/habits.json>
set_data proposal_id=<id> table="order_intake" revision=0 rows=[] columns=["sku","qty","name","note"] append_open=true
set_data proposal_id=<id> table="checkins" revision=0 rows=<_data/checkins.json> columns=["habit","name","day"] append_open=true
set_data proposal_id=<id> table="signup" revision=0 rows=[] columns=["choice","name","note"] append_open=true
```

Locally: `node cli/space-cli.mjs serve examples/en/livepage-app --closed orders,daily_sales,todos,products,weekly_board,habits` (`--closed` mirrors the online switches: only `order_intake` / `checkins` / `signup` take rows).
Local submissions are appended into these `_data/` files — `git checkout examples/en/livepage-app/_data` when you're done. Pushing them with the pack is harmless: when a same-named table exists online the page reads the live data, the pack's copy is only the fallback.

English runs about 1.4× wider than Chinese: the four figure labels are short (`Order value ($)` — amounts are in $, no currency code needed), the status pills never wrap (`white-space:nowrap`), the check-in button says `Done`, the vote count says `1 vote` / `3 votes`, the chart's average label is `Avg 550`, dates in the chart are `9/13`; placeholders are short words (`Optional note`).

## Make it yours

| Where | What to change |
|---|---|
| header `.org` / `h1` / `.lead` | shop name, who uses it, the one-line promise |
| `index.html` `OPTIONS` | the poll choices (`k` is the value stored in `signup.choice`; `t` / `d` are the label and the one-line note) |
| `index.html` `ST` / `PENDING` / `DONE` | the status words your tables use and which colour each gets; `PENDING` is the one that holds stock, `DONE` the one that closes a to-do |
| the filter arrays in `renderWorkbench` / `renderBoard` | the status words offered as filters (they must be the values in `todos.status` / `weekly_board.status`) |
| the three forms | `name` attributes match the columns of `order_intake` / `checkins` / `signup`; to collect one more field, add a column to the table and an input with the same `name` |
| section `#agent` | the three example sentences the owner says to their assistant |
| footer | platform mark and name (verbatim `MARK_SVG` / `PRODUCT_EN` from `core/space/src/theme.ts`) + one line on when to use it; delete it for your own shop |
| `manifest.json` | `title` / `share.*`; when sections change, `sections`, the page `id`s, the `nav.tabs` links and `data-track-section` change together |

Ground rule: a table with append open should hold only columns that can be public — reviewed reader rows appear in `GET ./_data/<table>.json`, readable by anyone who can open the page. Don't collect phone numbers in `order_intake` (the page promises a follow-up by email or WhatsApp instead). Each appended row records only device, time and version — it is not a real-name record.

## How to publish

```bash
# 1. Create a page (MCP create_proposal, allow_copy=true) → proposal_id; 2. create the nine tables (set_data above; move checkins days to this week)
# 3. Push the whole directory (with the _data/ fallback):
node core/space/cli/space-cli.mjs push core/space/examples/en/livepage-app \
  --proposal <proposal_id> --manifest core/space/examples/en/livepage-app/manifest.json --hold
# 4. Preview: the four figures, the chart, the product list and the check-in grid are filled; 5. publish_version; 6. set_visibility public (sample) — for your own shop, passcode or private
# 7. Check: submit each of the three forms once on a phone → list_data_rows table=order_intake / checkins / signup each read one row back
```

Changing data does not republish the page: `get_data` for the revision → `set_data` to replace the table. Changing the page: edit here → `push … --hold` → preview → `publish`.

Acceptance screenshots: `node --import tsx examples/verify-shots.mjs en/livepage-app` (QUALITY.md §7 / §9.4).
