Skip to content

mapping pipeline: candidate zone editing from the dashboard - #101

Open
MJohnson459 wants to merge 1 commit into
mainfrom
zone-editor-candidate-edit
Open

mapping pipeline: candidate zone editing from the dashboard#101
MJohnson459 wants to merge 1 commit into
mainfrom
zone-editor-candidate-edit

Conversation

@MJohnson459

Copy link
Copy Markdown
Contributor

Fleet UI: name a candidate's zones before promoting it (commit 262dc44, branch
zone-editor-candidate-edit, worktree .claude/worktrees/zone-editor-346; branched
off origin/main 6464f57 so it sits on top of #100's review pane. Unpushed.)

The MVP (PR #95, merged) edited the canonical revision only, which left the
case the mapping pipeline is built around unreachable: segment-map hands over a
map whose rooms are zone_01..zone_07, and renaming them required promoting
the placeholders first — publishing a map because it was wrong — besides
rebinding coordinates onto a frame they were not drawn on.

What changed:

  • POST /v1/sites/<site>/floors/<floor>/zones takes a revision: the revision
    being edited (omitted = canonical, as before). Still a derivation — that
    revision's map bytes re-packed with the submitted zones, accepted as an
    ordinary candidate, inert until promoted; nothing stored is written.
    vocabulary_revision continues from the edited revision so a later
    carry-forward can tell which naming is newer, and the audit row names the
    floor and the revision edited.
  • The editor moved out of the operations map into the review pane, the only
    place a candidate's own map is on screen. Editing is a mode there (floor
    picker, revision list and promote locked; no autosave). After a save the pane
    selects the new candidate and re-reads its zones, which retires the MVP's
    frozen-overlay workaround — what is on screen is the saved set from the server.
  • Vocabulary editing: kind (a <select> of zone/v0's kinds) and aliases
    (one comma-separated field) beside name and display_name. navigable is
    written only when it deviates from the kind, or a keepout changed to room
    would silently stay undispatchable. The editor refuses client-side exactly
    what the robot's loader refuses: a non-dispatchable name, and two zones
    answering one query (mirrors bundle.ambiguities).
  • arms the next map click as a zone's pose — the only way to give one to a
    segmented room (a polygon with no x/y, whose pose the robot otherwise derives
    as a centroid).

Two defects found and fixed on the way, both invisible without a browser:

  • accept() validated with require_posegraph=True (the upload bar) while
    promote() and the review pane use False, so edit zones sat beside a
    "promotable" verdict and could only ever fail for any revision without a
    posegraph — which is every sim bundle and every rsync-seeded floor. accept
    now takes the flag; a derivation is held to its source's bar, uploads are
    unchanged (asserted both ways).
  • The map pane's review button was hidden unless the floor on screen had
    candidates, and above 760 px the tab bar is hidden too — so at desk width the
    pane built for floors no robot is reporting was reachable only via a floor a
    robot was reporting. Now always present.

Verified: 13 API tests in test_zone_edit.py (derive from a candidate and prove
the candidate's map bytes travel; a floor with nothing published; vocabulary
revision; unknown and path-traversing revision; the posegraph bar both ways),
50 node tests in ui_test.mjs (kind list read out of bundle.py so it cannot
drift, alias round-trip, ambiguity, the navigable rule, editor-lives-in-review,
.zone-rows[hidden]), 256 pytest in mote_fleet/test, pre-commit clean, and four
new checks in browser_check.mjs — pixi run fleet-ui-check passes 30/30 against
a real broker, server, three fake robots and chrome, desk width and emulated
phone. Screenshots from that run are in the job tmp dir (fleet-ui-zones.png
shows the saved candidate selected with its own zones).

Out of scope, still open: carry-forward accept/reject needs task 345 (not built
yet); grid snapping + shared vertices is task 350. The upload.json validator
warning visible on derived candidates is task 348.

The zone editor shipped against the *canonical* revision, which left the case
the mapping pipeline is actually built around unreachable. `segment-map` hands
over a map whose rooms are called `zone_01`..`zone_07`; renaming them meant
deriving from the published revision, so the placeholders had to be **promoted
in order to be allowed to fix them** — publishing a map because it was wrong —
and the coordinates were then rebound onto a frame they had not been drawn on.

So the edit now names the revision it edits (`POST …/floors/<floor>/zones` takes
a `revision`; omitted, it is the canonical one as before), and the editor moves
into the review pane that #100 built, which is the only place a candidate's own
map is on screen. Editing stays a *derivation*: the named revision's map bytes
are re-packed with the submitted zones and accepted as an ordinary candidate,
inert until promoted, so nothing stored is ever written and promotion stays the
only write that moves a floor.

Five things are load-bearing.

**A derivation is held to `promote`'s bar, not the upload's.** A revision with
no posegraph cannot be extended — an error for a robot's upload, where the
session can be re-run, and a *warning* on something already stored, which
navigates perfectly and which `promote` accepts. `accept` therefore takes
`require_posegraph`. Found in a browser, not by reading: every sim site bundle
is such a revision, so `edit zones` sat beside a `promotable` verdict and could
only ever fail.

**The vocabulary half is editable, and `navigable` is not written when the kind
already implies it.** Kind and aliases join name and display_name in each row.
Every zone arrives from the server with `navigable` filled in (`zone_term`
defaults it from the kind), so writing it back verbatim would carry a
`keepout`'s `false` onto a zone just changed to `room` — a room nothing can be
dispatched to, with nothing on screen to say why. Empty lists are dropped for
the same reason: they say nothing the default does not.

**The editor refuses exactly what the robot's loader refuses.** A name a
dispatcher cannot type, and two zones answering one query — `ambiguities`
mirrors `bundle.ambiguities` (names and aliases, not display names), because the
loader *refuses* an ambiguous vocabulary rather than resolving it by dict order,
and a stored candidate no robot will load is worse than a rejected save.

**A pose can be placed, not only dragged.** A segmented room is a polygon with
no `x`/`y`, so it drew no cross to drag and the robot derived a centroid — the
middle of the outline rather than where you would send a robot. `⌖` arms the
next map click.

**After a save the pane selects the new candidate and re-reads it**, so the
zones on screen are the saved ones from the server. That retires the MVP's
frozen overlay, which existed only because the operations map had nothing to
re-render but the stale set the edit was made from (read as data loss,
2026-08-02). Editing is a mode: floor picker, revision list and promote are
locked while it is up, there is no autosave, `cancel` discards.

One pre-existing defect fell out. The map pane's review button was hidden unless
the floor on screen had candidates, and above 760 px the tab bar is hidden too —
so the pane built for floors no robot is reporting was reachable only through a
floor a robot *was* reporting. It is now always there, and says how many
candidates the floor has when it knows.

Verified: 13 API tests (`test_zone_edit.py`, incl. deriving from a candidate, a
floor with nothing published, the vocabulary revision continuing from the edited
revision, an unknown/traversing revision, and the posegraph bar), 50 node tests
(the kind list read out of `bundle.py` so it cannot drift, alias round-trip,
ambiguity, the `navigable` rule, the editor's home), and four new checks in
`browser_check.mjs` — `pixi run fleet-ui-check` passes 30/30 against a real
broker, server, fleet and chrome, at desk width and on an emulated phone.

Still open on the task: carry-forward accept/reject (needs task 345), and grid
snapping / shared vertices (task 350).

Claude-Session: https://claude.ai/code/session_01LUGSpEmdmC68oBaWp6B5Qz

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant