Repository navigation
Commit ea4aa5c
docs, cli: the on-ramp a first-time reader walks reads true (CONTRIBUTING, README curl, one pnpm floor, five examples, os dev / os init strings, tutorial transcript) (#22280)
Fixes #22156
Clause-②: no
This PR fixes the seven on-ramp items #22156 lists, one by one, each
measured on this branch. Five are docs-only. Two are CLI strings, and
patch round 1 adds `os start`'s help text to them. Those carry the one
patch changeset for `@objectstack/cli`. `Clause-②: no`: the CLI change
is help text and printed text, with no change to the accept set or the
public surface.
## Item by item
| # | Item | Change | Files |
|:--|:--|:--|:--|
| 1 | CONTRIBUTING.md described the retired `spec` repository |
Rewritten for this repository. It names `objectstack-ai/objectstack` in
the clone, upstream, issue and discussion URLs, and the
`internal/planning` links are gone. It lists real docs trees and says
which are generated (`references/`) or release-owned (`releases/`),
where it used to send authors to hand-write MDX in `references/`. The
bilingual `.cn.mdx` section is gone (0 such files), and so is the dead
`QUICK_START_IMPLEMENTATION.md` link. The pnpm floor is now 10, and the
title covers the whole stack. It points to AGENTS.md for the rules
instead of restating them: the restated naming, Zod-first and
single-source copies are removed. The flow now includes the changeset
step and the worktree-per-task note. The `needs:pack-smoke` criterion is
kept word for word, because `pack-smoke-optin.yml` says authors meet it
in this file. | `CONTRIBUTING.md` |
| 2 | README's first `curl` answered 401 | It now signs in as the dev
admin `os dev` seeds and sends the cookie. These are the two calls the
scaffolded README and `your-first-project.mdx` show, and the sentence
before them says a data call needs a session. | `README.md` |
| 3 | Three pnpm floors | All four places now say **pnpm 10**, with
`corepack enable` installing the pinned version. That is the floor the
workspace enforces (measured below). Patch round 1 adds two more lines
of the same drift. `examples/app-todo/README.md` said `pnpm 8+` for the
monorepo and now says `pnpm 10 (corepack enable)`.
`your-first-project.mdx`'s prerequisites row said `npm 9+ / pnpm 8+ /
yarn / bun` for a scaffolded project and now says `npm 9+ / pnpm 10.15+
/ yarn / bun`, the floor both scaffolders write (`engines.pnpm` in
`create-objectstack/src/templates/blank/package.json` and
`SCAFFOLD_PNPM_RANGE` in `init.ts`). The npm, yarn and bun parts were
not measured and are unchanged, and so is the `engines.protocol` line
#22215 edits. | `content/docs/getting-started/index.mdx`,
`examples.mdx`, `your-first-project.mdx`, `CONTRIBUTING.md`,
`examples/app-todo/README.md` (`README.md` already said pnpm 10) |
| 4 | "three ready-to-run examples" against five | The opening now says
five. It names the three runnable apps (each has a `pnpm dev:*` script),
then `app-multi-package` (linked to its section lower on the page) and
`embed-objectql`. | `content/docs/getting-started/examples.mdx` |
| 5 | `os dev --help` named only `$PORT` | `Server port (overrides
$OS_PORT; $PORT is the legacy alias)`. That is the order
`readEnvWithDeprecation('OS_PORT', 'PORT')` reads them in. Patch round 1
fixes `os start --help` the same way: `Port to listen on (overrides
$PORT, default 3000)` becomes `Port to listen on, default 3000
(overrides $OS_PORT; $PORT is the legacy alias)`. `os start` reads the
same pair at `start.ts:327`, and "default 3000" is kept because it
measured true. No test, snapshot or generated docs page pins either old
string, and `deployment/cli.mdx` is hand-written and already says
`OS_PORT` / `PORT`. | `packages/cli/src/commands/dev.ts`,
`packages/cli/src/commands/start.ts` |
| 6 | The tutorial's "clean" transcript left out a warning | The example
now **is** clean. `ticket.view.ts` declares the create and edit `form`
that places `description`, and the prompt asks for that form. One
paragraph explains that a field nothing names draws
`field-no-consumers`. In patch round 1 its consumer list became visibly
non-exhaustive ("a view, a form, a flow, a formula or an action, among
the others the rule counts"), because the rule also credits page blocks,
dataset members, validations, view filters and hooks. The transcript
also carries the `Logic:` and `Security:` lines the command prints
today. | `content/docs/getting-started/build-with-claude-code.mdx` |
| 7 | `os init --no-install` printed `npm install` | The package manager
is now resolved before the install branch, by the same rule as before:
the flag, then the invoking package manager, then npm. Before, a
`--no-install` run kept the literal `'npm'` initialiser, whatever was
asked. | `packages/cli/src/commands/init.ts`, new
`init-next-steps-package-manager.test.ts` |
## Route choices
### Item 6: a clean example, not a printed warning
I chose the PM's suggested route, measured against both alternatives on
the four axes:
- **Real business need.** A ticket's description is typed on the create
form. The published Console already draws it there through the default
form (PR #22204's Acceptance notes), and the lint rule deliberately does
not credit that unkeyed fallback layout (`validate-field-consumers.ts`
docblock). Declaring the form makes the placement explicit. A grid
column for long text, the one-line alternative, would teach the wrong
UI, and the prompt asked for `(subject, status, priority)` columns.
- **Long-term fit.** The page promises every example passes `os
validate` verbatim. A clean example keeps that promise whatever the rule
later credits. A transcript that prints the ~900-character warning would
drift again the day the rule changes.
- **Keeping AI from writing bad metadata.** This is a page agents copy.
Showing the warning with "this is expected" teaches that advisories are
noise. Showing the field placed teaches that every declared field has a
consumer, which is the rule's intent.
- **Startup scope.** Docs only: one `form` block (11 lines) and one
paragraph. No new gate.
Narrative check: the Console step ("Create a ticket. Confirm the
fields…") reads the same, and the counts stay `2 Objects 6 Fields` and
`1 Apps 1 Views 1 Actions`.
### Item 7: what changes and what does not
Measured (below): under `--no-install` all three invocations printed
`npm install`, including `--package-manager pnpm`, because the variable
was assigned only inside `if (flags.install)`. That contradicts the
flag's own help and the contract `create-objectstack` pins in
`scaffold-next-steps-pm.test.ts` (never a hardcoded npm). After the fix,
`--package-manager pnpm` and a pnpm-invoked run print `pnpm install` and
`pnpm exec objectstack …`.
A plain `npx os init … --no-install`, the card's literal command, still
prints `npm install`, and that is correct. The scaffold deliberately
supports npm, yarn and bun: it declares `engines.pnpm`, never a
`packageManager` stamp, and `init.test.ts` pins this ("does NOT pin a
packageManager — the scaffold also supports npm, yarn and bun").
`pnpm-workspace.yaml` is inert for npm. So the card's alternative,
dropping the pnpm files when recommending npm, would change which files
the scaffold writes. That is outside this card, and the design says it
is not needed. Rollback: revert the `init.ts` hunk. The variable goes
back to being assigned in the install branch, and nothing else reads it.
## Measurements (this branch; the CLI built from the worktree)
- **Item 2.** I booted a tutorial-shaped scaffold with `os dev --fresh`
on a random port. `GET /api/v1/data/support_desk_ticket` with no session
gave `401 {"error":"UNAUTHENTICATED",…}`. `POST
/api/v1/auth/sign-in/email` as the dev admin gave 200. The same GET with
the cookie gave `200 {"object":"support_desk_ticket","records":[],…}`.
`/api/v1/health` and `/api/v1/discovery` answer without a session, but
neither shows that the object's API exists, so the README signs in
instead. The server was stopped by its recorded PID.
- **Item 3.** Root `engines` declares only a Node floor (22.0.0).
`packageManager` is `pnpm@10.31.0`. The global pnpm here is 10.28.0
outside the repo and runs as 10.31.0 inside it, so pnpm 10 switches to
the pin. In a throwaway worktree at the base commit, `pnpm@8.15.9
install --frozen-lockfile --lockfile-only` failed with
`ERR_PNPM_LOCKFILE_BREAKING_CHANGE`, and `pnpm@9.15.9` failed with
`ERR_PNPM_LOCKFILE_CONFIG_MISMATCH … "overrides" configuration doesn't
match`. The floor is therefore pnpm 10.
- **Item 5.** `os dev --help` before (base source): `Server port
(overrides $PORT)`. After: `Server port (overrides $OS_PORT; $PORT is
the legacy alias)`. `os start --help` before, on the CLI built at
`8fe75a222`: `Port to listen on (overrides $PORT, default 3000)`. After,
on the CLI built at `fae81259b`: `Port to listen on, default 3000
(overrides $OS_PORT; $PORT is the legacy alias)`. The behaviour the new
text states was measured on that build. Each `os start` ran in an empty
directory, inside a private network namespace (`unshare -n`) so the
shared box's port 3000 stayed untouched. With no port variable, the
banner read `API: http://localhost:3000/`. With `OS_PORT=41077` it read
`:41077`. With `OS_PORT=41077 --port 41078` it read `:41078`.
- **Item 3, patch round 1.** On an `os init` scaffold, `pnpm@9.15.9
install` fails with `ERR_PNPM_UNSUPPORTED_ENGINE … Expected version`
10.15 or later, `Got: 9.15.9`. The monorepo's pnpm 8 refusal is the item
3 reading above.
- **Item 6.** I scaffolded the `blank` starter from `create-objectstack`
source (`--skip-install --skip-skills`), wrote the page's four files
verbatim and wired their barrels, then ran `os validate`. With the
page's original view: `✓ Validation passed`, then the
`field-no-consumers` warning for `support_desk_ticket.description`
(verdict `inert`). With the edited page, re-run at `fae81259b`: `Running
author-time rules (50)…`, `✓ Validation passed`, no warning, and the six
summary lines the transcript now shows. `tsc --noEmit` on the scaffold
passes, and `--listFiles` confirms it compiled `ticket.view.ts`.
- **Item 7.** `os init APP --no-install` before and after the fix:
| invocation | before | after |
|:--|:--|:--|
| npm user agent, no flag | `npm install` | `npm install` (control) |
| npm user agent, `--package-manager pnpm` | `npm install` | `pnpm
install` |
| pnpm user agent, no flag | `npm install` | `pnpm install` |
## Tests and gates, at `fae81259b` (this branch after patch round 1 and
merging `main` at `c8bb3c8d9`)
- New
`packages/cli/src/commands/init-next-steps-package-manager.test.ts`
(unit tier, in-process `Init.run`, nothing spawned): 3 cases.
- **Ablation.** With the fix committed, `scripts/ablation-replace.mjs`
put the old semantics back (`flags.install ? (flag ?? detect) : 'npm'`):
anchor 1→0, blob `c4c2caca` → `9b93b146`. Result: 2 failed (the flag
case and the pnpm-agent case), and the npm control passed. Restore: blob
equals HEAD, and `git diff HEAD` is empty.
- `pnpm --filter @objectstack/cli typecheck`: exit 0 at `fae81259b`
(`tsc --noEmit` compiles `src/**`, the new test included, and
`check:test-typecheck` says OK).
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2`: 265 files, 3918 tests passed, at `fae81259b` and before
that at `8fe75a222`. The integration tier is left to CI: the diff
touches no spawn entry and no integration-tier file.
- Derived gates: `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on the 12-path diff gave 95 commands; the
round-1 files added `check:examples-live-imports`. All 95 exited 0 at
`fae81259b`. `--ran` reconciliation: 95 derived, 95 run, 0 NOT-MEASURED
(a derived zero: every command has a recorded exit code). The earlier
round gave 94 of 94 at `8fe75a222`. On the first pass,
`check-docs-section-name` caught the new form section's missing `name`,
which is fixed.
## Acceptance notes
- **Two scaffolders, two detection rules.** `create-objectstack` names
pnpm whenever `pnpm --version` succeeds. `os init` reads
`npm_config_user_agent`. So under `npx` with pnpm installed, one says
pnpm and the other says npm. Both instructions install, so this is an
observation, not a defect. Carrier: none.
- **The `field-no-consumers` headline.** Its text says "nothing in this
stack reads or displays it" for a field the default form does draw. The
rule's docblock excludes that unkeyed fallback layout on purpose:
crediting it would credit every visible field. I read it as working to
its contract and did not edit `packages/lint`. Carrier: none.
- **No browser check of the new form.** The Console was not driven
against the new `form` view: no objectui build in this container. That
the form view is what the create and edit surfaces render is a reading
of `examples/app-crm/src/views/lead.view.ts`, not a measurement here.
- `packages/cli/test/start-port-banner-agreement.e2e.test.ts:204` quotes
the old `os start` help text ("overrides $PORT") in a comment. It
asserts nothing, and the file is outside the surface, so it is left as
is.
---
_Generated by [Claude
Code](https://claude.ai/code/session_0115N1oNnQS5WqofZ2DzaT3q)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4578c56 commit ea4aa5c
12 files changed
Lines changed: 261 additions & 410 deletions
File tree
- .changeset
- content/docs/getting-started
- examples/app-todo
- packages/cli/src/commands
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
0 commit comments