Skip to content

Choose the spectral axis with a union of groups - #1426

Draft
SimonHeybrock wants to merge 4 commits into
mainfrom
spectral-axis-union
Draft

SimonHeybrock wants to merge 4 commits into
mainfrom
spectral-axis-union

Conversation

@SimonHeybrock

Copy link
Copy Markdown
Member

Monitor and detector-view params selected the spectral axis with a coordinate_mode group next to four groups, toa_edges, toa_range, wavelength_edges and wavelength_range, each range with an enabled flag. A request carried the fields of both axes, and the UI showed all of them as separate tabs, whichever axis was selected.

This applies ADR 0008 of essapps ("a choice between ways is a union of groups; X | None means without X"): the five fields become one, spectral_axis, a union discriminated by kind:

class TOASpectralAxis(BaseModel):
    kind: Literal['toa'] = Field('toa', title="Time of arrival (TOA)", description=...)
    edges: TOAEdges = ...
    range_filter: TOARange | None = None

class WavelengthSpectralAxis(BaseModel): ...  # same shape

SpectralAxis = Annotated[TOASpectralAxis | WavelengthSpectralAxis, Field(discriminator='kind')]

The selector controlled two groups per axis, so each alternative holds both, and the enabled flags become range_filter: X | None. TOA-only specs subclass the params model and narrow the field to TOASpectralAxis, which replaces the validator that rejected wavelength. Factories read axis.kind, axis.edges and axis.range_filter instead of the get_active_* methods.

Dashboard param UI

The generic param widgets gain the two components the ADR asks of a UI generator, plus nesting:

  • A union of groups is a card with one radio button per alternative in its header, labelled by the title of the tag field; the card holds the chosen alternative's description and fields. Fields of the other alternatives keep their values while hidden.
  • A group typed X | None is a card with a switch in its header, collapsed while off.
  • A group nested in a group is a card titled by its field, with its description as a tooltip.
  • A Literal of one value, such as a tag, has no widget.
  • A union is refused as a field of a group (the ADR makes it a parameter of its own), as are hidden-field paths into a union.

The error highlight of a field is now an outline, since clearing it with border: none removed a card's own border. Literal labels via json_schema_extra, whose only user was the coordinate-mode selector, are removed.

Behaviour changes

  • Stored configs in the old shape lose their spectral settings and start from the default axis (TOA, default bins); other groups such as pixel weighting are kept. No migration code. The backend validates params the same way, so the dashboard and backend should be deployed together: an old dashboard talking to a new backend would start jobs with default binning.
  • The config dialog falls back to defaults for stored params that do not validate against the current model, e.g. a wavelength axis stored for a spec that is now TOA-only, which previously broke the dialog.
  • The range filter defaults to one pulse period in ms instead of 0–10 µs, which matters now that it is one switch away.
  • OutputView.params names top-level fields only, so every axis-dependent output now lists spectral_axis in "Affects outputs", without distinguishing edges from range filter.

Test plan

  • Open the config of a monitor and a detector view on an instrument with both axes (e.g. LOKI) and on a TOA-only one; switch axes, toggle the range filter, start, and check the job uses the chosen settings.
  • Reopen the config of a running job and check the stored axis, edges and range filter are shown.

🤖 Generated with Claude Code

SimonHeybrock and others added 4 commits October 23, 2026 11:18
Monitor and detector-view params selected TOA or wavelength with a
`coordinate_mode` field next to four groups, `toa_edges`, `toa_range`,
`wavelength_edges` and `wavelength_range`. A request carried the fields of
both modes, the UI showed all of them, and each range filter had its own
`enabled` flag.

They are now one field, `spectral_axis`, a union discriminated by `kind`
(ADR 0008 of essapps): `TOASpectralAxis | WavelengthSpectralAxis`. Each
alternative holds its bin edges and a range filter typed `X | None`, so the
selector's two groups per mode merge into one alternative. TOA-only specs
narrow the field to `TOASpectralAxis`, which replaces the validator that
rejected wavelength.

The dashboard's param widgets learn the two components the ADR asks of a
UI generator: a radio choice for a union of groups, and a checkbox for a
group typed `X | None`. Groups nested in groups are shown as indented
blocks, and a `Literal` of one value, such as a tag, has no widget.

Stored configs in the old shape lose their spectral settings and start
from the defaults; pixel weighting is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A union of groups becomes one card whose header is a row of segmented
buttons, so the choice visibly owns the fields below it; plain radio
buttons above the fields did not read that way. A group typed `X | None`
is a card with a switch in its header, collapsed while off: a checkbox
read as a bool field of the group. A group nested in a group is a card
titled by its field.

The error highlight of a field is an outline rather than a border, since
clearing it with `border: none` would remove a card's own border.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The segmented buttons outweighed the tabs above them. Radio buttons in the
header match the switch in the header of an optional group's card: in both,
the header holds the control and the body what it controls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A union of groups is refused as a field of a group, as ADR 0008 of
  essapps now states; it is a parameter of its own.
- UnionWidget clears the error state of every alternative, so an error
  does not stay on an alternative the user switched away from.
- Stored params that do not validate against the current model fall back
  to the defaults in the config dialog. A wavelength axis stored for a spec
  that is now TOA-only no longer breaks it.
- The range filter defaults to one pulse period in ms, not 0-10 µs.
- The wavelength alternative explains itself, as the TOA one does.
- Literal labels via json_schema_extra are gone; their only user was the
  coordinate-mode selector.
- The wavelength-instantiation test collects only specs with a spectral
  axis, instead of every spec whose params all have defaults.
- A round-trip test runs the real params models through ModelWidget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@SimonHeybrock SimonHeybrock self-assigned this Oct 23, 2026
SimonHeybrock added a commit to scipp/essapps that referenced this pull request Oct 26, 2026
An app without a client or a backend: specs and their bindings, a
dataset source, and the outputs computed so far. The form is generated
from the pydantic models; one choice widget serves a group, a union of
groups, and a parameter typed X | None. A place without a default
starts unset. The widgets follow scipp/esslivedata#1426, in Panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SimonHeybrock added a commit to scipp/essapps that referenced this pull request Oct 26, 2026
An app without a client or a backend: specs and their bindings, a
dataset source, and the outputs computed so far. The form is generated
from the pydantic models; one choice widget serves a group, a union of
groups, and a parameter typed X | None. A place without a default
starts unset. The widgets follow scipp/esslivedata#1426, in Panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SimonHeybrock added a commit to scipp/essapps that referenced this pull request Oct 26, 2026
An app without a client or a backend: specs and their bindings, a
dataset source, and the outputs computed so far. The form is generated
from the pydantic models; one choice widget serves a group, a union of
groups, and a parameter typed X | None. A place without a default
starts unset. The widgets follow scipp/esslivedata#1426, in Panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

1 participant