mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
docs: publish the UI standards in docs/contributing
The binding UI rulebook (card anatomy, spacing scale, text colour, tokens, primitive mapping, enforcement) lived outside the repository, so a contributor could follow a code comment to a file they could not open. It now lives at docs/contributing/ui-standards.md, every comment that cited it points there, and the docs index and ui-guidelines.md say which document wins when they disagree.
This commit is contained in:
1 parent
07f9615dba
commit
d44af06467
46 files changed
+244
-55
No files matched your search
@@ -457,7 +457,7 @@ describe("the refusal stays on screen, addressed to its own row", () => {
|
||||
const alert = /<[^>]*data-slot="grant-action-error"[^>]*>/.exec(html)?.[0];
|
||||
|
||||
expect(alert).toBeDefined();
|
||||
// A refusal somebody has to act on is content. UI-STANDARDS §3 reserves
|
||||
// A refusal somebody has to act on is content. ui-standards.md §3 reserves
|
||||
// muted for meta, and a sentence nobody reads is a sentence nobody acts
|
||||
// on.
|
||||
expect(alert).toContain("text-destructive");
|
||||
|
||||
@@ -318,7 +318,7 @@ export default function AchievementsPage() {
|
||||
}
|
||||
|
||||
// A failed load must never fall through to the zeroed summary tiles + the
|
||||
// "none unlocked yet" empty grid (UI-STANDARDS §6). Surface an honest
|
||||
// "none unlocked yet" empty grid (ui-standards.md §6). Surface an honest
|
||||
// retry instead.
|
||||
if (isError) {
|
||||
return (
|
||||
|
||||
+1
-1
@@ -135,7 +135,7 @@
|
||||
/* The one sanctioned step below `text-xs` (12px). Minted so badges,
|
||||
tag chips, and tabular-nums micro-labels stop hand-rolling
|
||||
`text-[0.6875rem]` / `text-[0.65rem]` arbitrary values that drift by a
|
||||
third of a pixel per site. UI-STANDARDS §5 pins the usage: never for
|
||||
third of a pixel per site. ui-standards.md §5 pins the usage: never for
|
||||
muted body or meta text (the muted floor stays `text-xs`) — only for
|
||||
chips, badges, and tabular micro-labels with verified contrast. */
|
||||
--text-2xs: 0.6875rem;
|
||||
|
||||
@@ -98,7 +98,7 @@ export default function InsightsWorkoutDetailPage({
|
||||
<Skeleton className="h-60 w-full rounded-lg" />
|
||||
</div>
|
||||
) : error ? (
|
||||
// A failed query must never read as "no data" (UI-STANDARDS §6).
|
||||
// A failed query must never read as "no data" (ui-standards.md §6).
|
||||
<QueryErrorCard error={error} onRetry={() => refetch()} />
|
||||
) : !data ? (
|
||||
<p
|
||||
|
||||
@@ -29,7 +29,7 @@ import { RichChartTooltip, type RichTooltipRow } from "./chart-tooltip";
|
||||
* v1.29 — 30-day daily bar chart for the nutrients hydration / caffeine
|
||||
* cards. One bar per day (dense series — 0 for an unlogged day, summed
|
||||
* across sources), an optional thin dashed EFSA reference line, no
|
||||
* attainment colouring (UI-STANDARDS: value stays foreground, the bar +
|
||||
* attainment colouring (ui-standards.md: value stays foreground, the bar +
|
||||
* reference line ARE the context). Registered in
|
||||
* `src/components/charts/chart-runtime.ts` — never import recharts
|
||||
* directly at the call site.
|
||||
|
||||
@@ -539,7 +539,7 @@ export function TodayHero({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Meta row — the hero's quiet tier (UI-STANDARDS §text: `text-xs
|
||||
{/* Meta row — the hero's quiet tier (ui-standards.md §3: `text-xs
|
||||
text-muted-foreground` is the meta floor; never an accent, never an
|
||||
opacity modifier). Freshness note (plan §2.4) — provisional day,
|
||||
last night's sleep not yet folded in. Muted, non-blocking,
|
||||
|
||||
@@ -73,7 +73,7 @@ import { useDayValueFormat } from "./use-day-value-format";
|
||||
* to look from, and the entry is where it is edited.
|
||||
*/
|
||||
|
||||
/** Section label, UI-STANDARDS §5. */
|
||||
/** Section label, ui-standards.md §5. */
|
||||
export const DAY_SECTION_LABEL =
|
||||
"text-muted-foreground text-xs font-medium tracking-wide uppercase";
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ describe("<HeroStrip>", () => {
|
||||
/data-slot="insights-hero-strip-greeting"[^>]*class="([^"]*)"/,
|
||||
);
|
||||
expect(greeting).not.toBeNull();
|
||||
// App H1 weight is bold (UI-STANDARDS §5) — never font-semibold.
|
||||
// App H1 weight is bold (ui-standards.md §5) — never font-semibold.
|
||||
expect(greeting![1]).toContain("font-bold");
|
||||
expect(greeting![1]).not.toContain("font-semibold");
|
||||
// No off-scale arbitrary type sizes (§5) — round to the neighbouring step.
|
||||
|
||||
@@ -65,7 +65,7 @@ describe("<SleepStageStackedBar>", () => {
|
||||
expect(html).toContain("Stage composition");
|
||||
expect(html).toContain("Last 23 nights");
|
||||
// The title routes through the canonical tile header rather than a
|
||||
// hand-rolled title row (UI-STANDARDS §1 / §8).
|
||||
// hand-rolled title row (ui-standards.md §1 / §8).
|
||||
expect(html).toContain('data-slot="tile-header"');
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ describe("Coach unverified-figures notice", () => {
|
||||
/>,
|
||||
);
|
||||
expect(html).toContain('data-slot="coach-unverified-notice"');
|
||||
// Muted meta treatment (UI-STANDARDS §3) — never a warning colour.
|
||||
// Muted meta treatment (ui-standards.md §3) — never a warning colour.
|
||||
expect(html).toContain("text-muted-foreground");
|
||||
});
|
||||
|
||||
|
||||
@@ -520,7 +520,7 @@ function ChatBubbleImpl({
|
||||
// #781 — the persisted cancelled-turn marker (an EMPTY assistant row the
|
||||
// server writes when the client aborts mid-generation, e.g. navigating
|
||||
// away). Rendered as a quiet interrupted note — muted meta text per
|
||||
// UI-STANDARDS §3, never an alarm colour: nothing failed, the user left —
|
||||
// ui-standards.md §3, never an alarm colour: nothing failed, the user left —
|
||||
// plus a retry that resubmits the question that produced the turn (the
|
||||
// regenerate closure, so the resend runs through the normal send path and
|
||||
// every budget/rate gate). No provenance, feedback, copy, or token footer:
|
||||
@@ -700,7 +700,7 @@ function ChatBubbleImpl({
|
||||
<p className="text-warning text-xs">{safeError}</p>
|
||||
)}
|
||||
{/* v1.32.14 — quiet grounding caveat when the guard withheld a figure.
|
||||
Muted meta (UI-STANDARDS §3), never a warning colour — it is a
|
||||
Muted meta (ui-standards.md §3), never a warning colour — it is a
|
||||
grounding caveat, not an error. Static single line, decorative icon,
|
||||
no interactivity. The `[…]` marks in the prose above point here. */}
|
||||
{showUnverifiedNotice && (
|
||||
|
||||
@@ -52,7 +52,7 @@ export function EcgDetail({ recordingId }: { recordingId: string }) {
|
||||
});
|
||||
|
||||
if (isError) {
|
||||
// A failed read must never read as "no data" (UI-STANDARDS §6), and the
|
||||
// A failed read must never read as "no data" (ui-standards.md §6), and the
|
||||
// alert is content, not muted meta — it also has to be recoverable.
|
||||
return (
|
||||
<QueryErrorCard
|
||||
|
||||
@@ -164,7 +164,7 @@ export function HeroStrip({
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
{/* Header glyph stays in the foreground tier — UI-STANDARDS §1
|
||||
{/* Header glyph stays in the foreground tier — ui-standards.md §1
|
||||
reserves primary/accent for actions, not decorative header
|
||||
icons. */}
|
||||
<Sparkles
|
||||
|
||||
@@ -24,7 +24,7 @@ import { cn } from "@/lib/utils";
|
||||
*
|
||||
* RSC-safe: no hooks, no browser API.
|
||||
*
|
||||
* @see .planning/audits/2026-07-05-fable/UI-STANDARDS.md §1
|
||||
* @see docs/contributing/ui-standards.md §1
|
||||
*/
|
||||
export function InsightSectionCard({
|
||||
children,
|
||||
|
||||
@@ -71,7 +71,7 @@ const AXIS_TICKS = [0, 360, 720, 1080, 1440];
|
||||
* line discloses the coverage so the shape doesn't read as more continuous
|
||||
* than it is.
|
||||
*
|
||||
* v1.29.x — a day navigator (`TileHeader`'s `right` slot, per UI-STANDARDS
|
||||
* v1.29.x — a day navigator (`TileHeader`'s `right` slot, per ui-standards.md
|
||||
* §11) pages the same route backward through prior days; "next" is disabled
|
||||
* at today, so the view can never step into the future. A day outside the
|
||||
* dense retention window (`DENSE_INTRADAY_RETENTION_DAYS`) reads back at the
|
||||
|
||||
@@ -99,7 +99,7 @@ export function LabsChangesCard({
|
||||
</div>
|
||||
{/* The delta is CONTENT — the number the row exists to report —
|
||||
so it takes the analyte's tier, not the meta tier of the
|
||||
line under it (UI-STANDARDS §3). At `text-xs` muted it wore
|
||||
line under it (ui-standards.md §3). At `text-xs` muted it wore
|
||||
the sub-line's typography and optically attached itself to
|
||||
that lower line, which is what made a row centred to the
|
||||
pixel read as bottom-aligned. `self-center` states the row's
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
* strictly association-framed, never a verdict. Each row links through to the
|
||||
* medication's Wirkung tab for the full view.
|
||||
*
|
||||
* Card anatomy per UI-STANDARDS §8: TileHeader + the inherited `px-4 md:px-6`
|
||||
* Card anatomy per ui-standards.md §8: TileHeader + the inherited `px-4 md:px-6`
|
||||
* inset (never re-declared), foreground for content, muted for meta.
|
||||
*/
|
||||
import Link from "next/link";
|
||||
|
||||
@@ -22,7 +22,7 @@ const WINDOW_DAYS = 30;
|
||||
* reference-intake meta line (omitted when the profile has no sex on
|
||||
* file — the catalog's own contract), a 30-day bar chart with a dashed
|
||||
* reference line. No ring, no attainment colour — the bar chart with a
|
||||
* reference line IS the context (UI-STANDARDS: value stays foreground,
|
||||
* reference line IS the context (ui-standards.md: value stays foreground,
|
||||
* never coloured by attainment). Display only: water arrives by sync
|
||||
* (Apple Health etc.), there is no in-app water entry here.
|
||||
*/
|
||||
|
||||
@@ -489,7 +489,7 @@ export function AuthShell({
|
||||
// `overflow-y-auto` never clips them: their static position sits
|
||||
// wherever the flow put them and extends the DOCUMENT's
|
||||
// scrollable overflow instead, painting a second vertical scroll
|
||||
// surface beside `<main>` (UI-STANDARDS §9 one-scroll-floor).
|
||||
// surface beside `<main>` (ui-standards.md §9 one-scroll-floor).
|
||||
// The class is not hypothetical and not confined to app code. A
|
||||
// Radix `<Switch>` outside a `<form>` renders a hidden
|
||||
// `position:absolute` bubble input on the first pass and drops it
|
||||
|
||||
@@ -274,7 +274,7 @@ export const NAV_DESTINATIONS: ReadonlyArray<NavDestination> = [
|
||||
*
|
||||
* Settings and Notifications are account utilities, not feature destinations:
|
||||
* on desktop they live in the sidebar footer + avatar menu, on mobile in the
|
||||
* top-bar user menu (UI-STANDARDS §10; the More hub carries features only).
|
||||
* top-bar user menu (ui-standards.md §10; the More hub carries features only).
|
||||
* They used to be a second hand-curated list on each bar — the exact drift the
|
||||
* one-model contract above set out to kill, just pushed down a level. This
|
||||
* list is the single source both bars consume, so the two surfaces can no
|
||||
|
||||
@@ -33,7 +33,7 @@ import { accountLabel } from "@/lib/sharing/account-access-view";
|
||||
* dark for the muted token at this text size, while the foreground token keeps
|
||||
* the context legible at the same contrast floor as the name.
|
||||
*
|
||||
* The row is the inline-action shape from UI-STANDARDS §11 rather than a
|
||||
* The row is the inline-action shape from ui-standards.md §11 rather than a
|
||||
* centred wrapping stack: sentence `min-w-0 flex-1`, action `shrink-0`, so the
|
||||
* way out stays beside the sentence instead of dropping onto its own line once
|
||||
* the name is long. `<MaintainershipBanner>` is the sibling that already had an
|
||||
|
||||
@@ -151,7 +151,7 @@ export function MedicationOrderEditor({
|
||||
// `relative` anchors the sr-only drag-hint paragraph: `sr-only` is
|
||||
// absolutely positioned, and without a positioned ancestor its 1px box
|
||||
// lands below the list in DOCUMENT coordinates and makes the document
|
||||
// itself scrollable (second scrollbar — UI-STANDARDS §9 one-scroll-floor).
|
||||
// itself scrollable (second scrollbar — ui-standards.md §9 one-scroll-floor).
|
||||
<div className="relative space-y-4" data-slot="medication-order-editor">
|
||||
{medications.length === 0 && (
|
||||
<EmptyState
|
||||
|
||||
@@ -110,7 +110,7 @@ export function ModuleOrderEditor({ items, onChange }: ModuleOrderEditorProps) {
|
||||
// `relative` anchors the sr-only drag-hint paragraph: `sr-only` is
|
||||
// absolutely positioned, and without a positioned ancestor its 1px box
|
||||
// lands below the list in DOCUMENT coordinates and makes the document
|
||||
// itself scrollable (second scrollbar — UI-STANDARDS §9 one-scroll-floor).
|
||||
// itself scrollable (second scrollbar — ui-standards.md §9 one-scroll-floor).
|
||||
<div className="relative space-y-2" data-slot="module-order-editor">
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
|
||||
@@ -224,7 +224,7 @@ export function TagGroupsCard({ catalog }: TagGroupsCardProps) {
|
||||
// `relative` anchors the sr-only drag-hint paragraph: `sr-only` is
|
||||
// absolutely positioned, and without a positioned ancestor its 1px box
|
||||
// lands below the list in DOCUMENT coordinates and makes the document
|
||||
// itself scrollable (second scrollbar — UI-STANDARDS §9 one-scroll-floor).
|
||||
// itself scrollable (second scrollbar — ui-standards.md §9 one-scroll-floor).
|
||||
<div className="relative space-y-3" data-slot="mood-tag-groups-card">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-muted-foreground min-w-0 text-xs">
|
||||
|
||||
@@ -296,7 +296,7 @@ export function TagManagerCard({ catalog }: TagManagerCardProps) {
|
||||
// `relative` anchors the sr-only drag-hint paragraph: `sr-only` is
|
||||
// absolutely positioned, and without a positioned ancestor its 1px box
|
||||
// lands below the list in DOCUMENT coordinates and makes the document
|
||||
// itself scrollable (second scrollbar — UI-STANDARDS §9 one-scroll-floor).
|
||||
// itself scrollable (second scrollbar — ui-standards.md §9 one-scroll-floor).
|
||||
<div className="relative space-y-4" data-slot="mood-tag-manager-card">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-muted-foreground min-w-0 text-xs">
|
||||
|
||||
@@ -13,7 +13,7 @@ import { useEffect, useRef, type ReactNode } from "react";
|
||||
* put it — the heading is at the top of the column anyway.
|
||||
*
|
||||
* Semibold rather than the app-wide bold PageHeader H1: the onboarding hero
|
||||
* is the documented exception (UI-STANDARDS §5). Do not sweep to font-bold.
|
||||
* is the documented exception (ui-standards.md §5). Do not sweep to font-bold.
|
||||
*/
|
||||
export function StepHeading({
|
||||
id,
|
||||
|
||||
@@ -404,7 +404,7 @@ describe("how a field says what is wrong with it", () => {
|
||||
// Announced, because it MOUNTS with the role rather than a class being
|
||||
// swapped onto a node that was already there.
|
||||
expect(alert).toContain('role="alert"');
|
||||
// Content, not meta. UI-STANDARDS §3, and the same reasoning
|
||||
// Content, not meta. ui-standards.md §3, and the same reasoning
|
||||
// `grant-action-error.tsx` spells out: a refusal rendered as muted is a
|
||||
// refusal nobody reads.
|
||||
expect(alert).toContain("text-destructive");
|
||||
@@ -521,7 +521,7 @@ describe("ending a managed profile", () => {
|
||||
authRef.value = { accounts: [entry()], active: null };
|
||||
const html = renderGatedSection();
|
||||
expect(html).toContain('data-slot="managed-profile-delete"');
|
||||
// The mobile tap floor UI-STANDARDS §11 asks of an inline action.
|
||||
// The mobile tap floor ui-standards.md §11 asks of an inline action.
|
||||
expect(html).toMatch(
|
||||
/data-slot="managed-profile-delete"[^>]*class="[^"]*min-h-11/,
|
||||
);
|
||||
@@ -629,7 +629,7 @@ describe("ending a managed profile", () => {
|
||||
const html = renderGatedSection();
|
||||
expect(html).toContain('data-slot="query-error-card"');
|
||||
expect(html).toContain("Could not load who looks after this record");
|
||||
// And the way back is reachable, which is what UI-STANDARDS §6 asks of an
|
||||
// And the way back is reachable, which is what ui-standards.md §6 asks of an
|
||||
// error state.
|
||||
expect(html).toContain('data-slot="query-error-retry"');
|
||||
});
|
||||
|
||||
@@ -172,7 +172,7 @@ export function grantActionErrorKey(err: unknown, action: GrantAction): string {
|
||||
* a person who pressed a button and got nothing has already looked away, and
|
||||
* a screen reader that does not announce it is the same silence the panel had
|
||||
* before. `text-destructive` because this is content somebody has to act on —
|
||||
* UI-STANDARDS §3 reserves muted for meta, and a refusal rendered as meta is a
|
||||
* ui-standards.md §3 reserves muted for meta, and a refusal rendered as meta is a
|
||||
* refusal nobody reads.
|
||||
*
|
||||
* `data-grant-error-for` carries the row id so a panel holding two failed rows
|
||||
|
||||
@@ -466,7 +466,7 @@ export function resolveInviteScope({
|
||||
* a button inside a label forwards its own tap to the label's control, so the
|
||||
* radio and the popover would fight for the same click (the same reason
|
||||
* `SectionOption` avoids the wrapping label). Muted, `text-xs` — the meta floor
|
||||
* per UI-STANDARDS §3.
|
||||
* per ui-standards.md §3.
|
||||
*/
|
||||
function AccessOption({
|
||||
level,
|
||||
|
||||
@@ -54,7 +54,7 @@ export function GrantRowItem({
|
||||
/**
|
||||
* Something the person has to read before they act on this row, rendered in
|
||||
* the content column rather than beside the button: consent copy is content
|
||||
* (UI-STANDARDS §3), and a sentence squeezed into the action column would
|
||||
* (ui-standards.md §3), and a sentence squeezed into the action column would
|
||||
* wrap into a stripe nobody reads.
|
||||
*/
|
||||
notice?: ReactNode;
|
||||
|
||||
@@ -92,7 +92,7 @@ export function ManagedProfileCard() {
|
||||
/>
|
||||
<div className="space-y-4">
|
||||
{/* Content, not meta: this is what somebody is consenting to take on,
|
||||
and UI-STANDARDS §3 reserves muted for the incidental. */}
|
||||
and ui-standards.md §3 reserves muted for the incidental. */}
|
||||
<p data-slot="managed-profile-explainer" className="text-sm">
|
||||
{t("recordSharing.managed.explainer")}
|
||||
</p>
|
||||
@@ -159,7 +159,7 @@ function ManagedProfileRow({
|
||||
data-managed-profile-id={profile.accountId}
|
||||
className="space-y-2 py-3 first:pt-0 last:pb-0"
|
||||
>
|
||||
{/* UI-STANDARDS §11 inline action row. */}
|
||||
{/* ui-standards.md §11 inline action row. */}
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
{/* A person's name is user data and stays content. */}
|
||||
@@ -168,7 +168,7 @@ function ManagedProfileRow({
|
||||
{t("recordSharing.lookingAfter.kindManaged")}
|
||||
</p>
|
||||
</div>
|
||||
{/* UI-STANDARDS §11: the actions stay beside the name, never below it.
|
||||
{/* ui-standards.md §11: the actions stay beside the name, never below it.
|
||||
Edit is offered without waiting for the roster — it changes what the
|
||||
record IS and does not depend on how many people look after it,
|
||||
unlike the deletion beside it, whose confirm copy states that
|
||||
@@ -223,7 +223,7 @@ function ManagedProfileRow({
|
||||
{/* A failed roster read is not an absent roster.
|
||||
Without this branch a 500 renders exactly as a still-loading one —
|
||||
the row with no guardian panel and no delete control — and the person
|
||||
waits for something that is never coming. UI-STANDARDS §6: a read
|
||||
waits for something that is never coming. ui-standards.md §6: a read
|
||||
that failed says so and offers the way back. It is deliberately NOT
|
||||
the same as the 404 arm: a 404 means this account is no longer a
|
||||
Guardian of that profile, which is an answer, and the row correctly
|
||||
|
||||
@@ -321,7 +321,7 @@ export function ManagedProfileCreateForm({
|
||||
* Two nodes, never one that swaps. The form used to render a single muted
|
||||
* `<p>` whose TEXT changed from hint to error, which is wrong twice over:
|
||||
*
|
||||
* * a refusal is content somebody has to act on, and UI-STANDARDS §3
|
||||
* * a refusal is content somebody has to act on, and ui-standards.md §3
|
||||
* reserves `text-muted-foreground` for meta. The same reasoning is written
|
||||
* out at `grant-action-error.tsx` — a refusal rendered as meta is a
|
||||
* refusal nobody reads;
|
||||
|
||||
@@ -292,7 +292,7 @@ function ManagedProfileEditFields({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Above the action row, per UI-STANDARDS §12: nothing follows it. */}
|
||||
{/* Above the action row, per ui-standards.md §12: nothing follows it. */}
|
||||
{error && (
|
||||
<p
|
||||
role="alert"
|
||||
|
||||
@@ -151,7 +151,7 @@ export function ManagedProfileGuardians({
|
||||
data-guardian-state={guardian.state}
|
||||
className="space-y-2 py-3 first:pt-0 last:pb-0"
|
||||
>
|
||||
{/* UI-STANDARDS §11 inline action row. */}
|
||||
{/* ui-standards.md §11 inline action row. */}
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">
|
||||
|
||||
@@ -449,7 +449,7 @@ function HandoverLink({
|
||||
</p>
|
||||
<div className="flex flex-col items-center gap-4 sm:flex-row sm:items-start">
|
||||
{/* The QR quiet zone stays white in both themes: a scanner needs the
|
||||
contrast, and UI-STANDARDS §4 names this exemption. */}
|
||||
contrast, and ui-standards.md §4 names this exemption. */}
|
||||
{/* eslint-disable-next-line @next/next/no-img-element -- a data: URL minted client-side; nothing for the image optimiser to do */}
|
||||
<img
|
||||
src={link.qrDataUrl}
|
||||
|
||||
@@ -574,7 +574,7 @@ export function DashboardLayoutSection({ id }: { id: string }) {
|
||||
// then sits at its static position (below this long list) in DOCUMENT
|
||||
// coordinates and silently makes the document itself scrollable, a
|
||||
// second vertical scrollbar next to the shell-owned `<main>` scroll
|
||||
// (UI-STANDARDS §9 one-scroll-floor; guarded by
|
||||
// (ui-standards.md §9 one-scroll-floor; guarded by
|
||||
// e2e/settings-overscroll.spec.ts).
|
||||
<div className="relative space-y-2">
|
||||
{/* v1.4.15 — table-style header naming the two
|
||||
|
||||
@@ -110,7 +110,7 @@ export function ReportScopePicker({
|
||||
return (
|
||||
<div className="space-y-3" data-testid={`report-scope-picker-${surface}`}>
|
||||
{/* The one-click way to a complete report. An inline action row
|
||||
(UI-STANDARDS §11): the sentence wraps, the action stays beside it.
|
||||
(ui-standards.md §11): the sentence wraps, the action stays beside it.
|
||||
It is offered, never applied — and it stays offered so a person can
|
||||
come back to the standard set after experimenting. On a phone the
|
||||
action's own label is wider than half the row, so beside it the
|
||||
|
||||
@@ -518,7 +518,7 @@ export function ShareLinkCreateForm({
|
||||
opens the record (and its shared documents). Promoted as the
|
||||
primary mobile affordance: a clinician scans it on the spot to
|
||||
open the record on their own device. Shown exactly once. The
|
||||
white quiet-zone is the documented QR exemption (UI-STANDARDS §4)
|
||||
white quiet-zone is the documented QR exemption (ui-standards.md §4)
|
||||
so the code stays scannable in the dark theme too. */}
|
||||
{qrDataUrl && (
|
||||
<div
|
||||
|
||||
@@ -17,7 +17,7 @@ import { cn } from "@/lib/utils";
|
||||
* geometry and pushes everything the user came for below the fold — so this
|
||||
* shape stays, promoted to a primitive instead of being copied a third time.
|
||||
*
|
||||
* Pick by surface (UI-STANDARDS §6):
|
||||
* Pick by surface (ui-standards.md §6):
|
||||
* - primary / list surface → `QueryErrorCard`
|
||||
* - dense metric strip or tile where the tall card would dominate →
|
||||
* `QueryErrorRow`
|
||||
|
||||
Reference in new issue
Block a user