Skip to content

dotbot/cli: add site new and site edit, a local site editor - #315

Open
geonnave wants to merge 10 commits into
DotBots:mainfrom
geonnave:site-editor
Open

geonnave wants to merge 10 commits into
DotBots:mainfrom
geonnave:site-editor

Conversation

@geonnave

@geonnave geonnave commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

A site is set up today by writing site.toml by hand and checking it by starting a controller and looking at the console. This adds a small editor for a site pack, launched before LH2 calibration:

  • dotbot site new NAME [--field 2m] writes the pack into the nearest of the two site homes: sites/NAME/ beside the project's dotbot.toml when one is in use, else ~/.dotbot/sites/NAME/, so dotbot site use NAME finds it at once. It starts as the site config init writes (a field with floor round it and a staging strip below) and opens the editor on it. It is not made the active site.
  • dotbot site new checks the name (16 ASCII characters, as a calibration push needs; not already a known site) and that the page is built and the port free before it writes anything.
  • dotbot site edit [SITE] opens the editor on a pack, by name (default: the active site, wherever its pack is) or by a pack folder's path.

Both start a FastAPI app on 127.0.0.1 (free port by default, --port to pick one; it answers only a client on this machine (the peer address is checked, not just the Host header), addressing it as 127.0.0.1 or localhost, from the editor's own page or from no page at all (curl): a request whose Origin or Sec-Fetch-Site names another site gets a 403, so neither a DNS-rebinding page nor a page on another site open in the same browser can read or save the pack) with no controller, broker or robot, print the URL, open a browser through webbrowser (so BROWSER is honoured) unless --headless, and stop on Ctrl-C or the page's Done button.

The file stays the first-class surface. The page never sees TOML: GET /api/site returns a model plus a revision (SHA-256 of the file bytes), and PUT sends the model with the revision it started from. The server parses the file with tomlkit and assigns only the keys that changed, so comments, blank lines, key order and keys the editor does not model ([connection]) survive; a rename keeps the area's place and comment, a new area follows the file's own style (table or inline), and a save with nothing changed writes nothing. The result is validated as SiteSection (422 and the file untouched on failure), written to a sibling temp file and renamed over. A file changed on disk since the page loaded it gets a 409 and the page offers Reload, so the editor and vim can be used side by side; saves are serialised, so two tabs on one revision get one 200 and one 409. A symlinked site.toml is written through to its target. Every patched file is read back and must give the model that was sent, so a layout the patcher cannot follow is refused (422) rather than written: areas split by another table, written as dotted keys or as an inline areas = {...}, or a comment with a line break in it. Deleting an area keeps the full-line comments heading the next table, and a new area goes above them. POST /api/preview backs a "View TOML" button showing the file as Save would write it.

The page is a second vite entry of console-web (site-editor.html, src/siteEditor/), built into the same dist/ and so shipped in the wheel with the console. It reuses the console's frame.ts, grid.ts (grid, rulers), zoom.ts geometry, areaColor.ts and tokens.css, so a site looks the same as on the live map. Areas are drawn with the Area tool or "+ Add area", moved and resized with eight handles, snapped to 50 mm by default (a snap menu offers off/10/50/100/250/500 mm; Alt drags free). The inspector's numbers are what Save writes. Live checks mirror the schema (at most one field, unique names, no comma, which the area resolver reads as an x,y,w,h literal) and warn about an area outside the extent, a field over 5 m (as config init does), and a a+b area that is not the bounding box of a and b. When the site has calibration files (in the pack or under ~/.dotbot/calibrations/<site>/), a note says so and changing the anchor or the extent asks for confirmation, naming calibrate-lh2 collect and reframe; area edits never ask. Wall, Obstacle and Charger are shown disabled until their tables exist in site.toml.

Plan: rendered copy https://claude.ai/artifact/LHUXjhx1nMWifC2oGRBayt, source https://github.com/DotBots/dotbot-workspace/blob/main/plans/site-editor/plan.html (both internal).

Validation: pytest for the round trip (byte-identical write-back of unchanged models on table-style, inline-style and commented-value files; move, add, delete, rename, comment edit, schema refusals) and the server (409, 422, no-op save, atomic write, preview) and the two commands (full pytest 1771 passed); vitest for snapping/resizing, checks, the API client and the editor (drag, save with the loaded revision, Reload on 409, the calibration confirm, Save blocked by a failing check); npm run lint, typecheck and build; wheel contains dist/site-editor.html and its assets. Also: the loopback, Origin and Sec-Fetch-Site guard, concurrent saves, a symlinked file, refused layouts and comment injection, comments heading tables kept on delete and add, renames from any header spelling and a swap, site new writing nothing when it cannot serve; vitest 843 passed, including a click that selects an off-grid area without snapping it. Smoked headless against a copy of a real two-by-four-metre pack: dragging an area by 150 mm and saving changed exactly one number in the file, every comment kept. Nothing here needs hardware.

File + -
dotbot/console-web/src/siteEditor/SiteEditor.tsx +457 -0
dotbot/tests/test_site_editor.py +356 -0
dotbot/console-web/src/siteEditor/Canvas.tsx +355 -0
dotbot/tests/test_site_toml.py +329 -0
dotbot/site_toml.py +323 -0
dotbot/console-web/src/siteEditor/Inspector.tsx +272 -0
dotbot/site_editor.py +229 -0
dotbot/console-web/src/siteEditor/edit.ts +200 -0
dotbot/cli/site_cmd.py +155 -2
dotbot/console-web/src/siteEditor/edit.test.ts +143 -0
dotbot/console-web/src/siteEditor/siteEditor.test.tsx +121 -0
dotbot/console-web/src/siteEditor/api.ts +69 -0
dotbot/console-web/src/siteEditor/api.test.ts +44 -0
dotbot/console-web/src/siteEditor/types.ts +37 -0
4 small files: site-editor.html, vite.config.ts, main.tsx, main.py +46 -3
Total, 18 files +3136 -5

Docs (doc/cli/, README) follow in this PR after review. Stacked on it, in merge order: #326 (place a spin calibration), #327 (undo, nudges, backdrops), #328 (walls and obstacles), #329 (objects), #330 (the editor as a console panel).

@codecov

codecov Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 90.31579% with 184 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.31%. Comparing base (ddc43f7) to head (6cbba6a).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
dotbot/console-web/src/siteEditor/SiteEditor.tsx 82.84% 65 Missing ⚠️
dotbot/cli/site_cmd.py 65.55% 31 Missing ⚠️
dotbot/console-web/src/siteEditor/Canvas.tsx 90.03% 28 Missing ⚠️
dotbot/site_toml.py 91.66% 17 Missing ⚠️
dotbot/console-web/src/siteEditor/api.ts 71.15% 15 Missing ⚠️
dotbot/console-web/src/siteEditor/main.tsx 0.00% 8 Missing and 1 partial ⚠️
dotbot/console-web/src/siteEditor/Inspector.tsx 96.88% 7 Missing ⚠️
dotbot/site_editor.py 95.27% 7 Missing ⚠️
dotbot/console-web/src/siteEditor/edit.ts 96.59% 5 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #315      +/-   ##
==========================================
+ Coverage   88.21%   88.31%   +0.10%     
==========================================
  Files         227      238      +11     
  Lines       36147    38047    +1900     
  Branches     2324     2538     +214     
==========================================
+ Hits        31886    33603    +1717     
- Misses       4257     4439     +182     
- Partials        4        5       +1     
Flag Coverage Δ
console 82.53% <88.20%> (+0.53%) ⬆️
python 91.21% <93.17%> (+0.06%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dotbot/cli/main.py 95.83% <ø> (ø)
dotbot/console-web/src/siteEditor/types.ts 100.00% <100.00%> (ø)
dotbot/tests/test_site_editor.py 100.00% <100.00%> (ø)
dotbot/tests/test_site_toml.py 100.00% <100.00%> (ø)
dotbot/console-web/src/siteEditor/edit.ts 96.59% <96.59%> (ø)
dotbot/console-web/src/siteEditor/Inspector.tsx 96.88% <96.88%> (ø)
dotbot/site_editor.py 95.27% <95.27%> (ø)
dotbot/console-web/src/siteEditor/main.tsx 0.00% <0.00%> (ø)
dotbot/console-web/src/siteEditor/api.ts 71.15% <71.15%> (ø)
dotbot/site_toml.py 91.66% <91.66%> (ø)
... and 3 more

... and 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

AI-assisted: Claude Opus 5.5
AI-assisted: Claude Opus 5.5
… page

Any web page in the browser can send requests to 127.0.0.1 with a valid
Host header, and behind a permissive CORS policy read the replies, so the
Host check alone let it save site.toml; a server bound beyond loopback
took any Host. The peer address, Origin and Sec-Fetch-Site are checked.

AI-assisted: Claude Opus 5.5
…ng tables

A patched file is read back and must give the model that was sent, so a
layout the patcher does not follow is refused rather than written.

AI-assisted: Claude Opus 5.5

This branch has not been deployed

No deployments
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