feat(sprint): add sprint progress tracking with scope change visibility#14
feat(sprint): add sprint progress tracking with scope change visibility#14
Conversation
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.
There was a problem hiding this comment.
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.
src/entries/background/index.ts
Outdated
| onMessage('getSprintProgress', async ({ data }) => { | ||
| logger.log('[rgp:bg] getSprintProgress received', data) | ||
|
|
||
| const cacheKey = `${data.projectId}/${data.iterationId}` |
There was a problem hiding this comment.
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.
src/lib/graphql/queries.ts
Outdated
| assignees(first: 5) { nodes { login avatarUrl } } | ||
| } | ||
| } | ||
| fieldValues(first: 20) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
fathiraz
left a comment
There was a problem hiding this comment.
@cubic-dev-ai All 5 issues have been addressed:
-
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.
-
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
-
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
-
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
-
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
|
@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. |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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>
| notStartedOptionId: notStartedOptionId || undefined, | |
| notStartedOptionId: notStartedOptionId && notStartedOptionId !== doneOptionId ? notStartedOptionId : undefined, |
| const settingsHash = [ | ||
| data.settings.sprintFieldId, | ||
| data.settings.doneFieldId, | ||
| data.settings.doneOptionId, | ||
| data.settings.notStartedOptionId, | ||
| data.settings.pointsFieldId, | ||
| data.settings.sprintSnapshotAt ?? data.sprintStartDate, | ||
| ].join('|') |
There was a problem hiding this comment.
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>
| 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('|') |
| 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 |
There was a problem hiding this comment.
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>
| return doneFieldValue.optionId !== data.settings.notStartedOptionId | |
| return doneFieldValue.optionId === data.settings.doneOptionId |
* main: feat(bulk): add bulk random assign functionality (#13)
Summary
Add comprehensive sprint progress tracking to the Sprint widget with real-time metrics and scope change visibility.
Changes
New Features
Technical Implementation
SprintProgressView
component for progress visualization
getSprintProgress
requests
UI/UX Details
reduced-motion support
Files Changed
|
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
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
Bug Fixes
Written for commit 2f53a93. Summary will update on new commits.