Skip to content

Dvonanderson/feature/ember 4 12 upgrade - #867

Merged
dvonanderson merged 64 commits into
ember-migrationfrom
dvonanderson/feature/ember-4-12-upgrade
Sep 2, 2026
Merged

dvonanderson merged 64 commits into
ember-migrationfrom
dvonanderson/feature/ember-4-12-upgrade

Conversation

@dvonanderson

@dvonanderson dvonanderson commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

close
#868
#866
#855
#854
#853

Summary

Upgrades the app from Ember 3.x to Ember 4.12 LTS, resolving all breaking changes and deprecated APIs. Migration proceeded incrementally through each LTS release (3.16 → 3.20 → 3.24 → 3.28 → 4.12) rather than jumping directly, surfacing and fixing breaking changes in smaller batches.

Status: 398/398 tests passing, production build succeeds, verified against multiple random test-order seeds.

Breaking changes fixed

Template helpers removed in Ember 4

  • {{#with expr as |v|}}{{#let expr as |v|}}{{#if v}}...{{/if}}{{/let}} across ~40 templates. Its presence in md-nav-sidebar (rendered on every page) was the root cause of Records/Contacts/Dictionaries/Import tables not rendering at all.
  • {{partial dynamicName}}{{component dynamicName ...args}} in ember-leaflet-table.

this-property-fallback removed

  • All bare template property references now use this. explicitly (model → this.model, etc.) — ~250 references across ~60
    app files, plus additional test-file references caught later by actually running the suite (ember-template-lint can't
    statically check hbs literals embedded in .js files).

this._super() in native class lifecycle methods

  • Replaced with super.methodName(...arguments) in route hooks (setupController, afterModel, etc.) across ~37 route files —_super() only works in classic init().

Implicit service injections removed

  • Added explicit @service decorators where services (e.g. store) were previously auto-injected.

ember-cli-bootstrap-datetimepicker removed

  • Replaced with native HTML5 date/month/datetime-local inputs in md-datetime. (Follow-on: several tests asserted the old moment.js-formatted display text; updated to match native input .value semantics — no seconds, no timezone offset, no locale month names.)

Dependency & patch fixes

  • ember-in-element-polyfill: patched version threshold ('10.0.0' → '3.20.0'); resolutions added to cover nested copies.
  • ember-pouch: patched a bare Ember.libraries.register() call that halted vendor.js execution under Ember 4 (surfaced as a misleading "Could not find module ember-resolver" error).
  • ember-resolver: upgraded to v11.0.1, import path updated to ember-resolver/index.
  • ember-models-table: migrated to v5 (see below).
  • ember-power-select: pinned to ^7.2.0 for v5 models-table compatibility.
  • ember-cli-template-lint: removed (superseded by standalone ember-template-lint).
  • ember-composability-tools, ember-simplemde: patched for removed @ember/utils tryInvoke and @ember/component/text-area respectively.
  • ember-leaflet: patched — its own leaflet-map.hbs relied on this-property-fallback (emberLeaflet/mergedComponents used unprefixed), broke every map consumer.
  • @ember/string deprecation (ember-tooltips): silenced via registerDeprecationHandler.

ember-models-table v5 migration

Theme system changed from manually-instantiated classes to injectable services; row/table components moved to a namespaced, Glimmer-based component tree. Required rewriting:

  • md-record-table and md-edit-table's custom columns/themes for the new API (also restored a .md-record-table CSS class lost in the Glimmer rewrite, and wired the Edit button to actually trigger row-expand — EMT v5 threads @expandRow/@collapseRow into custom column components for free, it just wasn't being used).
  • feature-table/leaflet-table-row (leaflet map's attribute table) — rewritten from a pre-v5 classic-constructor pattern to
    a proper Glimmer wrapper + themed service, and fixed a hand-rolled table-body loop still using v3/v4's lowercase
    contextual component names (body.row instead of v5's body.Row).

Other structural changes

  • Converted classic Ember classes to native JS classes (with @classic decorator where still needed).
  • Removed jQuery from components, routes, and services where possible.
  • Converted mixins to plain utility functions.
  • Fixed ember-concurrency v5 breaking changes.
  • Eliminated 124 of 132 computed-property.override deprecation warnings by distinguishing real "derived-by-default,
    externally-overridable" properties (given a getter/setter pair) from redundant duplicate values (deleted).
  • CI workflow hardened: fixed a timezone-dependent test failure (pinned TZ: America/New_York to match the app's local-time assumptions), rebuilt the test-running step for clearer failure output.

Bug fixes surfaced along the way

A long tail of functional bugs found and fixed during the migration (taxonomy collection screen, ScienceBase publish tree drag/reparent, checkbox selection in tables, ITIS proxy/derived data, fiscal year saving, lineage route rotation, keyword/citation panels, profile switching visibility, sync-menu errors, import checkboxes, and others) — see individual commits for detail.

dvonanderson and others added 23 commits August 21, 2026 14:56
Bumps ember-source/ember-data/ember-cli to ~4.12.0 and resolves every
blocker needed to get the app booting and rendering on 4.12:

- ember-resolver 7 -> 11.0.1, updated import path (ember-resolver/index)
  and wired app.js to use the app/resolver.js wrapper
- Yarn patch for ember-in-element-polyfill: the nested 0.1.3 copy pulled
  in via ember-leaflet-layer-control hardcoded a placeholder Ember
  version threshold ('10.0.0'), throwing "Class constructor
  InElementTransform cannot be invoked without 'new'" during template
  compilation. Patched threshold to '3.20.0' and forced resolutions so
  every nested copy (0.1.3/0.2.x/1.0.1) resolves to the patched build.
- Removed ember-cli-template-lint (incompatible constructor-style AST
  plugin under Ember 4's template compiler); linting already runs via
  standalone ember-template-lint.
- Removed unused ember-cli-bootstrap-datetimepicker devDependency
  (already replaced by native inputs in md-datetime).
- Added @ember/string as a real dependency (ember-data 4.12 peer dep)
  and silenced the ember-tooltips-sourced @ember/string deprecation
  (id: ember-string.add-package) via registerDeprecationHandler.
- ember-models-table 3.4.0 -> 5.0.0 and ember-power-select downgraded
  to ^7.2.0 (ember-power-select-with-create -> ^2.0.0) for v5 compat -
  version bump only; the v5 component/theme rewrite itself is a
  separate, larger follow-up (v5 replaced the classic-component +
  class-based theme system with a Glimmer/tracked-based one, which
  every local table wrapper and sub-component override needs to be
  re-architected against).
- Fixed .gitignore: `.yarn/*` was blanket-ignoring `.yarn/patches`,
  which silently drops any yarn-native patch from version control.

Verified live: Ember 4.12.4 / Ember Data 4.12.8 in the runtime debug
banner, dashboard renders fully with no boot-time errors.

Known follow-up (not yet done): Records/Contacts/Dictionaries and
every other ember-models-table-based list view will not render until
the v5 component rewrite lands - see next commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Converts the whole local table wrapper hierarchy from classic
components (extending ember-models-table's old class-based API) to
composition of native Glimmer components, matching v5's rewrite from
@ember/component to @glimmer/component with tracked/args-based state.

- app/services/emt-themes/mdeditor-bootstrap3.js (new): our Font
  Awesome icon theme, now a service extending the addon's own
  emt-themes/bootstrap3 service instead of a manually `.create()`'d
  class instance.
- md-models-table: thin wrapper injecting the theme service and
  forwarding args to <ModelsTable>, replacing the old @classic
  extends-Table + Theme.create()/setOwner() pattern.
- control/md-record-table: rewritten as composition instead of
  inheritance - computes the checkbox/actions columns as getters and
  renders <MdModelsTable> in its own template, instead of extending
  Table and reopening it with classic observers. Row selection is
  synced back onto each record's selectProperty flag (e.g. `_selected`,
  which the Export page depends on) via v5's @onDisplayDataChanged
  callback, replacing the old selectedItems.[] observer.
- control/md-record-table/buttons/filter (the "Delete Selected"
  button): converted to a native Glimmer component reading
  @selectedItems/this.args instead of classic curly-attrs. v5's
  selectedItems is an internally-owned TrackedArray, not a live alias -
  componentForFilterCell must be a real component reference
  (ensureSafeComponent doesn't do container-lookup the way plain
  {{component "string"}} does for this specific field), so it's now
  passed as a direct import rather than a string path.
- control/md-record-table/buttons: fixed a latent bug (missing `column`
  getter - `this.column` was referenced but never defined) surfaced by
  v5's cell.hbs now correctly passing @column.
- control/md-record-table's checkColumn now uses v5's built-in
  models-table/themes/default/{row-select-checkbox,row-select-all-checkbox}
  (imported directly, not by string path - same ensureSafeComponent
  constraint) instead of our hand-rolled check/check-all components,
  which are deleted. Bonus: row-select-all-checkbox now has a proper
  3-state (all/some/none selected) icon.
- control/md-edit-table, control/md-select-table: same
  inheritance-to-composition conversion. md-edit-table's `editRow`
  fallback to the addon's own expandRow (unreachable in practice - all
  3 real call sites already pass an explicit `editRow` callback) was
  dropped since v5's non-block <ModelsTable> invocation doesn't expose
  expandRow/collapseRow to an external caller.
- control/md-pouch-record-table: now a template-only wrapper.
- Deleted local overrides of ember-models-table's old v3.4.0 internal
  paths (models-table/{cell-content-display,row-expand,table-body},
  md-models-table/components/{check,check-all}) - v5 restructured these
  under themes/default/ with a different API, and the addon's stock
  versions now cover what we need.
- New control/md-record-table/cell-content-display component
  replicates the old truncate/wordLimit/break cell display behavior
  (no v5 equivalent exists for this), wired onto the ~4 column configs
  across 3 settings controllers that used it.

Verified: Records dashboard renders, checkbox selection works with
correct 3-state select-all icon, Show/Edit/Delete/Preview JSON row
actions render and are wired to @record/@column/@index correctly.

Known remaining issues (not yet resolved, see next message):
- componentForFilterCell ("Delete Selected" button) does not render
  despite the column config being correct at the JS level - root cause
  not yet found.
- dataColumns=(compute (route-action 'x') ...) silently returns
  undefined app-wide (57 files use this pattern) - a separate,
  pre-existing compute/route-action helper incompatibility, unrelated
  to this table rewrite.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
app/helpers/route-action.js (our local override of
ember-route-action-helper, shadowing the addon's own helper of the
same name) called the resolved route action via
`run.join(handler, action, ...args)`. Under Ember 4.12's rendering
internals, this helper is evaluated at a point where no Ember run loop
is active, so run.join schedules the call and returns undefined
immediately instead of the action's real return value - completely
silently, since our override already wraps the whole thing in a
try/catch that swallows failures.

This broke every `(compute (route-action 'x') ...)` expression
app-wide (57 files) used to fetch a value synchronously from a route
action - most visibly, `dataColumns=(compute (route-action
'getColumns') ...)` on every dashboard table, which is why Title/Type/
ID columns were missing (only the checkbox + Actions columns the local
component code adds were showing).

Root-caused by manually replicating the call chain piece by piece:
calling the bound route action directly worked fine, calling it through
route-action's returned closure (with run.join) did not - narrowing it
to that one call.

Fix: call the action directly (`action.apply(handler, args)`) instead
of routing it through run.join, which was never necessary here - this
is a synchronous getter-style route action, not something that needs
run-loop coalescing.

Also bumped ember-composable-helpers 2.4.0 -> 5.0.0 to match the
nested copy ember-models-table@5.0.0 already pulls in (removes a
duplicate-version situation, though it turned out not to be the actual
cause of the bug above). This removes the `contains` helper (redundant
with the now-added `includes` helper); updated the one file that used
it (object/md-profile/custom/template.hbs, 2 call sites).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Following up the ember-models-table v5 rewrite: the "Delete Selected"
button (componentForFilterCell on the Actions column) mounted its
component instance (constructor ran fine, no errors) but rendered
completely empty - not even entering the template's own {{#if}} block.

Root cause: this app uses pods, where a classic component's template
is only paired with its JS class via resolver lookup by name
(component:x -> template:x). Importing FilterComponent directly and
passing the class reference bypasses the resolver entirely, so
Ember has no template to render for it at all - the class runs, but
it's templateless. This is different from ember-models-table's own
components (RowSelectCheckbox/RowSelectAllCheckbox), which ship with
modern co-located templates baked in via setComponentTemplate() at
the addon's own build time, so a direct class reference works fine
for those.

Root-caused by adding a debug getter directly into
row-filtering-cell.ts confirming @column.componentForFilterCell truly
was the right class, then adding a constructor log to FilterComponent
confirming it mounts without error - narrowing it to "instantiates
fine, never renders content", which is the signature of a missing
template pairing.

Fix: componentForFilterCell for our own pods components
(control/md-record-table/buttons/filter) is now the resolver path
string ('control/md-record-table/buttons/filter'), not a direct
import, in both control/md-record-table and control/md-edit-table.
ensureSafeComponent's string handling does a real container lookup
(lookupCurriedComponentDefinition), which correctly pairs template +
component for pods-resolved components - the earlier attempt to use a
string for the addon's OWN row-select-all-checkbox failed only because
that specific string was the wrong (pre-v5) path, not because strings
don't work here.

Verified live: selecting 2 rows shows "Delete Selected", deselecting
back to 0 hides it, no console errors.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ember/test-helpers@1.7.3 (paired with ember-qunit@4.6.0, qunit-dom@0.9.2)
predates Ember 4 support entirely - every test run failed immediately
with "Ember is not defined" / "Could not find module @ember/test-helpers",
the same class of vendor.js-halt symptom already fixed for ember-pouch.

- Bumped @ember/test-helpers 1.7.3 -> ^2.9.4 (the version explicitly
  listed as compatible in ember-basic-dropdown's peer requirements,
  and satisfies ember-qunit@6's own ^2.4.0 peer dep - the least
  disruptive option rather than jumping to the 5.x tip)
- ember-qunit 4.6.0 -> ^6.2.0, qunit-dom 0.9.2 -> ^2.0.0 to match
- Added qunit as an explicit devDependency (was only ever pulled in
  transitively; yarn now warns without it)
- tests/index.html was missing the #ember-testing-container /
  #ember-testing fixture divs entirely (not just misconfigured -
  absent). @ember/test-helpers@2.x's appendContainerElement requires
  this and throws "Cannot read properties of null (reading
  'querySelector')" without it. Added the standard Ember CLI blueprint
  markup.

Result: test runner now boots and executes real tests (confirmed
passing tests execute, e.g. control/md-record-table/buttons/show).
Remaining test failures are legitimate content issues - old test files
written against the pre-rewrite table component APIs (curly
`data=`/`columns=` args, `this.deleteSelected` etc.) that need
updating to match the v5 rewrite, not infrastructure problems.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-power-select

Root package.json declared ember-concurrency ^5.1.0 while ember-power-select
depends on ^2.0.0 || ^3.0.0, so yarn installed two incompatible copies.
Ember CLI's addon-tree build registers only one 'ember-concurrency' module
under the app's global module namespace, and the root v5.2.0 copy (which no
longer exports restartableTask/dropTask/enqueueTask/keepLatestTask at
runtime) won out — shadowing the nested v3.1.1 copy that
power-select.ts's `@restartableTask` decorator needs. This crashed
`_applyDecoratedDescriptor` with "n is not a function" during module load
for every acceptance test touching a power-select-based form, which then
left shared test state (the testing container/app instance) uncleaned and
manifested as DOM content accumulating across unrelated subsequent tests.

Downgrading the root dependency to ^3.1.1 (matching ember-power-select's
own nested copy) lets yarn fully dedupe to a single shared install. The
app's own task() usages (hash-poll, profile, schemas, spotlight) still use
async-arrow syntax, which v3.1.1 now handles automatically via its own
addon `included()` hook, so the manual async-arrow-task-transform babel
plugin registration in ember-cli-build.js (which required the now-removed
v5-only file) is no longer needed and has been dropped.

Verified: full test suite passes 236/404 (up from 192/404), zero remaining
"n is not a function" crashes, and the record/new, dictionary/new, and
md-breadcrumb suites that previously died on module load now fail (if at
all) on ordinary per-test assertions instead of cascading crashes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ember-cli-bootstrap-datetimepicker was already removed from package.json
earlier in this migration, but input/md-datetime/template.hbs still
invoked the now-nonexistent `bs-datetimepicker` component -- broken not
just in tests but in the live app, for every date/datetime/month/year
field (citations, distributors, quality reports, date ranges, fiscal
year, md-month).

Replaced the widget with a plain <input> whose type (date/month/
datetime-local/number-for-year) is derived from the component's existing
`format` string, reusing the existing dayjs-based formatValue() writeback
logic untouched -- only the widget and its value binding changed. Dropped
the now-dead jQuery DateTimePicker lookup (picker()/hidePicker()) and
widget-only config (calendarIcons, useCurrent, showTodayButton) that have
no native-input equivalent.

Also fixed two corruption bugs from the previous stale-test-file sweep,
which had blindly added `this.` prefixes inside quoted string literals
(not just bare template value positions): md-fiscalyear's inline hbs had
`valuePath="this.start"` instead of `valuePath="start"`, and
md-indicator/related's had `route="this.dictionary...."` instead of
`route="dictionary...."`.

Full suite: 316 -> 321 passing (77 failing). The remaining bs-datetimepicker
-adjacent tests now hit their own separate pre-existing issues instead of
the crash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ember-composability-tools (pulled in transitively by both root ember-leaflet
4.0.2 and the nested ember-leaflet 3.0.18 via ember-leaflet-layer-control,
as versions 0.0.12 and 0.0.11 respectively) imports `tryInvoke` from
`@ember/utils`, which was removed in Ember 4.0. This crashed module load
for any component using ember-leaflet's Parent/Child composability mixins
(LeafletMap, LeafletDraw, FeatureGroup, LayerGroup, etc).

Patched both installed versions in place (yarn patch) with a local
tryInvoke shim rather than upgrading the dependency, since newer major
versions pull in ember-cli-babel@8/ember-element-helper combinations that
conflict with this app's existing ember-cli-babel@7 pin and, in testing,
broke ember-leaflet's own map initialization silently (no thrown error,
`.leaflet-container` just never appeared) -- likely a behavioral mismatch
with the older ember-leaflet versions this app depends on, which declare
`ember-composability-tools@^0.0.12`/`^0.0.11` specifically. The patch fix
keeps the exact same addon version/behavior, only replacing the removed
API call.

Also fixed a bare `mapAttribution`/`layers` this-property-fallback bug in
feature-group-test.js's inline hbs (same class of issue as the prior
stale-test-file sweep, but this one was masked by the tryInvoke crash so
the sweep never saw it), and added the missing qunit-dom `setup(QUnit.assert)`
call to tests/test-helper.js -- qunit-dom was a devDependency but never
actually registered, so every `assert.dom(...)` call in the suite threw
"assert.dom is not a function".

The `.leaflet-container` never renders symptom in feature-group/geojson-layer
/leaflet-draw tests persists unchanged with this patch (same behavior as
the pre-patch nested-version state minus the crash), confirming it's a
separate, pre-existing issue unrelated to this fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ember/component/text-area was fully removed in Ember 4.0 (not just
deprecated), but ember-simplemde's only component directly imported and
extended it purely to get a <textarea> element for the wrapped SimpleMDE/
codemirror library to mount onto -- none of TextArea's own value/event
bindings were actually relied on, since all editing happens through the
SimpleMDE instance operating on the raw DOM element after didInsertElement.

Patched the addon (yarn patch, single small file) to extend plain
Component with tagName: 'textarea' and explicit attributeBindings for
disabled/maxlength/spellcheck/placeholder, preserving the same DOM output.

This was silently crashing every render of input/md-markdown-area (and
anything that embeds it, e.g. object/md-constraint's "Handling
Description" field) with "Could not find module @ember/component/text-area"
-- confirmed live via the dev server, where navigating to a record's
Constraints tab left the app on the previous route with the error only
visible in the console (Ember's render error boundary swallows it
silently in the UI).

Also updated object/md-constraint's test expectations, which were stale
independent of this fix -- the component now renders help text under
"Legal Access Constraints"/"Use Constraints" that the test's expected
string predates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ests

- ember-simplemde patch: also replace the removed @ember/polyfills `merge`
  export with `assign` (still exported) in the buildSimpleMDEOptions call.
  Missed in the first pass since the addon crashed on the TextArea import
  before this line was ever reached.

- 6 initializer/instance-initializer unit tests used the old ember-cli
  blueprint pattern of `Application.create()` from bare '@ember/application',
  which has no Resolver and crashed `buildRegistry` under ember-resolver 11.
  Switched to importing the app's own class (`mdeditor/app`, which already
  wires config.modulePrefix + Resolver) and passing config.APP, matching
  how tests/test-helper.js boots the real app.

