Skip to content

feat(sprint): add sprint progress tracking with scope change visibility#14

Merged
fathiraz merged 3 commits intomainfrom
feat/sprint-progress-tracking
Mar 29, 2026
Merged

feat(sprint): add sprint progress tracking with scope change visibility#14
fathiraz merged 3 commits intomainfrom
feat/sprint-progress-tracking

Conversation

@fathiraz
Copy link
Copy Markdown
Owner

@fathiraz fathiraz commented Mar 29, 2026

Summary

Add comprehensive sprint progress tracking to the Sprint widget with real-time metrics and scope change visibility.

Changes

New Features

  • Progress Bars: Visual completion percentage for issues and story points
  • Sprint Timer: Days remaining indicator with color-coded urgency (danger ≤1 day, attention ≤3 days)
  • Scope Change Tracking: Shows items added after sprint start with assignee avatars and points
  • Recently Added Items List: Displays up to 5 recently added items with tooltips and badges
  • Responsive Loading States: Spinner while loading, Flash warnings for errors

Technical Implementation

  • New

SprintProgressView

component for progress visualization

  • Background message handlers for

getSprintProgress

requests

  • GraphQL queries for sprint data and field configurations
  • Extended TypeScript types for sprint progress data

UI/UX Details

  • Follows Primer design system with semantic color tokens
  • Smooth CSS transitions with

reduced-motion support

  • Tippy.js tooltips for assignee avatars
  • Metric bars with animated progress indicators
  • Flat design with proper border separation (no shadows)

Files Changed

File Changes

|

src/components/sprint/sprint-modal.tsx

| Integration of progress view |
|

src/components/sprint/sprint-progress-view.tsx

| New component (312 lines) |
|

src/entries/background/index.ts

| Message handlers for progress data |
|

src/lib/graphql/queries.ts

| Sprint progress GraphQL queries |
|

src/lib/messages.ts

| Extended message types |
|

src/lib/storage.ts

| SprintSettings type addition |

Testing

  • Progress bars render correctly with mock data
  • Days remaining indicator shows correct colors
  • Scope change section conditionally renders
  • Assignee avatars display with tooltips
  • Error state shows Flash warning
  • Reduced motion preferences respected

Related

Part of the ongoing sprint management enhancements for Refined GitHub Projects.


Summary by cubic

Adds sprint progress tracking to the Sprint widget with clear completion metrics, days left, and accurate scope change based on a snapshot timestamp. Adds optional story points and a “not started” status for better progress math.

  • New Features

    • New SprintProgressView with progress bars for issues and story points.
    • Days-left label with danger/attention states.
    • Scope change: counts items added after the sprint snapshot (or start), shows up to 5 with assignee avatars and point badges.
    • Settings: selectable story points field and optional “not started” status; smooth loading/error states and reduced-motion support.
  • Bug Fixes

    • Guard async progress loads to avoid stale updates after unmount.
    • Prevent selecting the same option for Done and Not started.
    • Sprint progress caching keyed by all relevant settings and snapshot time (2‑min TTL); scope-change uses full ISO timestamps for same-day accuracy.
    • GraphQL query raises fieldValues to 50 to avoid missing fields on large projects.

Written for commit 2f53a93. Summary will update on new commits.

Add comprehensive sprint progress tracking to the Sprint widget:

- Progress bars showing completion percentage for issues and story points
- Visual indicators for days remaining in sprint (danger/attention/secondary)
- Scope change tracking showing recently added items after sprint start
- Assignee avatars with tooltips for recently added issues
- Points badges for items with story point values
- Metric bars with smooth CSS transitions respecting reduced-motion preference
- Error handling with Flash warnings for failed progress loads

Technical changes:
- New SprintProgressView component for displaying progress metrics
- Background message handlers for getSprintProgress requests
- GraphQL queries for fetching sprint progress data and field configurations
- Extended message types for SprintProgressData and SprintInfo
- SprintSettings type for configuration storage

UI follows Primer design system with proper color tokens, spacing,
and motion preferences support.
Copy link
Copy Markdown

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Cursor auto review

Found 2 actionable issue(s) on changed lines.

Adds sprint progress UI, background aggregation, and settings for points and “not started” handling. Two correctness risks: progress responses are cached without inputs that change the computation, and the new GraphQL query requests fewer fieldValues than existing sprint pagination (20 vs 50), which can omit iteration/status/points on busy boards.

Generated automatically when this PR was submitted using Cursor CLI with --model auto.

onMessage('getSprintProgress', async ({ data }) => {
logger.log('[rgp:bg] getSprintProgress received', data)

const cacheKey = `${data.projectId}/${data.iterationId}`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cache key is only projectId + iterationId, but the payload depends on settings (done field, points, not-started, sprintSnapshotAt) and sprintStartDate. After saving settings or acknowledging a sprint, a cache hit can return wrong metrics until TTL expires. Include a stable fingerprint of those inputs in the key, or clear/invalidate this cache in saveSprintSettings / acknowledgeUpcomingSprint.

assignees(first: 5) { nodes { login avatarUrl } }
}
}
fieldValues(first: 20) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

