feat(settings): explain operator switches and keep the Coach memory reachable

Settings, AI says which AI features the server's operator switched off and
shows the one setup hint while no provider is configured. The consent
withdrawal says what it deletes and what stays, and a document-reading
consent on file gets its own withdrawal. Regenerate follows the briefing
capability and the connection test names an operator refusal.

The Coach memory (facts and stored conversations) is never gated on the
Coach: Settings, Coach always shows it, and Settings, AI shows it while
the Coach page is out of reach, whenever rows exist.

Onboarding offers AI only where it can be set up here: the done-screen
panel and the getting-started row go away while the operator has AI off
or inside somebody else's record.
This commit is contained in:
Marc-André Bombeck committed 2026-09-24 11:05:54 +02:00
1 parent 4449b34ac7
commit 3d85445e50
24 files changed
+913 -232

No files matched your search

+9 -9
View File
@@ -18,8 +18,8 @@
* off `useAuth().user.modules`.
*
* Operator precedence stays honest: a module the operator turned off
* server-wide (the module-availability blob, plus the assistant master flag
* `flags.coach` for the coach row) renders a disabled switch + a
* server-wide (the module-availability blob, or for the coach row the
* operator's Coach switch as the `coach` capability reports it) renders a disabled switch + a
* "disabled server-wide" hint — a per-user toggle could not re-enable it.
*
* The three CORE domains (weight, blood pressure, pulse) render as a
@@ -63,7 +63,7 @@ import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
import { ModuleToggleRow } from "@/components/settings/module-toggle-row";
import { useAuth } from "@/hooks/use-auth";
import { useFeatureFlags } from "@/hooks/use-feature-flags";
import { useAiCapability } from "@/hooks/use-ai-capability";
import { useTranslations } from "@/lib/i18n/context";
import { apiPatch } from "@/lib/api/api-fetch";
import {
@@ -118,7 +118,7 @@ const MODULE_ICONS: Record<ModuleKey, LucideIcon> = {
export function ModulesSection() {
const { t } = useTranslations();
const { user } = useAuth();
const flags = useFeatureFlags();
const coach = useAiCapability("coach");
const queryClient = useQueryClient();
const modules = user?.modules ?? {};
@@ -234,13 +234,13 @@ export function ModulesSection() {
// Operator precedence. A per-user toggle can never re-enable a
// module the operator turned off server-wide, so the switch goes
// disabled + hint. For coach the operator layer is BOTH the
// module-availability blob AND the assistant master flag
// (`flags.coach`, already master-composed); for cycle and the
// owned modules it is the module-availability blob alone.
// disabled + hint. For coach the operator layer is the
// operator's Coach switch, read from the resolved `coach`
// capability (`operator_disabled`, master applied); for cycle
// and the owned modules it is the module-availability blob.
const operatorAvailable =
delegate === "coach"
? moduleAvailability[key] !== false && flags.coach
? coach.reason !== "operator_disabled"
: moduleAvailability[key] !== false;
const disabledReason = operatorAvailable
? undefined