You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Saved connections and the native "切换组织" switcher (organizations and company servers) #152
The shell stores one server URL today. In the design, a user can keep several connections and switch between them from a native bottom sheet titled 切换组织 (design screen 8):
Cloud organizations, each opening its organization's single production environment;
private company servers.
The Cloud console's nouns are organization and environment (objectstack-ai/cloud#2908), so the app uses no third noun such as "workspace".
Ask
Connection store. Replace the single serverUrl with an ordered list of connections in SecureStore. Each record holds:
kind: cloud or server;
label (the organization name, or the server's branding product name);
host;
brand colour and initial;
lastRoute;
addedAt.
One connection is current. Migrate the existing single serverUrl into the list on first launch.
Switcher sheet (screen 8).
The sheet lists the connections, each with a kind badge (Cloud / 私有) and its host, and marks the current one.
It has an "添加组织或公司服务器" action, which goes back to the welcome screen.
Swipe-to-remove removes the connection and signs it out (below).
It links to app settings.
Switching.
Switching remounts the Console WebView on the chosen host at that connection's lastRoute.
Each connection's cookies are isolated by host.
The bridge's origin check follows the current connection.
Removal signs out.
Clear that host's cookies from the WebView cookie store; this needs a cookie-manager module. Name the module and its Expo config plugin in the PR.
Unregister its push token through the Console if it is loaded; otherwise record that the push token is left registered.
Opening the switcher from the Console.
Add a bridge method showConnectionSwitcher(), announced as a capability.
After acceptance, file the Console consumer card in objectstack-ai/objectstack: the header's organization button calls it when has('showConnectionSwitcher').
Offline page (screen 9): the secondary action becomes 切换组织 and opens the sheet.
Acceptance
Unit tests cover the store (add, dedupe by host, remove, current-connection rules, migration from the single URL).
Component tests cover the sheet.
A switch remounts the WebView on the right host and route.
docs/HYBRID-SHELL.md documents the connection model and the new bridge method.
Human floor: the removal step clears a host's cookies through a new cookie-manager native module. The PR names it. It is session handling, so this seat flags it to the maintainer at review.
Claim: PM loop round 1
Session: session_01QSeKYnW1Xf6HDSWz1eGAqv
Account: os-justin
Branch: claude/issue-152-saved-connections
Worktree: mobile-issue-152
Domain: n/a — single-lane repo (repo:mobile)
Seat: repo:mobile#1
File surface: stores/server-store.ts, lib/server-url.ts, a new connections store or module under lib/ or stores/, a new switcher sheet under components/, app/server-config.tsx (the add-connection entry and back to welcome), app/_layout.tsx and app/console.tsx (switching), components/shell/ConsoleWebView.tsx (remount on host and route, through the load state machine's recreate), components/shell/OfflineFallback.tsx, lib/shell/bridge/** (showConnectionSwitcher), app.config.ts, package.json and pnpm-lock.yaml (the cookie-manager module and its config plugin), locales/*.json (all ten), __tests__/**, docs/HYBRID-SHELL.md, .changeset/152-saved-connections.md (stop on breach; explain in the report)
Container & model: L, mode:subagent (jest-only build, light; judged per card), model: default tier (opus) — dispatch-gates --tier --repo objectstack-ai/mobile: no path-derived mandate, effort: xhigh (session handling on removal)
Clause-②: no
Metadata-protocol: none
Responsibility: n/a — not a defect card
Thread-read: 6576837988
Premise: holds. On main at 0fd7113, stores/server-store.ts holds a single serverUrl. BRIDGE_METHODS has no showConnectionSwitcher. There is no cookie-manager dependency.
Serial constraints cleared: #166 merged (0fd7113), and this card's remount routes through its load state machine. No other PR is in flight.
{
"issue": 152,
"status": "done",
"branch": "claude/issue-152-saved-connections",
"pr": "https://github.com/objectstack-ai/mobile/pull/173",
"session": "session_01QSeKYnW1Xf6HDSWz1eGAqv",
"premise_still_valid": true,
"summary": "The single serverUrl is now an ordered list of saved connections. The list lives in a versioned SecureStore document (objectstack_connections) and is migrated exactly once from objectstack_server_url, which is deleted after a successful write. Records are deduplicated by host, and when the current connection is removed the first remaining one becomes current. A native 切换组织 sheet (components/shell/ConnectionSwitcher.tsx) opens from the new bridge method showConnectionSwitcher() and from the offline page's secondary action. Its add entry goes back to the address step, so a connection is saved only after the probe and confirm step. Switching re-creates the WebView on the chosen host at that connection's lastRoute, through a new load-state-machine event, switch, that emits the existing recreate effect; console.tsx drops key={serverUrl}, and documentRef is still bumped only in recreate. Removing a connection asks its loaded Console to unregister the push token through the new shell event objectstack-native:connectionRemoved (waitUntil, then a connectionRemovedDone answer, 10 s timeout), drops the record, and then clears only that host's own cookies with @preeternal/react-native-cookie-manager 7.0.0, whose config plugin is registered in app.config.ts. Any other outcome records the host in pushLeftRegistered, and the shell calls no API. Measured premises: on main at 0fd7113 the store held a single serverUrl, BRIDGE_METHODS had no showConnectionSwitcher, and there was no cookie-manager dependency, as the PM stated. Per-host isolation already holds for host-only cookies, which are the platform default; cookies with a parent Domain (OS_COOKIE_DOMAIN) are shared by design and are left in place on removal.",
"tests": "Run at head 1996fb4. npx jest --ci --coverage --maxWorkers=2: Test Suites 39 passed, 39 total; Tests 669 passed, 669 total; lib/connections at 95.34% statements and 96.99% lines. New suites: __tests__/lib/connections/{model,cookies,sign-out}.test.ts, __tests__/components/ConnectionSwitcher.test.tsx and __tests__/components/ConsoleScreen.test.tsx. Extended suites: server-url (upgrade with a saved URL and a fresh install), server-store, console-load (switch), bridge-handler, bridge-protocol, bridge-scripts (showConnectionSwitcher and the connectionRemoved script run against real DOM events), ConsoleWebView (switch, the origin gate after a switch, askConsoleToRemove answers, timeout and re-create), ServerConfigScreen (connect gets the probed server; ?add=1 starts with an empty address) and RtlLayout. Cold run: npx jest --ci --no-cache --maxWorkers=2 --coverage --coverageThreshold='{}' over the two new component suites, 17 passed, Time 18.081 s. Both suites use the beforeAll warm-up. Ablation 1, through objectstack scripts/ablation-replace.mjs: the shared-name guard in lib/connections/cookies.ts. Blob f7f1dad went to e57ec6b and the anchor count went from 1 to 0; cookies.test.ts then had 2 failed and 13 passed. The restore was proven: blob equals HEAD and git diff HEAD is empty. The first attempt was refused before it ran, because the replacement text already occurred inside the anchor (count 2 to 2), so it gave no reading. Ablation 2: the switch dispatch in ConsoleWebView.tsx's layout effect was replaced by a no-op. Blob 4a58b2b went to 60dc7c0; ConsoleWebView and ConsoleScreen then had 8 failed and 52 passed (every switching case and the screen's switch, resume and remove-current cases), and the restore was proven. There is no build or dist step: the tests resolve the source through the jest ~/ moduleNameMapper.",
"gates": "Local, at head 1996fb4, each run through objectstack scripts/pm/os-verify-lock.sh. Exit codes were captured before any pipe and are logged in scratchpad issue-152/gates.txt: pnpm install --frozen-lockfile EXIT=0; npx tsc --noEmit EXIT=0 (VERDICT command-exit 0); npx eslint . --ext .ts,.tsx EXIT=0 (VERDICT command-exit 0; a --format json run over the new files lints 5 files with 0 errors and 0 warnings); npx jest --ci --coverage EXIT=0; cold run on the new component suites EXIT=0. This is the whole of mobile's CI: lint-typecheck.yml runs tsc and eslint, and ci.yml runs jest --ci --coverage. dispatch-gates.mjs is objectstack-only and does not apply here. PR #173 check-runs, read once right after the open: Lint & Type Check in_progress, Code Quality in_progress. Not waited on.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)",
"mcp_calls": "0 — no MCP GitHub calls",
"api_writes": "2 — (1) one fleet-write relay stroke: pr_create, sent as POST /repos/objectstack-ai/objectstack/dispatches and executed as POST /repos/objectstack-ai/mobile/pulls (draft, no labels, no assignees; read-back reports 13428 bytes sent and stored, identical); (2) this report comment through post-stamped.mjs, POST /repos/objectstack-ai/mobile/issues/152/comments. Plus git push of the branch, which is not a REST write. Reads used gh api GET only.",
"line_budget": "44 files changed, 3635 insertions(+), 219 deletions(-), 3854 changed lines (git diff --shortstat 0fd7113..1996fb4). That is over 3,000; tests account for about 1,900 of the lines.",
"files_changed": [
"A:.changeset/152-saved-connections.md",
"A:__tests__/components/ConnectionSwitcher.test.tsx",
"A:__tests__/components/ConsoleScreen.test.tsx",
"M:__tests__/components/ConsoleWebView.test.tsx",
"M:__tests__/components/RtlLayout.test.tsx",
"M:__tests__/components/ServerConfigScreen.test.tsx",
"A:__tests__/lib/connections/cookies.test.ts",
"A:__tests__/lib/connections/model.test.ts",
"A:__tests__/lib/connections/sign-out.test.ts",
"M:__tests__/lib/i18n.test.ts",
"M:__tests__/lib/server-store.test.ts",
"M:__tests__/lib/server-url.test.ts",
"M:__tests__/lib/shell/bridge-handler.test.ts",
"M:__tests__/lib/shell/bridge-protocol.test.ts",
"M:__tests__/lib/shell/bridge-scripts.test.ts",
"M:__tests__/lib/shell/console-load.test.ts",
"M:app.config.ts",
"M:app/console.tsx",
"M:app/server-config.tsx",
"A:components/shell/ConnectionSwitcher.tsx",
"M:components/shell/ConsoleWebView.tsx",
"M:components/shell/OfflineFallback.tsx",
"M:docs/HYBRID-SHELL.md",
"A:lib/connections/cookies.ts",
"A:lib/connections/model.ts",
"A:lib/connections/sign-out.ts",
"M:lib/server-url.ts",
"M:lib/shell/bridge/handler.ts",
"M:lib/shell/bridge/protocol.ts",
"M:lib/shell/bridge/scripts.ts",
"M:lib/shell/console-load.ts",
"M:locales/ar.json",
"M:locales/de.json",
"M:locales/en.json",
"M:locales/es.json",
"M:locales/fr.json",
"M:locales/ja.json",
"M:locales/ko.json",
"M:locales/pt.json",
"M:locales/ru.json",
"M:locales/zh.json",
"M:package.json",
"M:pnpm-lock.yaml",
"M:stores/server-store.ts"
],
"deviations": [
"lib/shell/console-load.ts and __tests__/lib/shell/console-load.test.ts are outside the claim's file surface. The ruled remount path is the machine's recreate, but its recreate event starts the new WebView at the old host's pending or last route, so the real producer of the remount is the machine. The fix is a switch event that emits the same recreate effect. The landing point and the reason are in the PR body.",
"The changed-line count (3854) exceeds 3,000, the AGENTS.md size class. Merging is the maintainer's anyway in this repo.",
"Ablation 1's first attempt was a refused no-op (the replacement text already occurred in the anchor), with no reading. It was re-run with a unique replacement.",
"Commit trailers use the model-free pair from AGENTS.md (Co-authored-by: Claude) instead of the harness reminder's model-named Co-Authored-By. The PR footer uses the AGENTS.md session-URL form.",
"The changeset is a patch, following the rule that Metadata-protocol none takes patch. Mobile's own history is mixed: 154, 156 and 159 used minor; 162 and 163 used patch."
],
"open_questions": [
{
"question": "With a list of connections, what should the existing bridge method resetServer() mean? The Console has no in-app caller (only nativeResetServer in ui/packages/mobile).",
"options": [
"A. As built: forget the current connection (the next saved one becomes current), open the picker, and clear no cookies. This is unchanged from the single-server shell, which never cleared cookies on reset.",
"B. Route it through the same removal as the swipe: ask the Console to unregister the push token, drop the record and clear the host's cookies. This is a sign-out, so it is a session-handling change.",
"C. Keep the list, unset the current connection and open the picker (forgets nothing)."
],
"recommendation": "A for now. It changes no session behaviour, and B is a session-handling decision on the lane's human floor; if the maintainer wants reset to sign out, B is a two-line change in app/console.tsx."
},
{
"question": "Cookies set with a parent Domain (a platform deployed with OS_COOKIE_DOMAIN, possibly ObjectOS Cloud's *.objectos.ai) are shared by every environment under that domain. Removal leaves them in place, so removing one Cloud organization does not sign it out if its session cookie is such a shared cookie. Is that acceptable?",
"options": [
"A. As built: never clear a shared cookie, and count it as kept. The removed connection may stay signed in through the shared cookie.",
"B. Clear shared cookies too. This signs out every sibling environment, which is what the PM ruled out.",
"C. Ask the Console to sign out server-side in the connectionRemoved handler (revoke the session), in addition to the push-token unregistration. This needs Console work and a contract decision."
],
"recommendation": "A, and decide C with the Console consumer card. First, measure whether Cloud sets OS_COOKIE_DOMAIN (cloud repo); if it does not, A fully signs out."
},
{
"question": "On an Android WebView provider without GET_COOKIE_INFO, the cookie store reports no domains and refuses per-name clearing, so removal clears nothing there (recorded as unscoped). Is that acceptable?",
"options": [
"A. As built: clear nothing, and record unscoped in a breadcrumb.",
"B. Fall back to writing expired host-only cookies for the names getCookie(url) reports (path / only). This could miss path-restricted cookies and could not tell host-only cookies from shared ones."
],
"recommendation": "A. B would guess, and the ruling says not to clear what cannot be scoped."
}
],
"security_findings": "withheld",
"out_of_scope_findings": [
"class: none (observation, not filed) · reach: push taps after this PR · evidence: hooks/usePushRouting.ts routes a notification's `route` to the current connection's Console (routeFromPushData returns the route without checking the server). With several saved servers each registering the device token, a tap on another server's notification opens its route on the current host. Documented as a Known limit in HYBRID-SHELL.md and in the PR Acceptance notes. · carrier: none · noted, not filed · dedupe words: push routing per connection, notification wrong server, multi-server push tap",
"class: none (observation) · reach: removal · evidence: react-native-webview 13.16 exposes no per-origin web-storage removal, and iOS WKWebsiteDataStore records are per registrable domain, so a removed host's localStorage and IndexedDB are not cleared, and its server-side session is not revoked. Documented as a Known limit. · carrier: none · noted, not filed",
"class: none (fixed in this PR, same surface) · evidence: components/ui/BottomSheet.tsx labels its backdrop with t('common.close'), a key no locale bundle had; the switcher is its first user. common.close is added to all ten bundles. · carrier: PR #173 (fixed there)",
"class: none (test infrastructure) · evidence: importing expo-web-browser under jest throws, because jest.setup.ts stubs AppState without a default export (TypeError reading currentState); ConsoleScreen.test.tsx mocks ~/lib/shell/external instead. · carrier: none · noted, not filed"
]
}
ACCEPT → PR #173 (head 1996fb4). It carries four questions for the maintainer (Q1 settled by the seat as no behaviour change; Q2–Q4 on sign-out and cookie handling). It is over the 3000-line threshold, so the maintainer approves and merges it; the seat does not enable auto-merge. Follow-up filed: #174 (push taps route to the sending connection).
Landed.#173 merged through the queue as 678fbe9 (maintainer). The merge_group run passed CI and Lint & Type Check. The card closed completed; pm:dispatched removed.
Notes:
Native rebuild owed: this adds the cookie-manager native module.
Ruling: batch #2 item 5 · letter B · maintainer 「批 #2 同意」 2026-11-03T08:21Z
This ruling answers the open questions on PR #173, which were merged as built.
Q3 ratified: an Android WebView that reports no cookie domains clears nothing and leaves a breadcrumb.
Q4 ratified: cookie values pass through JS memory transiently during removal; they are never stored, logged or sent.
Q2 redirected: removal is to become a server-side sign-out. When a connection is removed, the Console asks its server to revoke that organization's session. This is added to Cloud mode: sign in at cloud.objectos.ai, pick an organization, land in its environment #153 (Cloud mode), which must also confirm with objectstack-ai/cloud#3489 whether Cloud sets OS_COOKIE_DOMAIN.
Activity
objectstack-fleet commentedon Nov 2, 2026
repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv
First touch, gate #150 released. This card goes to
pm:queueas typeenhancement.app/server-config.tsx, and Private servers: probe with the anonymous discovery endpoints and confirm the server's identity before connecting #154 replaceslib/server-url.ts, which this card's store migration reads.objectstack-fleet commentedon Nov 2, 2026
repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv
Claim: PM loop round 1
Session:
session_01QSeKYnW1Xf6HDSWz1eGAqvAccount:
os-justinBranch:
claude/issue-152-saved-connectionsWorktree:
mobile-issue-152Domain:
n/a — single-lane repo (repo:mobile)Seat:
repo:mobile#1File surface:
stores/server-store.ts,lib/server-url.ts, a new connections store or module underlib/orstores/, a new switcher sheet undercomponents/,app/server-config.tsx(the add-connection entry and back to welcome),app/_layout.tsxandapp/console.tsx(switching),components/shell/ConsoleWebView.tsx(remount on host and route, through the load state machine'srecreate),components/shell/OfflineFallback.tsx,lib/shell/bridge/**(showConnectionSwitcher),app.config.ts,package.jsonandpnpm-lock.yaml(the cookie-manager module and its config plugin),locales/*.json(all ten),__tests__/**,docs/HYBRID-SHELL.md,.changeset/152-saved-connections.md(stop on breach; explain in the report)Container & model:
L,mode:subagent(jest-only build, light; judged per card),model: default tier (opus) — dispatch-gates --tier --repo objectstack-ai/mobile: no path-derived mandate,effort: xhigh(session handling on removal)Clause-②: no
Metadata-protocol: none
Responsibility:
n/a — not a defect cardThread-read: 6576837988
Premise: holds. On
mainat0fd7113,stores/server-store.tsholds a singleserverUrl.BRIDGE_METHODShas noshowConnectionSwitcher. There is no cookie-manager dependency.Serial constraints cleared: #166 merged (
0fd7113), and this card's remount routes through its load state machine. No other PR is in flight.objectstack-fleet commentedon Nov 2, 2026
repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv
os-dev-report
{ "issue": 152, "status": "done", "branch": "claude/issue-152-saved-connections", "pr": "https://github.com/objectstack-ai/mobile/pull/173", "session": "session_01QSeKYnW1Xf6HDSWz1eGAqv", "premise_still_valid": true, "summary": "The single serverUrl is now an ordered list of saved connections. The list lives in a versioned SecureStore document (objectstack_connections) and is migrated exactly once from objectstack_server_url, which is deleted after a successful write. Records are deduplicated by host, and when the current connection is removed the first remaining one becomes current. A native 切换组织 sheet (components/shell/ConnectionSwitcher.tsx) opens from the new bridge method showConnectionSwitcher() and from the offline page's secondary action. Its add entry goes back to the address step, so a connection is saved only after the probe and confirm step. Switching re-creates the WebView on the chosen host at that connection's lastRoute, through a new load-state-machine event, switch, that emits the existing recreate effect; console.tsx drops key={serverUrl}, and documentRef is still bumped only in recreate. Removing a connection asks its loaded Console to unregister the push token through the new shell event objectstack-native:connectionRemoved (waitUntil, then a connectionRemovedDone answer, 10 s timeout), drops the record, and then clears only that host's own cookies with @preeternal/react-native-cookie-manager 7.0.0, whose config plugin is registered in app.config.ts. Any other outcome records the host in pushLeftRegistered, and the shell calls no API. Measured premises: on main at 0fd7113 the store held a single serverUrl, BRIDGE_METHODS had no showConnectionSwitcher, and there was no cookie-manager dependency, as the PM stated. Per-host isolation already holds for host-only cookies, which are the platform default; cookies with a parent Domain (OS_COOKIE_DOMAIN) are shared by design and are left in place on removal.", "tests": "Run at head 1996fb4. npx jest --ci --coverage --maxWorkers=2: Test Suites 39 passed, 39 total; Tests 669 passed, 669 total; lib/connections at 95.34% statements and 96.99% lines. New suites: __tests__/lib/connections/{model,cookies,sign-out}.test.ts, __tests__/components/ConnectionSwitcher.test.tsx and __tests__/components/ConsoleScreen.test.tsx. Extended suites: server-url (upgrade with a saved URL and a fresh install), server-store, console-load (switch), bridge-handler, bridge-protocol, bridge-scripts (showConnectionSwitcher and the connectionRemoved script run against real DOM events), ConsoleWebView (switch, the origin gate after a switch, askConsoleToRemove answers, timeout and re-create), ServerConfigScreen (connect gets the probed server; ?add=1 starts with an empty address) and RtlLayout. Cold run: npx jest --ci --no-cache --maxWorkers=2 --coverage --coverageThreshold='{}' over the two new component suites, 17 passed, Time 18.081 s. Both suites use the beforeAll warm-up. Ablation 1, through objectstack scripts/ablation-replace.mjs: the shared-name guard in lib/connections/cookies.ts. Blob f7f1dad went to e57ec6b and the anchor count went from 1 to 0; cookies.test.ts then had 2 failed and 13 passed. The restore was proven: blob equals HEAD and git diff HEAD is empty. The first attempt was refused before it ran, because the replacement text already occurred inside the anchor (count 2 to 2), so it gave no reading. Ablation 2: the switch dispatch in ConsoleWebView.tsx's layout effect was replaced by a no-op. Blob 4a58b2b went to 60dc7c0; ConsoleWebView and ConsoleScreen then had 8 failed and 52 passed (every switching case and the screen's switch, resume and remove-current cases), and the restore was proven. There is no build or dist step: the tests resolve the source through the jest ~/ moduleNameMapper.", "gates": "Local, at head 1996fb4, each run through objectstack scripts/pm/os-verify-lock.sh. Exit codes were captured before any pipe and are logged in scratchpad issue-152/gates.txt: pnpm install --frozen-lockfile EXIT=0; npx tsc --noEmit EXIT=0 (VERDICT command-exit 0); npx eslint . --ext .ts,.tsx EXIT=0 (VERDICT command-exit 0; a --format json run over the new files lints 5 files with 0 errors and 0 warnings); npx jest --ci --coverage EXIT=0; cold run on the new component suites EXIT=0. This is the whole of mobile's CI: lint-typecheck.yml runs tsc and eslint, and ci.yml runs jest --ci --coverage. dispatch-gates.mjs is objectstack-only and does not apply here. PR #173 check-runs, read once right after the open: Lint & Type Check in_progress, Code Quality in_progress. Not waited on.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm (Clause-②: no)", "mcp_calls": "0 — no MCP GitHub calls", "api_writes": "2 — (1) one fleet-write relay stroke: pr_create, sent as POST /repos/objectstack-ai/objectstack/dispatches and executed as POST /repos/objectstack-ai/mobile/pulls (draft, no labels, no assignees; read-back reports 13428 bytes sent and stored, identical); (2) this report comment through post-stamped.mjs, POST /repos/objectstack-ai/mobile/issues/152/comments. Plus git push of the branch, which is not a REST write. Reads used gh api GET only.", "line_budget": "44 files changed, 3635 insertions(+), 219 deletions(-), 3854 changed lines (git diff --shortstat 0fd7113..1996fb4). That is over 3,000; tests account for about 1,900 of the lines.", "files_changed": [ "A:.changeset/152-saved-connections.md", "A:__tests__/components/ConnectionSwitcher.test.tsx", "A:__tests__/components/ConsoleScreen.test.tsx", "M:__tests__/components/ConsoleWebView.test.tsx", "M:__tests__/components/RtlLayout.test.tsx", "M:__tests__/components/ServerConfigScreen.test.tsx", "A:__tests__/lib/connections/cookies.test.ts", "A:__tests__/lib/connections/model.test.ts", "A:__tests__/lib/connections/sign-out.test.ts", "M:__tests__/lib/i18n.test.ts", "M:__tests__/lib/server-store.test.ts", "M:__tests__/lib/server-url.test.ts", "M:__tests__/lib/shell/bridge-handler.test.ts", "M:__tests__/lib/shell/bridge-protocol.test.ts", "M:__tests__/lib/shell/bridge-scripts.test.ts", "M:__tests__/lib/shell/console-load.test.ts", "M:app.config.ts", "M:app/console.tsx", "M:app/server-config.tsx", "A:components/shell/ConnectionSwitcher.tsx", "M:components/shell/ConsoleWebView.tsx", "M:components/shell/OfflineFallback.tsx", "M:docs/HYBRID-SHELL.md", "A:lib/connections/cookies.ts", "A:lib/connections/model.ts", "A:lib/connections/sign-out.ts", "M:lib/server-url.ts", "M:lib/shell/bridge/handler.ts", "M:lib/shell/bridge/protocol.ts", "M:lib/shell/bridge/scripts.ts", "M:lib/shell/console-load.ts", "M:locales/ar.json", "M:locales/de.json", "M:locales/en.json", "M:locales/es.json", "M:locales/fr.json", "M:locales/ja.json", "M:locales/ko.json", "M:locales/pt.json", "M:locales/ru.json", "M:locales/zh.json", "M:package.json", "M:pnpm-lock.yaml", "M:stores/server-store.ts" ], "deviations": [ "lib/shell/console-load.ts and __tests__/lib/shell/console-load.test.ts are outside the claim's file surface. The ruled remount path is the machine's recreate, but its recreate event starts the new WebView at the old host's pending or last route, so the real producer of the remount is the machine. The fix is a switch event that emits the same recreate effect. The landing point and the reason are in the PR body.", "The changed-line count (3854) exceeds 3,000, the AGENTS.md size class. Merging is the maintainer's anyway in this repo.", "Ablation 1's first attempt was a refused no-op (the replacement text already occurred in the anchor), with no reading. It was re-run with a unique replacement.", "Commit trailers use the model-free pair from AGENTS.md (Co-authored-by: Claude) instead of the harness reminder's model-named Co-Authored-By. The PR footer uses the AGENTS.md session-URL form.", "The changeset is a patch, following the rule that Metadata-protocol none takes patch. Mobile's own history is mixed: 154, 156 and 159 used minor; 162 and 163 used patch." ], "open_questions": [ { "question": "With a list of connections, what should the existing bridge method resetServer() mean? The Console has no in-app caller (only nativeResetServer in ui/packages/mobile).", "options": [ "A. As built: forget the current connection (the next saved one becomes current), open the picker, and clear no cookies. This is unchanged from the single-server shell, which never cleared cookies on reset.", "B. Route it through the same removal as the swipe: ask the Console to unregister the push token, drop the record and clear the host's cookies. This is a sign-out, so it is a session-handling change.", "C. Keep the list, unset the current connection and open the picker (forgets nothing)." ], "recommendation": "A for now. It changes no session behaviour, and B is a session-handling decision on the lane's human floor; if the maintainer wants reset to sign out, B is a two-line change in app/console.tsx." }, { "question": "Cookies set with a parent Domain (a platform deployed with OS_COOKIE_DOMAIN, possibly ObjectOS Cloud's *.objectos.ai) are shared by every environment under that domain. Removal leaves them in place, so removing one Cloud organization does not sign it out if its session cookie is such a shared cookie. Is that acceptable?", "options": [ "A. As built: never clear a shared cookie, and count it as kept. The removed connection may stay signed in through the shared cookie.", "B. Clear shared cookies too. This signs out every sibling environment, which is what the PM ruled out.", "C. Ask the Console to sign out server-side in the connectionRemoved handler (revoke the session), in addition to the push-token unregistration. This needs Console work and a contract decision." ], "recommendation": "A, and decide C with the Console consumer card. First, measure whether Cloud sets OS_COOKIE_DOMAIN (cloud repo); if it does not, A fully signs out." }, { "question": "On an Android WebView provider without GET_COOKIE_INFO, the cookie store reports no domains and refuses per-name clearing, so removal clears nothing there (recorded as unscoped). Is that acceptable?", "options": [ "A. As built: clear nothing, and record unscoped in a breadcrumb.", "B. Fall back to writing expired host-only cookies for the names getCookie(url) reports (path / only). This could miss path-restricted cookies and could not tell host-only cookies from shared ones." ], "recommendation": "A. B would guess, and the ruling says not to clear what cannot be scoped." } ], "security_findings": "withheld", "out_of_scope_findings": [ "class: none (observation, not filed) · reach: push taps after this PR · evidence: hooks/usePushRouting.ts routes a notification's `route` to the current connection's Console (routeFromPushData returns the route without checking the server). With several saved servers each registering the device token, a tap on another server's notification opens its route on the current host. Documented as a Known limit in HYBRID-SHELL.md and in the PR Acceptance notes. · carrier: none · noted, not filed · dedupe words: push routing per connection, notification wrong server, multi-server push tap", "class: none (observation) · reach: removal · evidence: react-native-webview 13.16 exposes no per-origin web-storage removal, and iOS WKWebsiteDataStore records are per registrable domain, so a removed host's localStorage and IndexedDB are not cleared, and its server-side session is not revoked. Documented as a Known limit. · carrier: none · noted, not filed", "class: none (fixed in this PR, same surface) · evidence: components/ui/BottomSheet.tsx labels its backdrop with t('common.close'), a key no locale bundle had; the switcher is its first user. common.close is added to all ten bundles. · carrier: PR #173 (fixed there)", "class: none (test infrastructure) · evidence: importing expo-web-browser under jest throws, because jest.setup.ts stubs AppState without a default export (TypeError reading currentState); ConsoleScreen.test.tsx mocks ~/lib/shell/external instead. · carrier: none · noted, not filed" ] }objectstack-fleet commentedon Nov 2, 2026
repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv
ACCEPT → PR #173 (head
1996fb4). It carries four questions for the maintainer (Q1 settled by the seat as no behaviour change; Q2–Q4 on sign-out and cookie handling). It is over the 3000-line threshold, so the maintainer approves and merges it; the seat does not enable auto-merge. Follow-up filed: #174 (push taps route to the sending connection).objectstack-fleet commentedon Nov 2, 2026
repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv
Landed. #173 merged through the queue as
678fbe9(maintainer). Themerge_grouprun passedCIandLint & Type Check. The card closedcompleted;pm:dispatchedremoved.Notes:
objectstack-fleet commentedon Nov 3, 2026
repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv
Ruling: batch #2 item 5 · letter B · maintainer 「批 #2 同意」 2026-11-03T08:21Z
This ruling answers the open questions on PR #173, which were merged as built.
OS_COOKIE_DOMAIN.Effort: none here. One added item on #153.