From 8d9098af229697ac1aba8f1e6a60ca58d63ac8bf Mon Sep 17 00:00:00 2001 From: Bernard <63512176+BernardJen@users.noreply.github.com> Date: Sat, 12 Sep 2026 13:24:40 +0200 Subject: [PATCH] chore(release): v1.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The editor feature set (epic #10): scene model and editing toolbar, pens and multi-pen plotting with pen-change pauses, shapes and single-line text, the image import wizard with five conversions, PDF/HEIC/camera import, cutting mode with overcut, blade compensation and holding tabs, magnet keep-out zones with travel routed around them, projects, app settings on the gateway, and a settings page. Also fixes a path default that would have bitten on exactly this upgrade: app settings and projects defaulted to locations beside the *bundle*, which on a packaged Pi is a read-only /opt. The file naming the right paths is a dpkg conffile, so a machine whose operator had edited it would keep the old copy, never learn the new variables, and silently fail to persist either. Both now derive from the state file's directory, which has been configured since the first packaged release — the same trick the update-status file already used. Verified with only PLOTTER_STATE set, which is precisely the upgraded-Pi case: app settings and a saved project both land beside the state file. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 10 +++++++++- README.md | 4 ++-- gateway/server.ts | 13 +++++++++---- package-lock.json | 4 ++-- package.json | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bde704..22b27f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,13 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.3.0] - 2026-09-12 + ### Added - **Save and load projects.** A versioned project file holds the whole job — objects, placements, pens per object, paper, mode and magnets — plus the pen library it refers to, so a drawing opened on another machine still comes out in the right colours. Save to your own device as a file, or to the plotter, which now keeps a library every connected client - can list, open and delete (`PLOTTER_PROJECTS`, default `gateway/projects/`). Project names + can list, open and delete (`PLOTTER_PROJECTS`, defaulting to a `projects/` directory beside the state file). Project names are sanitised before they become paths on the Pi, and the resolved path is checked against the projects directory as well. - **Import from anything.** One import that identifies a file by its *contents* rather than @@ -28,6 +30,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- App settings and saved projects defaulted to paths beside the *bundle* rather than + beside the state file. On a packaged Pi that is a read-only `/opt`, and the config file + naming the right paths is a dpkg conffile — so an upgraded machine whose operator had + edited it would have kept the old copy, never learned the new variables, and silently + failed to persist either. Both now default to the state file's directory, which has been + configured since the first packaged release (as the update-status file already did). - The daemon served `.mjs` as `application/octet-stream`, which browsers refuse to execute as a module — a lazily-loaded chunk would have worked in the dev server and failed only in the packaged build. `.wasm` and `.map` were missing too. diff --git a/README.md b/README.md index 7958461..a17af57 100644 --- a/README.md +++ b/README.md @@ -182,8 +182,8 @@ On macOS the daemon automatically runs `caffeinate -dimsu` for its lifetime so i | `PLOTTER_PATH` | _(auto)_ | Pin the serial device; otherwise auto-detect a `usbserial`/`wchusbserial`/`ttyUSB`/`ttyACM` port | | `PLOTTER_STATE` | `gateway/.plotter-state.json` | Where the remembered position is persisted | | `PLOTTER_SESSION` | `gateway/.session.json` | Where the shared editable session (artwork + page) is persisted | -| `PLOTTER_APP_SETTINGS` | `gateway/.app-settings.json` | Where app settings (machine setup, preferences) are persisted — shared by every client | -| `PLOTTER_PROJECTS` | `gateway/projects/` | Where saved projects are stored, one file per project | +| `PLOTTER_APP_SETTINGS` | _(beside `PLOTTER_STATE`)_ | Where app settings (machine setup, preferences) are persisted — shared by every client | +| `PLOTTER_PROJECTS` | _(`projects/` beside `PLOTTER_STATE`)_ | Where saved projects are stored, one file per project | | `GATEWAY_ALLOWED_ORIGINS` | _(none)_ | Extra browser origins allowed to open the WebSocket, comma-separated. Same-origin always passes; add `http://localhost:5173` when driving a live daemon from the Vite dev server | ## Registration (cut to a printed sticker) diff --git a/gateway/server.ts b/gateway/server.ts index 88d5e63..1f596f2 100644 --- a/gateway/server.ts +++ b/gateway/server.ts @@ -51,13 +51,18 @@ const SESSION_FILE = // App settings (machine setup, preferences) live here — one plotter, one setup, // so every client that attaches adopts these. Separate from the session file on // purpose: the artwork is replaced constantly, the machine setup almost never. +// +// Defaults are derived from the *state* file's directory, not from the bundle's, +// because the packaged app lives in a read-only /opt and its config file is a +// dpkg conffile: an upgraded Pi whose operator had edited that file would keep +// the old copy, never learn the new variable, and silently fail to write here. +// PLOTTER_STATE has been set since the first packaged release, so its directory +// is the one place known to be writable. (`UPDATE_STATUS` already does this.) const APP_SETTINGS_FILE = - process.env.PLOTTER_APP_SETTINGS ?? - join(fileURLToPath(new URL('.', import.meta.url)), '.app-settings.json'); + process.env.PLOTTER_APP_SETTINGS ?? join(dirname(STATE_FILE), '.app-settings.json'); // Saved projects live on the Pi so it holds a library of plots any client can // open — one file per project, in a directory of their own. -const PROJECTS_DIR = - process.env.PLOTTER_PROJECTS ?? join(fileURLToPath(new URL('.', import.meta.url)), 'projects'); +const PROJECTS_DIR = process.env.PLOTTER_PROJECTS ?? join(dirname(STATE_FILE), 'projects'); // ---- self-update config ---- // Where the update oneshot records its progress; the daemon reads it back after diff --git a/package-lock.json b/package-lock.json index 0c69959..f86f176 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "penplotter271", - "version": "1.2.1", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "penplotter271", - "version": "1.2.1", + "version": "1.3.0", "dependencies": { "@types/ws": "^8.18.1", "konva": "^10.3.2", diff --git a/package.json b/package.json index 8be0164..633aadd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "penplotter271", "private": true, - "version": "1.2.1", + "version": "1.3.0", "type": "module", "scripts": { "dev": "vite",