Skip to content

Commit eb1b987

Browse files
os-zhuangclaude
andauthored
chore(dx): add internal dogfood-verification Claude Code skill (#2259)
Crystallizes the browser-dogfooding process (isolated worktree + own port, dist-vs-src build model, visual/API-first verification, isolated-PR flow) into a reusable, invokable Claude Code skill at `.claude/skills/dogfood-verification/SKILL.md`. Internal agent tooling only — lives under `.claude/` (git-tracked, team-shared), deliberately NOT under the customer-published `skills/` directory, so it never enters the generated skill index, any npm package, or the docs site. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 37b7f83 commit eb1b987

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

  • .claude/skills/dogfood-verification
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
---
2+
name: dogfood-verification
3+
description: >
4+
Internal process for dogfooding the ObjectStack platform — booting a real
5+
example app (showcase / CRM) and driving it in a browser as a real user or
6+
admin to find runtime bugs that static checks and unit tests miss, then
7+
fixing and shipping cleanly. Use whenever the task is "verify in the browser",
8+
"act as a real admin/user", "dogfood the Setup/Studio app", or browser-verify
9+
a change in the running app. NOT a customer-published skill — this is internal
10+
agent tooling (lives in .claude/, never in the published `skills/` dir).
11+
---
12+
13+
# Dogfood verification
14+
15+
Hard-won process for booting and driving the real app. Three pillars: **isolate
16+
the environment**, **know the build model**, **verify visually/authoritatively
17+
before asserting**. Skipping pillar 1 or 3 is where time gets burned.
18+
19+
## 0. Pre-flight — isolate the environment (do this FIRST)
20+
21+
The dev working tree, the dev-server port, and the preview browser are all
22+
**shared**: a parallel Claude/dogfood session will yank the browser tab, leave
23+
unsaved drafts that block navigation, and dirty the working tree. Isolate up front:
24+
25+
- [ ] **Own port**: pick a free, non-default port (NOT 3000/3001/3210). Check first:
26+
`lsof -nP -iTCP:<port> -sTCP:LISTEN`. Add a named config to `.claude/launch.json`
27+
pointing at *this* working dir, e.g.
28+
`pnpm -C <abs>/examples/app-showcase exec objectstack dev --ui --seed-admin -p <port> -d file:/tmp/<run>/data.db`.
29+
- [ ] **Own data**: `--seed-admin` gives `admin@objectos.ai / admin123` on an empty DB.
30+
Persistent `-d file:/tmp/<run>/data.db` survives restarts (good for multi-step config
31+
runs); `--fresh` for a pristine first-run (wipes on exit).
32+
- [ ] **Confirm exclusive control**: after `preview_start`, check `location.origin` ===
33+
your `http://localhost:<port>`. If the tab keeps drifting to another app/route you
34+
didn't navigate to, or shows drafts you never made → **a parallel session owns the
35+
browser**. Stop fighting it; pin all checks to your absolute origin and lean on the
36+
API + source (pillar 3).
37+
- [ ] If you'll also open a PR, build it in a **separate git worktree off `origin/main`**
38+
(see §5) — never commit from the shared dirty branch.
39+
40+
## 1. Boot
41+
42+
- [ ] `preview_start` your named `.claude/launch.json` config; poll readiness:
43+
`curl -s -m3 -o /dev/null -w '%{http_code}' http://localhost:<port>/api/v1/health` → 200.
44+
- [ ] Console UI is at `/_console/`; apps at `/_console/apps/<appId>` (e.g.
45+
`com.objectstack.setup`, `com.objectstack.studio`). API root `/api/v1`,
46+
settings `/api/settings`, merged app/nav `/api/v1/meta/app?id=<appId>`.
47+
48+
## 2. Build/runtime model — batch, then ONE restart
49+
50+
- [ ] Packages load from **`dist`**, not `src` (`pkg.main = dist/index.js`). Editing
51+
`packages/*/src` has **no runtime effect** until you rebuild that package
52+
**and restart the server**. The `os dev` watcher only recompiles the example's
53+
own `objectstack.config.ts` / `src`, not workspace packages.
54+
- [ ] So: make **all** source edits first → `pnpm --filter <pkg...> build`
55+
`preview_stop` + `preview_start`. Don't edit→build→restart per fix.
56+
- [ ] `dist/` is gitignored — safe; never commit build output.
57+
58+
## 3. Verify — visual / API first, DOM last (the anti-false-positive rule)
59+
60+
The biggest trap: `preview_eval` DOM queries **right after navigation** return
61+
transitional/empty results (React hasn't hydrated) → you conclude "nav is empty /
62+
feature is broken" and it's a lie.
63+
64+
- [ ] **Before asserting any "missing / broken / unreachable" finding**, confirm with a
65+
**screenshot** (visual truth) or an **authoritative server response**
66+
(`/api/v1/meta/...`, `/api/settings`) — the metadata the server actually sends.
67+
Never report a severe finding from a single post-nav DOM dump.
68+
- [ ] DOM dumps are fine *after* you've confirmed the page rendered (screenshot first,
69+
then query for selectors).
70+
- [ ] **Test both sides of a gate**: a `requiresService`/`requiresObject`/permission gate
71+
should be verified both when the dependency is present *and* absent.
72+
- [ ] Server is the authoritative visibility gate (ADR-0057 D10) — client filtering is
73+
"courtesy". If a metadata flag doesn't change the UI, check whether enforcement is
74+
server-side (framework, fixable here) or client-side (objectui console, separate repo).
75+
- [ ] Prove it to the user with a `preview_screenshot` (or `preview_network` for API
76+
changes); note loading-spinner / async-data states explicitly rather than claiming
77+
final values you didn't wait for.
78+
79+
## 4. Browser escape hatches (gotchas)
80+
81+
- **Stuck page / blocked navigation** (unsaved-changes `beforeunload`, shared tab): neutralize then navigate —
82+
```js
83+
Object.defineProperty(Event.prototype,'returnValue',{configurable:true,get:()=>undefined,set:()=>{}});
84+
const op=Event.prototype.preventDefault;
85+
Event.prototype.preventDefault=function(){ if(this&&this.type==='beforeunload')return; return op.apply(this,arguments); };
86+
window.onbeforeunload=null; location.replace('<url>');
87+
```
88+
- **Login / React controlled inputs**: `preview_fill` sets `.value` but doesn't fire React
89+
`onChange` → form submits empty. Use the native setter + dispatch `input`+`change`, or
90+
just POST the auth endpoint via `fetch` from the page.
91+
- **Cross-origin**: pin `fetch` to your absolute `http://localhost:<port>` so a drifted tab
92+
doesn't hit the wrong server; `credentials:'include'` for cookie-authed routes.
93+
94+
## 5. Ship — isolated PR (when the working tree is shared/dirty)
95+
96+
- [ ] Capture only your files: `git diff -- <explicit paths…> > /tmp/fix.patch`
97+
(use **explicit paths**, not a multi-line shell var — it silently yields an empty patch).
98+
- [ ] `git worktree add -b <branch> /tmp/pr origin/main``git -C /tmp/pr apply /tmp/fix.patch`
99+
→ confirm `git -C /tmp/pr status` shows *only* your files.
100+
- [ ] Add a **changeset** (`.changeset/<slug>.md`, `"@objectstack/<pkg>": patch`) — CI's
101+
"Check Changeset" gate requires it for published-package changes.
102+
- [ ] Commit (end message with the `Co-Authored-By:` trailer), push, `gh pr create`,
103+
then `gh pr merge --squash --auto --delete-branch` (remove the worktree first so the
104+
local branch isn't locked: `git worktree remove /tmp/pr --force`).
105+
106+
## 6. Shell hygiene
107+
108+
- zsh eats `--include=*.ts` when the glob doesn't match → use the dedicated **Grep tool**,
109+
or quote the glob.
110+
- Prefer explicit args over multi-line `VAR="a b c"` in compound `git` commands.
111+
112+
---
113+
114+
**Golden rule:** if a finding would be severe ("the whole settings surface is unreachable"),
115+
treat your first read as a hypothesis and disprove it with a screenshot or the server's own
116+
metadata before writing it down. Most "P0s" found this way are hydration artifacts.

0 commit comments

Comments
 (0)