Merge work/hero-score-panel

This commit is contained in:
Marc-André Bombeck committed 2026-07-31 11:48:22 +02:00
commit f8074e1b07
10 files changed
+1811 -367

No files matched your search

+29 -16
View File
@@ -268,42 +268,55 @@ describe("v1.16.8 — insights overview cold paint", () => {
expect(src).not.toMatch(/useDashboardDerived\([^)]*data/);
});
it("reserves the health-score card's footprint while the payload is pending", () => {
it("reserves the score column's footprint while the payload is pending", () => {
const src = load(INSIGHTS_PATH);
// The pinned slot holds the collapsed card's footprint from first paint,
// so the resolved card is a swap rather than a push.
// The reserve holds the column from first paint, so the resolved panel is
// a swap rather than a push.
expect(src).toMatch(
/analyticsQuery\.isPending && !analytics \? \(\s*<HealthScoreCardSkeleton/,
/scorePending=\{analyticsQuery\.isPending && !analytics\}/,
);
});
it("pins the health score under the hero, outside the section registry", () => {
it("mounts the health score once, in the hero, outside the section registry", () => {
const src = load(INSIGHTS_PATH);
// An id a saved layout has never seen merges in as default-INVISIBLE, so
// registering the score would delete it for every customised page. It is
// also no longer inside the briefing branch, which used to take the whole
// also outside the briefing branch, which used to take the whole
// breakdown down with the flag.
const hero = src.indexOf("<HeroStrip");
const score = src.indexOf("<HealthScoreCard");
const region = src.indexOf("orderedSectionIds.map");
// The JSX mount, not the `<HeroStrip>` mentioned in the file's header
// comment — matching the comment would have made the slice below read a
// block of prose and pass on nothing.
const hero = src.search(/\n\s*<HeroStrip\n/);
expect(hero).toBeGreaterThan(-1);
expect(score).toBeGreaterThan(hero);
expect(score).toBeLessThan(region);
expect(src).not.toMatch(/"health-score":/);
// Exactly one score surface on the page, and it is the hero's prop.
expect(src.match(/healthScore=\{/g) ?? []).toHaveLength(1);
expect(src).not.toContain("<HealthScoreCard");
expect(src).not.toContain("<HealthScoreCardSkeleton");
const briefingBranch = src.slice(
src.indexOf('"daily-briefing": flags.briefing'),
src.indexOf("vitals: <VitalsDashboard"),
);
expect(briefingBranch).not.toContain("HealthScoreCard");
expect(briefingBranch).not.toContain("healthScore");
// The band itself is not gated on the briefing flag either.
const heroCall = src.slice(hero, src.indexOf("/>", hero));
expect(heroCall).toContain("healthScore={analytics?.healthScore ?? null}");
expect(heroCall).not.toContain("flags.briefing");
});
it("hero strip carries no score surface at all", () => {
it("hero strip owns the one score surface", () => {
const heroSrc = readFileSync(
join(ROOT, "src/components/insights/hero-strip.tsx"),
"utf8",
);
expect(heroSrc).not.toContain("HealthScoreCard");
expect(heroSrc).not.toContain("health-score-card-skeleton");
expect(heroSrc).not.toContain("healthScorePending");
expect(heroSrc).toContain("<HealthScoreCard");
expect(heroSrc).toContain("<HealthScoreCardSkeleton");
// The split appears only when the column has something in it.
expect(heroSrc).toContain("md:flex-row md:items-stretch");
expect(heroSrc).toMatch(
/\(healthScore \|\| scorePending\) &&\s*"md:flex-row/,
);
});
});
+19 -29
View File
@@ -26,10 +26,6 @@ import { QueryErrorCard } from "@/components/ui/query-error-card";
import { Skeleton } from "@/components/ui/skeleton";
import { cn } from "@/lib/utils";
import { HeroStrip } from "@/components/insights/hero-strip";
import {
HealthScoreCard,
HealthScoreCardSkeleton,
} from "@/components/insights/health-score-card";
import { useInsightsAdvisorQuery } from "@/components/insights/use-insights-advisor";
import { useAnalyticsQuery } from "@/lib/queries/use-analytics-query";
import { useDashboardSnapshot } from "@/lib/queries/use-dashboard-snapshot";
@@ -561,33 +557,27 @@ export default function InsightsPageClient() {
// <relative>" line with a discreet connect-provider hint, matching
// the briefing card footer. Only when a briefing is actually shown.
noProviderStale={briefingPayload !== null && !advisor.hasProvider}
// The Health Score lives in the band's right column and NOWHERE else
// on this page. It is deliberately not a registry section: an id a
// saved layout has never seen merges in as default-INVISIBLE
// (`orderedVisibleSectionIds`), so registering it would silently
// delete the score for every account that has ever customised this
// page. It is also outside the daily-briefing branch, so an instance
// with the briefing switched off keeps the whole breakdown.
healthScore={analytics?.healthScore ?? null}
// `isPending` settles false on success AND error, so a no-score
// account drops the reserve exactly once.
scorePending={analyticsQuery.isPending && !analytics}
// v1.21.2 (A5 / A6) — server-resolved Tension Verdict + return-to-
// baseline off the dashboard snapshot. The panel localises the keys;
// null leaves it quiet.
tension={heroTension}
returnToBand={heroReturnToBand}
// A pillar whose read FAILED is a named line inside the panel, and the
// retry there refetches the payload that carries every pillar.
onScoreRetry={() => void analyticsQuery.refetch()}
/>
{/* The Health Score is PINNED here, between the greeting and the
customizable region, and is deliberately NOT a registry section: an
id a saved layout has never seen merges in as default-INVISIBLE
(`orderedVisibleSectionIds`), so registering it would silently delete
the score for every account that has ever customised this page. It
also used to render inside the daily-briefing branch, which meant an
instance with the briefing switched off lost the whole breakdown.
`isPending` settles false on success AND error, so a no-score account
drops the reserve exactly once. */}
{analyticsQuery.isPending && !analytics ? (
<HealthScoreCardSkeleton />
) : analytics?.healthScore ? (
<HealthScoreCard
report={analytics.healthScore}
// v1.21.2 (A5 / A6) — server-resolved Tension Verdict + return-to-
// baseline off the dashboard snapshot. The card localises the keys;
// null leaves it quiet.
tension={heroTension}
returnToBand={heroReturnToBand}
// A pillar whose read FAILED is an error row inside the card, and a
// retry there refetches the payload that carries every pillar.
onRetry={() => void analyticsQuery.refetch()}
/>
) : null}
{/* v1.15.18 — the inline "Anpassen" toggle was removed. Customising the
overview (section show/hide + order) and sorting the nav pills now
live ONLY in Settings → Insights, reached via the top-right cog on the