overview: one endpoint, live projections and a response cache (m36)

This commit is contained in:
2026-08-27 17:48:20 +02:00
parent 7cdb74ec17
commit c8470724ce
40 changed files with 3298 additions and 2242 deletions
@@ -0,0 +1,133 @@
/**
* The part of Overview that does not wait for anything: the heading, the period
* picker, and the pulsing body the page shows while the window is in flight.
*
* It lives apart from `OverviewPage` so the route's pending component can render
* the identical surface while the page chunk loads. Importing the page itself
* would pull the charts into the main bundle, and a second hand-written copy of
* the frame would drift. Nothing here imports a chart.
*/
import * as stylex from "@stylexjs/stylex";
import { useNavigate, useSearch } from "@tanstack/react-router";
import type { Period } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import { DEFAULT_PERIOD, PERIODS } from "./period";
const styles = stylex.create({
page: {
display: "flex",
flexDirection: "column",
gap: "1rem",
},
headingRow: {
display: "flex",
flexWrap: "wrap",
alignItems: "center",
justifyContent: "space-between",
gap: "0.75rem",
},
heading: {
fontSize: "1.5rem",
lineHeight: "2rem",
fontWeight: 600,
},
periodGroup: {
display: "flex",
gap: "0.25rem",
},
period: {
borderStyle: "none",
borderRadius: "0.25rem",
paddingInline: "0.625rem",
paddingBlock: "0.25rem",
fontSize: "0.875rem",
lineHeight: "1.25rem",
},
/** The pressed fill is heavier than `surfaceHover`, so a hover cannot mimic it. */
periodSelected: {
backgroundColor: {
default: "oklch(92% 0.004 286.32)",
"@media (prefers-color-scheme: dark)": "oklch(37% 0.013 285.805)",
},
color: colors.text,
fontWeight: 500,
},
periodIdle: {
backgroundColor: { default: "transparent", ":hover": colors.surfaceHover },
color: colors.textSecondary,
},
loading: {
fontSize: "0.875rem",
lineHeight: "1.25rem",
color: colors.textMuted,
},
});
export function PeriodPicker({ period, onChange }: { period: Period; onChange: (period: Period) => void }) {
return (
<div role="group" aria-label="Period" {...stylex.props(styles.periodGroup)}>
{PERIODS.map((option) => (
<button
key={option}
type="button"
aria-pressed={option === period}
onClick={() => onChange(option)}
{...stylex.props(
styles.period,
option === period ? styles.periodSelected : styles.periodIdle,
shared.focusRing,
)}
>
{option}
</button>
))}
</div>
);
}
export function OverviewLoading() {
return (
<p role="status" {...stylex.props(styles.loading, shared.pulse)}>
Loading
</p>
);
}
export function OverviewFrame({
period,
onChange,
children,
}: {
period: Period;
onChange: (period: Period) => void;
children: React.ReactNode;
}) {
return (
<div {...stylex.props(styles.page)}>
<div {...stylex.props(styles.headingRow)}>
<h1 {...stylex.props(styles.heading)}>Overview</h1>
<PeriodPicker period={period} onChange={onChange} />
</div>
{children}
</div>
);
}
/**
* The route's pending surface. The picker stays live because it only writes the
* search parameter, which the route already re-reads on its own.
*/
export function OverviewPending() {
const period = useSearch({ from: "/shell/overview" }).period ?? DEFAULT_PERIOD;
const navigate = useNavigate({ from: "/overview" });
return (
<OverviewFrame
period={period}
onChange={(next) => void navigate({ search: (prev) => ({ ...prev, period: next }) })}
>
<OverviewLoading />
</OverviewFrame>
);
}