Repository navigation
Release v0.4.1 - #175
Merged
Merged
Release v0.4.1#175
Conversation
Pill and ModelSelect import utilities from @fleetbase/ember-core, but the addon never declared it, so every rendering test that touched them died with "Could not find module" and the integration suite has been failing on main. Declare ember-core as a dependency, and add ember-data with the tracked-storage polyfill it needs so its services can boot in the dummy app. The resource identity work that follows is tested through these components.
With ember-core installed, the report-widget instance-initializer finds a real universe/widget-service, which injects universe/registry-service, which needs the console's extension loader and cannot boot outside the console. Give the dummy app small stubs with the section/list/key storage the addon relies on, so rendering tests boot cleanly.
Attach::Popover copied @Class onto the component but never rendered it; only @classnames reached the DOM, on the inner div. Put @Class on the floating .ember-attacher element so a caller can size and position the popover as a whole, and replace the placeholder test with one that renders the popover.
computedStyle wrote pointer-events: none unless isShown, but show() never set isShown, and Floating forced pointer-events: none on the floating element as well. So an @Interactive popover could never be entered: the document mousemove saw whatever lay underneath and hid it. Maintain isShown in show() and hide(), let Floating take its pointer-events value from the popover, and only ever enable pointer events for interactive popovers so plain tooltips stay transparent to the pointer. @isShown is now followed after the first render as well. The test drives the real sequence: hover the target, leave it, move onto the popover and check it stays open with computed pointer-events auto.
…ip component its resource Pill always rendered an anchor, so a pill that only names a record looked clickable and did nothing when clicked; the hover style FleetOps added for it matched those pills too. Render a non-link span with the fleetbase-pill--static class when there is no @OnClick, move the hover style into the addon scoped to real anchors, and share the pill body between the two variants. The tooltip component now receives the resource it is describing, and the online dot can be driven by an explicit @online flag as well as a path on the resource.
filter/multi-option already passed @SEARCHFIELD and @searchPlaceholder to MultiSelect, which never forwarded them to PowerSelectMultiple, and there was no way to render a selected chip with a component. Forward @selectedItemComponent, @SEARCHFIELD, @searchFieldPosition and @searchPlaceholder.
…ilter filter/model rendered a fixed ModelSelect whose options were the bare label path. Each option and the selected item now render through the column's filterOptionComponent, or the select-option registered for the filtered model, falling back to modelNamePath text as before. filter/model-multiple is the multi-record filter FleetOps kept locally as filter/multi-model: the value travels as a comma-separated id list and the records are re-fetched on init so chips show names after a reload. It uses the same option hook.
table/cell/resource-identity always rendered its second line, so a cell with no identifier, meta paths or status still took two lines. Render the row only when there is something to put in it.
A descriptor teaches the console how to show one kind of record: title, identifier, image, status, online state, badges, the facts a summary lists, and how to open it. Descriptors live in the shared universe/registry-service so the host and every engine read one set, with a per-owner fallback store for apps without it. resolveResourceKey accepts a record, an identity stub, a model name or alias, a prefix:key string, a PHP class name or a polymorphic type, and strips the facilitator-, maintenance-subject-, customer- and attachable- subtype prefixes. openResource unwraps proxies, thenables and loadResource() stubs, swaps a subtype for its canonical record through the store, and never throws. canOpenResource gates on the descriptor's guard and the abilities service. relationValue reads a relation without ever returning a promise proxy, and safeIdentifier refuses UUIDs. The resource-type, resource-component and resource-relation helpers and the resource-registry service expose it to templates and to engines that prefer runtime lookup over importing ember-ui modules.
…ic interactive popover Attach::Popover registered listeners on the document and its target but never removed them when destroyed, so every escape key and mouse move kept calling into dead components. Remove them in willDestroy along with the pending timers. An interactive popover shown through @isShown, or whose target the pointer left before the listeners were bound, had no way to close on mouse-out because the document mousemove watcher was only installed on the target's mouseleave. Install it when the popover shows.
Resource::Summary is the compact card for one record: image or icon tile, title and identifier, status badge and online state, four to six facts from the descriptor (a fact with a related record renders that record's pill without its own popover), and one View action shown only when the resource can be opened. Hydration runs when the card is inserted and shows skeleton bars meanwhile; a failed hydrate falls back to what the record carries. Resource::HoverCard hosts it lazily. Stage 0 is a hidden anchor with hover and focus listeners on its target; only after 350ms of continuous hover does it render an interactive Attach::Popover with the summary, and when the popover hides it drops back to stage 0. Devices without hover never arm. The shared resource-identity util carries the presentation logic (title, identifier, image, status tone, badges, facts) the identity components derive from a descriptor.
A pill driven by the registry: the descriptor supplies the image or icon tile, the title, the identifier subtitle and the online dot; the pill opens the resource on click when its descriptor knows how and otherwise renders the static variant, and hovering shows the summary card unless @noPopover is set. Wrappers pass @useBlock so a caller's block replaces the default content only when the caller actually gave one.
A small image or icon with the status dot, the truncated name, and up to column.badgeLimit inline badges from the descriptor, all on one line. There is never a status badge; compact, showStatusBadge and showStatus are accepted and ignored so existing column definitions keep working. Badges whose relatedId is the row's own id are dropped. Clicking runs @OnClick, column.onClick and column.action, otherwise opens the resource through its descriptor, and stops propagation so a row action does not fire as well; column.permission disables the button. Without any open path the cell renders plain text and keeps the hover card.
The option row for a ModelSelect or PowerSelect: photo or icon, title and a line of detail, on one line in @compact form for a closed trigger. Given @option or @model the descriptor fills these in; the presentational arguments override it. The CSS is the select-option style FleetOps kept locally, moved into the addon.
user, company, group, role, file and category, read by duck typing so an API payload renders like a record. None opens anything by default because the console has no per-record routes for them; an engine that owns such a page supplies an opener with setResourceOpener. A file opens its URL in a new tab.
user/pill, user/summary, select-option/user and table/cell/user-identity, as wrappers over the resource identity bases for the user descriptor.
company/pill, company/summary, select-option/company and table/cell/company-identity, as wrappers over the resource identity bases for the company descriptor.
group/pill, group/summary, select-option/group and table/cell/group-identity, as wrappers over the resource identity bases for the group descriptor.
role/pill, role/summary, select-option/role and table/cell/role-identity, as wrappers over the resource identity bases for the role descriptor.
file/pill, file/summary, select-option/file and table/cell/file-identity, as wrappers over the resource identity bases for the file descriptor.
category/pill, category/summary, select-option/category and table/cell/category-identity, as wrappers over the resource identity bases for the category descriptor.
Tailwind refuses a selector on a utility class inside an @apply file, so give the pill subtitle a resource-pill-subtitle class and target that.
Glimmer refuses a bare helper as a named argument, so the family pills
pass @useBlock={{(has-block)}}. User::Pill also leaves the online dot to
the descriptor unless an @onlinepath is given.
The family wrappers pass @resourceType, but a polymorphic field holds a record whose model name already says what it is: a customer-vendor is a vendor, a facilitator-driver is a driver. Resolve the record first and use the wrapper's type only as a fallback for records that cannot be resolved on their own, so cell/customer-identity opens the vendor panel for a customer-vendor and the correct summary appears. canOpenResource takes the same fallback.
Port the trigger and chip sizing FleetOps had for its multi-model filter: the small form-input height clipped the multiple trigger into a scroller before anything was selected.
The registry and descriptors read records and identity stubs by path, which is exactly what Ember's get is for; disable ember/no-get in those files with a note. Prettier formatting and no settled() right after a test helper.
…r ember-core Clearing filter/model through the select's own clear control selected null and reported it as a change to an empty value; report it as a clear so the filter is removed. The dummy app gains the fetch and universe stubs the v0.4.2 branch already carries, which the dashboard and docs-panel unit tests need once ember-core is installed. The registry tests allow for the six core descriptors the instance-initializer registers, and the picker tests use the power-select helpers correctly.
Declaring @fleetbase/ember-core turned out to cost more than it bought: its app tree collides with ember-ui's (is-image-file), its services need the console (docs-panel, dashboard), and CI's unit subset went red. Drop it again and remove the two ember-core imports the tested components had instead: Pill falls back to the record's model name directly and ModelSelect uses a local generate-uuid. The dummy app keeps only the universe/registry-service stub the registry tests exercise. Two assertions in the resource-identity test contradicted the template on main (the button has py-0.5; includesText on a multi-element selector reads the first badge); they are corrected while the file is touched.
CodeQL flagged the Math.random fallback in generate-uuid; use crypto.getRandomValues when randomUUID is unavailable.
Fix CSS build performance with linked addons
…caping or encoding' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
The document mousemove handler hid the popover as soon as the pointer left the target, so the gap between a cell and its card closed the card before the View button could be reached. Leaving now schedules the hide, moving back over the target or onto the floating element cancels it, and hide() clears the pending state and the listener.
The card is now a 17rem column with a 36px image, the title and identifier, the status badge at the right and a two-column fact list, with facts that merely repeat the title, identifier or status dropped. It carries a 1px gray-200 border on white in light mode and gray-700 on gray-900 in dark mode, opens with the scale animation, and shows the online state as a dot at the image's top-left corner like the identity cell instead of text. Date facts go through date-fns. View opens the resource before the card closes, so the click reaches its target.
A pill for a resource with no image rendered a broken image; it now falls back to the descriptor's icon (or a cube). The clickable pill gets a little padding so its content no longer sits on the hover border, and the online dot carries a class the compact fact pill can size.
The core user, company, group, role, file and category descriptors no longer declare badges. The identity cell draws its status dot only when the column asks for one, names a path for it, or the resource reports an online state; places, contacts and vendors have none and render without a bulb.
… to a getter
- Pill images carried a 2px gray-800 ring that read as a black border in
light mode, including the related-record pills inside a summary card; they
now use a 1px gray-200 ring (gray-700 in dark mode).
- Every image and icon tile in pills, identity cells and summary cards is a
circle now, at one size per component, and each passes the console's
placeholder as a fallback so a dead avatar URL no longer shows a broken image.
- A pill with no record ("No driver assigned") is gray-500 in light mode
instead of the barely readable gray-400.
- The clickable pill's hover box keeps a 0.5rem right margin instead of a
negative one, so it no longer overlaps the content beside it; the summary
fact cell leaves room on the left and top so the pill's ring and dot are
not clipped.
- The column picker handed the column list to (mut @columns), which threw
"Cannot set property columns ... which has only a getter" on controllers
that compute their columns; the tabular actions now only forward the list
to an explicit @onColumnsChange.
The badge palette gains a Fleet-Ops section covering each value the option lists can put on a badge (driver, vehicle, vendor, contact, fleet, fuel report, inspection, work order, issue, place, telematic, device, sensor, part, equipment and trailer statuses), including the entries that had lived in the fleetops engine stylesheet: long-offline, attached, in-use, detached, retired, out-of-service, passed, submitted, needs-review and unsafe. Values such as processed, fuel-provider, not-equipped and in-stock were unstyled. Select options are shorter now: one 1.5rem circle regardless of resource, no square variant, a tighter two-line text block, and the placeholder image as a fallback.
A presence filter such as "assigned / not assigned" read badly as a dropdown whose placeholder doubled as the "any" state. filter/radio shows every option, with the placeholder as the first "any" radio that clears the filter, and takes the same options, optionLabel and optionValue arguments as filter/select.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release branch for v0.4.1: the resource identity system the console and its engines share. The coverage/playground release that previously held this slot is now
release/v0.4.2(#174, continuing #173).Architecture
Descriptor registry —
addon/utils/resource-registry.js. A descriptor is a plain object that teaches the console how to show one kind of record:key,aliases,modelNames,polymorphicTypes,labelKey,icon,title(r),identifier(r)(never a UUID),image(r)({ url, fallback, shape },{ icon }or{ component }),online(r),status(r),statusTones,badges(r, ctx),selectDetails(r),facts(r), optionalhydrate(r, owner),open(r, { owner, event }),canOpen(r, owner),permission, andcomponents(pill,summary,identity,selectOption, defaulting to<key>/pill,<key>/summary,table/cell/<key>-identity,select-option/<key>).Descriptors are stored in the shared
universe/registry-service(sectionresource-identity), so the host and every engine read one set; an owner without that service gets a per-owner fallback store. Exports:registerResourceDescriptor(s)(owner, …),setResourceOpener(owner, key, fn, { permission, canOpen }),getResourceDescriptors(owner)resolveResourceKey(owner, input)— a record (constructor.modelName), a stub withresourceType, a model name or alias, aprefix:keystring, a PHP class name, or a polymorphic type; strips thefacilitator-,maintenance-subject-,customer-andattachable-prefixes;nullwhen unknowngetResourceDescriptor,resourceComponentName(owner, kind, input)—nullwhen the key is unknown or the component is not resolvable inowneropenResource(owner, input, opts)— unwraps proxies, thenables andloadResource()stubs, loads the canonical record for a subtype (peekRecord ?? findRecord), callsdescriptor.open, returnsfalsewhen nothing happened, never throwscanOpenResource(owner, input, { resourceType })— open path exists,canOpenguard passes,abilities.can(permission)relationValue(record, name)(never a proxy),safeIdentifier(value)(drops UUIDs),unwrapResource,readDescriptorHelpers:
{{resource-type x}},{{resource-component "pill"|"summary"|"identity"|"select-option" x}},{{resource-relation record "name"}}. Theresource-registryservice fronts the util for engines that preferowner.lookup('service:resource-registry')over importing an ember-ui module.Bases —
Resource::Summary(compact card: 48px image or icon, title and identifier, status badge and online state, up to six facts, one View action shown only when the resource can be opened; hydrates on insert with skeleton bars, falls back on error),Resource::HoverCard(stage 0 is a hidden anchor with listeners on its target; after 350ms of continuous hover it renders an interactiveAttach::Popoverwith the summary; drops back to stage 0 when the popover hides; skipped on(hover: none)devices;z-index: 10000, above the context panel),Resource::Pill(descriptor-driven pill that opens on click when it can, otherwise the static variant; hover card unless@noPopover),Table::Cell::Identity(one line: image with status dot, name, up tocolumn.badgeLimit ?? 2badges; badges pointing at the row itself are dropped; click chain@onClick→column.onClick→column.action→openResource, withstopPropagation;column.permissiondisables;compact,showStatusBadgeandshowStatusare accepted and ignored) andResource::SelectOption(photo or icon, title, details;@compactfor the closed trigger).The record's own type always wins over a wrapper's
@resourceType, so a polymorphic field holding acustomer-vendorrenders and opens as a vendor.Core families — user, company, group, role, file and category each get
<key>/pill,<key>/summary,select-option/<key>andtable/cell/<key>-identity, with descriptors registered byinstance-initializers/register-core-resource-descriptorsin the host. None opens by default (the console has no per-record routes for them; a file opens its URL); an engine that owns such a page callssetResourceOpener.User::Pillkeeps its@user/ "No user" / email defaults and gains blocks,@subtitle,@showOnlineIndicator/@onlinePath,@noPopoverand the class args.Fixes (one commit each)
Attach::Popoverapplies@classto the floating element@isShownis followed after the first renderAttach::TooltipforwardshideDelay,hideDuration,@class,@style,floatingTarget,floatingContainer,onChangePillrenders a static span when it has no@onClick(hover style scoped to real anchors, in the addon), and passesresource=@resourceto@tooltipComponentUser::Pillupgraded as the user family pillMultiSelectforwards@selectedItemComponent,@searchField,@searchFieldPosition,@searchPlaceholderfilter/modeloption hook (@filter.filterOptionComponent ?? (resource-component "select-option" @filter.model), falling back tomodelNamePath), plusfilter/model-multipletable/cell/resource-identityrenders no empty meta rowAttach::Popovernever removed its document listeners on destroy, and an interactive popover shown programmatically could not close on mouse-outFor engines
Then
<Vehicle::Pill @vehicle={{x}} />,cellComponent: 'cell/vehicle-identity',<SelectOption::Vehicle @option={{x}} />andfilterComponent: 'filter/model'all work from the one descriptor. FleetOps adopts this in fleetbase/fleetops (PR B), which depends on this release being published.Dummy app
PillandModelSelectno longer import@fleetbase/ember-core(a local model-name fallback and a localgenerate-uuid), so the components this release adds can render in the test app without ember-core. The branch briefly declared ember-core as a dependency and reverted it: its app tree collides with ember-ui's and its services need the console. The dummy app gains auniverse/registry-servicestub with the section/list/key storage the registry uses.Tests and coverage
New and updated tests:
tests/unit/utils/resource-registry-test.js(resolution by key, alias, model name,prefix:key, PHP class and polymorphic type; subtype prefixes; the service store and the per-owner fallback;openResourceunwrapping proxies, thenables and stubs, alias → canonical load throughpeekRecord/findRecord, thefalsepath;canOpenResourceguards and permissions;relationValue,safeIdentifier,readDescriptor),tests/unit/services/resource-registry-test.js,tests/integration/helpers/resource-helpers-test.js,tests/integration/components/resource/smoke-test.js(the user family renders all four components; hovering a pill arms and shows the summary, which can be entered and tears down on leave; an opener makes pill and cell clickable),attach/popover-test.js(class application, computedpointer-eventsand entering from target to popover, escape, reactive@isShown),attach/tooltip-test.js(forwarded options),pill-test.js(static vs clickable, tooltip resource, blocks, online),multi-select-test.js,filter/model-test.jsandfilter/model-multiple-test.js(option component, column override, label fallback, restore from ids, clear), and the meta-row case intable/cell/resource-identity-test.js.Locally, the CI subset (
ember test --filter='!Integration', 70 tests) plus every new and touched integration module pass together (41 + 6 tests, 0 failures).mainhas no coverage gate (thetest:coverage/coverage:checkscripts andember-cli-code-coveragelive onrelease/v0.4.2), so no coverage number is reported here; the new files were written to be covered line by line and will meet the 100% gate when v0.4.2 lands on top of this.Follow-ups
Resource::Pill,Resource::Summary,Table::Cell::IdentityandResource::SelectOptiononce they are documented on fleetbase.io/docs/ui (seePLAYGROUND.mdonrelease/v0.4.2).release/v0.4.2(Release v0.4.3 #174) deletescustom-field/form, which ember-core'scustom-fields-registrystill opens; creating or editing custom fields will break on that release unless it is restored.mainhave always failed and are filtered out of CI (--filter='!Integration'); the coverage campaign on v0.4.2 replaces them.pnpm lint/pnpm test:emberonmainneed pnpm ≥ 10 (the workspace file has onlyallowBuilds), and pnpm 11's pre-run dependency check fails when an olderpnpmis first onPATH; runningnode_modules/.bin/eslint,ember-template-lint,stylelintandember testdirectly works.Manual test plan (after linking ember-ui into the console)
driver_namestill open the driver.activity-logpopover can still be entered; existingResourceIdentityusers in other engines are unchanged.