|
| 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