Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
248 commits
Select commit Hold shift + click to select a range
a581368
chore(segmentation): baseline for segment model work
PaulHax Sep 1, 2026
0536d73
feat(segmentation): add segment model types and helpers
PaulHax Sep 1, 2026
208a3a4
feat(segmentation): add segmentation store
PaulHax Sep 1, 2026
93c19e6
refactor(segmentation): move segment identity to segmentation store
PaulHax Sep 1, 2026
f8229d7
refactor(segmentation): inject shared segment registry into annotatio…
PaulHax Sep 1, 2026
8f6f34a
feat(segmentation): active segment target and cross-image intent
PaulHax Sep 1, 2026
17e0880
refactor(segmentation): retarget edit paths to stable segment identity
PaulHax Sep 1, 2026
f155bcd
feat(segmentation): serialize segmentation schema, manifest 7.0.0
PaulHax Sep 1, 2026
157be21
feat(segmentation): migrate legacy segment group state files
PaulHax Sep 1, 2026
d880a7e
refactor(segmentation): remove segment group identity surface
PaulHax Sep 1, 2026
211e898
test(segmentation): cover rasterize target and legacy labelmap migration
PaulHax Sep 1, 2026
211464e
test(segmentation): cover segment identity under edits and across images
PaulHax Sep 1, 2026
8e07e13
fix(segmentation): keep cross-image landing map when reselecting a se…
PaulHax Sep 1, 2026
8feaecd
fix(segmentation): rasterize into a default segment when an image has…
PaulHax Sep 1, 2026
74af43d
fix(segmentation): stop all-segments processes creating a segment
PaulHax Sep 1, 2026
71faf0a
fix(segmentation): declare segmentation manifest refs for the save ba…
PaulHax Sep 1, 2026
e3d1265
fix(segmentation): allow rasterizing a polygon that carries no segment
PaulHax Sep 1, 2026
bee58cf
fix(segmentation): serialize props for segments no tool references yet
PaulHax Sep 1, 2026
6e08bed
fix(segmentation): scope the active segment to the viewed image
PaulHax Sep 1, 2026
b0522f7
refactor(segmentation): drop dead parameters and duplicated helpers
PaulHax Sep 1, 2026
6aaf3b8
perf(segmentation): hoist stroke invariants out of the paint predicate
PaulHax Sep 1, 2026
0346d17
fix(segmentation): stop paint tool activation creating a segment
PaulHax Sep 1, 2026
e482b4b
refactor(segmentation): give processes their resolved target
PaulHax Sep 1, 2026
05a237f
fix(segmentation): route rasterize through the single edit entry point
PaulHax Sep 1, 2026
f757a57
fix(segmentation): scope the paint widget labelmap to its own image
PaulHax Sep 1, 2026
9a845b4
fix(segmentation): unbind segments whose artifact is pruned on save
PaulHax Sep 1, 2026
a4df54a
docs(segmentation): note why the decode color cursor is store-scoped
PaulHax Sep 1, 2026
870ec15
fix(segmentation): record the segment a polygon actually rasterized into
PaulHax Sep 1, 2026
0390c61
fix(segmentation): parse functional CSS colors instead of blackening …
PaulHax Sep 1, 2026
9224e61
fix(segmentation): make migrated segment ids unique and keep the acti…
PaulHax Sep 1, 2026
b43c50b
fix(segmentation): scope the orphan backstop to declared artifacts
PaulHax Sep 1, 2026
927de81
feat(segmentation): put display state on the segment model
PaulHax Sep 1, 2026
d8b3592
feat(segmentation): define the segment voxel accessor contract
PaulHax Sep 1, 2026
d6eb2bc
refactor(segmentation): route labelmap consumers through the voxel ac…
PaulHax Sep 1, 2026
068081b
test(segmentation): audit labelmap access at the accessor seam
PaulHax Sep 1, 2026
b1f0e7e
feat(segmentation): flat image-scoped segment list
PaulHax Sep 1, 2026
1b4c95c
feat(segmentation): store one bounded mask per segment
PaulHax Sep 2, 2026
61aa900
refactor(segmentation): delete the segment group layer
PaulHax Sep 2, 2026
6a1a138
fix(segmentation): keep segment storage in step with its bindings
PaulHax Sep 2, 2026
d10e0d3
fix(segmentation): apply the segmentation's fill opacity to its segments
PaulHax Sep 2, 2026
646ee96
fix(tools): move annotations onto a renamed template's id
PaulHax Sep 2, 2026
961ad88
fix(polygon): rasterize into the template the polygon names
PaulHax Sep 2, 2026
f122c1e
fix(state-file): migrate an unused legacy label into a template
PaulHax Sep 2, 2026
718bb68
test(e2e): expect the current manifest version on re-save
PaulHax Sep 2, 2026
841989b
fix(fill-holes): reject a slice the selected segment does not reach
PaulHax Sep 2, 2026
1d41603
fix(segmentation): stop named-color lookup reaching Object.prototype
PaulHax Sep 2, 2026
a027d2c
style: trim development storytelling from comments
PaulHax Sep 2, 2026
523257e
perf(processes): stop snapshotting the process output twice
PaulHax Sep 2, 2026
7d5b40f
fix(segment-registry): declare a template instead of minting a segment
PaulHax Sep 2, 2026
b34ab68
fix(state-file): keep the name a manifest gave a restored labelmap
PaulHax Sep 2, 2026
1689a7e
test(e2e): wait for a segment chip's name, not just the chip
PaulHax Sep 2, 2026
e526387
fix(segmentation): let a segment fill reach its own opacity
PaulHax Sep 2, 2026
67463f7
fix(cine): refuse to start a segmentation on a clip
PaulHax Sep 2, 2026
9e68d30
feat(segmentation): add display controls for the viewed image
PaulHax Sep 2, 2026
bfaaf11
feat(segmentation): name imported segments after the file
PaulHax Sep 2, 2026
242b36a
refactor(tools): drop unreachable members from the tool registry
PaulHax Sep 2, 2026
c56507d
refactor(segmentation): share the mask buffer, offset, and descriptor…
PaulHax Sep 2, 2026
a13af9a
refactor(segmentation): drop the labelmap handles and descriptors not…
PaulHax Sep 2, 2026
59e2188
fix(processes): refuse a result that is the storage buffer it was given
PaulHax Sep 2, 2026
1054059
feat(segmentation): let a locked segment keep the voxels written over it
PaulHax Sep 2, 2026
d545546
refactor(processes): run every process on one segment's own mask
PaulHax Sep 2, 2026
d88fd8c
feat(segmentation): save overlapping segments as separate files
PaulHax Sep 3, 2026
a506839
perf(segmentation): sweep the shared box a row at a time
PaulHax Sep 3, 2026
de9ae29
fix(processes): name the lock when every segment is locked
PaulHax Sep 3, 2026
87b576b
fix(polygon): refuse to rasterize into a locked segment
PaulHax Sep 3, 2026
95dac81
feat(processing): say that a staged segmentation is flattened
PaulHax Sep 3, 2026
aa6491f
fix(processes): stop a process at the voxels another segment holds
PaulHax Sep 3, 2026
1319cff
docs(processes): say what Fill Holes actually fills
PaulHax Sep 3, 2026
895f940
refactor(segmentation): reuse the shared extent and offset helpers
PaulHax Sep 3, 2026
b4e8bf7
perf(processes): clip the neighbouring masks to the box a run walks
PaulHax Sep 3, 2026
79a0c72
perf(processes): let a process say a segment has nothing to do
PaulHax Sep 3, 2026
2f9193f
perf(processing): refresh the flatten notice off a signal
PaulHax Sep 3, 2026
7342002
refactor(segmentation): make a write path name the gesture it is
PaulHax Sep 3, 2026
2b93605
fix(processes): let an all-segments preview outlive the selection
PaulHax Sep 3, 2026
2812c29
refactor(segmentation): sweep the shared box with plain j and k loops
PaulHax Sep 3, 2026
21365c4
refactor(processes): collapse a nested guard and an unreachable fallback
PaulHax Sep 3, 2026
923c592
refactor(tools): keep one name for the tool registry type
PaulHax Sep 3, 2026
f0247f2
refactor(io): read imported labelmap scalars through the shared accessor
PaulHax Sep 3, 2026
d6bf1ad
refactor(tools): declare a label through the one template-pool write
PaulHax Sep 3, 2026
1064e85
refactor(io): name the save archive in one place
PaulHax Sep 3, 2026
9c64204
test(segmentation): collapse the duplicated composite and seeding hel…
PaulHax Sep 3, 2026
e1639d2
refactor(segmentation): bind the display sliders to the viewed segmen…
PaulHax Sep 3, 2026
9ebf74b
feat(segmentation): say on the lock what it does to painting over a s…
PaulHax Sep 3, 2026
3e6fac8
test(segmentation): cover lock, overlap and layered save end to end
PaulHax Sep 3, 2026
2ab9527
test(segmentation): update scroll coverage for flat segment list
PaulHax Sep 4, 2026
1c0544c
fix(segmentation): harden segment model workflows
PaulHax Sep 4, 2026
a34522e
fix(segmentation): resolve label collisions on restore and pad stroke…
PaulHax Sep 5, 2026
fa82191
fix(processes): clamp smoothing padding to the parent image faces
PaulHax Sep 5, 2026
fa1c035
fix(tools): keep a scene's own templates when a session restores
PaulHax Sep 5, 2026
fc37c2d
fix(io): preserve the legacy fill opacity through the 7.0.0 migration
PaulHax Sep 5, 2026
6fe51eb
fix(processing): refresh the flatten notice after a voxel edit
PaulHax Sep 5, 2026
4ebf0b6
feat(tools): mint a segment when a delineation annotation is started
PaulHax Sep 6, 2026
12ef21f
feat(segmentation): add one registry for segment types
PaulHax Sep 6, 2026
45a22d8
refactor(segmentation): make a labelmap record reference a type
PaulHax Sep 6, 2026
0278668
refactor(tools): point shapes and pickers at the shared registry
PaulHax Sep 6, 2026
19af661
feat(config): configure segment types instead of per-tool labels
PaulHax Sep 6, 2026
71920a5
feat(io): carry segment types on the 7.0.0 manifest
PaulHax Sep 6, 2026
0da92f6
test(segmentation): move the suites onto global segment types
PaulHax Sep 6, 2026
0d48279
refactor(segmentation): name the registry entry a segment and its per…
PaulHax Sep 7, 2026
2d6ee69
feat(segmentation): stack the annotations sidebar and list segments a…
PaulHax Sep 7, 2026
2835b6c
feat(segmentation): open the segment editor from its color dot
PaulHax Sep 7, 2026
624eefa
feat(segmentation): fold the measurement lists into the segment list
PaulHax Sep 7, 2026
a5ee0b0
fix(segmentation): read pre-7.0 label configs into the segment list
PaulHax Sep 7, 2026
0cd910b
chore(hooks): add local quality checks
PaulHax Sep 7, 2026
fe09d47
chore(segmentation): drop the orphaned ColorDot component
PaulHax Sep 7, 2026
ff9b86c
test(state-file): share the scene-to-state-files serialize step
PaulHax Sep 7, 2026
d776c9a
test(paint): share the active-segment seat between the paint specs
PaulHax Sep 7, 2026
9d477ac
test(state-file): fold the two legacy-restore tests onto one setup
PaulHax Sep 7, 2026
e989de2
test(segmentation): share the extent contract and the e2e square
PaulHax Sep 7, 2026
d4774e5
test(segmentation): fold repeated setup in six more specs
PaulHax Sep 7, 2026
1b84c83
test(store): share the plain 4x4x4 parent image
PaulHax Sep 7, 2026
0a14c68
test: fold the last repeated setup in three specs
PaulHax Sep 7, 2026
23092df
test(state-file): share the segmentation snapshot
PaulHax Sep 7, 2026
2ab4af1
test: share the restore path and the legacy view config
PaulHax Sep 7, 2026
b9727fe
test(paint): pin the gaussian smooth output byte for byte
PaulHax Sep 7, 2026
7d6de7d
refactor(segmentation): reuse the shared helpers and drop repeat sweeps
PaulHax Sep 7, 2026
b07d8e3
refactor(tools): register segment references from the shared composable
PaulHax Sep 7, 2026
a64cc51
refactor(segmentation): name the wire order walk and the in-flight guard
PaulHax Sep 7, 2026
38b87d2
refactor(types): flatten the appearance resolver and the extent sweep
PaulHax Sep 7, 2026
777da27
refactor(polygon): group the slice plane and stop carrying a stale ex…
PaulHax Sep 7, 2026
8b2bdfa
refactor(paint): split the stroke value and the line walk out of pain…
PaulHax Sep 7, 2026
8fb84eb
refactor(paint): clip the sub-volume loops instead of testing per voxel
PaulHax Sep 7, 2026
fd9c5f8
refactor(segmentation): move the state-file half into its own module
PaulHax Sep 7, 2026
8008a22
refactor(segmentation): move voxel access into its own module
PaulHax Sep 7, 2026
4a089a7
refactor(tools): share the appearance lookup and the placement start
PaulHax Sep 7, 2026
c7222ee
perf(processing): stop rebuilding every mask extent to detect a change
PaulHax Sep 7, 2026
eac82dc
fix(config): say when a config color cannot be parsed
PaulHax Sep 7, 2026
77c0d96
refactor(ui): pass the item through the list slots instead of by index
PaulHax Sep 7, 2026
914d338
refactor(polygon): fold the process cancel into rasterizePolygon
PaulHax Sep 7, 2026
8fed473
perf(segments): group the shapes once instead of filtering per segment
PaulHax Sep 7, 2026
9c0c926
refactor(segments): name the offset constant and fold the icon pair i…
PaulHax Sep 7, 2026
6fe26fb
chore(lint): close the feature boundary gaps in the pure-layer lists
PaulHax Sep 7, 2026
2ee4ed7
perf(segmentation): keep an unchanged artifact's projection identity
PaulHax Sep 7, 2026
caa7a91
perf(segmentation): hold the projection identity when no artifact cha…
PaulHax Sep 7, 2026
5c09b16
fix(shortcuts): cycle the segment selection for every tool
PaulHax Sep 7, 2026
5632cfb
refactor(segmentation): let the legacy split hold its labelmap locally
PaulHax Sep 8, 2026
931bb99
refactor(segmentation): drop the stored label value from a mask
PaulHax Sep 8, 2026
accf1b3
refactor(segmentation): hold a mask's voxels on its own binding
PaulHax Sep 8, 2026
a257fdd
refactor(state-file): carry legacy group display only where it has no…
PaulHax Sep 8, 2026
e83070d
refactor(state-file): save a mask's labelmap on its own binding
PaulHax Sep 8, 2026
45e43a3
refactor(segmentation): name the renderer and probe locals after masks
PaulHax Sep 8, 2026
e598884
test(e2e): read a saved labelmap name off the mask that owns it
PaulHax Sep 8, 2026
70697c2
fix(segments): harden processing, persistence, and annotation workflows
PaulHax Sep 10, 2026
73d1a69
fix: honor segment deletion during scene restore
PaulHax Sep 10, 2026
95508d9
fix: settle restore when artifact sources stop loading
PaulHax Sep 10, 2026
e60d5cb
fix: cancel pending processing when targets become locked
PaulHax Sep 10, 2026
78c71c6
fix(image-stats): terminate completed histogram workers
PaulHax Sep 10, 2026
c40ec9c
fix(segments): expose accessible list actions and state
PaulHax Sep 10, 2026
e57bc3e
fix(segments): allow Escape to cancel the editor
PaulHax Sep 10, 2026
3753a1e
feat(annotations): restore flat measurement list
PaulHax Sep 11, 2026
89921b7
feat(annotations): refine sidebar controls
PaulHax Sep 11, 2026
561ee6c
feat(annotations): make segments collapsible
PaulHax Sep 11, 2026
b5952d8
fix: omit empty segment voxels from scalar probe
PaulHax Sep 11, 2026
9530e59
fix: reveal occupied segment slices with a highlight pulse
PaulHax Sep 11, 2026
b2ee6fc
fix: pad segment outlines with reusable render buffers
PaulHax Sep 11, 2026
1bd740e
feat(annotations): constrain segment list height
PaulHax Sep 14, 2026
01e5a1a
feat(data): show segmentation conversion progress
PaulHax Sep 14, 2026
af86a87
perf(paint): improve responsiveness and reuse imported segments
PaulHax Sep 14, 2026
ee3c9ca
perf(segmentation): speed up equivalent label grid imports
PaulHax Sep 14, 2026
c98d6d2
perf(segmentation): snapshot bounds before copying mask rows
PaulHax Sep 14, 2026
574f643
perf(segmentation): lazily mount segment tooltips
PaulHax Sep 14, 2026
5d54cc5
feat(segmentation): add segment shortcuts and responsive list controls
PaulHax Sep 15, 2026
b3f8a0c
feat(segmentation): add eyedropper mode and align selection with disp…
PaulHax Sep 15, 2026
61502d6
refactor(segmentation): isolate process inputs and coordinate previews
PaulHax Sep 15, 2026
0c07fc8
refactor(segmentation): narrow storage and interchange boundaries
PaulHax Sep 15, 2026
49cf24c
refactor(segmentation): colocate model editing and presentation
PaulHax Sep 15, 2026
1ae7950
refactor(segmentation): separate geometry and enforce dependency dire…
PaulHax Sep 15, 2026
13ae982
refactor(segmentation): simplify labelmap import and restore
PaulHax Sep 15, 2026
39bd42d
style(segments): tighten panel spacing and align list rows
PaulHax Sep 15, 2026
650eb6a
fix(paint): keep brush preview moving during size adjustment
PaulHax Sep 15, 2026
b2e6dc9
fix(segments): clarify overlap save and cover duplicate imports
PaulHax Sep 15, 2026
ca044f4
refactor: bind processing inputs directly to segmentations
PaulHax Sep 15, 2026
aab6d84
refactor: adopt segmentation naming and migrate configuration
PaulHax Sep 15, 2026
0e6f912
refactor: rename segmentation result instruction
PaulHax Sep 15, 2026
279ad1d
feat: choose labelmap bit depth from label count
PaulHax Sep 15, 2026
1164598
feat: pack processing labelmaps according to input multiplicity
PaulHax Sep 15, 2026
1c6bec5
chore(deps): sync package-lock with package.json
PaulHax Sep 19, 2026
159a7c4
test: fold new test clones and cut complexity regressions
PaulHax Sep 19, 2026
04a1e17
ci(checks): run the quality ratchets against the base branch
PaulHax Sep 19, 2026
3d19268
fix(tools): skip restored annotations whose image did not load
PaulHax Sep 19, 2026
81ef1c5
fix(segmentation): refuse a labelmap split for a parent that was removed
PaulHax Sep 19, 2026
bc9119c
fix(segmentation): check the lock before minting a target mask
PaulHax Sep 19, 2026
ffbf738
fix(resample): return a same-grid labelmap without reslicing
PaulHax Sep 19, 2026
3da2c24
docs(segmentation): stop promising render depth from registry order
PaulHax Sep 19, 2026
5aa4e0c
refactor(rendering): drop the inert per-segment depth offset
PaulHax Sep 19, 2026
0bf4ee6
perf(segmentation): index masks and names so import scales with label…
PaulHax Sep 19, 2026
9b468c9
perf(segmentation): plan overlap layers with an occupancy buffer
PaulHax Sep 19, 2026
7574a74
fix(processing): leave an empty segmentation unbound as a labelmap input
PaulHax Sep 19, 2026
d1eeb46
fix(processing): start single-file packing from a segment the image h…
PaulHax Sep 19, 2026
1680782
fix(processing): keep a segment's colour when a result colour is unsu…
PaulHax Sep 19, 2026
08d0ac7
docs(contract): describe labelmap packing and the accepted colour syntax
PaulHax Sep 19, 2026
da45485
perf(processing): stop re-running the overlap sweep on every segment …
PaulHax Sep 19, 2026
d1a7a54
fix(processing): say when a result intent is not recognized
PaulHax Sep 19, 2026
003d937
fix(processing): keep an unconfirmed preview when a task stages no la…
PaulHax Sep 19, 2026
28e0211
fix(segmentation): surface a crashed process worker instead of hanging
PaulHax Sep 19, 2026
fc089cd
fix(segmentation): terminate a cancelled process worker
PaulHax Sep 19, 2026
b960ef9
perf(segmentation): drop a full-volume copy and a redundant scan from…
PaulHax Sep 19, 2026
484fc83
perf(segmentation): scope a polygon fill to the polygon
PaulHax Sep 19, 2026
6c2508d
perf(segmentation): normalize labelmap scalars with an indexed loop
PaulHax Sep 19, 2026
f28b32b
perf(segmentation): cap concurrent mask reads and writes on save and …
PaulHax Sep 19, 2026
25aa14a
fix(segmentation): report masks dropped during restore
PaulHax Sep 19, 2026
0bfe51a
fix(segmentation): join a conversion already running for an image
PaulHax Sep 19, 2026
8bf6d1c
fix(segmentation): keep name and id handling consistent in the registry
PaulHax Sep 19, 2026
cfcd7f0
perf(segmentation): release a mask name instead of rescanning the scene
PaulHax Sep 19, 2026
b9d989a
fix(segmentation): say what deleting a segment removed
PaulHax Sep 19, 2026
3599155
perf(segmentation): keep segment row identity across unrelated annota…
PaulHax Sep 19, 2026
4a319ab
fix(segmentation): make the preview toggle idempotent
PaulHax Sep 19, 2026
767cebf
refactor(segmentation): reuse boundedMask and drop unused voxel acces…
PaulHax Sep 19, 2026
800d071
fix(crop): skip restored crops whose image did not load
PaulHax Sep 19, 2026
982b861
fix(layers): skip restored layers whose image did not load
PaulHax Sep 19, 2026
dd94d1c
chore(checks): drop the unused checks eslint config
PaulHax Sep 19, 2026
af2dd64
fix(checks): make the convention check see nested modules and staged …
PaulHax Sep 19, 2026
f3d08cf
fix(checks): allow tracked binaries outside the e2e baselines
PaulHax Sep 19, 2026
c6e4826
chore(lint): close the composables and nested-path gaps in the pure l…
PaulHax Sep 19, 2026
8063310
docs(contract): describe the intent vocabulary version honestly
PaulHax Sep 19, 2026
039356c
fix(processing): keep segments a result declares but leaves empty
PaulHax Sep 20, 2026
d8f4068
fix(segmentation): declare an empty segment once, not once per component
PaulHax Sep 20, 2026
95358c9
test(segmentation): compare the declared-empty rows whole
PaulHax Sep 20, 2026
e6948fa
refactor(segmentation): flatten the gaussian smooth worker passes
PaulHax Sep 20, 2026
e141dc8
fix(checks): skip a feature directory that does not exist yet
PaulHax Sep 20, 2026
64334bc
style(checks): format the check scripts with prettier
PaulHax Sep 20, 2026
a827d3f
fix(segmentation): key a running conversion on the child and its parent
PaulHax Sep 20, 2026
39e9b7f
fix(segmentation): seat a restored segment whose id spells a prototyp…
PaulHax Sep 20, 2026
caf8c9e
fix(segmentation): report masks whose parent image was never mapped
PaulHax Sep 20, 2026
d42d739
fix(segmentation): declare a header segment once across components
PaulHax Sep 20, 2026
aef3154
perf(segmentation): index the archive members once per restore
PaulHax Sep 20, 2026
72a39df
test(segmentation): pin that a session save resolves an unconfirmed p…
PaulHax Sep 20, 2026
968922a
test(segmentation): pin the two reasons an export splits into files
PaulHax Sep 20, 2026
bb885d4
test(segmentation): assert the mask io cap the code uses
PaulHax Sep 20, 2026
fd51452
test(segmentation): import a 16-bit labelmap and split it into byte m…
PaulHax Sep 20, 2026
2835eaa
fix(segmentation): name restored archive labelmap segments after the …
PaulHax Sep 20, 2026
297f2fc
test(segmentation): read the adopted id off the map without a cast
PaulHax Sep 20, 2026
0a100c7
perf(segmentation): stop allocating a closure per claimed voxel
PaulHax Sep 20, 2026
361318b
perf(paint): keep the per-voxel stroke path allocation-free
PaulHax Sep 20, 2026
c812890
fix(segmentation): roll back only the masks a failed process wrote
PaulHax Sep 20, 2026
f61186e
perf(segmentation): move a process input to its worker instead of clo…
PaulHax Sep 20, 2026
179726d
fix(paint): walk a stroke on the parent grid, not in the mask's frame
PaulHax Sep 20, 2026
d3b6eb3
fix(segmentation): let gaussian smooth say an empty mask has nothing …
PaulHax Sep 20, 2026
7613f7d
fix(segmentation): count only masks that hold voxels
PaulHax Sep 20, 2026
7057c64
fix(segmentation): keep Enter in the save dialog to one save
PaulHax Sep 20, 2026
a909a11
test(segmentation): pin the segment coincident offset
PaulHax Sep 20, 2026
432af91
fix(processing): say when a supported intent had its payload rejected
PaulHax Sep 20, 2026
43eaffc
docs(segmentation): narrow the staging sentence to tasks with a labelmap
PaulHax Sep 20, 2026
9c9ca2c
test(processing): restore the staging spies so case order stops matte…
PaulHax Sep 20, 2026
73411dc
fix(lint): deny the directory spellings of a feature entry point
PaulHax Sep 20, 2026
11dc5db
fix(processing): expire the session when the job history load gets a 401
PaulHax Sep 20, 2026
b33b9ca
docs(state-files): show the current manifest beside the legacy one
PaulHax Sep 20, 2026
571ca32
fix(processing): mint the provenance a source-less segmentation resul…
PaulHax Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ jobs:
name: Code check and tests
steps:
- uses: actions/checkout@v4
with:
# The ratchets diff against the merge base with the target branch, so
# the checkout needs history rather than a single commit.
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v4
with:
Expand All @@ -19,6 +23,16 @@ jobs:
run: npx prettier --list-different "src/**/*.[jt]s" "tests/**/*.[jt]s" "src/**/*.vue"
- name: Lint
run: npm run lint
- name: Quality ratchets
# On a pull request the checkout is the merge commit, so the merge base
# with the target branch is what the change is measured against. The
# merge queue has no pull request to read a base from.
env:
BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }}
run: |
git fetch --no-tags origin \
"+refs/heads/$BASE_REF:refs/remotes/origin/$BASE_REF"
node scripts/checks/check.mjs --base "origin/$BASE_REF"
- name: Tests
run: npm test

Expand Down
25 changes: 22 additions & 3 deletions backend-contract/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,11 @@ validate `annotations-file.schema.json`, then enforce
`validateAnnotationsFileSemantics` — every nonempty `labelName` must be declared
in its own tool-kind label namespace, which JSON Schema cannot express either.
Backend conformance tests must also assert that every payload under
`fixtures/negative/` is rejected by the combined validation path.
`fixtures/negative/` is rejected by the combined validation path. The one
exception is `negative/wrong-length-color.json`, which only the strict
known-intent union rejects: `result-intent.schema.json` is deliberately open,
so it accepts the row and demotes it to an ordinary result carrying no state
action.

## The neutral REST surface (OpenAPI)

Expand Down Expand Up @@ -106,8 +110,23 @@ Two versions on separate clocks:
`info.version`, kept in lockstep by `processing/__tests__/openapi.spec.ts`.
Versions this package as a published thing.
- **Shape versions**: `INTENT_VOCABULARY_VERSION` (`processing/wire.ts`) and
the task-spec `specVersion`. These version the wire vocabulary for additive
compatibility negotiation.
the task-spec `specVersion`. These name the shape of the wire vocabulary in
the generated OpenAPI description and in release notes. Neither travels on
the wire, so neither is negotiated: additive compatibility rests on both
sides failing open on a value they do not know.

