Skip to main content

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

  1. 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.
  2. Extractions out of workout-shared.tsx (§2) — utilities/components carved into new shared files. workout-shared.tsx shrank ~300 lines.
  3. Tracking import-repointing (§3) — ~11 tracking files only changed import paths. Mechanical; keep the @enode/* paths.
  4. Universal (UTC) time behaviour (§4) — derive.ts + workouts-today-list.
  5. Additive shared core (§5) — new modules + additive edits to api/dtos/workouts.ts and api/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.ts100%
…/today/ExercisePickerDrawer.tsx@enode/ui/workout-builder/ExercisePickerDrawer.tsx100%
…/today/SelectActionsCard.tsx@enode/ui/workout-builder/SelectActionsCard.tsx100%
…/today/SlotCard.tsx@enode/ui/workout-builder/SlotCard.tsx98%
…/today/SortableSlotCard.tsx@enode/ui/workout-builder/SortableSlotCard.tsx96%
…/today/SupersetChain.tsx@enode/ui/workout-builder/SupersetChain.tsx100%
…/today/WorkoutItemDrawer.tsx@enode/ui/workout-builder/WorkoutItemDrawer.tsx100%
…/today/WorkoutItemTemplatePickerDrawer.tsx@enode/ui/workout-builder/WorkoutItemTemplatePickerDrawer.tsx100%
…/today/workout-draft-context.tsx@enode/ui/workout-builder/workout-draft-context.tsx100%

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 fileExtracted fromSymbol(s)
@enode/core/format/measurement.ts…/today/workout-shared.tsxformatMeasurementValue
@enode/ui/athlete-chip.tsx…/today/workout-shared.tsxAthleteChip
@enode/ui/workout-card.tsxtracking workout listWorkoutCard (used by workouts-today-list)
@enode/ui/workout-builder/workout-item-content.tsxSlotCard.tsxslot card content / muscle media
@enode/ui/workout-builder/icons.tsxbuilder filesshared 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 promoted SelectActionsCard from @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.tsworkoutMinutes now reads UTC (getUTCHours/getUTCMinutes, was local); added workoutDayKey, formatWorkoutTime, and RECURRING_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/ui yet 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

  1. Merge calendar into the target; expect conflicts mainly in §1/§2 files.
  2. For each promoted/extracted file (§1, §2): integrate the other branch's edit into the shared location; keep the tracking original deleted.
  3. For §3 files: keep @enode/* import paths.
  4. After resolving: run npm run typecheck, npm run test, and npm run i18n:extract:check. Launch both apps (tracking, portal) and smoke the workout builder (shared) + the planner.
  5. The i18n baseline (baseline/en.json, keys.ts, i18n.catalog.json) is auto-generated — if it conflicts, take either side then re-run npm run i18n:extract and commit the regenerated result.