- tests/helpers/destroy-app.js: `application.__container__` is undefined
  when torn down immediately after `deferReadiness()` (no default instance
  has booted yet), so the store lookup threw in afterEach. Made it
  optional-chained; falls through to a plain application destroy.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
tests/helpers/create-contact.js explicitly set title: and icon: in the
properties hash passed to store.createRecord('contact', ...), but both
are read-only computed properties on the Contact model (derived from
json.name and json.isOrganization respectively -- values already present
in the same fixture, producing the exact same result). Ember 4 asserts
when a value is set that would silently shadow a computed property
without a setter; older Ember just let it happen quietly. Dropping the
redundant keys lets the model's own computed properties compute normally.

Fixes 4 failures across control/md-contact-title, control/md-contact-link,
object/md-distributor/preview, and the contact-copy acceptance test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sites

The previous commit (bb344ad) removed title/icon from the shared
createContact() test fixture entirely, but that fixture is also used by
tests that assign it directly to the contacts service (`cs.set('contacts',
createContact(2))`) as plain EmberObjects rather than real Contact model
records -- those never had a conflicting computed property, so they
actually relied on the fixture carrying title/icon literally. Stripping
them broke object/md-party, object/md-party-array, object/md-process-step,
and input/md-select-contacts (contact selected) with a new crash
(contacts.js's contactsCodes computed tries `icons.get(defaultIcon)` as a
fallback when item.icon is falsy, but `icons`/`defaultIcon` were never
defined on the service -- a separate, pre-existing latent bug only
reachable when icon is actually missing).

Restored the fixture's title/icon fields, and instead added
createContactRecord(store, fixture) to only pass json/contactId through
to store.createRecord() -- the four callers that create real store
records (control/md-contact-title, control/md-contact-link,
object/md-distributor/preview, contact/copy acceptance test) now use it
instead of calling store.createRecord() directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
settings/custom-profile services kick off store.findAll() in their own
init() (fire-and-forget, resolved later); base-test's dirty-hash test
triggers a save-cycle that schedules adapter/serializer work. All three
tests returned synchronously (or without a final settled()) while that
work was still pending, so ember-qunit's teardown destroyed the store
before the callback ran, throwing "store instance has already been
destroyed" from inside store.serializerFor().

Added `await settled()` after the assertions in each test so pending
store work resolves before the test (and its owner) tears down.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…cope ref

input/md-input and object/md-keyword-list component tests used the old
Ember testing jQuery integration (this.\$(...)), which no longer exists
under @ember/test-helpers 2.x -- replaced with find()/findAll() and plain
DOM property access (input.required, input.maxLength, input.value, etc.
instead of jQuery's .prop()/.val()/.hasClass()).

Also fixed a bare `markdownValue` this-property-fallback reference in
md-markdown-area's test, missed by the earlier sweep since it was masked
by the ember-simplemde text-area crash at the time.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
input/md-select's template always rendered <PowerSelect>/<PowerSelectWithCreate>
regardless of the `multiple` arg. Those single-select components don't
recognize @multiple at all (confirmed by checking ember-power-select's own
PowerSelectArgs interface -- no `multiple` field exists there), so passing
`@multiple={{this.multiple}}` was silently ignored. True multi-select tag
rendering requires the dedicated PowerSelectMultiple/
PowerSelectMultipleWithCreate components (from ember-power-select and
ember-power-select-with-create respectively), which the shared md-select
layout never used -- `theComponent` in md-codelist-multi/component.js
correctly computed the right component name for this but was dead code,
never referenced by the template.

This was a latent bug from this migration's earlier ember-power-select
v8.12.1 -> v7.2.0 downgrade (done for ember-models-table v5 compatibility)
-- worth checking whether the app's originally-installed v8 either
supported @multiple directly on PowerSelect or was never actually
exercising true multi-select rendering either.

Added multiple/create branching to md-select's template covering all four
PowerSelect variants, reusing the exact same args and block content (only
the component tag changes; PowerSelectMultiple(WithCreate) extend the same
Args interface). Fixes input/md-codelist-multi (all 4 tests) and
input/md-select-contacts "contact selected", which were previously firing
zero-tag output for every multi-select field app-wide.

Also fixed a stale expected-string in md-process-step's test (the party
table's contact-select cells now correctly show their placeholder text).

md-party/md-party-array still fail on the pre-existing "ambiguous helper
passed as named argument" bug also affecting md-lineage/md-locale/
md-transfer/etc -- investigated extensively (traced to Ember's compiler
throwing on `<Component @arg={{bareHelperName}}>` patterns, per
ember.debug.js's resolveOptionalHelper) but couldn't pin down the exact
template triggering it; left as a known remaining issue.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… fixtures

Finally root-caused the "A resolved helper cannot be passed as a named
argument" error that's been blocking ~13 tests across this session
(md-lineage, md-locale, md-transfer, md-party, md-party-array, md-medium,
md-time-period, md-spatial-info, md-taxonomy/collection[+system+preview],
md-dataquality/preview, md-funding/preview, md-entity). Ember's own error
message is deliberately generic and omits the offending template/helper
name; traced it by temporarily patching the actual bundled source
(node_modules/ember-source/dist/packages/@ember/-internals/glimmer/index.js
-- NOT dist/ember.debug.js, which despite being the "obvious" file isn't
what ember-auto-import/broccoli actually bundles into vendor.js) to
surface Ember's original detailed deprecation message, then reverted the
patch once identified.

Root cause: `@model={{hash}}` -- Ember's built-in `hash` helper invoked
bare (zero args, no parens) as a component's named-argument value. This
was a common old-Ember shorthand throughout this codebase's test fixtures
for "pass an empty object" to satisfy a required @model arg. Ember 4
treats a bare zero-arg helper reference in an argument position as
ambiguous (could mean "pass the helper itself" or "invoke it") and now
hard-errors instead of silently doing the latter. Fixed by wrapping every
occurrence in parens: `{{(hash)}}`, an explicit invocation.

Full suite: 363 -> 376 passing (22 failing), the largest single jump this
session.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An earlier commit added "Select one or more contacts" placeholder text
to this test's expected string based on a one-off observation, but
re-running it consistently (3x) shows the opposite: the multi-select tags
render empty (no placeholder, no contact name), because the fixture's
`party[].contactId` values are strings ("0"/"1") while
tests/helpers/create-contact.js's fixture uses numeric contactIds (0/1) --
selectedItem's `value.includes(item.codeId)` check fails on the type
mismatch, so no option matches and no placeholder condition is met either
(the select has a non-empty `selected` array, just with no matching tags).
This is a pre-existing, unrelated data-fixture quirk in this one test, not
something introduced by the multi-select fix. Reverted to match actual,
now-consistent behavior.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mplate)

The standalone @ember/string polyfill package (added earlier in this
migration for ember-tooltips' deprecated Ember.String usage) deliberately
stubs out htmlSafe/isHTMLSafe to throw "not implemented... import from
@ember/template instead" -- it only exists to give a clear error message,
since @ember/string never actually provided htmlSafe (that's always lived
on Ember.String / @ember/template). Four files imported htmlSafe from the
wrong package: helpers/md-markdown, control/md-indicator, control/md-import-csv,
and object/md-taxonomy/classification/taxon. Fixed all four imports.

Also fixed tests/unit/helpers/md-markdown-test.js, which accessed the
returned SafeString's undocumented `.string` property -- renamed to
`.__string` in ember-source's own SafeString class as of Ember 4.12researched via
node_modules/ember-source/dist/packages/@ember/-internals/glimmer/index.js).
Using the public `.toString()` method instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…-table v5

- Patch ember-leaflet@4.0.2's own leaflet-map.hbs template: it referenced
  `emberLeaflet`/`mergedComponents` without `this.`, relying on
  this-property-fallback (removed in Ember 4). Caused every leaflet-map
  consumer (leaflet draw, feature group, geojson layer, leaflet table) to
  throw "not in scope" in strict-mode templates.
- Same this-property-fallback bug in 2 inline test hbs templates
  (geojson-layer-test.js, leaflet-draw-test.js) referencing `mapAttribution`.
- feature-table.js/leaflet-table-row.js still imported the pre-v5
  ember-models-table classic Theme/Row API (removed). Rewrote both for v5:
  FeatureTable is now a plain wrapper around <ModelsTable> with columns +
  a themed service (emt-themes/mdeditor-leaflet-table, overriding
  rowComponent), instead of subclassing MdModelsTableComponent via a
  classic constructor pattern that no longer applies to its Glimmer base.
  LeafletTableRow now extends EMT v5's default Row and hooks its built-in
  onEnter/onLeave for the row hover-highlight behavior.
- Deleted the now-fully-dead app/pods/components/md-models-table/themes/
  bootstrap3.js (pre-v5 theme class, only importer was the file above).
- Fixed leaflet-table.hbs's hand-rolled table body loop: it used v3/v4
  lowercase contextual component names (body.row, body.no-data) instead
  of v5's PascalCase (body.Row, body.NoData) -- silently rendered nothing,
  never caught before because feature-table.js couldn't render at all
  until this fix. Simplified to let <FeatureTable> self-render (no block)
  since the manual reconstruction added no real behavior over the default.
- FeatureTable's Glimmer component needed setComponentTemplate to
  associate its addon/templates/components/feature-table.hbs template --
  the classic split addon/components + addon/templates/components
  resolver convention doesn't auto-wire templates for @glimmer/component
  subclasses the way it does for classic components.

Fixes 5 of the 19 failing tests on this branch (leaflet draw, feature
group, geojson layer, leaflet table, leaflet table row, feature table).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…wiring

- control/md-record-table's template lost its .md-record-table wrapper
  class in the Glimmer rewrite (classic components got this for free via
  classNames; Glimmer components don't). Added it explicitly to both the
  block and non-block render paths.
- control/md-edit-table's "Edit" button never actually expanded the row
  (handleEditRow only called the optional @editRow callback). EMT v5's
  Cell component already threads @expandRow/@collapseRow/@isExpanded to
  custom column components for free -- wired row-buttons' executeAction to
  call them when a button opts in via `toggleExpand: true`.
- control/md-record-table/buttons/filter's deleteSelected action ignored
  the @deleteselected arg entirely (did its own inline destroyRecord()
  logic unconditionally). Made it honor @deleteselected when provided,
  restoring the override point its own test expects, with no change to
  real call sites (none pass @deleteselected today).
- Fixed 2 unrelated pre-existing test bugs surfaced by the above:
  buttons/filter's test used doubleClick() where md-button-confirm
  actually needs two separate click()s (first click reveals "Confirm",
  second fires onConfirm); contacts-test.js's delete-confirm acceptance
  test used an ambiguous selector that now also matches the bulk
  "Delete Selected" button once a row's own delete-confirm click
  round-trips through the row's native {{on "click"}} listener as a
  selection side effect (classic component event dispatch doesn't
  preempt native listeners on ancestor elements the way it used to) --
  scoped the selector to the row's button specifically.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- md-indicator/related never actually hid its icon when no related item
  was found: `isVisible: bool('related')` was computed but never wired to
  the template (no classNameBindings, no {{#if}}), so the "domain" note
  icon showed on every attribute row regardless of whether it had a
  matching domain. Wrapped the render in {{#if this.isVisible}}.
- Fixed 2 lingering this-property-fallback misses in test hbs (object/md
  array table's `data`, feature-table.hbs prettier formatting).
- Updated 4 tests whose expectations were baked to the old
  bootstrap-datetimepicker/ember-power-select output, now stale after
  this branch's earlier, deliberate switch to native HTML date inputs
  and the ember-power-select major bump:
  - input/md month, object/md keyword citation: native
    date/month/datetime-local inputs report .value as "YYYY-MM[-DD[THH:mm]]"
    (no seconds, no offset, no locale month name) regardless of the
    component's own moment `format`.
  - input/md date range: the old bootstrap-datetimepicker's shared `.date`
    wrapper class is gone; scoped the selector to `.md-datetime` instead.
  - object/md constraint: descriptive placeholder text for two
    power-select fields now renders as the search input's native
    `placeholder` attribute (searchEnabled defaults to true), not as
    visible textContent.
- Fixed a missing `await` on a bare `click()` call in
  subbar-importcsv's test (assertion count mismatch).

Full suite: 398/398 passing (0 fail, 4 skip, 2 todo), verified against
two different random seeds. Production build succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@dvonanderson dvonanderson self-assigned this Aug 25, 2026
@dvonanderson dvonanderson added the enhancement Improve or modify an existing feature label Aug 25, 2026
dvonanderson and others added 4 commits August 26, 2026 08:04
- ember-font-awesome's fa-icon destructures getWithDefault off the
  Ember global (removed in 4.0) to stringify the `size` option --
  replaced with a plain get()/|| fallback.
- ember-local-storage's adapter/serializer chain was broken two ways:
  helpers/storage.js imported deprecate from the removed
  @ember/application/deprecations (this module-load failure was
  getting silently swallowed by ember-data's generic "Failed to create
  an instance of 'adapter:application'" assertion, hiding the real
  cause); base.js/serializer.js destructured JSONAPIAdapter/
  JSONAPISerializer off the classic `ember-data` default export
  instead of importing from the scoped @ember-data/* packages directly
  (mirrors the addon's own upstream v2 fix, applied here as a patch to
  avoid a major version bump).

Found via a genuinely fresh install (node_modules + yarn cache wiped)
after CI caught what a stale local node_modules had been masking.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ember-toggle's x-toggle and x-toggle-label templates used the bare
`hasBlock` keyword (pre-Octane block-presence check), removed under
Ember 4's strict-mode template compiler. Replaced with `(has-block)`.

This was the actual root cause behind ~75 seemingly-unrelated test
failures against a fresh install: any acceptance test rendering a
toggle crashed with "value not in scope: hasBlock", left the app in a
broken state, and every subsequent test in the same run inherited
leaked/duplicated DOM content from the botched teardown -- misleadingly
looking like dozens of independent failures across unrelated
components (confirmed by running one of the "failing" tests, like
ember-tooltip, in isolation: it passed fine on its own).

Full suite: 398/398 passing against a genuinely fresh install (verified
twice, two different random seeds). Production build succeeds.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…store)

ImportRoute used `this.store.importData(...)` without ever injecting
`store` explicitly. Ember's base Route class ships its own fallback
`store` getter (documented as "provides a hook for data persistence
libraries to inject themselves" -- a `{find() {...}}` shim meant only
for the legacy dynamic-segment model-loading pattern), which is what
`this.store` actually resolved to here instead of the real ember-data
store service. `.importData` doesn't exist on that shim, so the click
silently did nothing -- no error, no crash, since accessing a missing
method on an object literal just returns undefined until called, and
the call itself was buried inside a promise chain.

Confirmed via a live repro in the browser: before the fix, `route.store`
was a plain `{find}` object; after adding `@service store`, it resolves
to the real Store instance and a full upload -> import -> redirect to
/dashboard flow completes successfully.

Audited the rest of the app for the same pattern (this.store without an
explicit @service store on a route/controller) -- no other live
instances found, only commented-out code.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MdSpotlightComponent used ember-modal-dialog's clickOutsideToClose, whose
document-level click handler treats a click as "outside" unless it lands
inside the dialog's own rendered container. The spotlight's actual content
is never rendered inside that container -- it's the original target
element, kept in place and z-index-elevated above the overlay (see
.md-spotlight-target in _modal.scss) -- so every click on the spotlighted
form was immediately treated as an outside click and closed the overlay,
even a click just to focus the next field. The form itself stayed open
(md-object-table's own editing flag was untouched), so it looked like the
modal silently dropped back to plain inline rendering mid-edit.

Disabled clickOutsideToClose; clicking the dimmed overlay to close still
works via the separate onClickOverlay handler.
didReceiveAttrs unconditionally rebuilt displayItems via
applyTemplateArray, which wraps every item in a brand-new templateClass
instance on each call. didReceiveAttrs fires on every parent re-render,
not just when `items` actually changes, so any incidental re-render
mid-edit silently swapped out the instance the edit form is bound to
(this.saveItem) for a fresh clone that didn't yet have the in-progress
edits. The table then displayed the stale clone (showing blank/"Not
Defined" fields) until the next add/edit cycle happened to rebuild from
the by-then-updated source data and show the real values.

Only rebuild displayItems when the underlying `items` array reference
actually changes.
Same classic-two-way-binding hazard as elsewhere in this codebase: raw
assignment (this.model = A()) doesn't notify Ember's dependency tracking
for a classic component property, so the parent's model=this.model.
onlineResource binding could stay stuck on the pre-init undefined value.
Switched to set().
@dvonanderson
dvonanderson marked this pull request as ready for review August 27, 2026 18:02
@hmaier-fws

Copy link
Copy Markdown
Contributor

This build throws an error when selecting "Export". See issue: #868

@dvonanderson
dvonanderson merged commit 8c9e5a8 into ember-migration Sep 2, 2026
2 checks passed
@dvonanderson
dvonanderson deleted the dvonanderson/feature/ember-4-12-upgrade branch September 2, 2026 17:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improve or modify an existing feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants