Add self-update feature via GitHub Releases

Checks for updates on startup (silent, badge on button if available)
and on manual click. Downloads new portable .exe to temp dir, creates
a batch script to swap the running executable after quit, then relaunches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Zac
2026-02-09 22:23:48 -05:00
parent cccb20bd31
commit 7309d78344
6 changed files with 555 additions and 1 deletions
+184 -1
View File
@@ -185,7 +185,9 @@ body {
.content-header {
margin-bottom: 24px;
text-align: center;
display: flex;
align-items: center;
justify-content: space-between;
}
.content-header h1 {
@@ -424,6 +426,187 @@ button:disabled {
animation: pulse 2s infinite;
}
/* Update Button */
.btn-update {
display: flex;
align-items: center;
gap: 6px;
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--button-outline);
padding: 6px 12px;
font-size: 12px;
font-weight: bold;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
position: relative;
}
.btn-update:hover:not(:disabled) {
color: var(--text-primary);
border-color: var(--accent-primary);
}
.btn-update:hover:not(:disabled) .update-icon {
animation: spin 0.6s ease;
}
.btn-update:disabled .update-icon {
animation: spin 1s linear infinite;
}
.btn-update.update-available {
color: var(--success);
border-color: var(--success);
box-shadow: 0 0 8px rgba(76, 175, 80, 0.3);
}
.btn-update.update-available::after {
content: '';
position: absolute;
top: -3px;
right: -3px;
width: 8px;
height: 8px;
background: var(--success);
border-radius: 50%;
box-shadow: 0 0 6px rgba(76, 175, 80, 0.8);
}
.update-icon {
font-size: 14px;
display: inline-block;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Update Modal */
.update-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.update-modal-card {
background: var(--card-bg);
border: 1px solid var(--border);
border-radius: 8px;
width: 480px;
max-width: 90%;
max-height: 80vh;
display: flex;
flex-direction: column;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
}
.update-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--border);
}
.update-modal-header h3 {
font-size: 16px;
color: var(--text-primary);
margin: 0;
}
.update-modal-close {
background: transparent;
border: none;
color: var(--text-secondary);
font-size: 20px;
cursor: pointer;
padding: 0 4px;
line-height: 1;
}
.update-modal-close:hover {
color: var(--text-primary);
}
.update-modal-body {
padding: 20px;
overflow-y: auto;
flex: 1;
}
.update-modal-message {
font-size: 14px;
color: var(--text-primary);
margin: 0 0 12px 0;
}
.update-modal-notes {
font-size: 13px;
color: var(--text-secondary);
line-height: 1.5;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
padding: 12px;
background: var(--bg-primary);
border-radius: 4px;
border: 1px solid var(--border);
}
.update-modal-notes:empty {
display: none;
}
.update-progress-container {
margin-top: 16px;
display: flex;
align-items: center;
gap: 12px;
}
.update-progress-track {
flex: 1;
height: 8px;
background: var(--bg-primary);
border-radius: 4px;
overflow: hidden;
border: 1px solid var(--border);
}
.update-progress-fill {
height: 100%;
background: var(--accent-primary);
border-radius: 4px;
transition: width 0.3s ease;
}
.update-progress-text {
font-size: 12px;
font-weight: bold;
color: var(--text-secondary);
min-width: 36px;
text-align: right;
}
.update-modal-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 16px 20px;
border-top: 1px solid var(--border);
}
/* Responsive Design */
@media (max-width: 768px) {
.devices-sidebar {