From 0ad332bb4a9bd00600816e424f18b907eb3c39d4 Mon Sep 17 00:00:00 2001 From: Vuong Hoang Date: Sun, 23 Aug 2026 12:55:33 -0700 Subject: [PATCH] feat(booth): per-row link removal + render the link board as real UI The standing link board is the one MULTI-WRITER booth -- every agent session appends operator-facing URLs to it. "Delete the folder" was the only granularity available, so removing one dead link meant hand-editing markdown. It is 32 rows and only grows. booth links row number, entry id, raw row booth unlink 3 by row number booth unlink 8b40e0a5 by entry id (what the UI's x posts) POST /b//unlink form field `entry` = content id ROWS ARE ADDRESSED BY CONTENT ID, NEVER BY POSITION. The board is append-only and multi-writer: another session can post between listing it and clicking x, and an index would then delete a neighbour. An id either matches the row you saw or matches nothing. A row number typed at the CLI is resolved to its id BEFORE anything is deleted. Appends and prunes now take the same flock on .links.lock, so a post cannot be lost inside a prune's read-modify-write. UI: a booth carrying links.md renders as rows -- description, URL, provenance, copy button, per-row x -- instead of a markdown blob. links.md is filtered out of the gallery so it does not appear twice; the header counts LINKS not files; the empty-state and the one-click "Wipe now" both stand down for a board (same rule as the kept lane: nothing durable is one click from gone). booth/links.py extracted, STDLIB ONLY. The CLI needs this logic and must not require the service venv -- importing app.py drags in FastAPI, so deleting a line from a text file would have needed a web framework installed. THREE BUGS FOUND BY TESTING, all in the shell wrapper while the module was correct throughout -- module-only tests would have caught none of them: - `[ "$n" -eq 0 ] && echo ...` as the LAST statement made `booth links` exit 1 whenever the board had rows. `unlink`'s index lookup calls it inside $( ) under `set -e`, so a successful listing killed the caller and the removal silently did nothing while reporting success. - ids are 8 hex chars and roughly one in forty is ALL DIGITS; those were read as row numbers, resolved to nothing, and removed nothing. Now disambiguated by the id's actual shape, not by "is it numeric". - filtering links.md out of the gallery left `items` empty, so a full board rendered "This booth is empty" and an empty {% endif %} -{% if not items %} +{% if board %} + {# THE STANDING LINK BOARD. Every agent session on the fleet appends here, so + this is the one booth where the useful granularity is the ROW, not the + folder. Rendered as real UI rather than a markdown blob so a dead link can + be removed without hand-editing the file — and so provenance (who posted + it, when) is readable at a glance, which is the whole reason a bare URL + three days old is useless. + + Removal posts a CONTENT ID, never a row number: another session can append + between this page rendering and the × being clicked, and an index would + then delete a neighbour. #} +
+
+ {{ board|length }} link{{ '' if board|length == 1 else 's' }} + newest last · appended by any session · × removes one row +
+ {% for e in board %} +
+
+ {{ e.desc }} +
{{ e.url }}
+
+
+ {% if e.who %}{{ e.who }}{% endif %} + {% if e.when %}{{ e.when }}{% endif %} +
+ +
+ + +
+
+ {% endfor %} +
+{% endif %} + +{% if not items and not board %}
This booth is empty.
-{% else %} +{% elif items %} + {# `elif items` and not a bare `else`: a board booth has NO gallery items (its + links.md is rendered as the board above and filtered out), so a plain else + would emit an empty