feat: add 'Latest Only' dropdown to release timeline#219
Conversation
Add a new dropdown next to existing filters that allows showing only the latest release per repository. The setting is persisted and works together with existing filters and unread-only toggle. When user marks the latest release of a repo as read in 'Latest Only' mode, all other releases of that repo are also marked as read. Changes: - Add releaseLatestMode state to AppState and persist it - Add setReleaseLatestMode action to store - Add latest-only filtering logic in ReleaseTimeline - Add dropdown UI with mutual exclusion with other dropdowns - Cascade mark-as-read when in latest mode
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a persisted ChangesRelease Timeline Latest Mode Filter
Sequence Diagram(s)sequenceDiagram
participant User
participant ReleaseTimeline
participant Store
participant Data
User->>ReleaseTimeline: toggle "Latest Mode" option
ReleaseTimeline->>ReleaseTimeline: handleLatestModeChange('latest')
ReleaseTimeline->>Store: setReleaseLatestMode('latest')
Store->>Store: update and persist releaseLatestMode
Data->>ReleaseTimeline: state/data subscription updated
ReleaseTimeline->>ReleaseTimeline: run latestModeReleases memo (group by repo, keep latest)
ReleaseTimeline->>ReleaseTimeline: apply unread snapshot filter if enabled
ReleaseTimeline->>User: render filtered releases
User->>ReleaseTimeline: mark release as read
ReleaseTimeline->>Store: markReleaseAsRead(releaseId)
Store->>Store: if in 'latest' and releaseId is repo latest -> mark all repo releases read
Store->>ReleaseTimeline: emit updated read state
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a 'latest mode' filter to the release timeline, allowing users to view only the most recent release for each repository. It updates the application state, UI components, and read-marking logic to support this feature. The review feedback suggests optimizing performance and simplifying code by avoiding unnecessary Date object parsing and comparisons, leveraging string comparisons and existing list sorting instead.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const latestModeReleases = useMemo(() => { | ||
| if (releaseLatestMode !== 'latest') return preUnreadFilteredReleases; | ||
|
|
||
| const repoMap = new Map<number, typeof preUnreadFilteredReleases[0]>(); | ||
| for (const item of preUnreadFilteredReleases) { | ||
| const repoId = item.release.repository.id; | ||
| const existing = repoMap.get(repoId); | ||
| if (!existing || new Date(item.release.published_at) > new Date(existing.release.published_at)) { | ||
| repoMap.set(repoId, item); | ||
| } | ||
| } | ||
| return Array.from(repoMap.values()); | ||
| }, [preUnreadFilteredReleases, releaseLatestMode]); |
There was a problem hiding this comment.
Since preUnreadFilteredReleases is already sorted in descending order by published_at (newest first), the first release encountered for each repository is guaranteed to be the latest one. We can simplify this logic by checking if the repository ID is already in the map, completely avoiding the overhead of parsing and comparing Date objects.
const latestModeReleases = useMemo(() => {
if (releaseLatestMode !== 'latest') return preUnreadFilteredReleases;
const repoMap = new Map<number, typeof preUnreadFilteredReleases[0]>();
for (const item of preUnreadFilteredReleases) {
const repoId = item.release.repository.id;
if (!repoMap.has(repoId)) {
repoMap.set(repoId, item);
}
}
return Array.from(repoMap.values());
}, [preUnreadFilteredReleases, releaseLatestMode]);
| const latestRepoRelease = repoReleases.reduce((latest, r) => | ||
| new Date(r.published_at) > new Date(latest.published_at) ? r : latest | ||
| , repoReleases[0]); |
There was a problem hiding this comment.
Since published_at is an ISO 8601 string (always returned in UTC format from the GitHub API), we can compare the strings directly instead of parsing them into Date objects on every iteration of reduce. This is much more efficient and cleaner.
| const latestRepoRelease = repoReleases.reduce((latest, r) => | |
| new Date(r.published_at) > new Date(latest.published_at) ? r : latest | |
| , repoReleases[0]); | |
| const latestRepoRelease = repoReleases.reduce((latest, r) => | |
| r.published_at > latest.published_at ? r : latest | |
| , repoReleases[0]); |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/store/useAppStore.ts (2)
939-945:⚠️ Potential issue | 🟠 Major | ⚡ Quick winMissing initial state value for
releaseLatestMode.The initial state block does not include
releaseLatestMode. Without this, the state will beundefineduntil rehydration completes, which could cause runtime errors if the component accesses it before hydration.🐛 Proposed fix
Add the initial value after
releaseShowMode:releaseViewMode: 'timeline', releaseShowMode: 'all', +releaseLatestMode: 'all', releaseSelectedFilters: [],🤖 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/store/useAppStore.ts` around lines 939 - 945, The initial state is missing releaseLatestMode, causing undefined until rehydration; add a sensible default (e.g., 'all' or the intended enum/value) to the state object immediately after releaseShowMode so releaseLatestMode is defined on initialization and components won't access undefined before hydration completes; update the state initialization where releaseViewMode, releaseShowMode, releaseSelectedFilters, etc. are set (look for releaseShowMode and add releaseLatestMode alongside it).
1802-1808:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winMissing
releaseLatestModein partialize block — setting won't persist.The PR claims the setting is persisted via IndexedDB, but
releaseLatestModeis not included in thepartializefunction. The normalization at line 567 will always fall back to'all'on app restart since no value is ever saved.🐛 Proposed fix
Add
releaseLatestModeto the partialize block:releaseViewMode: state.releaseViewMode, releaseShowMode: state.releaseShowMode, +releaseLatestMode: state.releaseLatestMode, releaseSelectedFilters: state.releaseSelectedFilters,🤖 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/store/useAppStore.ts` around lines 1802 - 1808, The partialize state selector is missing releaseLatestMode so that preference never gets persisted; update the partialize block that currently returns releaseViewMode, releaseShowMode, releaseSelectedFilters, releaseSearchQuery, releaseExpandedRepositories, includePreRelease, includeKeysInBackup to also include releaseLatestMode so it is saved to IndexedDB and survives restarts (this will prevent the normalization logic that falls back to 'all' from overriding the user's saved setting).
🤖 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.
Outside diff comments:
In `@src/store/useAppStore.ts`:
- Around line 939-945: The initial state is missing releaseLatestMode, causing
undefined until rehydration; add a sensible default (e.g., 'all' or the intended
enum/value) to the state object immediately after releaseShowMode so
releaseLatestMode is defined on initialization and components won't access
undefined before hydration completes; update the state initialization where
releaseViewMode, releaseShowMode, releaseSelectedFilters, etc. are set (look for
releaseShowMode and add releaseLatestMode alongside it).
- Around line 1802-1808: The partialize state selector is missing
releaseLatestMode so that preference never gets persisted; update the partialize
block that currently returns releaseViewMode, releaseShowMode,
releaseSelectedFilters, releaseSearchQuery, releaseExpandedRepositories,
includePreRelease, includeKeysInBackup to also include releaseLatestMode so it
is saved to IndexedDB and survives restarts (this will prevent the normalization
logic that falls back to 'all' from overriding the user's saved setting).
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fd6ded3e-4829-459f-b4dc-34f7c299261d
📒 Files selected for processing (3)
src/components/ReleaseTimeline.tsxsrc/store/useAppStore.tssrc/types/index.ts
- Add releaseLatestMode to initial state (prevents undefined before hydration) - Add releaseLatestMode to partialize block (enables persistence) - Optimize Date comparisons: use ISO 8601 string comparison instead of Date objects
Summary
Add a new dropdown next to existing filters on the Release Timeline page that allows showing only the latest release per repository.
Changes
New Feature: Latest Only Mode
Filter Compatibility
Smart Mark-as-Read
UI
Files Changed
Summary by CodeRabbit