diff --git a/packages/settings-ui/README.md b/packages/settings-ui/README.md index f61041aa4..c7daa5565 100644 --- a/packages/settings-ui/README.md +++ b/packages/settings-ui/README.md @@ -8,14 +8,16 @@ Presentational primitives come from `@corbits/react-ui` package holds the workbench-specific section composition, HTTP clients, and the domain model of which sections exist and who can see them. -Sections are grouped Account (personal: chat notifications, account, -connections) and Everyone (account-wide: people, roles, grants, audit) — -the single-concept collapse of the old Personal/Workspace split, since +Sections are grouped Account (personal: account) and Everyone +(account-wide: connections, people, roles, grants, audit) — the +single-concept collapse of the old Personal/Workspace split, since there is one workbench per account today. `resolveSettingsSectionGroups` is the one source of truth a host reads for both the settings stage and its own section nav, so the two never drift; `insertEveryoneSections` lets a host splice in its own account-wide sections (e.g. Agents/Skills) -alongside these. +alongside these. Notifications and "Your agent" stay out of the registry +until a preference store backs them (honest absence over draft-only +toggles). ## Key modules @@ -25,8 +27,9 @@ alongside these. - `people-section.tsx` / `roles-section.tsx` / `grants-section.tsx` / `connections-section.tsx` — the Everyone-group and account-scoped management surfaces, each with its own API module -- `account-section.tsx` / `notifications-section.tsx` / `audit-section.tsx` - — the Account-group surfaces +- `account-section.tsx` / `audit-section.tsx` — the Account-group and + Advanced surfaces (`notifications-section.tsx` remains as a dormant + draft until a preference store exists) - `access.ts` / `tenancy-api.ts` — tenancy-gate resolution and the principals/roles/grants HTTP client - `access-policy.tsx` — the grant preview/editor shared by roles and grants diff --git a/packages/settings-ui/src/index.ts b/packages/settings-ui/src/index.ts index 3aa036566..a4260aaf2 100644 --- a/packages/settings-ui/src/index.ts +++ b/packages/settings-ui/src/index.ts @@ -20,7 +20,8 @@ export { AppearanceSection, AgentGeneralSection, } from "./account-section"; -export { NotificationsSection } from "./notifications-section"; +// NotificationsSection is not exported: it is draft-only and not in the +// registry (CL-6843). Re-export when a preference store backs it. export { AuditSection } from "./audit-section"; export { AccessPolicyBlock, AccessPolicyEditor } from "./access-policy"; export { diff --git a/packages/settings-ui/src/notifications-section.tsx b/packages/settings-ui/src/notifications-section.tsx index 13b63b3e1..450da26ee 100644 --- a/packages/settings-ui/src/notifications-section.tsx +++ b/packages/settings-ui/src/notifications-section.tsx @@ -2,6 +2,15 @@ // inbox / notify stack; these toggles are the operator-facing surface for // which classes of activity should surface. No per-user preference store // is mounted yet — draft state only, with an honesty note on save. +// +// REMOVED FROM THE SETTINGS REGISTRY (see section-registry.tsx; CL-6843): +// toggles that save nothing are fake controls, not a settings section — +// honest absence beats that. Re-add the "chat" section to +// `SETTINGS_SECTION_GROUPS`'s "account" group only once a per-user +// preference store exists for these fields and Save actually persists +// them, not before. (Workbench-scoped mute/mentions prefs live on +// `@corbits/chat-ui`'s workbench settings surface and already persist — +// that is a different surface.) import { Button, SettingsPanel } from "@corbits/react-ui"; import { Check } from "@corbits/icons"; diff --git a/packages/settings-ui/src/section-registry.tsx b/packages/settings-ui/src/section-registry.tsx index 1c4615b00..f682394e0 100644 --- a/packages/settings-ui/src/section-registry.tsx +++ b/packages/settings-ui/src/section-registry.tsx @@ -18,22 +18,13 @@ // `resolveSettingsSectionGroups` — the domain model of "what settings // exist and who can see them" lives here, not in an app. -import { - Bell, - Key, - ListBullets, - Shield, - Star, - User, - Users, -} from "@corbits/icons"; +import { Key, ListBullets, Shield, Star, User, Users } from "@corbits/icons"; import { AccountSection } from "./account-section"; import type { TenancyAccess } from "./access"; import { AuditSection } from "./audit-section"; import { ConnectionsSection } from "./connections-section"; import { GrantsSection } from "./grants-section"; -import { NotificationsSection } from "./notifications-section"; import { PeopleSection } from "./people-section"; import { RolesSection } from "./roles-section"; import type { SettingsSection, SettingsSectionGroup } from "./shell"; @@ -57,8 +48,10 @@ const SETTINGS_SECTION_GROUPS: readonly SettingsSectionGroupDef[] = [ label: SETTINGS_STRINGS.groupAccountLabel, // No "Your agent" section here: it has no preference store to back it. // Re-add only once a hub preference store exists and save actually persists. - // Account (General) leads — it is the reference's landing section - // (account card, theme, timezone); notifications follow. + // No Notifications ("chat") section either: toggles with no per-user + // preference store are fake controls — see notifications-section.tsx + // for the re-add condition (CL-6843). Account (General) is the sole + // Account-group section until those stores exist. sections: [ { id: "account", @@ -72,12 +65,6 @@ const SETTINGS_SECTION_GROUPS: readonly SettingsSectionGroupDef[] = [ /> ), }, - { - id: "chat", - title: SETTINGS_STRINGS.notificationsSectionTitle, - icon: Bell, - render: () => , - }, ], }, { diff --git a/packages/settings-ui/test/section-registry.test.tsx b/packages/settings-ui/test/section-registry.test.tsx index 7a1c0e5ee..70f63a12a 100644 --- a/packages/settings-ui/test/section-registry.test.tsx +++ b/packages/settings-ui/test/section-registry.test.tsx @@ -40,16 +40,16 @@ function ids(groups: ReturnType) { } describe("resolveSettingsSectionGroups", () => { - test("Account's Notifications and Account sections are always full; gated Everyone sections are absent, not disabled", () => { + test("Account's Account section is always full; gated Everyone sections are absent, not disabled", () => { expect(ids(resolveSettingsSectionGroups(denied))).toEqual([ - { id: "account", sections: ["account", "chat"] }, + { id: "account", sections: ["account"] }, { id: "everyone", sections: ["audit"] }, ]); }); test("an allowed gate adds its section in registry order", () => { expect(ids(resolveSettingsSectionGroups(allowed))).toEqual([ - { id: "account", sections: ["account", "chat"] }, + { id: "account", sections: ["account"] }, { id: "everyone", sections: ["connections", "people", "roles", "grants", "audit"], @@ -65,7 +65,7 @@ describe("resolveSettingsSectionGroups", () => { credentials: "denied", }; expect(ids(resolveSettingsSectionGroups(loading))).toEqual([ - { id: "account", sections: ["account", "chat"] }, + { id: "account", sections: ["account"] }, { id: "everyone", sections: ["grants", "audit"] }, ]); }); @@ -93,6 +93,17 @@ describe("resolveSettingsSectionGroups", () => { } }); + test("never registers the notifications section — no preference store exists to back it yet", () => { + for (const access of [denied, allowed]) { + const account = resolveSettingsSectionGroups(access).find( + (group) => group.id === "account", + ); + expect(account?.sections.map((section) => section.id)).not.toContain( + "chat", + ); + } + }); + test("every section carries a leading icon for a host's own nav", () => { for (const group of resolveSettingsSectionGroups(allowed)) { for (const section of group.sections) { @@ -114,7 +125,7 @@ describe("insertEveryoneSections", () => { extra, ); expect(ids(groups)).toEqual([ - { id: "account", sections: ["account", "chat"] }, + { id: "account", sections: ["account"] }, { id: "everyone", sections: ["agents", "skills", "audit"] }, ]); }); @@ -125,10 +136,7 @@ describe("insertEveryoneSections", () => { extra, ); const account = groups.find((group) => group.id === "account"); - expect(account?.sections.map((section) => section.id)).toEqual([ - "account", - "chat", - ]); + expect(account?.sections.map((section) => section.id)).toEqual(["account"]); }); test("is a no-op when there is nothing to insert", () => {