Repository navigation
Commit 31d24a5
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
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
22 | 22 | | |
23 | 23 | | |
24 | 24 | | |
| |||
Loading
0 commit comments