Skip to main content

vtcode_desktop_protocol/
update.rs

1//! Cached updater-status DTOs.
2
3use serde::{Deserialize, Serialize};
4
5/// Cached updater status for the desktop client. The service owns update
6/// policy (channel, pin, check interval); this snapshot never triggers
7/// network I/O, it only reports the last cached check.
8#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct UpdateStatusSnapshot {
11    /// Version of the running service binary.
12    pub current_version: String,
13    /// Latest known release version, when a check has reported one.
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub latest_version: Option<String>,
16    /// Whether `latest_version` is newer than `current_version` and not dismissed.
17    #[serde(default)]
18    pub update_available: bool,
19    /// HTTPS release page for `latest_version`, when known.
20    #[serde(default, skip_serializing_if = "Option::is_none")]
21    pub release_url: Option<String>,
22    /// Parsed `### Highlights` bullets for `latest_version`, taken from the
23    /// cached release notes. Empty when the cache has no notes (or no
24    /// Highlights section); clients render a link-only state then.
25    #[serde(default, skip_serializing_if = "Vec::is_empty")]
26    pub release_highlights: Vec<String>,
27    /// Parsed `### Highlights` bullets for the running `current_version`,
28    /// for the post-update "what's new" moment. Empty unless the cache holds
29    /// notes describing exactly the running version.
30    #[serde(default, skip_serializing_if = "Vec::is_empty")]
31    pub current_release_highlights: Vec<String>,
32    /// HTTPS release page for the running version, sent alongside
33    /// `current_release_highlights` so the "what's new" viewer can link out.
34    #[serde(default, skip_serializing_if = "Option::is_none")]
35    pub current_release_url: Option<String>,
36    /// Whether the running version's notes were already announced by any VT
37    /// Code surface. The marker lives in the shared update cache, so notes
38    /// shown by the CLI are not repeated by the desktop app and vice versa.
39    #[serde(default)]
40    pub current_release_seen: bool,
41    /// Release channel the service follows (stable/beta/nightly).
42    #[serde(default, skip_serializing_if = "Option::is_none")]
43    pub channel: Option<String>,
44    /// Whether the configured check interval has elapsed since the last check.
45    #[serde(default)]
46    pub check_due: bool,
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn update_status_snapshot_round_trips_and_older_payloads_decode() {
55        let snapshot = UpdateStatusSnapshot {
56            current_version: "0.180.5".into(),
57            latest_version: Some("0.181.0".into()),
58            update_available: true,
59            release_url: Some("https://github.com/vinhnx/vtcode/releases/tag/v0.181.0".into()),
60            release_highlights: vec!["Faster startup".into(), "New model picker".into()],
61            current_release_highlights: vec!["Older highlight".into()],
62            current_release_url: Some("https://github.com/vinhnx/vtcode/releases/tag/v0.180.5".into()),
63            current_release_seen: true,
64            channel: Some("stable".into()),
65            check_due: false,
66        };
67        let decoded: UpdateStatusSnapshot = serde_json::from_value(serde_json::to_value(&snapshot).unwrap()).unwrap();
68        assert_eq!(decoded, snapshot);
69
70        // Asymmetric fixtures: the post-update shape carries current-version
71        // notes with no update available, and a legacy payload carries only
72        // the running version.
73        let quiet = UpdateStatusSnapshot {
74            current_version: "0.181.0".into(),
75            latest_version: None,
76            update_available: false,
77            release_url: None,
78            release_highlights: Vec::new(),
79            current_release_highlights: vec!["Freshly installed".into()],
80            current_release_url: Some("https://github.com/vinhnx/vtcode/releases/tag/v0.181.0".into()),
81            current_release_seen: false,
82            channel: None,
83            check_due: true,
84        };
85        let decoded_quiet: UpdateStatusSnapshot =
86            serde_json::from_value(serde_json::to_value(&quiet).unwrap()).unwrap();
87        assert_eq!(decoded_quiet, quiet);
88
89        let legacy: UpdateStatusSnapshot =
90            serde_json::from_value(serde_json::json!({ "currentVersion": "0.179.0" })).unwrap();
91        assert_eq!(legacy.current_version, "0.179.0");
92        assert!(legacy.latest_version.is_none());
93        assert!(!legacy.update_available);
94        assert!(legacy.release_url.is_none());
95        assert!(legacy.release_highlights.is_empty());
96        assert!(legacy.current_release_highlights.is_empty());
97        assert!(legacy.current_release_url.is_none());
98        assert!(!legacy.current_release_seen);
99        assert!(legacy.channel.is_none());
100        assert!(!legacy.check_due);
101    }
102}