What happened
The maintainer of a downstream app (objectstack-ai/ats) ran pnpm dev, signed in with the credential the terminal handed them, opened the app — and got an empty left navigation. Their words: "我刚管理员登录进去,看不到 ATS 的左侧菜单".
The app was working correctly. The account was the problem, and the platform is what pointed them at it.
The mechanism
packages/cli/src/utils/format.ts:829-836 prints, gated on opts.seededAdmin, as the last and only credential in the boot banner:
➜ Console: http://localhost:4721/_console/
🔑 Dev admin: admin@objectos.ai / admin123
seeded on empty DB · dev only — do not use in production
(The credential itself comes from plugin-auth's maybeSeedDevAdmin, per the note at packages/cli/src/commands/dev.ts:335; the banner line is the CLI's.)
Meanwhile that app gates its navigation by capability — three groups, each behind a requiredPermissions the server strips per caller. Platform-owner standing (OS_PLATFORM_OWNER_EMAIL) confers the kernel capabilities only, so the dev admin holds none of the three. Measured on that app, GET /api/v1/meta/app per persona:
| signed in as |
ATS nav groups |
admin@objectos.ai (what the banner prints) |
0 — empty |
admin@platform.example |
Platform[7] |
ops@platform.example |
Platform[7] |
admin@quillstone.example |
Hiring[7] |
candidate01@mail.example |
Job Seeker[5] |
Four of five personas are fine. The one the banner names is the one that sees nothing.
Why this is a platform issue and not just that app's
The banner encodes an assumption: "the seeded dev admin is the account you want to look at the application with." That holds for an app with no audience model. It is exactly inverted for any app that partitions its UI by position, capability or permission set — and partitioning the UI by capability is a first-class platform feature that the docs actively teach.
In that class of app, the dev admin is by construction the account with kernel capabilities and no business role. The more correctly an app models its audiences, the more misleading the banner becomes.
It also outranks whatever the app tells you. That app's README says, on the very row for this account: "holds no ATS position, so no ATS group — use the personas below for the product", and its first quickstart line names a different account entirely. None of it wins against a line with a key emoji sitting directly under the command you just ran.
Suggestions
- Let the app contribute a first-run hint. A stack- or app-level field (say
devHint / devLogins[]) that the banner renders instead of, or beneath, the seeded-admin line. An app that seeds personas could then print the three that actually show something. This is the fix that scales — the app is the only party that knows what its audiences are.
- Failing that, soften the claim. The line asserts a credential without saying what it is for. Something closer to "platform/Setup access — an app may require a business role" costs nothing and stops the banner from over-promising.
- Optional, and possibly the cheapest real improvement: if the console renders an app whose navigation resolved to zero entries, say so in the UI rather than showing an empty shell. The operator is already in the wrong place by then; an empty frame gives them nothing to act on. (This one may belong in
objectui — happy to move it.)
Downstream tracking
objectstack-ai/ats#88 covers the app-side half (empty-state copy, README ordering, and whether that app should give its owner account a position at all). This issue is only about the platform's contribution: the banner, and the assumption inside it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG
What happened
The maintainer of a downstream app (
objectstack-ai/ats) ranpnpm dev, signed in with the credential the terminal handed them, opened the app — and got an empty left navigation. Their words: "我刚管理员登录进去,看不到 ATS 的左侧菜单".The app was working correctly. The account was the problem, and the platform is what pointed them at it.
The mechanism
packages/cli/src/utils/format.ts:829-836prints, gated onopts.seededAdmin, as the last and only credential in the boot banner:(The credential itself comes from plugin-auth's
maybeSeedDevAdmin, per the note atpackages/cli/src/commands/dev.ts:335; the banner line is the CLI's.)Meanwhile that app gates its navigation by capability — three groups, each behind a
requiredPermissionsthe server strips per caller. Platform-owner standing (OS_PLATFORM_OWNER_EMAIL) confers the kernel capabilities only, so the dev admin holds none of the three. Measured on that app,GET /api/v1/meta/appper persona:admin@objectos.ai(what the banner prints)admin@platform.examplePlatform[7]ops@platform.examplePlatform[7]admin@quillstone.exampleHiring[7]candidate01@mail.exampleJob Seeker[5]Four of five personas are fine. The one the banner names is the one that sees nothing.
Why this is a platform issue and not just that app's
The banner encodes an assumption: "the seeded dev admin is the account you want to look at the application with." That holds for an app with no audience model. It is exactly inverted for any app that partitions its UI by position, capability or permission set — and partitioning the UI by capability is a first-class platform feature that the docs actively teach.
In that class of app, the dev admin is by construction the account with kernel capabilities and no business role. The more correctly an app models its audiences, the more misleading the banner becomes.
It also outranks whatever the app tells you. That app's README says, on the very row for this account: "holds no ATS position, so no ATS group — use the personas below for the product", and its first quickstart line names a different account entirely. None of it wins against a line with a key emoji sitting directly under the command you just ran.
Suggestions
devHint/devLogins[]) that the banner renders instead of, or beneath, the seeded-admin line. An app that seeds personas could then print the three that actually show something. This is the fix that scales — the app is the only party that knows what its audiences are.objectui— happy to move it.)Downstream tracking
objectstack-ai/ats#88covers the app-side half (empty-state copy, README ordering, and whether that app should give its owner account a position at all). This issue is only about the platform's contribution: the banner, and the assumption inside it.🤖 Generated with Claude Code
https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG