Agent Skills › opencoredev/convt › test-convt-web

test-convt-web

GitHub

用于在本地运行和验证 convt.app 网站及计费 Worker,支持 Postgres、OAuth 模拟等依赖,覆盖桌面与移动端视图,适用于相关代码变更或预览检查。

.agents/skills/test-convt-web/SKILL.md opencoredev/convt

Trigger Scenarios

修改 apps/web, apps/billing 等核心应用代码 需要预览、截图或检查网站功能

Install

npx skills add opencoredev/convt --skill test-convt-web -g -y
More Options

Non-standard path

npx skills add https://github.com/opencoredev/convt/tree/main/.agents/skills/test-convt-web -g -y

Use without installing

npx skills use opencoredev/convt@test-convt-web

指定 Agent (Claude Code)

npx skills add opencoredev/convt --skill test-convt-web -a claude-code -g -y

安装 repo 全部 skill

npx skills add opencoredev/convt --all -g -y

预览 repo 内 skill

npx skills add opencoredev/convt --list

SKILL.md

Frontmatter
{
    "name": "test-convt-web",
    "description": "Run and verify the convt.app website (apps\/web, TanStack Start on Cloudflare Workers) and its billing Worker (apps\/billing) locally, with Postgres, Mailpit, the OAuth mock and the Polar and Resend mock, signed in as a fixture account, in a browser at desktop and mobile widths. Use when changing anything under apps\/web, apps\/billing, packages\/db, packages\/billing, packages\/mail, packages\/license, tools\/oauth-mock or tools\/billing-mock, or when asked to preview, screenshot or check the site."
}

Test the convt website

apps/web is a TanStack Start app built with Vite and the Cloudflare plugin, deployed with Wrangler as the convt-web Worker. apps/billing is the convt-billing Worker: the Polar webhook route, the billing crons, and the BillingRpc entrypoint the site calls through its BILLING service binding. It holds every billing secret and the license signing key; the site holds none. Every page follows the OS light or dark setting: the root route's inline script sets the dark class on <html> before first paint and useSystemTheme keeps it in sync (src/components/app/theme.ts); only /brand pins a theme (staticData.theme, rendered as data-theme). So the landing page, sign-in, /download, /device, checkout and the dashboard each need light and dark screenshots (agent-browser set media light|dark). /download asks for an account: signed out it redirects to /sign-in?redirect=%2Fdownload, the landing page's Get convt and the nav's Download link there directly when signed out, and a new sign-up lands back on /download. The page leads with one Download for <system> button for the User-Agent's system (?os=macos|windows|linux overrides it) and a meta line such as Apple silicon · v0.3.0 · 50.0 MB, then Install with Homebrew for macOS visitors (a one-line-per-command block with Copy), every platform as rows, and checksums with the build's source under Verify your download (src/components/site/download.tsx, test/unit/download.test.tsx). The account pages (/sign-in, /dashboard, /dashboard/licenses, /dashboard/billing, /dashboard/api, /account) use Better Auth and read Postgres through the HYPERDRIVE binding. Locally everything runs on this machine: a Postgres and a Mailpit container per checkout, an OAuth mock that stands in for GitHub and Google, and a billing mock (tools/billing-mock) that speaks the parts of Polar's API and Resend's /emails that convt-billing calls, serves Polar-like checkout and portal pages, and signs webhooks with Polar's real scheme. No real email, OAuth app, payment account or database is ever used, and convt-billing signs keys with the local dev key in .convt-dev/license.key.

Doctor

docker info --format '{{.ServerVersion}}'     # Docker must run; no sudo needed
bash scripts/db.sh status                      # this checkout's containers and ports
ss -ltnp 'sport = :3000'                       # who owns the default web port

scripts/db.sh names its containers convt-pg-<hash> and convt-mail-<hash> after a hash of the checkout path and labels them convt.checkout=<path>, so other checkouts and other projects' Postgres containers are never touched. Ports are random on 127.0.0.1; .convt-dev/services.env (mode 0600, gitignored) records them with generated passwords and BETTER_AUTH_SECRET.

Launch

work=$(mktemp -d /tmp/convt-web.XXXXXX)
setsid bun run dev:web >"$work/dev.log" 2>&1 </dev/null &
sleep 1; ps -o pgid= -p $! | tr -d ' ' >"$work/pgid"
for _ in $(seq 90); do grep -q 'dev-web: http' "$work/dev.log" && break; sleep 1; done
origin=$(grep -o 'dev-web: http://localhost:[0-9]*' "$work/dev.log" | cut -d' ' -f2)
curl -fsS "$origin/api/auth/ok"                # {"ok":true}
bun run db:seed                                # fixture accounts; safe to repeat

