Goal Roadmap — PRD
Product: Objectuve (codename: Enkidu) Feature: Goal Roadmap — an ordered, dated plan for how a user will accomplish a goal Status: Shipped — April 2026 (v1.9) Date: April 2026 Owner: Josh Lockhart PRD file: docs/product/completed/goal-roadmap-prd.md
1. Problem Statement
Objectuve is excellent at capturing the what of a goal — a name, a target date, a category, an optional target amount — and excellent at logging effort after the fact via GoalEvent. But it has almost nothing in between: users commit to an outcome without ever committing to a path.
Three concrete problems:
Outcomes without a plan. A user types "Run a half marathon by October" and hits save. There is no moment in the product where they're asked — or helped — to decompose that into a sequence of steps. Progress then feels binary (done / not done) until the target date, which is exactly the psychology the anti-social model is supposed to fix.
The milestone primitive is hidden and flat. The
MilestoneSTI subclass ofGoalalready exists (rails_api/app/models/milestone.rb, linked viaparent_goal_id), andMilestoneManager.vuelets users add named checkpoints to a goal. But milestones today are unordered, undated, buried inside the goal form, and visually treated as an afterthought. Users who do add them can't sequence them, can't see them as a timeline, and can't tell which one is "next."Coach has no plan to reason about. Coach generates advice and check-in prompts from goal-level fields and recent events. Because there's no first-class step sequence, Coach can't say things like "you're ahead on step 2, so step 3 is earlier than expected." The most valuable coaching signal — where are we in the plan? — is missing.
Goal Roadmap reframes the existing milestone primitive as the plan itself: an ordered, optionally-dated sequence of steps, shown as a timeline on the goal page, with a single clear next step.
2. Goals & Success Metrics
Primary Goals
- Let users sketch an ordered, dated roadmap for any goal in under 60 seconds.
- Make the roadmap the visual center of the Goal detail page — the answer to "what do I do next?"
- Measurably improve goal completion rates for goals that have a roadmap vs. those that don't.
Success Metrics
| Metric | Target (90 days post-launch) |
|---|---|
| % of newly-created goals that add ≥1 roadmap step within 7 days | 50%+ |
| Median steps per roadmap-using goal | 3–6 |
| Time-to-first-step-completed (create → first step checked) | <7 days for 60%+ of roadmap goals |
| Completion-rate lift for goals with a roadmap vs. without (at day 60) | +15pp |
| Median time to sketch a roadmap (open prompt → save) | <60 seconds |
| Regression in goal-creation completion rate | 0 (guardrail) |
Guardrails
- Anti-social: roadmap sketching is a single sheet with a text input + optional date per row. No wizard, no multi-step flow. <60s target.
- Optional, never blocking: a user can create a goal without a roadmap. The roadmap prompt on the Goal page is prominent but skippable.
- Backward compatible: existing goals and existing milestones keep working. Legacy milestones (no
position) are assigned sequential positions bycreated_atin a one-time backfill. - Accessibility: reorder must be keyboard-operable, not drag-only.
3. User Stories
- US-1: As a user who just created a goal, I want the Goal page to prompt me to sketch my roadmap as the first visible action, so planning feels like part of goal-setting, not an extra step.
- US-2: As a user sketching a roadmap, I want to add each step by typing a name and optionally picking a target date, and hit enter to add the next — so I can brainstorm fast.
- US-3: As a user, I want to reorder steps by drag or by keyboard (↑/↓ buttons), so I can refine the sequence as I think.
- US-4: As a user on the Goal page, I want to see my roadmap as a vertical timeline with the next incomplete step clearly highlighted, so I always know what to do now.
- US-5: As a user, I want to mark a step complete from the roadmap view with one tap, and feel a small celebration, so progress is felt, not just recorded.
- US-6: As a user with an existing goal that already has unordered milestones, I want those milestones to appear in my roadmap in a sensible default order that I can then rearrange.
- US-7: As a user, I want the goal's progress indicator to reflect completed steps ÷ total steps (when a roadmap exists), so progress feels earned step by step.
4. Feature Design
4a. "Sketch your roadmap" prompt on the Goal page
On ionic_frontend/src/views/Goal.vue, when a goal has zero roadmap steps, show a prominent empty-state card above the events section:
Sketch your roadmap Break this goal into a few steps you'll take to get there. You can always edit later. [ Sketch roadmap → ]
Tapping opens the RoadmapBuilder sheet. This empty state replaces the current low-key milestone treatment inside the edit form. Once ≥1 step exists, the card is replaced by the timeline view (4c).
4b. RoadmapBuilder.vue (built from today's MilestoneManager.vue)
A bottom sheet (Ionic modal) with:
- A single input row: Step name (text, required) + Target date (date picker, optional) + Add button
- Enter-to-add, focus returns to the input
- Below: the list of existing steps, each row has:
- Drag handle (⋮⋮) and ↑ / ↓ keyboard-accessible reorder buttons
- Inline-editable name
- Inline-editable target date
- Delete (trash) button
- Completion checkbox (same affordance as today's
MilestoneManager)
- Save / cancel at the bottom
The builder wraps MilestoneManager.vue's existing CRUD logic and adds ordering + per-step date. No new top-level route; it's a sheet.
4c. Roadmap timeline on Goal.vue
When the goal has ≥1 step, render a vertical stepper:
●── Step 1: Sign up for a race done · Apr 2
│
●── Step 2: Run 3× / week for 2 months done · May 30
│
◉── Step 3: First 10k run ← next · due Jun 21
│
○── Step 4: Half-marathon taper plan · Aug 15
│
○── Step 5: Race day · Oct 12Rules:
- Completed steps are filled circles with a muted label and completed date.
- The first incomplete step is the next step — visually emphasized (primary color, "next" chip, optional pulse).
- Subsequent incomplete steps are hollow circles.
- Tapping a step opens an inline actions row (mark complete, edit, delete). Long-press or a pencil icon opens
RoadmapBuilderscrolled to that step. - An "Edit roadmap" button sits in the card header and opens the full builder sheet.
4d. Progress rollup
Current goal progress logic (habit streaks, target amount, etc.) stays unchanged. When a roadmap exists, an additional progress signal is shown on the Goal page and the GoalCard:
- Roadmap progress =
completed_steps / total_steps, rendered as a small progress bar under the goal title. - When the last step is completed, a mid-tier celebration fires (via the Celebration System once available), and the user is prompted — not forced — to mark the goal itself complete.
4e. Celebration hooks
Each step completion fires a micro-tier celebration (checkmark scale + XP tick). The final step fires a mid-tier celebration. Both are delegated to the Celebration System when it lands; until then, reuse the existing canvas-confetti call pattern from Goal.vue.
4f. Coach integration (read-only in v1)
Coach gains implicit access to goal.milestones.order(:position) via the existing GoalType resolver changes below. No new Coach prompts are added in v1; this simply makes the plan structured enough that a later PRD ("Coach drafts my roadmap") can build on it.
5. Data & API Changes
5a. Migration
Add an ordering column to goals:
class AddPositionToGoals < ActiveRecord::Migration[8.1]
def change
add_column :goals, :position, :integer
add_index :goals, [:parent_goal_id, :position]
end
endOne-time backfill (in the same migration or a follow-up data migration):
Goal.where.not(parent_goal_id: nil).group_by(&:parent_goal_id).each do |_parent_id, siblings|
siblings.sort_by(&:created_at).each_with_index do |milestone, idx|
milestone.update_column(:position, idx)
end
end5b. Model
rails_api/app/models/goal.rb:
- Add
scope :ordered, -> { order(Arel.sql('COALESCE(position, 0)'), :created_at) }. - Update the existing
has_many :milestonesto include-> { ordered }.
5c. GraphQL
rails_api/app/graphql/types/goal_type.rb:
- Add
field :position, Integer, null: true. - The existing
field :milestones, [Types::GoalType]resolver already benefits from the ordered association.
New / updated mutations in rails_api/app/graphql/mutations/:
- Extend
UpdateGoalto accept astepsargument — a list of{ publicId?: String, name: String!, targetDate: String, position: Int! }— replacing the current string-onlynewMilestonespathway.UpdateGoalkeepsnewMilestonesaccepted for backward compatibility for one release, then is removed. - New
ReorderMilestonesmutation:goalId: ID!, orderedMilestoneIds: [ID!]!. Thin wrapper around aGoalTracking::ReorderMilestonesinteraction that updatespositionin a single transaction. - New
SetMilestoneTargetDatemutation (or fold intoUpdateGoalfor the milestone row). Decide during implementation; the PRD commits only to the capability.
5d. Interactions
rails_api/app/interactions/goal_tracking/:
- Extend
update_goal.rbto accept structuredstepsinput and diff against existing milestones (upsert bypublic_id, create when absent, soft-delete when removed). - New
reorder_milestones.rb— guards on ownership, updatespositionin a transaction.
5e. Frontend GraphQL
ionic_frontend/src/constants/graphql/goals.js:
- Extend
GOAL_QUERYto requestmilestones { id name targetDate completed position }. - Extend
UpdateGoalMutationto acceptstepsinput. - Add
ReorderMilestonesMutation. - Apollo cache: after reorder, write the new ordering into the cached goal.
6. Rollout Plan
- Feature flag:
goal_roadmap_enabled(user-level or cohort-level, whichever the existing flag infrastructure supports). Default off, enabled for staff first, then new-user cohort, then all. - Instrumentation (
UserActiontriggers):roadmap_step_addedroadmap_step_completedroadmap_step_reorderedgoal_has_roadmap(fires once, on first step added to a goal)
- Analytics funnel: goal created → roadmap prompt shown → roadmap opened → ≥1 step added → first step completed. Measures against the metrics in §2.
- Mobile: ships behind the same flag; no Capacitor native changes required.
- Docs: update
docs/product/prd-index.md(add this row under Exploration / Design) and — once shipped — add a user-facing entry todocs/product/user-guide.md.
7. Non-Goals (v1)
- Dependencies / gating between steps. No "can't start step 3 until step 2 is complete." Users sequence steps but can complete them in any order.
- Gantt or calendar visualization. Vertical stepper only in v1.
- Coach-drafted roadmaps. The model supports it, but the "Draft my roadmap with Coach" button is deferred to a follow-up PRD.
- Pre-built roadmap templates. No template gallery in v1.
- Per-step descriptions / notes / attached events. Each step is
name + target_date + completed. If a step needs a rich diary, the user can still log aGoalEventagainst the parent goal. - Shared / public roadmaps. A user's roadmap is visible exactly where the parent goal is visible (same privacy rules).
- Cross-goal roadmaps. Each roadmap belongs to exactly one goal.
8. Future / stretch (post-v1)
- Coach drafts my roadmap — tap a button on the empty-state card; Coach proposes a 3–6 step plan based on the goal name, target date, and category. User edits and saves.
- Roadmap templates — curated templates per goal category (Run a 5K, Learn a language, Ship a side project). Clone into a user's goal on pick.
- Step dependencies —
depends_on_milestone_idcolumn + UI for gated steps. - Step-level events — attach
GoalEventrecords directly to a step, not just the parent goal.
9. Open Questions
- Should completed steps stay visible in the timeline forever, or collapse after N days? (Recommendation: always visible — the ladder is part of the celebration.)
- When a step's target date passes without completion, do we nudge? (Likely yes — belongs in the Coach / lifecycle messaging PRD, not this one.)
- Does marking every step complete auto-complete the parent goal, or only prompt? (Recommendation: prompt. Auto-complete can surprise users who still have work outside the roadmap.)
10. Key Files to Touch (implementation reference)
- Backend:
rails_api/db/migrate/{ts}_add_position_to_goals.rb(new)rails_api/app/models/goal.rb(scope + association order)rails_api/app/graphql/types/goal_type.rb(addposition)rails_api/app/graphql/mutations/update_goal.rb(extendstepsinput)rails_api/app/graphql/mutations/reorder_milestones.rb(new)rails_api/app/interactions/goal_tracking/update_goal.rb(extend)rails_api/app/interactions/goal_tracking/reorder_milestones.rb(new)- Specs for each of the above under
rails_api/spec/
- Frontend:
ionic_frontend/src/views/Goal.vue(empty-state card + timeline card)ionic_frontend/src/components/RoadmapBuilder.vue(new — evolvesMilestoneManager.vue)ionic_frontend/src/components/RoadmapTimeline.vue(new — stepper visualization)ionic_frontend/src/components/MilestoneManager.vue(kept as internal implementation of the builder until fully absorbed)ionic_frontend/src/constants/graphql/goals.js(query + mutations)- Storybook stories for
RoadmapBuilderandRoadmapTimeline - Vitest unit tests; Cypress E2E for the create → sketch → complete-step flow
Next step: review problem framing and v1 scope with the team, then split into implementation phases (migration + model → GraphQL → RoadmapBuilder → RoadmapTimeline → instrumentation).
Related Documentation
- Goals — feature doc for the shipped implementation
Last updated: 2026-07-16