Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ The hub's on-device menu is driven by a `menu_config.py` file (a `MENU_ITEMS` li
- **Empty `MENU_ITEMS` → no block at all** (`if` with an empty body is a `SyntaxError`), and a hinted module with no file is harmless (ModuleFinder records it in `badmodules`; mpy-tool catches `FileNotFoundError`).
- The docstring stays the first statement, and `parseMenuConfig`'s `/^MENU_ITEMS\s*=/m` anchor is unaffected.

**This adds two top-level statements to the file**, which the starter repo's `check_project.py` does not yet allow — see "Starter-repo follow-ups" below.
**This adds two top-level statements to the file**, which the starter repo's `check_project.py` explicitly allows. See "Starter-repo compatibility" below.

- **`menu-panel.js` — `makeMenuPanel(deps)`.** A draggable floating panel listing the current slots (reorder by drag or ▲/▼, toggle `enabled`, remove, edit the hub display via a number/char/5×5-grid popover) and the addable programs. Position and open state persist under the **`menuPanel`** storage key (`{left, top, open}`); `content.js` reopens the panel after a reload when `open` was true. **Save does not reload** (a reload drops the hub's Bluetooth link). Save regenerates the file and writes it via `write-files-live`, which goes through the app's own store. If `menu_config.py` is open in an editor tab, its Monaco model is updated in place. On `live: true` the panel re-runs `loadState()` and shows `Saved ✓`. The slot controls stay live while a save is in flight, so every edit bumps an `editRevision` counter in `markDirty()`. Save re-checks the counter after every await, and again when the fallback's 800 ms reload timer fires. If it moved, the panel keeps the newer (still dirty) slots instead of refreshing over them or reloading them away, and asks for another Save. The fallback regenerates from the current slots right before its raw write. A `saving` flag keeps the re-rendered Save button disabled until the in-flight save settles, so two saves can't race. On `live: false` it falls back to the old path: `upsert-files` (single-path, never deletes), then `location.reload()`, because a raw write is invisible to the app and an open tab's stale buffer would clobber it on the next write. The panel resolves the config path and protected set from `lastPullManifest` (defaulting to `menu_config.py`); it intersects `protected` with the live `list-files` result and then **excludes** those protected files (and the config file itself) from the "Programs you can add" list.
- **`file-list.js` — `makeFileListWatcher(deps)`.** A `MutationObserver` on `document.body` (debounced 250ms) that finds the page's file rows and (a) adds a 🔒 badge to protected files and (b) attaches right-click / long-press "Add to menu" gestures that call back into `menuPanel.addSlot`. **The selectors are documented in `test/e2e/file-list-dom.md`** — primary path is the Blueprint `[role="tree"][aria-label="Files"]` / `li[role="treeitem"]` / `span.bp5-tree-node-label` structure, with a scoped exact-text fallback. **Gated to the mounted Explorer:** it bails when neither `div.pb-activities-tabview` nor the tree is present, because the Explorer unmounts when closed (the default state, and the state after a reload) and without the gate every settled editor keystroke would trigger a `list-files` round-trip and a text-walk that could badge editor chrome. The badge is inserted as a **sibling after** the label (never inside it) so the label's `textContent` stays a clean path for the next decorate. `protected` here also comes from `lastPullManifest`.
Expand Down Expand Up @@ -237,19 +237,19 @@ Design: `docs/superpowers/specs/2026-07-25-release-automation-design.md`.
- Chrome Web Store material: `docs/webstore-listing.md` is the copy-paste sheet for the entire developer-dashboard submission (listing text, permission justifications, data-usage checkboxes, reviewer notes). `PRIVACY.md` at the repo root is the listing's privacy policy — its GitHub blob URL (`https://github.com/Lansing-Tech-Studio/pybricks-git-extension/blob/main/PRIVACY.md`) goes in the dashboard, so **don't move or rename it** once the listing is live.
- ChromeOS: **unmanaged** Chromebooks now work — the whole product is a sideloaded extension with no server or Crostini requirement, so "Load unpacked" (or a future Web Store install) is all that's needed. **Managed** Chromebooks are still the open risk: they block sideloaded extensions, so those need the Web Store listing plus an admin force-install policy.

## Starter-repo follow-ups (required, not yet done)
## Starter-repo compatibility

The bundle-hint block above changes the shape of `menu_config.py`, so two things in `../pybricks-spike-prime-starter` need to change or the starter's own verifier will reject a file the extension just generated:
The bundle-hint block changes the shape of `menu_config.py`, so the starter repo (`../pybricks-spike-prime-starter`) had to learn it too. **Both follow-ups are done there** (checked 2026-09-18):

1. **`check_project.py:_menu_items_node()`** (around check_project.py:113) allows an optional docstring plus *exactly one* top-level statement and reports a problem otherwise. It must learn to accept the hint preamble — an `_BUNDLE_HINTS` `Assign` plus the guarded `If` — while still rejecting arbitrary top-level code. Suggested shape: skip a leading `Assign` to `_BUNDLE_HINTS` and an `If` whose test is `Name('_BUNDLE_HINTS')` and whose body is nothing but `ast.Import` nodes, then require exactly one remaining statement.
2. **The committed `menu_config.py`** needs the hint block added by hand, so the starter (and every fork made from it) uploads its missions correctly *before* the extension ever regenerates the file. Today `ModuleFinder` over the starter's `main.py` finds only `menu`, `menu_config`, `pix_display`, `robot` — neither sample mission has ever been reachable on-hub.
1. **`check_project.py:_menu_items_node()`** skips an optional docstring, then (via `_strip_bundle_hints`) the `_BUNDLE_HINTS` `Assign` plus an `If _BUNDLE_HINTS:` whose body is only `import` lines, and then requires exactly one `MENU_ITEMS` statement. It accepts the file `generateMenuConfig()` writes, including whole-program and `enabled: False` slots. It still reports a PROBLEM for a non-import inside the hint block, or for any other top-level statement.
2. **The committed `menu_config.py`** carries the hint block for both sample missions, so `ModuleFinder` over the starter's `main.py` reaches `mission_01_go_out_and_turn` and `mission_02_come_back_home`.

Neither is fixed here; this repo only owns the generator.
This repo still owns only the generator. If `generateMenuConfig()`'s output shape changes again, re-run the starter's `check_project.py` against a generated file before shipping.

## Team-features roadmap (phases 2–4, all shipped)

The approved design spec lives in the sibling starter-repo checkout: `../pybricks-spike-prime-starter/docs/superpowers/specs/2026-07-08-team-features-roadmap-design.md` (branch `template-v2`, PR #1). **Read it before starting any phase** — it holds the cross-phase contract (`menu_config.py` MENU_ITEMS schema, `.pybricks-git.json` manifest, setup-file convention) plus the decisions already locked with Brendon (git-layer read-only, panel + file-list gestures, full setup splice with safety rails).

Phase 1 (template repo v2) is done in the starter repo; the manifest and menu-config contract committed there are the interfaces this extension codes against. Phase 2 (protected files) is done — engine + notice shipped: the engine reads `.pybricks-git.json` from the fetched tree, `commit` keeps the tree version of protected paths and reports `protectedSkipped`, `pull` returns the `protected` set, and `content.js` shows a dismissable notice (see the ops table and "The git engine"). Phase 3 (floating menu manager) is done — panel + file-list gestures shipped: `menu-config.js`/`menu-panel.js`/`file-list.js` implement the `menu_config.py` editor and protected-file badging, `inject.js` gained the `upsert-files` op, and `pull` persists `lastPullManifest` for the UI (see "Menu manager (phase 3)" and the bridge-ops table). Phase 4 (new-program-from-template + setup splice) is done — `src/blocksplice.js` (parse/locate/refs/signature/splice/new-program, unit-tested), the new-program + Update-robot-setup panel/file-list UI, the setup-differs nudge, the `spliceReport` + extended `lastPullManifest` keys, and the committed E2E `test/e2e/drive-splice.mjs` all shipped (see "Setup propagation (phase 4)"). The safety rails (snapshot commit first, skip-on-doubt, variable-id remap by name, never touch protected/templates) are implemented and covered end-to-end.

**Production prerequisite (the roadmap's only open item, a manual step for Brendon, not code):** the starter repo's `.pybricks-git.json` names `robot_setup_template.py`, but that file was never authored — it must be created **in the code.pybricks.com editor** (block editor state can't be hand-written) and committed to the starter repo, and each team's fork needs a `robot_setup.py` copied from it. Until then the phase-4 features have no `teamSetup` to act on and stay hidden. The extension's phase-4 tests use harvested/derived fixtures, not that unwritten template.
**Production prerequisite (a manual, per-team step, not code):** the starter repo's `.pybricks-git.json` names `setupTemplate: robot_setup_template.py` and `teamSetup: robot_setup.py`. The template now exists: it was authored in the code.pybricks.com editor and committed to the starter (`ad88b2c`). But `robot_setup.py` is deliberately absent from the starter (`check_project.py` reports it as INFO). Each team's fork needs its own `robot_setup.py`, made from the template in the editor and committed. Until a fork has one, New program says to Pull first and nothing is ever flagged as "setup differs", so the phase-4 features have nothing to act on. The extension's phase-4 tests use harvested/derived fixtures, not that unwritten template.
Loading