Vacation rescue/wrap in allocation_service, new plan models/endpoints,
VacationRescueTip, useRescueDayActions, vacationSwaps util, sv+en i18n,
Dashboard/Calendar wiring.
Conflicts came from main's "day assertions" restructure and the earlier
merges; both intents kept:
- plan.py: both import sets; main's sync-with-wish handler kept next to the
branch's new /rescue-days/dismiss endpoint; HTTP_422_UNPROCESSABLE_CONTENT
applied to the branch's code (and a survivor in auth.py).
- Accepting a rescue day now goes through POST /api/day-assertions (an
ad-hoc 'vacation' assertion, no wish) instead of /plan/apply with an
`is_pinned` flag that no longer exists: on main a pin IS a day assertion
(see PlanningService.add_planned_day's docstring), and an assertion is
what keeps a later sync from retyping the day. useRescueDayActions,
its tests, VacationRescueTip's tests and the backend integration flow
follow that contract; applyPlan stays as a client for the live endpoint
minus the dropped field. The hook also gets the security branch's
useLatestRequest guard with an unconditionally cleared busy flag.
- Calendar.tsx: `locale` threaded into main's applyWishSyncResult call of
buildSyncToastMessage (the branch's new parameter); the branch's
swap-visibility toast tests drive the preset path through syncWithWish,
which is how main saves a wish now.
- types/api.ts: ProposedDayApplyItem kept (used by applyPlan) without
isPinned; the Add/UpdatePlannedDayRequest types stay removed as on main.
- fixtures/plan.ts: ResourceShortage import dropped (type removed on main).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T4wjgbVL4HAC5Rdaw75VbV
The planner knew exactly how many semesterdagar a parent was about to
forfeit and never placed a single day to prevent it. It also had no
weekend-wrap logic at all: REASON_VACATION_WRAP_WEEKEND was stamped on
every tier-3 interior vacation day, including a lone Tuesday in February.
Two changes.
Spend better. A workday adjacent to a free stretch, or in the middle of a
gap of three or fewer fenced by free days, now takes vacation --
_vacation_bridge_reason, consulted for every parent type. This departs
from spec 6.1's "wrapping suppressed for hourly": the FP-preservation
argument is salary-type independent, only the pay argument was hourly-moot.
In a both-parents window, both now take vacation on such a date rather
than one paying with FP. After funding, reallocate_vacation_by_value swaps
vacation onto higher-ranked dates 1-for-1 with FP days, so which dates hold
vacation improves while how many never drops -- greedy first, re-timed
after, never withheld. An invariant test asserts the count can only rise.
Name the losses. A new pass proposes concrete dates for vacation that would
otherwise expire, current year only, ranked red-day bridge then weekend
bridge then interior, later dates first. They ride on
PlanProposal.rescue_days, kept off `days` so nothing downstream commits
them, and surface on the Dashboard with accept-one, accept-all (one
batched /api/plan/apply, not N) and dismiss. Dismissal reuses the
tip_dismissal table, keyed on (parent, year) identity rather than the
group's dates -- those are recomputed from the whole plan's residual on
every propose, so date-hashing made dismissal self-defeating. Dismiss does
not bump the plan version; it changes no data and must not trigger a write.
Measured on the seeded demo: 12 forfeiting days for one parent now yield 12
proposed dates, all Fridays or Mondays. Total vacation placed is unchanged
at 203 days, confirming the reallocation only re-times.
Three defects here were found by driving a browser, while 2273 unit tests
passed -- all three lived in the seam between layers that unit tests mock
away. Adds API-level tests over the real request sequence. Rate limiting is
now off in tests (one file consumed the whole registration budget) with the
suite's first real 429 assertion.
PROPOSE_VS_SYNC_BUG.md documents a pre-existing, unfixed issue found along
the way: Dashboard figures swing on the first sync with no user action.
Backend 946 tests, frontend 1341, coverage above all gates.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YZaX6xbuMEMXuyKA1fq2Mm