diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..86d55a5 --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,33 @@ +name: Deploy landing page + +on: + push: + branches: [main] + paths: + - 'site/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +permissions: + contents: read + pages: write + id-token: write + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/configure-pages@v5 + - uses: actions/upload-pages-artifact@v3 + with: + path: site + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 90a0c45..44491a7 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ node_modules/ dist/ out/ release/ +build/playwright-browsers/ coverage/ playwright-report/ test-results/ diff --git a/README.md b/README.md index 3d0ea76..f7be151 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,12 @@ Users can add or replace their own OpenAI API key from Settings. App-managed key The initial release is macOS-first and is being built for OpenAI Build Week. The hackathon demo proves one complete browser regression loop deeply. It does not claim broad, deterministic automation across every desktop application. +## Download + +Download the Apple Silicon beta from the [TaskTape Replay site](https://codeswithroh.github.io/tasktape/) or from [GitHub Releases](https://github.com/codeswithroh/tasktape/releases/latest). The DMG contains TaskTape's tested browser runtime, so Google Chrome is not required. + +The beta is not signed or notarized yet. After copying TaskTape to Applications, macOS may require you to control-click the app, choose Open, and confirm once. A signed release requires an Apple Developer ID certificate and notarization credentials. + The reliable local-file capability learns extension groups and child-folder destinations from the demonstration, then organizes top-level files inside one approved folder. The user does not configure separate video or image fields. Tasks that need an application or website use OpenAI computer use through a native macOS input adapter. Model safety checks stop the run for review, and the agent is bounded to 25 turns. Scheduled runs require TaskTape to be open and the Mac to be awake. Background launch, history search, rollback, and safety-check resumption are not yet implemented. ## Product loop @@ -50,12 +56,13 @@ Ask the connected agent to reproduce a bug on a local development URL and turn i - [Architecture](docs/architecture.md) - [Agent-operated replay milestone](docs/agent-mcp-milestone.md) - [Voice and scheduling research](docs/voice-and-scheduling-research.md) +- [Market validation](docs/market-validation.md) - [Milestone roadmap](docs/roadmap.md) - [Verification log](docs/verification.md) ## Development -Prerequisites: Node.js 22+, pnpm 11.7.0, Xcode Command Line Tools, Google Chrome, and macOS for desktop capture, browser instrumentation, and computer-control verification. +Prerequisites: Node.js 22+, pnpm 11.7.0, Xcode Command Line Tools, and macOS for desktop capture and computer-control verification. The packaging command downloads the pinned Playwright Chromium runtime into the release bundle. ```bash cd /Users/rohitpurkait/Documents/codex_build_week diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000..30541b0 Binary files /dev/null and b/build/icon.icns differ diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..a86d703 Binary files /dev/null and b/build/icon.png differ diff --git a/build/icon.svg b/build/icon.svg new file mode 100644 index 0000000..d4d7027 --- /dev/null +++ b/build/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/architecture.md b/docs/architecture.md index 853eb30..c7194f9 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -27,7 +27,7 @@ The Electron main process starts an MCP Streamable HTTP server on `127.0.0.1:197 The MCP server and desktop renderer share the same browser evidence manager, workflow persistence, execution boundary, and run history. The renderer never hosts the server and receives only a narrow status object through preload IPC. Claude Code and Codex connection commands are shown in Settings, following Palmier's pattern of keeping setup beside the canonical local application state. -The first MCP capability intentionally targets local web applications. `start_bug_session` accepts only loopback HTTP or HTTPS URLs, launches a temporary headed Chrome context, starts Playwright tracing, and allows one active session. Accessible role, label, text, or CSS selectors drive bounded click, fill, select, key, and wait operations. Password fields are refused. +The first MCP capability intentionally targets local web applications. `start_bug_session` accepts only loopback HTTP or HTTPS URLs, launches a temporary headed Chromium context, starts Playwright tracing, and allows one active session. Release builds include the pinned Playwright browser runtime, while development can also use an explicitly configured or installed Chrome. Accessible role, label, text, or CSS selectors drive bounded click, fill, select, key, and wait operations. Password fields are refused. Every operation stores an ordered action plus a screenshot. The session also records console messages, page errors, failed requests, HTTP error responses, initial and final screenshots, and a Playwright trace containing DOM snapshots and action timing. Finishing compiles the recorded actions into a review-required computer workflow and closes the temporary browser. Running or scheduling remains a separate explicit action. diff --git a/docs/market-validation.md b/docs/market-validation.md new file mode 100644 index 0000000..04eab4d --- /dev/null +++ b/docs/market-validation.md @@ -0,0 +1,58 @@ +# Market validation + +Reviewed on July 19, 2026. + +## Decision + +TaskTape is worth releasing as a focused beta, not as a general browser agent or another screen recorder. + +The product promise is: + +> TaskTape lets a coding agent reproduce a local browser bug, prove the fix, and leave behind a reusable regression check. + +This is a conditional go. The next product gate is repeated use by real AI-heavy development teams, not download count. + +## Evidence that the problem is real + +- In the [2025 Stack Overflow Developer Survey](https://survey.stackoverflow.co/2025/ai), 66% of respondents cited AI solutions that are almost right as a frustration and 45% cited time spent debugging AI-generated code. More developers distrusted AI accuracy than trusted it. +- [Jam](https://jam.dev/) reports 200,000 users. Its product and customer stories consistently center on the cost of incomplete bug reports that omit reproduction steps, console errors, and network failures. These figures and stories are vendor-reported. +- [Momentic](https://momentic.ai/blog/series-a) raised a $15 million Series A after a $3.7 million seed round. It reports more than two billion customer testing steps since 2024 and customers including Notion, Quora, Webflow, and Xero. These figures are also vendor-reported. + +The demand is clear: AI accelerates code changes, while teams still need trustworthy, reviewable evidence that behavior works. + +## Competitive reality + +| Product | Strong at | Gap TaskTape can explore | +| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | +| [Jam](https://jam.dev/docs/creating-a-jam) | Human bug capture, video, actions, console, network, and issue-tracker handoff | A coding agent actively reproducing the issue and preserving that investigation as a local reusable check | +| [Playwright MCP](https://playwright.dev/) | Free browser control for agents, accessibility snapshots, screenshots, tracing, console, and network evidence | A reviewed check lifecycle, expected-outcome editing, replay history, and a proof-of-fix artifact | +| [Momentic](https://momentic.ai/blog/series-a) | Managed natural-language testing, adaptive replay, CI, visual checks, and run management | A small local reproduction workbench that starts inside an existing Claude or Codex debugging session | +| [Sentry Replay](https://docs.sentry.io/product/explore/session-replay/) | Passive production replay connected to real errors and telemetry | Active reproduction and creation of a future check without requiring production instrumentation | +| [Browserbase and Stagehand](https://www.browserbase.com/blog/stagehand-v3) | Cloud browser infrastructure, agent control, observability, and scale | A local desktop review and evidence workflow for one developer or a small team | + +## What is not differentiated + +Browser control, MCP, screenshots, console logs, network logs, and Playwright traces are established capabilities. TaskTape should use those primitives and never present them alone as the invention. + +A generic claim that Claude can control a browser and record what happened would duplicate Playwright MCP. A human-first screen recorder with developer logs would duplicate Jam. A broad AI testing platform would compete directly with Momentic before TaskTape has earned that scope. + +## Current wedge + +TaskTape joins steps that are still fragmented in the lightweight local workflow: + +1. Claude Code or Codex receives a bug report. +2. The agent reproduces it in a visible, bounded local browser. +3. TaskTape stores actions, screenshots, the DOM view, console events, failed requests, and a trace. +4. The developer reviews the expected result and replay instructions. +5. TaskTape creates a reusable check and records later pass or fail evidence. + +The desktop application is the review, persistence, and evidence layer around Playwright. It is not a replacement browser engine. + +## Risks and next validation gate + +- Playwright MCP can already produce most raw evidence, and strong agents can write tests from it. +- Jam now moves rich bug context into coding workflows. +- Momentic already sells an advanced managed testing lifecycle. +- A local macOS-only beta limits reach and has trust friction until it is signed and notarized. + +The release should be tested with ten teams that use coding agents on active web products. Continue investing if at least three teams create and rerun checks weekly without prompting. Stop or reposition if most testers prefer raw Playwright MCP or never rerun the saved evidence. diff --git a/docs/roadmap.md b/docs/roadmap.md index e5f14e3..79f6337 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -164,7 +164,7 @@ Verified result: Current boundary: -- Agent capture supports local browser applications, one active session, and an installed Chrome runtime. General desktop instrumentation, remote staging URLs, CI workers, and direct GitHub issue ingestion remain post-hackathon work. +- Agent capture supports local browser applications and one active session. Release builds include their browser runtime. General desktop instrumentation, remote staging URLs, CI workers, and direct GitHub issue ingestion remain post-hackathon work. ## Milestone 5: Product polish and submission - July 19-20 diff --git a/docs/verification.md b/docs/verification.md index d518218..0363d00 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -34,8 +34,12 @@ This file records what has actually been tested. Passing claims must include the ## Latest automated run -On 2026-07-18, `pnpm test` passed 61 tests across 15 files and `pnpm test:e2e` passed all 10 Electron journeys in 25.3 seconds. Formatting, lint, both TypeScript projects, and the production build also passed. The earlier paid gates remain valid: `pnpm test:live:computer` passed the paired broken and fixed OpenAI-driven desktop replay, and the live evaluator reliability gate passed all 10 evaluations. No additional paid model calls were used for the MCP milestone. +On 2026-07-19, `pnpm check` passed formatting, lint, both TypeScript projects, 62 tests across 15 files, and the production build. `pnpm test:e2e` then passed all 10 Electron journeys in 29.6 seconds. The earlier paid gates remain valid: `pnpm test:live:computer` passed the paired broken and fixed OpenAI-driven desktop replay, and the live evaluator reliability gate passed all 10 evaluations. No additional paid model calls were used for the public-release milestone. The first nine-journey Electron run on July 18 had one intermittent timeout while a synthetic MediaRecorder transitioned to the intent screen. That journey then passed twice consecutively in isolation, followed by a clean full run of all nine journeys in 20.9 seconds. No evaluator, execution, or persistence assertion failed during the timeout. -The arm64 production app, DMG, and ZIP built successfully with `pnpm package:mac`. A packaged-app MCP check launched the binary from `app.asar`, connected over an ephemeral loopback port, discovered all 12 tools, captured and finished the broken browser fixture, and found the saved check. The DMG and ZIP are each 128 MB. Google Chrome is a runtime prerequisite and is not bundled. The app is not code-signed because no Developer ID identity is installed on this Mac, and a product icon has not yet replaced Electron's default icon. macOS therefore requires permissions to be granted separately to the packaged bundle. +The 0.2.0 arm64 production app, DMG, and ZIP built successfully with `pnpm package:mac`. The release includes Playwright Chromium 1228 and the TaskTape product icon. `codesign --verify --deep --strict` passed after the packaging hook sealed the full app with an ad-hoc signature, and `hdiutil verify` reported a valid disk image checksum. A fresh-user-data MCP journey launched TaskTape directly from the read-only mounted DMG, connected over an ephemeral loopback port, reproduced the browser regression through the bundled runtime, wrote the trace and final screenshot, created the check, and ran it successfully. The 286 MB stable DMG has SHA-256 `a63e6d0791a7fc97d93be02ec8e52fbb6bcf276f5ef8324d799550e4fbaf4c89`. + +The static landing page was served locally and exercised in Chromium at 1440 by 900 and 390 by 844. Both viewports loaded the real product images, had no horizontal overflow or console errors, exposed consistent stable DMG links, and copied both MCP commands with visible feedback. Full-page captures were visually inspected. Public URL and release download verification remain pending until the branch is merged and GitHub Pages deploys. + +No Apple Developer ID identity is installed on the build Mac, so 0.2.0 is not Apple-signed or notarized. The ad-hoc signature provides bundle integrity but does not satisfy Gatekeeper distribution trust; users may need to approve the beta once through macOS. This is the only known release-credential blocker. diff --git a/eslint.config.js b/eslint.config.js index 4794561..4bdd401 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,7 +6,16 @@ import globals from 'globals' import tseslint from 'typescript-eslint' export default tseslint.config( - { ignores: ['out', 'release', 'coverage', 'playwright-report', 'test-results'] }, + { + ignores: [ + 'out', + 'release', + 'build/playwright-browsers', + 'coverage', + 'playwright-report', + 'test-results' + ] + }, eslint.configs.recommended, ...tseslint.configs.recommended, { @@ -18,5 +27,9 @@ export default tseslint.config( 'react-refresh/only-export-components': ['warn', { allowConstantExport: true }] } }, + { + files: ['site/**/*.js'], + languageOptions: { globals: globals.browser } + }, prettier ) diff --git a/package.json b/package.json index 0e074eb..ee84e55 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tasktape", - "version": "0.1.0", + "version": "0.2.0", "description": "Turn a narrated bug recording into a living regression check.", "type": "module", "main": "./out/main/index.js", @@ -21,11 +21,13 @@ "test:watch": "vitest", "test:e2e": "pnpm build && playwright test", "check": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build", - "package:mac": "pnpm build && electron-builder --mac" + "browser:install": "PLAYWRIGHT_BROWSERS_PATH=build/playwright-browsers playwright install --no-shell chromium", + "package:mac": "pnpm browser:install && pnpm build && electron-builder --mac" }, "build": { "appId": "com.tasktape.desktop", "productName": "TaskTape", + "afterPack": "scripts/after-pack.mjs", "directories": { "output": "release" }, @@ -33,8 +35,18 @@ "out/**/*", "package.json" ], + "extraResources": [ + { + "from": "build/playwright-browsers", + "to": "playwright-browsers", + "filter": [ + "**/*" + ] + } + ], "mac": { "category": "public.app-category.productivity", + "icon": "build/icon.icns", "extendInfo": { "NSMicrophoneUsageDescription": "TaskTape uses the microphone only when you record a voice note describing a workflow." }, diff --git a/scripts/after-pack.mjs b/scripts/after-pack.mjs new file mode 100644 index 0000000..e630b21 --- /dev/null +++ b/scripts/after-pack.mjs @@ -0,0 +1,11 @@ +import { execFileSync } from 'node:child_process' +import { join } from 'node:path' + +export default function afterPack(context) { + if (context.electronPlatformName !== 'darwin') return + + const appPath = join(context.appOutDir, `${context.packager.appInfo.productFilename}.app`) + execFileSync('codesign', ['--force', '--deep', '--sign', '-', appPath], { + stdio: 'inherit' + }) +} diff --git a/site/.nojekyll b/site/.nojekyll new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/site/.nojekyll @@ -0,0 +1 @@ + diff --git a/site/assets/tasktape-regression-found.png b/site/assets/tasktape-regression-found.png new file mode 100644 index 0000000..a5e066b Binary files /dev/null and b/site/assets/tasktape-regression-found.png differ diff --git a/site/assets/tasktape-replay-check.png b/site/assets/tasktape-replay-check.png new file mode 100644 index 0000000..3c056e7 Binary files /dev/null and b/site/assets/tasktape-replay-check.png differ diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..fa43c9a --- /dev/null +++ b/site/index.html @@ -0,0 +1,271 @@ + + + + + + + + + + + TaskTape Replay | Reproduce bugs. Prove fixes. + + + + + + + + +
+
+
+

+ Local browser verification for coding agents +

+

TaskTape Replay

+

Reproduce the bug. Prove the fix. Keep the check.

+

+ Give Claude Code or Codex a visible browser it can control through MCP. TaskTape + preserves the evidence and turns the session into a check your team can replay. +

+ +

Apple Silicon beta. Local-only sessions. No account required.

+
+ +
+ + TaskTape Replay desktop app showing an agent-created browser regression check +
+ Agent created A reproducible check, ready for review + Runs on your Mac +
+
+
+ +
+

One session preserves

+ +
+ +
+
+

From report to regression check

+

Your agent does the reproduction. You approve the truth.

+

+ TaskTape gives coding agents the browser tools they need while keeping the expected + result under human control. +

+
+
    +
  1. + 01 +

    Connect

    +

    Add TaskTape as a local MCP server in Claude Code or Codex.

    +
  2. +
  3. + 02 +

    Reproduce

    +

    Your agent opens the local app and follows the bug from start to failure.

    +
  4. +
  5. + 03 +

    Review

    +

    Confirm what should happen before saving the regression check.

    +
  6. +
  7. + 04 +

    Replay

    +

    Run the same check after a change and inspect pass or fail evidence.

    +
  8. +
+
+ +
+
+

Proof you can inspect

+

A failed check shows where reality diverged.

