From 1776d515ce1a2b87daa2fbb578e96a4e73a22d98 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 21 Aug 2026 00:51:19 +0000 Subject: [PATCH] docs(create-objectstack): say that the blank starter ships no app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scaffolded with the real CLI against published 17.1.0 packages and booted with `objectstack dev --ui`: `GET /api/v1/meta/app` returns two items, both platform apps (setup, account), while `GET /api/v1/data/my_app_note` serves the scaffolded object for the whole session. The object is live and simply has no route into Console navigation. That is deliberate. `blank` is described in the registry as "Minimal starter — one object, REST API, ready to extend"; all three `os init` templates ship `src/objects/` only, including the one described as "Full application with objects"; and no `*.app.ts` has ever existed under `src/templates/**` in this repo's history. Apps are what the agent authors in step 2 of the root README's loop, not what the scaffolder pre-writes into every project. What was missing is the disclosure: `pnpm dev` prints the Console URL on every boot, and nothing the newcomer could reach explained why their object is not there. Adds a "The Console" section to the generated README naming the path, the consequence and `src/apps/*.app.ts` as the remedy, plus a bidirectional test — the template's own tree decides which README claim is required, so adding an app later cannot leave a stale "ships no app" green. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r --- .../blank-template-console-disclosure.md | 18 +++ .../blank-template-console-disclosure.test.ts | 121 ++++++++++++++++++ .../src/templates/blank/README.md | 17 +++ 3 files changed, 156 insertions(+) create mode 100644 .changeset/blank-template-console-disclosure.md create mode 100644 packages/create-objectstack/src/blank-template-console-disclosure.test.ts diff --git a/.changeset/blank-template-console-disclosure.md b/.changeset/blank-template-console-disclosure.md new file mode 100644 index 0000000000..34ba53975a --- /dev/null +++ b/.changeset/blank-template-console-disclosure.md @@ -0,0 +1,18 @@ +--- +"create-objectstack": patch +--- + +Tell a newcomer that the `blank` starter ships no app, so an empty Console +reads as the intended starting point rather than a broken install (#10317). + +Measured on a real scaffold-and-boot (`create-objectstack my-app -t blank`, +published 17.1.0 packages, `objectstack dev --ui`): `GET /api/v1/meta/app` +returns the two platform apps (Setup, Account) and nothing of the project's +own, while `GET /api/v1/data/my_app_note` serves the scaffolded object the +whole time. The template ships `src/objects/` only — deliberately, as every +scaffolder template in this repo does — but nothing the newcomer could reach +said so, and `pnpm dev` advertises the Console URL on every boot. + +Documentation only: a new "The Console" section in the generated `README.md` +naming the Console path, the consequence, and `src/apps/*.app.ts` as the +remedy. No change to what the scaffolder writes into `src/`. diff --git a/packages/create-objectstack/src/blank-template-console-disclosure.test.ts b/packages/create-objectstack/src/blank-template-console-disclosure.test.ts new file mode 100644 index 0000000000..b834ee351f --- /dev/null +++ b/packages/create-objectstack/src/blank-template-console-disclosure.test.ts @@ -0,0 +1,121 @@ +// Copyright (c) 2026 ObjectStack contributors. Apache-2.0 license. +// +// Keeps the blank template's TREE and its README telling the same story about +// whether a scaffolded project renders in the Console. +// +// ## What was measured +// +// Scaffolded with the real CLI (`create-objectstack my-app -t blank`), installed +// against the published 17.1.0 packages, and booted with `objectstack dev --ui`: +// +// GET /api/v1/meta/app -> 200, items: [setup, account] (2, both platform) +// GET /api/v1/data/my_app_note-> 200 {"records":[],"total":0} +// +// The scaffolder's own object is live over REST for the whole session and is in +// no app's navigation, because `src/` ships `objects/` and nothing else. Adding +// a single `*.app.ts` to the scaffolded project and rebooting takes the same +// endpoint to three items, the third being the project's own app — so the empty +// `src/apps/` is the entire cause, and an app is the entire remedy. +// +// That emptiness is DELIBERATE, and this test does not challenge it. Every +// scaffolder template in this repo ships objects only: `create-objectstack`'s +// `blank` ("Minimal starter — one object, REST API, ready to extend") and all +// three `os init` templates — including the one literally described as "Full +// application with objects". No `*.app.ts` has ever existed under +// `src/templates/**` in this repo's history. Apps are what the agent authors in +// step 2 of the README loop, not what the scaffolder pre-writes into every +// project forever. +// +// What was missing is that nothing the newcomer can reach said so, while the +// dev-server banner advertises the Console URL on every boot. Hence the pin +// below: as long as the template ships no app, its README must disclose the +// Console consequence and name the remedy. +// +// The assertion is deliberately BIDIRECTIONAL. A one-way "README must say X" +// check rots the moment someone adds an app to the template — the README would +// keep claiming an empty starting point and the test would stay green on a +// sentence that had become false. So the tree decides which claim is required. + +import { describe, it, expect } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const HERE = path.dirname(fileURLToPath(import.meta.url)); +const blankRoot = path.resolve(HERE, 'templates', 'blank'); +const readme = fs.readFileSync(path.join(blankRoot, 'README.md'), 'utf8'); + +/** Every file under `dir`, recursively, as paths relative to `dir`. */ +function listFiles(dir: string): string[] { + const out: string[] = []; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const rel = entry.name; + if (entry.isDirectory()) { + out.push(...listFiles(path.join(dir, rel)).map((f) => path.join(rel, f))); + } else if (entry.isFile()) { + out.push(rel); + } + } + return out; +} + +const srcFiles = listFiles(path.join(blankRoot, 'src')); +const appFiles = srcFiles.filter((f) => f.endsWith('.app.ts')); + +describe('blank template — tree and README agree about the Console', () => { + // Vacuity guard. Every assertion below is conditioned on the template's own + // tree, so a test that read the wrong directory would silently assert + // nothing. This proves the tree really was read. + it('reads the real template tree', () => { + expect(srcFiles).toContain(path.join('objects', 'note.object.ts')); + }); + + it('ships objects only — no app, no views', () => { + // Not a style preference: this is the measured starting point the README + // section is written against. Changing it is a product decision, and the + // failure message says so rather than inviting a quiet re-baseline. + expect( + appFiles, + 'The blank template now ships an app. That is a product change, not a ' + + 'test failure to silence: update the README section "The Console" to ' + + 'describe what actually renders, then update this expectation.', + ).toEqual([]); + }); + + it('discloses that no app means no Console navigation', () => { + if (appFiles.length > 0) { + // The template ships an app — the "no app" claim must be gone. + expect(readme).not.toMatch(/ships no app|no app and no views/i); + return; + } + + // Points the reader at the Console they are about to be sent to by the + // `pnpm dev` banner. + expect(readme, 'README must name the Console path').toMatch(/\/_console\//); + + // States the consequence, so an empty Console does not read as "it broke". + expect( + readme, + 'README must say this starter ships no app', + ).toMatch(/ships no app|no app and no views/i); + + // Names the remedy concretely enough to act on. + expect( + readme, + 'README must name *.app.ts / src/apps/ as the remedy', + ).toMatch(/src\/apps\/|\*\.app\.ts/); + + // Ties the two together — the rule itself, not just the two nouns. + expect( + readme, + 'README must state the app -> navigation rule', + ).toMatch(/Console navigation only when an app lists it/i); + }); + + it('does not hard-code the rewritten object name', () => { + // `rewrite-identity.ts` rewrites `.ts` files only, so a README naming the + // template's own `blank_note` would ship literally into `my-app`, where the + // object is `my_app_note`. The README must stay generic. + expect(readme).not.toMatch(/\bblank_note\b/); + }); +}); diff --git a/packages/create-objectstack/src/templates/blank/README.md b/packages/create-objectstack/src/templates/blank/README.md index 7b0adb628b..58273c816d 100644 --- a/packages/create-objectstack/src/templates/blank/README.md +++ b/packages/create-objectstack/src/templates/blank/README.md @@ -21,6 +21,23 @@ curl -c cookies.txt -X POST http://localhost:3000/api/v1/auth/sign-in/email \ curl -b cookies.txt "http://localhost:3000/api/v1/data/" ``` +## The Console — this starter ships no app + +`pnpm dev` also serves the admin Console at `http://localhost:3000/_console/`, +and prints the link on boot. Open it and you will see the platform's own apps +(Setup, Account) and **not** the object in `src/objects/` — this starter ships +objects only, with no app and no views. + +That is the intended starting point, not a broken install. The object is live +the whole time — the `curl` above returns it, and an MCP client can read and +write it. What it has no route into is the Console's navigation. + +**An object appears in Console navigation only when an app lists it.** Add an +`*.app.ts` under `src/apps/` (plus the views it points at), and the Console +renders it after the next `pnpm dev` rebuild. The `objectstack-ui` skill covers +the shape; describing the app you want to your coding agent is the intended +path. + ## Your app is an MCP server Every ObjectStack app is itself a