Write down what is worth doing next
Eight proposals from the state of the app after the rework, with reasoning, and a shorter list of things that look worth changing but are not — the repetition among the fetch hooks, the two band-builders that share a name but not a problem, and the propose and apply endpoints that remain the testable primitives beneath the combined one. The first entry is the one that matters: a parent with sole custody has no way to say so, and is quietly told they have half the days they are entitled to.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# Ideas
|
||||
|
||||
Opinionated proposals from the state of the app after the calendar rework, roughly ordered by
|
||||
value. Not a backlog — things worth arguing about.
|
||||
|
||||
---
|
||||
|
||||
## 1. Sole custody has no way to exist
|
||||
|
||||
A parent with sole custody is entitled to all 480 days. The app cannot express that: every parent
|
||||
row defaults to half the child's total, and `planning_mode` (`solo` | `couple`) is an *app-usage*
|
||||
flag — whether you are coordinating two calendars — not a custody flag. So a sole-custody parent
|
||||
and someone who simply has not added their co-parent yet are byte-identical in the data, and both
|
||||
are told they have 240 days.
|
||||
|
||||
`config.py`'s module docstring already flags single custody as deliberately unbuilt. It needs its
|
||||
own field, never a reuse of `planning_mode`, and the rule for what it grants needs confirming
|
||||
against Försäkringskassan before a number is written down.
|
||||
|
||||
**Why it matters:** the app silently halves a real user's entitlement.
|
||||
|
||||
## 2. Multi-child is half-built
|
||||
|
||||
The tips engine now runs per child. The rest does not really. `TipsContext` was single-child until
|
||||
a fix two commits ago; the dashboard shows one child at a time via a header selector; the age-4
|
||||
maths is per `(parent, child)` but nothing shows two children's deadlines side by side.
|
||||
|
||||
A second child is the common case for this product's users, and their windows overlap in ways worth
|
||||
seeing together — days spent on one child are days not spent on the other, and two fourth
|
||||
birthdays mean two cliffs.
|
||||
|
||||
**Suggestion:** decide whether multi-child is a supported scenario. If yes, the timeline on
|
||||
Översikt is the natural place to show both; if no, say so in the UI rather than half-supporting it.
|
||||
|
||||
## 3. The planner cannot explain itself
|
||||
|
||||
Every funded day already carries a `reason` code (`vacation_wrap_red_day`, `fp_interior`,
|
||||
`swap_first_half`, `dubbeldag`, …) and every window produces a `WindowOutcome` with a cost
|
||||
breakdown and a strategy. **None of it reaches the user.** The calendar shows what was decided, not
|
||||
why.
|
||||
|
||||
"Why is this a vacation day and not a parental-leave day?" is the question this product exists to
|
||||
answer, and the data is already on the wire.
|
||||
|
||||
**Suggestion:** surface the reason in the day popover — one line, plain Swedish. Cheapest
|
||||
high-value change on this list.
|
||||
|
||||
## 4. Flexible periods can move without warning
|
||||
|
||||
A flexible wish is re-placed on every sync, and sync now runs on every edit. A summer period can
|
||||
therefore shift because you edited something in December. The change toast reports it, but a toast
|
||||
is ephemeral and the user may not connect cause to effect.
|
||||
|
||||
**Suggestion:** mark a flexible period visually as "placed by the planner, may move", and offer a
|
||||
one-click pin to fix it where it is. The machinery exists — `is_pinned` already protects days.
|
||||
|
||||
## 5. Dismissed tips could hide a growing problem
|
||||
|
||||
Dismissals expire when the tip's `data` changes, which is the right instinct. But `preserve_for_age_4`
|
||||
is a warning whose numbers change slowly and monotonically — dismiss it in year two and it may not
|
||||
resurface until the figure has moved materially, by which point less can be done.
|
||||
|
||||
**Suggestion:** exempt the age-4 warnings from dismissal entirely, or make them re-surface on a
|
||||
calendar cadence rather than only on data change.
|
||||
|
||||
## 6. Onboarding asks for the hardest thing last
|
||||
|
||||
The balances step asks a new user to fetch or type Försäkringskassan figures before they have seen
|
||||
the app do anything. The skip path exists and is labelled as estimated, which is good — but the
|
||||
ordering still front-loads the friction.
|
||||
|
||||
**Suggestion:** consider letting the user reach a working calendar first and prompting for real
|
||||
balances once they have a plan worth making accurate. Worth an A/B if this ever gets real users.
|
||||
|
||||
## 7. Vacation is modelled more thinly than parental leave
|
||||
|
||||
Parental-leave days have tiers, reserved portions, transferability, dubbeldagar and an age-4
|
||||
ceiling. Vacation has `total_days` and `saved_days_in` per year, plus a statutory floor on what
|
||||
must be taken. Real Swedish vacation has more shape than that — collectively agreed extra days,
|
||||
employer rules about carry-over beyond the statutory five years, and the distinction between
|
||||
earned and advance days.
|
||||
|
||||
Not obviously worth building. Worth knowing that vacation is the weaker half of the model, given
|
||||
the app's premise is optimising the two *together*.
|
||||
|
||||
## 8. Smaller things
|
||||
|
||||
- **`ParentFacts.name` is dead.** Carried through the allocator, never read. Removing it churns
|
||||
fixtures across 580+ tests, which is why it survived a cleanup sweep. Do it opportunistically.
|
||||
- **`isCreatingPeriod` has no test.** It gates the preset rail's busy state and drag re-entry, and
|
||||
is only incidentally exercised. A merge with `syncHook.isSyncing` looks right but has no safety
|
||||
net, so it was left alone.
|
||||
- **`ProposedDayApplyItem` and `makeProposedDayApplyItem`** are dead on the frontend — leftovers of
|
||||
the removed direct-apply path.
|
||||
- **E2E is flaky in parallel.** All specs share one demo workspace and race on auto-sync. CI pins
|
||||
`workers: 1`, so this only bites locally. Fixing it properly means per-spec workspaces.
|
||||
- **Promote the E2E job to blocking** once it has a run of green builds. It is deliberately
|
||||
non-blocking today.
|
||||
- **`total_days_per_child` is now computed**, which forecloses an env override for multiples. The
|
||||
assumption is that Swedish multiples rules add days at the sjukpenning tier, so the derivation
|
||||
still holds — worth confirming before anyone builds twins support.
|
||||
|
||||
---
|
||||
|
||||
## Deliberately not proposed
|
||||
|
||||
- **Replacing the hand-rolled fetch hooks** with a generic factory or a query library. The ~14
|
||||
hooks look repetitive but differ in load-bearing ways (background-refresh flags, window
|
||||
parameters, differing refetch semantics). A generic version would need enough escape hatches to
|
||||
cost more than it saves.
|
||||
- **Unifying the two band-building functions** (`CalendarWeek.computeBandSegments`,
|
||||
`YearTimeline.buildBands`). Same word, different problems: one groups by wish identity with
|
||||
gap-bridging and lane assignment at week granularity, the other by day-type majority at year
|
||||
granularity.
|
||||
- **Collapsing `/propose` and `/apply` into `/sync`.** They are the pure, independently testable
|
||||
primitives underneath it. Keeping them is what makes the allocator exercisable without a database.
|
||||
Reference in New Issue
Block a user