+

+ Each replay keeps the expected result beside the observed browser state. Open the + activity log for actions, screenshots, console output, requests, and the full trace. +

+
    +
  • Evidence stays on your machine
  • +
  • Expected outcomes require review
  • +
  • Every replay keeps a run record
  • +
+
+
+ + TaskTape showing a failed replay with the expected result and observed regression +
+
+ +
+
+

Two-command setup

+

Connect the agent you already use.

+

Open TaskTape, then add its local MCP endpoint to your coding agent.

+
+
+
+
+

Claude Code

+ +
+ claude mcp add --transport http tasktape
http://127.0.0.1:19790/mcp
+
+
+
+

Codex

+ +
+ codex mcp add tasktape --url
http://127.0.0.1:19790/mcp
+
+
+
+ +
+
+

Honest beta

+

Built for local development today.

+
+
+

macOS first. This download supports Apple Silicon Macs.

+

+ Local targets only. Replay sessions accept localhost URLs so your + development work stays bounded. +

+

+ Unsigned build. macOS may ask you to approve TaskTape in System + Settings under Privacy & Security. +

+
+
+ +
+

Make the fix measurable

+

Leave every reproduced bug with a check.

+

Free, local, and ready for Claude Code or Codex.

+ +
+
+ + + + + + diff --git a/site/script.js b/site/script.js new file mode 100644 index 0000000..b420378 --- /dev/null +++ b/site/script.js @@ -0,0 +1,39 @@ +const toast = document.querySelector('.toast') +const toastClose = toast?.querySelector('button') +let toastTimer + +function showDownloadHelp() { + if (!toast) return + toast.hidden = false + window.clearTimeout(toastTimer) + toastTimer = window.setTimeout(() => { + toast.hidden = true + }, 9000) +} + +document.querySelectorAll('.button-download').forEach((link) => { + link.addEventListener('click', showDownloadHelp) +}) + +toastClose?.addEventListener('click', () => { + toast.hidden = true +}) + +document.querySelectorAll('.copy-button').forEach((button) => { + button.addEventListener('click', async () => { + const value = button.dataset.copy + if (!value) return + + try { + await navigator.clipboard.writeText(value) + const label = button.querySelector('span') + if (!label) return + label.textContent = 'Copied' + window.setTimeout(() => { + label.textContent = 'Copy' + }, 1800) + } catch { + window.prompt('Copy this command', value) + } + }) +}) diff --git a/site/styles.css b/site/styles.css new file mode 100644 index 0000000..8d7cef6 --- /dev/null +++ b/site/styles.css @@ -0,0 +1,909 @@ +:root { + color-scheme: light; + --ink: #142019; + --muted: #59675f; + --line: #cbd6d0; + --paper: #ffffff; + --surface: #f5f8f6; + --mint: #e3f1ea; + --green: #14734f; + --green-dark: #0b583c; + --coral: #ef6548; + --coral-soft: #fff1ed; + --yellow: #f6cf66; + --shadow: 0 18px 50px rgba(20, 32, 25, 0.12); + font-family: + Inter, + ui-sans-serif, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + sans-serif; + font-synthesis: none; +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + color: var(--ink); + background: var(--paper); + font-size: 16px; + line-height: 1.55; +} + +img { + display: block; + max-width: 100%; +} + +a { + color: inherit; + text-decoration: none; +} + +button, +a { + -webkit-tap-highlight-color: transparent; +} + +:focus-visible { + outline: 3px solid var(--yellow); + outline-offset: 3px; +} + +.skip-link { + position: fixed; + z-index: 100; + top: 12px; + left: 12px; + padding: 10px 14px; + background: var(--ink); + color: white; + transform: translateY(-160%); +} + +.skip-link:focus { + transform: translateY(0); +} + +.site-header { + position: sticky; + z-index: 20; + top: 0; + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + min-height: 72px; + padding: 0 4vw; + border-bottom: 1px solid rgba(203, 214, 208, 0.85); + background: rgba(255, 255, 255, 0.94); + backdrop-filter: blur(14px); +} + +.brand { + display: inline-flex; + align-items: center; + gap: 9px; + width: max-content; + font-weight: 750; +} + +.brand-mark { + position: relative; + display: inline-block; + width: 24px; + height: 20px; +} + +.brand-mark span { + position: absolute; + width: 14px; + height: 7px; + border: 2px solid var(--green); + border-radius: 3px; +} + +.brand-mark span:first-child { + top: 1px; + left: 0; +} + +.brand-mark span:last-child { + right: 0; + bottom: 1px; + border-color: var(--coral); +} + +.brand-product { + padding-left: 8px; + border-left: 1px solid var(--line); + color: var(--muted); + font-weight: 550; +} + +nav { + display: flex; + align-items: center; + gap: 30px; + color: var(--muted); + font-size: 14px; +} + +nav a, +footer > a { + text-decoration: underline; + text-decoration-color: transparent; + text-underline-offset: 5px; +} + +nav a:hover, +footer > a:hover { + color: var(--ink); + text-decoration-color: var(--coral); +} + +.site-header > .button { + justify-self: end; +} + +.button { + display: inline-flex; + min-height: 48px; + align-items: center; + justify-content: center; + gap: 9px; + padding: 0 19px; + border: 1px solid transparent; + border-radius: 6px; + font-size: 15px; + font-weight: 700; + transition: + background-color 160ms ease, + border-color 160ms ease, + transform 160ms ease; +} + +.button:hover { + transform: translateY(-2px); +} + +.button svg, +.copy-button svg { + width: 19px; + height: 19px; + fill: none; + stroke: currentColor; + stroke-width: 1.8; + stroke-linecap: round; + stroke-linejoin: round; +} + +.button-small { + min-height: 40px; + padding: 0 15px; + background: var(--ink); + color: white; + font-size: 14px; +} + +.button-primary { + background: var(--green); + color: white; +} + +.button-primary:hover, +.button-small:hover { + background: var(--green-dark); +} + +.button-secondary { + border-color: var(--line); + background: var(--paper); +} + +.button-secondary:hover { + border-color: var(--green); +} + +.hero { + position: relative; + overflow: hidden; + padding: 70px max(4vw, calc((100vw - 1180px) / 2)) 0; + background: var(--surface); +} + +.hero::after { + position: absolute; + top: 96px; + right: 4vw; + width: 80px; + height: 9px; + background: var(--coral); + content: ''; +} + +.hero-copy { + max-width: 840px; +} + +.eyebrow { + margin: 0 0 13px; + color: var(--green-dark); + font-size: 13px; + font-weight: 750; + letter-spacing: 0; + text-transform: uppercase; +} + +.status-dot { + display: inline-block; + width: 8px; + height: 8px; + margin-right: 7px; + border-radius: 50%; + background: #35a875; + box-shadow: 0 0 0 3px rgba(53, 168, 117, 0.14); +} + +h1, +h2, +h3, +p { + text-wrap: pretty; +} + +h1 { + max-width: 760px; + margin: 0; + font-size: clamp(48px, 7vw, 88px); + line-height: 0.98; + letter-spacing: 0; +} + +.hero-statement { + margin: 22px 0 0; + font-size: clamp(25px, 3vw, 38px); + font-weight: 650; + line-height: 1.15; +} + +.hero-detail { + max-width: 720px; + margin: 18px 0 0; + color: var(--muted); + font-size: 18px; +} + +.hero-actions { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px; + margin-top: 28px; +} + +.download-note { + margin: 12px 0 0; + color: var(--muted); + font-size: 13px; +} + +.product-frame { + overflow: hidden; + margin: 0; + border: 1px solid #b9c8c0; + border-radius: 8px; + background: var(--paper); + box-shadow: var(--shadow); +} + +.hero-product { + position: relative; + z-index: 2; + width: min(1180px, 100%); + margin-top: 54px; +} + +.window-bar { + position: relative; + display: flex; + min-height: 37px; + align-items: center; + gap: 7px; + padding: 0 13px; + border-bottom: 1px solid var(--line); + background: var(--paper); +} + +.window-bar > span { + width: 9px; + height: 9px; + border-radius: 50%; + background: #c9d2cd; +} + +.window-bar > span:first-child { + background: var(--coral); +} + +.window-bar p { + position: absolute; + left: 50%; + margin: 0; + color: var(--muted); + font-size: 12px; + transform: translateX(-50%); +} + +.hero-product img, +.proof-product img { + width: 100%; + height: auto; +} + +.hero-product figcaption { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 15px 18px; + color: var(--muted); + font-size: 13px; +} + +.hero-product figcaption strong { + margin-right: 8px; + color: var(--green-dark); + text-transform: uppercase; +} + +.local-badge { + flex: none; +} + +.evidence-strip { + display: flex; + align-items: center; + justify-content: center; + gap: 30px; + padding: 24px 4vw; + border-block: 1px solid var(--line); + background: var(--ink); + color: white; +} + +.evidence-strip p { + margin: 0; + color: #b8c7be; + font-size: 13px; + text-transform: uppercase; +} + +.evidence-strip ul { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 24px; + margin: 0; + padding: 0; + list-style: none; + font-size: 14px; + font-weight: 650; +} + +.evidence-strip li::before { + margin-right: 8px; + color: var(--coral); + content: '•'; +} + +.section { + max-width: 1180px; + margin: 0 auto; + padding: 112px 0; +} + +.section-heading { + display: grid; + grid-template-columns: 1.35fr 0.65fr; + column-gap: 70px; +} + +.section-heading .eyebrow { + grid-column: 1 / -1; +} + +h2 { + margin: 0; + font-size: clamp(34px, 4.5vw, 58px); + line-height: 1.05; + letter-spacing: 0; +} + +.section-heading > p:last-child, +.proof-copy > p, +.final-cta > p, +.beta-details { + margin: 4px 0 0; + color: var(--muted); + font-size: 17px; +} + +.steps { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0; + margin: 62px 0 0; + padding: 0; + border-block: 1px solid var(--line); + list-style: none; +} + +.steps li { + min-height: 240px; + padding: 24px; + border-right: 1px solid var(--line); +} + +.steps li:last-child { + border-right: 0; +} + +.step-number { + display: block; + color: var(--coral); + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 13px; + font-weight: 700; +} + +.steps h3 { + margin: 55px 0 8px; + font-size: 23px; +} + +.steps p { + margin: 0; + color: var(--muted); + font-size: 15px; +} + +.proof { + display: grid; + grid-template-columns: 0.68fr 1.32fr; + align-items: center; + gap: 72px; + max-width: none; + padding-inline: max(4vw, calc((100vw - 1180px) / 2)); + background: var(--mint); +} + +.proof-copy h2 { + font-size: clamp(34px, 4vw, 52px); +} + +.proof-copy > p { + margin-top: 20px; +} + +.check-list { + display: grid; + gap: 12px; + margin: 30px 0 0; + padding: 24px 0 0; + border-top: 1px solid #b7ccc1; + list-style: none; + font-size: 14px; + font-weight: 650; +} + +.check-list span { + margin-right: 9px; + color: var(--green); +} + +.proof-product { + min-width: 0; +} + +.setup .section-heading { + display: block; + max-width: 720px; +} + +.setup .section-heading > p:last-child { + margin-top: 18px; +} + +.command-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 18px; + margin-top: 45px; +} + +.command-block { + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); +} + +.command-block header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 14px 17px; + border-bottom: 1px solid var(--line); + background: var(--paper); +} + +.command-block h3 { + margin: 0; + font-size: 16px; +} + +.command-block code { + display: block; + overflow-x: auto; + padding: 24px; + color: #224b38; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 14px; + line-height: 1.8; + white-space: nowrap; +} + +.copy-button { + display: inline-flex; + min-height: 34px; + align-items: center; + gap: 7px; + padding: 0 10px; + border: 1px solid var(--line); + border-radius: 5px; + background: var(--paper); + color: var(--muted); + cursor: pointer; + font: inherit; + font-size: 12px; +} + +.copy-button:hover { + border-color: var(--green); + color: var(--green-dark); +} + +.copy-button svg { + width: 15px; + height: 15px; +} + +.beta-note { + display: grid; + grid-template-columns: 0.8fr 1.2fr; + gap: 80px; + max-width: none; + padding-inline: max(4vw, calc((100vw - 1180px) / 2)); + border-block: 1px solid var(--line); + background: var(--coral-soft); +} + +.beta-note h2 { + max-width: 450px; + font-size: clamp(34px, 4vw, 50px); +} + +.beta-details { + display: grid; + gap: 18px; +} + +.beta-details p { + margin: 0; + padding-bottom: 18px; + border-bottom: 1px solid #e8c9c0; +} + +.beta-details p:last-child { + border-bottom: 0; +} + +.beta-details strong { + color: var(--ink); +} + +.final-cta { + padding: 118px 4vw; + text-align: center; +} + +.final-cta h2 { + max-width: 820px; + margin: 0 auto; +} + +.final-cta > p:not(.eyebrow) { + margin-top: 18px; +} + +.centered { + justify-content: center; +} + +.text-link { + padding: 12px 8px; + color: var(--green-dark); + font-weight: 700; + text-decoration: underline; + text-decoration-color: var(--coral); + text-underline-offset: 5px; +} + +footer { + display: grid; + grid-template-columns: 1fr auto 1fr; + align-items: center; + gap: 20px; + padding: 32px 4vw; + border-top: 1px solid var(--line); + color: var(--muted); + font-size: 13px; +} + +footer p { + margin: 0; + text-align: center; +} + +footer > a:last-child { + justify-self: end; +} + +.toast { + position: fixed; + z-index: 50; + right: 20px; + bottom: 20px; + display: grid; + grid-template-columns: 1fr auto; + width: min(440px, calc(100vw - 40px)); + padding: 17px; + border: 1px solid #8bb59f; + border-radius: 8px; + background: var(--paper); + box-shadow: var(--shadow); +} + +.toast[hidden] { + display: none; +} + +.toast strong { + color: var(--green-dark); +} + +.toast span { + grid-column: 1; + color: var(--muted); + font-size: 13px; +} + +.toast button { + grid-column: 2; + grid-row: 1 / 3; + align-self: center; + min-height: 36px; + margin-left: 14px; + border: 1px solid var(--line); + border-radius: 5px; + background: var(--surface); + cursor: pointer; +} + +@media (max-width: 1220px) { + .section { + margin-inline: 4vw; + } + + .proof, + .beta-note { + margin-inline: 0; + } +} + +@media (max-width: 900px) { + .site-header { + grid-template-columns: 1fr auto; + } + + nav { + display: none; + } + + .hero { + padding-top: 55px; + } + + .hero::after { + top: 70px; + width: 42px; + } + + .section { + padding-block: 80px; + } + + .section-heading, + .proof, + .beta-note { + grid-template-columns: 1fr; + } + + .section-heading { + gap: 22px; + } + + .steps { + grid-template-columns: 1fr 1fr; + } + + .steps li:nth-child(2) { + border-right: 0; + } + + .steps li:nth-child(-n + 2) { + border-bottom: 1px solid var(--line); + } + + .proof, + .beta-note { + gap: 48px; + } +} + +@media (max-width: 620px) { + .site-header { + min-height: 64px; + padding-inline: 18px; + } + + .brand-product, + .site-header .button svg { + display: none; + } + + .site-header .button { + min-height: 38px; + padding-inline: 12px; + } + + .hero { + padding: 42px 18px 0; + } + + .hero::after { + display: none; + } + + h1 { + font-size: 48px; + } + + .hero-statement { + font-size: 27px; + } + + .hero-detail { + font-size: 16px; + } + + .hero-actions { + align-items: stretch; + flex-direction: column; + } + + .button { + width: 100%; + } + + .hero-product { + width: calc(100% + 36px); + margin-top: 40px; + margin-left: -18px; + border-inline: 0; + border-radius: 0; + } + + .hero-product figcaption { + align-items: flex-start; + flex-direction: column; + gap: 7px; + } + + .evidence-strip { + align-items: flex-start; + flex-direction: column; + gap: 12px; + padding-inline: 18px; + } + + .evidence-strip ul { + justify-content: flex-start; + gap: 8px 18px; + } + + .section { + margin-inline: 18px; + padding-block: 70px; + } + + .proof, + .beta-note { + margin-inline: 0; + padding-inline: 18px; + } + + .steps, + .command-grid { + grid-template-columns: 1fr; + } + + .steps li, + .steps li:nth-child(2) { + min-height: 0; + border-right: 0; + border-bottom: 1px solid var(--line); + } + + .steps li:last-child { + border-bottom: 0; + } + + .steps h3 { + margin-top: 28px; + } + + .command-block code { + font-size: 12px; + } + + .final-cta { + padding: 78px 18px; + } + + footer { + grid-template-columns: 1fr auto; + padding-inline: 18px; + } + + footer p { + display: none; + } + + .toast { + right: 12px; + bottom: 12px; + width: calc(100vw - 24px); + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition-duration: 0.01ms !important; + } +} diff --git a/src/main/browser-evidence.test.ts b/src/main/browser-evidence.test.ts index e59c261..58767cc 100644 --- a/src/main/browser-evidence.test.ts +++ b/src/main/browser-evidence.test.ts @@ -1,14 +1,18 @@ import { createServer, type Server } from 'node:http' -import { mkdtemp, readFile, rm, stat } from 'node:fs/promises' +import { mkdir, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' -import { join, resolve } from 'node:path' +import { dirname, join, resolve } from 'node:path' import { chromium } from 'playwright-core' import { afterEach, describe, expect, it } from 'vitest' import { bugSessionSchema } from '../shared/agent-schema.js' import { savedWorkflowSchema } from '../shared/workflow-schema.js' -import { BrowserEvidenceManager, compileReplayInstructions } from './browser-evidence.js' +import { + BrowserEvidenceManager, + bundledChromiumExecutable, + compileReplayInstructions +} from './browser-evidence.js' import { readWorkflow, saveWorkflow } from './workflows.js' const cleanup: Array<() => Promise> = [] @@ -38,6 +42,25 @@ async function fixtureServer(): Promise<{ server: Server; url: string }> { } describe('browser evidence manager', () => { + it('finds the Chromium runtime bundled with a packaged app', async () => { + const resourcesPath = await mkdtemp(join(tmpdir(), 'tasktape-resources-')) + cleanup.push(() => rm(resourcesPath, { recursive: true, force: true })) + const executable = join( + resourcesPath, + 'playwright-browsers', + 'chromium-1228', + 'chrome-mac-arm64', + 'Google Chrome for Testing.app', + 'Contents', + 'MacOS', + 'Google Chrome for Testing' + ) + await mkdir(dirname(executable), { recursive: true }) + await writeFile(executable, '') + + expect(bundledChromiumExecutable(resourcesPath, 'arm64')).toBe(executable) + }) + it('captures a broken browser flow and compiles it into a saved Replay check', async () => { const root = await mkdtemp(join(tmpdir(), 'tasktape-agent-session-')) cleanup.push(() => rm(root, { recursive: true, force: true })) diff --git a/src/main/browser-evidence.ts b/src/main/browser-evidence.ts index 0f0827d..7f74d97 100644 --- a/src/main/browser-evidence.ts +++ b/src/main/browser-evidence.ts @@ -1,5 +1,5 @@ import { randomUUID } from 'node:crypto' -import { existsSync } from 'node:fs' +import { existsSync, readdirSync } from 'node:fs' import { mkdir, rename, writeFile } from 'node:fs/promises' import { join } from 'node:path' @@ -74,23 +74,55 @@ async function writeJson(path: string, value: unknown): Promise { await rename(temporary, path) } +export function bundledChromiumExecutable( + resourcesPath = process.resourcesPath, + architecture = process.arch +): string | undefined { + const root = join(resourcesPath, 'playwright-browsers') + if (!existsSync(root)) return undefined + + const platformDirectory = architecture === 'arm64' ? 'chrome-mac-arm64' : 'chrome-mac-x64' + const revisions = readdirSync(root, { withFileTypes: true }) + .filter((entry) => entry.isDirectory() && entry.name.startsWith('chromium-')) + .map((entry) => entry.name) + .sort() + .reverse() + + return revisions + .map((revision) => + join( + root, + revision, + platformDirectory, + 'Google Chrome for Testing.app', + 'Contents', + 'MacOS', + 'Google Chrome for Testing' + ) + ) + .find((candidate) => existsSync(candidate)) +} + function localChromeExecutable(): string | undefined { const configured = process.env.TASKTAPE_CHROMIUM_EXECUTABLE if (configured && existsSync(configured)) return configured const candidates = [ + bundledChromiumExecutable(), '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', '/Applications/Google Chrome for Testing.app/Contents/MacOS/Google Chrome for Testing', chromium.executablePath() ] - return candidates.find((candidate) => existsSync(candidate)) + return candidates.find((candidate): candidate is string => + Boolean(candidate && existsSync(candidate)) + ) } export async function launchTaskTapeBrowser(): Promise { const executablePath = localChromeExecutable() if (!executablePath) { throw new Error( - 'TaskTape needs Google Chrome to open an agent debugging session. Install Chrome and try again.' + 'TaskTape could not start its browser. Reinstall TaskTape, or choose a Chrome executable with TASKTAPE_CHROMIUM_EXECUTABLE.' ) } return chromium.launch({ diff --git a/src/renderer/src/settings/AgentConnectionSettings.tsx b/src/renderer/src/settings/AgentConnectionSettings.tsx index c33424c..467c940 100644 --- a/src/renderer/src/settings/AgentConnectionSettings.tsx +++ b/src/renderer/src/settings/AgentConnectionSettings.tsx @@ -56,7 +56,9 @@ export function AgentConnectionSettings(): React.JSX.Element {

Agent connection

-

Connect Claude Code or Codex to the local Replay tools.

+

+ Connect Claude Code or Codex. TaskTape includes the browser and keeps sessions local. +

diff --git a/tests/e2e/mcp.spec.ts b/tests/e2e/mcp.spec.ts index 7e02239..c49a3bf 100644 --- a/tests/e2e/mcp.spec.ts +++ b/tests/e2e/mcp.spec.ts @@ -19,8 +19,11 @@ test('shares one agent-recorded bug session with the TaskTape desktop app', asyn if (!address || typeof address === 'string') throw new Error('Fixture server did not start.') const targetUrl = `http://127.0.0.1:${address.port}` + const packagedExecutable = process.env.TASKTAPE_PACKAGED_APP const application = await electron.launch({ - args: [resolve('.')], + ...(packagedExecutable + ? { executablePath: resolve(packagedExecutable) } + : { args: [resolve('.')] }), env: { ...process.env, TASKTAPE_E2E: '1', TASKTAPE_USER_DATA: userData } }) const page = await application.firstWindow()