diff --git a/internal/web/static/index.html b/internal/web/static/index.html
index 58c902f..ce79cf8 100644
--- a/internal/web/static/index.html
+++ b/internal/web/static/index.html
@@ -4853,10 +4853,16 @@
renderLatestVersion();
if (data.hasUpdate && data.downloadURL) {
if (!manual) {
- // Skip silently only if the user explicitly hit "Don't
- // show again" for this version (or downloaded it).
- var skipKey = 'thefeed_skip_gh_update_' + normalizeVersion(data.latest);
- if (localStorage.getItem(skipKey) === '1') return;
+ // Server-stored skip survives per-port localStorage wipes.
+ var skipped = '';
+ try {
+ var sx = new XMLHttpRequest();
+ sx.open('GET', '/api/settings', false);
+ sx.send();
+ if (sx.status === 200) skipped = JSON.parse(sx.responseText).skipUpdateVersion || '';
+ } catch (e) { }
+ if (!skipped) skipped = localStorage.getItem('thefeed_skip_gh_update_' + normalizeVersion(data.latest)) === '1' ? data.latest : '';
+ if (normalizeVersion(skipped) === normalizeVersion(data.latest)) return;
}
showUpdateDialog(data.latest, data.downloadURL);
} else if (manual) {
@@ -4887,11 +4893,16 @@
+ '';
document.body.appendChild(overlay);
var dismiss = function () { if (overlay.parentNode) overlay.parentNode.removeChild(overlay); };
- document.getElementById('updateLater').onclick = dismiss;
- document.getElementById('updateSkip').onclick = function () {
+ var persistSkip = function () {
try { localStorage.setItem(skipKey, '1'); } catch (e) { }
- dismiss();
+ fetch('/api/settings', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ skipUpdateVersion: newVersion })
+ }).catch(function () { });
};
+ document.getElementById('updateLater').onclick = dismiss;
+ document.getElementById('updateSkip').onclick = function () { persistSkip(); dismiss(); };
document.getElementById('updateDownload').onclick = function () {
try {
var a = document.createElement('a');
@@ -4899,7 +4910,7 @@
a.style.display = 'none';
document.body.appendChild(a); a.click(); a.remove();
} catch (e) { }
- try { localStorage.setItem(skipKey, '1'); } catch (e) { }
+ persistSkip();
dismiss();
};
}
diff --git a/internal/web/web.go b/internal/web/web.go
index 1abdf38..4fe859a 100644
--- a/internal/web/web.go
+++ b/internal/web/web.go
@@ -99,6 +99,8 @@ type ProfileList struct {
Debug bool `json:"debug,omitempty"`
Theme string `json:"theme,omitempty"`
Lang string `json:"lang,omitempty"`
+ // SkipUpdateVersion is the latest release the user dismissed.
+ SkipUpdateVersion string `json:"skipUpdateVersion,omitempty"`
// ResolverBank is the shared pool of DNS resolvers used by all profiles.
ResolverBank []string `json:"resolverBank,omitempty"`
// ResolverScores stores accumulated performance data for bank resolvers.
@@ -2832,6 +2834,7 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
"lang": pl.Lang,
"scanPromptOff": pl.ScanPromptOff,
"profilePicsEnabled": pl.ProfilePicsEnabled,
+ "skipUpdateVersion": pl.SkipUpdateVersion,
"version": version.Version,
"commit": version.Commit,
})
@@ -2845,6 +2848,7 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
Lang *string `json:"lang"`
ScanPromptOff *bool `json:"scanPromptOff"`
ProfilePicsEnabled *bool `json:"profilePicsEnabled"`
+ SkipUpdateVersion *string `json:"skipUpdateVersion"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid JSON", 400)
@@ -2885,6 +2889,9 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
if req.ProfilePicsEnabled != nil {
pl.ProfilePicsEnabled = *req.ProfilePicsEnabled
}
+ if req.SkipUpdateVersion != nil {
+ pl.SkipUpdateVersion = *req.SkipUpdateVersion
+ }
if err := s.saveProfiles(pl); err != nil {
http.Error(w, fmt.Sprintf("save: %v", err), 500)
return