README: look & layout control, guardrails

This commit is contained in:
leora 2026-10-08 19:41:32 +08:00
parent 2fdd46af9d
commit a47bd648ac

View File

@ -35,6 +35,38 @@ The server stays small on purpose. It does three things:
`show_hero`, `show_cards`, `show_listing_form`, `show_compare`, `show_checkout`, `show_cash_meetup`, `show_hero`, `show_cards`, `show_listing_form`, `show_compare`, `show_checkout`, `show_cash_meetup`,
`show_receipt`, `toast`, `clear_panel`. `show_receipt`, `toast`, `clear_panel`.
### Look and layout (agent-controlled, per visitor)
The agent can restyle the whole page from chat with one action, `set_theme`. Its payload is a JSON theme spec:
design tokens applied as CSS variables. The model never sends raw HTML or JS. The server checks every value
(`market/theme.py`), and the browser applies the result (`static/theme.js`).
| Area | Spec keys |
|---|---|
| Background | `background.kind` solid/gradient/pattern (dots, grid, stripes, diagonal, checks, waves)/image (only images.unsplash.com, picsum.photos, upload.wikimedia.org), `colors`, `angle` |
| Colours | `mode` light/dark, `palette.{bg,surface,card,text,muted,accent,border}` |
| Type | `font`, `headingFont` (19 curated web-safe and Google fonts), `fontScale` 0.75 to 1.75 |
| Shape and spacing | `radius` 0 to 32, `density` compact/normal/comfy, `cardStyle` flat/outlined/elevated/glass |
| Layout | `layout` grid/list, `columns` 0 to 6, `sections.order` / `sections.hidden` (hero, chips, bullets, results, footer) |
| Copy | `copy.{logo,headline,subhead,footer,chips[],bullets[]}` |
| Chat | `chat.{position,width,height,bg,accent,text}` |
| Motion | `animations` on/off |
| Presets | `preset`: retro, minimal, neon, pastel, high contrast, sunset, ocean, plus `reset` |
| Escape hatch | `customCss`, sanitised and scoped (details below) |
**Per visitor.** The theme lives in the browser's `localStorage`. The server never stores it, so one visitor's restyle
never changes the page for anyone else. **Reset look** in the header, or saying "reset the look" in chat, restores the default.
**Guardrails.**
- `customCss` is sanitised before it's applied:
- The server drops `@import` and every other at-rule, `expression()`, `javascript:`, `behavior`, HTML, `!important`, custom properties, and negative margins.
- It neutralises `url()` pointing anywhere outside the image allowlist.
- Only an allowlist of properties survives. Positioning, z-index, `content` and transforms aren't on it.
- Rules are scoped under `#page`, and the browser checks them again.
- The CSS uses cascade layers in the order `base < user < protected`. The TEST DEMO banner, the agent badge, and the checkout, cash and receipt panels (fee lines, totals, the test-card notice and the card inputs) live in `protected`. No theme can hide, recolour or shrink them.
- Protected panels use the theme's surface colour as a solid fill, with black or white text, whichever contrasts more, so they always clear 4.5:1. The banner and badge use fixed high-contrast colours.
- Text sitting directly on the page background switches to readable ink. If no ink clears 4.5:1 against every gradient stop, it gets a scrim behind it.
- If the model is unavailable, the built-in agent understands the common look commands: presets, light/dark, background colours, gradients and patterns, accent and text colour, fonts, bigger or smaller text, corners, density, card style, list/grid, columns, chat position and size, hiding or showing sections, headline, logo and subtitle, animations, and reset.
### Fees ### Fees
- **Online payment:** a facilitation fee of `AGENT_MARKET_FEE_BPS` basis points. The default is 500, which is 5%. - **Online payment:** a facilitation fee of `AGENT_MARKET_FEE_BPS` basis points. The default is 500, which is 5%.
The buyer pays it on top of the price, the seller gets the full price, and checkout shows the fee on its own line. The buyer pays it on top of the price, the seller gets the full price, and checkout shows the fee on its own line.
@ -106,6 +138,8 @@ curl -s $BASE/api/actions -H 'X-Agent-Id: agent:my-bot' -H 'Content-Type: applic
python3 tests/e2e_http.py http://127.0.0.1:8795 # HTTP end-to-end (23 checks) python3 tests/e2e_http.py http://127.0.0.1:8795 # HTTP end-to-end (23 checks)
bash tests/llm_proxy_check.sh # fake LLM: 429 chain, cooldown, fallback, key handling, rate limit bash tests/llm_proxy_check.sh # fake LLM: 429 chain, cooldown, fallback, key handling, rate limit
python3 tests/nl_check.py http://127.0.0.1:8795 # real model: 6 natural-language prompts python3 tests/nl_check.py http://127.0.0.1:8795 # real model: 6 natural-language prompts
python3 tests/test_theme.py # theme spec + customCss sanitiser
NODE_PATH=... node tests/theme_check.js BASE ./screenshots # chat restyles, guardrail contrast, fresh session, reset (REQUIRE_LLM=1 to insist on the model)
NODE_PATH=/path/to/node_modules node tests/browser_e2e.js http://127.0.0.1:8795 ./screenshots # headless Chrome NODE_PATH=/path/to/node_modules node tests/browser_e2e.js http://127.0.0.1:8795 ./screenshots # headless Chrome
``` ```
@ -113,5 +147,6 @@ NODE_PATH=/path/to/node_modules node tests/browser_e2e.js http://127.0.0.1:8795
- Agent identity (`X-Agent-Id`) isn't verified, so anyone can claim any id. - Agent identity (`X-Agent-Id`) isn't verified, so anyone can claim any id.
- There's no real messaging between buyer and seller for cash meetups. - There's no real messaging between buyer and seller for cash meetups.
- The JSON file store is single-process, and rate limits live in memory, so a restart resets them. - The JSON file store is single-process, and rate limits live in memory, so a restart resets them.
- Free OpenRouter models have small daily request caps and get rate-limited upstream. Once those run out, the rule-based agent answers. - Free OpenRouter models have small daily request caps: 50 a day on a key with no credit. They also get rate-limited upstream. When the daily cap hits,
the server parks the whole model chain until OpenRouter's reset time, and the built-in agent answers, labelled as such.
- The mock payment provider is the only one. A real provider would need its own test keys and webhook secret. - The mock payment provider is the only one. A real provider would need its own test keys and webhook secret.