Skip to content

feat: Add update modal settings for major releases only#985

Merged
chhoumann merged 1 commit intomasterfrom
feat/update-modal-settings-major-releases
Nov 14, 2025
Merged

feat: Add update modal settings for major releases only#985
chhoumann merged 1 commit intomasterfrom
feat/update-modal-settings-major-releases

Conversation

@chhoumann
Copy link
Copy Markdown
Owner

@chhoumann chhoumann commented Nov 14, 2025

Summary

This PR implements a new setting to control when the update modal is shown, addressing issue #447. Users can now choose to see update modals only on major releases (new features, breaking changes) instead of every update.

Changes

Settings UI

  • Replaced toggle with dropdown: The "Announce Updates" setting now uses a dropdown with three options:
    • Show updates on each new release (default, maintains current behavior)
    • Show updates only on major releases (new features, breaking changes)
    • Don't show (disables update modals)

Implementation

  • New semver utility (src/utils/semver.ts): Robust semantic version parsing that handles:
    • Standard versions (e.g., 2.7.0)
    • Pre-release versions (e.g., 2.7.0-beta.1)
    • Build metadata (e.g., 2.7.0+123)
    • Invalid version strings (returns null for graceful error handling)
  • Version comparison logic: Added isMajorUpdate() function that compares major version numbers
  • Migration support: Automatically migrates legacy boolean announceUpdates values:
    • true"all"
    • false"none"

Testing

  • Added comprehensive test suite for semver utility (12 test cases, all passing)
  • Updated test mocks to use new string type instead of boolean
  • All existing tests continue to pass

Technical Details

The implementation uses a dedicated utility function (isMajorUpdate) that:

  1. Parses semantic versions using a robust parser that handles edge cases
  2. Compares major version numbers to detect major updates
  3. Defaults to showing updates when version parsing fails (errs on the side of caution)

This approach is more maintainable and testable than inline version parsing, and handles edge cases like pre-release versions and build metadata properly.

Migration Impact

  • Backward compatible: Existing users with boolean announceUpdates settings will be automatically migrated
  • Default behavior preserved: New installs default to "all", maintaining current behavior
  • No breaking changes: All existing functionality continues to work as before

Testing

  • ✅ All unit tests pass
  • ✅ Linting passes
  • ✅ Type checking passes
  • ✅ Manual testing of dropdown options
  • ✅ Migration logic tested with legacy boolean values

Closes #447

Summary by CodeRabbit

  • New Features

    • Replaced the update announcement toggle with a dropdown menu offering three options: "All" for all updates, "Major" for major version updates only, and "None" to disable notifications.
  • Tests

    • Updated test configurations to reflect the new announcement settings structure.

- Replace boolean announceUpdates setting with dropdown (all/major/none)
- Add robust semver parsing utility with comprehensive test coverage
- Migrate legacy boolean values to new string preference
- Show update modal only on major version bumps when 'major' option selected

Closes #447
@vercel
Copy link
Copy Markdown

vercel Bot commented Nov 14, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
quickadd Ready Ready Preview Nov 14, 2025 2:28pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 14, 2025

Walkthrough

The PR converts the announceUpdates setting from a boolean to a string-based option ("all", "major", or "none"), enabling granular control over update notifications. It introduces semantic versioning utilities to detect major version bumps and updates the announcement logic to conditionally surface notifications based on the selected policy and version change type.

Changes

