We can wishlist and approve destinations, and staff can set a flat per-country cost estimate, but there is no way to build the actual plan: a day-by-day breakdown of activities with time, location, materials/equipment, and per-activity cost. That breakdown is the most important input to understanding what a trip really costs a person/family and how the time is spent. It should also tie visually into the existing calendar.
Current state (verified 2026-08-03)
There is no Trip/Journey entity. Destinations are bare ISO strings ("HND", "US-LA") in parallel lists on TravelProfile (SpikerSoft.Data/Mongos/ProfileTravelSubDocuments.cs). No Country/Destination/Place entity.
Costs = UserProfile.TripCosts: List<TripCostEntry { Country, EstimatedCostCents, LineItems[{Label, AmountCents}], SetBy, SetAt }> — staff-only write (PUT /api/sponsor/profiles/{id}/trip-costs), flat free-text labels, no dates/days/activities. LocationFund, RewardEngine, and the sponsor cards all read TripCosts.
Nothing reusable named itinerary/activity: RedisOM/CalendarEvent.cs has a dead unreferenced List<TimeSpan> Itinerary (vestige — remove as part of this work); Activity/UserActivity is analytics telemetry (naming collision!); "Journey" is the lesson learning path.
No materials/equipment model exists anywhere in the codebase.
Design sketch
Data model — new top-level Mongo collection TripPlan (NOT embedded in UserProfile: unbounded growth, multi-member; also avoids the optional-owned-nav EF-Mongo landmine):
ItineraryDay { Date? or DayNumber, Notes, Items: ItineraryItem[] }
ItineraryItem { Title, StartTime?, EndTime?, Location(string, matches CalendarEvent.Location), EquipmentNeeded: string[] (first materials model in the codebase), EstimatedCostCents, Notes }
Name it ItineraryItem/PlannedActivity — never Activity (collides with analytics UserActivity).
Cost roll-up — feed, don't replace. Plan totals (sum of item costs per destination) roll up INTO the existing TripCostEntry.LineItems shape rather than replacing TripCosts, since LocationFund/RewardEngine/sponsor cards read it. Staff PUT trip-costs remains; the planner becomes a second (user-side) source. Decide precedence/merge rules (e.g. planner-derived entries flagged with SetBy = "planner").
Calendar tie-in. Project plan items as CalendarEvents using the existing GroupId (one group per TripPlan) + ExtendedProps seam — no CalendarEvent schema change. Sync strategy (one-way projection with regenerate-on-save is simplest) to be decided.
Frontend.libraries/features/trip-plan/ → @spikersoft/feature-trip-plan (+ libraries/domain/trip-plan/ state lib), lazy route in projects/spikersoft/src/routes.ts near calendar/sponsor; follow the feature-sponsor shape. Signal Forms + httpResource rules apply. Day editor UI: per-day item list with time range, location, equipment chips, cost; running per-day and per-trip totals.
Cleanup. Delete dead SpikerSoft.Data/RedisOM/CalendarEvent.cs to clear the Itinerary name collision.
## Problem
We can wishlist and approve destinations, and staff can set a flat per-country cost estimate, but there is no way to build the *actual plan*: a day-by-day breakdown of activities with time, location, materials/equipment, and per-activity cost. That breakdown is the most important input to understanding what a trip really costs a person/family and how the time is spent. It should also tie visually into the existing calendar.
## Current state (verified 2026-08-03)
- **There is no Trip/Journey entity.** Destinations are bare ISO strings (`"HND"`, `"US-LA"`) in parallel lists on `TravelProfile` (`SpikerSoft.Data/Mongos/ProfileTravelSubDocuments.cs`). No Country/Destination/Place entity.
- Costs = `UserProfile.TripCosts: List<TripCostEntry { Country, EstimatedCostCents, LineItems[{Label, AmountCents}], SetBy, SetAt }>` — staff-only write (`PUT /api/sponsor/profiles/{id}/trip-costs`), flat free-text labels, no dates/days/activities. `LocationFund`, `RewardEngine`, and the sponsor cards all read TripCosts.
- Calendar = generic FullCalendar personal/family calendar (`Mongos/CalendarEvent.cs`): RRULE recurrence, iCal import, parental access, `GroupId`, free-form `ExtendedProps` (BsonDocument), `Location` as plain string. Zero trip linkage today.
- Nothing reusable named itinerary/activity: `RedisOM/CalendarEvent.cs` has a dead unreferenced `List<TimeSpan> Itinerary` (vestige — remove as part of this work); `Activity`/`UserActivity` is analytics telemetry (naming collision!); "Journey" is the lesson learning path.
- No materials/equipment model exists anywhere in the codebase.
## Design sketch
**Data model** — new top-level Mongo collection `TripPlan` (NOT embedded in UserProfile: unbounded growth, multi-member; also avoids the optional-owned-nav EF-Mongo landmine):
- `TripPlan { Id, OwnerProfileId, FamilyKey?, DestinationCode, Title, StartDate?, EndDate?, Status(draft/active/archived), Days: ItineraryDay[], audit }`
- `ItineraryDay { Date? or DayNumber, Notes, Items: ItineraryItem[] }`
- `ItineraryItem { Title, StartTime?, EndTime?, Location(string, matches CalendarEvent.Location), EquipmentNeeded: string[] (first materials model in the codebase), EstimatedCostCents, Notes }`
- Name it `ItineraryItem`/`PlannedActivity` — never `Activity` (collides with analytics `UserActivity`).
**Cost roll-up — feed, don't replace.** Plan totals (sum of item costs per destination) roll up INTO the existing `TripCostEntry.LineItems` shape rather than replacing TripCosts, since LocationFund/RewardEngine/sponsor cards read it. Staff `PUT trip-costs` remains; the planner becomes a second (user-side) source. Decide precedence/merge rules (e.g. planner-derived entries flagged with `SetBy = "planner"`).
**Calendar tie-in.** Project plan items as `CalendarEvent`s using the existing `GroupId` (one group per TripPlan) + `ExtendedProps` seam — no CalendarEvent schema change. Sync strategy (one-way projection with regenerate-on-save is simplest) to be decided.
**Backend.** `SpikerSoft.Business/Domain/TripPlan/` (standard MediatR Commands/Queries/DTOs module) + `SpikerSoft.Api/Domain/TripPlan/TripPlanController.cs` + entity registered in `SpikerDbContext`. Owner + family-member authz (mirror calendar parental-access rules).
**Frontend.** `libraries/features/trip-plan/` → `@spikersoft/feature-trip-plan` (+ `libraries/domain/trip-plan/` state lib), lazy route in `projects/spikersoft/src/routes.ts` near calendar/sponsor; follow the `feature-sponsor` shape. Signal Forms + httpResource rules apply. Day editor UI: per-day item list with time range, location, equipment chips, cost; running per-day and per-trip totals.
**Cleanup.** Delete dead `SpikerSoft.Data/RedisOM/CalendarEvent.cs` to clear the Itinerary name collision.
## Out of scope
In-kind sponsorship (accommodation/equipment donation) — explicitly dropped for now, Stripe-only remains.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
We can wishlist and approve destinations, and staff can set a flat per-country cost estimate, but there is no way to build the actual plan: a day-by-day breakdown of activities with time, location, materials/equipment, and per-activity cost. That breakdown is the most important input to understanding what a trip really costs a person/family and how the time is spent. It should also tie visually into the existing calendar.
Current state (verified 2026-08-03)
"HND","US-LA") in parallel lists onTravelProfile(SpikerSoft.Data/Mongos/ProfileTravelSubDocuments.cs). No Country/Destination/Place entity.UserProfile.TripCosts: List<TripCostEntry { Country, EstimatedCostCents, LineItems[{Label, AmountCents}], SetBy, SetAt }>— staff-only write (PUT /api/sponsor/profiles/{id}/trip-costs), flat free-text labels, no dates/days/activities.LocationFund,RewardEngine, and the sponsor cards all read TripCosts.Mongos/CalendarEvent.cs): RRULE recurrence, iCal import, parental access,GroupId, free-formExtendedProps(BsonDocument),Locationas plain string. Zero trip linkage today.RedisOM/CalendarEvent.cshas a dead unreferencedList<TimeSpan> Itinerary(vestige — remove as part of this work);Activity/UserActivityis analytics telemetry (naming collision!); "Journey" is the lesson learning path.Design sketch
Data model — new top-level Mongo collection
TripPlan(NOT embedded in UserProfile: unbounded growth, multi-member; also avoids the optional-owned-nav EF-Mongo landmine):TripPlan { Id, OwnerProfileId, FamilyKey?, DestinationCode, Title, StartDate?, EndDate?, Status(draft/active/archived), Days: ItineraryDay[], audit }ItineraryDay { Date? or DayNumber, Notes, Items: ItineraryItem[] }ItineraryItem { Title, StartTime?, EndTime?, Location(string, matches CalendarEvent.Location), EquipmentNeeded: string[] (first materials model in the codebase), EstimatedCostCents, Notes }ItineraryItem/PlannedActivity— neverActivity(collides with analyticsUserActivity).Cost roll-up — feed, don't replace. Plan totals (sum of item costs per destination) roll up INTO the existing
TripCostEntry.LineItemsshape rather than replacing TripCosts, since LocationFund/RewardEngine/sponsor cards read it. StaffPUT trip-costsremains; the planner becomes a second (user-side) source. Decide precedence/merge rules (e.g. planner-derived entries flagged withSetBy = "planner").Calendar tie-in. Project plan items as
CalendarEvents using the existingGroupId(one group per TripPlan) +ExtendedPropsseam — no CalendarEvent schema change. Sync strategy (one-way projection with regenerate-on-save is simplest) to be decided.Backend.
SpikerSoft.Business/Domain/TripPlan/(standard MediatR Commands/Queries/DTOs module) +SpikerSoft.Api/Domain/TripPlan/TripPlanController.cs+ entity registered inSpikerDbContext. Owner + family-member authz (mirror calendar parental-access rules).Frontend.
libraries/features/trip-plan/→@spikersoft/feature-trip-plan(+libraries/domain/trip-plan/state lib), lazy route inprojects/spikersoft/src/routes.tsnear calendar/sponsor; follow thefeature-sponsorshape. Signal Forms + httpResource rules apply. Day editor UI: per-day item list with time range, location, equipment chips, cost; running per-day and per-trip totals.Cleanup. Delete dead
SpikerSoft.Data/RedisOM/CalendarEvent.csto clear the Itinerary name collision.Out of scope
In-kind sponsorship (accommodation/equipment donation) — explicitly dropped for now, Stripe-only remains.