Replaces the single-column greeting block with a 1fr / 1fr row. The left
side holds the date label (MONDAY, 11 MAY in tracked uppercase 11px,
swapped from the bullet separator to comma per spec), the serif 36px
greeting line ('Good morning, Jonathan.') with the first name italic,
and a milestone-aware tenure line. The right side (only rendered when
the user has a member_number — i.e. cab members) shows 'MEMBER · NNN'
zero-padded above 'Founding circle' in serif.
tenureMilestone(days) is the new helper that produces the milestone copy
through seven buckets: 0 / 1–6 / 7–20 / 21–55 / 56–180 / 181–364 / 365+.
The 1–6 bucket renders 'Day 2.', 'Day 3.' etc. (day count is days-since-
join; day one is the first 24 hours after joining). The months bucket
uses Math.floor(days/30) with a min of 2 to avoid a one-day-after-21
reading '1 months in.'. Years pluralise normally.
daysSince(iso) — small wrapper that handles SQL date strings ('YYYY-MM-DD
HH:MM:SS' or pure dates) and returns whole-day UTC delta. Same UTC
coercion the rest of the page uses.
Tests: 7 cases for tenureMilestone at the boundary days 0/1/7/22/60/200/
400 (plus an extra 730 to exercise the year plural). 43/43 passing.
This block replaces the in-line <MembershipCard> idea that was floating
around in earlier passes; the dark indigo card stays in src/components
for /members/:slug, but on /pulse the two-line stamp is enough.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
33 lines
1.4 KiB
TypeScript
33 lines
1.4 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { tenureMilestone } from '../src/lib/format.js';
|
|
|
|
describe('tenureMilestone — copy variants by day count', () => {
|
|
it('0 days reads "Day one."', () => {
|
|
expect(tenureMilestone(0)).toBe('Day one. The team is reading every note you leave.');
|
|
});
|
|
|
|
it('1 day reads "Day 2." (off-by-one — day 1 is the first 24h after joining)', () => {
|
|
expect(tenureMilestone(1)).toBe('Day 2. The team is reading every note you leave.');
|
|
});
|
|
|
|
it('7 days enters the "{n} days in" bucket', () => {
|
|
expect(tenureMilestone(7)).toBe('7 days in. The team is reading every note you leave.');
|
|
});
|
|
|
|
it('22 days reads "A few weeks in."', () => {
|
|
expect(tenureMilestone(22)).toBe('A few weeks in. The team is reading every note you leave.');
|
|
});
|
|
|
|
it('60 days reads "{n_months} months in." (months = floor(days/30))', () => {
|
|
expect(tenureMilestone(60)).toBe('2 months in. The team is reading every note you leave.');
|
|
});
|
|
|
|
it('200 days reads "Almost a year in." (switches to "Still" suffix)', () => {
|
|
expect(tenureMilestone(200)).toBe('Almost a year in. Still reading every note you leave.');
|
|
});
|
|
|
|
it('400 days reads "{n_years} year(s) in."', () => {
|
|
expect(tenureMilestone(400)).toBe('1 year in. Still reading every note you leave.');
|
|
expect(tenureMilestone(730)).toBe('2 years in. Still reading every note you leave.');
|
|
});
|
|
});
|