Guida al progetto
Il progetto di ogni bozza ha un CLAUDE.md nella radice: la mappa dei file e le regole dei componenti modificabili. Un'AI che lavora dentro il container della bozza lo legge lì; una collegata con l'MCP dell'editor il container non ce l'ha, quindi sta qui. I comandi per guardare il sito e controllarlo sono in Il comando wr, e ognuno è un tool dell'MCP dell'editor. La guida è in inglese, com'è nel progetto.
A React + Vite + Tailwind v4 site that the user edits in two ways at once: visually, in the Webround editor (the preview of this project runs inside it), and in code, here. The code is the source of truth: whatever the editor does ends up as a change to the files below, and whatever you change here shows up in the editor. Vite is already running on port 5173 (HMR): do not start another dev server.
Where things are
src/pages/*.tsx— one file per page,export default function Page(). The pages are listed insrc/config/routes.json:{ "about": { "component": "About" } }("lazy": trueloads it on demand;"header": false/"footer": falsehides the shared header/footer on that page). The key is the URL (homeis/). Every language also answers under/<lang>/…(src/config/langs.json).src/layout/Header.tsx,src/layout/Footer.tsx— the header and footer of every page (export by name). Edit them once, they change everywhere.src/layout/HeaderShop.tsx,src/layout/FooterShop.tsx— the places where a shop attaches (cart and account in the header, the shop's links in the footer). They draw nothing until the shop is added, which replaces them: keep<HeaderShop />in the header and<FooterShop />in the footer, wherever you move things.src/components/*— React components. A plain one has no editor magic and noWr*element in it (the editor does not read it); one withWr*elements is a shared component: see "Splitting a page" below.src/config/pages/<page>.json— values the editor edits and the translations of a page, keyed by element id. The header, the footer and each shared component have theirs insrc/config/pages/_shared/<name>.json(header.json,footer.json,shop-toolbar.json…).src/config/shared.jsonlists the shared components.src/theme.css— the theme (--wr-color-*variables);src/index.cssmaps them to Tailwind tokens. The site has ONE (light) theme: a dark theme exists only iftheme.csshas a.darkblock (same names, switched by thedarkclass on<html>; the editor's Theme panel turns it on and off).theme.cssalso maps each--wr-color-<name>to a class (@theme inline):index.cssonly imports it. To let visitors switch themes, putthemeToggleon theWrNav(it shows only if the site has a dark theme).
The editable components
Import them from @webround/core — always, never from @webround/editor (it is swapped in by the dev server while editing and
exports the same names): WrSection (a container; tag="div|section|footer|…"), WrText (a container: tag="div|span", div by default; what is inside is the text's own HTML, so a heading is html="<h1 class='editor-heading-h1'>…</h1>", a paragraph <p class='editor-paragraph'>…</p>, never tag="h1"), WrLink,
WrButton (their text is the children), WrImage, WrVideo, WrIframe, WrNav (render prop: {(items, lang) => …}).
Every one of them takes a literal, unique id (id="hero-title"): kebab-case, says what the element is, never computed, never
repeated on the site (the one exception: inside a shared component, an element repeated by a .map has the same literal id every time, and a
change of style to one is a change to all). The editor finds, selects, moves and deletes elements by id; an element without an id cannot be edited there.
Use WrLink for internal links (it adds the language prefix).
Splitting a page into components
A page that has grown is split into shared components: one section of the page each (a toolbar, a list, a gallery), in its own file, with its own texts. To make one:
- The file exports a component that returns
<WrShared name="shop-toolbar"> … </WrShared>; the first element inside is aWrSection(or anotherWr*element) with a literal id: that is the component's root. src/config/shared.jsonsays where it is:{ "shop-toolbar": "components/shop/ShopToolbar.tsx" }(lowercase words with hyphens; one file for each name).- Its texts and translations are in
src/config/pages/_shared/shop-toolbar.json, keyed by the ids of the elements in that file. The editor reads the page, then each shared component the page uses (also those used by another shared component), each with its own JSON.
The rules that keep the editor able to read it (wr page-outline <page> lists what it finds: if an element is missing there, one of these is why):
- A shared component is given data and functions, never elements. An element given as a prop or as children is drawn under the scope of the component that receives it, so it looks for its text in that component's JSON, not in its own.
- Never take the focus by yourself: no
autoFocus, no.focus()when something is drawn. The editor writes a text with the focus on it, and a screen that takes it makes working there impossible. When what the visitor pressed goes away and the next thing is where they should be (the form that replaces the one they sent), useuseFocusWhenLost(src/lib/focus.ts): it moves the focus only if it was lost, never from something in use. - Write the elements in the
return, not in a variable (let body = <…/>is not read). Conditions (&&,a ? b : c) and.mapare read. - A component declared in the same file as another is not read: one shared component, one file. A plain component (no
WrShared) must hold noWr*element. - Code above the
<WrShared>of a component runs in the page's scope: a hook that reads one of the component's own texts (useWrPage) finds nothing there. Read what was drawn instead (aref), or ask the page. - What the catalog says (a product's name, price, description) is not a text: put it in a plain element (
<h2>,<p>) inside aWrSection, whose style the editor changes. AWrTextwould let someone write over it, and the same text would show for every product.
What goes where
- Structure, layout, classes, links, sources → in the code, as plain JSX attributes. Nest as you like: the editor does not validate
structure (a
WrSectioninside aWrSectionis fine).WrText,WrImage,WrVideo,WrIframeandWrNavtake no children. - Text you write in the code (
html="…"/ children) is the default. The JSON holds what the editor changed or translated, and wins over the code: if a text "does not change", look for its id in the page's JSON. - When you delete an element, delete its key (and its children's) from the JSON too, or the old text comes back if the id is reused.
- Nothing structural in the JSON, and no new fields there by hand: it is the editor's, for what the code cannot say (translations).
- What the code generates (
.map, conditions, render props) shows on the page but cannot be moved or edited from the editor, except the elements a shared component repeats (see "Splitting a page").
Menu entries are translated too: WrNav's items are { "label": { "default": "…", "en": "…" }, "href": "/…" }. To change them from outside use wr node-set <nav id> --key items --value '[{"label":"About us","href":"/about"}]' --lang en (the whole list, labels in that one language: the other languages are kept, entries are matched by href).
SEO (search engines and social networks)
Title, description, sharing picture, noindex of a page live in its JSON under _seo (translated like any text); what is the same for
the whole site (public address siteUrl, siteName, titleTemplate like %s | Acme, favicon, default picture, extra sitemaps, the Google Search Console verification code searchConsole) lives in
src/config/seo.json. Do not write <title>, <meta> or canonical/hreflang links in the code: every page gets its tags from this
data automatically. Change it with wr seo-set <page> --title … --description … (add --lang en for another language), wr seo-site-set --site-url https://www.example.com, and read it with wr seo-get [page]. wr seo-suggest \<page\> [--lang en] proposes a title and description from what the page says (nothing is written). The favicon is wr seo-site-set --favicon /favicon.png.
The Search Console code: wr seo-site-set --search-console <code or the whole <meta> tag Google gives> (only the code is kept; it is put in the <head> of index.html as it is served and built, so it is in the HTML a crawler gets: do not write the tag in index.html yourself).
Structured data (JSON-LD) and the overrides: wr seo-set <page> --json-ld '[{"@type":"FAQPage","mainEntity":[]}]' [--lang en] (a list of objects with an @type; the site's own go with wr seo-site-set --json-ld … and are added to every page), --robots "noindex, follow", --og-title/--og-description/--og-image/--og-type for what social networks show, --twitter-card/--twitter-site/--twitter-creator, and --meta '[{"name":"author","content":"Jane"}]' for any other <meta>.
public/robots.txt, sitemap-index.xml and sitemap-pages.xml are generated (and kept up to date by themselves) once the site has a
siteUrl: do not edit them. The index cites the shop's sitemaps (products, collections) by itself when the site has the product/[slug] and shop/[slug] pages: the server writes them every day and nobody attaches them by hand (--extra-sitemaps is for any other sitemap). wr check
warns, once per problem and saying in which languages it happens, about pages without a title or description, texts longer than search results show, links to pages that do not exist (menu entries too), JSON values of elements that are no longer in the code (wr json-prune removes them; it refuses if some element computes its id), and a site without favicon or public address. Accessibility is checked too (wr check): valid ARIA attributes and roles (aria-label on a plain div is ignored: use a section, nav or a role), links that say only "click here", frames without a title, form fields without a label, buttons that do nothing, clickable things the keyboard cannot reach, videos that start by themselves. Things only the drawn page shows (contrast WCAG AA 4.5:1, pressable things under 24px, text under 12px, text that is cut when spaced out, sideways scrolling on a phone) are not in wr check (it reads the code): they are measured on the drawn page, by the editor in its preview and by wr inspect / wr diagnose in a browser, which list the same problems for the page they open (measured says whether it could be done; a page and a screen size at a time: --viewport mobile for the phone). The layout already wraps every page in a <main>: do not write one (WrSection tag="main"). Several <nav> each need an aria-label. Never remove the focus outline (outline-none) without a focus-visible: ring in its place; give animations motion-reduce:animate-none; give personal-data fields their autoComplete (email, given-name, tel…). A person can silence a problem on purpose (src/config/checks.json): do not un-silence or add to that list yourself. A heading made inside a text counts as a heading of the page. An address that leads to no page takes the visitor to the home page.
Style
Tailwind, mobile-first: the base classes are the phone, md: the tablet, lg: the desktop. Colours only from the semantic tokens
(bg-page, text-fg, text-muted, text-subtle, bg-surface, bg-surface-2, border-line, bg-accent, hover:bg-accent-hover,
text-on-accent, text-accent-text, bg-accent-soft, bg-inverse, text-on-inverse, and for what went wrong text-error, bg-error-soft, border-error): they follow the theme by themselves.
No hex colours, no dark: variants, no inline style for layout.
For sections that already follow all of this (hero, features, call to action, columns…) read what the editor inserts:
node_modules/@webround/editor/dist/blocks.js. Copy that style.
Working here
- Check the result: Vite prints errors in the terminal (
/tmp/wr-vite.log) and on the page. Before saying you are done, runnpm run check: it checks the types and the page's quality (images without alt text, headings out of order, links and buttons with no name, repeated ids), and tells you the file and line of each problem. Fix the errors; fix the warnings unless the site really wants them. - Look at the site:
wr diagnosesays what is wrong right now (the dev server's errors, the code's problems, what a real browser finds on the page).wr screenshot /about --viewport mobilesaves a picture and prints where: open it to see the result.wr helplists the rest. - Add packages with
npm install <pkg>. Do not editnode_modules/orvendor/(the@webround/*packages are installed from there). - Keep
src/layout/*returning ONE root element: the editor cannot add siblings to it nor remove it. - Start by reading the comments:
wr comment-listshows what the people who work on this site left on its elements ("make this shorter", "wrong photo"), each with the page, the element and what it was. Open comments are what was asked of you: do them, thenwr comment-resolve <id>so the next run does not do them twice. If you cannot, leave a comment on the element saying why (wr comment-add --page home --id hero-title --text "…"). Comments live in.wr/comments.json, never insrc/and never in the published site: do not move them there.
Changing the site from the terminal
The editor's own operations are commands, so you change a site the same way the editor does (and in every language): wr page-outline home
(what is on the page, with each text in each language), wr page-content home (every translated text and which languages are missing),
wr node-set --page home --id hero-title --key content --value "Hello" --lang en, wr node-insert --page home --block cta (wr blocks-list
shows the blocks), wr node-move, wr node-delete <id> --page home, wr page-create, wr langs-set, wr theme-set. wr help lists them all
with their parameters. A translated text is stored as { default, en… } in the page's JSON and the site's default language is called default:
wr node-set writes the language you ask for and keeps the others. Deleting an element also deletes its JSON. Prefer these to editing the JSON by hand.
Shared styles. When several elements must look the same, do not copy their classes: make a shared style, a name for a group of Tailwind classes kept
in src/theme.css (.s-card { @apply … }, in the components layer, so a utility on an element still wins). The element then has className="s-card", and
changing the style changes all of them. wr styleset-list shows the styles and where each is used; wr styleset-create --page home --id hero-card --name card
makes one from that element's classes; wr styleset-apply --page home --id other-card --name card puts it on another (it replaces that element's classes);
wr style-set --set card --property padding --value 6 changes it. To remove one: wr styleset-detach-all card (each element keeps how it looks), then
wr styleset-delete card. wr styleset-rename card hero-card renames one (the class changes everywhere it is used). Do not write @apply by hand with classes of the project's own: Tailwind rejects them and breaks the whole stylesheet.
What changes on hover, focus or press is the same command with a state: wr style-set --page home --id cta --property background-color --value accent-hover --state hover, and --state focus (what shows a person where they are) or --state active. Only colors, underline, shadow, opacity, size change and move can change with a state (wr style-catalog marks them). A color does not have to be the theme's: --value '#22c55e' writes bg-[#22c55e] (or hover:border-[#22c55e] with --state hover), which does not follow the theme. A gradient is a direction and its colors: --property gradient-direction --value to-br, gradient-from, gradient-via, gradient-to. Smooth changes and ready-made animations are properties too: --property transition --value colors --property duration, --property animation --value fade-in (also pulse, bounce, spin, ping, slide-up, zoom-in: they respect reduced motion by themselves; the first entry animation writes its keyframes in theme.css). To make a change hold on every screen instead of the one it names: --everywhere. To copy the style of one screen to another, or to make every screen share one list of classes: wr style-copy --page home --id hero --from md --to all.