bun run dev:web runs scripts/dev-web.sh: db.sh up, migrations, the OAuth mock on OAUTH_MOCK_PORT, the billing mock on BILLING_MOCK_PORT (delivering webhooks to the site's /webhooks/polar), apps/web/.dev.vars and apps/billing/.dev.vars from services.env, then Vite on 3000 or the next free port with BETTER_AUTH_URL set to match. convt-billing runs inside Vite as an auxiliary Worker; outside production the site forwards /webhooks/* and /__billing/* to it, because an auxiliary Worker has no port of its own. After seeding, mirror the fixtures into the billing mock with bun tools/billing-mock/src/preload.ts (it is in-memory, so repeat it after every restart). Readiness is /api/auth/ok returning 200. Server-side errors appear in dev.log, not the browser console. setsid puts Vite and the mock in one process group so cleanup can stop exactly those.

For a production-like check, build and serve the Worker with Wrangler. Pass the database through the Hyperdrive variable and point BETTER_AUTH_URL at Wrangler's port:

set -a; . .convt-dev/services.env; set +a
bun run build
cd apps/web && CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE=$DATABASE_URL \
  setsid bunx wrangler dev --port 8788 --ip 127.0.0.1 --var BETTER_AUTH_URL:http://localhost:8788 &

It reads the other values from .dev.vars, which the build copies into dist/server. Never run bun run deploy or wrangler deploy without the user's explicit authorization.

Sign in as a fixture

All fixtures are on the reserved .test domain and exist only in local databases:

Email Shows
new@convt.test Signed in, bought nothing.
trial@convt.test Pro monthly trial ending in 3 days, trial key.
desktop@convt.test Desktop order, key, invoice and one Mac.
pro@convt.test Pro yearly, older Desktop key, two Macs, API with two keys and usage, GitHub linked.
lapsed@convt.test Pro ended last month, last key, a failed invoice.
api@convt.test API only: one key, usage, six failed jobs.
unclaimed@convt.test No account: a Desktop purchase that signing up with this address claims.
refunded@convt.test A Desktop purchase refunded in full: the key shows REFUNDED.
pastdue@convt.test Pro monthly whose renewal failed: past due, last month's key.
disputed@convt.test A Desktop purchase with a lost chargeback: the key shows DISPUTED.
apipending@convt.test API enrollment waiting for a card (pending).

trial@ has no key (a trial is not paid coverage); a database seeded before P7 keeps its old trial key, because licenses are never deleted. Every subscription fixture has payment coverage and a billing_customers row with mock ids.

Sign in at /sign-in with the email, then read the code from Mailpit (its web UI is $MAILPIT_URL):

curl -fsS "$MAILPIT_URL/api/v1/search?query=to:pro@convt.test" \
  | python3 -c 'import json,re,sys; print(re.search(r"\d{6}", json.load(sys.stdin)["messages"][0]["Subject"]).group())'

Each address may receive 3 codes per 15 minutes and each IP 10 per hour; every local request comes from the same IP. To clear the limits in this checkout's own database: bash scripts/db.sh psql owner -c 'delete from otp_send_limits; delete from rate_limits'.

GitHub and Google buttons go to the OAuth mock's authorize page, which lists its identities: google-gmail and google-workspace (verified addresses), google-thirdparty and google-unverified (must confirm by code), github-verified, github-public-differs, github-no-email, github-pro (signs in to pro@convt.test), and error (access denied). Append &identity=<name> to the authorize URL to skip the page and &email=<address> to sign up with another address.

For a Pro login in one step, use the github-pro identity: Sign in, GitHub, then github-pro lands on pro@convt.test's dashboard (Pro yearly, a key, API usage) with no code to read. To sign a desktop build in to a fixture without the browser, bun run dev:sign-in <fixture> --config <dir> writes the app's account.json; see "Sign-in and renewal" in test-convt-desktop. test/integration/dev-sign-in.test.ts checks that its token renews pro@ with the Pro key, gets a cloud credential only for paid Pro, and that it refuses anything but a seeded fixture (an account that took a fixture's address included), unverified accounts and replacing a sign-in unasked.

Drive and evidence

Use headless agent-browser for UI verification, with a named session and its own persistent profile. Put both --session and --profile on every invocation. Never use a personal browser profile. UI work includes opening the real page and inspecting desktop and mobile screenshots in both themes.

The repeatable checks are scripts in apps/web/e2e, each printing PASS or FAIL lines and saving screenshots:

cd apps/web
E2E_SESSION=convt-web E2E_SHOTS=$work/shots bash e2e/sign-in.sh   # code, emailed link, OAuth identities, verify-email
E2E_SESSION=convt-web E2E_SHOTS=$work/shots bash e2e/fixtures.sh  # every fixture, 5 pages, 1280 and 390 px, light and dark
E2E_SESSION=convt-web E2E_SHOTS=$work/shots bash e2e/account.sh   # activate, copy key, rename, change email, connect, sign out
E2E_SESSION=convt-web E2E_SHOTS=$work/shots bash e2e/billing.sh   # checkout, keys, trial, switch, refund, API, emails, deletion

They read the site origin from apps/web/.dev.vars; set E2E_URL to test Wrangler instead. billing.sh drives the mock's hosted checkout and its /admin/* endpoints (end a trial, renew, fail a card, refund, change settings), runs crons through $E2E_URL/__billing/scheduled?cron=..., and screenshots each billing screen and the four emails (Mailpit's /view/<id>.html) in both themes. Run it once per fresh dev:web: it changes mock settings and deletes an account. account.sh calls a source module directly to prove getLicenseKey refuses another user's license, which only works under Vite. A layout change needs desktop and 390 px screenshots in both themes; look at each one with the Read tool.

Activate opens convt://activate?key=<token> through a temporary link. account.sh stubs HTMLAnchorElement.prototype.click to check the URL, because Chrome lets no page script stub location. Opening the link in the real desktop app is a GUI check (see test-convt-desktop): register the handler with integrations/linux/install.py and use CONVT_LICENSE_STORE=file with HOME and the XDG directories in a mktemp directory. Seeded keys are signed with .convt-dev/license.key, whose public half local builds embed.

Desktop sign-in

/device is the page the desktop app opens to sign in (P8). It needs a signed-in, verified session. A signed-out visit without provider goes through /sign-in and comes back; &provider=google (from the app's Continue with Google) goes straight to Google and back, and &provider=email shows the email step on the page itself, focused. The return trip drops provider, so it never restarts sign-in. Approve stores a five-minute one-time code in verifications and opens convt://auth?state=...&code=...; Cancel opens ...&error=access_denied. The app then calls POST /api/device/token (code and verifier), POST /api/device/license (bearer device token; returns the Pro key from convt-billing's currentProKey and access: pro, trial with ends_on and ends_at, can_start_trial with checkout_url /checkout/pro, or lapsed) and POST /api/device/sign-out. A new account gets can_start_trial with checkout_url /checkout/pro?from=app; convt-billing carries from=app to the success page, which then says "Your trial is on" and sends the buyer back to the app with no download step (a checkout from the web keeps the download). The mock checkout's Start trial makes it trial. /api/device/license allows 90 asks an hour per device, for the app's fast trial poll. These routes use no cookies and skip the Origin check; the logic and limits are in src/server/device-auth.ts. test/integration/device.test.ts covers the flow, one-use codes, revocation, cross-account isolation and rate limits. A full run with the real app is in test-convt-desktop, "Sign-in and renewal". Signed-in devices appear under Macs on the dashboard, where Sign out revokes their token.

Billing locally

  • The mock's admin API: curl -X POST $BILLING_MOCK_URL/admin/<name> -d '{...}' with state (GET), clock (advanceDays), trial-end, renew, card (decline), retry-payment, refund (order_id, amount), dispute (open, then lost/won/prevented), settings (allow_multiple_subscriptions), product (price drift), webhooks (mode auto or hold, dropNext, duplicate, delayMs, reorder, forgeNext, scheme standard or legacy, url), flush, resend-fault (timeout, delay, 5xx), complete-checkout. Polar sends no dispute webhook; disputes arrive through the reconciler (cron=*/15 * * * *).
  • Crons: curl "$origin/__billing/scheduled?cron=*+*+*+*+*" drains the outbox and advances deletions; *%2F15+*+*+*+* runs the reconciler; 17+3+*+*+* the daily check and digest (to alerts@convt.test in Mailpit).
  • bun run billing:outbox list shows ambiguous and dead emails; resolve <id> sent|resend records Leo's decision.
  • The second dev mode is cd apps/billing && CLOUDFLARE_HYPERDRIVE_LOCAL_CONNECTION_STRING_HYPERDRIVE_BILLING=$BILLING_DATABASE_URL bunx wrangler dev --port 8790 --test-scheduled, with the mock's webhook URL set to it (admin/webhooks url). The site under wrangler dev reaches it through the dev registry; on a machine short of file watches (EMFILE in the log) that registry never finishes starting.
  • Billing Worker settings and guards: packages/billing/src/env.ts. In production it refuses loopback provider or mail URLs and a signing key that does not match LICENSE_PUBLIC_KEY or is a dev key.

Tests

cd apps/web && bun test test/unit                              # views, redirects, Origin, redactor, env, table shape
bun run --cwd apps/web test:integration                         # Better Auth in process against a throwaway Postgres
bun run db:ci                                                   # schema drift, down files, all integration tests, convt-server
cd packages/billing && bun test test/unit && bun run test:integration   # verifier, catalog, guards; ingest, keys, outbox, reconciler, deletion
cd tools/billing-mock && bun test                               # the mock through the real SDK client, validateEvent, Resend idempotency
cd packages/mail && bun test                                    # template snapshots and escaping, Resend outcomes

The billing integration tests run the mock in process through the real Polar SDK client, as convt_billing against a disposable database, with an injected clock; one test builds convt-cli and activates an issued key with CONVT_LICENSE_STORE=file in a temporary HOME. A change to an email template changes its snapshot: bump templateVersion in packages/mail/src/templates.ts with it.

Integration tests and db:ci start their own labeled tmpfs Postgres and remove it by id on exit.

Preview for another device

Secure cookies are off in development, but the sign-in origin must be the one the device uses. Start with PUBLIC_ORIGIN=http://<tailnet-host>:<port> PORT=<port> MOCK_HOST=0.0.0.0 MOCK_PUBLIC_URL=http://<tailnet-host>:<mock-port> bun run dev:web, which binds Vite to all interfaces and sets BETTER_AUTH_URL and the mock's authorize URL to those addresses. This path was not exercised when P6 was verified, so check sign-in end to end the first time. Verify the URL from the device that will open it, and report which devices actually loaded the page. Read the remote-preview skill before starting or sharing a preview. Bind to the verified Tailscale address and report which devices actually loaded the page.

Checks

bun run check && bun run check-types && bun run build

Cleanup

kill -- -"$(cat "$work/pgid")" stops Vite and the OAuth mock you started (only that process group). Close browser sessions with agent-browser --session <name> close. The containers stay for reuse; bun run db:down removes this checkout's containers and its database volume (after the ownership guard), and bash scripts/db.sh prune removes those of checkouts that no longer exist.

P9 cloud converter and API keys

Read test-convt-server and docs/p9-cloud-plan.md to start the API, MinIO and sandbox worker. Configure CONVT_API_URL and the shared CONVT_WEB_TOKEN_SECRET in the local web Worker. Use the task session convt-p9 and profile ~/.agent-browser/profiles/convt-p9, on every command.

Check /dashboard/api: create a named key, inspect the shown-once field, dismiss it, reload, revoke it and verify a revoked key fails authentication. Inspect spend, reservations, cap reached and not-enrolled states. Check /dashboard/api/convert: pick a real file, choose a supported target, observe upload and job progress, download and inspect the result. Test malformed input, cancellation, an unavailable cancel endpoint and the retry button, the 2 GB file guard, exhausted monthly allowance, unpaid and trial states. Temporary reservations used to reach a limit must be cancelled afterward; restore any temporary cap only if it still has the value the test set. Never rewrite settled usage to stage a screenshot.

Capture desktop and 390 px screenshots in light and dark, inspect each image, and verify no horizontal overflow. Check keyboard focus and accessible names. Save P9 evidence in /home/leo/projects/convt-ui-brief/screens/p9/. If Vite hits EMFILE, restart only the owned process with CHOKIDAR_USEPOLLING=1. Hot reload can reset a selected file; finish source edits before the final browser run. Production Worker, Railway Buckets CORS and real payment enrollment remain separate launch checks.

Billing security regressions

Run bun test apps/billing/test to verify the public webhook body's streaming reader. A body without Content-Length must stop and cancel when it crosses 256 KiB, exact-limit raw bytes must survive unchanged, and non-POST requests must consume no body. The billing integration deletion case includes Pro usage and pending API usage: only the API fact for the subscription being revoked may delay deletion. bun audit and the database dependency test must reject vulnerable esbuild versions; bun run db:ci checks the overridden loader against real migration tooling.

Version History

  • 1920b04 Current 2026-10-18 15:25

Same Skill Collection

.agents/skills/test-convt-cli/SKILL.md
.agents/skills/test-convt-desktop/SKILL.md
.agents/skills/test-convt-server/SKILL.md
crates/convt-cli/SKILL.md

Metadata

Files
0
Version
1920b04
Hash
f4a4bfac
Indexed
2026-10-18 15:25

홈 - 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-10-19 07:56
浙ICP备14020137号-1