Skip to content

console(auth): LoginForm defaults registerUrl to '/register', so passing undefined on a disableSignUp boot still renders "Don't have an account? Sign up" #11634

Description

@objectstack-fleet

QA-source: objectstack-ai/objectstack#21784 · identity-auth.self-signup-gate · acceptance[3]

A clause of identity-auth.self-signup-gate fails in the ObjectStack 17.7 pre-release checklist run objectstack-ai/objectstack#21784 (framework subject 316be321e, console pin 2e818d0b51ec). An independent verifier (VF1, RUNNER rule 7) confirmed it: low (courtesy layer; the server refuses sign-up and /register bounces to login). It predates the 17.6.0 console pin 31971ff1e; no open duplicate was found. Owner: objectui.

Reproduction

  1. Boot with OS_DISABLE_SIGNUP=true; GET /api/v1/auth/config → emailPassword.disableSignUp: true.
  2. Fresh browser context → /_console/login; wait for network idle plus a few seconds.
  3. Expected: no register link. Actual: a[href="/_console/register"] "Sign up". Seen in 3 fresh contexts across 2 boots. The page did read the config (the dev-admin banner from the same call rendered).

Mechanism

objectui apps/console/src/pages/auth/LoginPage.tsx:299 passes registerUrl={signUpDisabled ? undefined : registerUrl}; LoginFormCard forwards it; packages/auth/src/LoginForm.tsx:147 destructures registerUrl = '/register', so undefined triggers the default. Present at objectui HEAD 31971ff1e too.

Done when

