Expanding a collapsible section that the selected layout collapses by default did not stick — most visibly the scheduler under Compact, which read as the layout locking the section.
Nothing was actually locking it. No CSS hides those sections in Compact, and the <details> was always clickable: the click worked, for about a second.
applyLayout() writes details.open for the advanced, audio and scheduler sections. It is meant to run when the operator picks a layout, but it also runs on every SSE frame:
render(update) → applyRigList(...) // whenever update.remotes is an array
→ trxUi.setActiveRig() // called unconditionally
→ applyLayout() // rewrites all three sections
So a layout was being treated as continuously-enforced state rather than a preset. Expand a section, wait for the next status frame, and it closes again.
The fix
A layout seeds the sections; it no longer holds them there. Section state is written only when the layout actually changes, plus the first time each section appears in the DOM — that second condition matters because advanced-radio-controls is constructed after the first applyLayout call and would otherwise never be seeded at all.
Switching layout still reseeds every section, so choosing "Compact" keeps meaning give me these defaults instead of becoming a one-way door.
Verification
The new ui-core assertions fail against the old behaviour and pass with the fix — confirmed by reverting the gate rather than assuming:
AssertionError: re-applying the same layout leaves sections alone
Then in real Chromium, activating the actual <summary> element:
seeded closed in compact : false
stays open after click : true
survives rig refresh : true ← was false before
reseeded by layout switch: true
Full gate green: typecheck, lint, 34/34 test, build, test:browser.
Known limitation
Toggles do not survive a page reload; on load the layout's defaults apply again. Persisting them is a small change (a localStorage map overlaid after seeding) but needs a decision on scope — per rig, like trxOperatorLayout:<rig>, or global. Left out deliberately: the request was for togglable, not sticky.
## What was wrong
Expanding a collapsible section that the selected layout collapses by default did not stick — most visibly the scheduler under **Compact**, which read as the layout locking the section.
Nothing was actually locking it. No CSS hides those sections in Compact, and the `<details>` was always clickable: the click worked, for about a second.
`applyLayout()` writes `details.open` for the advanced, audio and scheduler sections. It is meant to run when the operator picks a layout, but it also runs on every SSE frame:
```
render(update) → applyRigList(...) // whenever update.remotes is an array
→ trxUi.setActiveRig() // called unconditionally
→ applyLayout() // rewrites all three sections
```
So a layout was being treated as continuously-enforced state rather than a preset. Expand a section, wait for the next status frame, and it closes again.
## The fix
A layout **seeds** the sections; it no longer holds them there. Section state is written only when the layout actually changes, plus the first time each section appears in the DOM — that second condition matters because `advanced-radio-controls` is constructed *after* the first `applyLayout` call and would otherwise never be seeded at all.
Switching layout still reseeds every section, so choosing "Compact" keeps meaning *give me these defaults* instead of becoming a one-way door.
## Verification
The new `ui-core` assertions fail against the old behaviour and pass with the fix — confirmed by reverting the gate rather than assuming:
```
AssertionError: re-applying the same layout leaves sections alone
```
Then in real Chromium, activating the actual `<summary>` element:
```
seeded closed in compact : false
stays open after click : true
survives rig refresh : true ← was false before
reseeded by layout switch: true
```
Full gate green: `typecheck`, `lint`, 34/34 `test`, `build`, `test:browser`.
## Known limitation
Toggles do not survive a page reload; on load the layout's defaults apply again. Persisting them is a small change (a `localStorage` map overlaid after seeding) but needs a decision on scope — per rig, like `trxOperatorLayout:<rig>`, or global. Left out deliberately: the request was for togglable, not sticky.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz
A layout seeds the collapsible sections; it should not hold them there.
applyLayout writes the disclosure state of the advanced, audio and
scheduler sections, and it runs far more often than a layout change:
render() calls applyRigList() for every SSE frame carrying `remotes`,
which calls setActiveRig() unconditionally, which re-applies the layout.
An operator who expanded a section that the selected layout collapses by
default therefore had it shut again within about a second, which read as
the section being locked by the layout — most visibly the scheduler under
Compact.
Write the section state only when the layout actually changes, or the
first time each section appears in the DOM, since the advanced controls
are constructed after the first applyLayout call. Switching layout still
reseeds every section, so choosing a layout keeps meaning "give me these
defaults".
Verified in Chromium: with Compact selected, activating the scheduler
summary opens the section and it survives both a rig-state refresh and a
repeated applyLayout, while selecting Full still reseeds it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz
Signed-off-by: Stan Grams <sjg@haxx.space>
sjg
merged commit b31790ff48 into main2026-08-02 18:04:48 +02:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What was wrong
Expanding a collapsible section that the selected layout collapses by default did not stick — most visibly the scheduler under Compact, which read as the layout locking the section.
Nothing was actually locking it. No CSS hides those sections in Compact, and the
<details>was always clickable: the click worked, for about a second.applyLayout()writesdetails.openfor the advanced, audio and scheduler sections. It is meant to run when the operator picks a layout, but it also runs on every SSE frame:So a layout was being treated as continuously-enforced state rather than a preset. Expand a section, wait for the next status frame, and it closes again.
The fix
A layout seeds the sections; it no longer holds them there. Section state is written only when the layout actually changes, plus the first time each section appears in the DOM — that second condition matters because
advanced-radio-controlsis constructed after the firstapplyLayoutcall and would otherwise never be seeded at all.Switching layout still reseeds every section, so choosing "Compact" keeps meaning give me these defaults instead of becoming a one-way door.
Verification
The new
ui-coreassertions fail against the old behaviour and pass with the fix — confirmed by reverting the gate rather than assuming:Then in real Chromium, activating the actual
<summary>element:Full gate green:
typecheck,lint, 34/34test,build,test:browser.Known limitation
Toggles do not survive a page reload; on load the layout's defaults apply again. Persisting them is a small change (a
localStoragemap overlaid after seeding) but needs a decision on scope — per rig, liketrxOperatorLayout:<rig>, or global. Left out deliberately: the request was for togglable, not sticky.🤖 Generated with Claude Code
https://claude.ai/code/session_01GdyUjuXejCEfiub675z6cz