Skip to content

1.18: Vector versions as one group, a viewer with the drawing, Delete version, a depth stack, and pictures by paste - #40

Merged
NoSQLKnowHow merged 8 commits into
mainfrom
vector-versions
Nov 7, 2026
Merged

NoSQLKnowHow merged 8 commits into
mainfrom
vector-versions

Conversation

@NoSQLKnowHow

Copy link
Copy Markdown
Owner

What this is

Version 1.18, built to docs/DESIGN.md §35 and §36 (specified in #39). Two changes that share only App.tsx:

Vector (§35)

  • The versions of a Vector run are one compact card, and a viewer shows the drawing beside the vector.
  • Delete version sends one version to the bin (restore, Delete forever, the clean-up and Empty bin all count versions); the Deleted view has a card of versions.
  • Stacked (depth) is visible and orderable: a Height column, a stack view, and a Back sheet choice (stack_order: area_desc, the default and what 1.17 did; area_asc; palette). The order is applied after colours are assigned, so it can never change a layer's colour.

Pictures by paste (§36)

  • A paste works anywhere on the Images tab (one page listener), Generate switches to Edit with Undo when pictures arrive, Upscale a picture… takes a chosen, dropped or pasted picture, and the limit is up to 8 (STUDIO_MAX_INPUT_IMAGES; the default stays 4 until the Spark has measured memory).

Back up first

The first start of 1.18 upgrades the database to schema 8 (vectors.deleted_at, added as the last column so an upgraded database equals a new one). A copy is made first (data/studio.sqlite.before-schema-8); every run keeps its data. docs/SPARK_TEST.md §29 is the checklist and starts with this.

How it was built

The 1.15 code that bins, restores, deletes and purges one picture or track is now one piece of code for image, track and vector. New routes: POST /api/vectors/{id}/bin, POST …/restore, DELETE /api/vectors/{id}, GET …/drawing, GET …/layers/{n}.svg. No server change for §36. What was built, and where it differs from the spec, is in DESIGN.md §36.9.

Evidence (all local; the repo has no CI)

Run on the final tree:

  • Backend 1804 passed, 3 skipped (1.17 had 1745; the 3 skips are modules that need torch). Front end 858 (Vitest; 1.17 had 747). 281 in a real browser (Playwright; 1.17 had 257): 280 pass and one does not on the machine I used, see below. Type check clean.
  • Mutation checks, 530 mutants of the 1.18 code in three rounds: server 149, page (Vitest level) 349, browser level 32. Survivors were judged one by one: comments and docstrings, code 1.18 did not change, equivalents, and 7 real gaps on the server (the new routes' 404 sentences, a version deleted for good whose preview was already gone, one ink in the back sheet's list order, a 1.17 run traced as area_desc), each now killed by a test. Details in §36.9.
  • A real bug found by the browser scenarios, not the unit tests: the page's store decided whether the counts had changed from pictures and tracks in the bin only, so restoring a version left the number beside Deleted unchanged. Fixed, with a unit test that fails without the fix. Another, in my own code: the intake handed Array.forEach's three arguments to a function that takes one.
  • The one browser test that fails is music.spec.ts Add lyrics shows Voice and the lyrics box…. It fails the same way on the untouched 1.17 tree (3 runs out of 3) on the machine I used, whose Chromium is not the build the pinned Playwright expects. It is not about 1.18. One Vector scenario failed once under CPU load and passed on rerun.

Not checked

  • A real drawing by Qwen-Image in the viewer and the stack view (everything ran on the fake pipeline's flat shapes). Whether the stack reads as depth is a judgement only a person can make (SPARK_TEST.md §29 (d)).
  • XCS and LightBurn with a stacked file (§31.9's question is still open; a stacked file is where it matters most).
  • A real clipboard, your browsers and a phone. The browser tests build a ClipboardEvent by hand in Chromium; a phone's keyboard may not offer a picture to paste at all.
  • Eight pictures on the GPU. The server takes them (tested with the fake pipeline) but nobody has measured the memory, which is why the default is still 4.
  • Firefox and Safari, a screen reader, and dark mode of the viewer and stack view.
  • The Spark itself. Nothing of 1.18 has run on the GB10 yet.

Small choices made while building

See §36.9 for all five; the one most worth a look: in Generate, "a picture was accepted" means it passed the page's own checks. A file that passes those but that the server cannot decode (a corrupt PNG) switches the card to Edit and is then refused on upload, as a note in the tray. §36.2 said Generate should stay put for an unreadable picture; knowing that would mean decoding every file in the page first.

Known limits

The Back sheet is chosen before the run (changing it afterwards needs the clean-up run again, §35.7). The stack view is a picture of offset sheets with a shadow, not a 3-D model. A drop on the bare Images page still does nothing. A deleted version's drawing, ZIP and SVG stay on the disk until its days are over.

Notes

Version bumped to 1.18 (__init__.py, package.json, lock file, test data).


Generated by Claude Code

…N section 35)

A stacked Vector run can now say which colour is the back sheet: stack_order is area_desc (the most
ink at the back, today's rule and the default), area_asc (the least ink) or palette (the first ink of
the form's list, then the rest of the form's inks, then the inks the picture chose). Colours are
assigned before the reordering, so the order never changes a layer's colour. The choice only counts
for a run stacked by colour; side by side and by shape ignore it and the check says stack_order null.

The check gains height for each layer (its number when stacked, the back sheet being 1, and 1 when
side by side) and stack_order at its top. The option goes through the request, the run's stored
options, limits.vector (stack_orders and its default), the job's options file (a 1.17 file without it
means the default) and the contract file.

Tests: the disc/square/dot picture the spec measured, six nested colours making six levels, the
reverse and palette orders, colours unchanged by the order, the choice ignored side by side and by
shape, an unknown value refused by name, and a 1.17 options file still loading.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011NDCKtu1QcLwawbdzwmFA1
…s (DESIGN sections 35.2 to 35.4)

Schema 8 adds vectors.deleted_at (the last column, so an upgraded database equals a new one; a copy
of the database is made first). A version is now binned, restored, deleted for good, counted and
purged by the same code as a picture or a track: _ITEM_TABLES gains "vector", and the three routes
POST /api/vectors/{id}/bin, POST .../restore and DELETE /api/vectors/{id} are registered with the
picture routes. Binning answers moved: "version" or "run" (the last version sends the run).
vectors_for_runs now leaves out versions in the bin and binned_vectors_for_runs lists them; the run
payload gains binned_vectors (empty for a run that is itself in the bin); the Deleted view's filter
(RunFilter) and the counts include a run with a version in the bin. A version's four files (SVG,
ZIP, PNG preview and the model's drawing) are removed together.

Two routes for the viewer: GET /api/vectors/{id}/drawing (the stored <idx>-source.png, nosniff,
immutable, a download name ending -drawing.png, a 404 that says the drawing is no longer on the
disk) and GET /api/vectors/{id}/layers/{n}.svg (one layer's SVG out of the ZIP, with the same
script-forbidding headers as the combined SVG). The version payload gains drawing_url,
drawing_download_url and layer_urls.

A version that could not be made is named by its seed in the run's note, not by its place (a place
changes when another version is deleted).

Tests: tests/test_vector_version_bin.py (32), the picture-bin database tests now also run for
versions, the shared filter table gains a Vector run with a version in the bin, a schema 7 to 8
migration test, and the schema-7-specific tests follow the current schema. The contract file gains
binned_vectors, the binned version example and the new payload keys; the fake clean-up job packs one
SVG per layer and mirrors a stacked check.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011NDCKtu1QcLwawbdzwmFA1
… view, Back sheet

The Vector card is now one card for the run, as an Images card is: a grid of
the versions' previews at the left (each a button that opens the viewer), the
run's buttons in one row at the right. A run with exactly one version keeps its
facts and its three downloads on the card; a run with several has Download...,
which opens the viewer at the first version.

The viewer (VectorViewer) covers the page as the Images viewer does, shows the
model's drawing beside the vector (with Download drawing, and a plain line when
the file is gone), the colours line, the layer table, the smallest feature, the
notes, the downloads, one PNG size box for the whole viewer, and Delete version.
A version made Stacked (depth) also gets the Height column and the stack view:
the layers' own SVGs as sheets, back to front, with a Sheets shown control.

The form says Stacked (depth), has a Back sheet choice (greyed with the reason
unless the run is stacked by colour) and a Bleed hint that says what it does.
The Deleted view shows a run's deleted versions as one card with Restore and
Delete forever, and opens them read-only in the viewer. Deleting a run says how
many versions go with it.

Copy SVG code moved out of the page component into useCopyVectorSvg so that it
is tested on its own (one request at a time, the file only on the clipboard,
and the answer goes to wherever asked last: a toast, or the viewer's note).

Vitest: 747 -> 789 tests (vector.test.tsx rewritten for the new card; new
vectorviewer.test.tsx for the viewer, stack view, Height column and the card of
deleted versions).

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011NDCKtu1QcLwawbdzwmFA1
…with Undo, Upscale by drop or paste

One page-level paste listener (usePicturePaste) replaces the prompt box's own, so
a paste acts once. Where a paste goes is a pure function (paste.ts routePaste):
a paste with no picture is never touched; nothing is taken while a dialog, the
Options drawer or a viewer is open; on the Music and Vector tabs a picture is not
taken and a note says "Pictures are used on the Images tab."; on the Images tab a
picture wins in the prompt box, and in any other text field it is taken only when
the clipboard has no plain text; with Upscale a picture... focused it goes there.

In Generate a pasted or dropped picture is no longer refused: the pictures are
added in order, the card becomes Edit with the prompt kept, and a toast counts
them ("Refer to them as image 1, 2, 3.") and offers Undo, which removes exactly
those pictures and goes back to Generate. When none is accepted the card stays in
Generate and each rejection is named; without editing, "Editing isn't available."
and nothing is added. The tray reports what it refused (a file that is not a
picture, a file over the size limit, how many did not fit) and can be asked to
stay quiet, which Generate does because its tray is not on screen.

Upscale a picture... takes a chosen, dropped (it lights up) or pasted picture
(only while it has the focus; a click focuses it), uses the first of several and
says so, and says "Still upscaling the last picture." while busy. Generate's help
line and Upscale's tooltip gain the sentences the spec gives.

No server change. Backend tests added for a limit of 8 (eight in order, a ninth
refused naming the limit, reuse with all eight, the shipped default still 4).
Vitest 789 -> 837; new e2e/paste.spec.ts (12 scenarios), edit.spec.ts and
fourk.spec.ts updated for the new behaviour.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011NDCKtu1QcLwawbdzwmFA1
… checklist

e2e/vector.spec.ts: the 40 scenarios now drive the compact card (a grid of
preview buttons, Download... for several versions, the one-version card's own
downloads) and the viewer (the drawing beside the vector, the facts, one PNG size
box, the drawing and per-layer downloads with their headers, Escape and focus,
paging, the phone layout). Delete on a card says how many versions go.

Version 1.18 in backend/studio/__init__.py, frontend/package.json and its lock
file, and the test fixtures; two schema tests that were still named for schema 7
now say 8.

docs/SPARK_TEST.md section 29: the viewer and the drawing, Delete version, the
three Back sheet orders in the laser software, a 1.17 run opened in 1.18, paste
on the user's own browsers and phone, Upscale by drop and paste, and the
measurement of 6 and 8 pictures that sets the shipped limit.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011NDCKtu1QcLwawbdzwmFA1
…tests from the first mutation round

e2e/vectorversions.spec.ts (11 scenarios): Delete version in the viewer (the
question, the viewer moving on, the others numbered again, Undo restoring the
version at its place), the last version sending the run, the Deleted view's card
of versions with its read-only viewer, Restore and Delete forever (and the files
gone), a run in the bin showing its own versions, another open page following
live, a server refusal, the Back sheet (greyed, sent, remembered, Reuse), the
Height column and the stack view's slider, and a 320 px phone.

Fixed, found by those scenarios: the page's reducer counted only pictures and
tracks in the bin when deciding that the counts had changed, so restoring a
version of a Vector run left the number beside Deleted where it was.

Tests added for survivors of the first mutation round: a drop on the Music or
Vector tab while a dialog is open or before the page is ready; a stacked
version of one layer; the alt text, help line and range ends of the viewer's PNG
box; arrow keys with one and two versions; a file at exactly the size limit; the
tray's announcements; a back sheet when one ink is listed; a version whose files
are already gone; the words of the viewer routes' 404s.

README: the built 1.18 row.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011NDCKtu1QcLwawbdzwmFA1
…in the browser

Survivors of the server, page and browser-level rounds that were real gaps now
have tests: a run stored by 1.17 with no back sheet is traced as the default; a
version whose preview file is already gone still has its drawing removed; the
viewer routes' 404 words; the card's empty preview (shimmer, alert and stop
icons, hidden from a screen reader), the corners of a preview, and "+N" only past
four; a run with one thing in the bin; an unknown back sheet order listed by its
own name; the deleted card's accessible name; placeLabel. Copy SVG code in the
viewer is now a browser scenario (the answer is said inside the viewer, not as a
toast behind it).

Removed stackOrderLabel, which nothing used.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011NDCKtu1QcLwawbdzwmFA1
…s-about-versions tests

DESIGN.md: the status row and the tests line for 1.18, and a new section 36.9:
what was built for sections 35 and 36, the tests (backend 1804, front end 858,
281 in a browser), the 530 mutants of the 1.18 code in three rounds and what they
found (a real bug in the Deleted count, found by the browser scenarios; weak
tests, found by mutation), what was not checked (a real drawing, the laser
software with a stacked file, real clipboards and phones, 8 pictures on the GPU),
the small choices made while building, and the known limits.

Tests: the words about deleting versions (the toast and tooltip for a run with
several, Empty bin's question and toast, the question about one version and its
toasts) were only covered in the browser; they now have unit tests, and a run
with eight source pictures shows all eight on its card.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011NDCKtu1QcLwawbdzwmFA1
@NoSQLKnowHow
NoSQLKnowHow merged commit 59dfcbb into main Nov 7, 2026
NoSQLKnowHow pushed a commit that referenced this pull request Nov 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants