[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6XCBM7B95PB3Q6PHZ3TCM7F":3,"skill-same:01M6XCBM7B95PB3Q6PHZ3TCM7F":80},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":31,"publishedAt":52,"linkOnly":53,"markdownContent":54,"markdownBody":55,"frontMatter":56,"refName":57,"commitSha":58,"skillDirectory":59,"forksCount":60,"repositoryHtmlUrl":61,"repositoryHomepageUrl":6,"ownerHtmlUrl":62,"ownerBlogUrl":63,"ownerDescription":64,"updatedAt":65,"install":66,"resources":79},"01M6XCBM7B95PB3Q6PHZ3TCM7F","web-debug",null,"Validate the workbuddy webui (SPA + auth + JSON API) end-to-end on the same machine workbuddy runs on, using browser-use's headless harness. Use when the user says 'verify the frontend', 'test the SPA', 'check dashboard loads', '前端验证', '验下 webui', or after merging changes that touch internal\u002Fwebui, web\u002F, or the auth\u002Fcookie path.","Lincyaw\u002Fworkbuddy","Lincyaw","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F53161583?v=4","01M6XC87YTNETZ3AARKMHXJYJR","Aoyang Fang","01M6XC882ZSY5XBX2W6S88AGNH",2,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy\u002Fblob\u002Fmain\u002Fplugins\u002Fworkbuddy\u002Fskills\u002Fweb-debug\u002FSKILL.md",[18,23,26],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口",{"categoryNo":27,"categoryName":28,"taxonomyType":29,"taxonomyTypeDesc":30},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51],"web","web debug","debug","end","frontend","when","using browser","browser-use","headless","loads","touch","machine","auth","spa","webui","use","check","api","harness","browser","2026-11-10 10:12:30",false,"---\nname: web-debug\ndescription: \"Validate the workbuddy webui (SPA + auth + JSON API) end-to-end on the same machine workbuddy runs on, using browser-use's headless harness. Use when the user says 'verify the frontend', 'test the SPA', 'check dashboard loads', '前端验证', '验下 webui', or after merging changes that touch internal\u002Fwebui, web\u002F, or the auth\u002Fcookie path.\"\n---\n\n# web-debug\n\nHeadless-server-friendly recipe for driving the workbuddy SPA through a real Chromium via [browser-use\u002Fbrowser-harness](https:\u002F\u002Fgithub.com\u002Fbrowser-use\u002Fbrowser-harness). The point is to verify, with screenshots, that the dashboard \u002F sessions pages actually render, login works, and the JSON API contracts hold — not just that `go test` passed.\n\n## When to use\n\n- After merging anything that touches `internal\u002Fwebui`, `web\u002F`, `internal\u002Fapp\u002Fauth_handlers.go`, or `internal\u002Fauditapi`.\n- Before cutting a release tag.\n- When the user reports a regression in the dashboard \u002F login \u002F sessions UI.\n- When you need to capture screenshots of the SPA state for an issue \u002F PR description.\n\nDo **not** use for unit-level concerns (`go test .\u002Finternal\u002Fwebui\u002F...` covers those). This skill exists for the integration story: SPA build → embed → serve → real browser → API.\n\n## Step 1 — Make sure browser-harness is installed\n\n```bash\ncommand -v browser-harness >\u002Fdev\u002Fnull || {\n  git clone https:\u002F\u002Fgithub.com\u002Fbrowser-use\u002Fbrowser-harness ~\u002FDeveloper\u002Fbrowser-harness\n  cd ~\u002FDeveloper\u002Fbrowser-harness && uv tool install -e .\n}\n```\n\nIf you've never used the harness on this machine before, also skim `~\u002FDeveloper\u002Fbrowser-harness\u002Finstall.md` and `SKILL.md` for the day-to-day API. The functions you'll actually call (`new_tab`, `goto_url`, `wait_for_load`, `type_text`, `press_key`, `capture_screenshot`, `page_info`) all live in `~\u002FDeveloper\u002Fbrowser-harness\u002Fsrc\u002Fbrowser_harness\u002Fhelpers.py` — read that file when an interaction is doing something unexpected.\n\n## Step 2 — Bring up Chromium in headless-server-safe mode\n\nThe default `browser-harness --setup` flow assumes a real graphical Chrome session and toggling the `chrome:\u002F\u002Finspect` checkbox. **On a headless \u002F SSH server that does not work**: Chrome will accept TCP on the debug port but hang HTTP\u002FWS handshakes, and `DevToolsActivePort` is never written. Symptoms:\n\n```\nRuntimeError: fatal: DevToolsActivePort not found in [...long list of profile dirs...]\n# or\nRuntimeError: fatal: CDP WS handshake failed: timed out during opening handshake\n```\n\nWorkaround: launch Chromium **headless** with an **isolated user-data-dir** and **`--remote-allow-origins='*'`**, then point the harness at the resulting WS URL via `BU_CDP_WS`.\n\n```bash\n# 1. Clean any stale Chrome \u002F harness state\npkill -9 -f \"remote-debugging\" 2>\u002Fdev\u002Fnull; sleep 1\nrm -f \u002Ftmp\u002Fbu-default.sock \u002Ftmp\u002Fbu-default.pid\n\n# 2. Launch headless Chrome with a dedicated profile directory\nmkdir -p ~\u002F.config\u002Fgoogle-chrome-bu\nnohup google-chrome \\\n    --headless=new \\\n    --remote-debugging-port=9222 \\\n    --remote-allow-origins='*' \\\n    --user-data-dir=$HOME\u002F.config\u002Fgoogle-chrome-bu \\\n    --disable-gpu --no-sandbox \\\n    > \u002Ftmp\u002Fchrome.log 2>&1 &\ndisown\nsleep 5\n\n# 3. Verify Chrome's CDP HTTP endpoint actually responds\ncurl -sS --max-time 5 http:\u002F\u002F127.0.0.1:9222\u002Fjson\u002Fversion | head\n# → must return JSON with \"webSocketDebuggerUrl\"; if it times out, see Pitfalls below\n\n# 4. Tell the harness exactly which WS to use (skips DevToolsActivePort discovery)\nexport BU_CDP_WS=$(curl -sS http:\u002F\u002F127.0.0.1:9222\u002Fjson\u002Fversion \\\n    | python3 -c \"import sys,json;print(json.load(sys.stdin)['webSocketDebuggerUrl'])\")\n\n# 5. Smoke-test the harness\nbrowser-harness -c 'print(page_info())'\n# → {'url': 'about:blank', 'title': '', ...}\n```\n\nWhy each flag matters:\n\n| Flag | Why |\n|---|---|\n| `--headless=new` | Avoids requiring a working `DISPLAY`. The non-`new` headless mode is deprecated and renders incorrectly on some recent Chromium builds. |\n| `--remote-allow-origins='*'` | Chrome ≥ 128 requires this; without it the port listens but every HTTP\u002FWS request hangs forever. |\n| `--user-data-dir=...` | Chrome refuses `--remote-debugging-port` on the **default** user-data-dir for security. You must pass an explicit, non-default dir. |\n| `--disable-gpu --no-sandbox` | Stable headless on Linux servers without GL or user-namespace setup. |\n| `BU_CDP_WS` | The harness's normal Chrome discovery scans a fixed list of profile directories looking for `DevToolsActivePort`. With an isolated user-data-dir it won't find anything; this env var bypasses discovery and points it at the WS directly. |\n\nThis setup is **disposable** — kill the Chrome PID and the dedicated user-data-dir is harmless. It will not interfere with the user's real Chrome profile.\n\n## Step 3 — Build and run workbuddy\n\n```bash\ncd $WORKBUDDY_REPO\n\n# Full build: SPA → embed → Go binary\nmake build\n\n# Start serve in the background. Pick a non-conflicting port; use a throwaway DB.\nTOKEN=\"testtoken123abc\"\nWORKBUDDY_AUTH_TOKEN=\"$TOKEN\" .\u002Fbin\u002Fworkbuddy serve \\\n    --listen 127.0.0.1:18090 \\\n    --auth \\\n    --report-base-url http:\u002F\u002F127.0.0.1:18090 \\\n    --db-path \u002Ftmp\u002Fwb-frontend-test.db \\\n    > \u002Ftmp\u002Fworkbuddy.log 2>&1 &\ndisown\nsleep 5\n\n# Confirm health and bearer auth\ncurl -sS http:\u002F\u002F127.0.0.1:18090\u002Fhealth\ncurl -sS -H \"Authorization: Bearer $TOKEN\" http:\u002F\u002F127.0.0.1:18090\u002Fapi\u002Fv1\u002Fstatus\n```\n\n`workbuddy serve` must run from a directory that has `.github\u002Fworkbuddy\u002F` (the workbuddy repo itself, or a configured target repo). The `--db-path` flag points the SQLite to a throwaway location so this run does not touch the real `.workbuddy\u002Fworkbuddy.db`.\n\n## Step 4 — Drive the SPA\n\nA normal end-to-end pass covers: 401 redirect → login form → dashboard → sessions.\n\n```bash\nmkdir -p \u002Ftmp\u002Fwb-shots\n\nbrowser-harness -c \"\n# 1. Hit root with no cookie → expect 302 to \u002Flogin\nnew_tab('http:\u002F\u002F127.0.0.1:18090\u002F')\nwait_for_load()\nprint('after-root:', page_info())     # url should be \u002Flogin?next=%2F\ncapture_screenshot(path='\u002Ftmp\u002Fwb-shots\u002F01-login.png')\n\n# 2. Submit token (input is auto-focused on the login page)\ntype_text('testtoken123abc')\npress_key('Enter')\nwait_for_load()\nprint('after-login:', page_info())    # should land on \u002F\ncapture_screenshot(path='\u002Ftmp\u002Fwb-shots\u002F02-dashboard.png')\n\n# 3. Click into Sessions\ngoto_url('http:\u002F\u002F127.0.0.1:18090\u002Fsessions')\nwait_for_load()\ncapture_screenshot(path='\u002Ftmp\u002Fwb-shots\u002F03-sessions.png')\n\"\n```\n\nRead each PNG with the `Read` tool to actually see what rendered. `page_info()`'s `title` and `url` are necessary but not sufficient — the UI can render blank with a happy title.\n\n## Step 5 — Verify the API contracts curl-side\n\nThese checks belong with the SPA pass because the SPA depends on them holding:\n\n```bash\nTOKEN=testtoken123abc\nBASE=http:\u002F\u002F127.0.0.1:18090\n\n# Cookie path (used by browser)\ncurl -sS -i -b \"wb_session=$TOKEN\" $BASE\u002Fapi\u002Fv1\u002Fstatus | head -3\n# Bearer path (used by CLI\u002Fcurl)\ncurl -sS -i -H \"Authorization: Bearer $TOKEN\" $BASE\u002Fapi\u002Fv1\u002Fstatus | head -3\n# HTML 401 → 302 \u002Flogin\ncurl -sS -i -H \"Accept: text\u002Fhtml\" $BASE\u002F | head -3\n# JSON 401 stays 401\ncurl -sS -i $BASE\u002Fapi\u002Fv1\u002Fstatus | head -3\n# Legacy session path still works but carries Deprecation header\ncurl -sS -i -H \"Authorization: Bearer $TOKEN\" \\\n    \"$BASE\u002Fsessions\u002Fabc\u002Fevents.json\" | grep -iE \"HTTP|deprecation|sunset|link\"\n# New \u002Fapi\u002Fv1\u002Fsessions\u002F{id}\u002Fevents should NOT carry Deprecation\ncurl -sS -i -H \"Authorization: Bearer $TOKEN\" \\\n    \"$BASE\u002Fapi\u002Fv1\u002Fsessions\u002Fabc\u002Fevents\" | grep -iE \"HTTP|deprecation\"\n# In-flight aggregator returns array (never null)\ncurl -sS -H \"Authorization: Bearer $TOKEN\" \\\n    $BASE\u002Fapi\u002Fv1\u002Fissues\u002Fin-flight | python3 -m json.tool | head -20\n```\n\nExpected outcomes:\n\n- Cookie + Bearer both → 200 (REQ-085 \u002F cookie auth)\n- HTML no-creds → 302 with `Location: \u002Flogin?next=%2F`\n- JSON no-creds → 401 `{\"error\":\"unauthorized\"}`\n- `\u002Fsessions\u002F{id}\u002Fevents.json` → 200 with `Deprecation: true` and `Link: \u003C\u002Fapi\u002Fv1\u002Fsessions\u002F{id}\u002Fevents>; rel=\"successor-version\"` (REQ-088)\n- `\u002Fapi\u002Fv1\u002Fsessions\u002F{id}\u002Fevents` → 200 with no `Deprecation`\n- `\u002Fapi\u002Fv1\u002Fissues\u002Fin-flight` → JSON array\n\n## Step 6 — Tear down\n\n```bash\npkill -9 -f \"workbuddy serve\" 2>\u002Fdev\u002Fnull\npkill -9 -f \"remote-debugging\" 2>\u002Fdev\u002Fnull\nrm -f \u002Ftmp\u002Fwb-frontend-test.db \u002Ftmp\u002Fworkbuddy.log\n# Keep ~\u002F.config\u002Fgoogle-chrome-bu around; reuses faster on next run.\n```\n\n## Pitfalls\n\n- **Chrome's `--remote-debugging-port` fails silently on the default profile.** The log says `DevTools remote debugging requires a non-default data directory. Specify this using --user-data-dir.` Always pass `--user-data-dir`.\n- **CDP HTTP times out on Chrome ≥ 128 without `--remote-allow-origins='*'`.** TCP connects fine, then sits forever. Add the flag.\n- **Stale `SingletonLock` from a previous session blocks new launches.** If Chrome refuses to start: `rm -f ~\u002F.config\u002Fgoogle-chrome-bu\u002FSingleton*`.\n- **Harness's profile-dir search misses an isolated user-data-dir.** Always set `BU_CDP_WS` for this setup, or it falls back to scanning standard profile paths and fails with `DevToolsActivePort not found`.\n- **`workbuddy serve` requires a config dir.** It looks for `.github\u002Fworkbuddy\u002F` in the current working directory. Run it from the workbuddy repo or a workbuddy-configured target repo.\n- **Build can be silently broken at the TS level.** `tsc -b` runs as part of `make web` and will fail with cryptic union errors (e.g. `Property 'current' does not exist on type ...`) if a Layout prop drift slipped through merge conflict resolution. Treat a `make build` failure as a real bug — do not skip the SPA build to avoid it.\n- **`page_info()` lies on broken pages.** A SPA that crashed during hydration may still report a happy title. Always pair `page_info()` with a `capture_screenshot()` and read the image.\n\n## Index — related skills and references\n\n- **browser-harness install** → `~\u002FDeveloper\u002Fbrowser-harness\u002Finstall.md` — first-time setup, attach-and-escalate flow, profile picker handling.\n- **browser-harness day-to-day API** → `~\u002FDeveloper\u002Fbrowser-harness\u002FSKILL.md` — `new_tab`, `goto_url`, `click_at_xy`, screenshot-driven workflow, remote browsers via `BU_NAME`.\n- **browser-harness function reference** → `~\u002FDeveloper\u002Fbrowser-harness\u002Fsrc\u002Fbrowser_harness\u002Fhelpers.py` — authoritative list of pre-imported helpers.\n- **interaction skills** → `~\u002FDeveloper\u002Fbrowser-harness\u002Finteraction-skills\u002F` — `dialogs.md`, `iframes.md`, `screenshots.md`, `viewport.md`, etc., when a specific UI mechanic is misbehaving.\n- **workbuddy webui contract source of truth** → `internal\u002Fauditapi\u002Fhandler.go` (JSON shapes) and `web\u002Fsrc\u002Fapi\u002F*.ts` (consumer types). When adding a new endpoint to validate, start there.\n- **workbuddy session viewer routing** → `cmd\u002Fcoordinator.go` mux registration block — confirms which paths are SPA-served vs. JSON-served vs. legacy aliases.\n\n## When NOT to use this skill\n\n- For just-built backend changes that don't touch HTTP\u002FJSON shape: `go test .\u002F...` is enough.\n- For pure documentation or skill updates: there's nothing to render.\n- For verifying the Codex\u002FClaude agent flow itself: use `pipeline-monitor` instead.\n","# web-debug\n\nHeadless-server-friendly recipe for driving the workbuddy SPA through a real Chromium via [browser-use\u002Fbrowser-harness](https:\u002F\u002Fgithub.com\u002Fbrowser-use\u002Fbrowser-harness). The point is to verify, with screenshots, that the dashboard \u002F sessions pages actually render, login works, and the JSON API contracts hold — not just that `go test` passed.\n\n## When to use\n\n- After merging anything that touches `internal\u002Fwebui`, `web\u002F`, `internal\u002Fapp\u002Fauth_handlers.go`, or `internal\u002Fauditapi`.\n- Before cutting a release tag.\n- When the user reports a regression in the dashboard \u002F login \u002F sessions UI.\n- When you need to capture screenshots of the SPA state for an issue \u002F PR description.\n\nDo **not** use for unit-level concerns (`go test .\u002Finternal\u002Fwebui\u002F...` covers those). This skill exists for the integration story: SPA build → embed → serve → real browser → API.\n\n## Step 1 — Make sure browser-harness is installed\n\n```bash\ncommand -v browser-harness >\u002Fdev\u002Fnull || {\n  git clone https:\u002F\u002Fgithub.com\u002Fbrowser-use\u002Fbrowser-harness ~\u002FDeveloper\u002Fbrowser-harness\n  cd ~\u002FDeveloper\u002Fbrowser-harness && uv tool install -e .\n}\n```\n\nIf you've never used the harness on this machine before, also skim `~\u002FDeveloper\u002Fbrowser-harness\u002Finstall.md` and `SKILL.md` for the day-to-day API. The functions you'll actually call (`new_tab`, `goto_url`, `wait_for_load`, `type_text`, `press_key`, `capture_screenshot`, `page_info`) all live in `~\u002FDeveloper\u002Fbrowser-harness\u002Fsrc\u002Fbrowser_harness\u002Fhelpers.py` — read that file when an interaction is doing something unexpected.\n\n## Step 2 — Bring up Chromium in headless-server-safe mode\n\nThe default `browser-harness --setup` flow assumes a real graphical Chrome session and toggling the `chrome:\u002F\u002Finspect` checkbox. **On a headless \u002F SSH server that does not work**: Chrome will accept TCP on the debug port but hang HTTP\u002FWS handshakes, and `DevToolsActivePort` is never written. Symptoms:\n\n```\nRuntimeError: fatal: DevToolsActivePort not found in [...long list of profile dirs...]\n# or\nRuntimeError: fatal: CDP WS handshake failed: timed out during opening handshake\n```\n\nWorkaround: launch Chromium **headless** with an **isolated user-data-dir** and **`--remote-allow-origins='*'`**, then point the harness at the resulting WS URL via `BU_CDP_WS`.\n\n```bash\n# 1. Clean any stale Chrome \u002F harness state\npkill -9 -f \"remote-debugging\" 2>\u002Fdev\u002Fnull; sleep 1\nrm -f \u002Ftmp\u002Fbu-default.sock \u002Ftmp\u002Fbu-default.pid\n\n# 2. Launch headless Chrome with a dedicated profile directory\nmkdir -p ~\u002F.config\u002Fgoogle-chrome-bu\nnohup google-chrome \\\n    --headless=new \\\n    --remote-debugging-port=9222 \\\n    --remote-allow-origins='*' \\\n    --user-data-dir=$HOME\u002F.config\u002Fgoogle-chrome-bu \\\n    --disable-gpu --no-sandbox \\\n    > \u002Ftmp\u002Fchrome.log 2>&1 &\ndisown\nsleep 5\n\n# 3. Verify Chrome's CDP HTTP endpoint actually responds\ncurl -sS --max-time 5 http:\u002F\u002F127.0.0.1:9222\u002Fjson\u002Fversion | head\n# → must return JSON with \"webSocketDebuggerUrl\"; if it times out, see Pitfalls below\n\n# 4. Tell the harness exactly which WS to use (skips DevToolsActivePort discovery)\nexport BU_CDP_WS=$(curl -sS http:\u002F\u002F127.0.0.1:9222\u002Fjson\u002Fversion \\\n    | python3 -c \"import sys,json;print(json.load(sys.stdin)['webSocketDebuggerUrl'])\")\n\n# 5. Smoke-test the harness\nbrowser-harness -c 'print(page_info())'\n# → {'url': 'about:blank', 'title': '', ...}\n```\n\nWhy each flag matters:\n\n| Flag | Why |\n|---|---|\n| `--headless=new` | Avoids requiring a working `DISPLAY`. The non-`new` headless mode is deprecated and renders incorrectly on some recent Chromium builds. |\n| `--remote-allow-origins='*'` | Chrome ≥ 128 requires this; without it the port listens but every HTTP\u002FWS request hangs forever. |\n| `--user-data-dir=...` | Chrome refuses `--remote-debugging-port` on the **default** user-data-dir for security. You must pass an explicit, non-default dir. |\n| `--disable-gpu --no-sandbox` | Stable headless on Linux servers without GL or user-namespace setup. |\n| `BU_CDP_WS` | The harness's normal Chrome discovery scans a fixed list of profile directories looking for `DevToolsActivePort`. With an isolated user-data-dir it won't find anything; this env var bypasses discovery and points it at the WS directly. |\n\nThis setup is **disposable** — kill the Chrome PID and the dedicated user-data-dir is harmless. It will not interfere with the user's real Chrome profile.\n\n## Step 3 — Build and run workbuddy\n\n```bash\ncd $WORKBUDDY_REPO\n\n# Full build: SPA → embed → Go binary\nmake build\n\n# Start serve in the background. Pick a non-conflicting port; use a throwaway DB.\nTOKEN=\"testtoken123abc\"\nWORKBUDDY_AUTH_TOKEN=\"$TOKEN\" .\u002Fbin\u002Fworkbuddy serve \\\n    --listen 127.0.0.1:18090 \\\n    --auth \\\n    --report-base-url http:\u002F\u002F127.0.0.1:18090 \\\n    --db-path \u002Ftmp\u002Fwb-frontend-test.db \\\n    > \u002Ftmp\u002Fworkbuddy.log 2>&1 &\ndisown\nsleep 5\n\n# Confirm health and bearer auth\ncurl -sS http:\u002F\u002F127.0.0.1:18090\u002Fhealth\ncurl -sS -H \"Authorization: Bearer $TOKEN\" http:\u002F\u002F127.0.0.1:18090\u002Fapi\u002Fv1\u002Fstatus\n```\n\n`workbuddy serve` must run from a directory that has `.github\u002Fworkbuddy\u002F` (the workbuddy repo itself, or a configured target repo). The `--db-path` flag points the SQLite to a throwaway location so this run does not touch the real `.workbuddy\u002Fworkbuddy.db`.\n\n## Step 4 — Drive the SPA\n\nA normal end-to-end pass covers: 401 redirect → login form → dashboard → sessions.\n\n```bash\nmkdir -p \u002Ftmp\u002Fwb-shots\n\nbrowser-harness -c \"\n# 1. Hit root with no cookie → expect 302 to \u002Flogin\nnew_tab('http:\u002F\u002F127.0.0.1:18090\u002F')\nwait_for_load()\nprint('after-root:', page_info())     # url should be \u002Flogin?next=%2F\ncapture_screenshot(path='\u002Ftmp\u002Fwb-shots\u002F01-login.png')\n\n# 2. Submit token (input is auto-focused on the login page)\ntype_text('testtoken123abc')\npress_key('Enter')\nwait_for_load()\nprint('after-login:', page_info())    # should land on \u002F\ncapture_screenshot(path='\u002Ftmp\u002Fwb-shots\u002F02-dashboard.png')\n\n# 3. Click into Sessions\ngoto_url('http:\u002F\u002F127.0.0.1:18090\u002Fsessions')\nwait_for_load()\ncapture_screenshot(path='\u002Ftmp\u002Fwb-shots\u002F03-sessions.png')\n\"\n```\n\nRead each PNG with the `Read` tool to actually see what rendered. `page_info()`'s `title` and `url` are necessary but not sufficient — the UI can render blank with a happy title.\n\n## Step 5 — Verify the API contracts curl-side\n\nThese checks belong with the SPA pass because the SPA depends on them holding:\n\n```bash\nTOKEN=testtoken123abc\nBASE=http:\u002F\u002F127.0.0.1:18090\n\n# Cookie path (used by browser)\ncurl -sS -i -b \"wb_session=$TOKEN\" $BASE\u002Fapi\u002Fv1\u002Fstatus | head -3\n# Bearer path (used by CLI\u002Fcurl)\ncurl -sS -i -H \"Authorization: Bearer $TOKEN\" $BASE\u002Fapi\u002Fv1\u002Fstatus | head -3\n# HTML 401 → 302 \u002Flogin\ncurl -sS -i -H \"Accept: text\u002Fhtml\" $BASE\u002F | head -3\n# JSON 401 stays 401\ncurl -sS -i $BASE\u002Fapi\u002Fv1\u002Fstatus | head -3\n# Legacy session path still works but carries Deprecation header\ncurl -sS -i -H \"Authorization: Bearer $TOKEN\" \\\n    \"$BASE\u002Fsessions\u002Fabc\u002Fevents.json\" | grep -iE \"HTTP|deprecation|sunset|link\"\n# New \u002Fapi\u002Fv1\u002Fsessions\u002F{id}\u002Fevents should NOT carry Deprecation\ncurl -sS -i -H \"Authorization: Bearer $TOKEN\" \\\n    \"$BASE\u002Fapi\u002Fv1\u002Fsessions\u002Fabc\u002Fevents\" | grep -iE \"HTTP|deprecation\"\n# In-flight aggregator returns array (never null)\ncurl -sS -H \"Authorization: Bearer $TOKEN\" \\\n    $BASE\u002Fapi\u002Fv1\u002Fissues\u002Fin-flight | python3 -m json.tool | head -20\n```\n\nExpected outcomes:\n\n- Cookie + Bearer both → 200 (REQ-085 \u002F cookie auth)\n- HTML no-creds → 302 with `Location: \u002Flogin?next=%2F`\n- JSON no-creds → 401 `{\"error\":\"unauthorized\"}`\n- `\u002Fsessions\u002F{id}\u002Fevents.json` → 200 with `Deprecation: true` and `Link: \u003C\u002Fapi\u002Fv1\u002Fsessions\u002F{id}\u002Fevents>; rel=\"successor-version\"` (REQ-088)\n- `\u002Fapi\u002Fv1\u002Fsessions\u002F{id}\u002Fevents` → 200 with no `Deprecation`\n- `\u002Fapi\u002Fv1\u002Fissues\u002Fin-flight` → JSON array\n\n## Step 6 — Tear down\n\n```bash\npkill -9 -f \"workbuddy serve\" 2>\u002Fdev\u002Fnull\npkill -9 -f \"remote-debugging\" 2>\u002Fdev\u002Fnull\nrm -f \u002Ftmp\u002Fwb-frontend-test.db \u002Ftmp\u002Fworkbuddy.log\n# Keep ~\u002F.config\u002Fgoogle-chrome-bu around; reuses faster on next run.\n```\n\n## Pitfalls\n\n- **Chrome's `--remote-debugging-port` fails silently on the default profile.** The log says `DevTools remote debugging requires a non-default data directory. Specify this using --user-data-dir.` Always pass `--user-data-dir`.\n- **CDP HTTP times out on Chrome ≥ 128 without `--remote-allow-origins='*'`.** TCP connects fine, then sits forever. Add the flag.\n- **Stale `SingletonLock` from a previous session blocks new launches.** If Chrome refuses to start: `rm -f ~\u002F.config\u002Fgoogle-chrome-bu\u002FSingleton*`.\n- **Harness's profile-dir search misses an isolated user-data-dir.** Always set `BU_CDP_WS` for this setup, or it falls back to scanning standard profile paths and fails with `DevToolsActivePort not found`.\n- **`workbuddy serve` requires a config dir.** It looks for `.github\u002Fworkbuddy\u002F` in the current working directory. Run it from the workbuddy repo or a workbuddy-configured target repo.\n- **Build can be silently broken at the TS level.** `tsc -b` runs as part of `make web` and will fail with cryptic union errors (e.g. `Property 'current' does not exist on type ...`) if a Layout prop drift slipped through merge conflict resolution. Treat a `make build` failure as a real bug — do not skip the SPA build to avoid it.\n- **`page_info()` lies on broken pages.** A SPA that crashed during hydration may still report a happy title. Always pair `page_info()` with a `capture_screenshot()` and read the image.\n\n## Index — related skills and references\n\n- **browser-harness install** → `~\u002FDeveloper\u002Fbrowser-harness\u002Finstall.md` — first-time setup, attach-and-escalate flow, profile picker handling.\n- **browser-harness day-to-day API** → `~\u002FDeveloper\u002Fbrowser-harness\u002FSKILL.md` — `new_tab`, `goto_url`, `click_at_xy`, screenshot-driven workflow, remote browsers via `BU_NAME`.\n- **browser-harness function reference** → `~\u002FDeveloper\u002Fbrowser-harness\u002Fsrc\u002Fbrowser_harness\u002Fhelpers.py` — authoritative list of pre-imported helpers.\n- **interaction skills** → `~\u002FDeveloper\u002Fbrowser-harness\u002Finteraction-skills\u002F` — `dialogs.md`, `iframes.md`, `screenshots.md`, `viewport.md`, etc., when a specific UI mechanic is misbehaving.\n- **workbuddy webui contract source of truth** → `internal\u002Fauditapi\u002Fhandler.go` (JSON shapes) and `web\u002Fsrc\u002Fapi\u002F*.ts` (consumer types). When adding a new endpoint to validate, start there.\n- **workbuddy session viewer routing** → `cmd\u002Fcoordinator.go` mux registration block — confirms which paths are SPA-served vs. JSON-served vs. legacy aliases.\n\n## When NOT to use this skill\n\n- For just-built backend changes that don't touch HTTP\u002FJSON shape: `go test .\u002F...` is enough.\n- For pure documentation or skill updates: there's nothing to render.\n- For verifying the Codex\u002FClaude agent flow itself: use `pipeline-monitor` instead.\n",{"name":5,"description":7},"main","b9778c800f442939b879c9a556dfb8d6f4a40971","plugins\u002Fworkbuddy\u002Fskills\u002Fweb-debug",1,"https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy","https:\u002F\u002Fgithub.com\u002FLincyaw","http:\u002F\u002Fprofile.aoyangfang.top\u002F","Student in The Chinese University of Hong Kong, Shenzhen","2026-06-08 13:33:51",{"source":8,"skillName":5,"commands":67,"prompt":77,"zipAvailable":78},[68,71,74],{"tool":69,"command":70},"npx","npx skills add Lincyaw\u002Fworkbuddy --skill web-debug",{"tool":72,"command":73},"bunx","bunx skills add Lincyaw\u002Fworkbuddy --skill web-debug",{"tool":75,"command":76},"pnpm","pnpm dlx skills add Lincyaw\u002Fworkbuddy --skill web-debug","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy\u002Fblob\u002Fmain\u002Fplugins\u002Fworkbuddy\u002Fskills\u002Fweb-debug\u002FSKILL.md",true,[],[81,108,135,154,180,201],{"skillNo":82,"name":83,"nameZh":6,"description":84,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":85,"categories":86,"keywords":87,"publishedAt":52},"01M6XCBSW1MMEAWEF38VX9NH3E","workbuddy-guide","Explain and operate workbuddy: what it is, which command to reach for, and where to find deeper references. **Trigger whenever the user mentions 'workbuddy' in any context** — including operational requests (register\u002Fonboard a repo, add\u002Fbind a worker, 注册仓库, 加仓库, 接入新仓库, onboard repo, set up worker), explanatory questions (how to use workbuddy, workbuddy guide, teach me workbuddy, 怎么用workbuddy, 使用指南), deployment\u002Frunning\u002Foperating\u002Fdebugging workbuddy, and questions about how issues\u002Ftasks are scheduled or how dependencies between issues work (depends_on, blocked, dependency gate, 依赖, 调度, 排队, '为什么没派发'), and questions about parallel-development workflows (rollouts:N fan-out, synthesize\u002Fsynthesis reduce step, cherry-pick, '并行开发', '多个方案', 'rollout'). Invoke this skill before running workbuddy CLI commands so the documented onboarding flow (coordinator register + worker repos add) and the dependency\u002Fscheduling model aren't re-derived from scratch.","https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy\u002Fblob\u002Fmain\u002Fplugins\u002Fworkbuddy\u002Fskills\u002Fworkbuddy-guide\u002FSKILL.md",[],[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107],"guide","workbuddy guide","debugging","issues","SET","model","pick","up","rollouts","deeper","worker","bind","how to","operating","coordinator","re","operate","which","scheduled","invoke",{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":117,"publishedAt":52},"01M6XCBFR925WJSWEYVHGSTT56","setup-repo","Configure a GitHub repository for workbuddy orchestration — creates labels, the two canonical agent configs (dev + review), workflow definitions, issue templates, and config.yaml. Use when the user says 'set up repo', 'configure repo for workbuddy', 'initialize workbuddy', '配置仓库', '初始化workbuddy', or wants to onboard a new repo.","https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy\u002Fblob\u002Fmain\u002Fplugins\u002Fworkbuddy\u002Fskills\u002Fsetup-repo\u002FSKILL.md",[114],{"categoryNo":115,"categoryName":116,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[118,119,120,121,122,123,124,125,126,92,127,128,95,129,130,131,132,133,134,37],"setup","setup repo","repo","review","orchestration","workflow","config","dev","repository","configure","initialize","issue","new repo","初始化","canonical","for","labels",{"skillNo":136,"name":137,"nameZh":6,"description":138,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":139,"categories":140,"keywords":141,"publishedAt":52},"01M6XCBAEPZQTAS5FZYCPDEGAG","pipeline-monitor","Monitor the workbuddy agent pipeline, detect stuck issues, zombie processes, and missed redispatches. Works for both the 3-unit bundle (supervisor + coordinator + worker) and single-process `serve`. Use when the user says 'monitor the pipeline', 'check workbuddy status', '监工', '看看跑完了没', or asks to watch issue progress.","https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy\u002Fblob\u002Fmain\u002Fplugins\u002Fworkbuddy\u002Fskills\u002Fpipeline-monitor\u002FSKILL.md",[],[142,143,144,145,91,146,48,47,129,147,98,148,149,150,151,102,152,133,153,37],"monitor","pipeline","pipeline monitor","status","supervisor","missed","watch","progress","detect","unit","agent pipeline","single",{"skillNo":155,"name":156,"nameZh":6,"description":157,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":158,"categories":159,"keywords":163,"publishedAt":52},"01M6XCB5RTYNH7QK9RQ5Z4B6NK","merge-flow","Merge a batch of approved workbuddy PRs into main: order them, resolve conflicts against design intent, validate post-merge health, and report. Use when the user asks to 'merge the batch', 'merge approved PRs', 'integrate finished issues', '批量合并', '处理冲突'; or when the merge-agent is invoked.","https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy\u002Fblob\u002Fmain\u002Fplugins\u002Fworkbuddy\u002Fskills\u002Fmerge-flow\u002FSKILL.md",[160],{"categoryNo":161,"categoryName":162,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",[164,165,166,167,168,169,91,170,171,172,47,173,174,175,176,57,177,178,179,37],"merge","flow","merge flow","design","validate","report","batch","health","user","integrate","resolve conflicts","conflicts","intent","post","design intent","prs",{"skillNo":181,"name":182,"nameZh":6,"description":183,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":184,"categories":185,"keywords":189,"publishedAt":52},"01M6XCB29F96RJ02Z3BXTAVYSB","deploy","Workbuddy deployment topology guide — explains the 3-unit bundle (supervisor + coordinator + worker), the rolling-restart property, and the cutover from the legacy single-process `serve` install. Use when the user says 'how should I deploy workbuddy', 'install workbuddy as a service', 'switch to bundle', 'rolling restart', '部署 workbuddy', '升级到 bundle'.","https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy\u002Fblob\u002Fmain\u002Fplugins\u002Fworkbuddy\u002Fskills\u002Fdeploy\u002FSKILL.md",[186],{"categoryNo":187,"categoryName":188,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署",[182,190,191,192,146,193,194,88,47,195,196,98,197,198,199,200,151,102,153,37],"deployment","topology","how","process","install","Legacy","switch","service","as","bundle","serve",{"skillNo":202,"name":203,"nameZh":6,"description":204,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":205,"categories":206,"keywords":207,"publishedAt":52},"01M6XCB0SEG4W4RTFHFGXS7D51","example-skill","This description is intentionally long enough to satisfy the validator reliably.","https:\u002F\u002Fgithub.com\u002FLincyaw\u002Fworkbuddy\u002Fblob\u002Fmain\u002Finternal\u002Fvalidatedocs\u002Ftestdata\u002Fl4-pass\u002F.codex\u002Fskills\u002Fexample-skill\u002FSKILL.md",[],[208,209,210,211],"example skill","example","long","validator"]