refactor: web shared entity components — kill the render forks #145

Merged
nalum merged 7 commits from refactor/web-shared-components into main 2026-08-18 16:43:29 +00:00
Owner

Closes #129, #123 and the web half of #122. PR 7 of the #137 stack, based on #144 — the reference surface decides the component shapes that Android's PR 8 ports. Seven commits, one consolidation each:

  1. deadline.ts — deadlineLabel, isOverdue, byDeadline and the timed predicate in one module; the byte-identical Today/Jobs copies and Calendar's three incompatible due formats (including the lowercase "· due") all collapse onto it. The stale "6:00pm" doc comment dies with the copies.
  2. points.ts (#122 web) — one formatter set (pointsLabel, worthLabel, compactPoints) and one clamped ledger (balanceOf): a balance is what you can spend, and you cannot spend into the red. PointsChip, Rewards and Today now agree — no more "-5 points" on one board and "0" in the header.
  3. repeat.ts (#123) — weekday names from Intl in the active locale, NTH_NAMES once, and both rule sentences (eventRepeatLabel, jobRepeatLabel); Jobs' hardcoded English weekday array is gone.
  4. components/JobCard.tsx — one card for Today and Jobs. Today's fork had dropped the repeat glyph, description, due label and sort, and spelled the worth sentence under its own message id; it now simply consumes the shared card (this is the component work behind #107).
  5. components/MemberSelect.tsx — the mark-option grid once, replacing thirteen hand-rolled copies across seven files (single/multi select via a pressed predicate, optional sentinel, the Me label in one place).
  6. Stragglers — Today speaks event times through the exported timeRange() (all-day events finally say "All day" there), the switch panel's tile edge goes through markColorVar(), one mealLabel() replaces five ?? "Dinner" fallbacks, tonight's dinner strip gets its own --accent class instead of dressing as a reward, and the family tree's SVG mark is annotated as the recorded deliberate exception (same parseMark tokens, different drawing medium).
  7. AGENTS.md — the rule, written down: one rendering per entity per surface.

Verified against the live deploy with a headless-browser walk-through: the Today card now shows description, "Due tomorrow 17:30", the repeat glyph and "Worth 7 points" identically to the Jobs card; the job sheet reads "Every Wednesday" from the Intl path; the calendar agenda row carries the unified "Due 17:30" and the lowercase form is gone; the member filter renders through MemberSelect; the dinner strip no longer wears the rewards class. make check green.

🤖 Generated with Claude Code

Closes #129, #123 and the web half of #122. PR 7 of the #137 stack, based on #144 — the reference surface decides the component shapes that Android's PR 8 ports. Seven commits, one consolidation each: 1. **`deadline.ts`** — `deadlineLabel`, `isOverdue`, `byDeadline` and the timed predicate in one module; the byte-identical Today/Jobs copies and Calendar's three incompatible due formats (including the lowercase "· due") all collapse onto it. The stale "6:00pm" doc comment dies with the copies. 2. **`points.ts`** (#122 web) — one formatter set (`pointsLabel`, `worthLabel`, `compactPoints`) and one **clamped** ledger (`balanceOf`): a balance is what you can spend, and you cannot spend into the red. PointsChip, Rewards and Today now agree — no more "-5 points" on one board and "0" in the header. 3. **`repeat.ts`** (#123) — weekday names from Intl in the active locale, `NTH_NAMES` once, and both rule sentences (`eventRepeatLabel`, `jobRepeatLabel`); Jobs' hardcoded English weekday array is gone. 4. **`components/JobCard.tsx`** — one card for Today and Jobs. Today's fork had dropped the repeat glyph, description, due label and sort, and spelled the worth sentence under its own message id; it now simply consumes the shared card (this is the component work behind #107). 5. **`components/MemberSelect.tsx`** — the mark-option grid once, replacing thirteen hand-rolled copies across seven files (single/multi select via a `pressed` predicate, optional sentinel, the Me label in one place). 6. **Stragglers** — Today speaks event times through the exported `timeRange()` (all-day events finally say "All day" there), the switch panel's tile edge goes through `markColorVar()`, one `mealLabel()` replaces five `?? "Dinner"` fallbacks, tonight's dinner strip gets its own `--accent` class instead of dressing as a reward, and the family tree's SVG mark is annotated as the recorded deliberate exception (same parseMark tokens, different drawing medium). 7. **AGENTS.md** — the rule, written down: one rendering per entity per surface. Verified against the live deploy with a headless-browser walk-through: the Today card now shows description, "Due tomorrow 17:30", the repeat glyph and "Worth 7 points" identically to the Jobs card; the job sheet reads "Every Wednesday" from the Intl path; the calendar agenda row carries the unified "Due 17:30" and the lowercase form is gone; the member filter renders through MemberSelect; the dinner strip no longer wears the rewards class. `make check` green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
deadlineLabel, isOverdue and byDeadline lived as byte-identical
duplicates in Today.tsx and Jobs.tsx, and Calendar carried three more
incompatible due formats of its own ("Due 18:00" twice under separate
ids, a lowercase "· due 18:00" in the job sheet) plus its private
timed-or-not predicate — four formats for one concept (#129 target 3).
One deadline.ts now owns the label, the overdue verdict, the sort and
the timed predicate; every consumer imports it, and the calendar's
three sites speak the one "Due HH:MM" form. The stale "6:00pm" doc
comment goes with the copies.
Four formatters produced the same points strings under different
message ids, and three ledgers disagreed on negatives — PointsChip and
Rewards clamp at zero, Today's strip did not, so a member whose spends
outrun their claims read "-5 points" on one board and "0" in the
header (#122, #129 target 4). points.ts now owns the ledger (clamped:
a balance is what you can spend, and you cannot spend into the red),
the worth sentence and the plain count; every consumer imports it.
A weekly job and a weekly event could disagree on weekday names in a
translated UI: Jobs.tsx hardcoded an English message array while
EventDetail formatted a known week through Intl, and both carried
their own copy of NTH_NAMES (#123). repeat.ts now owns the Intl
weekday names, the nth vocabulary and both rule sentences; Jobs and
EventDetail import it.
The Today and Jobs cards were independent implementations of the same
card, and had drifted: Today's dropped the repeat glyph, the
description, the due label and the sort, always wore my mark instead
of the owner's, and spelled the worth sentence under its own message
id (#129 target 1, the component work behind #107). JobCard now lives
in components/, both boards consume it, and the Today strip simply
passes the me-view flags it needs — any future field lands on both
boards or neither.
Thirteen hand-rolled copies of the mark-option grid differed only in
their sentinel label and pressed-state plumbing (#129 target 4). The
picker is identity infrastructure — members are picked by tapping
their mark — so it now lives once in components/MemberSelect.tsx:
single- and multi-select via a pressed predicate, an optional sentinel
option, and the Me label in one place. Every picker consumes it.
The section-5.1 stragglers (#129): Today told event times through a
local eventTime() instead of the exported timeRange() — the same event
read "14:00" on Today and "14:00 – 15:30" on Calendar, and all-day
events showed nothing; the family tree redrew <Mark> as raw SVG and
the switch panel mixed its own colour lookup past markColorVar(); the
"Dinner" fallback was written five times; and tonight's dinner strip
wore the rewards strip's class. One mealLabel(), one mark path, one
time voice, and a dinner-strip class of its own.
docs: entity renderings share one component per surface
Some checks failed
check / commits (pull_request) Successful in 6s
check / web (pull_request) Successful in 1m33s
android / build (pull_request) Successful in 5m40s
android / report (pull_request) Successful in 4s
check / go (pull_request) Successful in 2m54s
check / report (pull_request) Successful in 4s
android / report (push) Has been cancelled
check / commits (push) Has been cancelled
check / go (push) Has been cancelled
check / report (push) Has been cancelled
check / web (push) Has been cancelled
tag / tag (push) Has been cancelled
android / build (push) Has been cancelled
fd138d1041
The rule the consolidation PRs enforce, written down so it holds (#129
target 6): an entity rendered on more than one board renders through
one exported component (or label helper) per surface — never a second
inline copy that drifts.

Test report

Suite Tests Result Skipped
Unit 1370 ✅ pass 1
Integration 86 ✅ pass —

Coverage: 28.0%

Updated by the check workflow · commit fd138d1041

<!-- ci-test-report --> ## Test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit | 1370 | ✅ pass | 1 | | Integration | 86 | ✅ pass | — | **Coverage:** 28.0% <sub>Updated by the check workflow · commit fd138d104198bd7412ae9b777c936cc8d47f2ed4</sub>

Android test report

Suite Tests Result Skipped
Unit (debug) 31 ✅ pass 0

Coverage: 2.4% of lines

Updated by the android workflow · commit fd138d1041

<!-- android-test-report --> ## Android test report | Suite | Tests | Result | Skipped | | --- | --: | --- | --: | | Unit (debug) | 31 | ✅ pass | 0 | **Coverage:** 2.4% of lines <sub>Updated by the android workflow · commit fd138d104198bd7412ae9b777c936cc8d47f2ed4</sub>
nalum changed target branch from fix/android-defects to main 2026-08-18 16:43:22 +00:00
nalum merged commit fd138d1041 into main 2026-08-18 16:43:29 +00:00
nalum deleted branch refactor/web-shared-components 2026-08-18 16:43:29 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
eagraiclainne/app!145
No description provided.