The phone layout had three problems that compounded when running ADC /
Pin / Serial demos: the editor refused to shrink past 42vh so panels
spilled over a clipped console, the workspace badge was crammed full of
buttons, and the IN-pin toggle button was unreliable to tap.
- styles.css: on `<= 768px`, make `.main-content` scroll vertically and
give the editor a fixed 50vh height (drops to 32vh via `:has()` when a
simulator panel is open). All panels + console pin to `flex: 0 0 auto`
so flexbox stops squashing them. Inner panel scroll caps tightened to
22vh so two stacked panels don't push the console below the fold.
`.pin-toggle` gets `touch-action: manipulation` + `user-select: none`
to fix iOS taps inside scrollable parents.
- script.js: pin button now has a `pointerup` backup with a 300 ms
debounce alongside `click` (Safari sometimes drops `click` on small
buttons inside scroll containers). The `⋮` workspace menu auto-closes
on outside `pointerdown` as well as `click`, so the open dropdown
can't sit on top of the Pin panel and absorb taps.
- script.js / index.html / styles.css: move every Workspace action
(Export, Import, Reset demos, plus the local-mode-only Folder…,
Reconnect, IndexedDB swap, Exit) out of the badge and into a new
"Workspace" section in the `⋮` menu. Badge keeps just the storage
label. Adds `.menu-separator`, `.menu-section-label`, `.menu-note`,
and `.menu-action` styles; removes the now-unused
`.workspace-badge-action` / `-exit` / `-note` rules.
- bundled-demos/pin_demo.py: pin 4 is now driven exclusively by the
IRQ handler, so it stays steady until the IN button is pressed —
previously it auto-flashed via on/off in the loop, which made the
IRQ effect indistinguishable from the existing animation. The IRQ
handler also no longer prints on every press (the panel indicator
is the feedback).
Cache busters: styles.css 32 -> 36, script.js 57 -> 59.
Co-authored-by: Cursor <cursoragent@cursor.com>
`workspace/` is runtime state (per-user folders, no-auth dev's `code/`)
and shouldn't be in git. The same files were previously committed under
both `workspace/code/` and `src/static/bundled-demos/`, which forced a
Docker `diff -q` sync check and leaked user-scoped paths into version
control.
- /workspace/ added to .gitignore; all previously tracked files removed
via `git rm --cached`.
- src/static/bundled-demos/ becomes the single source of truth: panel16
demos, led_tutorial, led_patterns, neopixel demos, and main.py move
here alongside the existing canonical demos.
- New BUNDLED_DEMOS_DIR config; user_workspace seeders read from it.
- main.py lifespan seeds WORKSPACE_ROOT/code/ on startup so a fresh
clone running `pipenv run dev` still gets the full sample set
(existing files never overwritten — user edits survive restarts).
- Dockerfile drops `COPY workspace` and the diff sanity check.
- README/LED_TUTORIAL repointed at the new canonical paths.
- test_led_patterns loads led_patterns.py from bundled-demos.
- test_api uses mkdir(exist_ok=True) for `code/` (startup pre-creates).
Co-authored-by: Cursor <cursoragent@cursor.com>
The previous build step copied `workspace/code/<demo>.py` into
`src/static/bundled-demos/` at image-build time. That failed for some
build contexts where `workspace/` wasn't materialised when the RUN
ran (cp: cannot stat ... No such file or directory).
Since `src/static/bundled-demos/*.py` are version-controlled and ship
with `COPY src ./src`, the runtime image already has them. Replace the
fragile cp loop with a `diff -q` invariant that fails the build if a
canonical demo drifted between `workspace/code/` and
`src/static/bundled-demos/`, catching mismatches at build time instead
of runtime.
Co-authored-by: Cursor <cursoragent@cursor.com>
Existing accounts (including admin) seeded before new demos shipped
had no easy way to pull in the latest copies — the registration-time
seeder is intentionally non-destructive. The new badge action fetches
src/static/bundled-demos/manifest.json, confirms the overwrite, and
re-copies each canonical demo into code/. Open tabs of those files are
refreshed in place so the user sees the new content immediately.
src/static/bundled-demos/ ships the six canonical files plus the
manifest so this works in local mode and on a static-only host. The
Dockerfile now mirrors workspace/code/<demo>.py into bundled-demos/
during the image build, keeping the two locations in sync.
Co-authored-by: Cursor <cursoragent@cursor.com>
`_CANONICAL_DEMO_FILENAMES` now also lists `pin_demo.py`,
`adc_slider_demo.py`, and `serial_demo.py` so first-time users get
working examples for every simulator. Seeding stays idempotent — the
dst-exists guard keeps re-registration / sign-in from clobbering edits.
Co-authored-by: Cursor <cursoragent@cursor.com>
Boot:
- Editor now picks local vs server mode based on URL flag, sign-in
state, and a stale local-mode flag. Signed-in users are no longer
bounced to IndexedDB if they had previously clicked "Use locally".
Local mode:
- New LocalWorkspaceClient (src/static/local-workspace.js) with
pluggable IndexedDB and File System Access backends. Picked folder
handles persist across reloads with a Reconnect button when the
permission lapses.
- Static-only host: scripts/serve_static_editor.py serves src/static/
with COOP/COEP so SharedArrayBuffer-backed sims keep working.
- Bundled MicroPython stubs ship under src/static/bundled-lib/ for
static hosting; FastAPI also exposes them at /api/public/lib-bundle.
Workspace import / export:
- Zero-dep ZIP encoder + reader (STORE + DEFLATE via
DecompressionStream). Export/Import buttons in the workspace badge
work in both local and server modes; imports are confined to code/.
Pin / ADC / Serial simulation:
- machine.py grows ADC, UART, expanded Pin, and PWM mocks, all driven
by SharedArrayBuffer when cross-origin isolated and falling back to
postMessage + [pin-out] stdout markers otherwise — pins, ADC slider,
and serial input now keep working over plain HTTP / LAN-IP origins.
- NeoPixel pins are claimed via a [pin-claim] marker and dropped from
the Pins panel so the data line doesn't flicker per write().
- New demos: adc_slider_demo.py, pin_demo.py, serial_demo.py.
Lib layout:
- Single source of truth at repo lib/; workspace/lib/ caching layer
removed and the directory deleted. Filesystem service reads stubs
directly from PROJECT_ROOT/lib.
UI:
- Home page slimmed to "Sign in" + "Use locally" with optional editor
/ manage-users links. Admin user/invite UI moved to /users.
- Workspace badge gains storage indicator, Folder…/Reconnect, Export,
Import, and Exit controls.
- Mobile-friendly tweaks: safer-area padding, larger touch targets,
iOS-zoom-proof serial input, file-tree highlight fix.
Tests:
- test_auth.py patches PROJECT_ROOT for the lib-shared test so the
repo-root lib refactor stays green. test_api.py asserts the new
"LED Editor" branding.
Co-authored-by: Cursor <cursoragent@cursor.com>
Use "LED Editor" in page titles and the home heading. On narrow
viewports, make the file tree an off-canvas drawer with backdrop,
hamburger toggle, Escape to close, and auto-close after opening a
file. Add safe-area and tap-target tweaks, cache-bust static assets.
Co-authored-by: Cursor <cursoragent@cursor.com>
Move machine.py and neopixel.py into a tracked /lib/ at the repo root and
auto-copy them into WORKSPACE_ROOT/lib whenever files are missing, so empty
volumes and fresh per-user workspaces always have the read-only stubs
available to Jedi and Pyodide. Allow all users to browse lib/ in the UI
(writes still gated by the API), and add tests covering initial seeding
and re-population after the dir is wiped.
Co-authored-by: Cursor <cursoragent@cursor.com>
Implement invite-token registration with optional email delivery, add admin UI actions for creating invites and opening user workspaces, and support superuser workspace override while preserving per-user code isolation with shared read-only lib.
Made-with: Cursor