diff --git a/ideas.md b/ideas.md new file mode 100644 index 0000000..0e83f65 --- /dev/null +++ b/ideas.md @@ -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.