From 70ddc21610447307e98def1e3b2d65d4aece4767 Mon Sep 17 00:00:00 2001 From: amosblomqvist Date: Thu, 17 Sep 2026 13:48:04 +0300 Subject: [PATCH] Typography round 5 (owner): drop the message count + active-row weight; ISO dates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three declutter moves, all trivially reversible: - The meta line drops the message count (idle meta is now just how long ago). The count informed no decision — clutter. messageCount still rides the API payload, so this is a one-line flip in sessionMeta if it's ever wanted. - The active row is marked by the background alone: weight was a second channel doing the same job, and the flip made selected titles jump. - Dates beyond the week render ISO (YYYY-MM-DD, zero-padded) instead of M/D/YYYY — the US convention swapped for the international standard: unambiguous in any locale, sorts lexically. A user-locale setting can come later — it's a format-function swap. Also dropped the row's vestigial text-sm: both lines carry explicit sizes now (16px title, 11px meta). Live: active weight 400 + bg-accent, metas '17m ago' / 'Waiting for your answer · 1d ago', no counts. Gates: typecheck, lint, 469/469. --- components/sidebar.tsx | 25 +++++++++++++++++-------- tests/render-sidebar.test.tsx | 14 +++++++++----- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/components/sidebar.tsx b/components/sidebar.tsx index c2721aa..521f606 100644 --- a/components/sidebar.tsx +++ b/components/sidebar.tsx @@ -86,7 +86,11 @@ function SessionStatus({ busy }: { busy: boolean }) { /** Compact "how long ago": "just now", "5m ago", "3h ago", "2d ago", * then a plain date. How long a session has been WAITING matters more - * than an absolute timestamp. Pure — exported for tests. */ + * than an absolute timestamp. Beyond a week the date renders ISO + * (YYYY-MM-DD, zero-padded) — the international standard: unambiguous + * in any locale and it sorts (owner, typography round 5; the old + * M/D/YYYY was the US convention. A user-locale setting can come later + * — trivial, a format function swap). Pure — exported for tests. */ export function timeAgo(iso: string, now = new Date()): string { const ms = now.getTime() - new Date(iso).getTime(); if (ms < 60_000) return "just now"; @@ -96,16 +100,21 @@ export function timeAgo(iso: string, now = new Date()): string { if (h < 24) return `${h}h ago`; const d = Math.floor(h / 24); if (d < 7) return `${d}d ago`; - return new Date(iso).toLocaleDateString(); + const date = new Date(iso); + const mm = String(date.getMonth() + 1).padStart(2, "0"); + const dd = String(date.getDate()).padStart(2, "0"); + return `${date.getFullYear()}-${mm}-${dd}`; } -/** The row's second line: state replaces the message count when the state - * is the more useful fact (most resting sessions are awaiting an answer — - * that's what the learner scans for). */ +/** The row's second line: the state when a state exists (waiting/working), + * otherwise just how long ago. The message count is DROPPED (owner, + * typography round 5): it informed no decision — clutter. messageCount + * still rides the API payload, so this is a one-line flip back if it's + * ever wanted. */ function sessionMeta(chat: ChatListItem): string { if (chat.pendingQuestion) return `Waiting for your answer · ${timeAgo(chat.updatedAt)}`; if (chat.busy) return "Teacher is working…"; - return `${chat.messageCount} messages · ${new Date(chat.updatedAt).toLocaleDateString()}`; + return timeAgo(chat.updatedAt); } /** @@ -127,8 +136,8 @@ export function SessionRow({ type="button" onClick={onOpen} className={cn( - "relative mb-0.5 flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left text-sm transition-colors hover:bg-accent", - active && "bg-accent font-medium", + "relative mb-0.5 flex w-full items-center gap-2 rounded-lg px-3 py-2 text-left transition-colors hover:bg-accent", + active && "bg-accent", )} > diff --git a/tests/render-sidebar.test.tsx b/tests/render-sidebar.test.tsx index b8913e3..681d47e 100644 --- a/tests/render-sidebar.test.tsx +++ b/tests/render-sidebar.test.tsx @@ -86,26 +86,30 @@ describe("SessionRow", () => { const working = render(item({ busy: true })); assert.ok(working.includes("Teacher is working…"), "working meta"); + // Owner, typography round 5: the message count is dropped — it + // informed no decision. Idle meta is just how long ago. const idle = render(item({ title: "Partial derivatives", messageCount: 7 })); assert.ok(idle.includes("Partial derivatives")); - assert.match(idle, /7 messages · \d{1,2}\/\d{1,2}\/\d{4}/, "idle keeps the count"); + assert.ok(!idle.includes("messages"), "the count is gone (flip-flop: one line in sessionMeta)"); + assert.match(idle, /\d{4}-\d{2}-\d{2}/, "idle shows the ISO date"); }); - it("active row is visually marked", () => { + it("active row is visually marked by the background alone (owner, round 5: no weight change)", () => { const html = render(item({}), true); - assert.ok(html.includes("font-medium"), "active emphasis"); + assert.ok(html.includes("bg-accent"), "active background"); + assert.ok(!html.includes("font-medium"), "weight is not a second channel doing the same job"); }); }); describe("timeAgo", () => { const now = new Date("2026-01-02T12:00:00.000Z"); - it("just now / minutes / hours / days, then falls back to a date", () => { + it("just now / minutes / hours / days, then the ISO date (owner, round 5)", () => { assert.equal(timeAgo("2026-01-02T11:59:30.000Z", now), "just now"); assert.equal(timeAgo("2026-01-02T11:55:00.000Z", now), "5m ago"); assert.equal(timeAgo("2026-01-02T09:00:00.000Z", now), "3h ago"); assert.equal(timeAgo("2025-12-31T12:00:00.000Z", now), "2d ago"); - assert.match(timeAgo("2025-06-02T12:00:00.000Z", now), /\d/, "beyond a week: a date"); + assert.equal(timeAgo("2025-06-02T12:00:00.000Z", now), "2025-06-02", "beyond a week: ISO — unambiguous, sortable"); }); it("negative drift (clock skew) reads as just now, never a future date", () => {