Beta UI fixes batch: backdrop-click closes dialogs, 1970 dates, refresh loses current folder, peering panel "p.peers is not iterable" #171

Closed
opened 2026-08-10 00:48:39 +00:00 by Cordy · 2 comments
Owner

Four clear UI fixes from Nikola's dogfood notes (2026-08-10), one surface, one PR:

  1. Click outside a dialog closes it. Settings/Appearance/License/etc. open as <dialog> windows; users instinctively click the page behind to dismiss. Native <dialog> reports backdrop clicks as clicks on the dialog element itself — one delegated handler covers every dialog, present and future.
  2. Spaces root (and other synthetic directories) show 1970-01-01. scope deliberately reports the Unix epoch as the stable ModTime for synthetic overlay directories — correct server-side, wrong to display. The UI renders an em dash for epoch mtimes instead of a fake date.
  3. Refreshing returns the user to Personal. The current path lives only in JS memory. Fix: reflect the path into location.hash on navigation and restore it on boot — which also gives every folder a shareable deep link for free.
  4. Peering panel shows "p.peers is not iterable" on open, before any interaction. peerRefresh iterates the peers list without a null-guard; an empty registry serves {"peers": null} and the raw exception lands in the status line. Guard the shape and never surface a raw JS error to the operator.
Four clear UI fixes from Nikola's dogfood notes (2026-08-10), one surface, one PR: 1. **Click outside a dialog closes it.** Settings/Appearance/License/etc. open as `<dialog>` windows; users instinctively click the page behind to dismiss. Native `<dialog>` reports backdrop clicks as clicks on the dialog element itself — one delegated handler covers every dialog, present and future. 2. **Spaces root (and other synthetic directories) show 1970-01-01.** `scope` deliberately reports the Unix epoch as the stable ModTime for synthetic overlay directories — correct server-side, wrong to *display*. The UI renders an em dash for epoch mtimes instead of a fake date. 3. **Refreshing returns the user to Personal.** The current path lives only in JS memory. Fix: reflect the path into `location.hash` on navigation and restore it on boot — which also gives every folder a shareable deep link for free. 4. **Peering panel shows "p.peers is not iterable" on open, before any interaction.** `peerRefresh` iterates the peers list without a null-guard; an empty registry serves `{"peers": null}` and the raw exception lands in the status line. Guard the shape and never surface a raw JS error to the operator.
Author
Owner

Shipped in v0.6.1 (PR #181), live on the dogfood.

  1. Backdrop close: one delegated handler over every <dialog> — a native dialog reports backdrop clicks with the dialog element itself as the event target, so the handler is four lines and covers future dialogs automatically.
  2. Epoch dates: fmtTime returns an em dash for any timestamp ≤ epoch; the server keeps reporting the stable epoch ModTime for synthetic dirs (correct for caching/sorting), the UI just stops pretending it's a date.
  3. Refresh persistence: every load(path) mirrors the path into location.hash (history.replaceState, no history spam); boot prefers a valid hash over Personal. Side effect as designed: folder URLs are now shareable deep links.
  4. p.peers is not iterable: {"peers": null} / {"keys": null} from an empty registry are normalized to [] at the parse site, so no render code can trip and no raw JS error can reach the panel's status line.

Verify on .249 (hard refresh to pick up the new UI): click outside any dialog; check the Spaces root's Modified column; navigate into a folder and hit F5; open the Peering panel with a fresh eye on the message line.

Shipped in **v0.6.1** (PR #181), live on the dogfood. 1. **Backdrop close**: one delegated handler over every `<dialog>` — a native dialog reports backdrop clicks with the dialog element itself as the event target, so the handler is four lines and covers future dialogs automatically. 2. **Epoch dates**: `fmtTime` returns an em dash for any timestamp ≤ epoch; the server keeps reporting the stable epoch ModTime for synthetic dirs (correct for caching/sorting), the UI just stops pretending it's a date. 3. **Refresh persistence**: every `load(path)` mirrors the path into `location.hash` (`history.replaceState`, no history spam); boot prefers a valid hash over Personal. Side effect as designed: folder URLs are now shareable deep links. 4. **`p.peers is not iterable`**: `{"peers": null}` / `{"keys": null}` from an empty registry are normalized to `[]` at the parse site, so no render code can trip and no raw JS error can reach the panel's status line. Verify on `.249` (hard refresh to pick up the new UI): click outside any dialog; check the Spaces root's Modified column; navigate into a folder and hit F5; open the Peering panel with a fresh eye on the message line.
Cordy closed this issue 2026-08-10 01:06:11 +00:00
Author
Owner

Post-mortem addendum: v0.6.1 shipped broken, v0.6.2 fixes it — verified live in the browser.

The S1 splice (epoch-dash) was applied via re.sub, whose replacement-template engine leaves unknown escapes like \" as literal backslash+quote. The emitted JS read return \"—\"; — a SyntaxError at the top of the first inline script block, which killed the entire UI (no views, no data, MI is not defined cascade). My post-conditions verified five of the six splices; the one regex splice was exactly the one without an emitted-text check. Nikola noticed immediately; browser console confirmed the failure in one look.

Fixes, both shipped in v0.6.2:

  1. The line itself repaired.
  2. Structural: the one-shot pipeline now extracts every inline <script> block and runs node --check on each before committing — a shipped UI can no longer contain unparseable JS, from any future splice or hand edit. (Worth folding into validate.yml permanently; noting for the next CI touch.)

Lessons recorded: never route splice text through re.sub's template engine when the payload contains escapes — build replacements with plain string ops; and every splice, regex or anchored, asserts on the emitted text. Verified on the dogfood via browser: zero console errors, all views render, #/home hash routing active, dash mtimes on folders visible.

**Post-mortem addendum: v0.6.1 shipped broken, v0.6.2 fixes it — verified live in the browser.** The S1 splice (epoch-dash) was applied via `re.sub`, whose replacement-template engine leaves unknown escapes like `\"` as literal backslash+quote. The emitted JS read `return \"—\";` — a SyntaxError at the top of the first inline script block, which killed the entire UI (no views, no data, `MI is not defined` cascade). My post-conditions verified five of the six splices; the one regex splice was exactly the one without an emitted-text check. Nikola noticed immediately; browser console confirmed the failure in one look. **Fixes, both shipped in v0.6.2:** 1. The line itself repaired. 2. **Structural: the one-shot pipeline now extracts every inline `<script>` block and runs `node --check` on each before committing** — a shipped UI can no longer contain unparseable JS, from any future splice or hand edit. (Worth folding into `validate.yml` permanently; noting for the next CI touch.) Lessons recorded: never route splice text through `re.sub`'s template engine when the payload contains escapes — build replacements with plain string ops; and every splice, regex or anchored, asserts on the emitted text. Verified on the dogfood via browser: zero console errors, all views render, `#/home` hash routing active, dash mtimes on folders visible.
Sign in to join this conversation.
No labels
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#171
No description provided.