Skip to content

Commit b1e858d

Browse files
committed
fix(v1.16.11): settings modal opens — CSS hidden rule + initSettings null-safe
User reported clicking the gear icon (⚙) did nothing. Two real bugs: 1. CSS overrode the HTML hidden attribute. <div id="settings-modal" class="modal-backdrop" hidden> relied on user-agent display:none for [hidden], but .modal-backdrop { display: flex; } in our stylesheet overrode that default. Toggling modal.hidden from JS had no visible effect. Fix: added .modal-backdrop[hidden] { display: none !important } so the HTML attribute is actually honoured. This hardens every modal that uses the class (help, connect, file viewer, command palette, debug, settings). 2. initSettings / initPluginConfig / loadPluginConfig were not null-safe. Same class of regression that bit initSessions in v1.16.0 → v1.16.7. One missing element throws TypeError and aborts the rest of init — including the settings-btn click handler. Fix: optional chaining (?.) on every addEventListener wire-up, null-guard on loadPluginConfig's plugin-config-status access. Port configuration: already wired via FIELD_MAP.port with bounds-validation and restart-required note. Just needed the modal to open. Now fully reachable. Verified: bun run typecheck green, bun test 232/232.
1 parent 6ebf4ed commit b1e858d

6 files changed

Lines changed: 63 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,42 @@ All notable changes to this project will be documented in this file.
44

55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66

7+
## [1.16.11] — 2026-04-22
8+
9+
Fixed — the Settings modal (gear icon) refused to open.
10+
11+
### Two problems, both real
12+
13+
**1. CSS overrode the `hidden` HTML attribute.** The markup was `<div id="settings-modal" class="modal-backdrop" hidden>`. The `hidden` attribute relies on the user-agent default `display: none`, but `.modal-backdrop { display: flex }` from our stylesheet overrode that default. Result: toggling `modal.hidden = true/false` in JS had no visible effect, since the CSS kept forcing `display: flex`. The modal stayed in whatever visibility state the initial paint left it in.
14+
15+
Fix: added `.modal-backdrop[hidden] { display: none !important; }` to `styles.css` so the `hidden` attribute is actually honoured.
16+
17+
**2. `initSettings` could be silently aborted.** Same class of regression that hit `initSessions` in v1.16.0 → v1.16.7. Unnecessary `document.getElementById(id).addEventListener(...)` chains throw `TypeError` the moment any ID is missing, and the rest of `initSettings` stops — including the `settings-btn` gear-icon handler.
18+
19+
Fix: optional chaining (`?.`) on every `addEventListener` wire-up in `initSettings` and `initPluginConfig`. Also null-guarded `loadPluginConfig` which touches `plugin-config-status` before any work. Any one missing element can no longer stop the rest from wiring.
20+
21+
### Port field: already configurable, just couldn't be reached
22+
23+
The port field (`#pcf-port`) was already wired in the Connection tab — `FIELD_MAP.port = { id: 'pcf-port', kind: 'int' }`, with bounds-validation, save-via-PATCH `/settings`, and the "restart required" note that appears when you touch fields like `port`, `host`, or `tunnel`. It just needed the modal to actually open.
24+
25+
After installing v1.16.11:
26+
27+
1. Click the gear (⚙) in the header.
28+
2. Go to the **Connection** tab.
29+
3. Change **Port**. The "Changes to these fields require an OpenCode restart" note lights up.
30+
4. Click **Save**.
31+
5. Restart OpenCode. The new port takes effect.
32+
33+
### Why initSessions didn't teach us to blind initSettings at the same time
34+
35+
Good question. v1.16.7 blinded `initSessions` because the crash was visible via the v1.16.6 `bootstrap().catch()`. `initSettings` runs AFTER `initSessions` in the bootstrap order, and as long as `initSessions` completes (which it now does thanks to v1.16.7), `initSettings` was reachable and its IDs all existed — nothing threw during that session. But the class-of-bug was latent: one markup change and it would have reproduced the same symptom. v1.16.11 preemptively closes that door.
36+
37+
### Small CSS rule, big payoff
38+
39+
The `.modal-backdrop[hidden] { display: none !important; }` rule also hardens the other modals that use the same pattern (help, connect, file viewer, command palette, debug). They all now honour `hidden` as the single source of truth for visibility, regardless of what the class stylesheet says.
40+
41+
---
42+
743
## [1.16.10] — 2026-04-22
844

