From 4619f619fa1d766d8a5dccabac2c547e27fdb0ab Mon Sep 17 00:00:00 2001 From: Developer Date: Sun, 13 Sep 2026 20:51:07 +0200 Subject: [PATCH] Uploads keep the name the file came with (#83) API: name on both listings and both uploads, and what the server does to it. Textures and Fonts: tiles and rows are captioned by name, and an asset made in the picker is named after the file. Architecture: the api/ line names the libraries. Development: 780 editor checks. --- API.md | 15 +++++++++++---- Architecture.md | 3 ++- Development.md | 4 ++-- Fonts.md | 3 ++- Textures.md | 6 ++++++ 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/API.md b/API.md index f9106fd..8c5385b 100644 --- a/API.md +++ b/API.md @@ -7,11 +7,11 @@ All routes go through `api/index.php?r=…` (the site's nginx does not pass `PAT | GET | `projects/{id}` | `{id,name,revision,updatedAt,project}` | | PUT | `projects/{id}` | `{revision, project}` → `{revision,updatedAt}` or **409** `{error,revision}` when stale | | DELETE | `projects/{id}` | 204 | -| GET | `textures` | `[{file,width,height,bytes,updatedAt}]` — what the image picker offers | -| POST | `textures` | **raw image bytes** → `{file,width,height,bytes}` (201) | +| GET | `textures` | `[{file,name,width,height,bytes,updatedAt}]` — what the image picker offers | +| POST | `textures&name=hero.png` | **raw image bytes** → `{file,name,width,height,bytes}` (201) | | DELETE | `textures/{file}` | 204 | -| GET | `fonts` | `[{file,bytes,updatedAt}]` — what the font picker offers | -| POST | `fonts` | **raw font bytes** → `{file,bytes}` (201) | +| GET | `fonts` | `[{file,name,bytes,updatedAt}]` — what the font picker offers | +| POST | `fonts&name=Serif.ttf` | **raw font bytes** → `{file,name,bytes}` (201) | | DELETE | `fonts/{file}` | 204 | Every project document carries `formatVersion` (`PROJECT_FORMAT_VERSION` in `core/model/Project.ts`, @@ -25,4 +25,11 @@ files are served statically from `data/textures/` and `data/fonts/`, so reading PHP. An image is downscaled and re-encoded in the browser when it would not fit; a font cannot be, so an oversized typeface is refused rather than shrunk. +**`name` is the original file name**, kept beside the stored file as `{file}.json` and returned +with it. The stored file is always `{sha256}.{ext}`, so the name is a caption and never a path: +the server strips directories, control characters and anything not valid UTF-8, and caps it at +120 characters. Uploading the same bytes again under a different name **renames** what the +pickers show; an upload that carries no `name` leaves the known one alone rather than erasing it. +A file uploaded before names were kept simply has `""`, and reads as it always did. + There is no authentication yet; `data/` is world-readable. Both are phase-2 topics. diff --git a/Architecture.md b/Architecture.md index ea08d6c..41a3255 100644 --- a/Architecture.md +++ b/Architecture.md @@ -72,7 +72,8 @@ src/editor/animation/ AnimationEditor (header + picker), AnimationCanvas (previ src/editor/statemachine/ StateMachineEditor (header + picker, on the graph canvas) src/editor/list/ ListEditor (a row per item), slot (which list is being edited, and how it is written back) src/editor/console/ log (ring buffer, argument formatting, the console.* capture), ConsolePanel (the tail under the tabs) -api/ PHP backend data/projects/ persisted projects +api/ PHP backend: index.php (routing), lib/{Store,TextureStore,FontStore,OriginalName,Response}.php + data/projects/ persisted projects, data/textures/ + data/fonts/ content-addressed uploads tests/ Vitest unit tests tools/ seed-*, e2e (steps in tools/e2e/, rig in tools/e2e/lib/), smoke-*, lib/, assets/ (the walk demo's sheet) ``` diff --git a/Development.md b/Development.md index 8c1fb94..57245b9 100644 --- a/Development.md +++ b/Development.md @@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs # code is lost (tail's is reported), so a red run reads as green npm run build # writes dist/ and regenerates index.html ./build.sh # the same under `set -o pipefail`, for callers that pipe the output -node tools/seed-e2e.mjs && node tools/e2e.mjs # 775 editor checks, screenshots in /tmp/gm-e2e-*.png +node tools/seed-e2e.mjs && node tools/e2e.mjs # 780 editor checks, screenshots in /tmp/gm-e2e-*.png GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step ./e2e.sh start [GM_STEPS] # the same run detached, see below ./e2e.sh status # live progress, or the last run's result @@ -151,4 +151,4 @@ widths to find click points. The **render device is not in this repo** — `/dev and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`, -WebGL 2.0, and the same 775 checks as here. +WebGL 2.0, and the same 780 checks as here. diff --git a/Fonts.md b/Fonts.md index 82b3723..596ef7d 100644 --- a/Fonts.md +++ b/Fonts.md @@ -16,7 +16,8 @@ holds**, one row each. There is no thumbnail to show for a font, so each row *is the sample line is drawn in that very face. Picking one and pressing **Use font** makes the asset; **Upload…** is the way in for a face the server does not have yet. A row captioned with an asset name is one this project already wears, and picking it uses that asset rather than -making a second one. +making a second one; any other row is captioned with the name the file was uploaded under, and +picking it names the asset after that file — `DejaVuSerif.ttf` becomes `DejaVuSerif`. **Where it goes.** The bytes are uploaded to `POST ?r=fonts` and stored as `data/fonts/{sha256}.{ext}` — named after their own content, exactly like an image, and with diff --git a/Textures.md b/Textures.md index 78e5704..0de86d2 100644 --- a/Textures.md +++ b/Textures.md @@ -17,6 +17,12 @@ makes a texture asset on that file; **Upload…** is the way in for a picture th have yet, and lands in the same import as a drop. A tile captioned with an asset name is one this project already wears — picking it selects that asset rather than making a second one. +**Tiles are captioned by name, not by hash.** A picture this project wears shows its asset name; +any other shows the name the file was uploaded under, which the server keeps beside it (see the +[API](API)). Picking such a file names the new asset after it — `hero-idle.png` becomes +`hero-idle`, exactly as dropping the file would have — instead of the old `Texture 2`. Only a +file from before names were kept still reads as its pixel size. + **Where it goes.** The image is uploaded as raw bytes to `POST ?r=textures` and stored as `data/textures/{sha256}.{ext}` — the file is named after its own content. That name buys four things: the same picture uploaded twice is one file, a stored file can be cached forever