fix(web): address self-review findings on #105 (settings cache + brittle reveal)

- Fix the gesture enable-reload race: PUT /api/settings writes settings.json
  without invalidating WebServer's 2s _settingsCache, and the toggle reloads
  ~400ms after save — within the TTL — so renderIndexHtml could render the
  pre-toggle state (bundle not injected until a 2nd reload). renderIndexHtml
  now reads settings via readSettings(true), a fresh read that bypasses the
  cache; readSettings gains a forceFresh param.
- Replace the brittle multi-monitor reveal (string match on the button's
  aria-label + inline style) with a stable `btn-multimonitor--hidden` class
  marker: the template carries the class, the server strips it when the setting
  is on, and applyHeaderVisibilitySettings()/solo-mode CSS toggle the same class.
  Editing the button's copy no longer silently breaks the reveal.
- Test: test/render-index-html.test.ts (reveal, solo injection + escaping,
  gesture availability vs. enablement, fresh-read wiring).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arkon
2026-06-08 15:46:10 +02:00
co-authored by Claude Opus 4.8
parent 62b7c4903b
commit 29d3fd48c1
6 changed files with 124 additions and 15 deletions
+1 -1
View File
@@ -105,7 +105,7 @@
</div>
</div>
<button class="btn-icon-header btn-response-viewer-header" onclick="app.toggleResponseViewer()" title="View last response" aria-label="View last response"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg></button>
<button class="btn-icon-header btn-multimonitor" onclick="app.launchMultiMonitor()" title="Open Codeman across all displays" aria-label="Open Codeman across all displays" style="display:none;"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg></button>
<button class="btn-icon-header btn-multimonitor btn-multimonitor--hidden" onclick="app.launchMultiMonitor()" title="Open Codeman across all displays" aria-label="Open Codeman across all displays"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="2" y="4" width="13" height="9" rx="1.5"/><rect x="11" y="9" width="11" height="8" rx="1.5"/></svg></button>
<button class="btn-icon-header btn-notifications" onclick="app.toggleNotifications()" title="Notifications" aria-label="Toggle notifications" style="display:none;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M18 8A6 6 0 0 0 6 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 0 1-3.46 0"/></svg>
<span class="notification-badge" id="notifBadge" style="display:none;">0</span>
+3 -2
View File
@@ -1474,11 +1474,12 @@ Object.assign(CodemanApp.prototype, {
// Multi-monitor button — hidden by default (App Settings → Display → "Header
// Displays"). The server renders the correct initial state on every reload;
// this handles a live toggle from a settings save (no reload).
// this handles a live toggle from a settings save (no reload). Toggle the
// marker class (matches the server-side reveal) rather than an inline style.
const showMultiMonitorButton = settings.showMultiMonitorButton ?? defaults.showMultiMonitorButton ?? false;
const multiMonitorBtn = document.querySelector('.btn-multimonitor');
if (multiMonitorBtn) {
multiMonitorBtn.style.display = showMultiMonitorButton ? '' : 'none';
multiMonitorBtn.classList.toggle('btn-multimonitor--hidden', !showMultiMonitorButton);
}
// Notification bell is retired (notifications live in Settings → Notifications
+7
View File
@@ -846,6 +846,13 @@ body {
transform: rotate(45deg);
}
/* Multi-monitor header button: hidden by default (opt-in via App Settings →
Display → "Header Displays"). The server strips this class at render when the
setting is on; the client toggles it live on save. */
.btn-multimonitor--hidden {
display: none !important;
}
.btn-icon-header.btn-settings {
width: 30px;
height: 30px;