fieldValues(first: 20) can omit iteration, status, or number values when a project has many fields, so items may be excluded from the sprint set or never marked done. Align with GET_PROJECT_ITEMS_WITH_FIELDS (e.g. first: 50) or paginate field values until the configured field IDs are present.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/sprint/sprint-progress-view.tsx">

<violation number="1" location="src/components/sprint/sprint-progress-view.tsx:102">
P1: Guard async `sendMessage` results in `useEffect` to prevent stale responses (or post-unmount updates) from overwriting current sprint progress state.</violation>
</file>

<file name="src/components/sprint/sprint-modal.tsx">

<violation number="1" location="src/components/sprint/sprint-modal.tsx:274">
P1: Prevent choosing the same status for both Done and Not started; this configuration can invert sprint completion logic and show incorrect progress.</violation>
</file>

<file name="src/entries/background/index.ts">

<violation number="1" location="src/entries/background/index.ts:1491">
P2: Sprint progress cache key omits settings inputs, so recent setting changes can return stale metrics for up to 2 minutes.</violation>

<violation number="2" location="src/entries/background/index.ts:1602">
P2: Scope-change classification drops time-of-day precision, so same-day additions after snapshot can be missed.</violation>
</file>

<file name="src/lib/graphql/queries.ts">

<violation number="1" location="src/lib/graphql/queries.ts:205">
P1: `fieldValues` is capped too low for sprint progress and can omit required fields, producing incorrect metrics on projects with many fields.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Fixes the following issues identified by cubic-dev-ai:

1. **sprint-progress-view.tsx**: Guard async sendMessage results in useEffect
   - Add cancelled flag to prevent stale responses from overwriting state
   - Skip state updates if component unmounted or dependencies changed

2. **sprint-modal.tsx**: Prevent choosing same status for Done and Not started
   - Filter out the selected done option from the not started dropdown
   - Show validation error if user attempts to select the same option

3. **background/index.ts**: Include settings in sprint progress cache key
   - Cache key now includes all settings that affect computation:
     sprintFieldId, doneFieldId, doneOptionId, notStartedOptionId,
     pointsFieldId, and sprintSnapshotAt
   - Prevents stale metrics when settings change

4. **background/index.ts**: Fix scope-change classification time precision
   - Use full ISO timestamp instead of date-only comparison
   - Same-day additions after the snapshot time are now correctly identified

5. **graphql/queries.ts**: Increase fieldValues cap from 20 to 50
   - Prevents omission of required fields on projects with many fields
   - Aligns with existing sprint pagination patterns
Copy link
Copy Markdown
Owner Author

@fathiraz fathiraz left a comment

Choose a reason for hiding this comment

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

@cubic-dev-ai All 5 issues have been addressed:

  1. sprint-progress-view.tsx:102 - Added cancelled flag guard to prevent stale async responses from overwriting state. The useEffect now returns a cleanup function that sets cancelled=true, and all .then/.catch handlers check this flag before updating state.

  2. sprint-modal.tsx:274 - Implemented validation to prevent selecting the same status for both Done and Not started:

    • Filtered out the selected done option from the not started dropdown options
    • Added validation check in onChange handler that shows an error if user attempts to select the same option
  3. background/index.ts:1491 - Extended cache key to include all settings that affect computation:

    • Cache key now includes: sprintFieldId, doneFieldId, doneOptionId, notStartedOptionId, pointsFieldId, and sprintSnapshotAt
    • This ensures setting changes invalidate the cache immediately instead of returning stale metrics for up to 2 minutes
  4. background/index.ts:1602 - Fixed scope-change classification time precision:

    • Changed from date-only comparison (slice(0,10)) to full ISO timestamp comparison
    • Same-day additions after the snapshot time are now correctly identified as scope changes
  5. graphql/queries.ts:205 - Increased fieldValues cap from 20 to 50:

    • Prevents omission of required fields on projects with many fields
    • Aligns with existing sprint pagination patterns

@fathiraz
Copy link
Copy Markdown
Owner Author

@cubic-dev-ai Please re-run the review to verify all 5 issues have been resolved. All changes have been committed and pushed to this branch.

@cubic-dev-ai
Copy link
Copy Markdown

cubic-dev-ai bot commented Mar 29, 2026

@cubic-dev-ai Please re-run the review to verify all 5 issues have been resolved. All changes have been committed and pushed to this branch.

@fathiraz I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

3 issues found across 6 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/components/sprint/sprint-modal.tsx">

<violation number="1" location="src/components/sprint/sprint-modal.tsx:170">
P1: Re-validate `notStartedOptionId` on save; it can match `doneOptionId` after done-option changes and persist an invalid configuration.</violation>
</file>

<file name="src/entries/background/index.ts">

<violation number="1" location="src/entries/background/index.ts:1491">
P2: Sprint progress cache key omits settings that affect done-calculation, which can return stale metrics after settings changes.</violation>

<violation number="2" location="src/entries/background/index.ts:1585">
P2: Done-status logic is inconsistent: sprint progress counts non-"not started" statuses as done, but end-sprint logic requires the configured done option.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

excludeConditions: excludeConditions.filter((c) => c.fieldId && (c.optionId || c.optionName.trim())),
pointsFieldId: selectedPointsField?.id,
pointsFieldName: selectedPointsField?.name,
notStartedOptionId: notStartedOptionId || undefined,
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

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

P1: Re-validate notStartedOptionId on save; it can match doneOptionId after done-option changes and persist an invalid configuration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/components/sprint/sprint-modal.tsx, line 170:

<comment>Re-validate `notStartedOptionId` on save; it can match `doneOptionId` after done-option changes and persist an invalid configuration.</comment>

<file context>
@@ -160,6 +165,12 @@ function SettingsView({ projectId, owner, isOrg, number, getFields, currentSetti
         excludeConditions: excludeConditions.filter((c) => c.fieldId && (c.optionId || c.optionName.trim())),
+        pointsFieldId: selectedPointsField?.id,
+        pointsFieldName: selectedPointsField?.name,
+        notStartedOptionId: notStartedOptionId || undefined,
+        notStartedOptionName: notStartedOptionId
+          ? (selectedDoneField?.options?.find((o) => o.id === notStartedOptionId)?.name ?? undefined)
</file context>
Suggested change
notStartedOptionId: notStartedOptionId || undefined,
notStartedOptionId: notStartedOptionId && notStartedOptionId !== doneOptionId ? notStartedOptionId : undefined,
Fix with Cubic

Comment on lines +1491 to +1498
const settingsHash = [
data.settings.sprintFieldId,
data.settings.doneFieldId,
data.settings.doneOptionId,
data.settings.notStartedOptionId,
data.settings.pointsFieldId,
data.settings.sprintSnapshotAt ?? data.sprintStartDate,
].join('|')
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

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

P2: Sprint progress cache key omits settings that affect done-calculation, which can return stale metrics after settings changes.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/entries/background/index.ts, line 1491:

<comment>Sprint progress cache key omits settings that affect done-calculation, which can return stale metrics after settings changes.</comment>

<file context>
@@ -1478,11 +1480,172 @@ export default defineBackground(() => {
+  onMessage('getSprintProgress', async ({ data }) => {
+    logger.log('[rgp:bg] getSprintProgress received', data)
+
+    const settingsHash = [
+      data.settings.sprintFieldId,
+      data.settings.doneFieldId,
</file context>
Suggested change
const settingsHash = [
data.settings.sprintFieldId,
data.settings.doneFieldId,
data.settings.doneOptionId,
data.settings.notStartedOptionId,
data.settings.pointsFieldId,
data.settings.sprintSnapshotAt ?? data.sprintStartDate,
].join('|')
const settingsHash = [
data.settings.sprintFieldId,
data.settings.doneFieldId,
data.settings.doneFieldType,
data.settings.doneOptionId,
data.settings.doneOptionName,
data.settings.notStartedOptionId,
data.settings.pointsFieldId,
data.settings.pointsFieldName,
data.settings.sprintSnapshotAt ?? data.sprintStartDate,
].join('|')
Fix with Cubic

if (data.settings.doneFieldType === 'SINGLE_SELECT' && 'optionId' in doneFieldValue) {
// If a "not started" option is configured, everything except that option counts as done
if (data.settings.notStartedOptionId) {
return doneFieldValue.optionId !== data.settings.notStartedOptionId
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai bot Mar 29, 2026

Choose a reason for hiding this comment

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

P2: Done-status logic is inconsistent: sprint progress counts non-"not started" statuses as done, but end-sprint logic requires the configured done option.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/entries/background/index.ts, line 1585:

<comment>Done-status logic is inconsistent: sprint progress counts non-"not started" statuses as done, but end-sprint logic requires the configured done option.</comment>

<file context>
@@ -1478,11 +1480,172 @@ export default defineBackground(() => {
+      if (data.settings.doneFieldType === 'SINGLE_SELECT' && 'optionId' in doneFieldValue) {
+        // If a "not started" option is configured, everything except that option counts as done
+        if (data.settings.notStartedOptionId) {
+          return doneFieldValue.optionId !== data.settings.notStartedOptionId
+        }
+        return doneFieldValue.optionId === data.settings.doneOptionId
</file context>
Suggested change
return doneFieldValue.optionId !== data.settings.notStartedOptionId
return doneFieldValue.optionId === data.settings.doneOptionId
Fix with Cubic

* main:
  feat(bulk): add bulk random assign functionality (#13)
@fathiraz fathiraz merged commit 081d956 into main Mar 29, 2026
3 checks passed
@fathiraz fathiraz deleted the feat/sprint-progress-tracking branch March 30, 2026 08:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant