Calendar branch — merge plan & change log
This branch (calendar) builds the portal Planner (scheduling + calendar +
drag-and-drop). Most of it is additive and portal-only, but several changes
promote tracking code into the shared packages or change shared behaviour,
which is where merge conflicts will appear when calendar lands in dev/main.
How to use this doc: when a merge surfaces a conflict in one of the files below, find it here to see what calendar did and why, then integrate the other branch's intent into the new location/shape rather than reverting.
Scope note: some promotions (the workout-builder move) predate the planner work but live on this branch, so they're documented here too — they are the highest-risk part of the merge.
0. TL;DR — the only things that can conflict
- Workout-builder promotion (§1) — tracking files moved to
@enode/ui/workout-builder/*and@enode/core/workouts/*. Git tracks them as renames (96–100%). If another branch edited an original, you get a rename/modify (or modify/delete) conflict → re-apply their edit at the new path, keep the deletion of the tracking original. - Extractions out of
workout-shared.tsx(§2) — utilities/components carved into new shared files.workout-shared.tsxshrank ~300 lines. - Tracking import-repointing (§3) — ~11 tracking files only changed import
paths. Mechanical; keep the
@enode/*paths. - Universal (UTC) time behaviour (§4) —
derive.ts+workouts-today-list. - Additive shared core (§5) — new modules + additive edits to
api/dtos/workouts.tsandapi/workouts.ts.
Everything else (§6) is portal-only and cannot conflict with tracking.
1. Shared-component promotions (MOVES — highest conflict risk)
The workout builder is now shared by the tracking app and the portal's
schedule drawer, so it moved out of apps/tracking/src/app/workouts/today/ into
the shared packages. These are git renames:
| Original (tracking) | New (shared) | Similarity |
|---|---|---|
…/today/supersets.ts | @enode/core/workouts/supersets.ts | 100% |
…/today/ExercisePickerDrawer.tsx | @enode/ui/workout-builder/ExercisePickerDrawer.tsx | 100% |
…/today/SelectActionsCard.tsx | @enode/ui/workout-builder/SelectActionsCard.tsx | 100% |
…/today/SlotCard.tsx | @enode/ui/workout-builder/SlotCard.tsx | 98% |
…/today/SortableSlotCard.tsx | @enode/ui/workout-builder/SortableSlotCard.tsx | 96% |
…/today/SupersetChain.tsx | @enode/ui/workout-builder/SupersetChain.tsx | 100% |
…/today/WorkoutItemDrawer.tsx | @enode/ui/workout-builder/WorkoutItemDrawer.tsx | 100% |
…/today/WorkoutItemTemplatePickerDrawer.tsx | @enode/ui/workout-builder/WorkoutItemTemplatePickerDrawer.tsx | 100% |
…/today/workout-draft-context.tsx | @enode/ui/workout-builder/workout-draft-context.tsx | 100% |
Why: the portal schedule drawer reuses the exact same workout builder (slot cards, item editor, exercise/template pickers, superset logic, the multi-select action pill). Keeping two copies would diverge; promotion makes the portal a first-class consumer.
Conflict resolution: for each, if the incoming branch modified the tracking
original, Git reports a rename/modify or modify/delete. Take their change and
apply it to the shared file (the @enode/* path), then accept the removal of
the tracking original. Confirm the tracking app still imports from the shared
path (see §3).
2. Extractions out of workout-shared.tsx (new shared files)
These were carved out of tracking files (mainly workout-shared.tsx, which
shrank ~303 lines, and SlotCard.tsx) into new shared files:
| New shared file | Extracted from | Symbol(s) |
|---|---|---|
@enode/core/format/measurement.ts | …/today/workout-shared.tsx | formatMeasurementValue |
@enode/ui/athlete-chip.tsx | …/today/workout-shared.tsx | AthleteChip |
@enode/ui/workout-card.tsx | tracking workout list | WorkoutCard (used by workouts-today-list) |
@enode/ui/workout-builder/workout-item-content.tsx | SlotCard.tsx | slot card content / muscle media |
@enode/ui/workout-builder/icons.tsx | builder files | shared builder icons |
Conflict resolution: if a branch edited the moved sections inside
workout-shared.tsx (or SlotCard.tsx), re-apply those edits to the new file
above. The tracking file now only re-imports/keeps what stayed.
3. Tracking files modified — import repointing only (mechanical)
These tracking files changed only their import paths to the promoted code
(behaviour unchanged). Example: set-list.tsx swapped
import { formatMeasurementValue } from "./workout-shared" →
from "@enode/core/format/measurement".
MoveSetDrawer.tsx,TemplatesPickerDrawer.tsx,complete-set-reps-card.tsx,pending-reps-overlay.tsx,performance-chart.tsx,set-list.tsx,WorkoutMultiItemDrawer.tsx,WorkoutEditingFlow.tsx— repoint to@enode/ui/workout-builder/*,@enode/core/workouts/supersets, or@enode/core/format/measurement.TrainingSession.tsx,WorkoutDetailsDrawer.tsx— repoint imports and use the promotedSelectActionsCardfrom@enode/ui/workout-builder.workout-shared.tsx— see §2 (lost the extracted symbols).
Conflict resolution: if the incoming branch also imported a moved symbol,
both sides repoint — keep the @enode/* path. If it edited unrelated parts of
these files, accept both.
4. Behavioural change — universal (UTC) workout time
Scheduled-workout time is universal wall-clock: a workout authored at 09:00
shows at 09:00 for every viewer, on the same calendar day, regardless of
timezone. The single interpretation now lives in @enode/core/workouts/derive.
@enode/core/workouts/derive.ts—workoutMinutesnow reads UTC (getUTCHours/getUTCMinutes, was local); addedworkoutDayKey,formatWorkoutTime, andRECURRING_TIME_TEMPLATE.apps/tracking/…/workouts-today-list.tsx— converged onto the core helpers (workoutMinutes,formatWorkoutTime); previously it computed UTC inline. No behaviour change for tracking — it already rendered workout times in UTC; it just shares the helper now.
Conflict resolution: if a branch changed the time logic in
workouts-today-list.tsx, prefer the core helpers (@enode/core/workouts/derive)
over re-introducing inline date math. The tracking behaviour to preserve is
"render workout time in UTC, not the viewer's local zone."
5. Additive shared core (low risk)
Mostly new files; the two modified shared files are additive.
@enode/core/api/dtos/workouts.ts(+68) — V2 read DTOs (WorkoutReturnDtoV2, items/blocks/normatives) and the schedule-save create DTOs. Additive types.@enode/core/api/workouts.ts(+80) — schedule endpoints (getAllSchedules,getRecurringWorkouts,getTemplates,getWorkout,createSchedule/updateSchedule/deleteSchedule),scheduleEndpoint, and the HTTP-313 shared-definition handling. Additive functions.- New modules (no conflict unless same path added elsewhere):
@enode/core/workouts/{store,hooks,derive,draft,supersets,name-suggest,duration,timeline-layout,reschedule},@enode/core/api/mappers/workout-schedule.ts,@enode/core/format/measurement.ts, plus their*.test.ts.
Conflict resolution: only dtos/workouts.ts and api/workouts.ts are
modified shared core files — resolve any overlap as additive (keep both sides'
additions). New modules conflict only if another branch created the same path.
6. Portal-only (no tracking/shared conflict surface)
Entirely additive; cannot conflict with tracking:
apps/portal/src/app/dashboard/planner/*— the Planner page, schedule drawer, template picker, and the portal-local components extracted from the page (see the components folder; intentionally not promoted to@enode/uiyet to avoid widening this merge surface).apps/portal/package.json— added@dnd-kit/core(already in the monorepo via@enode/ui).docs/testing/planner-manual-test-protocol.md, this file.
7. Merge checklist
- Merge
calendarinto the target; expect conflicts mainly in §1/§2 files. - For each promoted/extracted file (§1, §2): integrate the other branch's edit into the shared location; keep the tracking original deleted.
- For §3 files: keep
@enode/*import paths. - After resolving: run
npm run typecheck,npm run test, andnpm run i18n:extract:check. Launch both apps (tracking,portal) and smoke the workout builder (shared) + the planner. - The i18n baseline (
baseline/en.json,keys.ts,i18n.catalog.json) is auto-generated — if it conflicts, take either side then re-runnpm run i18n:extractand commit the regenerated result.