Skip to content

Commit 31d24a5

Browse files
docs(readme): a 30-second demo GIF opens the README, rendered from a real showcase run (#22241)
Fixes #22232 Clause-②: no Part A of the card: the README now opens on a thirty-second GIF recorded from a really running app, rendered by a committed script. Part B (repository topics and the About line) is the maintainer's click on the repository settings page and is not in this PR. ## What changed | File | Change | |:---|:---| | `docs/screenshots/readme-demo.gif` | new: 1200 x 675, 43 frames, 29.88 s, loops forever, **497,614 bytes** | | `docs/screenshots/render-readme-demo.mjs` | new: the generator (boots the app, makes the calls, screenshots the Console, renders the frames, assembles the GIF) | | `README.md` | top only: the GIF replaces the embedded cover image under the chip row, a one-line caption names the three beats, the "Watch: ObjectStack in 90 Seconds" link stays as text under it. `docs/screenshots/hero-cover-dark.png` is byte-untouched (the docs site's og:image). The architecture diagram and everything below it are unchanged. | No `packages/**`, `apps/docs`, `content/**`, `CHANGELOG.md` or `package.json` change; nothing publishes. ## The recording Example and object: `examples/app-showcase`, object `showcase_team` (three fields, so the whole typed definition fits one frame without scrolling). 1. **One definition, and the app runs.** The Team object exactly as `examples/app-showcase/src/data/objects/team.object.ts` declares it (comment lines omitted; the frame says so), then `pnpm dev:showcase -- --fresh -p PORT` booting it on a throwaway database, then the Console's Team list (2 rows). 2. **An agent creates a record over MCP.** A JSON-RPC `tools/call` of `create_record` against the running app's `/api/v1/mcp`, sent with the dev admin's API key; the real response; then the Console list with the new "Data Guild" row (3 rows) and its record page reading "Created by Dev Admin". 3. **A read-only identity is refused.** The byte-identical call sent with the Auditor persona's API key; the real refusal; then the Console list, unchanged (3 rows). Closing frame: "The ontology is the software." and the four chips Executable · AI-writable · Agent-operable · You own it. Measured in the run that rendered the committed GIF (port 39497): `showcase_team` went 2 → 3 on the admin's call and stayed 3 on the refused call (read back over REST after each call). - Admin's call: `200`, record `QZu-Fcgx29Po-Sfn` "Data Guild", `created_by` = the admin's user id. - Read-only call, the real response bytes: ```json {"result":{"content":[{"type":"text","text":"You do not have permission to perform this action. Contact your administrator if you need access."}],"isError":true},"jsonrpc":"2.0","id":1} ``` ### Keys and identities - Both keys are minted through the documented `POST /api/v1/keys` (content/docs/ai/connect-mcp.mdx, "Headless: API keys"), each from a session opened with `POST /api/v1/auth/sign-in/email`. - Writer: the dev admin that `--fresh` seeds (`admin@objectos.ai`). - Read-only identity: the showcase's own Auditor persona, `auditor.demo@example.com` ("Ada Auditor (demo)"), which `examples/app-showcase/src/security/seed-approval-demo.ts` provisions on every dev boot. It holds only the `auditor` position, bound to `showcase_auditor` (no Team grant), plus the member baseline `showcase_member_default`, which grants `showcase_team` read and no create. No code was changed to get a refusal. Before the GIF calls it "read-only", the script proves it can read: its `query_records` on `showcase_team` returned both rows. ### Honesty rules, enforced by the script rather than trusted - Every browser frame is a screenshot of the real Console against the app that run booted; the only additions are an outline around the new row and around the record count. - The request panel shows the bytes sent: the body is sent pretty-printed, exactly as drawn; the API key is masked after its public prefix. - The response panel shows the bytes received, with one re-layout for legibility: the escapes inside `result.content[0].text` are drawn as the characters they encode. - Terminal lines are lines that run's dev server printed, picked by pattern in order, with a dim ellipsis at each gap. A pattern that stops matching is a refusal (exit 5), not a stand-in line. - No assistant chat transcript, no model identifier, no record the run did not create; the caller is "an agent" / "any MCP client". - The script also refuses (exit 5) when the admin's call does not create the row, when the read-only identity cannot read the object, when its create is NOT refused, or when the row count moves on the refusal; and (exit 6) when the GIF is not 1200 px wide, runs outside 20-35 s, or exceeds 4,000,000 bytes. ## Re-render ```bash node docs/screenshots/render-readme-demo.mjs # rewrite the committed GIF node docs/screenshots/render-readme-demo.mjs --out /tmp/demo # preview: GIF, every frame and story.json, nothing committed touched ``` Prerequisites are the ones `pnpm dev` checks itself: a built workspace (`pnpm build`) and a built Console (`pnpm objectui:build`), plus `ffmpeg` on PATH. Playwright's Chromium is resolved from `examples/app-showcase` (preinstalled binary under `PLAYWRIGHT_BROWSERS_PATH`, never `playwright install`), `sharp` from the docs app's Next dependency (it verifies screenshot and GIF dimensions and duration), and ffmpeg assembles the GIF in two passes (palettegen, then paletteuse with bayer dithering). Four end-to-end runs in this container produced the same beats; byte-identity is not a goal (ids, timestamps, port). **Fonts:** the frames the script draws use Inter and IBM Plex Mono from Google Fonts, the hero cover's pair; all eight faces loaded in the committed render. Without them the script refuses to write the committed GIF (exit 3), as `render-hero-cover.mjs` does; `--out` previews the system fallback (Liberation Sans / DejaVu Sans Mono). The Console screenshots carry the Console's own fonts. No font file is committed. ## Verification Run on the final head `8336490e` (change set: the three files above, 662 changed lines, the binary counted 0). - `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 13 families. All 13 exited 0: check-ci-filter-parity, check-closing-keyword-parity (and its `--self-test`), check-comment-mask-corpus, `@objectstack/lint` check:doc-formula-expressions, check:cross-package-test-inputs, check:doc-anchors, check:doc-authoring, check:driver-memory-census, check:gitlink-declared, check:nul-bytes, check:refd-timer-probe, check:watch-hint-literal. `--ran` reconciliation: "13 derived, 13 run, 0 NOT-MEASURED, 0 UNRUN". - `pnpm check:published-readme-links` exited 0: "221 outbound link(s) across 101 published markdown file(s) ... 144/144 relative target(s) found in the tree." - Narrowed eslint, spelled as `pnpm lint` runs it (`--no-inline-config`), over the three changed paths: - population, read from the config: `eslint --print-config` answers a config object for `render-readme-demo.mjs` and `undefined` for `README.md` and the GIF; - count, read from `--format json`: 3 results, 1 file linted with 0 errors and 0 warnings, 2 "File ignored because no matching configuration was supplied"; - invariance: that config's `parserOptions` is `{ ecmaVersion, sourceType }` with no `project` or `projectService` (type-aware linting is off repo-wide), and nothing imports the new script, so this diff cannot move any untouched file's verdict. - Not run locally: the repo-wide `pnpm lint` and the rest of CI's farm, which CI owns. ## Acceptance notes - Serial constraint: #22224 (the README contributor-path section, lower in the file) is still open; `origin/main` at `3ae59661` has no README change since this branch's base `7b926f76`, so no merge was needed. Both PRs edit different hunks of README.md. - Observed in a fresh `pnpm dev:showcase -- --fresh` boot whose full output was read during this work, not investigated, not filed (outside this card): the boot-diagnostics block prints a `sys_migration` "UNIQUE constraint failed" insert warning, a pre-bind settings read warning, an analytics `admitObjectRead` warning and a sharing-rule "expands to NO recipients" warning. The terminal frame shows none of them because it shows a pattern-picked subset of lines; nothing in the GIF depends on them. - The MCP refusal is an `isError: true` tool result carrying prose only, with HTTP 200 and no machine-readable error code; the GIF shows it as received. --- _Generated by [Claude Code](https://claude.ai/code/session_011hRnra93sK5Q2gTYYTbdJR)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3aa0c7b commit 31d24a5

3 files changed

Lines changed: 662 additions & 3 deletions

File tree

‎README.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
`Executable` · `AI-writable` · `Agent-operable` · `You own it` · Apache-2.0
1717

1818
<p align="center">
19-
<a href="https://youtu.be/CX_FlOoOtr0">
20-
<img src="docs/screenshots/hero-cover-dark.png" width="900" alt="ObjectStack in 90 Seconds — watch the overview on YouTube">
21-
</a>
19+
<img src="docs/screenshots/readme-demo.gif" alt="A 30-second recording of a real run of the showcase example app, in three beats. One: the Team object's typed definition, then pnpm dev:showcase booting it on a fresh database, then the Console listing two teams. Two: an agent's MCP tools/call create_record request to /api/v1/mcp with the dev admin's API key, the real response carrying the new Data Guild record, then the Console showing that row and its record page saying Created by Dev Admin. Three: the same call sent with a read-only Auditor key, refused with isError true and a permission message, then the Console still showing three rows. It closes on: The ontology is the software. Executable, AI-writable, Agent-operable, You own it.">
20+
<br>
21+
<sub><b>1</b> · One definition, and the app runs &nbsp;·&nbsp; <b>2</b> · An agent creates a record over MCP &nbsp;·&nbsp; <b>3</b> · A read-only identity is refused</sub>
2222
<br>
2323
<a href="https://youtu.be/CX_FlOoOtr0"><b>▶&nbsp; Watch: ObjectStack in 90 Seconds</b></a>
2424
</p>

‎docs/screenshots/readme-demo.gif‎

486 KB
Loading

0 commit comments

Comments
 (0)