Skip to content

v0.3.0

Choose a tag to compare

@github-actions github-actions released this 23 Jul 06:59
· 50 commits to main since this release
eddfd81

Admin family: layout stability pass

No more layout jumps when interacting with the admin components (user
feedback: the schedule header shoved everything around when buttons
appeared). Three mechanisms, applied family-wide:

  • The Save/Discard commit tail now RESERVES its space while the draft is
    clean (rendered invisible and inert via a new CommitControls
    reserveSpace opt-in — the rich-text/code toolbars keep their
    historical pop-in), so going dirty no longer reflows the header.
  • Two-step delete buttons (and the Manage-roles toggle) render BOTH
    labels stacked with the inactive one invisible (shared ConfirmButton),
    so arming "Delete" → "Confirm delete?" can't change the button's width.
  • Validation errors float as absolutely-positioned chips below their
    anchor instead of reflowing siblings (name/username/date/role/
    adjustment errors alike).
    Verified by pixel measurement: the schedule header's Delete button, week
    grid and preview strip hold identical coordinates across clean → dirty →
    armed-confirm → restored; the users form holds position when an
    adjustment error appears.
    Follow-up structural pass (same release): actions moved out of the
    headers entirely into a shared footer action bar (hairline top,
    destructive Delete on the left, the reserved Save cluster on the right,
    pinned to the pane's bottom) — headers are identity-only and never fight
    buttons for width again. Forms got readability caps (users 640px,
    holiday 420px) and hairlined section headings.

User Manager: availability adjustments

The detail form gains an "Availability adjustments" section editing the
per-user schedule overrides Ignition already models (vacation = available
false, extra cover = available true): from/until instants
(datetime-local, wire format 'YYYY-MM-DD HH:mm'), note, add/remove rows.
Blank rows drop on save; partially filled or inverted rows BLOCK save
('adjustmentInvalid' in output.validationErrors). Rides in the existing
onUserSave payload (user.scheduleAdjustments, rebuilt wholesale via
system.user.createScheduleAdjustment — see the /users reference script,
which also documents that the ScheduleAdjustment bean's getter names
vary, hence the defensive accessors). e2e covers the full round-trip
incl. validation blocking.

Holiday Manager (new component — admin family #4)

Thirteenth component: mustrysolutions.ingots.admin.holidaymanager — the
missing quarter of the schedule story: schedules can "observe holidays"
but nothing in the runtime showed or edited which dates those are. A rail
sorted by NEXT occurrence (annual repeats compute their next date, Feb-29
observes Feb 28 off-leap-years, past one-offs sink and dim) + a small
detail form (name/date/repeat) with strict calendar-checked date
validation (no silent Feb-31→March rollover). All the family patterns:
create/rename/delete with validation, draft discipline,
onHolidaySave {holiday, isNew, oldName?} / onHolidayDelete {name}
persisted via system.user.addHoliday/editHoliday/removeHoliday,
output.count/isDirty/validationErrors, 7-language labels, --adm-*
theming. Demo at /holidays (real persistence, seeded demo holidays);
the Admin Console gains a fourth tab. 13 pure-logic jest cases + 3 e2e
tests incl. a create→rename→delete lifecycle.

Admin Console demo view (composition recipe)

The verify project gains /admin: the three admin components composed in a
NATIVE tab container — the intended pattern instead of a merged
mega-component (tabs and page security are the platform's job; per-tab
capability flags do the "what do you want to use" part). Each tab is the
same live component + reference scripts as its standalone demo, sharing one
refresh tick. Documented in the README ("Composing an Admin Console");
smoke-tested end-to-end. NOTE for view authors: tab-container children
require position: {tabIndex: N} — without it only the first child renders.

User Manager: role-catalog management (opt-in)

config.allowRoleManagement (default off) adds a manage mode to the Roles
section: add, inline-rename and two-step-delete roles in the user source's
catalog, firing onRoleSave {name, oldName?} / onRoleDelete {name}
immediately (persist via system.user.addRole/editRole/removeRole).
Renames keep user assignments (the source stores role ids); the UI warns
that security policies reference roles BY NAME. Folded into the User
Manager rather than a separate component — Ignition roles are flat
strings, and role gaps are noticed while assigning them. The /users
demo persists for real (and refuses to delete 'Administrator'); e2e
covers add → assign → rename-keeps-assignment → delete.

User Manager (new component — the admin family is complete)

Twelfth component: mustrysolutions.ingots.admin.usermanager — Vision's
User Management for Perspective, closing out the admin family. A filterable
user rail + a detail form editing names, schedule (dropdown), language,
notes, role chips and contact-info rows, all against the flat PyUser mirror
(data.users, bound via system.user.getUsers(); role/schedule catalogs
bound alongside). Passwords are OPT-IN and payload-only:
config.allowPasswordChange (default off) stages a password that travels
only in the onUserSave {user, isNew, password?} event — never through
props, state or output — and the docs say loudly to put the component
behind security levels + TLS first. Create/delete flows with username
validation (onUserDelete {username}); AD/LDAP sources degrade to a
viewer via config.editable: false. The /users demo persists for real
(roles/contacts rebuilt wholesale, delete-admin refused, complexity policy
respected). output.count/isDirty/validationErrors, 7-language labels,
shared --adm-* theming. 7 pure-logic jest suites' worth of draft ops + 5
e2e tests incl. a create-with-password→edit→delete lifecycle.

Roster Manager (new component — second of the admin family)

Eleventh component: mustrysolutions.ingots.admin.rostermanager — a runtime
UI over the gateway's alarm-notification rosters (Vision's Roster
Management; Perspective has nothing). A roster is the ORDERED escalation
sequence alarm pipelines walk, so the UI treats order as the point: rows
carry "Contact N" ordinals and reorder by dragging a grip; a typeahead
picker over the bound data.availableUsers directory appends users; rows
resolve names/contact points and warn when a user has NO contact info (the
real failure mode). Create/delete flows with name validation; draft-only
edits with the shared Save/Discard tail. Controlled: system.roster is
append-only — no reorder primitive — so onRosterSave {name, users, isNew}
carries the full desired order and the author's script reconciles
(createRoster → removeUsers → addUsers); onRosterDelete {name}. The
/roster demo persists for real and seeds a small demo directory
(including a contact-less user to show the warning).
output.count/isDirty/validationErrors, 7-language labels, shared
--adm-* theming (now a SCSS mixin used by the whole family). 14 new
pure-logic jest cases + 4 e2e tests incl. a create→persist→delete
lifecycle and a reorder-persist round-trip.

Schedule Manager (new component — first of the admin family)

Tenth component: mustrysolutions.ingots.admin.schedulemanager — a runtime
UI over the gateway's user schedules, the highest-vote component-shaped gap
on the Ideas portal ("Perspective - Admin Components", 102 votes since 2019,
no native commitment; only copy-in Exchange view templates exist). Vision has
Schedule Management; Perspective has nothing. Master-detail: schedule list
with live active-now dots + a 7-day week grid where availability is
painted — drag empty space to add a range (snapped), drag block edges to
resize, click a block to remove; a red now-line marks the current time and a
preview strip answers "active now? until when?" (output.isActiveNow, 30s
tick, midnight-continuity and weekly wrap handled). Name/description/flags
edit inline; + New schedule creates from a blank draft; Delete asks twice.
Controlled: data.schedules is a flat mirror of Ignition's
BasicScheduleModel beans (bind via system.user.getSchedules()), edits stay
draft-only until Save fires onScheduleSave {schedule, isNew, oldName?} /
onScheduleDelete {name} and the author's script persists via
system.user.* — the /schedule demo ships reference scripts incl. the
rename add-then-remove reconcile. output.count/isDirty/validationErrors,
name validation, 7-language labels, --adm-* family theming. Alternating
A/B schedules deliberately render week A read-only pre-1.0 (unverified bean
layout). All range/availability/transition/paint math pure + node-tested.
Demo at /schedule; 7 e2e tests (incl. a full create→rename→delete
lifecycle against the live gateway) + 72 pure-logic jest cases. Family plan
in docs/admin-components-plan.md.

On-Screen Keyboard (new component)

Ninth component: mustrysolutions.ingots.input.keyboard — a touch keyboard for
the runtime, the gap Perspective leaves to the OS keyboard (Windows
TabTip / Linux Squeekboard), which breaks in Perspective Browser/mobile
and causes the "double-keyboard" problem; the Exchange only offers view
templates that can't stop the OS keyboard. Its value display is a <div>,
not an <input>, so tapping it never raises the OS keyboard — the actual
fix, not a workaround. Two families sharing one controlled shell: a
numeric keypad (layout: numpad, editing value.value — min/max clamp
with an out-of-range badge, decimals, units, allowNegative, setpoint-style
fresh entry) and a QWERTY keyboard (layout: text/email/url,
editing value.text — one-shot shift, a ?123 symbols layer, maxLength,
@/.com// convenience keys). mode: inline or popover (a field trigger
that opens a portalled panel; Enter commits + closes, outside-click/Escape
discards). Enter fires onCommit {value, text, isValid} (value is a number
or string by layout); live onChange; read-only output.*
(value/text/isValid/length/draft); 7-language labels, --kbd-* theming.
All editing rules pure + node-tested. Demo at /keyboard; 8 e2e tests +
26 pure-logic jest cases.

Color Picker (new component)

Eighth component: mustrysolutions.ingots.input.colorpicker — a runtime colour
input, the gap Perspective only fills at design time (the property-editor
selector) and that the Exchange covers only with copy-in views (#112,
#2388, #2691), not a compiled component. HSV area + hue/alpha bars
(continuous drag; hue survives greys/black via a working copy),
hex/rgb/hsl formats switchable at runtime, optional alpha
(#RRGGBBAA/rgba()), three presentations from mode + showInput
(full inline panel / swatch+code popover / compact icon button) whose
popover triggers carry a contrast-aware eyedropper glyph so they read as a
control, portalled panel (flips on overflow, outside-click/Escape close,
optional config.popoverScrim backdrop), a bound swatch palette
(data.swatches) + recent-colours row, and an eyedropper (EyeDropper API
where available). Controlled write-back: value.color is the bound truth,
a pick fires onChange {value, hex, rgb, hsl, alpha} and mirrors
read-only output.* (hex/rgb/hsl/alpha/isValid); 7-language labels,
--cp-* theming. All colour maths is pure + node-tested. Demo at
/color; 6 e2e tests + 30 pure-logic jest cases.