Files
F-r-ldraledigplannerare/ideas.md
T
jacobh2 e4719cb9ae Rework balances around per-parent days left and deduce the partner from FK
Manual entry now takes one parent's sjukpenningnivå, lägstanivå and
dubbeldagar left (plus optional reserved days) and derives the rest in
build_snapshot, shared with the scraper. The scraper also reads the
family panel and exposes a partner deduction; with two parents it is
saved as a 'deduced' snapshot (migration 002). Parent count is the single
source of entitlement: one member gets 480 days and 96 savable at age 4,
two get 240 and 48 each. Workspaces are capped at two parents.

Also lands the in-flight squash of migrations 002–011 into the initial
schema and the removal of planning_mode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 12:27:29 +02:00

6.7 KiB

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.

Update (2026-09-19): Addressed. One member = sole parent (480 days, 96 savable at age 4); two members = 240 each (48 savable at age 4 per parent). Entitlement follows parent count, and onboarding asks explicitly ("Bara jag" / "Vi är två föräldrar").

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.

Update (2026-09-19): Step 3 now presents per-parent cards (one or two, depending on household size) with a "Klar" (Done) button that is always enabled, letting the user advance without entering data first. Skip copy was corrected.

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.