Commit Graph

4 Commits

Author SHA1 Message Date
David Montero a68e7f8e94 feat(editor): rename boards & custom chips; show which target owns each file
Phase 2 of the run-system/UX work.

- BoardInstance gains an optional user ; boardDisplayName(board) resolver
  (name || kind label) routes every INSTANCE-label surface: file-explorer
  section header, compile console (EditorToolbar), canvas selector/tooltip/
  context-menu, Serial Monitor tabs, Oscilloscope board picker, Board Options
  subtitle. Board/component pickers keep the KIND label (they pick new boards).
- Inline rename on board AND chip section headers (double-click the name, or a
  hover pencil button). Board -> updateBoard(id,{name}); chip -> chipName in
  properties. Enter commits, Escape cancels (cancel-flag ref guards the
  unmount-fires-onBlur footgun), empty clears to the kind / 'Custom Chip'.
- FileTabs shows an owner badge naming the board/chip whose files are shown
  (resolved as a selector so it doesn't re-render on every sim pin toggle).
- CustomChipDialog no longer clobbers a user-given chipName: chip.json's name
  only seeds the blank defaults (My Chip / Custom Chip); loading an example
  relabels explicitly.
- Persistence: board name round-trips via projectPayload (+ dirty hash),
  vlxFile, ProjectByIdPage load + loadProjectState; chipName rides components_json.
- Drive-by: fixed a pre-existing rules-of-hooks violation in BoardOptionsModal
  (early return before a useCallback).

Reviewed by a 3-agent adversarial pass (completeness / persistence / correctness);
all major findings folded in.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 06:22:25 +02:00
David Montero 5a23e89eb5 feat(custom-chip): program lives in its own editor group, not the board sketch
A programmable custom-chip (a CPU emulator that runs a ROM/program, e.g. the
Z80 or 8080) now keeps its program (larson.s, chaser.c, ...) in a dedicated
editor file group — group-chip-<chipId> — rendered as its own collapsible
section in the file explorer, exactly like each board owns its sketch group.
Behaviour/driver chips and predefined chips carry no programFile and get no
group; they stay editable only in the chip designer.

Fixes two reported issues on the Z80 examples:
- /example/z80-larson-no-board: the board-less chip example now opens its
  program (larson.s) as the active group, editable on the left — previously
  the editor showed but no file appeared.
- /example/z80-led-chaser-c: the chip program (chaser.c) no longer shows as
  a sibling tab inside the Arduino sketch group; it sits in its own chip
  section instead. The board group shows only sketch.ino.

Details:
- useEditorStore: chipFileGroupId()/CHIP_GROUP_PREFIX helpers.
- loadExample: seedChipProgramGroups() routes each chip's programFile into its
  own group (seeded from the example files), sweeps stale chip groups, keeps
  the program OUT of the board group, and for a board-less chip example makes
  the chip group active so the program is the editable file shown.
- EditorToolbar.prepareCustomChips: resolves the program from the chip's own
  group (falls back to board files for older projects) before assembling ROM.
- FileExplorer: renders one collapsible section per programmable chip with an
  IC icon; clicking switches the editor to the chip group. Lazy-creates a
  group for chips dropped on the canvas.
- projectPayload + vlxFile: serialise chip groups alongside board groups and
  include them in the dirty-check hash, so chip-program edits persist on
  save / autosave / .vlx export and round-trip via replaceFileGroups on load.
- Regression tests for board-less + board+chip routing and stale-group sweep.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 22:07:56 +02:00
David Montero Crespo 16f168ba7f feat(board-options): per-board options modal + persistence
Adds a new BoardOptionsModal accessible from the EditorToolbar that exposes
per-board options (currently used for board-specific compile flags). Wires
the modal through:

- types/boardOptions.ts            new BoardOptions shape
- types/board.ts                   BoardInstance gains `boardOptions` + `spiffsFiles`
- store/useSimulatorStore.ts       boardOptions persisted in loadProjectState
- components/editor/EditorToolbar.tsx     button to open the modal
- components/simulator/BoardOptionsModal.{tsx,css}  the modal itself
- components/simulator/SimulatorCanvas.tsx  passes the options through
- utils/projectPayload.ts          board options serialised in saved projects
- pages/ProjectByIdPage.tsx        re-includes the by-id loader needed for
                                   project URLs that reference boards with
                                   their persisted options.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 21:50:45 -03:00
David Montero Crespo 6a88375bc0 feat: persist multi-board projects + add auto-save
The project save/load pipeline only persisted a single `board_type`, so
multi-board workspaces silently lost every board except the active one
on save, and wires referencing the dropped boards' IDs orphaned to the
canvas corner on reload. An audit of the production backup found 74/306
projects (24%) with at least one orphaned wire and 174/301 non-trivial
projects whose code was still the default Blink template — strong signal
that users save once and never re-save.

Backend
- Add `boards_json` column on `projects` with idempotent ALTER TABLE in
  the lifespan migration list.
- New `FileGroup` schema + `file_groups` array on
  ProjectCreate/Update/Response. Legacy `files`/`code` kept for back-compat.
- `project_files.py` now uses `{pid}/{groupId}/{filename}` subdirs via
  `read_groups`/`write_groups`. Legacy flat layouts are auto-promoted on
  read; legacy single-list `files` only updates the active group, leaving
  other boards' files intact.
- `_persist_files_from_body` honors file_groups → files → code priority.

Frontend
- `useSimulatorStore.addBoard` accepts an optional `explicitId` so
  saved board IDs can be restored verbatim (wires reference IDs literally).
- New `loadProjectState({boards, fileGroups, components, wires,
  activeBoardId})` action: tears down current boards, recreates from the
  payload, restores file groups atomically, recalculates wire positions
  on the next frame, and refreshes the Interconnect.
- `useEditorStore.replaceFileGroups` for atomic multi-group restore.
- `SaveProjectModal` and `ProjectByIdPage`/`ProjectPage` now go through
  `buildSavePayload` / `buildLoadPayload` (handles pre-backfill projects
  by synthesising a default board from `board_type`).

Auto-save (#useAutoSaveProject hook)
- 2.5s debounced silent PUT triggered ONLY when an authenticated user
  has a `currentProject` with a UUID. State hash detects real changes
  vs. UI-only churn; baseline is reset on project load so the just-loaded
  state isn't immediately re-saved.
- `beforeunload` flush via `fetch keepalive: true` (supports PUT +
  credentials, survives unload).
- Compact status indicator in `AppHeader` (idle/dirty/saving/saved/error).

Backfill script (one-off, idempotent)
- `backend/scripts/backfill_boards_2026_05.py` populates `boards_json`
  for legacy projects. Heuristic per project, based on which board IDs
  the wires reference:
    Case A — wires only ref 'arduino-uno' but board_type ≠ uno:
             rename id→board_type and rewrite wire endpoints.
    Case B — single-board normal: keep verbatim.
    Case C — multi-board: recreate one board per distinct ref, infer
             kind by stripping trailing -N suffix.
  Also moves any flat files into the active board's group subdir.
  Stdlib-only, runs from host or `docker exec`.

Docker
- `Dockerfile.standalone` now copies `backend/scripts/` into the image
  so the backfill is callable via `docker exec velxio-app python
  /app/scripts/backfill_boards_2026_05.py --apply`.

Verified locally on the restored production backup (363 projects):
33 Case A, 316 Case B, 14 Case C, 135 wire endpoints renamed, 0 orphans.
Re-running the script after apply skips all 363 (idempotent).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 13:43:33 -03:00