@smartbit4all/ng-client 7.0.10 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/MIGRATION-7.0.md CHANGED
@@ -1252,7 +1252,7 @@ shorts link produced no player. It plays now.
1252
1252
 
1253
1253
  | Removed | Replacement |
1254
1254
  |---|---|
1255
- | `SmartformwidgetComponent` | nothing — no host imported the class; `<smartform>` still renders it. 7.1 splits it into per-type components |
1255
+ | `SmartformwidgetComponent` | nothing — no host imported the class; `<smartform>` still renders it. 7.2 splits it into per-type components |
1256
1256
  | `SmartWidgetSettings` (its only member was `static useUtc`) | nothing — **delete the assignment** |
1257
1257
 
1258
1258
  3.6 moved the `useUtc` static into `SmartNgClientConfig.useUtcDates`; **3.8 then removed the
@@ -1717,6 +1717,133 @@ keep its own default when the library passed `false` or `''` — it now receives
1717
1717
  This is the only part of 7.0 where a bugfix can change what you see on screen without any
1718
1718
  code of yours changing.
1719
1719
 
1720
+ ### Model sync: the client's models travel with every call (7.1.0)
1721
+
1722
+ Whether an action carried the page model used to be a per-action decision of the backend
1723
+ (`UiAction.submit` / `model` / `composite`) plus one hidden rule (an action with no params sent
1724
+ it anyway), and the model travelled under three ad-hoc `params` keys (`model`, `clientPageModel`,
1725
+ `compositeModel`) that only one endpoint wrote into the backend view automatically. The bug that
1726
+ forced the change: an action on an embedded view sent only its own model, the handler changed the
1727
+ container's model, the container's whole component model came back, and whatever the user had
1728
+ typed into the container since the last round-trip was gone.
1729
+
1730
+ From 7.1.0 the client sends, with **every** action, widget action, upload and grid selection
1731
+ call, the data of **every open view that changed since it was last synced** — slot-hosted and
1732
+ dialog views alike — in a new `UiActionRequest.modelSync.models` map keyed by view uuid. The
1733
+ backend writes each one into the matching view **before the handler runs and before it takes the
1734
+ base of its change push**, so a handler always sees what is on screen and the client's own values
1735
+ are never echoed back. A view equal to its last synced copy is left out; a view without a copy is
1736
+ always sent.
1737
+
1738
+ **Backend minimum, and the server switch.** The platform build that ships
1739
+ `UiActionRequest.modelSync` (platform ADR-0011 on the server side), **with model sync switched
1740
+ on**: `view.model-sync.enabled=true` in the host's `application.properties`. The switch defaults
1741
+ to `false`, and in that mode the server behaves exactly as before model sync existed: nothing is
1742
+ written into the views before the handler, and a request that carries `modelSync` is **rejected**
1743
+ with an error naming the property, not ignored. A 7.1.0 client sends the envelope with every
1744
+ call, so against a server with the switch off every call fails loudly; against an older platform
1745
+ the field is silently ignored and every handler that read `params.model` sees nothing. **Order:
1746
+ upgrade the backend, set the flag, then upgrade the client.** Server code can read the mode as
1747
+ `viewApi.isModelSyncEnabled()`.
1748
+
1749
+ **A 6.x client against a backend with the switch on** is rejected too: with
1750
+ `view.model-sync.enabled=true` every request that can carry `modelSync` — an action, a widget or
1751
+ upload action, a tree action, a grid selection change — must carry it, and one without it (an
1752
+ old client, or host code that builds a `UiActionRequest` by hand and calls the view context
1753
+ service directly) gets an error saying so. A call that cannot carry it (loading a view, a
1754
+ message result, expanding a tree node, a map interaction) is neither synced nor refused. The old `params.model` / `clientPageModel` / `compositeModel`
1755
+ keys are never read as a sync source; they are only *filled* by the backend, see below. So the
1756
+ switch and the client upgrade go together per host: flip the flag in the same deploy that ships
1757
+ the 7.1.0 client. Internal Java callers with nothing to sync send an empty `new ModelSync()`.
1758
+
1759
+ #### What changed on the client
1760
+
1761
+ - `UiAction.model` and `UiAction.composite` are **deprecated no-ops** for a view's own actions.
1762
+ `submit` keeps one meaning: validate before sending, the embedded views' forms included. For an
1763
+ executor that is not a view (a tree, the filter editor) `model` still flushes its form and its
1764
+ model still rides on `params.model` as before, because it has no view to sync.
1765
+ - Forms are flushed into their data (`submitForm(false)`) on every open view before a call, so an
1766
+ unsaved field value is part of what is compared and sent. **Only the user's edits are
1767
+ flushed**: the form remembers, per control, the value it last exchanged with the data (built
1768
+ or refreshed from it, or written into it), and a control still equal to that writes nothing.
1769
+ This is what keeps an untouched `false` or `0` from turning into `''` on a round trip (the
1770
+ control is now built with `value ?? ''`, and a value written back takes the type the field has
1771
+ in the data: `'true'` → `true`, `'12'` → `12`, `''` → `null`), and a backend push from
1772
+ reaching the data only to be overwritten by the control's stale value on the next call. The
1773
+ same rule the other way: a data field the backend changed reaches its control even when the
1774
+ user edited it (the backend wins on a field it changed), a field it did not change keeps what
1775
+ the user typed. `SmartFormService.resetExchangedValues()` starts over when a form is rebuilt.
1776
+ The form's own dirty flag is not involved: it lives until the form is rebuilt and says nothing
1777
+ about what the data holds.
1778
+ - A push of some `data.*` paths marks only those paths as synced; whatever else the view holds
1779
+ keeps its standing, so a value written into the data outside a form while a call was in flight
1780
+ is still sent by the next one. A whole-model push and the initial load mark all of it.
1781
+ - Calls run concurrently, and an envelope built earlier may complete later: its copies are never
1782
+ written over the copies a later envelope or push already settled. The server-side ordering of
1783
+ rapid per-field actions stays out of scope (ADR-0010).
1784
+ - A dialog the backend closed (`ViewState.TO_CLOSE`) no longer fires a `DEFAULT_CLOSE` action of
1785
+ its own on top of the response that closed it; only the user's own close does.
1786
+ - The per-field actions a widget's `valueChangeMode` fires are ordinary actions and inherit the
1787
+ sync; they no longer set `model: true`, and still carry `params.item`.
1788
+
1789
+ #### Removed from the public API
1790
+
1791
+ | Gone | What to do |
1792
+ |---|---|
1793
+ | `SmartComponentApiClient.sendModelOnWidgetAction` | Delete the assignment. Every widget action carries the changed models now. Loud: `this.sendModelOnWidgetAction = true` no longer compiles. |
1794
+ | `SmartComponentApiClient.addModelToWidgetParams()` / `addModelToWidgetAction()` | Delete. The grid service builds its own envelope. |
1795
+ | `SmartViewContextService.getCompositeModel()` | Delete. The backend gets every changed view. |
1796
+ | `SmartViewContextService.submitEmbeddedForms()` | `validateEmbeddedForms(containerUuid)` — validation only; the flush is part of every call. |
1797
+ | `SmartViewContextService.dataChanged()` | Delete. It was never wired on the client (`handleValueChanges` had no caller); a field change is a uiAction. |
1798
+
1799
+ New on `SmartViewContextService`: `joinModelSync(uuid, participant)` / `leaveModelSync(uuid,
1800
+ participant)` (the client does this on its own uuid; leaving only takes effect for the participant
1801
+ that is registered, so a component destroyed after its successor joined under the same uuid does
1802
+ not take the successor with it), `markModelSynced(uuid, keys?)` (the client calls it whenever the
1803
+ backend's model reached it: without keys the whole data, with keys the pushed `data` paths), and `withModelSync(carrier, call)` for a host that issues its own backend call on a request
1804
+ that can carry the envelope (a `UiActionRequest`, a `GridSelectionChange`): it puts the envelope
1805
+ on the carrier, runs the call with it and settles the copies on success, and hands back the raw
1806
+ `ViewContextChange` — what to do with the response stays with the caller. The grid and tree
1807
+ services are the in-tree examples; `beginModelSync()` / `completeModelSync()` underneath are
1808
+ public too.
1809
+
1810
+ #### The Java side of the host: three kinds of handler
1811
+
1812
+ The backend **backfills** the old `params` keys from the synced model on every request (with the
1813
+ switch on, every request carries `modelSync`), so no handler has to change on the day the client
1814
+ is upgraded. Two kinds must be
1815
+ looked at anyway, and one of them breaks without a compiler error. Run
1816
+ `node tools/model-sync-audit/cli.mjs ../<host>` — it lists every read of the request model with
1817
+ one of these categories and a reason ([its README](../../tools/model-sync-audit/README.md)):
1818
+
1819
+ | | The handler | What to do |
1820
+ |---|---|---|
1821
+ | **A** | reads `params.model` (`extractClientModel(request)`, `actionRequestHelper(request).get(UiActions.MODEL, …)`) and only passes it to `setModel(viewUuid, …)` | Delete both statements. The sync already wrote the client's model into the view. |
1822
+ | **B** | reads it for something else: saves it, validates it, forwards it, mutates it before setting it back | Nothing required. `getModel(viewUuid)` is the honest read now; `extractClientModel` is `@Deprecated`. A grid row action's `params.model` is still the clicked row and is never synced. |
1823
+ | **C** | reads the view's model too (`getModel(viewUuid)`, `view.getModel()`) and uses the two together — a diff, a merge, "what changed" | **Breaks silently.** After the sync `getModel` *is* the client's model, so the comparison compares a thing with itself. The server-side read must become `viewApi.isModelSyncEnabled() ? getPreviousModel(viewUuid) : getModel(viewUuid)`: the model as the server knew it before this call, request-scoped (`viewApi.getPreviousModel(uuid, Class)` outside a `PageApiImpl`). The off branch is the old code. |
1824
+
1825
+ A handler that must work in both server modes — a shared module deployed to hosts with the
1826
+ switch off as well as on — branches inline on `viewApi.isModelSyncEnabled()` and keeps its old
1827
+ code verbatim in the off branch: an A site becomes
1828
+ `if (!viewApi.isModelSyncEnabled()) { setModel(viewUuid, extractClientModel(request)); }`, a
1829
+ client read `viewApi.isModelSyncEnabled() ? getModel(viewUuid) : extractClientModel(request)`, a
1830
+ C site's server read the ternary above. No wrapper helper on purpose: once every host runs with
1831
+ the switch on, the off branches are deleted line by line and what remains is the plain new code.
1832
+ The platform's own handlers are written this way. `getPreviousModel` with the switch off returns
1833
+ `getModel` and logs an ERROR: that code assumes a mode the server is not running in.
1834
+
1835
+ Two traps the audit cannot see:
1836
+
1837
+ - A handler that forwards the *request* to a callback which reads `params.model` (the platform's
1838
+ password editor does this) depends on `extractClientModel` writing the typed instance back into
1839
+ the params. Keep `extractClientModel` there, or hand the model over explicitly.
1840
+ - A handler that reads `compositeModel` by hand gets it backfilled only when its action still
1841
+ has `composite=true`. Read the children's models from `viewApi` instead; the flag goes away
1842
+ once no host reads the key.
1843
+
1844
+ The measured spread, over the 56 host repositories that were audited when this shipped: 1061 read
1845
+ sites, 301 A, 671 B, 77 C.
1846
+
1720
1847
  ### Bugfixes shipped with 7.0
1721
1848
 
1722
1849
  - `SmartformwidgetComponent.ngAfterViewInit` no longer crashes with