Skip to content

Add validation analytics, execution tracking, and founder overview#144

Merged
Gamius00 merged 6 commits into
release-v-1.0.3from
codex/analytics-system
Jul 10, 2026
Merged

Add validation analytics, execution tracking, and founder overview#144
Gamius00 merged 6 commits into
release-v-1.0.3from
codex/analytics-system

Conversation

@FleetAdmiralJakob

@FleetAdmiralJakob FleetAdmiralJakob commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Builds the validation-phase analytics system end to end on the current main branch. This supersedes the stale draft analytics foundation in #87 and completes the execution/recovery/founder-observability slices from the validation tracking PRD.

The PR adds:

  • Optional PostHog React Native setup with identified-only validation tracking. Autocapture, lifecycle events, anonymous pre-auth tracking, and session replay stay disabled.
  • A shared analytics API for stable validation events and PostHog person properties keyed by Clerk user id plus validation_student_code when available.
  • Convex-backed validation state for activity/next-day return, session execution status, missed reasons, recovery adjustments, and founder attribution.
  • Learning-plan session actions for started, completed, partially completed, missed with structured reason, and adjusted/replanned recovery blocks.
  • Mobile UI controls on the learning-plan session list for the validation execution loop.
  • Founder-only /validation/overview with daily execution rows, Product Signal vs Accountability Signal attribution, and CSV sharing.
  • Docs/env updates for PostHog, validation student profile terminology, and the product/recovery loop language.
  • Focused Convex and runtime-config tests covering the new state model and validation analytics behavior.

Issues Resolved

Closes #76
Closes #77
Closes #78
Closes #79
Closes #80

Note for #79/#80: PR #144 lands the backend/session-state foundation. The remaining learner-facing missed/recovery UX and app-side event wiring are tracked as follow-up design work in #155; this PR should not auto-close #155.

Closes #81
Closes #82
Closes #83
Closes #84

How This Maps To The Validation Plan

  • First Real Block: study_slot_started is emitted only after a durable Convex transition to started succeeds.
  • Execution outcomes: sessions can be completed, partiallyCompleted, missed, or adjusted, with state mirrored into synced day entries.
  • Missed reasons: missed blocks store stable reason keys (no_time, forgot, no_motivation, too_hard, too_big, unclear, other) and emit both missed/outcome analytics.
  • Recovery Loop: missed blocks can create smaller linked recovery blocks and emit plan_adjusted with old/new context.
  • Generalprobe: completed rehearsal-phase sessions emit generalprobe_completed without creating a separate content system.
  • Return signal: identified users emit user_returned_next_day once per local day after prior validation activity.
  • Founder workflow: founders can see who should act today, who acted, who missed, who likely needs check-in, and manually annotate attribution.

Implementation Notes

  • Convex schema changes are widening-only: optional fields plus new tables (validationUserStates, validationAttributions). No backfill migration is required.
  • validationStudentCode is synced from Clerk unsafe metadata into Convex and PostHog as an analytics join key only. It is not used for authorization.
  • Founder access is controlled by users.validationRole === "founder"; the mobile profile sync does not let users grant that role to themselves.
  • PostHog envs are optional. The app starts normally without EXPO_PUBLIC_POSTHOG_API_KEY, and capture helpers no-op when analytics is not configured.
  • Critical analytics events are emitted after successful backend actions, not before, so failed mutations do not create false product signals.

Still Open Work / Follow-Ups

  • Configure production/EAS PostHog envs (EXPO_PUBLIC_POSTHOG_API_KEY, EXPO_PUBLIC_POSTHOG_HOST) and confirm ingestion in the real PostHog project. DONE ✅
  • Seed validation cohort metadata: validationStudentCode for students and validationRole: "founder" for internal founder/coach accounts.
  • Recovery-loop learner UX for the remaining Capture missed study slots with structured reasons #79/Add a minimal Recovery Loop for missed learning blocks #80 UI work: Define Recovery Loop UX for missed learning-plan sessions #155 tracks the Figma/design prerequisite before wiring missSession, adjustMissedSession, partiallyCompleted, study_slot_missed, missed_reason_selected, and plan_adjusted into the app UI.
  • Build the actual PostHog dashboard/reporting views for the 10-day validation readout using the events/properties introduced here.
  • Run an end-to-end validation rehearsal on real devices with real cohort accounts, including CSV share from /validation/overview.
  • Close or supersede draft PR Add validation-phase analytics tracking #87 after this PR lands.
  • Keep the founder overview intentionally minimal unless the validation workflow proves it needs more. A polished admin dashboard/CRM remains out of scope.

