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