Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions packages/settings-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
3 changes: 2 additions & 1 deletion packages/settings-ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions packages/settings-ui/src/notifications-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
23 changes: 5 additions & 18 deletions packages/settings-ui/src/section-registry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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",
Expand All @@ -72,12 +65,6 @@ const SETTINGS_SECTION_GROUPS: readonly SettingsSectionGroupDef[] = [
/>
),
},
{
id: "chat",
title: SETTINGS_STRINGS.notificationsSectionTitle,
icon: Bell,
render: () => <NotificationsSection />,
},
],
},
{
Expand Down
26 changes: 17 additions & 9 deletions packages/settings-ui/test/section-registry.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,16 +40,16 @@ function ids(groups: ReturnType<typeof resolveSettingsSectionGroups>) {
}

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"],
Expand All @@ -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"] },
]);
});
Expand Down Expand Up @@ -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) {
Expand All @@ -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"] },
]);
});
Expand All @@ -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", () => {
Expand Down
Loading