Validation

Local validation run during implementation:

  • pnpm convex codegen
  • pnpm test
  • pnpm typecheck
  • pnpm lint
  • git diff --check && git diff --cached --check

CodeRabbit notes:

  • Initial CodeRabbit CLI run surfaced follow-ups around shared constants/loading states and analytics id validation; those were addressed.
  • The subsequent CLI rerun stayed in reviewing for the full 10-minute window and emitted no new findings before timeout.

Out Of Scope

  • Anonymous analytics, autocapture, lifecycle event tracking, and session replay.
  • Replacing the Confluence validation student table.
  • A polished internal dashboard beyond the lightweight founder overview/export.
  • New AI plan-quality work, payment flows, parent-facing flows, or broader product areas outside validation execution/recovery tracking.

Summary by CodeRabbit

  • New Features

    • Added a validation analytics overview for founders, including day navigation, attribution updates, and CSV sharing.
    • Added analytics tracking for onboarding, entry creation, plan generation, file uploads, and session start/completion events.
    • Added support for tracking session progress states like started, completed, missed, and adjusted.
  • Bug Fixes

    • Improved handling of optional analytics settings so the app works even when PostHog values aren’t provided.
    • Kept session status and calendar entries in sync for better progress accuracy.
  • Documentation

    • Updated environment and product docs with the new optional analytics settings and validation terminology.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d61f83b7-e599-4948-8773-b594b96a055e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds session execution lifecycle tracking (started, completed, partially completed, missed, adjusted) with new Convex mutations and schema fields, integrates PostHog analytics via a new hook and identity component capturing validation events across onboarding/entry/plan flows, and adds a founder-only daily validation overview screen with attribution recording.

Changes

Session Lifecycle & Validation Analytics

Layer / File(s) Summary
Schema validators and shared types
convex/schema.ts, src/features/learning-plans/types.ts, src/features/learning-plans/constants.ts, src/types/dayEntries.ts, src/types/validation.ts
New execution-status/missed-reason/attribution-source validators, validationUserStates/validationAttributions tables, and mirrored frontend types/label maps.
PostHog runtime infra and hook
src/lib/runtime-config.ts, src/lib/runtime-config.test.ts, src/lib/analytics-core.ts, src/lib/analytics.ts, .env.example, app.config.ts, package.json, docs/contexts/integrations/CONTEXT.md, docs/contexts/platform/CONTEXT.md
Optional PostHog env vars, captureValidationEvent/definedAnalyticsProperties helpers, useValidationAnalytics hook, new dependencies, and docs.
Session execution mutations and sync
convex/learningPlans.ts, convex/learningPlans.test.ts, convex/dayEntries.ts, convex/learningSessionContent.ts, convex/users.ts, src/features/learning-plans/replan-recovery.test.ts
startSession, recordSessionOutcome, missSession, adjustMissedSession mutations with transition guards, day-entry sync, and expanded tests.
Validation analytics backend
convex/validationAnalytics.ts, convex/validationAnalytics.test.ts
markActivity, markReturnedNextDay, dailyOverview, and recordAttribution founder-gated functions with tests.
PostHogProvider and identity wiring
src/app/_layout.tsx, src/components/analytics-identity.tsx, src/context/AuthContext.tsx
App-level PostHog provider, AnalyticsIdentity component, and onboarding completion event capture.
Session detail screen instrumentation
src/app/learning-plans/[planId]/sessions/[sessionId]/index.tsx, docs/contexts/product/CONTEXT.md
Wires start/outcome mutations and emits study_slot_started/study_slot_completed/generalprobe_completed.
Screen-level event capture
src/app/entry/new.tsx, src/app/learning-plans/[planId]/generating.tsx, src/app/learning-plans/new.tsx
Emits homework_created/exam_created, study_plan_generated, and material_uploaded events.
Founder overview screen
src/app/validation/overview.tsx
New role-gated screen with CSV export and attribution recording UI.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related issues

Possibly related PRs

  • Dayova/dayova-mvp#107: Both touch completion-related mutations on day entries in the same subsystem.
  • Dayova/dayova-mvp#135: Both modify the learning-plan material upload screen and its registerUploadedDocument completion flow.
  • Dayova/dayova-mvp#150: Both touch the dayEntries.executionStatus schema/session lifecycle wiring.

Suggested labels: ready-for-agent

Suggested reviewers: Gamius00

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning Issues [#79, #80] are not fully satisfied because the summary shows state mutations, but no app-side capture for study_slot_missed, missed_reason_selected, or plan_adjusted. Wire missed and recovery flows to emit study_slot_missed, missed_reason_selected, and plan_adjusted only after successful mutations, and expose the recovery path in the UI.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Out of Scope Changes check ✅ Passed The changes stay focused on validation analytics, execution tracking, founder overview, and supporting docs/runtime updates.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main additions: validation analytics, session execution tracking, and the founder overview.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/analytics-system

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@FleetAdmiralJakob FleetAdmiralJakob changed the title Add analytics system for learning and validation flows Add validation analytics, execution tracking, and founder overview Jun 23, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
src/types/dayEntries.ts (1)

15-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Reuse shared execution/missed types to prevent enum drift.

DayEntry repeats literal unions already defined for learning-plan sessions. Importing shared aliases keeps schema/UI contracts synchronized with fewer update points.

Proposed refactor
+import type {
+  SessionExecutionStatus,
+  MissedReason,
+} from "~/features/learning-plans/types";
+
 export type DayEntry = {
   id: Id<"dayEntries"> | Id<"learningPlanSessions">;
@@
-  executionStatus?:
-    | "notStarted"
-    | "started"
-    | "completed"
-    | "partiallyCompleted"
-    | "missed"
-    | "adjusted";
+  executionStatus?: SessionExecutionStatus;
   startedAt?: number;
   outcomeAt?: number;
-  missedReason?:
-    | "no_time"
-    | "forgot"
-    | "no_motivation"
-    | "too_hard"
-    | "too_big"
-    | "unclear"
-    | "other";
+  missedReason?: MissedReason;
   adjustedFromSessionId?: Id<"learningPlanSessions">;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/dayEntries.ts` around lines 15 - 32, The DayEntry type in
dayEntries.ts contains inline literal unions for executionStatus and
missedReason that duplicate type definitions already present in the
learning-plan sessions types. Replace these inline literal unions with imported
type aliases from the shared type definitions file (likely the learning-plan
sessions type file). Import the execution status type alias and the missed
reason type alias, then use them to define the executionStatus and missedReason
properties instead of repeating the literal unions inline. This ensures both
DayEntry and learning-plan sessions share the same type definitions, preventing
enum drift and reducing future maintenance burden.
src/app/learning-plans/[planId]/index.tsx (1)

382-382: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: other sessions silently no-op while one action is pending.

runSessionAction early-returns when any pendingSessionId is set, but only the active card receives isPending (Line 563), so buttons on other sessions remain visually enabled yet do nothing on tap. Consider disabling all action buttons (or surfacing a global pending state) while any action is in flight.

Also applies to: 243-280

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/learning-plans/`[planId]/index.tsx at line 382, The issue is that
when a `pendingSessionId` is set, the early return in `runSessionAction`
prevents actions from executing, but the `isPending` visual indicator only
appears on the active card (referenced at Line 563), leaving buttons on other
sessions appearing enabled and clickable while silently doing nothing. To fix
this, extend the `isPending` state handling to disable action buttons across all
sessions (not just the active card) while any action is pending, or introduce a
global pending state indicator that makes it clear to users that an operation is
in flight and other interactions are blocked.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@convex/learningPlans.ts`:
- Around line 1257-1267: The query for learningPlanSessions is returning the
first 50 sessions in ascending sortOrder but needs to find the actual maximum
sortOrder to avoid collisions when a plan has more than 50 sessions. Modify the
query chain by adding `.order("desc")` after the `.withIndex()` call to sort by
sortOrder in descending order, then change `.take(50)` to `.take(1)` to retrieve
only the highest sortOrder. Replace the reduce operation that finds the max
among the 50 lowest values with a simple check for the first result's sortOrder
or fallback to 0 if no sessions exist, then add 1 to get the new sortOrder
value.

In `@convex/validationAnalytics.ts`:
- Around line 84-113: The localDayKey parameter is accepted as an arbitrary
string in both mutations but is relied upon for string comparison operations
that assume YYYY-MM-DD format semantics. Add validation at the beginning of each
mutation handler (in the unnamed first mutation and in markReturnedNextDay) to
verify that localDayKey matches the expected YYYY-MM-DD date format before it is
used in any comparisons or persisted to the database. If the format is invalid,
throw an error to prevent malformed values from corrupting the
lastActivityDayKey and lastReturnDayKey fields.

In `@docs/contexts/integrations/CONTEXT.md`:
- Line 20: Fix the typo in the analytics coverage list where "generalprobe
completions" should be corrected to "general probe completions" with a proper
space between "general" and "probe", or use the exact canonical event label name
if that differs. Locate the text "generalprobe completions" in the analytics
coverage section and update it to use the proper spacing or canonical naming
convention for this event type.

In `@src/app/validation/overview.tsx`:
- Around line 28-29: The csvCell function does not protect against formula
injection vulnerabilities where user-controlled values starting with =, +, -, or
@ characters can be interpreted as formulas by spreadsheet applications. Modify
the csvCell function to detect when the stringified value starts with any of
these dangerous characters and prepend a single quote to force the spreadsheet
application to treat the entire cell as plain text, ensuring the quote escaping
logic remains intact after this check.

In `@src/components/analytics-identity.tsx`:
- Around line 61-82: The dedupe key returnCaptureKeyRef.current is being set
before the markReturnedNextDay promise resolves, which blocks retries if the
mutation fails. Move the assignment of returnCaptureKeyRef.current =
returnCaptureKey from its current position (before the markReturnedNextDay call)
to inside the .then() callback, placing it after the check for result.captured
to ensure the dedupe key is only set when the mutation succeeds.

In `@src/lib/runtime-config.test.ts`:
- Around line 88-89: The test for the typed-env configuration is incomplete. In
addition to asserting that EXPO_PUBLIC_POSTHOG_API_KEY is undefined, add a
corresponding assertion to verify that EXPO_PUBLIC_POSTHOG_HOST is also
undefined. Add an expect statement for env.EXPO_PUBLIC_POSTHOG_HOST right after
the existing EXPO_PUBLIC_POSTHOG_API_KEY assertion to ensure both optional
PostHog configuration keys are properly validated when not provided.

---

Nitpick comments:
In `@src/app/learning-plans/`[planId]/index.tsx:
- Line 382: The issue is that when a `pendingSessionId` is set, the early return
in `runSessionAction` prevents actions from executing, but the `isPending`
visual indicator only appears on the active card (referenced at Line 563),
leaving buttons on other sessions appearing enabled and clickable while silently
doing nothing. To fix this, extend the `isPending` state handling to disable
action buttons across all sessions (not just the active card) while any action
is pending, or introduce a global pending state indicator that makes it clear to
users that an operation is in flight and other interactions are blocked.

In `@src/types/dayEntries.ts`:
- Around line 15-32: The DayEntry type in dayEntries.ts contains inline literal
unions for executionStatus and missedReason that duplicate type definitions
already present in the learning-plan sessions types. Replace these inline
literal unions with imported type aliases from the shared type definitions file
(likely the learning-plan sessions type file). Import the execution status type
alias and the missed reason type alias, then use them to define the
executionStatus and missedReason properties instead of repeating the literal
unions inline. This ensures both DayEntry and learning-plan sessions share the
same type definitions, preventing enum drift and reducing future maintenance
burden.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 98738de1-f987-4270-8257-1ba131e8b304

📥 Commits

Reviewing files that changed from the base of the PR and between 9531c85 and 4a3631f.

⛔ Files ignored due to path filters (2)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (30)
  • .codex/environments/environment.toml
  • .env.example
  • app.config.ts
  • convex/dayEntries.ts
  • convex/learningPlans.test.ts
  • convex/learningPlans.ts
  • convex/schema.ts
  • convex/users.ts
  • convex/validationAnalytics.test.ts
  • convex/validationAnalytics.ts
  • docs/contexts/integrations/CONTEXT.md
  • docs/contexts/platform/CONTEXT.md
  • docs/contexts/product/CONTEXT.md
  • package.json
  • src/app/_layout.tsx
  • src/app/entry/new.tsx
  • src/app/learning-plans/[planId]/generating.tsx
  • src/app/learning-plans/[planId]/index.tsx
  • src/app/learning-plans/new.tsx
  • src/app/validation/overview.tsx
  • src/components/analytics-identity.tsx
  • src/context/AuthContext.tsx
  • src/features/learning-plans/constants.ts
  • src/features/learning-plans/types.ts
  • src/lib/analytics-core.ts
  • src/lib/analytics.ts
  • src/lib/runtime-config.test.ts
  • src/lib/runtime-config.ts
  • src/types/dayEntries.ts
  • src/types/validation.ts

Comment thread convex/learningPlans.ts Outdated
Comment thread convex/validationAnalytics.ts
Comment thread docs/contexts/integrations/CONTEXT.md Outdated
Comment thread src/app/validation/overview.tsx Outdated
Comment thread src/components/analytics-identity.tsx Outdated
Comment thread src/lib/runtime-config.test.ts

Copy link
Copy Markdown
Collaborator Author

PostHog dashboard follow-up

I evaluated whether to create the PostHog dashboard directly through the PostHog MCP tools. The tool surface can create dashboards and saved insights, but PostHog's schema currently only shows one of the planned validation events: homework_created. The rest of this PR's validation event taxonomy is not visible yet, so creating the dashboard now would mostly produce empty or misleading tiles.

Recommended path: merge/deploy this PR, run one smoke pass that emits the validation events, then paste the prompt below into the PostHog dashboard agent.

Create a pinned dashboard called “Dayova Validation Analytics”.

Before creating anything:
1. Verify these events exist in the project schema:
   onboarding_completed
   homework_created
   exam_created
   material_uploaded
   study_plan_generated
   study_slot_started
   study_slot_completed
   study_slot_partially_completed
   study_slot_missed
   missed_reason_selected
   plan_adjusted
   user_returned_next_day
   generalprobe_completed

2. Verify these event/person properties where present:
   clerk_id
   validation_student_code
   learning_plan_id
   learning_plan_session_id
   phase
   planned_day_key
   start_time
   duration_minutes
   subject
   exam_type_label
   exam_date_key
   file_type
   file_size_bytes
   outcome
   outcome_at
   missed_reason
   adjusted_at
   previous_activity_day_key

If fewer than half of the expected events exist, stop and report which events are missing. Do not create the dashboard yet.

If the events exist, create the dashboard with these tiles:

1. “Validation Funnel”
   Funnel over the last 30 days:
   onboarding_completed
   exam_created OR homework_created
   material_uploaded
   study_plan_generated
   study_slot_started
   study_slot_completed OR study_slot_partially_completed
   user_returned_next_day

2. “Activation Overview”
   Bold-number tiles for the last 14 days:
   unique users with onboarding_completed
   unique users with study_plan_generated
   unique users with study_slot_started
   unique users with study_slot_completed or study_slot_partially_completed
   unique users with user_returned_next_day

3. “Daily Validation Event Volume”
   Line chart over the last 30 days with separate series for:
   onboarding_completed
   homework_created
   exam_created
   material_uploaded
   study_plan_generated
   study_slot_started
   study_slot_completed
   study_slot_partially_completed
   study_slot_missed
   plan_adjusted
   user_returned_next_day
   generalprobe_completed

4. “Study Slot Outcomes”
   Stacked bar or line chart over the last 30 days:
   study_slot_completed
   study_slot_partially_completed
   study_slot_missed

5. “Missed Reasons”
   Breakdown of missed_reason_selected by missed_reason over the last 30 days.

6. “Recovery Loop”
   Trend over the last 30 days:
   study_slot_missed
   plan_adjusted
   study_slot_started
   study_slot_completed
   Use separate series, not a funnel.

7. “Material Uploads”
   Trend of material_uploaded over the last 30 days, with breakdown by file_type if available.
   Add a second metric/table for average or median file_size_bytes if PostHog supports it cleanly.

8. “Generalprobe Completion”
   Trend and bold-number tile for generalprobe_completed over the last 30 days.

9. “Validation Cohort Activity Table”
   Create a HogQL/table insight grouped by validation_student_code:
   columns should include validation_student_code, total validation events, last event timestamp, study slots started, study slots completed/partially completed, missed slots, recovery plans, next-day returns.
   Use the last 30 days.
   Exclude rows where validation_student_code is empty/null if possible.

10. “Event Health / Instrumentation”
   Table showing counts for every expected validation event over the last 7 days.
   This should make missing or broken instrumentation obvious.

Dashboard settings:
- Pin the dashboard.
- Add tags: validation, analytics, dayova.
- Use a dashboard description:
  “Tracks Dayova validation cohort activation, learning-plan execution, recovery loops, next-day returns, and instrumentation health. Events come from PR #144 validation analytics.”
- Mark event definitions as verified where the events exist.
- Add short descriptions to each event definition explaining when it fires.

Follow-up issue for backend-confirmed analytics transport: #146.

@FleetAdmiralJakob
FleetAdmiralJakob changed the base branch from main to release-v-1.0.3 June 26, 2026 18:50
@FleetAdmiralJakob

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/learning-plans/`[planId]/sessions/[sessionId]/index.tsx:
- Around line 846-848: `completeAndLeave` can race with the auto-start path and
call `startSession` twice, which can duplicate `study_slot_started` and conflict
with `recordSessionOutcome`. Add a single shared helper/promise for starting
tracking in `index.tsx` so both the auto-start effect and `completeAndLeave` use
the same in-flight `startSession` call instead of rechecking
`content.session.executionStatus` and firing a second mutation. Update the logic
around `didStartTrackingRef`, `completeAndLeave`, and the auto-start effect to
await the shared guard before proceeding.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35685912-8d9e-4558-a60d-156b41b747d8

📥 Commits

Reviewing files that changed from the base of the PR and between 440d321 and 1bee84a.

⛔ Files ignored due to path filters (2)
  • convex/_generated/api.d.ts is excluded by !**/_generated/**
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (17)
  • app.config.ts
  • convex/dayEntries.ts
  • convex/learningPlans.test.ts
  • convex/learningPlans.ts
  • convex/learningSessionContent.ts
  • convex/schema.ts
  • convex/users.ts
  • docs/contexts/product/CONTEXT.md
  • package.json
  • src/app/_layout.tsx
  • src/app/entry/new.tsx
  • src/app/learning-plans/[planId]/generating.tsx
  • src/app/learning-plans/[planId]/sessions/[sessionId]/index.tsx
  • src/app/learning-plans/new.tsx
  • src/context/AuthContext.tsx
  • src/features/learning-plans/replan-recovery.test.ts
  • src/features/learning-plans/types.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/contexts/product/CONTEXT.md
🚧 Files skipped from review as they are similar to previous changes (11)
  • package.json
  • app.config.ts
  • src/app/learning-plans/[planId]/generating.tsx
  • src/app/learning-plans/new.tsx
  • src/app/entry/new.tsx
  • src/app/_layout.tsx
  • convex/schema.ts
  • convex/dayEntries.ts
  • convex/learningPlans.test.ts
  • src/context/AuthContext.tsx
  • convex/learningPlans.ts

Comment on lines 846 to +848
const didEnsureRef = useRef(false);
const didAutoFinishRef = useRef(false);
const didStartTrackingRef = useRef(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Race: completeAndLeave can double-fire startSession (and duplicate study_slot_started).

The auto-start effect sets didStartTrackingRef.current synchronously but the actual startSession mutation is still in flight when completeAndLeave independently re-checks content.session.executionStatus === "notStarted" (stale until the query resubscribes) and calls startSession again. This can duplicate the study_slot_started analytics event and/or race recordSessionOutcome against a second startSession call, potentially surfacing a spurious completion error to the user.

Consolidate both call sites onto a single guarded/deduplicated helper so completeAndLeave awaits the same in-flight call instead of firing a second one.

🔧 Proposed fix: share a single start-tracking promise
 	const didStartTrackingRef = useRef(false);
+	const startSessionPromiseRef = useRef<ReturnType<typeof startSession> | null>(
+		null,
+	);
+
+	const ensureSessionStarted = useCallback(() => {
+		if (!sessionId) return Promise.reject(new Error("Missing sessionId"));
+		if (!startSessionPromiseRef.current) {
+			didStartTrackingRef.current = true;
+			startSessionPromiseRef.current = startSession({ sessionId })
+				.then((started) => {
+					void capture(
+						"study_slot_started",
+						definedAnalyticsProperties({
+							...learningSessionAnalyticsProperties(started),
+							started_at: started.startedAt,
+						}),
+					);
+					return started;
+				})
+				.catch((error) => {
+					didStartTrackingRef.current = false;
+					startSessionPromiseRef.current = null;
+					throw error;
+				});
+		}
+		return startSessionPromiseRef.current;
+	}, [capture, sessionId, startSession]);
 	useEffect(() => {
 		if (
 			!sessionId ||
 			!content ||
 			content.session.executionStatus !== "notStarted" ||
 			didStartTrackingRef.current
 		)
 			return;

-		didStartTrackingRef.current = true;
-		void startSession({ sessionId })
-			.then((result) => {
-				void capture(
-					"study_slot_started",
-					definedAnalyticsProperties({
-						...learningSessionAnalyticsProperties(result),
-						started_at: result.startedAt,
-					}),
-				);
-			})
-			.catch((error: unknown) => {
-				didStartTrackingRef.current = false;
-				logDiagnosticError("Failed to start learning session tracking.", error, {
-					source: "learningSession.startSession",
-					level: "warn",
-				});
-			});
-	}, [capture, content, sessionId, startSession]);
+		void ensureSessionStarted().catch((error: unknown) => {
+			logDiagnosticError("Failed to start learning session tracking.", error, {
+				source: "learningSession.startSession",
+				level: "warn",
+			});
+		});
+	}, [content, ensureSessionStarted, sessionId]);
 			if (content?.session.executionStatus === "notStarted") {
-				const started = await startSession({ sessionId });
-				didStartTrackingRef.current = true;
-				void capture(
-					"study_slot_started",
-					definedAnalyticsProperties({
-						...learningSessionAnalyticsProperties(started),
-						started_at: started.startedAt,
-					}),
-				);
+				await ensureSessionStarted();
 			}

Also applies to: 947-974, 1056-1076

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/learning-plans/`[planId]/sessions/[sessionId]/index.tsx around lines
846 - 848, `completeAndLeave` can race with the auto-start path and call
`startSession` twice, which can duplicate `study_slot_started` and conflict with
`recordSessionOutcome`. Add a single shared helper/promise for starting tracking
in `index.tsx` so both the auto-start effect and `completeAndLeave` use the same
in-flight `startSession` call instead of rechecking
`content.session.executionStatus` and firing a second mutation. Update the logic
around `didStartTrackingRef`, `completeAndLeave`, and the auto-start effect to
await the shared guard before proceeding.

@FleetAdmiralJakob

Copy link
Copy Markdown
Collaborator Author

CodeRabbit Follow-up Applied

Fixed the valid CodeRabbit finding about duplicate startSession calls in the learning-session screen.

Files modified:

  • src/app/learning-plans/[planId]/sessions/[sessionId]/index.tsx

Commit: c6e7ad9

Validation run locally:

  • pnpm typecheck
  • pnpm test
  • pnpm lint

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment