Move each comparison (vs Playwright MCP, vs Playwright CLI, vs BrowserMCP,
vs Claude Extension) from inline tables on the homepage into dedicated
pages under docs/. Add a new in-depth "vs Chrome Direct CDP" page with a
screenshot of Chrome permission dialog that blocks autonomous agents.
Homepage comparison section now links to the individual pages instead of
showing inline tables. Sidebar has a new "Comparison" group with distinct
lucide icons per page: shield-alert, drama, terminal, puzzle, bot.
Session: ses_120bfd373ffex2vJTLlX06mQ83
- Create website/src/docs/remote-access.mdx with full remote access guide
(traforo tunnel setup, Docker/devcontainer, LAN, security, env vars)
- Add docs/remote-access to website navigation in docs.json
- Add "Remote access" section to skill.md with quick-start snippet
linking to https://playwriter.dev/docs/remote-access
- Converted original docs/remote-access.md content to MDX with
diagram code fences and Aside components
Session: ses_1257fc4baffeU7BhTtBImAFQ1a
- Add HeroSection component with VideoBackgroundShader (dot-grid fluid sim)
using hero-bg.mp4 video and orange dot color at 70% opacity
- Two CTA buttons: Chrome Extension (white) and GitHub (outlined)
- 4.7 star rating badge with "20k+ users" linking to Chrome Web Store
- Serif title: "let agents control your Chrome browser."
- "Learn more" arrow linking to #getting-started
- Replace old Hero/Logo block with Above + HeroSection in index.mdx
- Set rendering: static for prerendered HTML at build time
- Set primary color to #FF9500 (bright orange, matching screenshot labels)
- Update @holocron.so/vite to 0.21.0 and wrangler to ^4.101.0
- Fix spiceflow duplication via pnpm dedupe
Session: ses_12885eca3ffenoMavJE7csK9E1
Update the website dependency set before publishing Playwriter so the workspace starts from a clean tree.
- update @holocron.so/vite to 0.16.0
- update spiceflow RSC build to 1.26.0-rsc.1
- update wrangler and workerd through the lockfile
- allow workerd as an approved built dependency for pnpm
Session: ses_unknown
Upgrade @holocron.so/vite from ^0.7.1 to ^0.8.0.
Use the new `<Logo>` component in the hero section instead of a plain
`<h1>`, and switch `<img>` to the framework `<Image>` component.
Add icon fields to the GitHub and Releases anchors in docs.json
(github, log). Fix docs.json indentation.
Add website/styles.css with Tailwind `@source` directive for the
new holocron vite plugin version.
The holocron docs framework has been published as @holocron.so/vite on npm.
The local package is no longer needed in this repo.
Removed files:
- holocron/src/ (vite plugin, components, styles, config, sync, mdx processing)
- holocron/AGENTS.md
- holocron/package.json, tsconfig.json, schema.json
Session: ses_1da6d3925ffeK4qAOv03OUyIfl
Mention .cpuprofile recording via CDP Profiler.* commands and
profano CLI for inspecting hot functions when vitals alone are
not enough to diagnose performance issues.
Session: ses_208943d84ffenqdbjxK0cO0iak
Add a generated website resource that shows how to profile a real site with Playwriter using CDP, Navigation Timing, and PerformanceObserver.
The new guide focuses on the questions people actually ask during performance work: whether rendering is good or bad, which requests dominate transfer cost, what usually blocks first paint or LCP, and what is most likely blocking interactivity. It also includes ready-to-run Playwriter commands plus reusable TypeScript examples so the profiling workflow can be copied directly.
Session: ses_22252b7b8ffee2VFXqmaCzA2NR
Chrome's --remote-debugging-port shows a persistent automation infobar
that agents cannot dismiss, interrupting users mid-workflow. Playwriter
runs silently with no blocking dialogs, enabling fully autonomous agent
operation without user intervention.
Added to both README.md (markdown table + blockquote) and
website/src/content/index.mdx (ComparisonTable component).
Distribute the three grid columns (TOC, content, aside) with
justify-content: space-between on the hero and content grids.
The gap-x is preserved as minimum spacing. When max-width is
increased later, sidebars will push toward screen edges while
content stays centered.
The website was using `import.meta.dirname` with relative paths to
resolve `publicDir` and `cacheDir`, which broke on Vercel because the
deployment directory structure differs from source layout:
- Source: `src/pages/index.tsx` → `../../public` → `website/public/`
- Vercel: `rsc/index.js` → `../../public` → `/var/public` (ENOENT)
Now uses spiceflow's `publicDir` export (from `virtual:spiceflow-dirs`)
which resolves correctly in both dev and Vercel deployments.
Also makes cache writes optional with try/catch since Vercel's
filesystem is read-only — images regenerate in-memory on each request.
- **spiceflow** bumped to `1.18.0-rsc.22` (adds `publicDir`/`distDir`)
- **image-cache.ts** wraps `mkdirSync`/`writeFileSync` in try/catch
- **index.tsx** imports `publicDir` from spiceflow instead of computing it
Previously, placeholder images for PixelatedImage had to be manually generated
by running a separate script (generate-placeholders.ts) before each build, and
passed explicitly as static Vite imports. The placeholder prop was never actually
wired up in the MDX content, so the fade-in effect wasn't working.
This replaces the entire approach with on-demand server-side processing:
**Architecture**
index.tsx is now a server component (no more 'use client'). During each render,
it calls buildImageManifest() which walks the mdast AST, finds all image srcs
(both <PixelatedImage src=...> JSX nodes and markdown  nodes), and
for each local public/ image:
1. Checks .cache/images/<name>.json — if present and mtime matches, returns cached data
2. On cache miss: reads dimensions with image-size, generates 64px-wide placeholder
PNG with sharp, encodes as base64 data URI, writes JSON cache
Placeholder injection uses two mechanisms:
- mdxComponents mapping: wraps PixelatedImage with PixelatedImageWithPlaceholder
that injects placeholder + dimensions from the manifest via closure. This handles
JSX elements like <PixelatedImage src=...> in MDX, including when nested inside
other components like <Bleed>. renderNode is NOT called for nested JSX children
in safe-mdx, so the component mapping is the correct injection point.
- renderNode: converts markdown images  to PixelatedImage with placeholder
**Cache format** (.cache/images/, gitignored)
{ mtime: 1234567890, width: 1280, height: 800, placeholder: 'data:image/png;base64,...' }
Invalidation is mtime-based — any change to the source image regenerates the cache.
On warm runs (no source change), the manifest is built entirely from fast JSON reads.
**Files**
- website/src/lib/image-cache.ts (new): buildImageManifest(), collectImageSrcs(),
getJsxAttr(), getAttrStringValue() — pure server utilities, no React
- website/src/pages/index.tsx: remove 'use client', make IndexPage async, move
rendering logic inside component body, add PixelatedImageWithPlaceholder wrapper
- website/src/components/markdown.tsx: update PixelatedImage JSDoc to reflect
the new auto-injection pattern
- website/package.json: add image-size dep, simplify build script (no more
generate-placeholders.ts pre-step)
- deleted: website/scripts/generate-placeholders.ts
- deleted: website/src/assets/placeholders/placeholder-*.png (6 files)
markdown.tsx is a 'use client' module. Any function exported from it cannot
be called in a server component (RSC boundary). flattenTocTree and the TOC
type definitions (TocNodeType, VisualLevel, TocTreeNode, FlatTocItem) are
pure functions with no hooks or browser APIs, so they belong in toc-tree.ts
which has no 'use client' directive.
Changes:
- toc-tree.ts: add all TOC types and flattenTocTree (self-contained, no imports
from markdown.tsx to break the circular dep)
- markdown.tsx: re-export the types from toc-tree.ts for backward compatibility,
remove the now-duplicated definitions
- toc-tree.test.ts: update import to come from toc-tree.ts directly; also update
6 stale inline snapshots (prefix spacing normalization from the prefix fix in
addPrefixes — '└ ' → '└─ ')
--code-bg was being used for both code block backgrounds and border colors,
which created semantic confusion. The usages in TocLink hover background,
SectionRow aside border, and SidebarBanner border are really border/surface
accents, not code backgrounds.
Rename to --border-subtle with adjusted values:
- light: rgba(0,0,0,0.02) → rgba(0,0,0,0.08) (more visible border)
- dark: rgba(255,255,255,0.05) → rgba(255,255,255,0.12) (more visible border)
Note: --code-bg as a name still exists as a concept in the codebase where it
refers to actual code block backgrounds, those usages are separate.
Inter variable font v4.1 (roman, 344KB woff2) and JetBrainsMono Nerd Font
Mono v3.3.0 (regular, 2.3MB ttf) are now served from /fonts/ in public.
Removes runtime dependency on raw.githubusercontent.com for font loading.
Non-root tree items (visualLevel > 0) now use 4px vertical padding
instead of 2px, giving nested headings more breathing room while
keeping root-level items compact.
- Add FullWidth marker component that spans both content and aside grid
columns (lg:col-[2/-1]). Extracted at parse time like Aside/Hero.
FullWidth sections get my-5 vertical margin to match content rhythm.
- Fix auto-expand: clicking an expandable TOC item now expands it.
Previously only ancestors were expanded, not the item itself.
- TOC tree prefix characters use 1.2em font size with negative margin
so they don't inflate line height. Prefix indentation reduced to 1
char per level.
- Use FullWidth for the hero screenshot in index.mdx as demo.
- Add SVG ChevronIcon component for expandable items, shown on the right
- Toggle expand only on chevron click, not the whole row
- Remove width: fit-content from aside so it fills its column
- Add 6px margin between root-level TOC groups
- Use regular font weight (400) for all tree items
- Remove lowercase text-transform from tree items
- Remove line-height override from TocLink (inherits from parent)
Removed fontSize from the shared proseStyle object and added it to all
slot-main containers instead. Prose elements (P, OL, List) now inherit
font-size from their parent container rather than setting it explicitly,
so inside the aside they respect --type-toc-size (13px) instead of
overriding it with --type-body-size (14px).
- SectionRow aside and SidebarBanner now use a border instead of bg fill
- font size changed from --type-toc-size (13px) to --type-body-size (14px)
- line height aligned with prose style (1.6)
The 80ms setTimeout debounce blocked the input from reflecting keystrokes
while the TOC re-render was in progress. useTransition splits the work:
setQuery is urgent (input stays responsive), while setSearchState is
deferred and interruptible — new keystrokes cancel in-progress renders.
Input opacity drops to 0.5 during isPending as a subtle loading signal.
Replace flat TocItem/PreparedTocItem/TocGroup types with a recursive
TocTreeNode -> FlatTocItem pipeline that supports arbitrary page nesting.
**Types:**
- TocNodeType = 'page' | 'h1'-'h6' — heading level encoded in type field,
no separate headingDepth. Smaller objects, self-descriptive.
- TocTreeNode = { label, href, type, children } — recursive tree, the wire format.
- FlatTocItem = { label, href, type, visualLevel, prefix, parentHref, pageHref }
— flat rendering item with depth clamped to 0-3.
**Depth clamping:**
flattenTocTree() walks the recursive tree and clamps visual depth to max 4
levels (0-3). When pages nest deeply, headings flatten to siblings at level 3.
Formula: visualLevel = min(pageDepth + headingDepth, 3). Preserves correct
visual gap for skipped markdown levels (## -> ####) via the h-type encoding.
**Component changes:**
- TableOfContents accepts FlatTocItem[], renders with visibility filtering
instead of two-tier TocGroup. Expand/collapse works at any depth by
tracking expanded hrefs and computing visibility from parentHref chain.
- Search expand override merged into visibility computation so matched items
inside collapsed branches become visible during search.
- search.ts operates directly on FlatTocItem[] — removed SearchEntry and
buildSearchEntries(). Walks parentHref chain for ancestor expansion.
**Extraction:**
slugify, extractText, generateTocTree moved to toc-tree.ts (side-effect-free)
so they can be tested without importing the page module.
**Tests:**
21 vitest tests covering slugify, extractText, tree building (nested, skipped
levels, pop-back), flattening (prefixes, depth clamping, page wrapping,
parentHref/pageHref tracking), and full mdast -> tree -> flat pipeline.
**Remaining for multi-page:**
- Multi-page data source (multiple MDX files / manifest)
- Route-aware active tracking (pathname for page, IntersectionObserver for
headings on current page only)
- Cross-page navigation (SPA or full page load)
Add <Hero> component that gets auto-extracted from MDX at parse time
(same pattern as <Aside>) and rendered above the 3-column grid, aligned
with the center content column.
Layout changes:
- slot-page uses flex column with gap-[--layout-gap] for uniform spacing
between navbar, hero, and content grid
- --layout-gap (21px) is the single source of truth for both the flex gap
(flow spacing) and --sticky-top offset (viewport-relative sticky offset)
- Remove --content-top-gap variable and all its usage (spacer divs,
sidebar padding-top) — the flex gap handles all vertical spacing now
- Hero accepts arbitrary div props for extensibility
Hero extraction pipeline:
- isHeroNode() detects <Hero> mdxJsxFlowElement nodes in the mdast
- Hero nodes are filtered out before TOC generation and section grouping
- Rendered via RenderNodes through SafeMdxRenderer with full component map
- EditorialPage receives hero as a prop, renders it in a matching grid
(same column widths) so content aligns with the center column
- Sticky sidebar container gets maxHeight + flex column so TOC scrolls
when it exceeds viewport height
- Search input stays pinned at top (flexShrink: 0), nav scrolls below
- Nav gets paddingRight 4px for scrollbar gap
- Search input padding-y reduced from 4px to 2px
- Search-to-tree gap set to 12px
Adds a search input above the left sidebar table of contents powered by
Orama (in-memory, typo-tolerant, ~20μs per query).
**search.ts** — flat SearchEntry[] data model with parent/group references,
Orama DB creation, searchToc() that returns matched/dimmed/focusable hrefs
and group expand overrides. Reference: mintlify.com/oramasearch/orama/quickstart.md
**markdown.tsx** changes:
- TocLink: new props dimmed (opacity 0.3 + tabindex=-1), isHighlighted
(code-bg background), linkRef (scroll-into-view)
- TableOfContents: search input with F hotkey badge, 80ms debounced Orama
search, arrow key navigation through matched results, Enter navigates
to section anchor, Escape clears and blurs
- Groups containing matches auto-expand, non-matching items dimmed at 0.3
- IntersectionObserver scroll lock via ref to prevent activeId bouncing
during programmatic smooth scroll (scrollend + safety timeout)
- Global F key listener focuses search (skips input/textarea/contenteditable)
The built-in vite:asset load hook uses a regex that doesn't match when
query params get normalized to ?raw= (URLSearchParams.toString() adds
the =). This pre-load plugin handles ?raw imports for non-JS files
explicitly.
- Wrap subscribe, getSnapshot, getServerSnapshot in useCallback to prevent
React from tearing down and recreating the IntersectionObserver on every render
- Move window.location.hash read from render into subscribe, keeping render pure
- Add emit() helper that deduplicates ref writes and only notifies React on change
- Initialize activeRef with fallbackId; hash is read on first subscribe
Replaces useState + useEffect with a single useSyncExternalStore that:
- Initializes activeRef from window.location.hash to fix flash on first paint
- Uses IntersectionObserver in subscribe to track scroll position
- Returns fallbackId as server snapshot to prevent hydration errors
One less state hook (no useState, no useEffect for the observer).
No-op subscribe meant useSyncExternalStore was effectively a constant.
Replaced with a direct window.location.hash read in the useState
initializer, guarded by typeof window for SSR. One less hook.