refactor: simplify-pass apply-yes cleanup

Five independent atomic edits from the W5 simplify review, grouped
in a single commit since none pull weight as a compound theme:

- coach-input: collapse `!disabled && value.trim().length > 0`
  triplication onto a single `canSubmit` (S-01).
- hero-strip: extract the duplicate weekly-report Button shell into
  a shared label fragment with the Link wrapping conditionally
  (S-02).
- api-token-overview-section: extract `<TokenStatusBadge size>` so
  the desktop table and mobile card list stay in lock-step on the
  revoked / expired / active 3-way ternary (S-03).
- trends-row: hoist the dynamic-load chart skeleton to a shared
  `ChartSkeleton` component (S-04). No chart-visual change —
  placeholder only.
- correlation-row: collapse the grid className ternary onto `cn()`
  with a conditional `md:grid-cols-2` (S-05).

Test count unchanged (2105).

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-10 22:17:29 +02:00
1 parent 65a8b5a8b6
commit 1631d8042c
5 files changed
+98 -92

No files matched your search

@@ -55,6 +55,46 @@ function TruncatedCell({
); );
} }
/**
* v1.4.22 W5 reconcile (S-03) — the revoked / isExpired / active
* 3-way ternary is identical between the desktop table and the
* mobile card list; the only difference is `text-xs` vs `text-[10px]`.
* Centralise so the two surfaces stay in lock-step.
*/
function TokenStatusBadge({
token,
size,
}: {
token: ApiTokenInfo;
size: "sm" | "xs";
}) {
const { t } = useTranslations();
const expired =
token.expiresAt != null && new Date(token.expiresAt) < new Date();
const sizeClass = size === "xs" ? "text-[10px]" : "text-xs";
if (token.revoked) {
return (
<Badge variant="destructive" className={`shrink-0 ${sizeClass}`}>
{t("settings.tokenRevoked")}
</Badge>
);
}
if (expired) {
return (
<Badge variant="destructive" className={`shrink-0 ${sizeClass}`}>
{t("settings.tokenExpired")}
</Badge>
);
}
return (
<Badge
className={`bg-dracula-green/15 text-dracula-green shrink-0 ${sizeClass}`}
>
{t("common.active")}
</Badge>
);
}
/** /**
* F-18 (v1.4.19): the auto-login flow names tokens * F-18 (v1.4.19): the auto-login flow names tokens
* "web auto-login 2026-05-05T19:46:20.603Z" / "iOS auto-login …" by * "web auto-login 2026-05-05T19:46:20.603Z" / "iOS auto-login …" by
@@ -181,8 +221,6 @@ export function ApiTokenOverviewSection() {
</thead> </thead>
<tbody className="divide-border divide-y"> <tbody className="divide-border divide-y">
{tokens.map((token, i) => { {tokens.map((token, i) => {
const isExpired =
token.expiresAt && new Date(token.expiresAt) < new Date();
return ( return (
<tr <tr
key={token.id} key={token.id}
@@ -213,19 +251,7 @@ export function ApiTokenOverviewSection() {
</div> </div>
</td> </td>
<td className="px-3 py-2 text-right"> <td className="px-3 py-2 text-right">
{token.revoked ? ( <TokenStatusBadge token={token} size="sm" />
<Badge variant="destructive" className="text-xs">
{t("settings.tokenRevoked")}
</Badge>
) : isExpired ? (
<Badge variant="destructive" className="text-xs">
{t("settings.tokenExpired")}
</Badge>
) : (
<Badge className="bg-dracula-green/15 text-dracula-green text-xs">
{t("common.active")}
</Badge>
)}
</td> </td>
{/* v1.4.22 C2 (5th attempt) — drop {/* v1.4.22 C2 (5th attempt) — drop
`whitespace-nowrap` on the date cells. The `whitespace-nowrap` on the date cells. The
@@ -264,8 +290,6 @@ export function ApiTokenOverviewSection() {
data-testid="admin-tokens-mobile-list" data-testid="admin-tokens-mobile-list"
> >
{tokens.map((token) => { {tokens.map((token) => {
const isExpired =
token.expiresAt && new Date(token.expiresAt) < new Date();
return ( return (
<li <li
key={token.id} key={token.id}
@@ -278,25 +302,7 @@ export function ApiTokenOverviewSection() {
value={formatTokenName(token.name)} value={formatTokenName(token.name)}
className="min-w-0 flex-1 font-medium" className="min-w-0 flex-1 font-medium"
/> />
{token.revoked ? ( <TokenStatusBadge token={token} size="xs" />
<Badge
variant="destructive"
className="shrink-0 text-[10px]"
>
{t("settings.tokenRevoked")}
</Badge>
) : isExpired ? (
<Badge
variant="destructive"
className="shrink-0 text-[10px]"
>
{t("settings.tokenExpired")}
</Badge>
) : (
<Badge className="bg-dracula-green/15 text-dracula-green shrink-0 text-[10px]">
{t("common.active")}
</Badge>
)}
</div> </div>
<TruncatedCell <TruncatedCell
value={token.user.username} value={token.user.username}
@@ -56,32 +56,26 @@ export function CoachInput({
}: CoachInputProps) { }: CoachInputProps) {
const { t } = useTranslations(); const { t } = useTranslations();
const canSubmit = !disabled && value.trim().length > 0;
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
(event: KeyboardEvent<HTMLTextAreaElement>) => { (event: KeyboardEvent<HTMLTextAreaElement>) => {
if (event.key !== "Enter") return; // Shift+Enter inserts a newline; plain Enter and ⌘/Ctrl+Enter submit.
// Shift+Enter inserts a newline. if (event.key !== "Enter" || event.shiftKey) return;
if (event.shiftKey) return;
// Plain Enter and ⌘/Ctrl+Enter both submit.
event.preventDefault(); event.preventDefault();
if (!disabled && value.trim().length > 0) { if (canSubmit) onSubmit();
onSubmit();
}
}, },
[disabled, onSubmit, value], [canSubmit, onSubmit],
); );
const handleFormSubmit = useCallback( const handleFormSubmit = useCallback(
(event: FormEvent<HTMLFormElement>) => { (event: FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
if (!disabled && value.trim().length > 0) { if (canSubmit) onSubmit();
onSubmit();
}
}, },
[disabled, onSubmit, value], [canSubmit, onSubmit],
); );
const canSubmit = !disabled && value.trim().length > 0;
return ( return (
<form <form
data-slot="coach-input" data-slot="coach-input"
+5 -5
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import { cn } from "@/lib/utils";
import { useTranslations } from "@/lib/i18n/context"; import { useTranslations } from "@/lib/i18n/context";
import type { CorrelationResult } from "@/lib/insights/correlations"; import type { CorrelationResult } from "@/lib/insights/correlations";
import { CorrelationCard } from "./correlation-card"; import { CorrelationCard } from "./correlation-card";
@@ -57,11 +58,10 @@ export function CorrelationRow({ results }: CorrelationRowProps) {
</p> </p>
</div> </div>
<div <div
className={ className={cn(
okResults.length === 1 "grid grid-cols-1 gap-4",
? "grid grid-cols-1 gap-4" okResults.length > 1 && "md:grid-cols-2",
: "grid grid-cols-1 gap-4 md:grid-cols-2" )}
}
> >
{okResults.map((result) => ( {okResults.map((result) => (
<CorrelationCard key={result.kind} result={result} /> <CorrelationCard key={result.kind} result={result} />
+33 -25
View File
@@ -238,33 +238,41 @@ export function HeroStrip({
* current ISO week. Older parents that haven't adopted the * current ISO week. Older parents that haven't adopted the
* weeklyReportHref prop still get the disabled affordance. * weeklyReportHref prop still get the disabled affordance.
*/} */}
{weeklyReportHref ? ( {(() => {
<Button // v1.4.22 W5 reconcile (S-02) — collapse the
asChild // enabled/disabled twin Buttons (variant, size, slot,
variant="default" // class, icon, label all identical) into a single
size="sm" // shared structure with the Link wrapping conditionally.
data-slot="insights-hero-strip-action-weekly-report" const weeklyLabel = (
className="gap-1.5" <>
>
<Link href={weeklyReportHref}>
<FileText className="h-3.5 w-3.5" aria-hidden="true" /> <FileText className="h-3.5 w-3.5" aria-hidden="true" />
<span>{t("insights.heroActionWeeklyReport")}</span> <span>{t("insights.heroActionWeeklyReport")}</span>
</Link> </>
</Button> );
) : ( return weeklyReportHref ? (
<Button <Button
type="button" asChild
variant="default" variant="default"
size="sm" size="sm"
disabled data-slot="insights-hero-strip-action-weekly-report"
title={comingSoon} className="gap-1.5"
data-slot="insights-hero-strip-action-weekly-report" >
className="gap-1.5" <Link href={weeklyReportHref}>{weeklyLabel}</Link>
> </Button>
<FileText className="h-3.5 w-3.5" aria-hidden="true" /> ) : (
<span>{t("insights.heroActionWeeklyReport")}</span> <Button
</Button> type="button"
)} variant="default"
size="sm"
disabled
title={comingSoon}
data-slot="insights-hero-strip-action-weekly-report"
className="gap-1.5"
>
{weeklyLabel}
</Button>
);
})()}
{/* B2b wires this into the Coach drawer. The button is {/* B2b wires this into the Coach drawer. The button is
enabled whenever the parent supplies an `onAskCoach` enabled whenever the parent supplies an `onAskCoach`
handler; older parents that haven't adopted B2b yet handler; older parents that haven't adopted B2b yet
+10 -12
View File
@@ -26,17 +26,20 @@ import {
* the row's visual rhythm. * the row's visual rhythm.
*/ */
// v1.4.22 W5 reconcile (S-04) — both `dynamic()` calls used the same
// pulse-skeleton placeholder. Hoist so the loading affordance stays
// in lock-step (cleared against `feedback_charts_visual_identity.md`
// — placeholder only, no chart visual change).
const ChartSkeleton = () => (
<div className="bg-muted/40 h-[220px] w-full animate-pulse rounded-md motion-reduce:animate-none" />
);
const HealthChart = dynamic( const HealthChart = dynamic(
() => () =>
import("@/components/charts/health-chart").then((mod) => ({ import("@/components/charts/health-chart").then((mod) => ({
default: mod.HealthChart, default: mod.HealthChart,
})), })),
{ { ssr: false, loading: ChartSkeleton },
ssr: false,
loading: () => (
<div className="bg-muted/40 h-[220px] w-full animate-pulse rounded-md motion-reduce:animate-none" />
),
},
); );
const MoodChart = dynamic( const MoodChart = dynamic(
@@ -44,12 +47,7 @@ const MoodChart = dynamic(
import("@/components/charts/mood-chart").then((mod) => ({ import("@/components/charts/mood-chart").then((mod) => ({
default: mod.MoodChart, default: mod.MoodChart,
})), })),
{ { ssr: false, loading: ChartSkeleton },
ssr: false,
loading: () => (
<div className="bg-muted/40 h-[220px] w-full animate-pulse rounded-md motion-reduce:animate-none" />
),
},
); );
interface TrendsRowProps { interface TrendsRowProps {