Skip to content

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:

  1. 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.

  2. The milestone primitive is hidden and flat. The Milestone STI subclass of Goal already exists (rails_api/app/models/milestone.rb, linked via parent_goal_id), and MilestoneManager.vue lets 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."

  3. 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

  1. Let users sketch an ordered, dated roadmap for any goal in under 60 seconds.
  2. Make the roadmap the visual center of the Goal detail page — the answer to "what do I do next?"
  3. Measurably improve goal completion rates for goals that have a roadmap vs. those that don't.

Success Metrics

MetricTarget (90 days post-launch)
% of newly-created goals that add ≥1 roadmap step within 7 days50%+
Median steps per roadmap-using goal3–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 rate0 (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 by created_at in 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 12

Rules:

  • 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 RoadmapBuilder scrolled 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:

ruby
class AddPositionToGoals < ActiveRecord::Migration[8.1]
  def change
    add_column :goals, :position, :integer
    add_index :goals, [:parent_goal_id, :position]
  end
end

One-time backfill (in the same migration or a follow-up data migration):

ruby
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
end

5b. Model

rails_api/app/models/goal.rb:

  • Add scope :ordered, -> { order(Arel.sql('COALESCE(position, 0)'), :created_at) }.
  • Update the existing has_many :milestones to 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 UpdateGoal to accept a steps argument — a list of { publicId?: String, name: String!, targetDate: String, position: Int! } — replacing the current string-only newMilestones pathway. UpdateGoal keeps newMilestones accepted for backward compatibility for one release, then is removed.
  • New ReorderMilestones mutation: goalId: ID!, orderedMilestoneIds: [ID!]!. Thin wrapper around a GoalTracking::ReorderMilestones interaction that updates position in a single transaction.
  • New SetMilestoneTargetDate mutation (or fold into UpdateGoal for the milestone row). Decide during implementation; the PRD commits only to the capability.

5d. Interactions

rails_api/app/interactions/goal_tracking/:

  • Extend update_goal.rb to accept structured steps input and diff against existing milestones (upsert by public_id, create when absent, soft-delete when removed).
  • New reorder_milestones.rb — guards on ownership, updates position in a transaction.

5e. Frontend GraphQL

ionic_frontend/src/constants/graphql/goals.js:

  • Extend GOAL_QUERY to request milestones { id name targetDate completed position }.
  • Extend UpdateGoalMutation to accept steps input.
  • 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 (UserAction triggers):
    • roadmap_step_added
    • roadmap_step_completed
    • roadmap_step_reordered
    • goal_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 to docs/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 a GoalEvent against 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 dependenciesdepends_on_milestone_id column + UI for gated steps.
  • Step-level events — attach GoalEvent records 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 (add position)
    • rails_api/app/graphql/mutations/update_goal.rb (extend steps input)
    • 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 — evolves MilestoneManager.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 RoadmapBuilder and RoadmapTimeline
    • 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 → RoadmapBuilderRoadmapTimeline → instrumentation).


  • Goals — feature doc for the shipped implementation

Last updated: 2026-07-16

Loading…