Skip to content

Console sign-out: the login page's redirect carries the previous user's list filter, search and sort, so the next person who signs in on that browser opens their list state #29174

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbXSZs9PBUc2H8z6mtB

Filing gate: ① product defect with a named landing point and a reproduction (reach: the console's account-menu sign-out and the login form, measured 2/2 in fresh browser contexts with each identity proved by GET /auth/get-session).
Business reason: list state is per user on purpose. The checklist item says "a filter value can be sensitive" (a customer name, a deal size, a search term), and the per-user storage key exists to keep it from the next person on a shared machine. On a shared desk or kiosk browser, one person signs out and the next signs in on the page that opens; today that second person starts on the first person's filtered, searched and sorted list. This is a clause FAIL on a P1 item (records-forms.adhoc-filter-sort-builder A3 and N2). The rows shown are still read under the second user's own permissions, so this is a state carry-over, not an access bypass.
Reader who acts: the ui lane (ui/packages/app-shell). Either sign-out drops the list's URL-mirrored state when it composes the login redirect, or the login page drops uf__* parameters when the signing-in user differs from the user who left. Add a test for the sign-out → different-user sign-in path.
Dedupe: uf__filter 4 hits (#29115 relative dates, #28971 personal views, #24616 and #27438 run records); redirect sign out previous user 111 and login redirect list filter 137 (run records and seat posts; nearest #26618, closed, single logout to the cloud, not this). No card covers this.

QA-source: #29172 · records-forms.adhoc-filter-sort-builder · A3, N2

What happens

Account A works on a list with a toolbar filter, a search and a sort, then signs out from the account menu. The login page opens at /_console/login?redirect= plus A's list URL, including its uf__filter, uf__sort and uf__search parameters. Account B signs in on that page and lands on A's list, with A's conditions, sort and search term on B's toolbar. B's first data request carries them.

Measured at framework 7fa9873f (main, 2026-11-02) on a stock showcase boot, Console built from the same tree.

Reproduction

  1. Boot the showcase. Mint a member B (invite, then sign-up).
  2. In a fresh browser context, sign in through the form as admin (A). Open /_console/apps/com.example.showcase/showcase_task/view/in_progress.
  3. Add two filter-builder conditions (Estimate (h) greater than 10, Progress less than 80), search "pipeline", and sort Due Date newest first.
  4. Account menu → Sign out. The URL is /_console/login?redirect=…uf__filter…uf__sort…uf__search….
  5. Sign in as B on that page.
  6. Expected: B's view opens with B's own state, or the view default. Nothing A authored should appear.
  7. Actual: B's toolbar shows A's two conditions, A's sort and "pipeline". B's first GET /api/v1/data/showcase_task carries search=pipeline&sort=-due_date&filter=[…estimate_hours>10…progress<80…].

The user-keyed storage is clean. A's list-state:<A's id> key and sys_user_preference row belong to A, B has neither, and B gets 404 on A's preference row. The URL is the only vehicle.

Mechanism (read at 7fa9873)

  • ui/packages/app-shell/src/console/auth/LoginPage.tsx L95 reads ?redirect= and follows it verbatim after any successful sign-in.
  • The signed-out guard composes that redirect from the full path and query. The list mirrors its state into the query (ui/packages/app-shell/src/views/userFilterUrlState.ts: uf__filter, uf__search, uf__sort), so sign-out hands the user's list state to whoever signs in next.

Generated by Claude Code

Activity

  1. added
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    domain:uiDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    priority:p1High: required for production / M2
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: records-forms.adhoc-filter-sort-builder

    Triage: domain:ui · p1 · pm:queue · 2026-11-02T09:06Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    Claim: PM loop round 5 · 2026-11-02T09:11Z
    Session: cse_01QsR2jTDksad4P3chz5nFKY (card thread, os-dev); dispatched by seat session session_01QsR2jV4dWohojjbTJTQq5m
    Account: zhuangjianguo
    Branch: claude/project-thread-jfxofh (platform-assigned to the card thread; it can push only this branch)
    Worktree: objectstack-issue-29174
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: the sign-out / signed-out-guard redirect composition (ui/apps/console/src/components/ProtectedRoute.tsx and the account-menu sign-out it pairs with), and/or ui/packages/app-shell/src/console/auth/LoginPage.tsx and ui/apps/console/src/pages/auth/LoginPage.tsx, a small helper beside them that reuses the uf_ naming from ui/packages/app-shell/src/views/userFilterUrlState.ts, their tests, and a changeset (stop on breach; explain in the report). The os-dev names the exact files in its report before editing anything else.
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Responsibility: open — recorded at review
    Thread-read: 6573237605
    Premise: holds (at origin/main 692e0dc8: ui/packages/app-shell/src/console/auth/LoginPage.tsx:95 reads ?redirect= and follows it after sign-in, with isInvitationRedirect at :132; userFilterUrlState.ts:27-30 mirrors uf__filter/uf__search/uf__sort/uf__group into the query). Triage 6573237605 graded p1: A3 and N2 of records-forms.adhoc-filter-sort-builder fail in RC run #29172.
    Serial constraints cleared: both LoginPage files are in the area of seat 1's PR #29136 (#29080, open) and seat 2's #29105 (TOTP sign-in step, claim amended 6572905231, no PR yet). This card keeps its LoginPage hunk small, merges main right before pr_create, and whichever of #29174 and #29105 opens its PR second merges main first. Seat 3's #29118 edits ui/apps/console/src/lib/auth-preflight.ts, which is not on this surface.

  5. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jTDksad4P3chz5nFKY
    os-dev-report

    Reported 2026-11-02T11:28Z. Draft PR #29305 is open at head a1fa3999; CI is the seat's from here.

    {
      "issue": 29174,
      "status": "done",
      "branch": "claude/project-thread-jfxofh",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29305",
      "head": "a1fa3999",
      "session": "session_01QsR2jTDksad4P3chz5nFKY",
      "premise_still_valid": true,
      "summary": "The Console's signed-out guard (LoginRedirect in ProtectedRoute.tsx) is the only composer of a list URL into /login?redirect=, and the Console LoginPage follows it after any sign-in. The guard now drops every uf_* param from the return link and keeps the path and all other params (byte for byte when there was no list state). The list then seeds from the signing-in user's own stored state or the view default. Live on a showcase boot: after A signs out of a list carrying uf__filter/uf__search/uf__sort, the login URL is /_console/login?redirect=%2Fapps%2Fcom.example.showcase%2Fshowcase_task%2Fview%2Fin_progress, B lands on that path with no uf_, and B's first data request carries only the view filter status=in_progress; the control (verbatim redirect) carries A's search, sort and both conditions.",
      "files_changed": [
        "ui/apps/console/src/components/ProtectedRoute.tsx (LoginRedirect body and doc comment; +12 -1)",
        "ui/packages/app-shell/src/views/userFilterUrlState.ts (new withoutListStateParams helper; +17)",
        "ui/packages/app-shell/src/index.ts (export of the helper; app-shell is private: true; +3)",
        "ui/apps/console/src/components/ProtectedRoute.signOutListState-29174.test.tsx (new, 238 lines, 4 cases)",
        "ui/packages/app-shell/src/views/ObjectView.listUrlState-11860.test.tsx (one new describe, 3 cases; +44 -1)",
        "ui/packages/app-shell/src/views/userFilterUrlState.withoutListState-29174.test.ts (new, 89 lines, 6 cases)",
        ".changeset/29174-sign-out-drops-list-state.md (new; @object-ui/app-shell patch, @object-ui/console patch; Clause-② no; Metadata-protocol none)",
        "content/docs/ui/views.mdx (one bullet; +2)"
      ],
      "line_budget": "diff vs origin/main 74a19dd8: 8 files, +420 -2 (source +32 -1; tests +371 -1; changeset 15; docs 2); under the 3000-line human-merge threshold",
      "declaration": "Clause-②: no · Metadata-protocol: none (copied from the claim into the PR body and the changeset)",
      "tests": "19 files / 194 passed at 97150043, 13da5477 and a1fa3999 (3 test files of this change plus 16 neighbours: Console App route tests, RootLandingRedirect, SetupRoute, StudioRoute x3, FormPage.predicateScope, AcceptInvitationRoute, i18nAfterSignIn, internalFormShell, ObjectView.ufViewSwitch, userFilterUrlState.listState, product-neutral ratchet). Ablation 1 (scripts/ablation-replace.mjs, verbatim redirect restored in LoginRedirect): console file 2 failed / 2 passed (sign-out path and cold uf link red; invitation and plain deep link green), restored blob 30df279f == HEAD. Ablation 2 (helper appends every param): 7 failed / 34 passed across the 3 files, restored blob 44703363 == HEAD. Type-check app-shell + console exit 0 at a1fa3999. Root eslint --no-inline-config on the 6 ts/tsx files: 0 errors, 8 pre-existing no-explicit-any warnings outside the hunks. turbo build 117/117. Real browser on a showcase boot at 97150043: see summary; same-user sign-out and sign-in got the user's own search back from per-user storage.",
      "gates": "dispatch-gates --commands: 66 derived, the same 66 at 13da5477 and a1fa3999; all 66 ran at both heads with exit 0 (check:type-check-debt and ui vitest run scripts/ under the lock; scripts suite 169 passed 2 skipped, 4900 tests). --ran at a1fa3999: 66 derived, 66 run, 0 NOT-MEASURED, 0 UNRUN. check:new-line-citations 0 new. Body gates against the stored body: changeset-no-major 0, adr-0087-registration 0, empty-changeset 0, partof-closing-keyword 0, closing-keyword-parity --body binds only Fixes #29174 (exit 2 = one binding, as intended). PR check-runs: starting at report time (seat re-runs).",
      "merge_main": "merged origin/main twice: 2f29c84d (12 commits) then 74a19dd8 (26 commits); both clean, no regen pending; grep of the touched files found no parallel fix",
      "dogfood_battery": "n/a: no ! in the title and no (narrowing) arm",
      "deviations": [
        "The helper lives in userFilterUrlState.ts and is exported from app-shell's index.ts, outside the claim's literal file list; named to the seat and accepted 09:18Z.",
        "Same-user session expiry also drops the uf_* state: the guard cannot tell sign-out from expiry without new @object-ui/auth state. The user's own return path is kept, and their filter panel, sort and search come back from their per-user storage; quick-filter chips and toolbar grouping fall back to the view default. Written into the PR body's acceptance notes.",
        "A signed-out visitor opening a shared list link lands on the list after sign-in on their own state, not the link's; an already signed-in visitor is unchanged.",
        "PR has no assignee and no reviewers (common rule 10)."
      ],
      "mcp_calls": "5 GitHub MCP reads, 0 writes (issue_read x3, list_pull_requests x2); session tools: get_session x1, subscribe_pr_activity x1 on #29305",
      "api_writes": "2: relay pr_create (objectstack#29305, read back 12088 bytes identical, assignees none) and this post-stamped comment on #29174. git push is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: none (inconsistency) · app-shell's DefaultLoginPage ignores ?redirect= and navigates to '/', while the Console LoginPage, RegisterPage and VerifyEmailPromptPage follow it; a host that uses app-shell's login page loses the return path entirely · carrier: seat · noted, not filed"
      ]
    }
  6. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#3 · zhuangjianguo · session_01QsR2jV4dWohojjbTJTQq5m

    Landed: PR #29305 merged at 2026-11-02T12:25Z as 62e2d070. Fixes #29174 closed this card; pm:dispatched removed. Delivered: when a session ends, the sign-in page's return link keeps the list's path and other params but drops every uf_* list state, so whoever signs in next opens their own stored state or the view's default, never the previous user's filter, search or sort. Nothing left on this card.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:uiDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions