Skip to content

Releases: moazzamak/dsh-train-dashboard

v0.2.1 — the trigger moves into the session header action row

Choose a tag to compare

@moazzamak moazzamak released this 06 Oct 08:01

Put the trigger in the session header's action row, not the overlay layer

The one-click chart glyph was registered in shell.overlay. The slot catalog
defines that seat as "a frame-wide floating layer, above every column and outside
their scroll containers... a badge, a toast stack or a status pill all belong
here", and the layer is click-through on purpose so an overlay never blocks the
app underneath it. A BUTTON registered there is wrong twice over, and both were
visible in the running application:

  • it is drawn in the window's top-left, beside the Application/Edit menus and
    the sidebar's reopen control, because that is where the frame's overlay band
    sits, so the trigger read as a stray icon in the window chrome;
  • it cannot be clicked without opting back into pointer events, because the layer
    itself is pointer-events: none.

It now registers in conversation.session.header.actions, the seat the catalog
describes as "title-adjacent Session actions in ascending order" and the one the
shipped jobs, subagent, agent-preset and agent-team controls occupy. Its styling
copies theirs rather than inventing a look: borderless, transparent, at least
28px, --dsw-radius-sm, tertiary label colour at rest and secondary on hover
with a subtle fill, which is what makes the row read as one set of controls. The
30px bordered button and the earlier position: fixed are both gone, and the
seat is a named constant (TRIGGER_SLOT) so the next reader sees the choice
rather than a string.

Verification: npm test, 62 tests passing. The seat test now asserts the trigger
is in the header action row AND that nothing of this plugin is registered in
shell.overlay, which is the assertion that would have caught this; the wrapper
test also asserts the icon-only button carries an accessible name. A render
through real React passes 16 checks over the mounted SVG.

v0.2.0 — select the timeline, and give the stylesheet an owner

Choose a tag to compare

@moazzamak moazzamak released this 06 Oct 07:55

Three things land here: the chart's window, the sheet's ownership, and the
trigger's place. The sheet is why the plugin was drawing black charts in a
running harness, and it is the one to read first.

THE STYLESHEET IS NOW OWNED BY data-plugin. The harness client loader owns
plugin styles by that attribute: claimStyles marks every <style> that LACKS it
as belonging to whichever plugin materialises next, and removeOwnedStyles
deletes every <style> whose data-plugin equals an id when that entry is
replaced or pruned. This sheet carried only a private data-dsh-train-dashboard
marker, so it looked unowned to the loader: the next plugin to materialise
claimed it, and that plugin's first refresh or prune deleted it. Losing a sheet
is silent and severe, because an SVG with no fill: none fills black,
which is exactly how it showed: every chart in the tab drew black, and the
trigger fell back to the browser's default button. The sheet is injected while
apply runs, which is after the loader's claim pass, so the plugin's own claim
never saw it either. The sheet now carries data-plugin and data-plugin-css, a
second apply no longer stacks a duplicate, and the test asserts all of it.

THE CHART ZOOMS TO A WINDOW THE READER SELECTS. Drag across the chart to select
it; the wheel zooms about the pointer; the strip under the chart is the whole
run at a fixed scale with the window drawn on it, and dragging or clicking it
chooses anywhere on the run; Reset, 0 or a double-click returns to the whole
run. The axis rescales to the samples IN the window, which is the point of the
interaction: on a 20,000-step run with an early spike and a flat tail, the
whole-run axis sits at 26.3 and a tail window scales to 2.589-2.601.

Two details are load-bearing:

  • The line is drawn with one sample beyond each edge, so it meets the frame
    instead of starting in mid-air. Those bracketing samples must NOT reach the
    axis: scaling to them hands the scale back to the spike the reader zoomed in
    to get away from. pointsToDraw is the geometry and pointsInRange is the
    axis, with a test pinning the difference. The path is clipped to the plot,
    which matters because an out-of-window sample can now project far off it.
  • Zooming changes the DOMAIN, not an SVG transform, so strokes stay one pixel
    and the ticks are the standard nice ones for the window on screen. A window
    is clamped to the run, slides back inside when dragged past an edge, and
    stops at a registered floor, because a window holding one or two samples is a
    chart that says nothing.

The range is reachable without a mouse, since one that exists only as a dragged
rectangle is one a keyboard user cannot choose and a screen reader cannot
report: the chart takes focus (+, -, 0, arrow keys, Esc), every control
is a labelled button, and the readout and the accessible name both carry the
window in steps.

THE TRIGGER RENDERS THE WRAPPER ITS OWN STYLESHEET POSITIONS, and no longer
pins itself under the window controls. position: fixed at the top right put
it beneath minimise/maximise/close, where it cannot be clicked; it now sits in
the frame's overlay row as a normal icon button of that row.

Verification: npm test, 62 tests passing, up from 51. The eleven new ones pin
the window arithmetic and the reader-facing controls: clamping and the floor,
the zoom anchor holding its place on the frame, panning, brush direction,
pixel-to-value mapping, the readout text, and that the chart is focusable with
labelled controls. A separate render through real React (not the test file's
stub) passes 16 checks over the mounted SVG.