mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
Merge remote-tracking branch 'origin/release/v1.46.0' into feat/v146-ai
# Conflicts: # src/__tests__/baselines/source-file-lines.json
This commit is contained in:
commit
cf7302b50a
38 files changed
+1118
-268
No files matched your search
@@ -5,6 +5,8 @@ import { Activity, Smile } from "lucide-react";
|
||||
import { TrendCard } from "../trend-card";
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { primeLocales } from "@/__tests__/helpers/prime-locales";
|
||||
import { summarize } from "@/lib/analytics/trends";
|
||||
import { summaryToTrend7Delta } from "@/lib/analytics/trend-delta";
|
||||
|
||||
primeLocales("de");
|
||||
|
||||
@@ -166,3 +168,42 @@ describe("<TrendCard> 7-day trend label", () => {
|
||||
expect(html).not.toContain("text-warning");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The blood-pressure tile read "7-Tage-Trend: 125 (−11.412)" when the
|
||||
* week's only readings were one sitting minutes apart: the regression's
|
||||
* slope per day exploded and the tile printed seven days of it. The tile is
|
||||
* fed the way `strip-tiles.tsx` feeds it, from `summarize()` through
|
||||
* `summaryToTrend7Delta()`, so the delta it renders is the one the
|
||||
* dashboard would.
|
||||
*/
|
||||
describe("<TrendCard> blood-pressure tile after one sitting", () => {
|
||||
it("renders the 7-day mean, not a thousands-of-mmHg trend", () => {
|
||||
const MINUTE = 60 * 1000;
|
||||
const summary = summarize([
|
||||
{ date: new Date(Date.now() - 10 * MINUTE), value: 130 },
|
||||
{ date: new Date(Date.now() - 8 * MINUTE), value: 124 },
|
||||
{ date: new Date(Date.now() - 6 * MINUTE), value: 118 },
|
||||
]);
|
||||
const html = render(
|
||||
<TrendCard
|
||||
label="BD (SYS)"
|
||||
latest={summary.latest}
|
||||
unit="mmHg"
|
||||
fractionDigits={0}
|
||||
avg7={summary.avg7}
|
||||
avg30={summary.avg30}
|
||||
slope30={summary.slope30}
|
||||
trend7Delta={summaryToTrend7Delta(summary)}
|
||||
icon={Activity}
|
||||
directionSentiment="up-bad"
|
||||
/>,
|
||||
"de",
|
||||
);
|
||||
expect(html).not.toContain('data-slot="trend7-delta"');
|
||||
expect(html).not.toContain("7-Tage-Trend");
|
||||
expect(html).toContain("7T");
|
||||
// No grouped thousands anywhere on the tile.
|
||||
expect(html).not.toMatch(/\d\.\d{3}/);
|
||||
});
|
||||
});
|
||||
@@ -12,6 +12,10 @@ import { readSource } from "@/__tests__/helpers/source-files";
|
||||
|
||||
import {
|
||||
MAIN_SCROLL_CLASS,
|
||||
RAIL_ACCOUNT_BAND,
|
||||
RAIL_WIDTH_COLLAPSED,
|
||||
RAIL_WIDTH_EXPANDED,
|
||||
SHELL_HEADER_BAND,
|
||||
SHELL_ROOT_CLASS,
|
||||
SUB_SHELL_GRID_FLOOR,
|
||||
} from "../shell-metrics";
|
||||
@@ -137,3 +141,53 @@ describe("the bottom edge is cleared in both shells", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The side insets. An iPad in a Safari tab laid the shell out 10 px in from
|
||||
* the left edge in portrait; the shell padded that inset and its strip showed
|
||||
* the page colour beside the rail, from the logo band to the account row.
|
||||
* Desktop browsers report no side inset, so `e2e/tablet-shell.spec.ts`
|
||||
* simulates one through `--safe-area-left` and measures the result; these
|
||||
* hold the decisions that route the inset to the rail.
|
||||
*/
|
||||
describe("the side insets belong to what meets them", () => {
|
||||
const css = () => readSource("src/app/globals.css", ".shell-safe-area {");
|
||||
const rail = () =>
|
||||
readSource(
|
||||
"src/components/layout/sidebar-nav.tsx",
|
||||
"export function SidebarNav",
|
||||
);
|
||||
|
||||
it("are env() behind one property each, which the shell and the rail read", () => {
|
||||
const root = readSource("src/app/globals.css", "--safe-area-left");
|
||||
expect(root).toMatch(
|
||||
/--safe-area-left:\s*env\(safe-area-inset-left,\s*0px\);/,
|
||||
);
|
||||
expect(root).toMatch(
|
||||
/--safe-area-right:\s*env\(safe-area-inset-right,\s*0px\);/,
|
||||
);
|
||||
expect(css()).not.toMatch(/padding-(?:left|right):\s*env\(/);
|
||||
});
|
||||
|
||||
it("the desktop shell pads no side, the phone shell pads both", () => {
|
||||
const block = /\.shell-safe-area\s*\{([\s\S]*?)\n \}/.exec(css())![1];
|
||||
expect(block).toMatch(/padding-left:\s*var\(--safe-area-left\);/);
|
||||
expect(block).toMatch(/padding-right:\s*var\(--safe-area-right\);/);
|
||||
expect(block).toMatch(
|
||||
/@variant shell-desktop\s*\{\s*padding-right:\s*0;\s*padding-left:\s*0;\s*\}/,
|
||||
);
|
||||
});
|
||||
|
||||
it("the rail takes the left inset inside its own background", () => {
|
||||
const source = rail();
|
||||
expect(source).toMatch(/<aside[\s\S]*?pl-\(--safe-area-left\)/);
|
||||
expect(RAIL_WIDTH_COLLAPSED).toBe("w-[calc(4rem+var(--safe-area-left))]");
|
||||
expect(RAIL_WIDTH_EXPANDED).toBe("w-[calc(16rem+var(--safe-area-left))]");
|
||||
});
|
||||
|
||||
it("the account band mirrors the logo band", () => {
|
||||
const height = (value: string) => /\bh-(\d+)\b/.exec(value)?.[1];
|
||||
expect(height(RAIL_ACCOUNT_BAND)).toBe(height(SHELL_HEADER_BAND));
|
||||
expect(RAIL_ACCOUNT_BAND).toContain("border-t");
|
||||
});
|
||||
});
|
||||
@@ -56,6 +56,7 @@ vi.mock("@/hooks/use-mounted", () => ({
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
import { RAIL_WIDTH_COLLAPSED } from "../shell-metrics";
|
||||
import {
|
||||
SIDEBAR_EXPANDED_CONTENT_WIDTH,
|
||||
SidebarCollapseToggle,
|
||||
@@ -559,7 +560,7 @@ describe("<SidebarNav> width transition never overflows the nav", () => {
|
||||
});
|
||||
mockMountedRef.value = true;
|
||||
const html = render();
|
||||
expect(asideClass(html).split(" ")).toContain("w-16");
|
||||
expect(asideClass(html).split(" ")).toContain(RAIL_WIDTH_COLLAPSED);
|
||||
expect(columnClass(html).split(" ")).not.toContain(
|
||||
SIDEBAR_EXPANDED_CONTENT_WIDTH,
|
||||
);
|
||||
|
||||
@@ -91,3 +91,52 @@ export const MAIN_SCROLL_CLASS =
|
||||
* widths in both the expanded and the collapsed rail.
|
||||
*/
|
||||
export const SHELL_HEADER_BAND = "h-16 border-b";
|
||||
|
||||
/**
|
||||
* The rail's account band, the bottom counterpart of its logo band: the same
|
||||
* height, the border on the other side, the avatar centred in it. Padded on
|
||||
* its own (`p-3`, `p-2` on short windows) it came out 61 to 69 px tall with
|
||||
* 18 px above the avatar and more below, and its divider sat at no height
|
||||
* the logo band shares. The home-indicator inset stays under the band (the
|
||||
* rail's `pb-(--safe-area-bottom)`), so the band itself never shifts.
|
||||
*/
|
||||
export const RAIL_ACCOUNT_BAND = "h-16 shrink-0 border-t";
|
||||
|
||||
/**
|
||||
* The rail's width, collapsed (4rem) and expanded (16rem), plus the window's
|
||||
* left inset. The rail runs to the window's left edge and takes that inset as
|
||||
* padding inside its own background (`pl-(--safe-area-left)` on the
|
||||
* `<aside>`), so its content keeps the 4rem / 16rem it was laid out for and
|
||||
* stays clear of the inset. The shell leaves the left side to the rail
|
||||
* (`.shell-safe-area`, globals.css): an iPad in a Safari tab laid the shell
|
||||
* out 10 px in from the left, and that strip showed the page colour beside
|
||||
* the rail. Zero inset: exactly `w-16` / `w-64`.
|
||||
*/
|
||||
export const RAIL_WIDTH_COLLAPSED = "w-[calc(4rem+var(--safe-area-left))]";
|
||||
export const RAIL_WIDTH_EXPANDED = "w-[calc(16rem+var(--safe-area-left))]";
|
||||
|
||||
/**
|
||||
* A rail band that draws a border across the rail (the logo band, the footer
|
||||
* group's scroll edge, the account band) reaches back over the left inset, so
|
||||
* its line runs to the window's edge like the top bar's does, and adds the
|
||||
* inset to its own left padding, so its content stays where it was.
|
||||
*/
|
||||
const RAIL_BAND_BLEED = "-ml-(--safe-area-left)";
|
||||
|
||||
/** The logo band's and the footer group's horizontal padding, inset included. */
|
||||
export const RAIL_BAND_INSET = {
|
||||
header: {
|
||||
collapsed: `${RAIL_BAND_BLEED} pr-3 pl-[calc(0.75rem+var(--safe-area-left))]`,
|
||||
expanded: `${RAIL_BAND_BLEED} pr-6 pl-[calc(1.5rem+var(--safe-area-left))]`,
|
||||
},
|
||||
footer: {
|
||||
collapsed: `${RAIL_BAND_BLEED} pr-1.5 pl-[calc(0.375rem+var(--safe-area-left))]`,
|
||||
expanded: `${RAIL_BAND_BLEED} pr-3 pl-[calc(0.75rem+var(--safe-area-left))]`,
|
||||
},
|
||||
} as const;
|
||||
|
||||
/** The account band, collapsed (the avatar centred) and expanded. */
|
||||
export const RAIL_ACCOUNT_BAND_CLASS = {
|
||||
collapsed: `${RAIL_ACCOUNT_BAND} ${RAIL_BAND_BLEED} border-sidebar-border flex items-center justify-center pl-(--safe-area-left)`,
|
||||
expanded: `${RAIL_ACCOUNT_BAND} ${RAIL_BAND_BLEED} border-sidebar-border flex items-center pr-3 pl-[calc(0.75rem+var(--safe-area-left))]`,
|
||||
} as const;
|
||||
@@ -26,7 +26,13 @@ import {
|
||||
visibleNavDestinations,
|
||||
visibleUtilityDestinations,
|
||||
} from "@/components/layout/nav-model";
|
||||
import { SHELL_HEADER_BAND } from "@/components/layout/shell-metrics";
|
||||
import {
|
||||
RAIL_ACCOUNT_BAND_CLASS,
|
||||
RAIL_BAND_INSET,
|
||||
RAIL_WIDTH_COLLAPSED,
|
||||
RAIL_WIDTH_EXPANDED,
|
||||
SHELL_HEADER_BAND,
|
||||
} from "@/components/layout/shell-metrics";
|
||||
import {
|
||||
RailNavList,
|
||||
useNavOverflows,
|
||||
@@ -268,10 +274,8 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) {
|
||||
if (collapsed) {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"border-sidebar-border border-t p-3",
|
||||
"[@media(max-height:940px)]:p-2",
|
||||
)}
|
||||
data-slot="sidebar-account"
|
||||
className={RAIL_ACCOUNT_BAND_CLASS.collapsed}
|
||||
>
|
||||
<div className="flex items-center justify-center">
|
||||
<DropdownMenu>
|
||||
@@ -294,8 +298,11 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border-sidebar-border border-t p-3">
|
||||
<div className="flex items-center gap-3 px-2 py-1">
|
||||
<div
|
||||
data-slot="sidebar-account"
|
||||
className={RAIL_ACCOUNT_BAND_CLASS.expanded}
|
||||
>
|
||||
<div className="flex w-full min-w-0 items-center gap-3 px-2 py-1">
|
||||
{/* The avatar + name in the expanded footer routes straight
|
||||
into the account settings; the kebab to the right still opens
|
||||
the rest of the user menu. */}
|
||||
@@ -343,7 +350,8 @@ function SidebarUserSection({ collapsed }: { collapsed: boolean }) {
|
||||
*
|
||||
* Steps (rows are full-width, so the click target is the whole row):
|
||||
* - default: 40 px rows, 4 px apart
|
||||
* - max-height 940 px: gaps close (40 px pitch), list and user padding tighten
|
||||
* - max-height 940 px: gaps close (40 px pitch), the list padding tightens
|
||||
* (the account band keeps the logo band's height, `RAIL_ACCOUNT_BAND`)
|
||||
* - max-height 820 px: 36 px rows
|
||||
* - max-height 740 px: 32 px rows — the one step under 40 px, taken only
|
||||
* where the alternative is rows hidden behind a scroll edge
|
||||
@@ -627,8 +635,8 @@ export function SidebarNav() {
|
||||
<aside
|
||||
aria-label={t("nav.sidebar")}
|
||||
className={cn(
|
||||
"bg-sidebar border-sidebar-border shell-desktop:flex shell-desktop:flex-col relative hidden h-full flex-shrink-0 overflow-hidden border-r pb-(--safe-area-bottom) transition-[width] duration-200 motion-reduce:transition-none",
|
||||
collapsed ? "w-16" : "w-64",
|
||||
"bg-sidebar border-sidebar-border shell-desktop:flex shell-desktop:flex-col relative hidden h-full flex-shrink-0 overflow-hidden border-r pb-(--safe-area-bottom) pl-(--safe-area-left) transition-[width] duration-200 motion-reduce:transition-none",
|
||||
collapsed ? RAIL_WIDTH_COLLAPSED : RAIL_WIDTH_EXPANDED,
|
||||
)}
|
||||
>
|
||||
{/* The rail animates its width; the content does not. Expanded, this
|
||||
@@ -656,7 +664,7 @@ export function SidebarNav() {
|
||||
className={cn(
|
||||
"border-sidebar-border",
|
||||
SHELL_HEADER_BAND,
|
||||
collapsed ? "px-3" : "px-6",
|
||||
RAIL_BAND_INSET.header[collapsed ? "collapsed" : "expanded"],
|
||||
)}
|
||||
>
|
||||
<Link
|
||||
@@ -755,7 +763,7 @@ export function SidebarNav() {
|
||||
className={cn(
|
||||
RAIL_LIST_GAP,
|
||||
"pb-1",
|
||||
collapsed ? "px-1.5" : "px-3",
|
||||
RAIL_BAND_INSET.footer[collapsed ? "collapsed" : "expanded"],
|
||||
navOverflows && "border-sidebar-border border-t pt-1",
|
||||
)}
|
||||
>
|
||||
|
||||
Reference in new issue
Block a user