mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
feat(lists): read and show the list cap on every capped list
Twelve routes answer a capped list with meta.truncated / meta.limit on the envelope, and the web client read only data, so no surface said a list had stopped short. apiGetList / apiPostList are now the one client reader of the cap and ListCapNotice the one line that shows it; every capped list on the web reads its cap and shows the notice under the list, the paged readers (side effects, illness episodes) stop on the route's answer instead of a full-page guess, and the ECG list moves its five readers onto one hook. The record activity feed sent its cap twice, in data and on the envelope; it rides the envelope alone now. A guard derives the capped routes from the tree and binds each to a client that reads its cap.
This commit is contained in:
1 parent
be79603d53
commit
dc5120b59a
49 files changed
+574
-204
No files matched your search
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { renderToStaticMarkup } from "react-dom/server";
|
||||
|
||||
import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { readListCap } from "@/lib/api/list-cap-shape";
|
||||
|
||||
import { ListCapNotice } from "../list-cap-notice";
|
||||
|
||||
const render = (node: React.ReactNode) =>
|
||||
renderToStaticMarkup(<I18nProvider initialLocale="en">{node}</I18nProvider>);
|
||||
|
||||
describe("ListCapNotice", () => {
|
||||
it("says the list stopped at its ceiling, with the number", () => {
|
||||
const html = render(<ListCapNotice cap={{ truncated: true, limit: 50 }} />);
|
||||
expect(html).toContain('data-slot="list-cap-notice"');
|
||||
expect(html).toContain("Showing the latest 50");
|
||||
});
|
||||
|
||||
it("renders nothing for a complete list or no cap", () => {
|
||||
expect(
|
||||
render(<ListCapNotice cap={{ truncated: false, limit: 50 }} />),
|
||||
).toBe("");
|
||||
expect(render(<ListCapNotice cap={null} />)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
describe("readListCap", () => {
|
||||
it("reads the envelope's cap and nothing else", () => {
|
||||
expect(
|
||||
readListCap({ truncated: true, limit: 100, requestId: "r" }),
|
||||
).toEqual({ truncated: true, limit: 100 });
|
||||
expect(readListCap({ aggregate: {} })).toBeNull();
|
||||
expect(readListCap({ truncated: "yes", limit: 1 })).toBeNull();
|
||||
expect(readListCap(undefined)).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
"use client";
|
||||
|
||||
import type { ListCap } from "@/lib/api/list-cap-shape";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
/**
|
||||
* The line under a list the server cut at its ceiling. Renders nothing for a
|
||||
* complete list, so a surface passes the cap it read (`apiGetList`) and
|
||||
* never decides on its own whether to show it. Meta tier: one muted sentence
|
||||
* at the `text-xs` floor.
|
||||
*/
|
||||
export function ListCapNotice({
|
||||
cap,
|
||||
className,
|
||||
}: {
|
||||
cap: ListCap | null | undefined;
|
||||
className?: string;
|
||||
}) {
|
||||
const { t } = useTranslations();
|
||||
if (!cap?.truncated) return null;
|
||||
return (
|
||||
<p
|
||||
data-slot="list-cap-notice"
|
||||
className={cn("text-muted-foreground text-xs", className)}
|
||||
>
|
||||
{t("common.listCapped", { count: String(cap.limit) })}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user