A disabled sign-up renders no register link (pass null, or drop the default).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: sign-in and identity | identity-auth.self-signup-gate | P2

    Triage: first grade — bug · priority:p3 · domain:ui · area:identity · pm:queue. A disabled sign-up renders no register link

    Triage: lands in packages/auth/src/LoginForm.tsx ⇒ domain:ui; rationale: the console already withholds the URL; the component's default restores it.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-05T02:07Z. ⛔ Not a claim, ⛔ not a dispatch.

    • Direction. LoginForm's registerUrl loses its '/register' default (packages/auth/src/LoginForm.tsx about :147), so an absent URL means no link. The console already passes nothing when sign-up is off.
      • The changeset states the behavior change for @object-ui/auth callers that relied on the default.
      • A null arm is not added: a default that comes back on undefined is the trap, and a second "off" value would keep it.
    • Why p3. A courtesy layer: the server refuses sign-up and /register bounces to login. The verifier graded it low.

    Generated by Claude Code

  2. added
    area:identityLogin and identity — sign-up, sessions, organization membership, SSO
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 5, 2026
  3. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_015W8GBu6sBiqus2L2xjMsAL
    Account: os-steve
    Branch: claude/issue-11634-loginform-register-default
    Worktree: objectui-issue-11634
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/auth/src/LoginForm.tsx (the registerUrl default, about :147, and its docblock, about :139), the tests beside it, any content/docs/** page that states the '/register' default, and .changeset/11634-*.md (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default tier)
    Clause-②: no
    Thread-read: 5986895176
    Serial constraints cleared: none — no open PR or in-flight claim touches packages/auth/src/LoginForm.tsx or either console LoginPage.tsx (objectui#11600 touches only packages/auth/CHANGELOG.md and package.json). objectui#11633 is in apps/console/src/pages/auth/VerifyEmailPage.tsx: a different file, and no pin of either card asserts the other's behaviour. Read 2026-10-05T07:55Z.

    Why Clause-②: no: the '/register' default on LoginForm's registerUrl is dropped (triage 5986895176), so an absent URL renders no link. The prop's type is unchanged and nothing is added to the accept set or the public surface. This narrows a default. For a caller that omitted the prop and relied on the default, it is a behaviour change, so the changeset is minor with that change stated (objectui AGENTS.md: objectui's own breaking changes ship as minor).

  4. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 11634,
    "status": "done",
    "branch": "claude/issue-11634-loginform-register-default",
    "pr": "#11652",
    "session": "session_015W8GBu6sBiqus2L2xjMsAL (mode:subagent; this is the parent PM seat's session)",
    "premise_still_valid": true,
    "summary": "LoginForm's registerUrl no longer defaults to '/register'. The prop type is unchanged (registerUrl?: string) and no null arm is added. Leaving the prop out, or passing undefined, now renders no sign-up link. Both console login pages already pass undefined when /auth/config reports emailPassword.disableSignUp: true, so a deployment with sign-up off no longer offers 'Sign up'. Live counts with sign-up off: [1,1,1] before, [0,0,0] after; with sign-up on, still [1,1,1]. This run resumed from 050fc0d. It kept that commit after review and added the '@object-ui/auth' minor changeset with the caller migration sentence (1895044), then merged origin/main once at e398a54 (b906a13). Draft PR #11652 is assigned to os-steve.",
    "tests": "All run by this run on final head b906a13 (sha quoted in the PR body). Exit codes were written to files before any pipe. vitest from the repo root: packages/auth/ exit 0, 28 files / 285 tests passed. apps/console/src/pages/auth/ exit 0, 9 files / 52 tests passed (includes the new LoginPage.sign-up-gate-11634.test.tsx). packages/app-shell/src/console/auth/ exit 0, 3 files / 14 tests passed. pnpm --filter @object-ui/auth type-check exit 0 (tsc --noEmit && tsc -p tsconfig.test.json); --listFiles on tsconfig.test.json counts LoginForm.test.tsx once; auth has no workspace deps, so the build closure is empty. pnpm --filter @object-ui/auth lint exit 0: 0 errors, 26 warnings; the only one in LoginForm.tsx, hasSocialProviders unused, is already on main. eslint --format json on the 3 changed ts/tsx files: 3 files, 0 errors, exit 0. Lint narrowing evidence: (1) population: eslint.config.js files globs /.{ts,tsx}, plus the test blocks for the two test files; (2) count: 3 from the json output; (3) invariance: eslint.config.js sets no parserOptions.project or projectService, so linting is not type-aware and this diff cannot change the verdict on any untouched file. Checks, all exit 0: check-changeset-presence ('3 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'), check-changeset-no-major, check-changeset-fixed, check-changeset-overwrite, check:changeset-claims ('No pending changeset names a file this change touches.'), check:pending-changeset-literals, check:new-line-citations ('0 new citation(s)'), check:control-bytes, check:test-path-roots, check:vi-mock-specifiers/-inherit/-override-shape. NOT MEASURED: console type-check (apps/console tsc --noEmit, the only program that compiles the new console test). Reason: it resolves @object-ui/ through built dist types, so it needs the console's whole workspace build closure, which was not built; declared to CI. ABLATION, from the committed head, through objectstack scripts/ablation-replace.mjs (the anchor must hit, on-disk counts and blob hashes are checked) plus an own trap restore that compares against the HEAD blob. No dist step: both pin files reach packages/auth/src (one through a relative import, one through the vitest alias), and the console red in leg A shows it. Leg A put the '/register' default back (default-line count 0 → 1 during → 0 after): 3 failed / 25 passed; failing: 'renders no sign-up link when registerUrl is left out', '... is passed as undefined' (both: expected a href=/register ... to be null), and console 'renders no sign-up link when the server reports disableSignUp: true'. Leg B dropped !ssoEnforced from the sign-up row guard (guard count 1 → 0 → 1): 1 failed / 25 passed; failing: the re-judged enforced-mode pin. After each leg the blob was back to the HEAD blob c62cd03ca3a4 and git diff HEAD was empty. LIVE: objectstack main 27991556 from an own worktree (turbo build of example-showcase^... and cli..., exit 0), examples/app-showcase objectstack dev --seed-admin --fresh -p 4634. This worktree's console Vite dev server on 5634 was proxied to it. Fresh Chromium contexts (/opt/pw-browsers/chromium) loaded /login, waited for network idle, the identifier field and 4 s more, then counted a[href$="/register"]. With OS_DISABLE_SIGNUP=true (config emailPassword.disableSignUp: true): before ('/register' default put back on the head via ablation-replace) [1,1,1], each '/register "Sign up"'; after (head) [0,0,0], noAccountText 0. With sign-up on (disableSignUp: false), after: [1,1,1]. In every context the dev-admin banner rendered and the page's own /auth/config response carried the disableSignUp value. Both servers were started and stopped by this run (process groups killed, ports confirmed closed).",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "3 — each through the objectstack-fleet[bot] relay (POST /repos/objectstack-ai/objectstack/dispatches): pr_create → POST /repos/objectstack-ai/objectui/pulls (#11652, draft; read-back 9283 bytes sent = stored, identical); assign via label-write.mjs → POST /repos//issues/11652/assignees (os-steve; read back matched); comment via post-stamped.mjs → POST /repos//issues/11634/comments (this report). Plus one git push of the branch (not REST). Reads: gh api single REST reads of issue 11634, its comments and PR 11652.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: none (dormant sibling, zero pull) · reach: none — no caller passes undefined to switch the link off · evidence: LoginForm's forgotPasswordUrl still defaults to '/forgot-password', the same undefined-brings-it-back shape; both login pages pass '/forgot-password' explicitly and AuthPublicConfig has no password-reset-off flag · carrier: 承接者:无 · noted in PR Acceptance notes, not filed · dedupe words: forgotPasswordUrl default, LoginForm undefined default, forgot password link",
    "class: none (polish, read from code, not measured live) · evidence: the sign-up row renders outside LoginForm's config-loading gate. The console page starts signUpDisabled at false and DefaultLoginPage starts it at undefined, which falls to '/register', so on a sign-up-off boot both pages show the link until their config read resolves. DefaultLoginPage's comment says its undefined start avoids that flicker · carrier: 承接者:无 · noted in PR Acceptance notes, not filed · dedupe words: sign-up link flicker, config pending, DefaultLoginPage signUpDisabled"
    ],
    "gates": [
    {
    "name": "vitest packages/auth/",
    "command": "pnpm exec vitest run packages/auth/",
    "exit": 0,
    "reading": "28 files / 285 tests passed"
    },
    {
    "name": "vitest console auth",
    "command": "pnpm exec vitest run apps/console/src/pages/auth/",
    "exit": 0,
    "reading": "9 files / 52 tests passed"
    },
    {
    "name": "vitest app-shell console/auth",
    "command": "pnpm exec vitest run packages/app-shell/src/console/auth/",
    "exit": 0,
    "reading": "3 files / 14 tests passed"
    },
    {
    "name": "auth type-check",
    "command": "pnpm --filter @object-ui/auth type-check",
    "exit": 0,
    "reading": "closure empty (no workspace deps); test project lists LoginForm.test.tsx"
    },
    {
    "name": "auth lint",
    "command": "pnpm --filter @object-ui/auth lint",
    "exit": 0,
    "reading": "0 errors, 26 warnings (one in LoginForm.tsx, already on main)"
    },
    {
    "name": "eslint changed files",
    "command": "pnpm exec eslint --format json (3 changed ts/tsx files)",
    "exit": 0,
    "reading": "3 files, 0 errors"
    },
    {
    "name": "changeset-presence",
    "command": "node scripts/check-changeset-presence.mjs",
    "exit": 0
    },
    {
    "name": "changeset-no-major",
    "command": "node scripts/check-changeset-no-major.mjs",
    "exit": 0
    },
    {
    "name": "changeset-fixed",
    "command": "node scripts/check-changeset-fixed.mjs",
    "exit": 0
    },
    {
    "name": "changeset-overwrite",
    "command": "node scripts/check-changeset-overwrite.mjs",
    "exit": 0
    },
    {
    "name": "check:changeset-claims",
    "command": "pnpm check:changeset-claims",
    "exit": 0
    },
    {
    "name": "check:pending-changeset-literals",
    "command": "pnpm check:pending-changeset-literals",
    "exit": 0
    },
    {
    "name": "check:new-line-citations",
    "command": "pnpm check:new-line-citations",
    "exit": 0
    },
    {
    "name": "check:control-bytes",
    "command": "pnpm check:control-bytes",
    "exit": 0
    },
    {
    "name": "check:test-path-roots",
    "command": "pnpm check:test-path-roots",
    "exit": 0
    },
    {
    "name": "check:vi-mock-specifiers",
    "command": "pnpm check:vi-mock-specifiers",
    "exit": 0
    },
    {
    "name": "check:vi-mock-inherit",
    "command": "pnpm check:vi-mock-inherit",
    "exit": 0
    },
    {
    "name": "check:vi-mock-override-shape",
    "command": "pnpm check:vi-mock-override-shape",
    "exit": 0
    },
    {
    "name": "console type-check",
    "command": "apps/console tsc --noEmit",
    "exit": null,
    "reading": "NOT MEASURED: needs the console's whole workspace dist build closure; declared to CI"
    }
    ],
    "line_budget": "n/a — no skills/
    or governed ledger touched. Branch diff against origin/main (merge base e398a54): +126 / -6 over 4 files.",
    "files_changed": [
    ".changeset/11634-loginform-register-default.md",
    "apps/console/src/pages/auth/tests/LoginPage.sign-up-gate-11634.test.tsx",
    "packages/auth/src/LoginForm.tsx",
    "packages/auth/src/tests/LoginForm.test.tsx"
    ],
    "deviations": [
    "Resumption: resumed from head 050fc0d (1 commit beyond merge base 76993f8). Reviewed against the dispatch and kept unchanged: the default is dropped, the prop doc says so, there are LoginForm pins for left out / undefined / '/x', the enforced-mode pin is re-judged to pass registerUrl, and there is a console LoginPage pin. Added on top: 1895044 (the changeset the lost run had not written) and b906a13 (one merge of origin/main at e398a54). Before its first push I amended the merge commit's message locally to carry the model-free trailer pair. No force push, no rebase, no amend of a pushed commit. Every reading in this report was taken by this run.",
    "File surface: the claim lists LoginForm.tsx, the tests beside it, content/docs pages and .changeset/11634-. The lost run also added apps/console/src/pages/auth/tests/LoginPage.sign-up-gate-11634.test.tsx, which is outside that list. The dispatch's Zone 3 asks for exactly this console pin, and it uses the existing mock-AuthClient seam (as LoginPage.dev-admin-hint.test.tsx does). No production file outside packages/auth changed.",
    "Zone 3 'fix the docblock example': measured that LoginForm's @example already passes registerUrl="/register" explicitly, which stays correct without a default, so it is unchanged. The registerUrl prop doc comment carries the new semantics instead.",
    "Zone 2 item 2, callers: neither console LoginPage relies on the default; both pass an explicit URL when sign-up is on, so neither was touched. Examples that render LoginForm with no registerUrl: packages/auth/README.md (AuthGuard fallback and the forms example), the AuthShell doc comment example, and skills/objectui/guides/auth-permissions.md (governed). None states or expects a sign-up link, so none needs the URL passed, and none is edited. No story, apps/site page, content/docs page or e2e spec renders LoginForm or states the default. Zone 2 item 4: no test pinned the default positively. The enforced-mode pin rendered with no registerUrl and asserted no 'Sign up'; it was re-judged (it now passes the URL) and named in the PR. Ablation leg B shows it now measures the !ssoEnforced guard.",
    "Live route: /login on this worktree's Vite dev server (basename '/'), not /_console/login. It is the same LoginPage route; the backend's /_console serves the published console, not this source. The 'before' leg put the '/register' default back on the branch head through ablation-replace instead of checking out base. Vs base, LoginForm.tsx then differs only in the prop doc comment, and both console LoginPage files are byte-identical to base.",
    "Narrowed checks: console type-check NOT MEASURED (reason in tests), declared to CI. The repo-wide pnpm lint is CI's; the narrowed lint evidence is in tests. The gate list was hand-derived, because dispatch-gates.mjs derives only objectstack's tree. Added to the dispatch's list: changeset-presence/-fixed/-overwrite, check:new-line-citations, check:control-bytes, check:test-path-roots, check:vi-mock-
    , and eslint on the changed console test file.",
    "origin/main moved to 0baf86f after the merge: one commit (objectui#11627, app-shell install-local), with no overlap with auth paths. Per the one-merge instruction it was not merged again.",
    "Changeset kept at minor. In-tree callers are unaffected, but the README and AuthShell examples, and any external caller that omits registerUrl, now render no sign-up link. So 'not observable to any caller' is not airtight.",
    "Commit trailers use the model-free pair (Claude-Session + Co-authored-by: Claude) that objectui AGENTS.md requires (objectui#9441 ruling). The harness reminder's model-named Co-Authored-By line was not used, because the repo rule takes precedence.",
    "Cleanup: the own objectstack worktree (objectstack-issue-11634-live) and the objectui worktree /home/user/objectui-issue-11634 were removed after the PR opened; the branch's last sha b906a13 is on origin. Both dev servers this run started were stopped, and nothing it started is still running."
    ]
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#11652 → main f1a177c, verified by content

    domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL (os-steve) · 2026-10-05T09:23Z.

    • Merged through the merge queue as squash commit f1a177c (one parent, 8057a8b), an ancestor of origin/main. Its +/- lines are identical to the PR head b906a13 diffed from its merge base: 4 files, +126/−6.

    • Content check against the first parent:

      reading 8057a8b f1a177c
      registerUrl = '/register' in auth LoginForm.tsx 1 0
      a bare registerUrl, in LoginForm's destructure 0 1
      .changeset/11634-loginform-register-default.md (@object-ui/auth: minor) exists no yes
      LoginPage.sign-up-gate-11634.test.tsx exists no yes
      forgotPasswordUrl = '/forgot-password' (control, untouched) 1 1
    • The card closed completed through the PR's Fixes line. It was the only issue closed in that window. pm:dispatched is removed in this stroke.

    • Left as noted, not filed:

      • The forgotPasswordUrl default has the same shape but no caller wants it off.
      • The sign-up link can show while a login page's config read is pending.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions