Components · Content

Spatial finding

The finding a spatial model output makes, complete without the image: what, where in the units the reader navigates in, how big, from what. The marks are pixels; this is the claim.

Stable · v1.0 shadcn/ui WCAG 2.2 AA

Overview

Attribution overlays has one test for a spatial finding: cover the image, and the finding is still complete. That page also has the sharpest rule in the imaging half of this system — an overlay shows where the model’s response concentrated, and calling it an explanation makes it read as evidence for a finding it is not evidence for. This system ships no image viewer, on purpose; what it can ship is the words beside the image, and the words are where every rule on that page that is not about canvas rendering actually lives.

The rule

The control that shows the marks cannot be labelled anything that sounds like a reason. Its label is a constant — Show where the response concentrated — and the source contains none of the words explain, reasoning, why or because, which an assertion checks.

An address, in the reader’s units

address is required and is series, slice and the size of the series. A coordinate nobody can type into their own viewer is not an address, and it is not expressible here.

Off by default, one action to remove

Marks start hidden. The reader shows them with a switch, and hides them with the same switch. There is no prop to start them shown.

Navigating is not assessing

The only callback is onNavigate, called with the address. There is no onSeen, onAssessed or onReviewed, so the component cannot be wired to record an evaluation nobody made.

Anatomy

Spatial finding · marks available, not yet shown
Finding 1
Region marked, left upper quadrant
series 4, slice 47 of 300 · 14 mm across the mark
ForeCall v2.4 · detection only, not a diagnosis
ElementRule
What The finding in words — what was marked and where in the anatomy. Quotable, dictatable, readable under magnification.
Address Series and slice, with the size of the series, so “slice 47” is “slice 47 of 300”. A button when the caller can navigate there; text otherwise.
Extent “14 mm across the mark” — the size of the mark, which is not the size of anything in the patient. See Derived measurement before a contour becomes a number.
Provenance Detector and version, with a fixed suffix: detection only, not a diagnosis.
Marks state One of four: available (a switch, off), none (scoped words), partial (what was and was not analysed), outside scope (a reason). Each renders differently, because they are different facts.

Four states, none of them “normal”

marks.kindRenders as
available A switch, off: “Show where the response concentrated (3 marks)”. Showing and hiding call onShownChange, so a dismissal can be recorded.
none “No regions marked for pulmonary nodules. This says nothing about anything else.” Scoped to scope, which is required. The source contains neither normal nor no abnormality, which an assertion checks.
partial “Analysed: 3 of 4 series. Not analysed: the non-contrast series. The unanalysed part has not been assessed.”
outside-scope “Not analysed — outside the validated scope: paediatric study.” Withhold rather than extrapolate, per Confidence disclosure.

Do's and don'ts

Do

Show where the response concentrated (1 mark)

The control names what the overlay is. A reader who turns it on knows they are looking at where the model attended, not at why it concluded.

Don't

Explain this finding

The same pixels, now offered as a reason. This label is not expressible: the control’s text is a constant, and the source is asserted to contain no word of explanation.

Do

series 4, slice 47 of 300

The reader can type this into their own viewer. The denominator says how far into the study it is.

Don't

ROI #a91f · z=0.41

An internal identifier and a normalised coordinate. Nobody can navigate there, so the finding has no address, so the reader cannot find it. Not expressible: address is series, slice and count.

Do

No regions marked for pulmonary nodules. This says nothing about anything else.

Scoped to what the model detects. Absence of marks over 299 slices means the model produced no output there, and the sentence says exactly that.

Don't

Normal — no abnormality detected

A single-condition detector read as a clearance of the whole study. Not expressible: the none state has one wording, and it is scoped.

What the component enforces, and what it only asks for

The words are held here. The pixels — outline rather than fill, no ramp, no animation, never an alarm hue — are drawn by a viewer this package does not ship, and the rules about them stay with whoever draws them.

RuleHeld by How
The address is in the units the reader navigates inThe component address is required and is { series, slice, of }; it renders as “series 4, slice 47 of 300”. What, extent and provenance render beside it, so the finding is complete with the image covered.
Asserted: SpatialFinding: the address is in the reader's units, and complete without the image
Marks are off by default and removable in one action; the control never says “explain”The component The switch starts off and there is no prop to start it on. Its label is a constant, and the component’s code contains none of explain, explanation, reasoning, why or because.
Asserted: SpatialFinding: marks start hidden, and the control never says explain
Unhighlighted is not “assessed and clear”The component The none state requires a scope and renders “No regions marked for … This says nothing about anything else.” The code contains neither normal nor no abnormality.
Asserted: SpatialFinding: nothing-marked is scoped, and never 'normal'
Not-analysed is distinct from nothing-markedThe component Four kinds — available, none, partial, outside-scope — each with its own wording. A partial analysis cannot be rendered as a negative one.
Asserted: SpatialFinding: partial and outside-scope are distinct from none
Navigating there is not assessing itThe component onNavigate is the only callback and receives the address. There is no way to wire a “seen” or “assessed” event to anything here.
Asserted: SpatialFinding: navigating is the only callback; nothing records assessment
Showing and hiding the marks reaches the caller, each timeThe component Driven in a browser: the switch reads off to the accessibility tree before anything moves, onShownChange has not fired, and each move after that reports its own value — so a dismissal can be recorded as one.
Asserted: SpatialFinding: the marks switch starts off, and each move reaches the caller
The address button navigates with the address, unalteredThe component Driven in a browser: a button named by the address calls onNavigate with exactly { series, slice, of }.
Asserted: SpatialFinding: the address navigates, with the address and nothing else
The address button meets the touch floorThe component It was 16 px tall — a standalone control, not a link inside a sentence — found the first time the components were measured with a stylesheet.
Asserted: Layout: every control a finger can reach is at least 44 px tall
The address shows the system’s focus ringThe component The governed --ring outline, at 3:1 against its surround; it had none of its own. Measured with the stylesheet a consumer has.
Asserted: Focus: every focusable control shows a ring that meets 3:1 against its surround
The words come first in the reading orderYou This is the words. Whether it precedes the image in your layout is document order on the review screen.
Outline, not fill; no ramp; no animation; never an alarm hueYou Canvas rendering in your viewer. Nothing here draws a pixel.
Attribution is not evidence in the recordYou What you write to the Event log when the switch moves is your choice; onShownChange gives you the moment, not the words.
The extent is the size of the mark, not of the lesionYou extent is a string you write. “14 mm across the mark” is honest; “14 mm lesion” is a measurement with a reproducibility nobody stated — see Derived measurement.
Why this is written down

A rule stated on a component page reads as a rule the component keeps. 7 of these 11 are; 4 are obligations this page is handing to the reader. The four are about pixels and layout, which is the boundary this package drew when it declined to ship a viewer.

Accessibility

Outcomes of use

Clinical safety notes

Risk controls this page specifies

Trace these in your risk file (ISO 14971) and usability engineering file (IEC 62366-1). Which of these the component keeps and which are yours is stated in the guarantees table; a control the component does not keep is one your risk file must show you keep.

Implementation

$ npx shadcn@latest add https://md.notjustany.tech/r/spatial-finding.json
<SpatialFinding
  label="Finding 1"
  what="Region marked, left upper quadrant"
  address={{ series: 4, slice: 47, of: 300 }}   // required: the reader's units
  extent="14 mm across the mark"
  detector={{ name: "ForeCall", version: "2.4" }}
  marks={{ kind: "available", count: 1, onShownChange: (shown) => log(shown) }}
  onNavigate={(a) => viewer.goTo(a)}   // the only callback; there is no onAssessed
/>

// marks.kind is available | none | partial | outside-scope. "none" needs a scope,
// and renders as "No regions marked for …" — never as normal.
NotJustAnyMed.Tech Design System · Spatial finding · v1.0 · draft for review
Reference applications named in this system are fictional; all patient data shown is fabricated.