mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
fix(settings): unlist Preview features until the topic pages ship
The switch was stored and shown, but nothing reads the topics flag yet, so the page promised a change it could not make. The section, its nav entry, its loader and its copy are gone, so its palette entry goes with them; /settings/preview is no longer a route. The stored flag, /api/settings/preview-features and its backup round-trip stay, and the column's pending-consumer note says why.
This commit is contained in:
1 parent
704822c010
commit
0918451e02
21 files changed
+23
-327
No files matched your search
@@ -58,7 +58,6 @@ const SETTINGS_ROUTES = [
|
||||
"/settings/api",
|
||||
"/settings/mcp",
|
||||
"/settings/advanced",
|
||||
"/settings/preview",
|
||||
"/settings/about",
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -6141,10 +6141,6 @@
|
||||
"access": {
|
||||
"title": "Geteilter Zugriff",
|
||||
"subtitle": "Wer deine Gesundheitsakte öffnen kann und wessen Akte du öffnen kannst."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Vorschau-Funktionen",
|
||||
"subtitle": "Probier aus, was noch nicht fertig ist. Alles hier bleibt aus, bis du es einschaltest."
|
||||
}
|
||||
},
|
||||
"profile": "Profil",
|
||||
@@ -7628,17 +7624,6 @@
|
||||
"passkeyRemovedToast": "Passkey entfernt.",
|
||||
"integrationDisconnected": "{provider} getrennt.",
|
||||
"integrationDisconnectFailed": "{provider} konnte nicht getrennt werden.",
|
||||
"preview": {
|
||||
"topics": {
|
||||
"title": "Themen (Beta)",
|
||||
"description": "Fügt einen eigenen Eintrag Themen hinzu, sobald die Übersichtsseiten über den einzelnen Insights-Seiten fertig sind.",
|
||||
"detail": "Deine Insights-Seiten, die Navigation und alle Links bleiben genau wie sie sind. Die Themenseiten kommen mit einem späteren Update; dieser Schalter legt fest, ob du sie siehst.",
|
||||
"savedOn": "Themen-Vorschau eingeschaltet.",
|
||||
"savedOff": "Themen-Vorschau ausgeschaltet."
|
||||
},
|
||||
"loadError": "Deine Vorschau-Einstellungen konnten nicht geladen werden.",
|
||||
"feedbackLink": "Feedback in den GitHub-Diskussionen geben"
|
||||
},
|
||||
"timezoneSaved": "Zeitzone gespeichert.",
|
||||
"moduleState": {
|
||||
"on": "An",
|
||||
|
||||
@@ -6141,10 +6141,6 @@
|
||||
"access": {
|
||||
"title": "Shared access",
|
||||
"subtitle": "Who can open your health record, and whose you can open."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Preview features",
|
||||
"subtitle": "Try what is not finished yet. Everything here stays off until you switch it on."
|
||||
}
|
||||
},
|
||||
"profile": "Profile",
|
||||
@@ -7628,17 +7624,6 @@
|
||||
"passkeyRemovedToast": "Passkey removed.",
|
||||
"integrationDisconnected": "{provider} disconnected.",
|
||||
"integrationDisconnectFailed": "Could not disconnect {provider}.",
|
||||
"preview": {
|
||||
"topics": {
|
||||
"title": "Topics (beta)",
|
||||
"description": "Will add a separate Topics entry with overview pages above the single-metric Insights pages once they are ready.",
|
||||
"detail": "Your Insights pages, navigation and links stay exactly as they are. The topic pages arrive in a later update; this switch decides whether you see them.",
|
||||
"savedOn": "Topics preview switched on.",
|
||||
"savedOff": "Topics preview switched off."
|
||||
},
|
||||
"loadError": "Could not load your preview settings.",
|
||||
"feedbackLink": "Share feedback in the GitHub discussions"
|
||||
},
|
||||
"timezoneSaved": "Timezone saved.",
|
||||
"moduleState": {
|
||||
"on": "On",
|
||||
|
||||
@@ -6141,10 +6141,6 @@
|
||||
"access": {
|
||||
"title": "Acceso compartido",
|
||||
"subtitle": "Quién puede abrir tu historial de salud y cuál puedes abrir tú."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Funciones en vista previa",
|
||||
"subtitle": "Prueba lo que aún no está terminado. Todo aquí sigue desactivado hasta que lo actives."
|
||||
}
|
||||
},
|
||||
"profile": "Perfil",
|
||||
@@ -7628,17 +7624,6 @@
|
||||
"passkeyRemovedToast": "Passkey eliminada.",
|
||||
"integrationDisconnected": "{provider} desconectado.",
|
||||
"integrationDisconnectFailed": "No se pudo desconectar {provider}.",
|
||||
"preview": {
|
||||
"topics": {
|
||||
"title": "Temas (beta)",
|
||||
"description": "Añadirá una entrada aparte, Temas, con páginas de resumen por encima de las páginas de cada métrica cuando estén listas.",
|
||||
"detail": "Tus páginas de análisis, la navegación y los enlaces se quedan exactamente como están. Las páginas de temas llegan en una actualización posterior; este interruptor decide si las ves.",
|
||||
"savedOn": "Vista previa de Temas activada.",
|
||||
"savedOff": "Vista previa de Temas desactivada."
|
||||
},
|
||||
"loadError": "No se pudieron cargar tus ajustes de vista previa.",
|
||||
"feedbackLink": "Comparte tu opinión en las discusiones de GitHub"
|
||||
},
|
||||
"timezoneSaved": "Zona horaria guardada.",
|
||||
"moduleState": {
|
||||
"on": "Activado",
|
||||
|
||||
@@ -6141,10 +6141,6 @@
|
||||
"access": {
|
||||
"title": "Accès partagé",
|
||||
"subtitle": "Qui peut ouvrir votre dossier de santé, et lequel vous pouvez ouvrir."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Fonctions en aperçu",
|
||||
"subtitle": "Essayez ce qui n’est pas encore terminé. Tout reste désactivé ici tant que vous ne l’activez pas."
|
||||
}
|
||||
},
|
||||
"profile": "Profil",
|
||||
@@ -7628,17 +7624,6 @@
|
||||
"passkeyRemovedToast": "Passkey supprimée.",
|
||||
"integrationDisconnected": "{provider} déconnecté.",
|
||||
"integrationDisconnectFailed": "Impossible de déconnecter {provider}.",
|
||||
"preview": {
|
||||
"topics": {
|
||||
"title": "Thèmes (bêta)",
|
||||
"description": "Ajoutera une entrée Thèmes distincte, avec des pages de synthèse au-dessus des pages par mesure, dès qu'elles seront prêtes.",
|
||||
"detail": "Vos pages d’analyse, la navigation et les liens restent exactement tels quels. Les pages de thèmes arrivent dans une mise à jour ultérieure ; cet interrupteur décide si vous les voyez.",
|
||||
"savedOn": "Aperçu des thèmes activé.",
|
||||
"savedOff": "Aperçu des thèmes désactivé."
|
||||
},
|
||||
"loadError": "Impossible de charger vos réglages d’aperçu.",
|
||||
"feedbackLink": "Donner votre avis dans les discussions GitHub"
|
||||
},
|
||||
"timezoneSaved": "Fuseau horaire enregistré.",
|
||||
"moduleState": {
|
||||
"on": "Activé",
|
||||
|
||||
@@ -6141,10 +6141,6 @@
|
||||
"access": {
|
||||
"title": "Accesso condiviso",
|
||||
"subtitle": "Chi può aprire la tua cartella sanitaria e quale puoi aprire tu."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Funzioni in anteprima",
|
||||
"subtitle": "Prova ciò che non è ancora finito. Qui tutto resta spento finché non lo attivi."
|
||||
}
|
||||
},
|
||||
"profile": "Profilo",
|
||||
@@ -7628,17 +7624,6 @@
|
||||
"passkeyRemovedToast": "Passkey rimossa.",
|
||||
"integrationDisconnected": "{provider} disconnesso.",
|
||||
"integrationDisconnectFailed": "Impossibile disconnettere {provider}.",
|
||||
"preview": {
|
||||
"topics": {
|
||||
"title": "Temi (beta)",
|
||||
"description": "Aggiungerà una voce Temi separata, con pagine di panoramica sopra le pagine delle singole metriche, appena saranno pronte.",
|
||||
"detail": "Le pagine delle analisi, la navigazione e i link restano esattamente come sono. Le pagine dei temi arrivano con un aggiornamento successivo; questo interruttore decide se le vedi.",
|
||||
"savedOn": "Anteprima dei temi attivata.",
|
||||
"savedOff": "Anteprima dei temi disattivata."
|
||||
},
|
||||
"loadError": "Impossibile caricare le impostazioni di anteprima.",
|
||||
"feedbackLink": "Lascia un parere nelle discussioni su GitHub"
|
||||
},
|
||||
"timezoneSaved": "Fuso orario salvato.",
|
||||
"moduleState": {
|
||||
"on": "Attivo",
|
||||
|
||||
@@ -6141,10 +6141,6 @@
|
||||
"access": {
|
||||
"title": "공유 접근",
|
||||
"subtitle": "내 건강 기록을 누가 열 수 있고, 내가 누구의 기록을 열 수 있는지예요."
|
||||
},
|
||||
"preview": {
|
||||
"title": "미리 보기 기능",
|
||||
"subtitle": "아직 완성되지 않은 기능을 써 보세요. 직접 켜기 전까지는 모두 꺼져 있습니다."
|
||||
}
|
||||
},
|
||||
"profile": "프로필",
|
||||
@@ -7628,17 +7624,6 @@
|
||||
"passkeyRemovedToast": "패스키를 삭제했어요.",
|
||||
"integrationDisconnected": "{provider} 연결을 해제했어요.",
|
||||
"integrationDisconnectFailed": "{provider} 연결을 해제하지 못했어요.",
|
||||
"preview": {
|
||||
"topics": {
|
||||
"title": "주제 (베타)",
|
||||
"description": "준비되면 개별 지표 페이지 위에 개요 페이지가 있는 별도의 주제 항목을 추가해요.",
|
||||
"detail": "인사이트 페이지, 메뉴, 링크는 지금 그대로 유지됩니다. 주제 페이지는 이후 업데이트에서 제공되며, 이 스위치로 표시 여부를 정합니다.",
|
||||
"savedOn": "주제 미리 보기를 켰습니다.",
|
||||
"savedOff": "주제 미리 보기를 껐습니다."
|
||||
},
|
||||
"loadError": "미리 보기 설정을 불러오지 못했습니다.",
|
||||
"feedbackLink": "GitHub 토론에서 의견 남기기"
|
||||
},
|
||||
"timezoneSaved": "시간대를 저장했습니다.",
|
||||
"moduleState": {
|
||||
"on": "켜짐",
|
||||
|
||||
@@ -6141,10 +6141,6 @@
|
||||
"access": {
|
||||
"title": "Gedeelde toegang",
|
||||
"subtitle": "Wie je gezondheidsdossier kan openen, en wiens dossier jij kunt openen."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Previewfuncties",
|
||||
"subtitle": "Probeer wat nog niet af is. Alles hier blijft uit tot je het aanzet."
|
||||
}
|
||||
},
|
||||
"profile": "Profiel",
|
||||
@@ -7628,17 +7624,6 @@
|
||||
"passkeyRemovedToast": "Passkey verwijderd.",
|
||||
"integrationDisconnected": "{provider} ontkoppeld.",
|
||||
"integrationDisconnectFailed": "{provider} kon niet worden ontkoppeld.",
|
||||
"preview": {
|
||||
"topics": {
|
||||
"title": "Onderwerpen (bèta)",
|
||||
"description": "Voegt een apart item Onderwerpen toe, met overzichtspagina’s boven de pagina’s per meting, zodra die klaar zijn.",
|
||||
"detail": "Je inzichtpagina’s, navigatie en links blijven precies zoals ze zijn. De onderwerppagina’s komen met een latere update; deze schakelaar bepaalt of je ze ziet.",
|
||||
"savedOn": "Preview van onderwerpen ingeschakeld.",
|
||||
"savedOff": "Preview van onderwerpen uitgeschakeld."
|
||||
},
|
||||
"loadError": "Je preview-instellingen konden niet worden geladen.",
|
||||
"feedbackLink": "Geef feedback in de GitHub-discussies"
|
||||
},
|
||||
"timezoneSaved": "Tijdzone opgeslagen.",
|
||||
"moduleState": {
|
||||
"on": "Aan",
|
||||
|
||||
@@ -6141,10 +6141,6 @@
|
||||
"access": {
|
||||
"title": "Udostępniony dostęp",
|
||||
"subtitle": "Kto może otworzyć twoją kartę zdrowia i czyją kartę możesz otworzyć ty."
|
||||
},
|
||||
"preview": {
|
||||
"title": "Funkcje w podglądzie",
|
||||
"subtitle": "Wypróbuj to, co nie jest jeszcze gotowe. Wszystko tutaj pozostaje wyłączone, dopóki tego nie włączysz."
|
||||
}
|
||||
},
|
||||
"profile": "Profil",
|
||||
@@ -7628,17 +7624,6 @@
|
||||
"passkeyRemovedToast": "Usunięto passkey.",
|
||||
"integrationDisconnected": "Rozłączono {provider}.",
|
||||
"integrationDisconnectFailed": "Nie udało się rozłączyć {provider}.",
|
||||
"preview": {
|
||||
"topics": {
|
||||
"title": "Tematy (beta)",
|
||||
"description": "Doda osobną pozycję Tematy ze stronami przeglądowymi nad stronami poszczególnych pomiarów, gdy będą gotowe.",
|
||||
"detail": "Strony analiz, nawigacja i linki pozostają dokładnie takie, jakie są. Strony tematów pojawią się w późniejszej aktualizacji; ten przełącznik decyduje, czy je zobaczysz.",
|
||||
"savedOn": "Podgląd tematów włączony.",
|
||||
"savedOff": "Podgląd tematów wyłączony."
|
||||
},
|
||||
"loadError": "Nie udało się wczytać ustawień podglądu.",
|
||||
"feedbackLink": "Podziel się opinią w dyskusjach na GitHubie"
|
||||
},
|
||||
"timezoneSaved": "Strefa czasowa zapisana.",
|
||||
"moduleState": {
|
||||
"on": "Włączone",
|
||||
|
||||
@@ -388,9 +388,9 @@ model User {
|
||||
// Shape: { "version": 1, "labs": { "view": "list", "order": ["<id>"], "sortDir": "manual" }, ... }
|
||||
moduleListLayoutJson Json? @map("module_list_layout_json")
|
||||
/// Per-user opt-in to preview features, one boolean per flag, all off by
|
||||
/// default (`src/lib/settings/preview-features.ts`). Read and written by
|
||||
/// Settings > System through `/api/settings/preview-features`.
|
||||
/// @pending-consumer(topics preview) the `topics` flag is stored and shown, but no surface reads it yet; the topic overview pages will.
|
||||
/// default (`src/lib/settings/preview-features.ts`). Read and written
|
||||
/// through `/api/settings/preview-features` and carried by the backup.
|
||||
/// @pending-consumer(topics preview) the `topics` flag is stored, but no surface reads it and the Settings > System switch is unlisted until the topic overview pages ship.
|
||||
previewFeaturesJson Json? @map("preview_features_json")
|
||||
/// Optional activity goals the person set for themselves: workouts, active
|
||||
/// minutes and steps per week, steps per month. Null or an absent key = no
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
/**
|
||||
* Copy that promises something not yet shipped says so.
|
||||
*
|
||||
* The Topics preview switch exists before its pages do, so its description is
|
||||
* in the future tense in every locale's source (English) and in German; and
|
||||
* the workout-route switch is off for new accounts, which is what its detail
|
||||
* The workout-route switch is off for new accounts, which is what its detail
|
||||
* says rather than "off by default": an account that already held tracks at
|
||||
* the upgrade started with it on (migration 0434).
|
||||
* the upgrade started with it on (migration 0434). (The Topics preview switch
|
||||
* this file also held is unlisted until its pages ship, and its copy with it.)
|
||||
*/
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
@@ -13,11 +12,6 @@ import en from "../../messages/en.json";
|
||||
import de from "../../messages/de.json";
|
||||
|
||||
describe("settings copy tense", () => {
|
||||
it("describes the Topics preview as something it will add", () => {
|
||||
expect(en.settings.preview.topics.description).toMatch(/^Will add /);
|
||||
expect(de.settings.preview.topics.description).toMatch(/sobald/);
|
||||
});
|
||||
|
||||
it("says workout routes are off for new accounts", () => {
|
||||
expect(en.settings.privacy.workoutRoutes.detail).toMatch(
|
||||
/^Off for new accounts\./,
|
||||
|
||||
@@ -146,10 +146,7 @@ describe("historical Settings addresses", () => {
|
||||
// one silently. Module pages are new in v1.44 and are allowed here.
|
||||
const listed = new Set(HISTORICAL_SETTINGS_PATHS);
|
||||
const unlisted = routable.filter(
|
||||
(path) =>
|
||||
!listed.has(path) &&
|
||||
!path.startsWith("/settings/modules/") &&
|
||||
path !== "/settings/preview",
|
||||
(path) => !listed.has(path) && !path.startsWith("/settings/modules/"),
|
||||
);
|
||||
expect(unlisted).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import { PreviewSection } from "../preview-section";
|
||||
import { primeLocales } from "@/__tests__/helpers/prime-locales";
|
||||
|
||||
primeLocales("de");
|
||||
|
||||
function render(topics: boolean | undefined, locale: "en" | "de" = "en") {
|
||||
const client = new QueryClient({
|
||||
defaultOptions: { queries: { retry: false, refetchOnMount: false } },
|
||||
});
|
||||
if (topics !== undefined) {
|
||||
client.setQueryData(queryKeys.previewFeatures(), { topics });
|
||||
}
|
||||
return renderToStaticMarkup(
|
||||
<I18nProvider initialLocale={locale}>
|
||||
<QueryClientProvider client={client}>
|
||||
<PreviewSection />
|
||||
</QueryClientProvider>
|
||||
</I18nProvider>,
|
||||
);
|
||||
}
|
||||
|
||||
describe("<PreviewSection>", () => {
|
||||
it("shows the Topics switch off by default, saved on change", () => {
|
||||
const html = render(false);
|
||||
expect(html).toContain('data-slot="settings-preview-topics-switch"');
|
||||
expect(html).toMatch(
|
||||
/data-slot="settings-preview-topics-switch"[^>]*aria-checked="false"|aria-checked="false"[^>]*data-slot="settings-preview-topics-switch"/,
|
||||
);
|
||||
expect(html).toContain("Topics (beta)");
|
||||
// A switch, no Save: one save model per card.
|
||||
expect(html).not.toContain('type="submit"');
|
||||
});
|
||||
|
||||
it("reads on once the person switched it on", () => {
|
||||
expect(render(true)).toMatch(
|
||||
/data-slot="settings-preview-topics-switch"[^>]*aria-checked="true"|aria-checked="true"[^>]*data-slot="settings-preview-topics-switch"/,
|
||||
);
|
||||
});
|
||||
|
||||
it("says the existing pages stay as they are and nothing raw leaks", () => {
|
||||
const html = render(false, "de");
|
||||
expect(html).toContain("Themen (Beta)");
|
||||
expect(html).not.toContain("settings.preview.");
|
||||
});
|
||||
});
|
||||
@@ -170,12 +170,19 @@ describe("SETTINGS_SECTION_SLUGS", () => {
|
||||
// v1.23 — "Data & Privacy" assembles the export / deletion / retention /
|
||||
// encryption / session / activity pieces into one pane, after `advanced`.
|
||||
"privacy",
|
||||
// v1.44 — per-user preview opt-ins, in the System group before About.
|
||||
"preview",
|
||||
"about",
|
||||
]);
|
||||
});
|
||||
|
||||
it("lists no Preview features page until the topic pages ship", () => {
|
||||
// The stored flag and its API stay; the page and its nav entry return
|
||||
// with the first preview the switch actually turns on.
|
||||
expect(isSettingsSectionSlug("preview")).toBe(false);
|
||||
expect(SETTINGS_SECTIONS.some((s) => s.slug === ("preview" as never))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
it("`isSettingsSectionSlug` accepts known slugs and rejects others", () => {
|
||||
for (const slug of SETTINGS_SECTION_SLUGS) {
|
||||
expect(isSettingsSectionSlug(slug)).toBe(true);
|
||||
@@ -242,7 +249,6 @@ describe("<SettingsShell>", () => {
|
||||
gesundheitsakte: "data",
|
||||
privacy: "data",
|
||||
advanced: "data",
|
||||
preview: "system",
|
||||
about: "system",
|
||||
});
|
||||
// Every routable section has a nav entry, and nothing else does.
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
"use client";
|
||||
|
||||
/**
|
||||
* Settings > System > Preview features.
|
||||
*
|
||||
* One switch per preview feature, each off until the person turns it on, each
|
||||
* saved the moment it changes (`PATCH /api/settings/preview-features`). A
|
||||
* preview adds something beside what is there and never replaces a page.
|
||||
*
|
||||
* The `topics` flag is stored and shown here; the topic overview pages that
|
||||
* read it arrive in a later release, and the card says so rather than
|
||||
* promising a change the switch does not make yet.
|
||||
*/
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import Link from "next/link";
|
||||
import { LayoutDashboard } from "lucide-react";
|
||||
|
||||
import { toastWrittenOutcome } from "@/components/outcome/outcome-toast";
|
||||
import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { InlineAction } from "@/components/ui/inline-action";
|
||||
import { QueryErrorRow } from "@/components/ui/query-error-row";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { apiGet, apiPatch } from "@/lib/api/api-fetch";
|
||||
import { showErrorToast } from "@/lib/errors/error-toast";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
import {
|
||||
DEFAULT_PREVIEW_FEATURES,
|
||||
type PreviewFeatures,
|
||||
} from "@/lib/settings/preview-features";
|
||||
|
||||
const DISCUSSIONS_URL = "https://github.com/MBombeck/HealthLog/discussions";
|
||||
|
||||
export function PreviewSection() {
|
||||
const { t } = useTranslations();
|
||||
const queryClient = useQueryClient();
|
||||
const key = queryKeys.previewFeatures();
|
||||
|
||||
const query = useQuery({
|
||||
queryKey: key,
|
||||
queryFn: () => apiGet<PreviewFeatures>("/api/settings/preview-features"),
|
||||
});
|
||||
const features = query.data ?? DEFAULT_PREVIEW_FEATURES;
|
||||
|
||||
const toggle = useMutation({
|
||||
mutationKey: key,
|
||||
mutationFn: (topics: boolean) =>
|
||||
apiPatch<PreviewFeatures>("/api/settings/preview-features", { topics }),
|
||||
onMutate: async (topics) => {
|
||||
await queryClient.cancelQueries({ queryKey: key });
|
||||
const previous = queryClient.getQueryData<PreviewFeatures>(key);
|
||||
queryClient.setQueryData<PreviewFeatures>(key, {
|
||||
...(previous ?? DEFAULT_PREVIEW_FEATURES),
|
||||
topics,
|
||||
});
|
||||
return { previous };
|
||||
},
|
||||
onSuccess: (saved, topics) => {
|
||||
if (saved) queryClient.setQueryData(key, saved);
|
||||
toastWrittenOutcome(
|
||||
"success",
|
||||
topics
|
||||
? t("settings.preview.topics.savedOn")
|
||||
: t("settings.preview.topics.savedOff"),
|
||||
);
|
||||
},
|
||||
onError: (failure, _topics, context) => {
|
||||
queryClient.setQueryData(key, context?.previous);
|
||||
showErrorToast(t("settings.savingError"), { cause: failure });
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<SettingsCard data-slot="settings-preview-topics">
|
||||
<SettingsCardHeader
|
||||
anchor="preview-topics"
|
||||
icon={LayoutDashboard}
|
||||
title={t("settings.preview.topics.title")}
|
||||
titleId="settings-preview-topics-title"
|
||||
description={t("settings.preview.topics.description")}
|
||||
status={
|
||||
<Switch
|
||||
data-slot="settings-preview-topics-switch"
|
||||
checked={features.topics}
|
||||
disabled={query.isLoading || toggle.isPending}
|
||||
onCheckedChange={(next) => toggle.mutate(next)}
|
||||
aria-labelledby="settings-preview-topics-title"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{query.isError ? (
|
||||
<QueryErrorRow
|
||||
inset
|
||||
error={query.error}
|
||||
slot="settings-preview-error"
|
||||
message={t("settings.preview.loadError")}
|
||||
onRetry={() => void query.refetch()}
|
||||
/>
|
||||
) : null}
|
||||
<p className="text-sm">{t("settings.preview.topics.detail")}</p>
|
||||
<p className="text-muted-foreground text-xs">
|
||||
<InlineAction asChild tone="muted" size="xs">
|
||||
<Link
|
||||
href={DISCUSSIONS_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t("settings.preview.feedbackLink")}
|
||||
</Link>
|
||||
</InlineAction>
|
||||
</p>
|
||||
</SettingsCard>
|
||||
);
|
||||
}
|
||||
@@ -114,9 +114,10 @@ export const SETTINGS_SECTION_SLUGS = [
|
||||
// pieces into one coherent privacy pane. Sits next to `advanced` (the
|
||||
// destructive-action home) and before `about`.
|
||||
"privacy",
|
||||
// v1.44 — "Preview features": per-user opt-in to things finished enough to
|
||||
// try and not yet the default. Sits in the System group before About.
|
||||
"preview",
|
||||
// v1.44 — "Preview features" (per-user opt-ins, Settings > System) is not
|
||||
// listed until the first preview it switches, the topic pages, ships: a
|
||||
// switch that changes nothing is a promise the page cannot keep. The stored
|
||||
// flag, `/api/settings/preview-features` and its backup round-trip stay.
|
||||
"about",
|
||||
] as const;
|
||||
|
||||
|
||||
@@ -124,10 +124,6 @@ const LOADERS: Record<
|
||||
import("@/components/settings/privacy-section").then(
|
||||
(m) => m.PrivacySection,
|
||||
),
|
||||
preview: () =>
|
||||
import("@/components/settings/preview-section").then(
|
||||
(m) => m.PreviewSection,
|
||||
),
|
||||
};
|
||||
|
||||
const SECTIONS: Record<SettingsSectionSlug, ComponentType<SectionProps>> = {
|
||||
@@ -162,7 +158,6 @@ const SECTIONS: Record<SettingsSectionSlug, ComponentType<SectionProps>> = {
|
||||
export: dynamic(LOADERS.export, { loading: () => <SectionLoading /> }),
|
||||
advanced: dynamic(LOADERS.advanced, { loading: () => <SectionLoading /> }),
|
||||
privacy: dynamic(LOADERS.privacy, { loading: () => <SectionLoading /> }),
|
||||
preview: dynamic(LOADERS.preview, { loading: () => <SectionLoading /> }),
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
CloudSun,
|
||||
Download,
|
||||
FileHeart,
|
||||
FlaskConical,
|
||||
Gauge,
|
||||
Info,
|
||||
KeyRound,
|
||||
@@ -275,12 +274,6 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [
|
||||
group: "data",
|
||||
},
|
||||
// ── System ──
|
||||
{
|
||||
slug: "preview",
|
||||
titleKey: "settings.sections.preview.title",
|
||||
icon: FlaskConical,
|
||||
group: "system",
|
||||
},
|
||||
{
|
||||
slug: "about",
|
||||
titleKey: "settings.sections.about.title",
|
||||
|
||||
@@ -44,9 +44,6 @@ export const settingsKeys = {
|
||||
*/
|
||||
moduleListLayout: () => ["settings", "module-list-layout"] as const,
|
||||
|
||||
/** The per-user preview-feature opt-ins behind `/api/settings/preview-features`. */
|
||||
previewFeatures: () => ["settings", "preview-features"] as const,
|
||||
|
||||
/**
|
||||
* v1.35.0 — which pillars count toward the Health Score, behind
|
||||
* `GET/PATCH /api/auth/me/health-score-config`. Its own cell rather than
|
||||
|
||||
@@ -98,7 +98,6 @@ export const SETTINGS_DESTINATION_INVENTORY = {
|
||||
export: PERSONAL,
|
||||
advanced: PERSONAL,
|
||||
privacy: PERSONAL,
|
||||
preview: PERSONAL,
|
||||
about: PERSONAL,
|
||||
} as const satisfies Record<
|
||||
SettingsSectionSlug,
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
*
|
||||
* Flags:
|
||||
* - `topics`: topic overview pages above the single-metric Insights pages.
|
||||
* Stored and shown in Settings > System; no surface reads it yet.
|
||||
* Stored, served by `/api/settings/preview-features` and carried by the
|
||||
* backup. The Settings > System switch is not listed until the topic pages
|
||||
* ship, because nothing reads the flag yet.
|
||||
*
|
||||
* Unknown keys in a stored blob are dropped on read, so a flag retired later
|
||||
* disappears cleanly and a malformed blob reads as everything off.
|
||||
|
||||
Reference in new issue
Block a user