← BrightSiteCONTENT HISTORY

Update to BrightSite

Snapshot Sep 30, 2026 · 22:57 UTC · version 1.1.0

Collection source: not recorded for this historical snapshot.

WHAT CHANGED · RULE-BASED ANALYSIS

First saved snapshot

No earlier snapshot is available to establish a change.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "name": "visual-editor-authoring",
  "description": "Author BrightSite pages, components, and layouts whose content is editable in the visual editor — so a non-technical user can click and change text, images, links, and component props without a second pass. Use when creating or editing a page/component/layout via the BrightSite MCP (create_page, update_page, create_component, create_layout), when building a site or template, or when the user says \"make this editable,\" \"the client should be able to edit this,\" \"build the site so they can change it themselves,\" or \"use the visual editor / components properly.\" Load this BEFORE writing HEEx, not after.",
  "included_files": [
    {
      "relative_path": "editability-contract.md",
      "size_in_bytes": 34226
    }
  ],
  "skill_md_contents": "---\nname: visual-editor-authoring\ndescription: Author BrightSite pages, components, and layouts whose content is editable in the visual editor — so a non-technical user can click and change text, images, links, and component props without a second pass. Use when creating or editing a page/component/layout via the BrightSite MCP (create_page, update_page, create_component, create_layout), when building a site or template, or when the user says \"make this editable,\" \"the client should be able to edit this,\" \"build the site so they can change it themselves,\" or \"use the visual editor / components properly.\" Load this BEFORE writing HEEx, not after.\n---\n\n# Author Visual-Editor-Editable Content\n\nBrightSite content authored as plain HEEx renders fine on the public site but is often\n**not editable in the visual editor** — the user can't click an element to change its text,\nswap an image, or edit a component's props. They then have to come back and ask for it to\nbe \"made editable.\" This skill makes you author it correctly the first time.\n\nRead **[editability-contract.md](editability-contract.md)** (in this skill's directory) in\nfull before authoring. It is the source of truth for every rule below. This file is the\nhow-to; the contract is the reference.\n\n## When to use this\n\n- You are about to call `create_page`, `update_page`, `create_component`,\n  `update_component`, or `create_layout` and the content should be client-editable.\n- You're building a whole site, a template, or a default/starter layout.\n- The user wants non-technical end users (small-business owners) to self-edit content.\n\nIf you're only auditing or fixing an **existing** site for editability, use the\n`visual-editor-audit` skill instead.\n\nIf you're doing a **full redesign or large rebuild** that shouldn't be visible on the live\nsite until it's ready, build it on a **staging site** — load the `staging-redesign` skill\nand pass `site: \"staging\"` on the authoring calls below (it composes with every rule here).\n\n## The rule that prevents 90% of second passes\n\n> The editor sees exactly two editable things: elements tagged `data-bs-edit=\"field\"`, and\n> `component()` instances whose component has a **non-empty `props_schema`**. Raw HTML is\n> invisible.\n\nSo before you write any HEEx, decide for each meaningful piece of content: is it a\n`data-bs-edit` field, part of a component's `props_schema`, or a collection? If it's none\nof those, the user can't edit it.\n\n## Workflow\n\n### Step 1: Read the contract\n\nRead `editability-contract.md` in this directory. Internalize the decision rule and the\nfour silent traps.\n\n### Step 2: Plan the editable surface before writing HEEx\n\nFor the page/component you're about to build, list the content the user will want to\nchange, and assign each a mechanism:\n\n- **One-off scalar** (a heading, a single image, one button) → inline `data-bs-edit`.\n- **A reused section** (hero, CTA band, footer block) → a `component()` with `props_schema`.\n- **Repeating content** (pricing tiers, gallery, team, FAQ) → a collection (component\n  `item_schema`, or inline `data-bs-collection`/`data-bs-item` **+ `data-bs-item-schema`**\n  for the rich editor). Never a bare `:for`. Auto-number ordered items with `bs_index(idx)`.\n  Put the `data-bs-collection`/`data-bs-item`/`data-bs-edit` markers on the rendered DOM too.\n- **Shared design, per-page data** (related-card grids, page-specific lists) → a reusable\n  component bound to a **page `@params` collection** (`%{cards: @params.related_cards}`),\n  with the data + `item_schema` in the page's `params_schema`. **Never** pass the editable\n  items as an inline literal in the `component(...)` call — the props panel can't read a\n  literal, so the fields show empty.\n\n### Step 3: Author with the markers\n\nApply the contract. The high-frequency rules:\n\n- Add `data-bs-edit=\"field_name\"` to every editable element.\n- Use `data-bs-edit-type=\"richtext\"` for any text with `<br>` or inline formatting — and\n  inside richtext use inline `style=\"…\"`, **never Tailwind classes** (they're dropped on\n  save).\n- Internal links: `href={page_url(\"page_id\")}` **and** `data-bs-edit-type=\"page\"`; for a\n  CTA use `data-bs-edit-type=\"button\"` (label + link edited as one grouped button).\n- Inline collections with a known item shape: add `data-bs-item-schema='{…}'` to the\n  `data-bs-collection` wrapper for the rich (collapsible / drag-reorder / typed) editor.\n- **Any collection — including one rendered inside a component — needs the markers on its\n  rendered DOM:** `data-bs-collection` on the container, `data-bs-item={idx}` per item,\n  `data-bs-edit` per field. A populated `item_schema` alone does NOT give canvas\n  hover/click-select; without the markers the panel shows empty fields and the cards don't\n  highlight. Never a bare `:for` with `{item.field}` and no markers.\n- Ordered lists: render the number with `bs_index(idx)` (auto-renumbers) — don't store it.\n- Link/CTA props in reusable components: resolve the href through the small `resolve` helper\n  (page ID → `page_url`, else raw) so the prop accepts a page ID *or* a literal\n  path/anchor/tel. (See the contract's \"smart `resolve` href helper.\")\n- Elixir `\"\"` is **truthy**: guard `:if`/`||` on non-empty (`x && x != \"\"`), or a CTA with an\n  empty label still renders / a fallback never fires.\n- `<a>` with an icon/child markup: put `data-bs-edit-target` on the text child.\n\n### Step 4: For components, do BOTH calls\n\n`create_component` does **not** accept `props_schema`. A component without it has zero\neditable fields. Always:\n\n1. `create_component(...)` with the `heex`.\n2. `update_component(..., props_schema: {…})` to define the editable props — each with a\n   `label`, `type`, `default`, and an integer `order`.\n\nA `type:\"page\"` prop's `default` must be a page **ID** (not a path).\n\n### Step 5: Verify before reporting done\n\nRun the pre-ship checklist from the contract. Concretely, the page must have ≥1 editable\nnode, every reused section must be a component with a non-empty `props_schema`, every\nrepeating block must be a collection, and internal links must use `page_url` +\n`data-bs-edit-type=\"page\"`. If you can, re-fetch with `get_page` / `get_component` and\nconfirm the markers are present in the stored HEEx and the `props_schema` is non-empty.\n\n## Anti-patterns to avoid\n\n- **Shipping a page of clean `<div>`s with no `data-bs-edit` and no components.** It looks\n  done and edits nothing. This is the #1 cause of the second pass.\n- **Creating a component and stopping** — leaving `props_schema` at `{}`. The editor shows\n  \"no editable properties.\" Always follow `create_component` with `update_component`.\n- **Tailwind classes inside a richtext field.** `class=\"text-teal-500 italic\"` is silently\n  dropped on save. Use `style=\"color:#14b8a6; font-style:italic;\"`.\n- **`type:\"text\"` on text containing `<br>` or `<span>`.** The first edit flattens it to\n  plain text. Use `richtext`.\n- **Bare `:for` loops for editable repeating content.** One opaque block — no add/remove/\n  reorder. Use a collection.\n- **A component collection with `item_schema` but no markers on the rendered loop.** The\n  panel shows empty placeholder fields and the canvas cards don't hover/select. Add\n  `data-bs-collection`/`data-bs-item`/`data-bs-edit` to the rendered DOM — the `item_schema`\n  is necessary but not sufficient.\n- **Passing editable items as an inline literal** in `component(\"x\", %{cards: [...]})`. The\n  panel reads the instance, not the literal → empty fields. Bind to `@params.<collection>`.\n- **Hardcoded `href=\"/slug\"` for internal links.** No page picker, breaks on slug change.\n  Use `page_url(...)` + `data-bs-edit-type=\"page\"`.\n- **Trusting Elixir truthiness with empty strings.** `\"\" || @fallback` is `\"\"`, and\n  `:if={@label}` is true when `@label == \"\"`. Guard on `x && x != \"\"`.\n- **Props with no `order` key.** They list in random order; users notice. Set `order` on\n  every prop.\n\n## The dangerous step\n\nThe component two-call sequence (Step 4). It is the easiest thing to get wrong because\n`create_component` succeeds and looks complete — but a component with an empty\n`props_schema` is locked. Never report a component done until `update_component` has set a\nnon-empty `props_schema`.\n\n## Tools used\n\n- `mcp__brightsite__create_page` / `mcp__brightsite__update_page` — author pages; put\n  `data-bs-edit` markers in `heex`, repeating content in `params_schema` collections.\n- `mcp__brightsite__create_component` then `mcp__brightsite__update_component` — the\n  two-call sequence to create an editable component (`props_schema` only on update).\n- `mcp__brightsite__create_layout` / `mcp__brightsite__update_layout` — same markers apply\n  to layout content.\n- `mcp__brightsite__get_page` / `mcp__brightsite__get_component` — re-fetch to verify\n  markers and a non-empty `props_schema` before reporting done.\n\n## Uploading images to the media library\n\nTo reference a real image in a page (`media(file_id)` / `media(file_id, aspect: \"16:9\")`\n/ `media_url(file_id)`), the file must first exist in the org's media library. Use the\n**two-step presigned flow** — do NOT use `mcp__brightsite__upload_file` (the local-path\nconvenience tool returns a bare `Internal error`):\n\n1. `mcp__brightsite__request_upload` `{account_id, file_name, content_type}` → returns\n   `{file_id, upload_url}` (a presigned Cloudflare R2 URL, ~2h expiry).\n2. HTTP **PUT** the raw bytes to `upload_url` with a matching `Content-Type` header:\n   `curl -X PUT -H \"Content-Type: image/jpeg\" --data-binary @file.jpg \"$URL\"` → expect **200**.\n3. `mcp__brightsite__complete_upload` `{account_id, file_id, file_name, name,\n   content_type, width, height, size}` → creates the DB media record and returns\n   `{id, thumb_url, md_url, lg_url, orig_url}`. The returned `id` == the `file_id` you\n   passed; use it in `media(...)`.\n\nGotchas: use a `.jpg` extension, never `.jpeg` (the CDN signed-URL pipeline 404s on\n`.jpeg` objects). Resize huge originals (3500px+) down to ~1400–2000px before the PUT so\nuploads stay fast. For blog feature images, the same `id` is what you pass as\n`feature_image_id` (preferred over `feature_image_url`, which is for external URLs).\n"
}

SHA-256: 83414836eac6a6fdf9effd754a49c9e038e0e2f15d325dffc9e583d8e3c4313b