e4719cb9ae
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>
121 lines
6.7 KiB
Markdown
121 lines
6.7 KiB
Markdown
# 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.
|