Monorepo containing three packages:
| Package | npm | Description |
|---|---|---|
@ytspar/devbar |
Published | Development toolbar — breakpoints, vitals, console, screenshots, accessibility, ruler |
@ytspar/sweetlink |
Published | AI debugging toolkit — CLI + WebSocket bridge for screenshots, DOM queries, JS execution |
@ytspar/playground |
Private | devbar.dev website — Vite SPA deployed to Cloudflare Pages |
pnpm install # Install all dependencies
pnpm build # Build all packages (sweetlink → devbar → playground)
pnpm test # Run all tests (vitest)
pnpm test:watch # Watch mode
pnpm test:coverage # With coverage report
pnpm dev # Start playground dev server (http://localhost:5173)
pnpm lint # Biome lint check
pnpm lint:fix # Biome auto-fixBoth devbar and sweetlink are published to npm. The devbar package depends on sweetlink using "@ytspar/sweetlink": "workspace:^" in its package.json.
Critical: Always use pnpm publish (not npm publish) for devbar.
pnpm publish resolves workspace:^ to the actual version (e.g., ^1.9.1) in the published tarball. npm publish does NOT resolve workspace protocols, resulting in a broken package that consumers can't install.
- Bump the version in
packages/<pkg>/package.json - Build:
pnpm build - Run tests:
pnpm test - Publish sweetlink first (if changed):
cd packages/sweetlink && pnpm publish --access public --no-git-checks - Publish devbar second (depends on sweetlink):
cd packages/devbar && pnpm publish --access public --no-git-checks - Commit version bumps, push to main
- If sweetlink changed → bump sweetlink version
- If devbar changed → bump devbar version
- If sweetlink version bumped AND devbar depends on it → also bump devbar (so it picks up the new
workspace:^resolution) - Update release notes in
packages/playground/src/release-notes.jsonfor every new version (publishing will fail without this)
The playground package deploys to https://devbar.dev/ via Cloudflare Pages + GitHub Actions.
Deployment is automatic on push to main. The workflow (.github/workflows/playground.yml):
- Builds all packages
- Generates test coverage data
- Builds the playground with Vite
- Deploys
packages/playground/distto Cloudflare Pages with Wrangler
Cloudflare Pages config:
- Project name:
devbarby default; override with the GitHub repo variableCLOUDFLARE_PAGES_PROJECT_NAMEif the Cloudflare project uses a different name. - Required GitHub secrets:
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_ID. - The API token needs Cloudflare Pages edit access for the account.
- Production branch should be
main;test/*andplayground/*branches deploy as Cloudflare preview deployments. - Custom domain
devbar.devis configured in Cloudflare Pages, not through a repoCNAMEfile. - Cloudflare DNS should point the apex domain at Pages with a proxied
CNAMErecord:devbar.dev→devbar.pages.dev.
Initial Cloudflare setup:
pnpm dlx wrangler pages project create devbar --production-branch mainThen add devbar.dev as a custom domain in the Cloudflare Pages dashboard and point DNS at Cloudflare.
Manual deployment trigger:
gh workflow run playground.ymlpackages/
├── devbar/ # Vanilla JS toolbar (no framework deps)
│ ├── src/
│ │ ├── GlobalDevBar.ts # Main entry point
│ │ ├── constants.ts # PALETTE colors, breakpoints, CSS
│ │ ├── settings.ts # Settings persistence (localStorage + Sweetlink)
│ │ ├── modules/
│ │ │ ├── rendering/ # UI rendering (expanded, collapsed, compact, buttons)
│ │ │ ├── ruler.ts # Ruler measurement tool
│ │ │ ├── screenshot.ts # Screenshot capture
│ │ │ ├── performance.ts # Web Vitals collection
│ │ │ └── keyboard.ts # Keyboard shortcuts
│ │ ├── accessibility.ts # Accessibility audit (axe-core)
│ │ ├── network.ts # Network request tracking
│ │ └── ui/ # Reusable UI primitives (icons, buttons, modals)
│ └── package.json
├── sweetlink/ # CLI + browser bridge
│ ├── src/
│ │ ├── cli/sweetlink.ts # CLI entry point (screenshot, exec, query, logs, etc.)
│ │ ├── browser/ # Browser-side command handlers
│ │ │ └── commands/ # exec.ts, dom.ts, screenshot.ts, etc.
│ │ ├── server/ # WebSocket server + request handlers
│ │ ├── daemon/ # Persistent Playwright daemon (v2)
│ │ │ ├── types.ts # DaemonState, DaemonAction, constants
│ │ │ ├── stateFile.ts # State file I/O (scoped per app port)
│ │ │ ├── server.ts # HTTP server with bearer auth (18 actions)
│ │ │ ├── browser.ts # Persistent browser/page, headed mode
│ │ │ ├── client.ts # CLI client for daemon communication
│ │ │ ├── index.ts # Daemon entry point (forked process)
│ │ │ ├── refs.ts # @ref system from accessibility tree
│ │ │ ├── diff.ts # Snapshot diffing + annotated screenshots
│ │ │ ├── ringBuffer.ts # Generic ring buffer (50K entries)
│ │ │ ├── listeners.ts # Page event listeners → ring buffers
│ │ │ ├── cursor.ts # Cursor highlight injection (addInitScript)
│ │ │ ├── devices.ts # Named device presets for batch screenshots
│ │ │ ├── visualDiff.ts # Byte-level screenshot comparison
│ │ │ ├── recording.ts # Video recording via Chromium screencast
│ │ │ ├── session.ts # Session manifest types
│ │ │ ├── viewer.ts # Self-contained HTML viewer with video
│ │ │ └── evidence.ts # PR evidence upload + terminal capture
│ │ └── types.ts # Shared types
│ └── package.json
└── playground/ # devbar.dev website (Vite)
├── src/
│ ├── main.ts
│ ├── landing-content.ts # All page sections including releases
│ └── style.css
└── package.json
All hardcoded colors should use the PALETTE constant from packages/devbar/src/constants.ts. Don't introduce new hex values — add them to PALETTE first.
- Framework: Vitest with happy-dom environment
- 83 test files, ~2198 tests
- Tests are colocated with source files (e.g.,
expanded.test.tsnext toexpanded.ts) - Mock pattern:
vi.mock('./module.js', () => ({ ... }))— note.jsextensions for ESM
- Repository: https://github.com/ytspar/devbar (redirects from ytspar/devtools)
- Main branch:
main(not protected, direct push allowed) - Pushes to main trigger the playground deployment workflow