• v0.4.0 c75d7a2797

    fix: four defects the U4 bug-hunt panel found in code it did not add

    vh released this 2026-09-22 09:51:14 -07:00 | 111 commits to main since this release

    All four pre-date U4 and sit in files it touched, which is why a diff-scoped
    robustness lens saw them. They are separated from the unit's own commit so the
    feature history stays readable; the release tags both.

    • A booth name reached a JS string context. The confirm dialogs interpolated
      the name into a string literal inside onsubmit. Jinja's autoescape is
      HTML-attribute escaping, not JS-string escaping: the browser decodes the
      entity back to a quote before the JS parser sees it, so a name crafted to
      close the string executed on submit. Booth names are agent-authored — making
      a folder under the data dir is the whole API — so this was a live path, not a
      theoretical one. The name now travels as a data attribute to a delegated
      handler, where escaping is escaping.

    • An unreadable links.md returned 500 for the whole booth page. is_file()
      then an unguarded read_text(). The board is one tile on that page, and a
      page that will not load is worse than one missing a tile — the posture
      read_blurred, marks_for and read_manifest already take.

    • The index order had no tie-breaker, which violates the deterministic-order
      invariant. Equal-mtime booths fell back to whatever iterdir() yielded, and
      two booths landed by one rsync batch share an mtime exactly. Now
      (mtime, name) reverse: newest first, then name. The operator refers to
      cards positionally, so a sequence that moves between renders misfiles his
      judgment rather than crashing.

    • /b/<n>/marks.json reported damage as empty success. booth marks exits 3
      on an unreadable file precisely so a caller can tell "not yet" from "broken";
      the HTTP mirror — the only reader a remote session has — returned the same
      empty list for both. It now carries error and detail. The status stays
      200 deliberately: reads are lenient here, and a pinned status code is a
      promise to remote clients this fix has no business breaking.

    Each has a regression test. 410 tests.

    Downloads