945
Cleanup release. v1.16.9 confirmed working: user logs showed `[pilot:event-hook] clients=1` + `[pilot:bus-emit] clients=1` during a prompt, and the browser received `[sse] onmessage message.part.updated`, `message.updated`, `session.status`, `session.idle`, `session.updated`, `session.diff` — all in live time. The singleton event bus was the real root cause.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lesquel/opencode-pilot",
3-
"version": "1.16.10",
3+
"version": "1.16.11",
44
"description": "Remote control web dashboard for OpenCode — multi-project tabs, live SSE streaming, mobile-friendly, push/Telegram notifications, tunnel for phone access from anywhere.",
55
"publishConfig": {
66
"access": "public"

src/server/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// ─── Shared constants ────────────────────────────────────────────────────────
22
// Single source of truth for magic numbers and version strings.
33

4-
export const PILOT_VERSION = "1.16.10"
4+
export const PILOT_VERSION = "1.16.11"
55
export const DEFAULT_PORT = 4097
66
export const DEFAULT_HOST = "127.0.0.1"
77
export const DEFAULT_PERMISSION_TIMEOUT_MS = 300_000

src/server/dashboard/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
<script>
4444
(function() {
4545
try {
46-
var GEN = "1.16.10";
46+
var GEN = "1.16.11";
4747
var KEY = "pilot:asset-gen";
4848
if (localStorage.getItem(KEY) === GEN) return;
4949
localStorage.setItem(KEY, GEN);

src/server/dashboard/settings.js

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,22 @@ function closeSettingsModal() {
122122
}
123123

124124
export function initSettings() {
125-
document.getElementById('settings-btn').addEventListener('click', openSettingsModal)
125+
// Every `addEventListener` below is wrapped in optional chaining (`?.`).
126+
// Same class of regression that bit `initSessions` in v1.16.0 → v1.16.7:
127+
// a single missing element throws TypeError and aborts the rest of init,
128+
// so the gear-icon listener never gets attached and the modal refuses to
129+
// open. This blinds it against any future markup change.
130+
document.getElementById('settings-btn')?.addEventListener('click', openSettingsModal)
126131

127-
document.getElementById('settings-close').addEventListener('click', closeSettingsModal)
132+
document.getElementById('settings-close')?.addEventListener('click', closeSettingsModal)
128133

129-
document.getElementById('s-sound').addEventListener('change', e => {
134+
document.getElementById('s-sound')?.addEventListener('change', e => {
130135
const settings = { ...getState().settings, sound: e.target.checked }
131136
setState({ settings })
132137
saveSettings()
133138
})
134139

135-
document.getElementById('s-notif').addEventListener('change', async e => {
140+
document.getElementById('s-notif')?.addEventListener('change', async e => {
136141
let settings = { ...getState().settings, notif: e.target.checked }
137142
setState({ settings })
138143
saveSettings()
@@ -155,14 +160,14 @@ export function initSettings() {
155160
}
156161
})
157162

158-
document.getElementById('s-theme').addEventListener('change', e => {
163+
document.getElementById('s-theme')?.addEventListener('change', e => {
159164
const settings = { ...getState().settings, theme: e.target.checked }
160165
setState({ settings })
161166
saveSettings()
162167
applySettings()
163168
})
164169

165-
document.getElementById('s-tools').addEventListener('change', e => {
170+
document.getElementById('s-tools')?.addEventListener('change', e => {
166171
const settings = { ...getState().settings, tools: e.target.checked }
167172
setState({ settings })
168173
saveSettings()
@@ -287,9 +292,9 @@ function initPluginConfig() {
287292
})
288293
})
289294

290-
document.getElementById('settings-save').addEventListener('click', onSave)
291-
document.getElementById('pcf-reset').addEventListener('click', onReset)
292-
document.getElementById('pcf-vapid-generate').addEventListener('click', onGenerateVapid)
295+
document.getElementById('settings-save')?.addEventListener('click', onSave)
296+
document.getElementById('pcf-reset')?.addEventListener('click', onReset)
297+
document.getElementById('pcf-vapid-generate')?.addEventListener('click', onGenerateVapid)
293298

294299
// Inline validation hints (advisory only — do not block save)
295300
attachInlineValidation('pcf-telegram-token', v => {
@@ -320,16 +325,18 @@ function initPluginConfig() {
320325

321326
async function loadPluginConfig() {
322327
const statusEl = document.getElementById('plugin-config-status')
323-
statusEl.style.display = 'none'
328+
if (statusEl) statusEl.style.display = 'none'
324329
try {
325330
const data = await fetchPluginSettings()
326331
_lastLoadedSnapshot = data
327332
applySnapshotToInputs(data)
328333
updateRestartNote(data)
329334
} catch (err) {
330-
statusEl.className = 'plugin-config-status error'
331-
statusEl.textContent = 'Could not load plugin settings: ' + (err?.message || err)
332-
statusEl.style.display = 'block'
335+
if (statusEl) {
336+
statusEl.className = 'plugin-config-status error'
337+
statusEl.textContent = 'Could not load plugin settings: ' + (err?.message || err)
338+
statusEl.style.display = 'block'
339+
}
333340
}
334341
}
335342

src/server/dashboard/styles.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4905,6 +4905,10 @@ body {
49054905
justify-content: center;
49064906
z-index: 1000;
49074907
}
4908+
/* Honour the HTML `hidden` attribute even when the class sets display:flex —
4909+
* without this, <div class="modal-backdrop" hidden> stays visible because the
4910+
* class wins over the user-agent `[hidden]` default. */
4911+
.modal-backdrop[hidden] { display: none !important; }
49084912
.modal-panel {
49094913
background: var(--surface);
49104914
border: 1px solid var(--border);

0 commit comments

Comments
 (0)