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",