# Platform Tour Interactively tour the Common Fabric platform as a user, exercising core workflows through browser automation. You are LARPing as a curious user exploring the platform. Narrate what you see as you go — the user is watching. ## Usage ``` /tour ``` If no URL is provided, default to `http://localhost:8000`. Examples: - `/tour` — local dev server - `/tour https://toolshed.saga-castor.ts.net` — production ## Setup 1. **Determine the base URL.** Use `$ARGUMENTS` if provided, otherwise `http://localhost:8000`. Trim any trailing slash. 2. **Compute the space slug.** Use today's date in `YYYY-MM-DD` format plus `-claude` (e.g. `2026-02-16-claude`). 3. **Create a screenshot directory.** Create `./tour-screenshots/YYYY-MM-DD/` for this run. All screenshots go here with numbered prefixes and descriptive names (`01-space-home.png`, `02-new-note.png`, etc.). 4. **Detect browser automation tool.** Run `which agent-browser` to check availability. **If `agent-browser` is available:** Use it throughout. Open with `agent-browser --headed open {url}` so the user can watch. Use `agent-browser snapshot -i` to discover interactive elements, interact via `@ref`s, and `agent-browser screenshot {path}` to capture. **If `agent-browser` is NOT available:** Fall back to the Playwright MCP tools. Use `browser_navigate`, `browser_snapshot`, `browser_click`, `browser_type`, and `browser_take_screenshot` instead. The same principles apply — snapshot before interacting, use refs from snapshots, re-snapshot after DOM changes. 5. **Navigate to the space.** Open `{base-url}/{space-slug}`. Take an initial snapshot and screenshot. Describe what you see. ## Shadow DOM Helpers These reusable JS eval snippets help navigate shadow DOM. To avoid shell quoting issues with `agent-browser eval`, write JS to a temp file first, then run `agent-browser eval "$(cat /tmp/tour-helper.js)"`. ### Find Details Disclosures ```js (() => { function findInShadow(node, depth) { if (depth > 10) return []; const results = []; if (node.shadowRoot) { const details = node.shadowRoot.querySelectorAll('details'); details.forEach((d) => { const s = d.querySelector('summary'); if (s) { const rect = s.getBoundingClientRect(); results.push({ text: s.textContent.trim().substring(0, 50), open: d.open, x: rect.x + rect.width / 2, y: rect.y + rect.height / 2, }); } }); node.shadowRoot.querySelectorAll('*').forEach((child) => { results.push(...findInShadow(child, depth + 1)); }); } return results; } const all = []; document.querySelectorAll('*').forEach((el) => { all.push(...findInShadow(el, 0)); }); return all; })() ``` ### Find Cell Link Chips ```js (() => { function findCellLinks(node, depth) { if (depth > 10) return []; const results = []; if (node.shadowRoot) { const links = node.shadowRoot.querySelectorAll("cf-cell-link"); links.forEach((l) => { const rect = l.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { const inner = l.shadowRoot ? l.shadowRoot.textContent.trim().substring(0, 60) : l.textContent.trim().substring(0, 60); results.push({ text: inner, x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }); } }); node.shadowRoot.querySelectorAll("*").forEach((child) => { results.push(...findCellLinks(child, depth + 1)); }); } return results; } const all = []; document.querySelectorAll("*").forEach((el) => { all.push(...findCellLinks(el, 0)); }); return all; })() ``` ### Find Shadow DOM Buttons Replace `BUTTON_TEXT` with the target text (e.g. "+"). ```js (() => { function findButtons(node, depth) { if (depth > 12) return []; const results = []; if (node.shadowRoot) { node.shadowRoot.querySelectorAll("button").forEach((b) => { if (b.textContent.trim() === "BUTTON_TEXT") { const rect = b.getBoundingClientRect(); if (rect.width > 0 && rect.y > 0 && rect.y < 800) results.push({ text: b.textContent.trim(), x: rect.x + rect.width/2, y: rect.y + rect.height/2 }); } }); node.shadowRoot.querySelectorAll("*").forEach((child) => { results.push(...findButtons(child, depth + 1)); }); } return results; } const all = []; document.querySelectorAll("*").forEach((el) => { all.push(...findButtons(el, 0)); }); return all; })() ``` ### Scroll Content Area ```js (() => { function findScrollable(node, depth) { if (depth > 8) return []; const results = []; if (node.shadowRoot) { for (const el of node.shadowRoot.querySelectorAll('*')) { if (el.scrollHeight > el.clientHeight + 50) { el.scrollBy(0, 400); results.push({ tag: el.tagName, scrollH: el.scrollHeight, clientH: el.clientHeight }); } results.push(...findScrollable(el, depth + 1)); } } return results; } const all = []; document.querySelectorAll('*').forEach(el => all.push(...findScrollable(el, 0))); return all.filter(r => r.scrollH > r.clientH + 50); })() ``` ### Scroll Content Area Up Same as above but use `el.scrollBy(0, -400)` instead of `el.scrollBy(0, 400)`. ## The Tour Work through these steps in order. At each step: - Take a snapshot to discover interactive elements - Interact using refs from the snapshot — never hardcode selectors - After each major action, re-snapshot (refs are invalidated by DOM changes) - Take a screenshot and save it to the screenshot directory - **Narrate what you see** — describe the UI, what changed, anything surprising - If a step fails, note the failure, take a screenshot of the error state, and continue to the next step ### Step 0: Register / Login Fresh spaces require authentication. You will see "Register" and "Login" buttons on the landing page. - Click **Register** to create a new identity - Click **Generate Passphrase** on the next screen - Click **I've Saved It - Continue** to complete registration If the space is already authenticated (you see the Patterns view with a header toolbar), skip this step. ### Step 1: Explore the Space Home You should now be on the authenticated space page. Describe the layout: the header breadcrumb, the "Patterns" heading, the "Notes" dropdown, any toolbar buttons, and the FAB (floating action button) in the bottom-right. Take a screenshot. ### Step 2: Create a New Note Find the **"Notes ▾"** dropdown button in the content toolbar area. Click it to reveal a menu with options like "New Note", "New Notebook", "All Notes". Click **"New Note"**. After the note view loads, take a screenshot. ### Step 3: Edit the Note The note has two parts to edit: **Title:** The title is inside nested shadow DOM: `x-root-view > shadowRoot > x-app-view > shadowRoot > x-body-view > shadowRoot > cf-render > shadowRoot > div > cf-screen > cf-vstack`. It will NOT appear in accessibility snapshots. 1. Write a JS eval to find the title span: ```js (() => { const rv = document.querySelector("x-root-view"); const av = rv.shadowRoot.querySelector("x-app-view"); const bv = av.shadowRoot.querySelector("x-body-view"); const cr = bv.shadowRoot.querySelector("cf-render"); const spans = cr.shadowRoot.querySelectorAll("span"); for (const span of spans) { if (span.textContent.trim() === "New Note") { const rect = span.getBoundingClientRect(); return { x: rect.x + rect.width/2, y: rect.y + rect.height/2, text: span.textContent }; } } return "title span not found"; })() ``` 2. Click at the returned coordinates to activate edit mode 3. A `cf-input` appears. Find the input inside it: ```js (() => { const rv = document.querySelector("x-root-view"); const av = rv.shadowRoot.querySelector("x-app-view"); const bv = av.shadowRoot.querySelector("x-body-view"); const cr = bv.shadowRoot.querySelector("cf-render"); const cfInput = cr.shadowRoot.querySelector("cf-input"); const input = cfInput.shadowRoot.querySelector("input"); input.focus(); input.value = "YOUR TITLE HERE"; input.dispatchEvent(new Event("input", { bubbles: true })); input.dispatchEvent(new Event("change", { bubbles: true })); return "set title: " + input.value; })() ``` 4. Press Enter to confirm **Body:** The body textbox (code editor) IS accessible via snapshot. Use `agent-browser snapshot -i` to find it, then `agent-browser type @ref "your text"`. If the ref is ambiguous (matches multiple), use the textbox that doesn't have `"Note title..."` as placeholder. Take a screenshot showing the edited note with the updated title in the breadcrumb. ### Step 4: Create a Second Note via Omnibox and Link It Open the Omnibot (click the FAB in the bottom-right). Ask it to create a new note and then append a link to it from the first note. For example: > Create a new note called "Related Thoughts" with some placeholder content. Then append a link to it inside my "YOUR FIRST NOTE TITLE" note. (Replace "YOUR FIRST NOTE TITLE" with whatever you titled the note in Step 3.) Wait for the LLM to use `createNote` and then `appendLink` (or edit the first note's content). Once done, navigate to the first note and verify a `[[📝 Related Thoughts (...)]]` wiki-link appears at the bottom of its content. Click the wiki-link to confirm it navigates to the second note. Take screenshots of both notes showing the link. ### Step 5: Return to Space Home Click the space name link in the breadcrumb (e.g. "2026-02-16-claude") to navigate back to the space home. Verify the notes you created appear in the Patterns list with their titles. The breadcrumb should show an updated item count. Take a screenshot. ### Step 6: Open the Chat Find the **"Open" button** (the FAB) in the bottom-right corner of the page and click it. A chat panel will appear in the bottom-right with: - An input field: "Ask the LLM a question..." - A "Send" button - A model selector (defaults may vary — typically Claude Sonnet 4.5 or Opus 4.1) - "Tools" button showing available tool count - An "Expand" button to make the panel larger Click Expand for better visibility. Take a screenshot. ### Step 7: Reference the Note in Chat 1. Click into the chat input textbox 2. Type `@` to trigger the mention autocomplete — a dropdown list will appear 3. Find and click your note (e.g. "📝 2026-02-16") in the dropdown 4. The mention will be inserted as a markdown link in the input 5. Type after the mention: " What does this note say?" 6. Click Send and wait ~15 seconds for the LLM to respond The LLM should use the `read` tool and quote the note's content back. Take a screenshot of the response. ### Step 8: Ask for the Pattern Index In the chat, type: "Can you list the available patterns from the pattern index?" and send. Wait ~20 seconds. The LLM should use the `listPatternIndex` tool and return categorized lists of available patterns. Take a screenshot. ### Step 9: Launch a Counter Pattern In the chat, type: "Please launch the counter pattern with an initial value of 5" and send. Wait ~25 seconds. The LLM should: 1. Use `fetchAndRunPattern` to compile and run the counter 2. Use `navigateTo` to navigate to the new pattern The counter view shows: a heading "Simple Counter", a large number (5), text like "Counter is the 5th number", and "- Decrement" / "+ Increment" buttons. Take a screenshot. Note: the counter pattern may not appear in `listPatternIndex`, but the LLM can still launch it via `fetchAndRunPattern` using the path `counter/counter.tsx`. **Testing interactivity:** The `cf-button` component wraps a native `