refactor(v1.5): apply simplify-review safe-to-apply suggestions

Drops dead `id` props and `scroll-mt-28` classes from admin section
components (load-bearing in v1.4 monolithic admin, dead since the v1.5
phase-4b dynamic-route split). Also removes the unused
`LEGACY_ANCHOR_TO_SLUG` map, the `admin-shell` re-export shim, the
`BERLIN_TZ` and duplicate `MS_PER_DAY` constants, the verbose
`AdminSectionPage` docstring, the `useEffect` paraphrase comment in
`ai-section`, and rewrites the `_exhaustive: never` exhaustiveness
guard to the modern `slug satisfies never` form. `UserManagementSection`
now reads `currentUserId` via `useAuth()` instead of accepting it as a
prop.

Findings 1, 2, 3, 4, 6, 7, 8, 11, 15, 16, 17 from
.planning/phase-6-simplify-findings.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.7 committed 2026-05-09 17:41:02 +02:00
1 parent 79864bb8c2
commit 88c8db1abc
22 files changed
+41 -147

No files matched your search

+2 -17
View File
@@ -7,19 +7,8 @@ import {
isAdminSectionSlug,
} from "@/components/admin/section-slugs";
/**
* Dynamic admin section route — mirrors the v1.4 settings split. Each of
* the `ADMIN_SECTION_SLUGS` is pre-rendered at build via
* `generateStaticParams()` so the URLs are statically known to Next.js,
* while `dynamicParams = false` tells the router to 404 (rather than
* attempting on-demand rendering) for any slug not in the list.
*
* The actual section components are mounted by the client-only
* `<AdminSectionRenderer>` because most of them use TanStack Query and
* the auth hook (server-rendered they'd just paint a loader). The shell,
* however, is server-rendered so the sidebar paints with no JS hydration
* lag.
*/
// Section bodies need TanStack Query / useAuth, so they're rendered by the
// client-only `<AdminSectionRenderer>`. The shell stays server-rendered.
export const dynamicParams = false;
@@ -28,16 +17,12 @@ export function generateStaticParams() {
}
interface PageProps {
// Next.js 16 made route `params` an async Promise. We `await` it before use.
params: Promise<{ section: string }>;
}
export default async function AdminSectionPage({ params }: PageProps) {
const { section } = await params;
// Defence-in-depth — `dynamicParams = false` already 404s unknown slugs at
// routing time, but we re-check here so a hand-rolled override of the
// route config can never silently fall through to a typo'd slug.
if (!isAdminSectionSlug(section)) {
notFound();
}
+3 -6
View File
@@ -106,7 +106,7 @@ export function AdminSectionRenderer({
title={t("admin.section.users.title")}
subtitle={t("admin.section.users.subtitle")}
>
<UserManagementSection currentUserId={user.id} />
<UserManagementSection />
</SectionFrame>
);
case "api-tokens":
@@ -145,12 +145,9 @@ export function AdminSectionRenderer({
<DangerZoneSection />
</SectionFrame>
);
default: {
// exhaustiveness guard — TS will flag a missing case here
const _exhaustive: never = slug;
void _exhaustive;
default:
slug satisfies never;
return null;
}
}
}
@@ -106,7 +106,7 @@ describe("admin sections — SSR smoke", () => {
});
it("<UserManagementSection> renders with filter pills", () => {
const html = render(<UserManagementSection currentUserId="u1" />);
const html = render(<UserManagementSection />);
expect(html).toContain("User Management");
// Filter pills painted from i18n
expect(html).toContain("All");
+1 -11
View File
@@ -33,17 +33,7 @@ import {
import { cn } from "@/lib/utils";
import { useTranslations } from "@/lib/i18n/context";
import {
ADMIN_SECTION_SLUGS,
isAdminSectionSlug,
type AdminSectionSlug,
} from "./section-slugs";
// Re-export so callers don't have to chase two modules. New server-side
// callers (e.g. `generateStaticParams()`) should still import from
// `./section-slugs` directly — values exported through this client
// boundary become unusable proxies on the server.
export { ADMIN_SECTION_SLUGS, isAdminSectionSlug, type AdminSectionSlug };
import { isAdminSectionSlug, type AdminSectionSlug } from "./section-slugs";
interface AdminSection {
slug: AdminSectionSlug;
@@ -9,7 +9,7 @@ import { formatDate, formatDateTime } from "@/lib/format";
import { useTranslations } from "@/lib/i18n/context";
import { type ApiTokenInfo } from "./_shared";
export function ApiTokenOverviewSection({ id }: { id?: string } = {}) {
export function ApiTokenOverviewSection() {
const { t } = useTranslations();
const [expanded, setExpanded] = useState(false);
@@ -24,10 +24,7 @@ export function ApiTokenOverviewSection({ id }: { id?: string } = {}) {
});
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<Key className="text-primary h-5 w-5" />
+2 -5
View File
@@ -29,7 +29,7 @@ function formatBytes(bytes: number, fmt: ReturnType<typeof useFormatters>) {
return `${fmt.number(bytes / 1024 / 1024, 2)} MB`;
}
export function BackupsSection({ id }: { id?: string } = {}) {
export function BackupsSection() {
const { t } = useTranslations();
const fmt = useFormatters();
const queryClient = useQueryClient();
@@ -72,10 +72,7 @@ export function BackupsSection({ id }: { id?: string } = {}) {
const rows: BackupRow[] = data?.rows ?? [];
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<Database className="text-primary h-5 w-5" />
+2 -5
View File
@@ -10,7 +10,7 @@ import { Switch } from "@/components/ui/switch";
import { useTranslations } from "@/lib/i18n/context";
import { PasswordInput, useAdminSettings, useUpdateSettings } from "./_shared";
export function BugReportSection({ id }: { id?: string } = {}) {
export function BugReportSection() {
const { t } = useTranslations();
const { data: settings } = useAdminSettings();
const updateSettings = useUpdateSettings();
@@ -45,10 +45,7 @@ export function BugReportSection({ id }: { id?: string } = {}) {
}
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex flex-wrap items-start justify-between gap-2">
<div className="flex items-center gap-2">
<Bug className="text-primary h-5 w-5" />
+2 -5
View File
@@ -17,7 +17,7 @@ import {
import { Button } from "@/components/ui/button";
import { useTranslations } from "@/lib/i18n/context";
export function DangerZoneSection({ id }: { id?: string } = {}) {
export function DangerZoneSection() {
const { t } = useTranslations();
const queryClient = useQueryClient();
const [wipeMsg, setWipeMsg] = useState<string | null>(null);
@@ -65,10 +65,7 @@ export function DangerZoneSection({ id }: { id?: string } = {}) {
});
return (
<div
id={id}
className="bg-destructive/5 border-destructive/30 scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-destructive/5 border-destructive/30 rounded-xl border p-6">
<div className="flex items-center gap-2">
<AlertTriangle className="text-destructive h-5 w-5" />
<h2 className="text-destructive text-lg font-semibold">
@@ -45,7 +45,7 @@ import {
useSystemStatus,
} from "./_shared";
export function FeedbackInboxSection({ id }: { id?: string } = {}) {
export function FeedbackInboxSection() {
const { t } = useTranslations();
const queryClient = useQueryClient();
const { data: status } = useSystemStatus();
@@ -72,10 +72,7 @@ export function FeedbackInboxSection({ id }: { id?: string } = {}) {
}
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center gap-2">
<Inbox className="text-primary h-5 w-5" />
<h2 className="text-lg font-semibold">{t("admin.feedback.title")}</h2>
@@ -4,16 +4,13 @@ import { Settings } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
import { SettingsToggle, useAdminSettings, useUpdateSettings } from "./_shared";
export function GeneralSettingsSection({ id }: { id?: string } = {}) {
export function GeneralSettingsSection() {
const { t } = useTranslations();
const { data: settings } = useAdminSettings();
const updateSettings = useUpdateSettings();
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center gap-2">
<Settings className="text-primary h-5 w-5" />
<h2 className="text-lg font-semibold">{t("admin.appSettings")}</h2>
+2 -5
View File
@@ -16,7 +16,7 @@ import {
useUpdateSettings,
} from "./_shared";
export function GlitchtipSection({ id }: { id?: string } = {}) {
export function GlitchtipSection() {
const { t } = useTranslations();
const { data: settings } = useAdminSettings();
const updateSettings = useUpdateSettings();
@@ -72,10 +72,7 @@ export function GlitchtipSection({ id }: { id?: string } = {}) {
}
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex flex-wrap items-start justify-between gap-2">
<div className="flex items-center gap-2">
<AlertTriangle className="text-primary h-5 w-5" />
@@ -14,7 +14,7 @@ import { formatDateTime } from "@/lib/format";
import { useTranslations } from "@/lib/i18n/context";
import { type AdminAuditEntry } from "./_shared";
export function LoginOverviewSection({ id }: { id?: string } = {}) {
export function LoginOverviewSection() {
const { t } = useTranslations();
const [expanded, setExpanded] = useState(false);
const [filter, setFilter] = useState<"all" | "failed">("all");
@@ -49,10 +49,7 @@ export function LoginOverviewSection({ id }: { id?: string } = {}) {
: data?.entries;
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<ScrollText className="text-primary h-5 w-5" />
+2 -5
View File
@@ -22,7 +22,7 @@ import {
useUpdateSettings,
} from "./_shared";
export function RemindersSection({ id }: { id?: string } = {}) {
export function RemindersSection() {
const { t } = useTranslations();
const { data: settings } = useAdminSettings();
const updateSettings = useUpdateSettings();
@@ -113,10 +113,7 @@ export function RemindersSection({ id }: { id?: string } = {}) {
});
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center gap-2">
<Clock className="text-primary h-5 w-5" />
<h2 className="text-lg font-semibold">
-22
View File
@@ -28,25 +28,3 @@ export type AdminSectionSlug = (typeof ADMIN_SECTION_SLUGS)[number];
export function isAdminSectionSlug(value: string): value is AdminSectionSlug {
return (ADMIN_SECTION_SLUGS as readonly string[]).includes(value);
}
/**
* Legacy hash-anchor → new slug mapping. Used by `<StatusCardGrid>` to
* rewrite hrefs and by `proxy.ts` to handle any server-side legacy URL
* forms (path-style anchors are redirectable; pure `#fragments` never
* reach the server, so those are fixed at the call-sites instead).
*/
export const LEGACY_ANCHOR_TO_SLUG: Record<string, AdminSectionSlug> = {
"section-system-status": "system-status",
"section-admin-general": "general",
"section-admin-services": "services",
"section-admin-umami": "integrations",
"section-admin-glitchtip": "integrations",
"section-admin-webpush": "integrations",
"section-admin-bugreport": "integrations",
"section-admin-feedback": "feedback",
"section-admin-reminders": "reminders",
"section-user-management": "users",
"section-api-tokens": "api-tokens",
"section-login-overview": "login-overview",
"section-danger-zone": "danger-zone",
};
+2 -5
View File
@@ -4,16 +4,13 @@ import { Bell, Globe, Key, MessageCircle } from "lucide-react";
import { useTranslations } from "@/lib/i18n/context";
import { SettingsToggle, useAdminSettings, useUpdateSettings } from "./_shared";
export function ServicesSection({ id }: { id?: string } = {}) {
export function ServicesSection() {
const { t } = useTranslations();
const { data: settings } = useAdminSettings();
const updateSettings = useUpdateSettings();
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center gap-2">
<Globe className="text-primary h-5 w-5" />
<h2 className="text-lg font-semibold">{t("admin.servicesGlobal")}</h2>
@@ -19,16 +19,13 @@ import { formatDateTime } from "@/lib/format";
import { useFormatters, useTranslations } from "@/lib/i18n/context";
import { StatusItem, useSystemStatus } from "./_shared";
export function SystemStatusSection({ id }: { id?: string } = {}) {
export function SystemStatusSection() {
const { t } = useTranslations();
const fmt = useFormatters();
const { data: status, isError } = useSystemStatus();
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center gap-2">
<Server className="text-primary h-5 w-5" />
<h2 className="text-lg font-semibold">{t("admin.systemStatus")}</h2>
+2 -5
View File
@@ -16,7 +16,7 @@ import {
useUpdateSettings,
} from "./_shared";
export function UmamiSection({ id }: { id?: string } = {}) {
export function UmamiSection() {
const { t } = useTranslations();
const { data: settings } = useAdminSettings();
const updateSettings = useUpdateSettings();
@@ -75,10 +75,7 @@ export function UmamiSection({ id }: { id?: string } = {}) {
}
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex flex-wrap items-start justify-between gap-2">
<div className="flex items-center gap-2">
<Activity className="text-primary h-5 w-5" />
@@ -19,6 +19,7 @@ import {
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import { useAuth } from "@/hooks/use-auth";
import { formatDate } from "@/lib/format";
import { useTranslations } from "@/lib/i18n/context";
import { type AdminUser, PasswordInput } from "./_shared";
@@ -32,14 +33,10 @@ import { type AdminUser, PasswordInput } from "./_shared";
*/
type UserFilter = "all" | "admin" | "user";
export function UserManagementSection({
id,
currentUserId,
}: {
id?: string;
currentUserId: string;
}) {
export function UserManagementSection() {
const { t } = useTranslations();
const { user } = useAuth();
const currentUserId = user?.id ?? "";
const queryClient = useQueryClient();
const [editingUser, setEditingUser] = useState<AdminUser | null>(null);
const [editUsername, setEditUsername] = useState("");
@@ -157,10 +154,7 @@ export function UserManagementSection({
}
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2">
<Users className="text-primary h-5 w-5" />
@@ -9,7 +9,7 @@ import { Label } from "@/components/ui/label";
import { useTranslations } from "@/lib/i18n/context";
import { PasswordInput, useAdminSettings, useUpdateSettings } from "./_shared";
export function WebPushVapidSection({ id }: { id?: string } = {}) {
export function WebPushVapidSection() {
const { t } = useTranslations();
const { data: settings } = useAdminSettings();
const updateSettings = useUpdateSettings();
@@ -48,10 +48,7 @@ export function WebPushVapidSection({ id }: { id?: string } = {}) {
}
return (
<div
id={id}
className="bg-card border-border scroll-mt-28 rounded-xl border p-6"
>
<div className="bg-card border-border rounded-xl border p-6">
<div className="flex flex-wrap items-start justify-between gap-2">
<div className="flex items-center gap-2">
<BellRing className="text-primary h-5 w-5" />
+1 -8
View File
@@ -214,14 +214,7 @@ function InsightsSettingsCard({
// 15-minute device-code window expires. Polling stops as soon as
// `deviceCode` is cleared (success / cancel / unmount).
useEffect(() => {
if (!deviceCode) {
// No active device code → make sure the spinner is off. setState
// inside an effect is intentional here; the alternative (deriving
// `devicePolling` from `deviceCode`) would force every error
// path to also reset `deviceCode`, which we don't want — the
// start handler resets `devicePolling` itself when fetch fails.
return;
}
if (!deviceCode) return;
let cancelled = false;
const intervalMs = Math.max(deviceCode.intervalSeconds, 3) * 1000;
+1 -3
View File
@@ -16,10 +16,8 @@
* does not produce 360 zero-rows of noise.
*/
const BERLIN_TZ = "Europe/Berlin";
const BERLIN_DAY_FORMATTER = new Intl.DateTimeFormat("en-US", {
timeZone: BERLIN_TZ,
timeZone: "Europe/Berlin",
year: "numeric",
month: "2-digit",
day: "2-digit",
+1 -3
View File
@@ -17,8 +17,6 @@ import {
} from "@/lib/insights/bucket-series";
import { annotate } from "@/lib/logging/context";
const MS_PER_DAY = 24 * 60 * 60 * 1000;
const BERLIN_DAY_FORMATTER = new Intl.DateTimeFormat("en-US", {
timeZone: "Europe/Berlin",
year: "numeric",
@@ -285,7 +283,7 @@ export async function generateMoodStatusForUser(
// ~90-day window so the model still gets a recency-weighted view of
// tag patterns. The mood DB query already pulls 3 years (no where
// filter); we slice by date here, not by record count.
const tagWindowCutoff = new Date(now.getTime() - 90 * MS_PER_DAY);
const tagWindowCutoff = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
const recentEntries = entries.filter(
(entry) => entry.moodLoggedAt.getTime() >= tagWindowCutoff.getTime(),
);