Placeholders are now generated into src/assets/placeholders/ instead of
public/ so Vite's asset pipeline processes them. Since all are < 4KB,
Vite auto-inlines them as base64 data URIs via assetsInlineLimit — zero
extra HTTP requests for placeholders.
- Static imports resolve to data:image/png;base64,... at build time
- JSDoc on placeholder props documents why static imports are required
(synchronous availability, Vite inlining) and warns against dynamic
imports and public/ paths
- New LazyVideo component: same pixelated poster pattern, uses native
<video preload="none" loading="lazy"> for zero-JS lazy loading
Build-time script generates tiny 32px-wide versions of all images in
public/ using sharp. At runtime, PixelatedImage component renders these
with CSS image-rendering: pixelated (nearest-neighbor / point sampling)
for a crisp mosaic effect, then fades in the real image on load.
- Auto-discovers images in public/, skips placeholder- prefixed files
- Mtime check skips regeneration when placeholder is newer than source
- Callback ref handles cached images where onLoad fires before mount
- Explicit z-index prevents placeholder from covering the real image
- Runs automatically as part of pnpm build in website package
Add explicit guidance to use playwriter first for JS-rendered websites and blocking overlays, based on repeated failures where webfetch/curl returned empty shells and screenshots were used too early.
Key updates:\n- Add a hard 'when to use playwriter vs webfetch/curl' rule in the main skill docs\n- Add a 'snapshot before screenshot' rule to prevent slow image-first debugging loops\n- Add a concrete anti-pattern for JS-rendered sites in common mistakes\n- Add obstacle-handling flow for cookie/login/age-gate overlays\n- Add media extraction/download pattern using evaluate + Node fetch/fs\n- Update skill frontmatter description (and well-known discovery copies) so context-loaded summaries also include the JS-heavy-site rule
All CSS variables previously scoped under .liveline-page with --ll- prefix
are now at :root in globals.css with no prefix. Four names conflicting with
shadcn were renamed: --brand-primary, --brand-secondary, --link-accent,
--page-border.
Class renames: .liveline-page → .editorial-page, .liveline-article →
.editorial-article, .ll-bleed → .bleed, .ll-inline-code → .inline-code,
.ll-prose → .editorial-prose.
Filenames kept as-is for this commit to isolate the rename.
Dark mode fix: import liveline.css and liveline-prism.css via CSS @import
in globals.css instead of JS import in the route file. This brings them
into the Tailwind compilation chain so @variant dark works. @variant dark
silently fails in CSS files imported only via JS — documented this
constraint in website/AGENTS.md.
- Prism dark colors use CSS variables on :root with @variant dark
- liveline.css dark overrides use @variant dark nested inside selectors
- Table cells use --ll-text-primary for full contrast in both modes
- Prose paragraphs at 0.82 opacity, bold and inline code punch through
to full opacity for visual hierarchy
- Prose line-height increased to 22px
- Add website/AGENTS.md documenting dark mode rules
- Add <strong> to every paragraph so each has at least one bold keyword
- Increase light mode Prism saturation: brighter purple, amber, green, blue
- Set font-weight 500 for code blocks and inline code
- Add letter-spacing 0.02em to code blocks and inline code
- Replace favicon.ico with transparent PNG favicons (32px, 16px) generated
from the gray extension icon, preserving alpha channel
- Move favicon links from hardcoded JSX to typed LinksFunction export in
root.tsx, rendered via <Links /> component
- Type liveline meta export as MetaFunction
- Add properly sized OG image (1200x630) for X/Slack/Discord previews,
update meta tags to reference it
- Remove 5 ChartPlaceholder stubs that were never replaced with real content
- Add OG meta tags (title, description, og:image 1280x800, twitter:card
summary_large_image) for rich previews on X, Slack, Discord
- Rename PropsTable to ComparisonTable with configurable headers prop,
fixing the hardcoded 'Prop/Type/Default' headers on comparison tables
- Remove stale Antigravity/Jetski comparison section
- Switch dark mode from .dark class toggle to prefers-color-scheme media
query via Tailwind @custom-variant. Remove inline JS script from root.tsx,
update all .dark selectors in liveline.css, liveline-prism.css to use
@variant dark from globals.css so strategy is defined in one place
- Fix scrollbar colors: light gray on light backgrounds, subtle white on
dark backgrounds (was using --color-muted which was invisible in both)
- Update remote access section with traforo link, VPN/firewall context,
MCP env var docs, and more use cases from docs/remote-access.md
- Fixed --ll-bleed to 44px (8px padding + 36px line-number width, border-box)
- Line numbers fixed width 36px (always fits 3 digits, no layout shift)
- Line number opacity increased from 0.15 to 0.3 in both light and dark mode
- Commented out code block background
- Add A (link) component with --ll-accent color, bright #58a6ff in dark mode
- Add OL (ordered list) component for numbered steps
- Remove BackButton from EditorialPage
- Use string-dedent for all CodeBlock strings, properly indented in source
- Getting started steps as OL with clickable Chrome Web Store link
- Star on GitHub link in intro paragraph
- Inline heading rule (flex + 1px line) replaces separate Divider
- Heading padding 24px top/bottom, article gap 32px
- Sidebar TOC item spacing increased to 5px
- Centered image captions
- SectionHeading now renders as flex row with hairline rule extending
to fill remaining width after the heading text (matching benji.org)
- Removed separate Divider from Section component
- Restored original diagram layout from before refactor
- Increased article gap to 32px, heading padding to 24px top/bottom
- Centered image captions
- Add @font-face for JetBrainsMono Nerd Font Mono (nerd-fonts v3.3.0) with
proper box-drawing glyph support for seamless Unicode diagram rendering
- Update --ll-font-code to use NF Mono as primary, fallback to JetBrains Mono/SF Mono
- Fix liveline-prism.css: use var(--ll-font-code) instead of hardcoded SF Mono
- Remove unused Google Fonts JetBrains Mono link from root.tsx
- Fix diagram: keep Unicode box chars for seamless lines, use ASCII < > v for
arrows (◄ ► ▼ render at inconsistent widths), recount all columns
Inline code now uses a subtle rgba(0,0,0,0.04) background pill via a ::before
pseudo-element with absolute inset and 4px border-radius, matching
benji.org/liveline exactly. Dark mode uses rgba(255,255,255,0.08).
Moved styles from inline to CSS class .ll-inline-code.
- Replace arrow + 'Index' link with bold 'playwriter' logo text in TOC sidebar
- Remove 'Playwriter' subtitle and '2025' year from page header, start with first paragraph
- Remove rounded-lg from chart placeholder images for sharp edges
Replace all hardcoded .dark class selectors with Tailwind v4's @variant dark
directive, which compiles to whatever strategy is configured in
@custom-variant dark. This means changing the dark mode strategy
(class, prefers-color-scheme, data-attribute, or combined) only requires
updating one line in globals.css — all dark mode variable overrides
and token styles follow automatically.
Changes:
- globals.css: update @custom-variant to (&:where(.dark, .dark *)) to
include the .dark element itself with zero specificity, replace
.dark {} block with @variant dark {}
- liveline.css: replace .dark .liveline-page {} with
@variant dark { .liveline-page {} }
- liveline-prism.css: replace 20+ .dark .token.* rules with a single
@variant dark {} block
Code blocks and chart placeholders now extend 36px beyond the 550px prose
column on each side via negative margins (.ll-bleed class). This aligns the
code text (after line numbers) flush with the prose left edge.
- --ll-bleed: 36px CSS variable (8px div padding + 28px line-number span)
- Responsive: bleed disabled below 650px viewport
- overflow-x-hidden on page wrapper prevents horizontal scroll
Inline script in <head> applies .dark class on <html> based on OS preference
and listens for live changes. All liveline page colors now use CSS variables
with dark overrides in .dark .liveline-page block.
- liveline.css: new vars (--ll-text-muted, --ll-text-hover, --ll-divider,
--ll-code-line-nr, --ll-btn-bg/shadow, --ll-fade-*) + full dark overrides
- liveline-prism.css: GitHub Dark syntax highlighting theme under .dark
- liveline.tsx: ~30 hardcoded rgba/hex colors replaced with CSS vars
- root.tsx: anti-FOUC script toggling .dark from prefers-color-scheme
Simplifies article content by extracting repeated patterns into
reusable components:
- Section: wraps Divider + SectionHeading + children
- List: styled ul with typography inline styles
- Li: styled li with correct padding
Article JSX now reads as clean declarative markup instead of
verbose inline-styled HTML.
Recreates the benji.org/liveline page in the website folder using React,
Tailwind, and the same editorial design system. Charts are replaced with
static SVG placeholders.
Typography:
- Inter variable font from rsms.me (same source as next/font)
- font-weight 475 body, 560 headings, font-optical-sizing: auto
- 14px body, 20px line-height, -0.09px letter-spacing
- Near-black text rgb(17,17,17), 40% opacity secondary
- Display P3 accent colors with @supports fallback
Layout:
- 550px narrow content column, fixed TOC sidebar
- Stagger-in entrance animation with cascading delays
- Fixed header fade gradient (multi-stop white-to-transparent)
- Scroll-spy TOC highlighting via IntersectionObserver
Code blocks:
- Prism.js syntax highlighting with GitHub-light color palette
- Line numbers column with 20px right padding
- 12px / 18px font size
Props tables, dividers, captions, code blocks, bullet lists all
styled to match the original computed values.
Fixes:
- Bug: page.screenshotWithAccessibilityLabels was called as method on page,
fixed to standalone function call
- Removed duplicate 'debugging playwriter issues' section at end of file
(identical to the detailed one earlier)
- Softened waitForTimeout rule: short waits (1-2s) now acknowledged as
acceptable for non-deterministic events like popups/animations
- Fixed download example path from /tmp/ to ./
- Added note that bare 'page' in examples is for brevity, real automation
should use state.myPage
- Consolidated diff behavior explanation to one place in snapshot section
(was repeated 3 times with slight inconsistencies)
- Trimmed redundant 'prefer snapshots' advice to reference the dedicated
'choosing between snapshot methods' section
- Consolidated cmd+click popup advice (was in both mistakes and common
patterns with duplicate examples)
- Cross-referenced iframe approaches: frameLocator for locator chaining,
contentFrame for Frame objects needed by snapshot()
- Clarified require is the only option in sandbox (ESM import unavailable)
The observe step now has an inner loop — if the page isn't ready (still
loading, wrong URL, expected content missing), the agent loops back to
observe again instead of acting on stale state. The ASCII diagram shows
the 'not ready' branch looping back to observe.
Every observe step in the example now prints page.url() before the
snapshot so the agent always knows the current URL. This catches
redirects and unexpected navigation between actions.
Add a 'computer use' section to skill.md showing how Playwriter covers all
actions from the Anthropic computer_20250124 tool and the Claude Chrome
extension's custom computer tool, using Playwright APIs instead of
screenshot-based coordinate clicking.
Each subsection shows the declarative locator-based approach first (preferred)
with coordinate-based fallback for canvas/maps/custom widgets.
Also adds docs/claude-extension-tools.json with all 16 tool schemas extracted
from the Claude Chrome extension v1.0.39 (minified source) for reference.
Add observe → act → observe feedback loop section with ASCII diagram
and Framer command palette example. Agents must take a snapshot after
every action to verify the result before proceeding.
Also change page creation pattern to reuse existing about:blank tabs
instead of always creating new ones, with a note to navigate immediately
in the same execute call to prevent concurrent agents from grabbing the
same tab.
When the Playwriter extension can't connect because Chrome isn't running,
agents now have platform-specific commands (macOS, Linux, Windows) to start
Chrome from the CLI. Also documents the --allowlisted-extension-id and
--auto-accept-this-tab-capture flags for enabling automatic tab capture
for screen recording without manual extension clicks.
Closes#48
Switch from getCDPSessionForPage (creates a new WS via Target.attachToTarget)
to getExistingCDPSessionForPage which reuses Playwright's internal CRSession.
This is critical for the relay server where Target.attachToTarget is intercepted
and cannot create real new sessions.
Also add FrameLocator support in accessibilitySnapshot — FrameLocator (from
locator.contentFrame()) is now auto-resolved to the real Frame object needed
for OOPIF CDP session attachment.
- New PlaywrightCDPSessionAdapter wrapping Playwright's CDPSession as ICDPSession
- New getExistingCDPSessionForPage() using context.getExistingCDPSession()
- resolveFrame() helper converts FrameLocator to Frame via elementHandle().contentFrame()
- getAriaSnapshot() frame param now accepts Frame | FrameLocator
- Executor CDP cache uses ICDPSession, borrowed sessions detach as no-op
- Test for getExistingCDPSession through the relay
- Updated framer iframe guide with alternative page.frames() example
- Bump playwright submodule to @xmorse/playwright-core@1.59.2
- Fix styles-api.md import path
- Consolidate build scripts into single build-client-bundles.ts
- Add @mozilla/readability for Firefox Reader View content extraction
- New getPageMarkdown() utility extracts main page content as plain text
- Supports search and diff-since-last-call like other utilities
- Add test with file snapshot