Cohort / File(s) Summary
Test mock updates
src/engine/CaptureChoiceEngine.notice.test.ts, src/engine/CaptureChoiceEngine.template-property-types.test.ts, src/engine/MacroChoiceEngine.notice.test.ts, src/engine/TemplateChoiceEngine.notice.test.ts
Updated mock QuickAddSettings to use announceUpdates: "all" instead of announceUpdates: true, reflecting the type change from boolean to string.
Settings type & UI
src/quickAddSettingsTab.ts
Changed QuickAddSettings.announceUpdates from boolean to "all" | "major" | "none"; updated DEFAULT_SETTINGS to "all"; replaced toggle with dropdown menu in settings UI with three options.
Main plugin logic
src/main.ts
Added import of isMajorUpdate; enhanced loadSettings to normalize boolean values to string equivalents ("all" or "none"); updated announceUpdate to conditionally announce based on announceUpdates value and major version detection.
Semver utilities
src/utils/semver.ts, src/utils/semver.test.ts
Added new module with ParsedVersion interface, parseSemver() function (parses semver strings, strips pre-release/build metadata, returns null on invalid input), and isMajorUpdate() function (compares major version components, defaults to true on parse failure). Comprehensive test suite covering standard versions, pre-release/build metadata, invalid inputs, and major update detection scenarios.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Settings as Settings UI
    participant Main as Plugin (main.ts)
    participant Semver as Semver Utils
    
    User->>Settings: Select announcement option
    Settings->>Main: Store announceUpdates ("all"/"major"/"none")
    
    Note over Main: New version detected
    Main->>Semver: isMajorUpdate(currentVersion, previousVersion)
    Semver->>Semver: parseSemver(currentVersion)
    Semver->>Semver: parseSemver(previousVersion)
    Semver-->>Main: boolean (major bump detected?)
    
    Main->>Main: Compute shouldAnnounce based on<br/>announceUpdates + isMajorUpdate result
    
    alt announceUpdates = "all"
        Main->>User: Show update notice
    else announceUpdates = "major" + isMajorUpdate = true
        Main->>User: Show update notice
    else announceUpdates = "major" + isMajorUpdate = false
        Main->>User: Skip notice
    else announceUpdates = "none"
        Main->>User: Skip notice
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Semver utility module: Logic for parsing semantic versions and detecting major updates should be verified for correctness across edge cases (pre-release, build metadata, invalid inputs, null handling)
  • Settings normalization in loadSettings: Verify that boolean-to-string conversion correctly handles existing user settings with boolean values for backwards compatibility
  • Conditional announcement logic in main.ts: Ensure the shouldAnnounce flag computation aligns correctly with all three announcement modes and properly integrates with the major version detection
  • UI dropdown change: Confirm that the three options map correctly to the new string values and that onChange handler properly casts/stores the selection
  • Test mock consistency: Verify all test files now use the updated mock structure consistently

Poem

🐰 Updates now have finer taste,
From boolean to string at last!
"All" or "major" paths we trace,
Semver logic sets the pace,
Users choose their update grace! 🎉

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding settings to control update modal display with focus on major releases only.
Linked Issues check ✅ Passed The PR fully implements the requested feature from issue #447: dropdown with three options (show all updates, major only, none) for controlling update modal behavior.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the feature: new semver utility, settings type changes, UI dropdown, migration logic, and corresponding test updates.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/update-modal-settings-major-releases

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/engine/TemplateChoiceEngine.notice.test.ts (1)

3-10: Mock value looks good; consider centralizing shared settings mock

announceUpdates: "all" correctly reflects the new union type and default behavior. As this same DEFAULT_SETTINGS shape is duplicated across multiple tests, you might eventually extract a shared createMockQuickAddSettings()/mock module to keep these in sync more easily when fields change.

src/quickAddSettingsTab.ts (1)

168-189: Dropdown wiring for update announcements is correct; minor typing tweak possible

The dropdown correctly:

  • Reads the current value from settingsStore.
  • Offers the three expected options ("all", "major", "none").
  • Writes back a value consistent with QuickAddSettings["announceUpdates"].

To avoid the explicit type assertion, you could optionally centralize the allowed keys in a typed helper (e.g., a const ANNOUNCE_OPTIONS = ["all","major","none"] as const) and narrow value via that, but the current implementation is entirely fine.

src/utils/semver.ts (1)

25-60: Well-implemented parsing logic with good edge case handling.

The function correctly strips pre-release and build metadata, validates input thoroughly, and handles various edge cases (empty strings, invalid formats, negative numbers). The defensive programming with optional chaining and fallback values is solid.

One minor spec deviation: the official semver spec prohibits leading zeros in numeric identifiers (e.g., "01.2.3" is invalid), but this implementation would parse "01" as 1. This is likely fine for practical use since plugin versions rarely have leading zeros, and the tolerant behavior is reasonable.

If strict semver compliance is desired, you could add a leading-zero check:

 	const major = Number.parseInt(parts[0] ?? "", 10);
 	const minor = Number.parseInt(parts[1] ?? "", 10);
 	const patch = Number.parseInt(parts[2] ?? "", 10);
 
+	// Semver spec: numeric identifiers MUST NOT include leading zeroes
+	if (parts[0]?.startsWith("0") && parts[0] !== "0" ||
+	    parts[1]?.startsWith("0") && parts[1] !== "0" ||
+	    parts[2]?.startsWith("0") && parts[2] !== "0") {
+		return null;
+	}
+
 	// Validate that all parts are valid numbers
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 06a77a3 and eaf5492.

📒 Files selected for processing (8)
  • src/engine/CaptureChoiceEngine.notice.test.ts (1 hunks)
  • src/engine/CaptureChoiceEngine.template-property-types.test.ts (1 hunks)
  • src/engine/MacroChoiceEngine.notice.test.ts (1 hunks)
  • src/engine/TemplateChoiceEngine.notice.test.ts (1 hunks)
  • src/main.ts (3 hunks)
  • src/quickAddSettingsTab.ts (4 hunks)
  • src/utils/semver.test.ts (1 hunks)
  • src/utils/semver.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/utils/semver.test.ts (1)
src/utils/semver.ts (2)
  • parseSemver (25-60)
  • isMajorUpdate (76-90)
src/main.ts (2)
src/quickAddSettingsTab.ts (2)
  • DEFAULT_SETTINGS (56-91)
  • QuickAddSettings (14-54)
src/utils/semver.ts (1)
  • isMajorUpdate (76-90)
src/quickAddSettingsTab.ts (1)
src/settingsStore.ts (1)
  • settingsStore (7-20)
🔇 Additional comments (9)
src/engine/MacroChoiceEngine.notice.test.ts (1)

3-10: Mock default settings correctly align with new announceUpdates type

Switching announceUpdates to "all" in the mocked DEFAULT_SETTINGS matches the runtime union type and default, so tests stay in sync with the settings shape without changing behavior.

src/engine/CaptureChoiceEngine.notice.test.ts (1)

3-10: Test settings mock updated to string-based announceUpdates

Using "all" here keeps the mock compatible with the new "all" | "major" | "none" type and with the production default.

src/engine/CaptureChoiceEngine.template-property-types.test.ts (1)

8-15: announceUpdates mock value matches new default

Setting announceUpdates to "all" in this mock keeps the test harness aligned with the real settings type and default without affecting the template-property-types behavior under test.

src/main.ts (2)

196-211: Settings load and boolean-to-string migration look correct

Merging loadedData into DEFAULT_SETTINGS and then normalizing a legacy boolean announceUpdates to "all"/"none" cleanly preserves existing behavior while moving to the new string union. The narrow type cast on settings keeps this migration local to loadSettings without widening the global settings type.


302-321: Update announcement logic now cleanly respects user preference and major bumps

Deriving shouldAnnounce from announceUpdates and delegating major-bump detection to isMajorUpdate(currentVersion, knownVersion) matches the new semantics:

  • "none": always suppress.
  • "all": preserve previous “always show on change” behavior.
  • "major": only show when the major version increases, defaulting to “show” on parse failures.

Persisting this.settings.version = currentVersion before returning ensures upgrades/downgrades don’t retrigger unnecessarily. This aligns well with the PR’s objective.

src/quickAddSettingsTab.ts (1)

14-21: announceUpdates type and default now match new behavior

Updating QuickAddSettings.announceUpdates to the "all" | "major" | "none" union and setting the default to "all" keeps the settings contract consistent with the new announcement flow in main.ts and maintains previous behavior for new installs.

Also applies to: 56-63

src/utils/semver.test.ts (1)

1-75: Semver tests thoroughly cover expected parsing and major-update behavior

This suite does a good job exercising parseSemver and isMajorUpdate, including pre-release/build metadata handling, invalid inputs, null/undefined, negative numbers, major vs minor/patch bumps, downgrades, and the “err on the side of caution” path when parsing fails. It gives strong confidence that the announcement gating logic built on top will behave as intended.

src/utils/semver.ts (2)

7-11: LGTM!

The interface is clean and captures the essential components of a semantic version.


76-90: LGTM! Conservative fallback is well-reasoned.

The major version comparison logic is correct, and the conservative fallback behavior (returning true when parsing fails) is clearly documented and aligns with the goal of not missing important updates due to parsing issues. This is a sensible default that prioritizes user awareness over strict filtering.

The function correctly identifies only increases in the major version number, so minor and patch updates will return false as expected.

@chhoumann chhoumann merged commit d63f8c9 into master Nov 14, 2025
4 checks passed
@chhoumann chhoumann deleted the feat/update-modal-settings-major-releases branch November 14, 2025 15:50
github-actions Bot pushed a commit that referenced this pull request Nov 14, 2025
# [2.8.0](2.7.0...2.8.0) (2025-11-14)

### Bug Fixes

* restore Insert After matching for table separator rows ([#983](#983)) ([1393e6a](1393e6a)), closes [#970](#970)
* support frontmatter tags in getFieldValues filtering ([#980](#980)) ([c9de468](c9de468)), closes [#927](#927)

### Features

* Add embed replacement option for link placement ([#984](#984)) ([06a77a3](06a77a3)), closes [#893](#893)
* add provider-native model discovery ([#982](#982)) ([f195c06](f195c06))
* add textarea type for advanced script settings ([#981](#981)) ([dc9a650](dc9a650))
* add update modal settings for major releases only ([#985](#985)) ([d63f8c9](d63f8c9)), closes [#447](#447)
* add write to top of file switch for capture to active file ([#986](#986)) ([5361e6c](5361e6c)), closes [#248](#248) [#248](#248)
@github-actions
Copy link
Copy Markdown

🎉 This PR is included in version 2.8.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setting for update modal - show only on major or above releases

1 participant