fuzzyworld

tower / commit

refactor(ui): stop opening and closing sections automatically

5e4052724b9b17035acedb99fec6d7ee903dbdb9 · Pavel Safronov · 2026-09-06 21:58:42 -0700 · parents 0e0c2dba8801

refactor(ui): stop opening and closing sections automatically

The accordion and the self-opening Selected panel were one mistake in two
parts: exclusive opening meant clicking a place could collapse the panel
about to describe it, so Selected had to open itself to compensate. Dropping
the accordion removes the need for the compensation — sections stay where
they are put, so a selection appears in a panel that is already open.

What remains is initial state rather than behaviour. View and World start
open; Selected starts open but hidden until something is selected, so the
first selection shows without anything moving. Close the editor and it stays
closed through every later selection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
 decisions.md  | 33 ++++++++++++++++++++++++++++-----
 index.html    |  6 +++---
 src/ui/app.ts | 33 ---------------------------------
 3 files changed, 31 insertions(+), 41 deletions(-)

diff --git a/decisions.md b/decisions.md
index ad7c249..3d32941 100644
--- a/decisions.md
+++ b/decisions.md
@@ -944,11 +944,11 @@ property of a value here, not a category of setting.
 **Order follows scope.** *View* moved to the top: it changes the whole app, not one
 panel. *About* moved to the bottom, where the least-used thing belongs.
 
-**One section open at a time.** Seven panels in a fixed column pushed the useful
-ones off-screen. They are `<details>` elements now — native disclosure, keyboard and
-screen-reader behaviour for free — with a toggle handler closing the others. The
-Selected panel opens itself when the selection changes, or the accordion would hide
-the very thing just clicked.
+**Foldable sections.** Seven panels in a fixed column pushed the useful ones
+off-screen. They are `<details>` elements now — native disclosure, keyboard and
+screen-reader behaviour for free. *This first shipped as an accordion, one open at a
+time, with the Selected panel opening itself on selection; both were removed — see
+D-032.*
 
 **Mode gating moved from a CSS class to the `hidden` property**, since `<details>`
 already carries open/closed state and stacking a `.hidden` class on top of it was a
@@ -959,3 +959,26 @@ with links to design.md and decisions.md. Those documents are the author's worki
 notes on an unpublished novel, not a public invitation, and the links went to a repo
 a reader may not have. Now it says what is true and no more: *Tower is the setting
 of a novel. This map is its companion piece.*
+
+---
+
+## D-032 — No automatic opening or closing
+
+**Reverted at the author's request:** the accordion was annoying, and nothing should
+open or close by itself.
+
+**The two behaviours were one mistake.** Exclusive opening meant that clicking a
+place could collapse the panel about to describe it, so the Selected panel had to
+open itself to compensate. Removing the accordion removed the need for the
+compensation: sections the author opens stay open, so a selection simply appears in
+a panel that is already there. A workaround that only exists to undo another
+behaviour is a sign the first behaviour is wrong.
+
+**What remains is state, not behaviour.** View and World start open, and Selected
+starts open but stays hidden until something is selected — so the first selection
+shows without anything having moved. After that the panels are wherever the author
+put them: closing the editor keeps it closed through every later selection.
+
+**The general rule this is worth remembering by:** an interface that rearranges
+itself in response to a click costs the user their place. Prefer a sensible initial
+state and then hold still.
diff --git a/index.html b/index.html
index 1c4622e..b040c28 100644
--- a/index.html
+++ b/index.html
@@ -50,19 +50,19 @@
       <p class="hint" id="tool-hint">Click a marker to select. Drag to move. Shift-click two points to measure a sightline.</p>
     </details>
 
-    <details class="sec" id="world-section">
+    <details class="sec" id="world-section" open>
       <summary>World <span id="counts" class="dim"></span></summary>
       <ul id="list" class="list"></ul>
     </details>
 
-    <details class="sec" id="detail" hidden>
+    <details class="sec" id="detail" hidden open>
       <summary>Selected</summary>
       <p id="d-name"></p>
       <p id="d-blurb" class="dim"></p>
       <p id="d-where" class="dim"></p>
     </details>
 
-    <details class="sec" id="editor" hidden>
+    <details class="sec" id="editor" hidden open>
       <summary>Selected</summary>
       <label>Name <input id="f-name" type="text" /></label>
       <label>Kind <select id="f-kind"></select></label>
diff --git a/src/ui/app.ts b/src/ui/app.ts
index 50e8873..7fd1f06 100644
--- a/src/ui/app.ts
+++ b/src/ui/app.ts
@@ -64,7 +64,6 @@ export class App {
   private readonly writer = !new URLSearchParams(location.search).has('reader')
   private tool: Tool = 'select'
   private selectedId: string | null = null
-  private lastShownId: string | null = null
   private draft: [number, number][] = []
   private probeFrom: SurfacePoint | null = null
 
@@ -74,7 +73,6 @@ export class App {
       onDragPlace: (id, p) => this.onDragPlace(id, p),
       onHover: (p) => this.onHover(p),
     })
-    this.wireSections()
     this.buildViews()
     this.buildTools()
     this.buildKindSelect()
@@ -98,28 +96,6 @@ export class App {
     void this.setView(this.initialView())
   }
 
-  // -------------------------------------------------------------- sections
-
-  /**
-   * Accordion: opening one section closes the rest. The sidebar carries seven
-   * panels and the map is the point of the page, so at most one should be eating
-   * vertical space at a time.
-   */
-  private wireSections(): void {
-    const all = [...document.querySelectorAll<HTMLDetailsElement>('aside details.sec')]
-    for (const d of all) {
-      d.addEventListener('toggle', () => {
-        if (!d.open) return
-        for (const other of all) if (other !== d) other.open = false
-      })
-    }
-  }
-
-  private openSection(id: string): void {
-    const el = $<HTMLDetailsElement>(id)
-    if (!el.open) el.open = true
-  }
-
   // ----------------------------------------------------------------- views
 
   /**
@@ -623,15 +599,6 @@ export class App {
     const detail = $<HTMLDetailsElement>('detail')
     const p = this.selectedId ? this.store.get(this.selectedId) : undefined
 
-    // Reveal the panel when the selection changes, or the accordion hides the very
-    // thing that was just clicked.
-    if (p && p.id !== this.lastShownId) {
-      this.lastShownId = p.id
-      queueMicrotask(() => this.openSection(this.writer ? 'editor' : 'detail'))
-    } else if (!p) {
-      this.lastShownId = null
-    }
-
     // Readers get a read-only card. Without this branch renderEditor would happily
     // un-hide the edit panel — including the private notes field — for everyone.
     if (!this.writer) {