### Result instruction rollout

Contract artifact 0.3.0 uses intent vocabulary 3 and names segmentation import
`import-segmentation`. Deploy the updated producer and VolView client together.
An older client treats the unfamiliar instruction as an ordinary result and
will not apply its segmentation automatically. Update Girder's pinned VolView
package when releasing the paired change.

This vocabulary change does not change task-spec versions or saved-session
schemas. Girder projects stored job outputs into current instructions when
results are requested; stored output references and mask provenance keep their
identities.

## Regenerating

Expand Down
2 changes: 1 addition & 1 deletion backend-contract/fixtures/negative/wrong-length-color.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "6600000000000000000000e1",
"intent": "add-segment-group",
"intent": "import-segmentation",
"url": "/api/v1/file/6600000000000000000000e1/proxiable/otsu.nii.gz",
"name": "otsu.nii.gz",
"segments": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "6600000000000000000000e2",
"intent": "add-segment-group",
"intent": "import-segmentation",
"url": "/api/v1/file/6600000000000000000000e2/proxiable/threshold.seg.nrrd",
"name": "threshold.seg.nrrd",
"source": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "6600000000000000000000e1",
"intent": "add-segment-group",
"intent": "import-segmentation",
"url": "/api/v1/file/6600000000000000000000e1/proxiable/otsu.nii.gz",
"name": "otsu.nii.gz",
"segments": [
Expand Down
3 changes: 3 additions & 0 deletions backend-contract/generated/annotations-file.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand All @@ -46,6 +47,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand All @@ -68,6 +70,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/generated/job-results.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@
"properties": {
"intent": {
"type": "string",
"const": "add-segment-group"
"const": "import-segmentation"
},
"id": {
"type": "string",
Expand Down
11 changes: 7 additions & 4 deletions backend-contract/generated/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "VolView neutral backend contract",
"version": "0.2.0",
"description": "DRAFT 0.x — shapes may change until a second backend passes the conformance kit (the pinned 1.0 criterion). The neutral REST surface the VolView client calls to run processing tasks against a backend. A conforming server-side BACKEND implements these endpoints and the referenced wire schemas — no VolView client change is needed to bring a new backend online. Everything here is neutral: no backend routes, ids, status enums, or URL shapes leak. The artifact version is the draft artifact version, distinct from the shape versions: the result-intent vocabulary is at version 2 (INTENT_VOCABULARY_VERSION); the task-spec shape at version 1 (specVersion)."
"version": "0.3.0",
"description": "DRAFT 0.x — shapes may change until a second backend passes the conformance kit (the pinned 1.0 criterion). The neutral REST surface the VolView client calls to run processing tasks against a backend. A conforming server-side BACKEND implements these endpoints and the referenced wire schemas — no VolView client change is needed to bring a new backend online. Everything here is neutral: no backend routes, ids, status enums, or URL shapes leak. The artifact version is the draft artifact version, distinct from the shape versions: the result-intent vocabulary is at version 3 (INTENT_VOCABULARY_VERSION); the task-spec shape at version 1 (specVersion)."
},
"servers": [
{
Expand Down Expand Up @@ -717,7 +717,7 @@
}
},
"multiple": {
"description": "When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. Only labelmap source refs bind plurally today — every group whose parent is the active dataset, in store order; on other type tags the flag has no effect yet.",
"description": "When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. For a labelmap input the client stages the active image's segmentation as overlap-free parts: when true, every mask, spread across as many files as it takes for no two masks to share a voxel; when absent or false, only the non-conflicting subset that fits one file, with the remaining masks omitted whole and reported to the user. No mask is ever clipped to fit.",
"type": "boolean"
}
},
Expand Down Expand Up @@ -1203,7 +1203,7 @@
"properties": {
"intent": {
"type": "string",
"const": "add-segment-group"
"const": "import-segmentation"
},
"id": {
"type": "string",
Expand Down Expand Up @@ -1701,6 +1701,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand All @@ -1723,6 +1724,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand All @@ -1745,6 +1747,7 @@
"type": "object",
"properties": {
"color": {
"description": "A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.",
"type": "string"
},
"strokeWidth": {
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/generated/result-intent.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@
"properties": {
"intent": {
"type": "string",
"const": "add-segment-group"
"const": "import-segmentation"
},
"id": {
"type": "string",
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/generated/task-spec.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@
}
},
"multiple": {
"description": "When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. Only labelmap source refs bind plurally today — every group whose parent is the active dataset, in store order; on other type tags the flag has no effect yet.",
"description": "When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. For a labelmap input the client stages the active image's segmentation as overlap-free parts: when true, every mask, spread across as many files as it takes for no two masks to share a voxel; when absent or false, only the non-conflicting subset that fits one file, with the remaining masks omitted whole and reported to the user. No mask is ever clipped to fit.",
"type": "boolean"
}
},
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@volview/backend-contract",
"version": "0.2.0",
"version": "0.3.0",
"private": true
}
26 changes: 13 additions & 13 deletions backend-contract/processing/__tests__/wire.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,12 @@ describe('neutral job status fixtures', () => {
// ---------------------------------------------------------------------------

describe('result intent fixtures', () => {
it('exports vocabulary version 2 and the exactly-four state intents', () => {
expect(INTENT_VOCABULARY_VERSION).toBe(2);
it('exports vocabulary version 3 and the exactly-four state intents', () => {
expect(INTENT_VOCABULARY_VERSION).toBe(3);
expect([...RESULT_INTENTS]).toEqual([
'add-base-image',
'add-layer',
'add-segment-group',
'import-segmentation',
'add-annotations',
]);
expect(wire).not.toHaveProperty('intent.download');
Expand All @@ -208,8 +208,8 @@ describe('result intent fixtures', () => {
it.each([
'intent.add-base-image',
'intent.add-layer',
'intent.add-segment-group.with-segments',
'intent.add-segment-group.embedded',
'intent.import-segmentation.with-segments',
'intent.import-segmentation.embedded',
'intent.add-annotations',
'intent.unknown',
])('validates %s', (name) => {
Expand Down Expand Up @@ -253,11 +253,11 @@ describe('result intent fixtures', () => {
).toBe(false);
});

it('parses add-segment-group WITH segments and a source provenance tag', () => {
it('parses import-segmentation WITH segments and a source provenance tag', () => {
const parsed = resultIntentSchema.parse(
wire['intent.add-segment-group.with-segments']
wire['intent.import-segmentation.with-segments']
) as Record<string, unknown>;
expect(parsed.intent).toBe('add-segment-group');
expect(parsed.intent).toBe('import-segmentation');
expect(Array.isArray(parsed.segments)).toBe(true);
expect(parsed.source).toEqual({
providerId: 'analysis-provider',
Expand All @@ -266,18 +266,18 @@ describe('result intent fixtures', () => {
});
});

it('parses add-segment-group WITHOUT segments (embedded metadata) but with source', () => {
it('parses import-segmentation WITHOUT segments (embedded metadata) but with source', () => {
const parsed = resultIntentSchema.parse(
wire['intent.add-segment-group.embedded']
wire['intent.import-segmentation.embedded']
) as Record<string, unknown>;
expect(parsed.intent).toBe('add-segment-group');
expect(parsed.intent).toBe('import-segmentation');
expect(parsed.segments).toBeUndefined();
expect(parsed.source).toMatchObject({ outputId: 'outputLabelmap' });
});

it('rejects a segment-group source without provider identity', () => {
const value = structuredClone(
wire['intent.add-segment-group.with-segments']
wire['intent.import-segmentation.with-segments']
) as { source: { providerId?: string } };
delete value.source.providerId;
expect(knownResultIntentSchema.safeParse(value).success).toBe(false);
Expand Down Expand Up @@ -373,7 +373,7 @@ describe('result intent fixtures', () => {
expect(knownResultIntentSchema.safeParse(short).success).toBe(false);
expect(resultIntentSchema.safeParse(short).success).toBe(true);

const good = wire['intent.add-segment-group.with-segments'] as {
const good = wire['intent.import-segmentation.with-segments'] as {
segments: { color: number[] }[];
};
const long = structuredClone(good);
Expand Down
7 changes: 6 additions & 1 deletion backend-contract/processing/annotations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@ export type WirePolygon = z.infer<typeof wirePolygonSchema>;

// A label's style. Every field is optional: a label may exist purely as a name.
export const annotationLabelSchema = z.strictObject({
color: z.string().optional(),
color: z
.string()
.optional()
.describe(
'A hex colour such as `#d60000` or a CSS colour keyword such as `lime`. Any other syntax, including functional forms such as `rgb()` and `hsl()`, is ignored: the label keeps the colour the client already holds for it, and the client tells the user the value was rejected.'
),
strokeWidth: z.number().optional(),
fillColor: z.string().optional(),
});
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/processing/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ export const buildOpenApiDocument = (): Record<string, unknown> => ({
// VERSION / specVersion) below. It is deliberately literal, not derived from
// the shape-version constants — the artifact and the shapes version on
// separate clocks.
version: '0.2.0',
version: '0.3.0',
description:
'DRAFT 0.x — shapes may change until a second backend passes the ' +
'conformance kit (the pinned 1.0 criterion). ' +
Expand Down
2 changes: 1 addition & 1 deletion backend-contract/processing/task-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ const sourceRefParam = z.object({
.boolean()
.optional()
.describe(
'When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. Only labelmap source refs bind plurally today — every group whose parent is the active dataset, in store order; on other type tags the flag has no effect yet.'
"When true, the parameter takes more than one value: the client sends one staged file per value, listed in `uris` in selection order. When absent or false, it takes a single value. For a labelmap input the client stages the active image's segmentation as overlap-free parts: when true, every mask, spread across as many files as it takes for no two masks to share a voxel; when absent or false, only the non-conflicting subset that fits one file, with the remaining masks omitted whole and reported to the user. No mask is ever clipped to fit."
),
});

Expand Down
18 changes: 11 additions & 7 deletions backend-contract/processing/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@ import {
} from './task-spec';
import { pathSegmentIdSchema } from './ids';

// Bump when the intent vocabulary's shape changes so producers and the applier
// can negotiate compatibility. Adding an intent is a compatible bump: an older
// client demotes the unknown intent through the fail-open branch above.
export const INTENT_VOCABULARY_VERSION = 2;
// A client-side marker for the shape of the intent vocabulary below, bumped
// when that shape changes. It never travels on the wire: no request header,
// response field, or schema property carries it, so the two sides never see
// each other's value and cannot negotiate on it. It names the vocabulary in
// the generated OpenAPI description and in this package's release notes.
// Adding an intent stays compatible without it: an older client demotes the
// unknown intent through the fail-open branch above.
export const INTENT_VOCABULARY_VERSION = 3;

// ---------------------------------------------------------------------------
// Input value: what the client sends at submit
Expand Down Expand Up @@ -158,7 +162,7 @@ export type NeutralJobStatus = z.infer<typeof neutralJobStatusSchema>;
export const RESULT_INTENTS = [
'add-base-image',
'add-layer',
'add-segment-group',
'import-segmentation',
'add-annotations',
] as const;
export type ResultIntentName = (typeof RESULT_INTENTS)[number];
Expand Down Expand Up @@ -212,13 +216,13 @@ const addLayer = z
.object({ intent: z.literal('add-layer'), ...resultListItemSchema.shape })
.passthrough();

// `add-segment-group` carries OPTIONAL `segments` (the bare-labelmap +
// `import-segmentation` carries OPTIONAL `segments` (the bare-labelmap +
// labels-sidecar case; a `seg.nrrd` with embedded metadata carries none — the
// client uses `segments` when present, else the file's own metadata) and an
// optional `source` provenance tag (the idempotency key).
const addSegmentGroup = z
.object({
intent: z.literal('add-segment-group'),
intent: z.literal('import-segmentation'),
...resultListItemSchema.shape,
segments: z.array(segmentDescriptorSchema).optional(),
source: resultSourceSchema.optional(),
Expand Down
Loading