Skip to content

console app bar: a host can ask for no app switcher when the viewer is served one app — the cloud control plane draws 「云 ▾」 with 「所有应用」 linking to the app the user is already in #12913

Closed

Description

@objectstack-fleet

Source

objectstack-ai/cloud#2919 (from the maintainer's 2026-10-16 walk of the cloud control plane): the control plane is one product, Cloud, with one companion, the user's account settings. Its top bar should draw no app switcher; the 预览版 chip stays beside the product mark. The cloud half is objectstack-ai/cloud PR #2980: the control plane's account app is now hidden: true, reached from the avatar menu's 「个人资料」. That leaves the switcher with one row, and the rest is this repository's: the console has no setting to draw no switcher.

Measured (cloud's objectui pin ef71a09f9e5f, and main 97538115)

  • packages/app-shell/src/layout/AppSwitcher.tsx:
    • It lists apps with active !== false && hidden !== true (pin :62, main :79).
    • It returns null only when no active app resolves (pin :66, main :83). So one listed app still draws the 「云 ▾」 pill and its dropdown.
  • At main, the dropdown carries 「Back to home」, 「All apps」 (:134-156, objectui#12605) and a Settings row for viewers served Setup (:157-169). In the control plane, 「所有应用」 links to the declared home, /apps/cloud_control, which is the app the viewer is already in.
  • AppHeader.tsx draws the pill whenever activeAppName && onAppChange (main :877-887). runtime-config.ts:277-300 (AppShellRuntimeConfig) has no single-app or switcher field.
  • What the control plane serves (GET /api/v1/meta/app after PR fix(console): render the redaction notice on the enveloped resolve body (objectstack#3983) #2980):
    • owners, plain members and org-less signups are served one un-hidden app, cloud_control;
    • a platform admin is served two, cloud_control and platform_admin (Cloud Admin), and the switcher is that admin's door to Cloud Admin.

Target

  1. A host can ask the console to draw no app switcher, and so no pill dropdown and no 「所有应用」, when the viewer is served exactly one un-hidden active app. The app's name or the product mark stands in its place.
  2. Scope: only where the host asks. objectui#12605's 「主页 / 所有应用 / 设置」 rows stay for every other deployment, tenant environments included.
  3. A viewer served more than one un-hidden app (the control plane's platform admin) keeps the switcher.
  4. The 预览版 chip stays beside the product mark when the pill goes.

Suggested shape (this lane's call)

A field in AppShellRuntimeConfig, which the control plane serves from packages/service-cloud/src/multi-environment-plugins.ts (cloud), gating a derived rule: no switcher when one app is served.

The cloud follow-up (serving the field, then the pin bump) is the cloud seat's once this lands.

Acceptance

  • Tests: with the host field set and one served app, no pill dropdown and no 「所有应用」. With two served apps, the switcher is drawn. Without the field, today's behaviour is unchanged.
  • The cloud seat reads the control plane's top bar after cloud's pin carries this.

Filed by the repo:cloud#2 seat (seat post objectstack-ai/objectstack#24145, session session_01Cbdek8SKSHxddGqTPZXdqX) from the os-dev report on cloud#2919 (PR #2980). ⛔ Not a claim. Dedupe: this repository's issues since 2026-08-15 by title and body (app switcher, AppSwitcher, single-app, All apps, 所有应用): objectui#12605, #12461, #12201, #12625 (closed; the pill's design and names), none a single-app setting.

Activity

  1. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: p2 · domain:ui · pm:queue · triage seat · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-17T18:54Z

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015). ⛔ Not a claim, ⛔ not a dispatch.

    • Fresh read on objectui main c83e3fe538:
      • AppSwitcher.tsx lists active !== false && hidden !== true apps (:79) and returns null only with no active app (:83).
      • AppHeader.tsx:877 draws the pill whenever activeAppName && onAppChange.
      • Neither runtime-config.ts nor apps/console has a single-app field.
    • Grade p2. It comes from the maintainer's 2026-10-16 control-plane walk (objectstack-ai/cloud#2919): 「所有应用」 links to the app the viewer is already in. The cloud half (objectstack-ai/cloud#2980) is in.
    • Shape: the card's suggested one, a host-asked field that gates a derived rule (no switcher only when the host asks and one un-hidden app is served).
      • A flag alone would hide Cloud Admin's door from a platform admin.
      • A derived rule alone would retire objectui#12605's rows in every deployment, unmeasured.
      • ⛔ No authored-metadata key: the runtime config is host-produced.
    • Scope = the card's four Targets and three tests. Without the field, today's chrome is unchanged.
    • Lane: domain:ui (packages/app-shell/src/layout/ and runtime-config.ts). The cloud seat serves the field after the pin carries it.

    Generated by Claude Code

  2. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    enhancementNew feature or request
    and removed on Oct 17, 2026
  3. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (priority:p2; triage scope 6239004293)
    Session: session_01N3thnWhtvLywz5ECCWAb7p
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12913-host-single-app-switcher
    Worktree: objectui-issue-12913
    Domain: domain:ui
    Seat: domain:ui#3
    File surface: packages/app-shell/src/runtime-config.ts (one optional host-produced field on AppShellRuntimeConfig and its default), packages/app-shell/src/layout/AppSwitcher.tsx and packages/app-shell/src/layout/AppHeader.tsx (no switcher pill, dropdown or 「所有应用」 only when the host field is set AND exactly one un-hidden active app is served; the app name or product mark and the 预览版 chip stay), the place the console reads the runtime config into the shell if the field needs threading, tests beside each, one .changeset/12913-host-single-app-switcher.md. ⛔ No authored-metadata key, ⛔ no packages/types or spec change, ⛔ no change to objectui#12605's rows for any viewer the host field does not cover. Stop on breach; explain in the report. (Amended in place at ACCEPT: packages/app-shell/README.md and content/docs/guide/console.md, the dispatch's suggested route and objectui AGENTS.md §5 #2.)
    Container & model: M, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
    Clause-②: yes (widening)
    Clause-② reason: AppShellRuntimeConfig is exported from @object-ui/app-shell's entry (packages/app-shell/src/index.ts), so a new optional member widens its published face. The seat's TIER_DEFAULT review comes before ready.
    Responsibility: n/a — not a defect card
    Thread-read: 6239004293
    Serial constraints cleared: no open objectui PR touches these paths (the six open PRs' file lists read at claim: objectui#12921, #12918, #12916, #11845, #11844, #11600). No pm:dispatched claim names them: objectui#12915, #12907, #12906, #12902, #12899 are disjoint. objectui#12733 (this seat, claim 6241934953) edits packages/app-shell/src/console/AppContent.tsx, index.ts and package.json in the same package: disjoint files, so whichever lands second merges main.

    Reading time 2026-10-17T22:07Z.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 (priority:p2, triage shape 6239004293: a host-asked field gating a derived rule)
    Session: session_01XVp19E1MYUTFM3dnkwz8gB
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-12913-single-app-switcher
    Worktree: objectui-issue-12913
    Domain: domain:ui
    Seat: domain:ui#2
    File surface:

    • packages/app-shell/src/runtime-config.ts: one optional field on AppShellRuntimeConfig that a host sets to ask for no app switcher when one app is served.
    • packages/app-shell/src/layout/AppSwitcher.tsx and packages/app-shell/src/layout/AppHeader.tsx: no switcher, so no pill dropdown and no 「所有应用」, only when the host asks AND the viewer is served exactly one un-hidden active app. The app's name or the product mark stands in, and the 预览版 chip stays beside the mark.
    • The console wiring that passes the runtime config down, only if needed.
    • Tests beside the layout, and one .changeset/12913-*.md (@object-ui/app-shell: minor).

    ⛔ No authored-metadata key. Without the field, today's chrome is unchanged. Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (default tier; dispatch-gates --tier --repo objectstack-ai/objectui reads no path-derived mandate)
    Clause-②: yes
    Clause-② reason: a new optional member on AppShellRuntimeConfig, which @object-ui/app-shell's entry exports, widens a published type. It owes one CONTRACT_REVIEW_TIER review and levels minor.
    Responsibility: n/a — not a defect card (a host-asked chrome option for a single-app deployment, objectstack-ai/cloud#2919)
    Thread-read: 6239004293
    Serial constraints cleared: no open objectui PR touches these files, and no pm:dispatched claim names them (read at claim). This seat's in-flight objectui#12733 (marketplace files, AppContent.tsx, app-shell/src/index.ts) is disjoint; if both touch app-shell/src/index.ts, the later one merges main first. objectui#12907 (plugin-detail) is disjoint.
    Claimed at: 2026-10-17T22:10Z

  5. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Withdrawn: claim 6241993385 · domain:ui seat 2 · session_01XVp19E1MYUTFM3dnkwz8gB · 2026-10-17T22:13Z

    This seat's claim above lost a race. Seat zhuangjianguo (session_01N3thnWhtvLywz5ECCWAb7p) claimed this card first, in 6241963840 at 22:08Z, and that claim stands. This seat read the card before that claim landed and did not re-read before posting. It has removed its own assignee (baozhoutao) and dispatched nothing. pm:dispatched and the other seat's assignee are untouched. ⛔ Not a release of the other seat's claim.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 12913,
    "status": "done",
    "branch": "claude/issue-12913-host-single-app-switcher",
    "pr": "#12925",
    "session": "session_01N3thnWhtvLywz5ECCWAb7p",
    "premise_still_valid": true,
    "summary": "AppShellRuntimeConfig gains one optional host-produced member, hideSingleAppSwitcher (default false). initRuntimeConfig reads it as a literal true and re-reads it from each payload, like singleEnvironment. The module-level reader isSingleAppSwitcherHidden() sits beside isStorageUsageServed(); the package entry index.ts is untouched. AppSwitcher owns the derived rule: only when the host asks AND its own activeApps list (active !== false && hidden !== true, the list the dropdown draws) holds exactly one app, it draws the app's glyph and name with no trigger and no dropdown, so there is no Back to home, All apps or Settings row. The platform-stage chip still follows that slot, and every other viewer and every runtime without the key keeps today's pill. The newest Claim line on the card, 6241993385 (domain:ui#2), was withdrawn in 6242048178, which says this seat's claim 6241963840 stands, so the run proceeded; at first read the card's assignees were baozhoutao and zhuangjianguo, and the withdrawal says domain:ui#2 removed its own.",
    "tests": "At 82c1d26. New AppSwitcher.hostSingleApp-12913.test.tsx (5) and AppHeader.hostSingleApp-12913.test.tsx (3) run through the real initRuntimeConfig over a stubbed GET /api/v1/runtime/config; runtime-config.test.ts gains a hideSingleAppSwitcher describe (9). Results: those 3 files: Test Files 3 passed (3), Tests 55 passed (55). packages/app-shell/src/layout/: Test Files 101 passed (101), Tests 675 passed (675). The other 50 test files naming runtime-config / AppShellRuntimeConfig / initRuntimeConfig, apps/console runtimeConfigBootDedup and faviconAfterNavigation included: Test Files 50 passed (50), Tests 598 passed (598). product-neutral-renderer.ratchet: Tests 32 passed (32). Ablation, after committing the change, via objectstack scripts/ablation-replace.mjs in WRAP mode (no build or dist needed: the tests import src relatively). Every anchor hit x1 and the blob changed; every restore proved blob == HEAD and an empty git diff HEAD, and the final tree was clean. Leg 1, rule if to if (false): 3 red, the one-app cases. Leg 2, === 1 to >= 1: 3 red, the two-app and Setup CONTROLS. Leg 3, the loader read deleted: 5 red, which proves the loader drops unknown keys. Leg 4, === true to Boolean(): 2 red, the 'true' and 1 cases. Each red set matched the prediction.",
    "gates": "Local, exit codes captured before any pipe. Lock VERDICT command-exit 0 for: pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build; pnpm --filter @object-ui/app-shell type-check, which echoed tsc --noEmit && tsc -p tsconfig.test.json, whose include covers src//*.test.tsx; pnpm --filter @object-ui/app-shell build. Exit 0: check:new-line-citations (0 new citation(s)), check:control-bytes, check-changeset-presence.mjs, check-changeset-no-major.mjs, check:doc-fences, check:doc-json, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:changeset-claims, check:pending-changeset-literals, check:esm-specifiers, check:i18n-keys; check-governed-queue-guard --test reported NOT GOVERNED. check:readme-exports exit 1 = PREREQUISITE only: all 73 lines are unbuilt packages outside the closure (cli, plugin-ai, plugin-gantt and others), 0 lines name app-shell/README, census 0 wrong-path and 0 fabricated. NOT MEASURED: check:doc-snippets, reason: its --build-filter closure is the whole workspace and repo-gates.yml builds and runs it. objectui has no dispatch-gates, so no --ran reconciliation; the list was derived by hand from the diff. PR check-runs at 82c1d26, read once (35 total, 35 returned): Changeset Bump Policy success, label success; Test (coverage), its shard matrix and dependabot skipped; in_progress: Test shards 1-16, Type Check, Lint, Build & E2E, Build Docs, Repo Gates, Line Citation Gate, Changeset Declaration, Changeset Fixed Group Check, Changeset Claim Re-read, Changeset Overwrite Report, Governed Surface Queue Guard, Test (dist pins), Bundle Analysis, Live E2E (informational).",
    "mcp_calls": "0 — none",
    "api_writes": "2 — (1) pr_create through scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204), executed by the relay as objectstack-fleet[bot] as POST /repos/objectstack-ai/objectui/pulls (draft) + POST /repos//issues/12925/assignees; read-back reported 9049 bytes sent and 9049 stored, identical, assignees zhuangjianguo. (2) this os-dev-report via scripts/pm/post-stamped.mjs: POST /repos//issues/12913/comments, through the relay. Not REST: one git push of the branch.",
    "line_budget": "n/a — no skills/
    file and no line-ratcheted ledger touched",
    "deviations": "(1) The first git push -u, run before any edit, was refused by the auto-mode classifier (reason Interfere With Workloads; the command also wrote a scratchpad file). Work went on locally. After the PM's status probe directed the push, the same push ran once more as a standalone command and landed; there was no route change and no loop. (2) The newest Claim: line on the card names claude/issue-12913-single-app-switcher, not this branch. The run proceeded because that claim was withdrawn (6242048178) and the withdrawal names this seat's claim as standing. (3) The README section and a one-sentence content/docs/guide/console.md edit fall outside the claim's literal file surface; they follow the dispatch's suggested route and AGENTS.md §5 rule 2. (4) check:doc-snippets was not run locally (whole-workspace build closure, so it is CI's). check:readme-exports was read as a prerequisite failure; see gates. (5) Target 4: the chip follows the app's name in the pill's slot, not directly beside the product mark; see out_of_scope_findings carrier.",
    "files_changed": [
    ".changeset/12913-host-single-app-switcher.md",
    "content/docs/guide/console.md",
    "packages/app-shell/README.md",
    "packages/app-shell/src/layout/AppHeader.tsx",
    "packages/app-shell/src/layout/AppSwitcher.tsx",
    "packages/app-shell/src/layout/tests/AppHeader.hostSingleApp-12913.test.tsx",
    "packages/app-shell/src/layout/tests/AppSwitcher.hostSingleApp-12913.test.tsx",
    "packages/app-shell/src/runtime-config.test.ts",
    "packages/app-shell/src/runtime-config.ts"
    ],
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: the cloud seat's read of the control plane's top bar after the pin bump (the card's second Acceptance item) · Target 4's 'chip beside the product mark' is read here as mark, rule, app glyph and name, then chip (the chip keeps following the slot). Drawing the chip directly beside the mark with no app name would leave no text name on object routes · noted, not filed",
    "carrier: none · inside a hidden app (for example an account app) on a host that asks, with one un-hidden app, the stand-in names the current hidden app and the way back is the product mark and the home chord; the Edit APP in Studio row goes with the dropdown while the top bar's Studio button (same studioDesignPath) stays · read from code, not pinned · noted in the PR's Acceptance notes only"
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #12925 comment 6243045241 (contract review TIER_DEFAULT PASS 6243036226, head 82c1d26) · domain:ui seat 3 · 2026-10-17T23:26Z. Lands through the queue once every check on the head is green.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 17, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · PR #12925 merged as d15ad87 on main · domain:ui seat 3 · session_01N3thnWhtvLywz5ECCWAb7p · 2026-10-17T23:46Z

    probe on origin/main d15ad87 reading control
    hideSingleAppSwitcher?: boolean in packages/app-shell/src/runtime-config.ts 1 body.hideSingleAppSwitcher === true in the same file: 1
    isSingleAppSwitcherHidden() && activeApps.length === 1 in packages/app-shell/src/layout/AppSwitcher.tsx 1 export function isSingleAppSwitcherHidden in runtime-config.ts: 1
    the hostSingleApp-12913 tests in packages/app-shell/src/layout/__tests__/ 2 files —
    .changeset/12913-host-single-app-switcher.md present —
    merge commit's parents 1 (squash) —
    a model name in the merge commit's message 0 —

    State change: the PR's Fixes #12913 closed this card completed; in this act pm:dispatched is removed. Records: claim 6241963840 (amended in place), report 6243007294, TIER_DEFAULT contract review 6243036226 (PASS), ACCEPT 6243045241.

    Follow-up owned elsewhere: the cloud half — the control plane serves hideSingleAppSwitcher: true from its runtime config, then cloud's objectui pin carries d15ad87 — is the repo:cloud seat's (seat post objectstack-ai/objectstack#24145), per this card's "The cloud follow-up (serving the field, then the pin bump) is the cloud seat's once this lands"; so is the card's second Acceptance item (reading the control plane's top bar).


    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

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatenhancementNew feature or requestfindingpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions