release: Fleet-Ops v0.6.66 - #321
Merged
Merged
Conversation
The inspection platform (#267) shipped a public, tokenised link and the console's internal CRUD, and nothing a driver's app could authenticate against: a driver who inspects the same truck every morning had no way to file a DVIR except a link minted for them each time. This adds the consumable surface under v1: GET /v1/inspection-forms published forms; vehicle=, type= GET /v1/inspection-forms/{id} one published form POST /v1/inspections file against a form GET /v1/inspections driver=, vehicle=, type=, result=, status= GET /v1/inspections/{id} GET /v1/vehicles/{id}/inspections Every lookup is scoped to the API key's company, and an unpublished form answers 404 rather than 403: to a driver a form that cannot be filled in does not exist. The submit body is exactly what the public link accepts, plus the form, driver and vehicle the link already knew. Both doors now go through one InspectionSubmitter, so they cannot drift on what a submission is. The app queues a submit while offline and replays it; a replay carrying the same Idempotency-Key answers with the submission the first attempt filed rather than filing twice. The platform has no idempotency middleware, so the key is kept on the submission's meta and looked up there. Photos are validated as base64 or a URL by a small Rule rather than a regex: a DVIR photo is easily a megabyte of base64, and a backtracking pattern over that only fails on a real handset. Coverage: the platform arrived with no tests, and the gate is 100% of server/src. The models, the public and internal controllers, the resources, the hub and report schema changes and the new API are covered against an in-memory SQLite database, as the trailer lifecycle tests are. Four tests on main asserted the old contract and are updated to the new one, kept exact: the maintenance hub leads its KPIs with failed inspections and adds an Inspections section and doc; a hub with no published form is told to create one; the report schema registers work orders, maintenance history and inspections; a closed work order records its maintenance as `completed`. The migration is re-stamped to today, content unchanged.
…, and translate the sidebar Both form components assigned to @resource in their constructors, which Glimmer refuses once the template has read the attribute in the same render pass ("You attempted to update items ... already used in the same computation"). The model's items / item_results are now the single source of truth, read through getters and replaced only from actions. Text fields update from the DOM event rather than passing the stale rendered value, and every control honours cannot-write. The sidebar items are translation keys, added to all nine locales.
The first cut stored a form as a JSON list of pass/fail items. That is a checklist. Fleetio parity — and the fliit module that already has it — wants groups of typed fields, built from the platform's own custom-field system, so an inspection is a form filled in rather than a list ticked. A form's groups are platform categories owned by the form; its fields are custom fields whose subject is the form, filed under `fleetops_inspection_form` so a field the console's generic panel adds to the form record is not mistaken for one a driver answers. Both come back as `grouped_fields`, which is what the app codes against. A submission answers through custom-field values. `inspection_item_results` stays: issues, work orders and the history are built from it, so every pass-fail value is mirrored into a result row keyed the way the app keys it. "Not applicable" is carried through as its own status rather than flattened into a pass. Photos and signatures arrive as base64 — the app is offline-first and cannot upload first — and are stored as platform files, leaving `file:<uuid>` as the value, which is the platform's convention. The submission resource answers `custom_field_values` with each field's identity beside its answer and every file reference resolved, plus the `files` the inspection carries. The rules a field insists on when it fails are checked before any photo is stored, so a refusal leaves nothing behind. The tests' in-memory schema gains `categories`, the real `custom_fields` columns and the file columns the projection reads.
The console writes a form's structure by posting the whole draft — the builder lays a form out before the record exists, so the structure has to arrive with the save that creates it. `InspectionFormController` takes it under `inspection_form.field_groups` (and fliit's `draft`, so a form authored there still saves) and prunes what the post no longer lists. `InspectionSubmissionController` accepts `custom_field_values` on the same terms as the driver API, so the console and the app write the same rows, and still accepts `item_results` for a legacy checklist. Two bugs found on the way. `Request::array()` is a core-api macro taking exactly one argument, so every `$request->array($a, $request->array($b))` in this controller silently discarded its fallback and the flat spelling never worked; both reads now go through a helper that tries each key. And a field written straight into the table by a seed carries no `component`, so the resource names one from the type. `InspectionExport` gives compliance the spreadsheet it asks for: one row per submission, defects named, follow-up and out-of-service called out, behind the usual `export` route. The migration folds a first cut's `items` into a "Checklist" group of pass-fail fields, skipping any form already built with fields so it can be run again safely.
Every new line, in the harness's own style: an in-memory database with the platform's `categories`, `custom_fields` and `custom_field_values` beside the inspection tables, and a real local filesystem so the file store can be watched turning a driver's base64 into a platform file and the resource resolving it back. What is pinned here is the contract the app codes against: `grouped_fields` with a field's id, type, options and meta; `custom_field_values` accepted by uuid or by name; a pass-fail answer read whatever shape it arrives in, "not applicable" kept as its own status; the comment and photo a field insists on refusing the whole set before any file is written; a meter and a boolean answered back as a number and a boolean rather than the strings the value column stores. One behaviour changed to match: a number value now leaves the resource as a number. The `custom_field_values` guard in the submission resource went — `withCustomFields()` has always loaded that relation, so it was dead.
The console side of the second cut. A form is built in `inspection-form/builder`: field groups with a grid size, fields added, edited, reordered and deleted through a FleetOps field editor that offers the inspection type list and, for `pass-fail`, the On fail section the driver app reads — default severity, whether a photo or a comment is required, whether the defect parks the truck, and the instructions the driver sees. A field is answered through `inspection-field/input`. `pass-fail`, `signature` and the inspection flavour of `file-upload` are FleetOps' own, as are `textarea`, `number` and `boolean` — the platform's type map has no component for those three. Everything else is handed to the platform's `custom-field/input`. The record gets Overview, Photos and Audit tabs; Photos is `ModelMultiFileUpload` against `inspection_photo`, Audit the platform's `ActivityLog`. The builder holds the structure as a draft of plain objects, so a form can be laid out before the record exists and the whole thing is posted with the save. It has to be posted separately: `inspection-form` and `inspection-submission` live in `@fleetbase/fleetops-data` and declare no attribute for the structure or the answers, so Ember Data drops both in both directions. Until that package gains them, the structure and the answers ride their own request to the internal endpoint, which is also the only spelling the server reads. Glimmer discipline throughout: nothing writes to `@resource` during render, text inputs update from the DOM event, and every control honours `cannot-write`.
Two things the console does that the driver app does not. It uploads a photo the moment it is picked and keeps the reference the upload answered with, which names the file by its public id — `file:file_…`, not `file:<uuid>`. That reference was passed through untouched, so nothing downstream could resolve it: the file was never claimed by the submission and the resource handed the raw string back. A `file:` reference that does not name a uuid is now looked up and rewritten to the one it does. And it can clear a field. `custom_field_values.value` is a NOT NULL column, so an answer that is nothing is not an answer — it deletes the row instead of writing a null the column will not take.
Three components kick a load off in the next runloop; if the panel closes first the task is performed on something already torn down. And the form the answering screen assigns writes its own uuid into the column, never the id the console addresses it by.
A severity that is not one of the four the field editor offers has no key, and asking for one puts "Missing translation" on the record. Fall back to the value itself.
`valueTypeFor` in the app's useInspections sends `datetime` for a date-time field and `date` for a date; the console said `date` for both. The server takes either, but the two clients should file the same row.
buildSubmission in the app's useInspections.ts emits both bodies at once, every pass-fail answer carrying not_applicable and unsafe, photos and the signature as bare base64. That exact shape now goes through the submitter in a test, so a change here that the app would not survive fails here first.
Same as the others: the details panels kick their load off in the next runloop, and closing the panel first would perform a task on a component already torn down.
The tokenised link built its checklist from `items`, which a form built in the new builder does not have — so the link showed an empty form and would not submit. It now falls back to the pass/fail fields of `grouped_fields`, keyed the way the server keys a derived result, so a link submission and an app submission name the same item. The other field types are still not offered there: a public link is a checklist for a contractor, not the app.
`InspectionFormSync` read every custom field subjected to the form, not just the ones filed under the inspection kind. The builder posts the whole form and prunes what the post no longer lists, so a custom field the console's generic panel had added to the form *record* was deleted by the next save of the structure. Both the read and the prune are now scoped.
The form's own details view asked for a translation of whatever severity a field carries, which for a field converted from a hand-written first-cut item can be anything. Same fallback as the record.
…d editor
Six things from the first real run of the builder:
- Settings rendered as a raw metadata editor, collapsed. They are three
known switches — two the server reads when a submission has failures, one
the driver app reads before it will submit — so they are checkboxes in an
unlabelled input group, and the panel opens.
- Type and status showed smart-humanize output ("Dvir"). They now come from
labelled option lists in fleet-ops-options, so DVIR reads as DVIR.
- Six inspection types were missing: pre-operational, post-operational,
safety inspection, maintenance inspection, damage assessment and annual
inspection.
- Every PowerSelect is wrapped in the fleetbase-model-select /
fleetbase-power-select / ember-model-select div so it renders as the rest
of the console does.
- The field editor opens as a right-side overlay over the form panel rather
than a modal, sized xs so the form stays visible beside it. It holds the
field in tracked state and writes through to the overlay's shared handle,
which is what the builder reads back on save.
- Frequency is dropped from the form. The column and the API field stay, but
nothing schedules an inspection from it, so the dropdown only asked the
author a question the product does not act on.
…sing focus Three from the second run: - Collapsing the Form Builder panel emptied the form. ContentPanel unrenders its body when closed, so the builder component was destroyed and the draft went with it; reopening rebuilt it from nothing. The draft now lives on the controller, which the collapse does not touch, and the builder renders @groups and reports changes through @onchange. The load is skipped when the controller already holds a structure, so reopening cannot overwrite unsaved edits with server state either. - Typing in a group's name or description lost focus after each keystroke. The iteration was unkeyed, so replacing the edited group object changed its identity and Glimmer rebuilt the input. Both loops are keyed on uuid, which every group and field is guaranteed. The two inputs also take their value once on insert rather than re-binding it, so a mid-word edit cannot move the caret to the end. - The settings checkbox description sat tight against its label; it now has mt-1.
The panel's Save was disabled for good. Without pojoResource the header falls through to `cannot-write @resource`, and the overlay carries a plain object rather than an Ember Data record, so the permission check had nothing to judge and denies by default. The field is a POJO, which is exactly what pojoResource is for. Required and editable now stack with spacing rather than sitting in a row.
Sharing one row with four buttons truncated every label to a few characters in a two- or three-column group — unreadable, and impossible to keep track of while building a form. The name takes its own line, with the required mark and the type pill beside it and the actions right-aligned underneath. The pill no longer truncates; the label is the only thing that gives way.
Five things reported while testing inspection forms in the console. Publish stayed in the header after a form was published, so the only thing pressing it could do was report that the form was already published. It now appears only while the form can be published, and as `type="success"`. Generate Link moves the other way: it needs a published form, so it appears at the same moment Publish leaves. A field's machine name came from `dasherize`, which rewrites spaces and underscores and leaves everything else alone — "Sidewall condition, offside rear" became `sidewall-condition,-offside-rear`. The name is an identifier: it travels as an item result's `item_key` and is what a report groups on. It is now a real slug, with anything that is not a letter or a digit acting as a separator. `frequency` is gone. It was inherited metadata: a column, an attribute and a filter that nothing scheduled an inspection from. It is removed from the v1 resource, the model's fillable and filter params, the report schema, the index column and query param, the details panel and the create defaults. The database column is deliberately left in place — the create migration has already run on live instances, and a dead nullable column is cheaper than editing a migration mid-test. A later migration can drop it. Type rendered through `smart-humanize`, so a form of type `dvir` read "Dvir". Option lists already carry the labels, so this adds a table cell that reads them: `table/cell/fleet-ops-option` takes the list name from the column's `optionsKey` and falls back to humanizing anything with no matching option, so a retired value is still legible. The index Type column and the details panel's type and status now use it. The Created column's own bug is in fleetops-data, where the model hands out a raw `Date` — fleetbase/fleetops-data#77 fixes that.
Three things reported while testing inspections in the console.
**The sheet.** An inspection form rendered as a bag of inputs: groups
were collapsible content panels, fields sat in a CSS grid, some had a
border and some did not, and failing a check grew that grid cell — which
stretched the row and left its neighbour floating in white space. It was
not a form anyone would want to work down.
It is now one sheet. Every field is a row of the same shape: what is
being checked on the left, the control on the right, a full-width
control under its label when it needs one. A failure opens its defect
block below its own row, so answering one line can never change the
shape of another. Groups are plain sections that are always open — a
panel that shuts invites an inspector not to read a line they have to
read — with the one number that matters in the header: what failed, or
what is still owed. A running total sits at the foot, and an unsafe-to-
operate answer is called out there. A failed row is marked down its own
edge so a long sheet can be scanned.
Pass / fail / not-applicable is one segmented control rather than three
loose buttons. Toggles get their own row like everything else. Number
fields have a placeholder and show their unit. All eleven field types
are rendered here now rather than five of them being handed to the
platform's `custom-field/input`, which brought its own label chrome and
made the sheet read as two interleaved forms.
One `inspection-sheet` component renders it everywhere — the console's
submission form, the read-only record, and the public link — so the
three cannot drift. The seeding and the `custom_field_values` payload
moved to `utils/inspection-answers` for the same reason.
Two modifiers replace `value={{...}}` on inputs that re-render on every
keystroke: `sync-value` writes a value in only while the field is not
being typed in, which is the focus bug from the form builder in another
guise, and `when-changed` runs something on change but not on insert.
**The public link opened a blank page.** The generated path was
`/inspection?…`, which matches the *authenticated* `console/:slug`
route. The public one is `/~/:slug`, a sibling of `console`, outside its
chrome and its auth gate — the same route ledger's invoice links use.
The path now carries the `~/`, and the page itself was rebuilt on the
shared sheet instead of the pass-fail-only checklist it had. Uploads are
off there: the file endpoint needs a session and a link does not have
one, so the row says where a photo can be added rather than offering a
button that would fail.
**A generated link existed only as a toast.** It went to the clipboard
and, once that was overwritten, there was no way to find out what had
been handed out, to whom, or whether it still worked. Links are now
listed — under the form's details and below the generate form — with the
URL to copy again, the vehicle and driver it was for, when it was made,
when it was last opened, whether it is active, expired, used or revoked,
and a way to revoke one.
That needs the link itself, which was stored only as a sha256. A new
migration adds an encrypted `token` column beside the hash; `token_hash`
is untouched and remains the unique index every public request resolves
through. A link is a capability URL — one published form, once, for one
vehicle, until it expires — not a credential, and being re-readable is
how share links behave. Links minted before this are listed without a
URL rather than pretending to have one.
Revoke always 404'd. The link resource sent `id` as `$this->id`, which on a Fleetbase model is the table's auto-increment column, while the revoke endpoint resolves a link by uuid or public id. So the list could show links and never act on one. It sends the public id now, and the lookup accepts a numeric id too so an already-open console still works. The links list was also stacking a gray-900 fill inside a gray-800 panel, which reads as a hole punched in the page. In dark the sunken surface is now the same gray-800, and separation comes from the border under a section header and a shadow on the card. The rows are tighter: they sit inside a panel that already pads. Putting the record's own details into a bare section went too far. Only the form's field groups needed to stop being collapsible panels — the details are a resource form like every other one in the console, so they are back in their content panel exactly as they were, keeping only `sync-value` on the two number inputs so the caret stays put mid-edit. The sheet itself now has an inset, so its section borders no longer land on the overlay panel's own edge and read as a double rule. How the field groups themselves should be laid out — whether the builder's grid survives — is still open, and nothing here presumes an answer.
Two faults, both between the page and its data.
The page asked for `fleet-ops/public/inspections/forms/{id}` and got a
404 whose body was Laravel's "There is nothing to see here" — no route
matched. FleetOps mounts its API at the application root:
`fleetops.api.routing.prefix` is null, which is why its consumable
routes are `/v1/...` and its internal ones `/int/v1/...` rather than
sitting under an engine name the way ledger's do. The public routes
follow it, so the namespace is `public`. Verified against a running
instance: the endpoint now answers 200 with the form, its four groups
and the link's driver, vehicle and expiry.
Behind that sat a second fault that would have shown an empty sheet on
a page that finally loaded. A form's structure arrives in two shapes:
`field_groups`, whose fields come in a sibling `fields` array joined on
`category_uuid`, and `grouped_fields`, which nests them. The normalizer
preferred `field_groups` whenever it was present — but a public payload
omits `category_uuid`, so the join matched nothing and every group came
back empty. It now takes whichever shape actually produced fields, and
falls back to `field_groups` when neither did, so a form whose groups
are laid out but still empty keeps them in the builder.
Checked against the live payload for all four shapes: public, internal,
groups-without-fields, and nothing at all.
The author's `grid_size` survives — for the fields that stay compact. A field that needs room leaves the grid entirely and becomes a full-width band under it: a pass-fail that has failed and now owes a severity, a comment and photos, or a note, upload or signature that never fitted a column. That is what stops one answer changing the shape of another, and it needed no compromise on the authoring intent: after promotion there is no neighbouring cell left to stretch. A group header now carries one dot per field in answer order — filled for a pass, red for a failure, grey for not-applicable, hollow for unanswered, amber for a required answer still missing or a failure still owing its comment or photo. The right of the header says what is outstanding, or how the group is laid out when nothing is. A dot is neutral rather than green for an answered field that is not a check: a toggle that is off has not passed anything. Pass, fail and not-applicable are one segmented control, and so is severity, whose chosen grade fills red — it is a grade of failure, not a neutral pick. Unsafe to operate is a bordered pill, because it is the gravest thing on the sheet. Photos are 60x44 slots with a dashed slot inviting the next one, and a single upload or signature shows the same slot beside its label rather than a bare filename. The foot is four tallies and, when something is wrong, a banner that names the field instead of counting it — the unsafe failure with its severity, and the first required answer still missing — each with a jump that scrolls to that field and holds it for a moment. Jumping is by id rather than a held element, because a promoted field moves between the grid and its band as the answer changes. Layout is measured against the sheet, not the window: it renders in a 600px overlay panel on a wide screen and full width on a phone, so a viewport media query would collapse the columns in exactly the wrong places. Container queries drop 3 and 4 columns to 2 below 660px and to one below 420px. The same sheet renders in all three places it is read: the console's submission form, the read-only record, and the public link. The link page's own details and sign-off sections were rebuilt in the same language. Checked by rendering the real LGV Daily Pre-Trip payload from a running instance through the templates' own markup and stylesheet, in both themes, against the concept.
`fieldMarker`, `isPromoted` and `ROOMY_FIELD_TYPES` were never written to `utils/inspection-answers`. A scripted edit that was meant to insert them matched nothing and reported nothing, so the group component and `summarize` both called a function that did not exist. Every sheet threw on render: the public link, and the console submission form the moment a form was chosen. They exist now, and three checks were run against the real module rather than a restatement of its rules — which is what let this through, since the render harness reimplemented the logic instead of importing it: - every export the sheet imports resolves, exercised end to end on a live LGV Daily Pre-Trip payload; - every `this.x` in all fourteen inspection templates resolves to a member of its component; - every translation key the inspection code names exists.
Seven things reported against the sheet. Promotion moved a field to the end of its group, so failing a check re-sorted the group and the fields after it jumped up past it. A field that needs room now stays exactly where it was authored and spans the full width of the grid instead. It is still alone on its row, so it still cannot stretch a neighbour, and nothing moves. A failure's own comment box, photo slots and segmented controls kept the neutral border while everything around them turned red. They take the failure's edge colour now, so the block reads as one thing. The sheet was forcing a height and a font size onto every `.form-input`, `.form-select` and power-select trigger, which made an input on this screen shorter and tighter than the same input anywhere else in Fleetbase. That is gone; controls keep the console's own sizing, and the segmented controls match a real control's height rather than setting their own. Groups sit further apart, and the group block has padding on all four sides — the last group used to touch the divider above the tallies, and the public link's details and sign-off panels had no bottom padding at all. Every field is stacked now, label above control, whatever the column count. A one-column group used to put the label beside the control, which read as two different forms interleaved. The "2 col · 1 promoted" line is gone. It described the layout to someone who can see the layout. The header keeps only what is outstanding, and only when something is. Checked by rendering the real LGV Daily Pre-Trip payload through the sheet's markup and stylesheet in both themes, and by re-running the three checks: every template reference, every local import and every translation key resolves.
A failed pass-fail used to widen into a full-width band in place. That still re-flowed the group every time a check failed. The failure now keeps its cell, and its severity, unsafe flag, comment and photos open in a flyout anchored to it. The sheet's layout depends on the form alone; answering a field can never change it. Notes, uploads and signatures keep their full-width band, because the form decides their size. The read-only record keeps each defect inline, where it is the most important thing on the page. How the flyout behaves: - Choosing Fail opens it, with no second click, and focuses the comment. - A caret points at the Fail button, and the field is ringed with an outline, which takes no space, so opening it moves nothing either. - It closes on Done, its close button, Escape, or a press outside it and its field — never on blur, because focus leaves for the native photo picker. Escape and Done return focus to the Fail button. - Closing is always safe: every answer is saved as it is typed. So Done still closes when a required comment or photo is missing, and the chip it leaves says what is owed in amber instead of trapping the inspector. - Only one is open at a time. Switching an accidental Fail back to Pass keeps the comment and photos. - On a sheet narrower than 520px — the public link on a phone — the same content comes up as a bottom sheet with a backdrop, mounted in the root wormhole so it is pinned to the screen rather than to the sheet. Placement is done here rather than with the platform's Floating, which positions once and never follows, and FleetOps does not declare floating-ui. The flyout lives in a layer inside the sheet, so it scrolls with its field for free and needs no scroll listener. It goes below the field unless only the space above can hold it: whatever hangs below can always be scrolled to, but a panel pushed above the start of the sheet could not be reached at all. It is revealed once, by the least scroll that brings it fully into view with a margin, or its title if it is taller than the view. The reveal is worked out from layout after the opening animation finishes: measured mid-animation it was clamped against a briefly shorter scroll area and stopped exactly the animated 4px short. An invisible 8px spacer below the flyout gives that margin room at the very end of the page, since an absolutely placed box extends the scroll area but its margin does not. A defects tray at the foot of the sheet lists every failure with its severity, field and evidence, and each row reopens that flyout. It replaces the separate unsafe banner. Verified by loading the shipped modifier and stylesheet into a real browser and checking placement, focus, scrolling with the field, dismissal, teardown, both sides of the flip rule, the reveal and its margin, a flyout taller than the view, and the end of the page; and by re-running the template-reference, local-import and translation sweeps.
…e driver pill - vendor/pill and vendor/summary wrappers existed for integrated vendors only, so an order whose facilitator is a plain vendor threw "Attempted to resolve vendor/pill" and crashed the details view. - The hidden Trailers and Devices columns on vehicles and trailers rendered "[object Array]" once shown; they now use cell/resource-list, one pill per related record. - Contact and customer popovers showed raw type strings such as "customer-fleet-ops:contact"; they now read "Customer" or "Contact". - The vehicle details driver pill received a bare belongs-to proxy; it now gets the loaded relation so an unassigned driver renders static.
They were written with the previous commit but a vendor/ ignore rule kept them out of the index; force-added so vendor/pill resolves.
…ell, titleize column labels - The trailer, inspection and long-offline badge rules leave the engine stylesheet; ember-ui's palette now owns every Fleet-Ops status. - cell/resource-list pills draw a 1.25rem circle with the name centred beside it and a hover box that stays inside the cell. - Column labels read in title case: Device Attached, Driver Assigned, Trailer Attached, the trailer columns and Attached Asset.
Resource identity system: pills, one-line identity cells, hover summaries and select options
Acknowledging or snoozing a Radar item failed with "Unknown column 'public_id'". Fleetbase\Models\Alert uses HasPublicId, whose creating hook checks alerts.public_id for a collision, but the core migration never created the column, so every alert insert failed: Radar's state rows, and the low-stock, sensor and device event alerts FleetOps already raises. Add it, guarded so it is a no-op wherever core-api adds it itself.
- The header's search, view toggle, refresh and new-notice controls sit at the right edge again: the hub header aligns its children to the start at 768px and up, which shrank Radar's row to its content. - Search, toggle and buttons share one 2rem height, and the search text clears the magnifying glass; ember-ui's form-input-sm padding was winning on specificity. - Last sync and the snooze toast format times with date-fns instead of the browser locale, which rendered Arabic digits. - Rows get a real checkbox, so selection and bulk actions work; the Font Awesome regular square never rendered. - Rail buttons, including the snooze dropdown, line up at one height, the open-record icon is smaller, and saved views are shorter with the save button keeping its padding. - Keyboard hints, the count and paging float in a container pinned to the bottom of the viewport. - Rows, decision cards, handover cards and brief links open the record in a context panel instead of navigating away from Radar. - The select modal renders options with the resource select-option components.
PHP CI installs core-api 1.6.55 from composer.lock, which predates the alert snooze and assignment API. RadarItemStateTest died on "undefined relationship [assignedTo]", and the same calls would fail wherever that core is deployed. RadarItemState now writes the state columns itself (acknowledge, snooze, wake, assign, plan, resolve), resolves user names from one uuid lookup instead of the new relations, and reads dates through RadarRules::carbon rather than model casts. The controller routes every state change through it. Coverage was far short of the gate: the probe tests replace the controller's loaders and plumbing, so none of that ran. A new database test drives the real controller over in-memory SQLite: every source loader, handover orders, shift extension, company membership, notices, score history and the failure paths. Edge-case tests cover the remaining branches in the rules, brief, agenda and state helpers. All Radar files are at 100% lines and methods, and the full suite passes against core-api 1.6.55.
- Ember CI: merge the duplicate .fleet-ops-radar-body selector stylelint rejected. - Row checkboxes show as checked: in-array takes the item first, and Radar passed the list first, so every row read as unselected. - Shift-click selects or clears every row between the last row toggled and this one. - The keys and paging footer is a full-width bar flush with the bottom and side edges, the same 39px height as the sidebar's attribution footer so the borders line up. - Last sync shows the date as well as the time. - Send PIN asks how to send it, offering only the channels the link's recipient can receive, as the inspection form's link list does. It used to post without `via` and fail validation. - Picking a vehicle for a driver no longer shows "-". ModelSelect only resolves a uuid on first render, and the modal fed the picked uuid back in; it now keeps the chosen record.
… details routes Radar opened records through each resource's panel.view, and most of those registered only an overview tab with no header, title or buttons, drew the default header rule above the tab list, and opened narrower than the route. Work order, maintenance, schedule, part, equipment, issue and device panels now carry their route's header component, tabs and action buttons; inspection submissions and forms gain panels. Header menus are shared between routes and panels so they cannot drift. Radar loads records by public id with a single-record query, as the details routes do, which stops Ember Data's RecordIdentifier type error. Closing an issue from Radar uses the close-issue modal, and selected inspection links can be revoked in bulk. Adds the missing issue timeline, panel header and card re-exports the host panel needs.
…ze and fitted geofence maps Driver, vehicle, trailer, fleet, place, contact, customer, vendor, fuel report, service area and zone context panels now open with their details route's title, tabs and actions, and without the header rule above the tabs. The driver, vehicle, trailer and fleet header menus move into their action services so the route and the panel share one definition. New tabs: driver activity, fleet vehicles and drivers, vendor personnel, vehicle inspections. Opening a facilitator from the orders table froze the page with no error: the index payload normalizes it as a bare facilitator record, and the polymorphic descriptor handed it back to registry.open, which resolved it to itself in an endless promise loop. The base now opens the concrete descriptor with the concrete record. Service area and zone panels use a geofence map that re-measures Leaflet once the overlay settles and fits the whole boundary, so the map is no longer grey or off-centre, and is taller. The zone's description field shows its description. Work order, maintenance and schedule forms can clear their target, assignee and type selectors. The Radar open-record test follows the panel behaviour.
…-design-ad15e9 Replace the Resources Hub with Radar
core-api 1.6.62 gives custom fields a public id, and the release branch installs it because it has no lockfile. Test fixtures that created a custom_fields table without the column failed on insert, which broke PHP CI in the inspection field contracts. The column is nullable, so older core-api versions are unaffected.
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.
Summary
Planned release work
v1inspections API#319 and #320 target this release branch for review before the release is merged to
main. Companion PRs: fleetbase/fleetops-data#77, fleetbase/fleetbase#652 and fleetbase/postman#60.Verification