@object-ui/plugin-gantt 17.3.0 → 17.6.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/CHANGELOG.md CHANGED
@@ -1,5 +1,664 @@
1
1
  # @object-ui/plugin-gantt
2
2
 
3
+ ## 17.6.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 5edc0c5: `object-gantt` / `object-map` / `object-calendar` no longer drop a sort entry that omits `order`.
8
+
9
+ The three blocks each inlined a byte-identical private copy of the `sort` →
10
+ `$orderby` conversion. That copy required BOTH `field` and `order` on an array
11
+ entry and silently skipped any entry missing one, so a stored view sorting by
12
+ `[{ field: 'amount' }]` reached the wire with no ordering at all — the authored
13
+ sort key was lost, not applied. The same copy already treated the STRING
14
+ spelling `"amount"` as ascending, so this was an inconsistency between two
15
+ spellings of one thing rather than deliberate strictness.
16
+
17
+ All three now import the shared `convertSortToQueryParams` sink from
18
+ `@object-ui/core` (introduced by objectstack#7137, already used by
19
+ `object-timeline` and `record:line_items`), and the private copies are gone —
20
+ the sink is the repo's only definition. Two behavior changes come with it, both
21
+ of which make the blocks more faithful to what is already declared rather than
22
+ more tolerant:
23
+
24
+ - An array entry that omits `order` now orders ASCENDING instead of vanishing.
25
+ That is what `QueryParams.$orderby`'s own member shape
26
+ (`{ field: string; order?: 'asc' | 'desc' }`) says, and what
27
+ `@object-ui/data-objectstack`'s `serializeOrderBy` already did with a missing
28
+ direction.
29
+ - When nothing orderable was authored, the query now carries no `$orderby` at
30
+ all instead of an empty object. `{}` is truthy and meant "no ordering" only by
31
+ accident of the adapter's serializer.
32
+
33
+ Reachability, so the size of this is not overstated: `SortConfig.order` and
34
+ `ElementDataSourceSort.order` are REQUIRED in objectui's own types, so a typed
35
+ caller could never author the dropped shape. The affected surface is untyped
36
+ stored view metadata (`ElementSavedView` is a loose record by design) — which is
37
+ exactly where an order-less entry can arrive today.
38
+ - 6098ecd: plugin-gantt README: the `onTaskUpdate` drag example no longer writes blank dates on a progress drag.
39
+
40
+ `GanttViewProps.onTaskUpdate`'s second parameter is
41
+ `Partial< Pick< GanttTask, 'title' | 'start' | 'end' | 'progress' > >`
42
+ (`src/GanttView.tsx:345`), so the destructured `start` / `end` are
43
+ `Date | undefined`. The README's "Drag-and-drop rescheduling" example destructured
44
+ them and passed both straight to `save(...)`, under a comment asserting they are
45
+ always real `Date` objects. Both layers of that assertion were wrong.
46
+
47
+ The runtime half is the one that mattered. `onTaskUpdate` is the single exit for
48
+ every edit path, and the progress grip commits changes with **no** dates at all —
49
+ `commitTaskUpdates([{ task, changes: { progress: cur.value } }])`
50
+ (`src/GanttView.tsx:1335`), forwarded verbatim by `onTaskUpdate(task, changes)`
51
+ (`:1091`). A host that copied the example therefore called
52
+ `save(task.id, { start: undefined, end: undefined })` on every progress drag,
53
+ blanking the record's start and end dates. Drag is documented as opt-in via
54
+ `onTaskUpdate` alone and progress drag needs no extra switch, so the path was
55
+ reachable by default rather than a corner configuration.
56
+
57
+ The example now guards with `if (!start || !end) return;` and its comment states
58
+ the real shape — that only the keys an edit touched are present, and that the
59
+ progress grip sends just `{ progress }`. Documentation only; no renderer behavior
60
+ changed.
61
+ - bb3fab6: Docs only: `packages/plugin-gantt/README.md` no longer teaches two identifiers the
62
+ package does not export, nor a task shape it does not produce (objectui#5012).
63
+ Each README import was judged against the entry module's real export surface
64
+ (35 names, read from the build product's `dist/index.d.ts`), and every corrected
65
+ snippet was type-checked against that same build product:
66
+
67
+ - **`ganttComponents`** — taught as a components map to iterate over for "manual
68
+ registration" (`Object.entries(ganttComponents).forEach(...)`). It does not
69
+ exist anywhere in the package, so the snippet was `Object.entries(undefined)`:
70
+ a `TypeError` on the first line a reader copied. Registration is *only* the
71
+ side effect of importing the entry point, which runs the two
72
+ `ComponentRegistry.register(...)` calls in `src/index.tsx`. The section is
73
+ replaced by what actually happens: the schema types those calls claim
74
+ (`object-gantt` → `plugin-gantt:object-gantt`, `gantt` → `view:gantt`, both
75
+ with a bare-`type` fallback), the package's real export surface, and — for the
76
+ use case the old snippet was reaching for — registering the exported
77
+ `ObjectGanttRenderer` under a key of your own.
78
+
79
+ - **`GanttSchema`** — taught as the component schema type in the TypeScript
80
+ section. Pure fiction: zero hits in this package and in `@object-ui/types`
81
+ (a plain grep appears to find it only as a substring of `ObjectGanttSchema`;
82
+ under a word boundary it has no hits at all). The authored type does exist
83
+ under its real name, so the example is rewritten around it rather than
84
+ dropped: `ObjectGanttSchema` from `@object-ui/types`, which is
85
+ **record-driven** — `type: 'object-gantt'` plus an object name and the fields
86
+ to read. It never carried the `tasks` array the old snippet assigned to it.
87
+ No export was added to make the old name true.
88
+
89
+ - **`GanttTask`** — the one real name of the three, and the reason the section
90
+ still failed to compile. The documented shape had drifted from the exported
91
+ type on three counts: the label field is `title`, not `name`; `start`/`end`
92
+ are `Date` objects, not ISO strings; and `color` is a CSS color, not a
93
+ Tailwind class. Both the "Task Structure" reference block and the typed
94
+ example now match the exported declaration, and the reference block is pinned
95
+ against it in both assignment directions so a future drift fails a check
96
+ instead of compiling as an unrelated local interface.
97
+
98
+ No code, types or runtime behaviour change — the diff is one README and this
99
+ changeset. The correction reaches npm with the package's next publish, which is
100
+ why it declares a patch: `README.md` is in the package's published `files`.
101
+ - 449ffe8: Fix the `GanttViewMode` JSDoc to name all five granularities (objectui#5132).
102
+
103
+ The comment above `export type GanttViewMode` said "one column per day, week,
104
+ month, or quarter" while the type itself, `VIEW_MODES`, `NOMINAL_DAYS`
105
+ (`year: 365.25`), the column builder, the toolbar and the header-band logic
106
+ have honored a fifth member, `'year'`, all along — `'year'` was fully live,
107
+ just undocumented. Doc-only change, no behavior difference.
108
+ - Updated dependencies [88085e3]
109
+ - Updated dependencies [69251bf]
110
+ - Updated dependencies [57e668f]
111
+ - Updated dependencies [516663d]
112
+ - Updated dependencies [41ac1b7]
113
+ - Updated dependencies [1eaf0a1]
114
+ - Updated dependencies [7c297e3]
115
+ - Updated dependencies [a09bc33]
116
+ - Updated dependencies [460c4d0]
117
+ - Updated dependencies [0ae27f7]
118
+ - Updated dependencies [2533ec5]
119
+ - Updated dependencies [78c0f9a]
120
+ - Updated dependencies [bbe8b86]
121
+ - Updated dependencies [8477be5]
122
+ - Updated dependencies [279fb13]
123
+ - Updated dependencies [2e82ab2]
124
+ - Updated dependencies [ad07b65]
125
+ - Updated dependencies [41f498b]
126
+ - Updated dependencies [ef0d150]
127
+ - Updated dependencies [f34226e]
128
+ - Updated dependencies [564b605]
129
+ - Updated dependencies [e1d4251]
130
+ - Updated dependencies [40d3a33]
131
+ - Updated dependencies [8b9dc62]
132
+ - Updated dependencies [1184192]
133
+ - Updated dependencies [a2a9747]
134
+ - Updated dependencies [65e88e6]
135
+ - Updated dependencies [a1609a6]
136
+ - Updated dependencies [53f23bc]
137
+ - Updated dependencies [c4533dc]
138
+ - Updated dependencies [be60815]
139
+ - Updated dependencies [37f6844]
140
+ - Updated dependencies [93de4f6]
141
+ - Updated dependencies [2b50261]
142
+ - Updated dependencies [384f30d]
143
+ - Updated dependencies [ac600e5]
144
+ - Updated dependencies [97fba31]
145
+ - Updated dependencies [232f61a]
146
+ - Updated dependencies [d374caf]
147
+ - Updated dependencies [5673576]
148
+ - Updated dependencies [c1ef923]
149
+ - Updated dependencies [911ceaa]
150
+ - Updated dependencies [98eab36]
151
+ - Updated dependencies [af5e292]
152
+ - Updated dependencies [3fbbea1]
153
+ - Updated dependencies [0bffb18]
154
+ - Updated dependencies [800f455]
155
+ - Updated dependencies [5458414]
156
+ - Updated dependencies [3241559]
157
+ - Updated dependencies [7f96b10]
158
+ - Updated dependencies [167ec42]
159
+ - Updated dependencies [616a2a5]
160
+ - Updated dependencies [6c68b13]
161
+ - Updated dependencies [0046d8f]
162
+ - Updated dependencies [f1d4748]
163
+ - Updated dependencies [bea374e]
164
+ - Updated dependencies [b1119ec]
165
+ - Updated dependencies [5607092]
166
+ - Updated dependencies [9f23d2b]
167
+ - Updated dependencies [578e025]
168
+ - Updated dependencies [af025ee]
169
+ - Updated dependencies [d109a4d]
170
+ - Updated dependencies [598c89a]
171
+ - Updated dependencies [4a0bd17]
172
+ - Updated dependencies [b8b9af4]
173
+ - Updated dependencies [31676be]
174
+ - Updated dependencies [8c0d52e]
175
+ - Updated dependencies [aff10e2]
176
+ - Updated dependencies [70a774b]
177
+ - Updated dependencies [7dd93c0]
178
+ - Updated dependencies [229b17e]
179
+ - Updated dependencies [9ce096f]
180
+ - Updated dependencies [e05db88]
181
+ - Updated dependencies [7458a41]
182
+ - Updated dependencies [ad13d63]
183
+ - Updated dependencies [5ffcc14]
184
+ - Updated dependencies [d971e51]
185
+ - Updated dependencies [97abb24]
186
+ - Updated dependencies [deb157a]
187
+ - Updated dependencies [9c60144]
188
+ - Updated dependencies [e7747f1]
189
+ - Updated dependencies [d2ce342]
190
+ - Updated dependencies [9695da7]
191
+ - Updated dependencies [ac2f332]
192
+ - Updated dependencies [a777058]
193
+ - Updated dependencies [75444e3]
194
+ - Updated dependencies [58b8346]
195
+ - Updated dependencies [2d0bd16]
196
+ - Updated dependencies [a9e17b4]
197
+ - Updated dependencies [b8ce7dc]
198
+ - Updated dependencies [dad51e5]
199
+ - Updated dependencies [1c9c342]
200
+ - Updated dependencies [787c738]
201
+ - Updated dependencies [8396656]
202
+ - Updated dependencies [dbbd38a]
203
+ - Updated dependencies [8871c14]
204
+ - Updated dependencies [93fe362]
205
+ - Updated dependencies [dfc6975]
206
+ - Updated dependencies [3cf4de0]
207
+ - Updated dependencies [c9dc811]
208
+ - Updated dependencies [144ef9b]
209
+ - Updated dependencies [138ab04]
210
+ - Updated dependencies [a0b9e91]
211
+ - Updated dependencies [99bd015]
212
+ - Updated dependencies [21e4585]
213
+ - @object-ui/types@17.6.0
214
+ - @object-ui/fields@17.6.0
215
+ - @object-ui/i18n@17.6.0
216
+ - @object-ui/react@17.6.0
217
+ - @object-ui/plugin-detail@17.6.0
218
+ - @object-ui/components@17.6.0
219
+ - @object-ui/core@17.6.0
220
+
221
+ ## 17.5.0
222
+
223
+ ### Patch Changes
224
+
225
+ - ebb4e0e: The date formatter's last three en-US channels now follow the display locale
226
+ (objectui#4272).
227
+
228
+ objectui#4468 (PR #4512) pointed every date _renderer_ at `useDisplayLocale()`.
229
+ Three channels were out of its reach because they are properties of the
230
+ formatter's signature and of its callers rather than of any renderer, so a `zh`
231
+ console still met English dates in three places:
232
+
233
+ - **`formatDate`'s `'short'` branch** hardcoded
234
+ `toLocaleDateString('en-US', { month: 'short' })`, so it rendered an English
235
+ month even when the caller had threaded `options.locale` into that very call.
236
+ Its only consumers are ObjectGrid's two mobile-card date cells, which threaded
237
+ no locale — fixing either half alone moves nothing, so both land here.
238
+ - **`formatDateTime` took no options parameter at all**, so no caller could
239
+ localize it however hard it tried; it always handed `Intl` an `undefined` tag,
240
+ which means the MACHINE's locale — neither of the repo's two locale channels.
241
+ The parameter is optional and lands together with its consumers, plugin-gantt's
242
+ four tooltip call sites.
243
+ - **The lookup picker's MongoDB `$date` fallback** called a bare
244
+ `toLocaleDateString()` with no tag.
245
+
246
+ One resolver everywhere, as before: `useDisplayLocale()` (tenant regional
247
+ default → active UI language → `'en'`). `Intl` accepts `'zh'` verbatim, so there
248
+ is still no mapping table anywhere.
249
+
250
+ English output is byte-identical at every touched site — `en` and `en-US` agree
251
+ on all twelve short month names — and the `'short'` layout itself is unchanged:
252
+ only the month token is localized, the compact `"Jan 15, '24"` shape around it
253
+ is a deliberate fixed layout for narrow cards.
254
+
255
+ `@object-ui/fields` is `minor` because `formatDateTime`'s new optional parameter
256
+ is visible in the package's entry `.d.ts`; the plugin packages' own `.d.ts` files
257
+ are byte-identical, so their change is module-local.
258
+
259
+ - 828549a: The gantt's conflict dialog shows the number of affected tasks again, not a literal `{2}`
260
+
261
+ `gantt.conflict.body` was resolved at the render site with a literal string replace on **single** braces — `t('gantt.conflict.body').replace('{count}', String(n))` — while all ten locale packs spell the placeholder the i18next way, `{{count}}`. `"…{{count}}…".replace("{count}", "2")` consumes the inner seven characters and leaves the outer pair behind, so every user on every loaded pack read "自动重新排程 **{2}** 个受影响的任务?". The dialog now interpolates through i18next (`t('gantt.conflict.body', { count })`), the idiom `gantt.delete.body` already used.
262
+
263
+ The two sibling keys three lines away in the same file, `gantt.autoScheduleDlg.body` and `.skipped`, were **not** broken — pack and call site both used single braces, and they rendered correctly. They are converted anyway, because that split is the whole mechanism: two write-confirmation dialogs in one component carried two different interpolation idioms, so `conflict.body` drifting to the i18next spelling in the packs (which is the correct spelling, and matches every other placeholder in the bundle) silently broke the render. Leaving the auto-schedule keys on the literal-replace idiom leaves the same trap armed for the next translator. All ten packs and the plugin's bundled English fallback table now agree on `{{count}}` for all three; only the braces moved, no translation was reworded.
264
+
265
+ `gantt.quickFilter.resultSummary` stays deliberately single-brace — its `ObjectGantt` call site really does resolve `{shown}`/`{total}` with a literal replace, and that convention is pinned by its own parity test. It is now the only key in the gantt namespace on that idiom, and the comments at both spellings say so.
266
+
267
+ Nothing caught this, and each gate was silent for its own reason: the cross-pack parity check compares en against each pack, and all eleven spellings agreed; the en-drift check compares a pack against its own history, and the packs were born matching. Both are **relative** comparisons, and the defect lived in the **absolute** relationship between a pack's spelling and the syntax the call site resolves. The existing render test asserted the dialog body contains `'1'` — which `{1}` satisfies. The new pin asserts the absolute form directly, under a real loaded pack, for every way a placeholder can survive to the screen.
268
+
269
+ - e1ade8f: An illegal gantt dependency link now says why it was refused, instead of doing nothing
270
+
271
+ Dragging a dependency onto a target the gantt refuses — itself, a locked row, a group row, or one that would close a dependency cycle — produced no feedback of any kind: no toast, no dialog, no cursor change, no target outline, not even a console warning. The guard was right and completely invisible, so a user drawing a legitimate-looking dependency got a dead interaction and no way to learn the constraint. The rejection was silent in both places it could have shown: a refused bar never became the drop target, so it got no hover treatment at all, and the release handler only ran its body when a target _had_ been registered, so the drop itself was a no-op.
272
+
273
+ Both halves are now wired, and both read the **same** verdict. `canReceiveLink`'s four-branch boolean became `classifyLinkTarget`, which returns which branch refused (or `null`), with the boolean derived from it. The hover affordance and the drop toast are two consumers of that one classification, so the reason a user is shown cannot drift from the reason the link was actually refused — there is no second classifier to disagree. The branch names are the leaves of the new `gantt.link.rejected.*` keys, so a branch added later without a message surfaces as a missing key rather than as a plausible-but-wrong sentence.
274
+
275
+ During the drag, a refused bar under the pointer gets `cursor: not-allowed` and a destructive outline; on release it raises a toast naming the reason. Four messages, one per branch, in all ten packs. Both the cursor and the outline are driven from inline `style` rather than utility classes, matching the bar's existing read-only cursor three lines away and for the same reason recorded there: `cursor-not-allowed` and the ring alpha utilities are not emitted in the prebuilt components CSS, so a class would look correct in a DOM test and render nothing in a browser.
276
+
277
+ Deliberately unchanged: a host veto through `onBeforeDependencyCreate` stays silent. That rejection carries a reason only the host knows, and the gantt has none to show — surfacing it means exposing a rejection-reason output on the public component, which is a separate contract rather than a rider on this one. The four built-in reasons are the gantt's own policy and are the only ones it can explain.
278
+
279
+ One of the four, `group`, has no end-to-end path today: a `type: 'group'` row renders no bar, so the drag can never target it. The message is kept anyway — without it the branch would render a raw key on screen if it ever did fire — and the test pins the reachability fact, so it goes red the day group rows gain a bar. Filed as objectui#4209.
280
+
281
+ - db4ad6b: Gantt tooltip currency re-formats when the tenant currency resolves
282
+ (objectui#4542).
283
+
284
+ ObjectGantt's `tasks` memo builds every tooltip string eagerly inside its
285
+ callback, and the `'currency'` case resolves its code down to the tenant
286
+ default (`resolveFieldCurrency(def, tenantCurrency)`). `tenantCurrency` was
287
+ not in the memo's dependency array, so the value was read but never watched.
288
+
289
+ That default comes from `GET /api/v1/auth/me/localization`, which is cosmetic
290
+ and non-blocking and therefore answers AFTER first paint. The context change
291
+ re-rendered ObjectGantt, but with none of `data` / `ganttConfig` /
292
+ `objectSchema` / `displayLocale` changed the memo handed back its cached task
293
+ array — so a tooltip amount kept the pre-resolution rendering (a plain
294
+ `1,234.50` instead of `€1,234.50`) until something unrelated invalidated the
295
+ memo.
296
+
297
+ This is the currency twin of objectui#4272, which added `displayLocale` to
298
+ this same array for the same reason, and it is not covered by that dep: the
299
+ producer writes currency and locale from one response, so a tenant that
300
+ configures BOTH re-runs the memo through the locale channel — but a tenant
301
+ that configures a currency and no locale (the common shape, since the tenant
302
+ locale is frequently unset) leaves `displayLocale` untouched and the currency
303
+ stale.
304
+
305
+ Module-local: the fix is one dependency, the package's `.d.ts` files are
306
+ byte-identical, and rendering is unchanged whenever the channel resolves
307
+ before first paint or a field carries its own currency code.
308
+
309
+ - a908882: Gantt tooltip numbers and currency follow the display locale (objectui#4553).
310
+
311
+ `formatFieldValue`, the tooltip value formatter inside ObjectGantt's `tasks`
312
+ memo, had its four TEMPORAL call sites threaded with `useDisplayLocale()` by
313
+ objectui#4272. The numeric cases beside them passed no locale, so they reached
314
+ `new Intl.NumberFormat(undefined, …)` — the MACHINE's locale, which is neither
315
+ of the repo's two locale channels.
316
+
317
+ One tooltip therefore rendered two conventions. A German session read
318
+ `5. Jan. 2024` on the date row and `1,234.50` on the amount row directly below
319
+ it, where German groups with `.` and marks the decimal with `,`. Inverted
320
+ separators do not read as an unstyled number; they read as a different number.
321
+ The currency row was affected in the symbol's POSITION too — `1.234,50 EUR`
322
+ rather than `EUR1,234.50` — while the currency CODE itself was already resolved
323
+ correctly (objectui#4542 made the memo watch it); only the locale rendering that
324
+ code was missing.
325
+
326
+ `number` / `integer` / `float` / `decimal` and `currency` now pass the
327
+ `displayLocale` already read at component level, using each formatter's existing
328
+ locale parameter. No formatter signature changed and no memo dependency changed
329
+ (`displayLocale` has been in that array since objectui#4272), so this is
330
+ consumer-side threading only: the package's `.d.ts` files are byte-identical and
331
+ English output is unchanged at every touched site.
332
+
333
+ Known gap, tracked on objectui#4553: the `percent` row still does not follow the
334
+ display locale. `formatPercent(value, precision)` takes no locale parameter —
335
+ it is `${percentDisplayValue(value).toFixed(precision)}%`, so it builds no
336
+ `Intl.NumberFormat` at all and renders in NO locale rather than the machine's
337
+ (ASCII decimal mark, never grouped, identical on every machine). Closing that
338
+ needs a `@object-ui/fields` signature change, which is outside this change's
339
+ ruled surface, and is pinned by a test here so the gap cannot drift unnoticed.
340
+
341
+ - 0ca6096: A gantt task titled `A$&B` no longer prints `{{title}}` back into its own delete dialog — the two hand-rolled provider-less fallback interpolators are literal, like i18next
342
+
343
+ objectui#3418 fixed the shared helper's fallback interpolator: `String.prototype.replace` became `split(needle).join(value)`, because `replace` and `replaceAll` both interpret `$&`, `` $` ``, `$'` and `$$` in the **replacement** string and i18next does not. Two hand-rolled copies of that interpolator never got the fix. Both are deliberate non-users of `createSafeTranslation` — each falls back per key so a host dictionary that covers the common keys but lags on newer ones still resolves what it has — so the shared fix had no path to reach them.
344
+
345
+ The reachable one is gantt's. `gantt.delete.body` is `'"{{title}}" will be permanently removed. …'` and its call site interpolates the record's own title, which is user data:
346
+
347
+ | task title | rendered before | rendered now |
348
+ | ---------- | --------------------------------------------------------------------- | ----------------------------------------- |
349
+ | `A$&B` | `"A{{title}}B" will be permanently removed.` | `"A$&B" will be permanently removed.` |
350
+ | `` x$`y `` | `"x"y" will be permanently removed.` | `` "x$`y" will be permanently removed. `` |
351
+ | `p$$q` | `"p$q" will be permanently removed.` | `"p$$q" will be permanently removed.` |
352
+ | `u$'v` | `"u" will be permanently removed. …v" will be permanently removed. …` | `"u$'v" will be permanently removed.` |
353
+
354
+ The first row is the ugly one: `$&` expands to the matched text, so the placeholder itself is printed back to the user inside the record's own name. Gantt's copy also carried the other half of the same defect — a bare string needle substitutes only the **first** occurrence, where i18next substitutes every one — and `split`/`join` fixes both at once.
355
+
356
+ The import wizard's copy used a `g`-flagged `RegExp`, which covered the repeated-placeholder half but could not touch the `$`-pattern half: that harm lives in the replacement string, not the needle. Its values are authored metadata — field labels and type names spliced into `grid.import.missingRequiredHint` and `grid.import.legacyReferenceBlocked` — so a label containing `$&` corrupted the hint the same way. Retiring the `RegExp` also retires an unescaped needle, since the placeholder name went into the pattern uninterpolated; that was inert while every placeholder name is a bare identifier, and is now structurally impossible.
357
+
358
+ This is the provider-less path only (standalone embedding, unit tests). With an `I18nProvider` mounted, i18next serves these keys and was already literal on both sides — which is exactly why the divergence was invisible. No pack, key or call site changed; the three `{{count}}` gantt keys take numbers and were never affected, and `gantt.quickFilter.resultSummary`'s deliberate single-brace idiom is resolved by its call site rather than this interpolator and is untouched.
359
+
360
+ - 36310dc: `formatPercent` groups its output and follows the display locale — the last
361
+ tooltip/cell channel (objectui#4553).
362
+
363
+ PR #4557 threaded the gantt tooltip's number and currency rows and measured that
364
+ the percent row could not follow: `formatPercent(value, precision)` took no
365
+ locale parameter, and its whole body was
366
+ `${percentDisplayValue(value).toFixed(precision)}%`. It built no
367
+ `Intl.NumberFormat` and never reached `formatDisplayNumber` — so unlike its
368
+ siblings it did not render in the MACHINE's locale, it rendered in **no** locale:
369
+ an ASCII decimal mark, never a grouping separator, byte-identical on every
370
+ machine.
371
+
372
+ **English output MOVES, and that is the fix.** Because the function never
373
+ grouped, `1235%` was wrong in en-US too, not only in German. Grouping and locale
374
+ therefore land together:
375
+
376
+ | | before | after |
377
+ | ---------- | ------- | -------------- |
378
+ | en, 1234.5 | `1235%` | `1,235%` |
379
+ | de, 1234.5 | `1235%` | `1.235\u00a0%` |
380
+ | de, 80 | `80%` | `80\u00a0%` |
381
+
382
+ Values below the grouping threshold are unchanged in English (`80%`, `12.5%`,
383
+ `33.33%`), so the move is confined to four digits and up. German changes at every
384
+ magnitude, because the no-break space before the sign is part of the locale's
385
+ percent convention — which is what routing through `Intl` buys over appending a
386
+ literal `%`.
387
+
388
+ The scaling contract is untouched: `percentDisplayValue` still disambiguates a
389
+ fraction-stored percent (`0.8` → 80%) from a whole one, so the list cell and the
390
+ dashboard measure formatter still agree.
391
+
392
+ Consumers are threaded in the same change, the parameter never landing
393
+ speculatively:
394
+
395
+ - **fields** — `PercentCellRenderer`, on BOTH of its paths. Its whole-percent
396
+ branch (`progress` / `completion` fields, which store 0-100 and must skip the
397
+ fraction scaling) was a second bare `toFixed` call; leaving it behind would
398
+ have made one grid internally inconsistent, so both branches now share one
399
+ locale-aware body and differ only in the scaling policy.
400
+ - **plugin-gantt** — the tooltip percent row, completing objectui#4553's switch.
401
+ - **plugin-grid** — the mobile card's percent cell, which sits in the same
402
+ density row as a date cell objectui#4272 had already localized.
403
+ - **plugin-dashboard** — `renderFieldValue`'s percent branch. It is a plain
404
+ function rather than a component, so it takes the locale as an optional fourth
405
+ parameter beside the `tenantCurrency` already threaded that way, and both of
406
+ its callers pass it and declare it in their memo dependency arrays.
407
+
408
+ Bumps follow each package's own `.d.ts` diff, measured in both directions.
409
+ `@object-ui/fields` and `@object-ui/plugin-dashboard` are `minor` on the
410
+ objectui#4272 / PR #4544 precedent — quoted from that changeset: "`@object-ui/fields`
411
+ is `minor` because `formatDateTime`'s new optional parameter is visible in the
412
+ package's entry `.d.ts`; the plugin packages' own `.d.ts` files are
413
+ byte-identical, so their change is module-local." Here `formatPercent` and
414
+ `renderFieldValue` each gain an entry-visible optional parameter, while
415
+ plugin-gantt's and plugin-grid's `.d.ts` files are byte-identical and stay
416
+ `patch`.
417
+
418
+ - Updated dependencies [0e67b53]
419
+ - Updated dependencies [ceccdcf]
420
+ - Updated dependencies [d6e5124]
421
+ - Updated dependencies [debad27]
422
+ - Updated dependencies [dc2aa3e]
423
+ - Updated dependencies [ee66e2e]
424
+ - Updated dependencies [e2e6360]
425
+ - Updated dependencies [ee26e65]
426
+ - Updated dependencies [5900ac5]
427
+ - Updated dependencies [932cbcd]
428
+ - Updated dependencies [734d186]
429
+ - Updated dependencies [f650253]
430
+ - Updated dependencies [6d01319]
431
+ - Updated dependencies [3d9769a]
432
+ - Updated dependencies [8f85f8b]
433
+ - Updated dependencies [d0c3b26]
434
+ - Updated dependencies [3fc2971]
435
+ - Updated dependencies [aca27fa]
436
+ - Updated dependencies [dde7283]
437
+ - Updated dependencies [f7c6430]
438
+ - Updated dependencies [4dadf0d]
439
+ - Updated dependencies [ae10a01]
440
+ - Updated dependencies [0f21348]
441
+ - Updated dependencies [d2e2caf]
442
+ - Updated dependencies [92876f0]
443
+ - Updated dependencies [f279deb]
444
+ - Updated dependencies [4b70d28]
445
+ - Updated dependencies [eb7f586]
446
+ - Updated dependencies [e901131]
447
+ - Updated dependencies [ebb4e0e]
448
+ - Updated dependencies [3a9021e]
449
+ - Updated dependencies [d9d3463]
450
+ - Updated dependencies [2a40f69]
451
+ - Updated dependencies [bec3e14]
452
+ - Updated dependencies [613b167]
453
+ - Updated dependencies [b4d3c22]
454
+ - Updated dependencies [1f9b905]
455
+ - Updated dependencies [8f60d73]
456
+ - Updated dependencies [cb13400]
457
+ - Updated dependencies [828549a]
458
+ - Updated dependencies [e1ade8f]
459
+ - Updated dependencies [bc64bfe]
460
+ - Updated dependencies [abb0f81]
461
+ - Updated dependencies [38ab505]
462
+ - Updated dependencies [63fe8fd]
463
+ - Updated dependencies [3e19fe7]
464
+ - Updated dependencies [bb58d1d]
465
+ - Updated dependencies [433ff9f]
466
+ - Updated dependencies [5cc847c]
467
+ - Updated dependencies [6314e87]
468
+ - Updated dependencies [5e2e9fa]
469
+ - Updated dependencies [297534b]
470
+ - Updated dependencies [e7663f2]
471
+ - Updated dependencies [fa21254]
472
+ - Updated dependencies [33c32bf]
473
+ - Updated dependencies [66fb4fa]
474
+ - Updated dependencies [b953a97]
475
+ - Updated dependencies [e076fd5]
476
+ - Updated dependencies [d7f3e30]
477
+ - Updated dependencies [6d641c9]
478
+ - Updated dependencies [7e4f0e5]
479
+ - Updated dependencies [a84385b]
480
+ - Updated dependencies [45e1949]
481
+ - Updated dependencies [92250d6]
482
+ - Updated dependencies [c1d939f]
483
+ - Updated dependencies [58bebf6]
484
+ - Updated dependencies [36310dc]
485
+ - Updated dependencies [52d878a]
486
+ - Updated dependencies [456aac8]
487
+ - Updated dependencies [405e808]
488
+ - Updated dependencies [49ae9f4]
489
+ - Updated dependencies [a3ae404]
490
+ - Updated dependencies [7d04b0e]
491
+ - Updated dependencies [bfdf3d4]
492
+ - Updated dependencies [bb68488]
493
+ - Updated dependencies [c0f9a4b]
494
+ - Updated dependencies [b1e42d0]
495
+ - Updated dependencies [2459a3e]
496
+ - Updated dependencies [ac853ce]
497
+ - Updated dependencies [fa51109]
498
+ - Updated dependencies [d6aa172]
499
+ - Updated dependencies [c32a8a1]
500
+ - Updated dependencies [fe52a04]
501
+ - Updated dependencies [d46f9b8]
502
+ - Updated dependencies [3f5f87c]
503
+ - Updated dependencies [2fea4d2]
504
+ - Updated dependencies [f5e1143]
505
+ - Updated dependencies [dad805d]
506
+ - Updated dependencies [7f1cb33]
507
+ - Updated dependencies [f148a64]
508
+ - Updated dependencies [bb68488]
509
+ - Updated dependencies [2e3b0c0]
510
+ - Updated dependencies [35997ce]
511
+ - Updated dependencies [9461dd3]
512
+ - Updated dependencies [78fa331]
513
+ - Updated dependencies [47f551b]
514
+ - Updated dependencies [31ab1ac]
515
+ - Updated dependencies [0082db8]
516
+ - Updated dependencies [ab04728]
517
+ - Updated dependencies [b388950]
518
+ - Updated dependencies [5bf09fd]
519
+ - Updated dependencies [06915b0]
520
+ - Updated dependencies [ff84b05]
521
+ - @object-ui/i18n@17.5.0
522
+ - @object-ui/react@17.5.0
523
+ - @object-ui/components@17.5.0
524
+ - @object-ui/plugin-detail@17.5.0
525
+ - @object-ui/core@17.5.0
526
+ - @object-ui/fields@17.5.0
527
+ - @object-ui/types@17.5.0
528
+
529
+ ## 17.4.0
530
+
531
+ ### Patch Changes
532
+
533
+ - 022002a: `PageComponentSchema.dataSource` now reaches the remaining object-bound public
534
+ blocks: `object-gantt` / `object-timeline` / `object-map` / `object-pivot` /
535
+ `object-master-detail-form` / `embeddable-form` / `record:line_items`
536
+ (objectstack#7121).
537
+
538
+ objectstack#6953 wired the spec's per-element data binding
539
+ (`dataSource: { object, view?, filter?, sort?, limit? }`) to the eight blocks it
540
+ named and left the same declaration inert on these seven. Each gates its fetch on
541
+ its own object key and nothing mapped `dataSource.object` onto it, so a page
542
+ written the way the spec documents rendered an empty gantt / an empty timeline
543
+ rail / a map with no markers / an empty cross-tab / a field-less form — with no
544
+ request and no diagnostic anywhere. Spec-valid metadata rendering nothing is the
545
+ objectstack#4413 shape.
546
+
547
+ Composition follows objectstack#5576's landed semantics unchanged, through the
548
+ shared `ElementDataSourceGate` (no change to it or to the resolution layer): a
549
+ named saved view supplies the baseline, a key written on the component itself
550
+ overrides it, an explicit binding key overrides both, `filter` AND-combines
551
+ ("additional filter criteria" — a binding can narrow a view, never widen it), and
552
+ a `view` name that does not resolve renders a configuration error on every one of
553
+ these blocks instead of degrading to the object's full scope.
554
+
555
+ Each block maps **only** the keys it genuinely reads, which for this batch means
556
+ several keys stay deliberately unmapped rather than being parked somewhere
557
+ plausible:
558
+
559
+ - `object-gantt` and `object-map` take `object` / `filter` / `sort`; neither has a
560
+ row cap or a field-list read site.
561
+ - `object-pivot` takes `object` / `filter`; a cross-tab orders itself by its own
562
+ row/column grouping and cannot be computed over a truncated page.
563
+ - `object-timeline` takes `object` only — its fetch is
564
+ `find(objectName, { options: { $top: 100 } })`, with no filter/sort read site
565
+ at all, so a named view is error-checked and then contributes nothing.
566
+ - `embeddable-form` and `object-master-detail-form` take `object` only (the
567
+ parent object, in the master-detail case); a form that writes one record has no
568
+ collection query for `filter` / `sort` / `limit` to narrow.
569
+ - `record:line_items` takes `object` onto **`childObject`** — the collection it
570
+ actually lists — and nothing else: its query is the parent FK plus a fixed
571
+ `$top: 500`, and its `columns` are editable `GridColumn` objects rather than a
572
+ field-name projection a view could supply.
573
+
574
+ The per-block coverage table, including every residual gap named above, is in
575
+ `content/docs/guide/data-source.md`.
576
+
577
+ No behaviour change for a block that carries no `dataSource`: the binding-free
578
+ path returns the schema by reference, so nothing remounts and nothing refetches.
579
+
580
+ - 6d762da: The five locale keys behind #3546's eight no-fallback `t()` call sites are now defined in all ten packs, so the built-in-view toasts, the activity-timeline source link, the wizard's required-field toast and the Gantt refresh button's accessible name are translated instead of falling back to English — or, on two surfaces, to the key itself (part of #3546).
581
+
582
+ `scripts/check-i18n-call-site-keys.mjs` measured 258 keys that a `t()` call site asks for and no pack defines. These five were the subset with no working inline default: `console.objectView.cannotEditMetaView`, `console.objectView.cannotDeleteMetaView`, `detail.viewSource`, `gantt.toolbar.refresh` and `wizard.missingRequired`. Adding a `defaultValue` is deliberately not the fix — that mechanism is what kept all 258 invisible for months.
583
+
584
+ **Two of the eight sites really did render the raw key**, and both go through a binding with nothing in front of i18next. `ObjectView.tsx` calls `useObjectTranslation()` directly, so five toasts read `console.objectView.cannotEditMetaView` / `cannotDeleteMetaView` on screen; the `|| 'Built-in views cannot be renamed.'` guards next to them were dead on every path, because i18next answers a miss with the key itself and a non-empty string never falls through `||`. Those four unreachable English strings are removed rather than repaired: one key served four call sites (rename / pin / set-as-default / configure), so the pack copy covers any change to a built-in view instead of naming one operation. `RecordActivityTimeline.tsx` fails the same way for a subtler reason — `useDetailTranslation` is `createSafeTranslation(..., 'detail.back')`, and because `detail.back` does resolve, the probe hands back i18next's `t` for every key and bypasses the defaults map wholesale, so `detail.viewSource` reached the user verbatim.
585
+
586
+ **The other two sites were not rendering a raw key**, contrary to the issue's description, and are fixed here as the milder "English in all ten languages" class. `wizard.missingRequired` is its own hook's probe key, so the probe failed and `createSafeTranslation` correctly served its English default. `gantt.toolbar.refresh` goes through `useGanttTranslation`, which deliberately does not use `createSafeTranslation` and falls back per key — so the refresh button's `aria-label` was "Refresh", in English, never the key. Screen-reader users heard an English word rather than an identifier; a `zh` session now hears 刷新.
587
+
588
+ Regression cover is provider-mounted on purpose: with no `I18nProvider` the defaults maps answer every one of these keys and the assertions pass while the console is broken, which is precisely the false-green the issue documents. For the two sites whose English output was already correct, `en` cannot discriminate before from after — the `zh` assertions are the ones that pin the fix.
589
+
590
+ - Updated dependencies [794c497]
591
+ - Updated dependencies [993336f]
592
+ - Updated dependencies [f0a625a]
593
+ - Updated dependencies [b5980f4]
594
+ - Updated dependencies [8aad9fd]
595
+ - Updated dependencies [6719877]
596
+ - Updated dependencies [56ff091]
597
+ - Updated dependencies [0186cdc]
598
+ - Updated dependencies [7864f03]
599
+ - Updated dependencies [ea41a59]
600
+ - Updated dependencies [0cbdca8]
601
+ - Updated dependencies [d229dfa]
602
+ - Updated dependencies [ecae400]
603
+ - Updated dependencies [4bc6c23]
604
+ - Updated dependencies [d3e738a]
605
+ - Updated dependencies [c3b01a7]
606
+ - Updated dependencies [f5f8744]
607
+ - Updated dependencies [7ed3360]
608
+ - Updated dependencies [69becd2]
609
+ - Updated dependencies [5e52495]
610
+ - Updated dependencies [0fa5e4d]
611
+ - Updated dependencies [b750823]
612
+ - Updated dependencies [5bfaabd]
613
+ - Updated dependencies [e06810e]
614
+ - Updated dependencies [ab3ad4f]
615
+ - Updated dependencies [65bb513]
616
+ - Updated dependencies [c97a45e]
617
+ - Updated dependencies [b19162d]
618
+ - Updated dependencies [c2fd122]
619
+ - Updated dependencies [1bd6faa]
620
+ - Updated dependencies [ac2139c]
621
+ - Updated dependencies [b14ab3a]
622
+ - Updated dependencies [e24d767]
623
+ - Updated dependencies [7b3e048]
624
+ - Updated dependencies [8c60819]
625
+ - Updated dependencies [aca561a]
626
+ - Updated dependencies [e64a52e]
627
+ - Updated dependencies [844d17f]
628
+ - Updated dependencies [d8a0be4]
629
+ - Updated dependencies [48132f7]
630
+ - Updated dependencies [4dcd52a]
631
+ - Updated dependencies [42ae5c6]
632
+ - Updated dependencies [0ef9dfd]
633
+ - Updated dependencies [f4b97c8]
634
+ - Updated dependencies [1d723e3]
635
+ - Updated dependencies [0109f54]
636
+ - Updated dependencies [7e5bb5d]
637
+ - Updated dependencies [fbc23e0]
638
+ - Updated dependencies [6d762da]
639
+ - Updated dependencies [e6fdbdc]
640
+ - Updated dependencies [4178d5a]
641
+ - Updated dependencies [54233b1]
642
+ - Updated dependencies [c2ecbae]
643
+ - Updated dependencies [acc34c5]
644
+ - Updated dependencies [c4768a7]
645
+ - Updated dependencies [f9faa7d]
646
+ - Updated dependencies [97b63d7]
647
+ - Updated dependencies [6bb454a]
648
+ - Updated dependencies [11c1e71]
649
+ - Updated dependencies [523be48]
650
+ - Updated dependencies [7e2b7e9]
651
+ - Updated dependencies [33526fd]
652
+ - Updated dependencies [32413ec]
653
+ - Updated dependencies [c1e1e6b]
654
+ - @object-ui/components@17.4.0
655
+ - @object-ui/plugin-detail@17.4.0
656
+ - @object-ui/react@17.4.0
657
+ - @object-ui/core@17.4.0
658
+ - @object-ui/fields@17.4.0
659
+ - @object-ui/i18n@17.4.0
660
+ - @object-ui/types@17.4.0
661
+
3
662
  ## 17.3.0
4
663
 
5
664
  ### Patch Changes
@@ -1188,6 +1847,7 @@ fields` formatters) and feeds `GanttView` a `task.fields` array that replaces
1188
1847
  non-en/zh locales) and the `common.addToFavorites` /
1189
1848
  `common.removeFromFavorites` keys across all ten built-in locales so
1190
1849
  the `builtInLocales` parity tests pass.
1850
+
1191
1851
  - @object-ui/components@5.0.2
1192
1852
  - @object-ui/fields@5.0.2
1193
1853
  - @object-ui/react@5.0.2
@@ -1487,6 +2147,7 @@ fields` formatters) and feeds `GanttView` a `task.fields` array that replaces
1487
2147
  Library builds (vite lib mode) now externalize every non-relative import instead of bundling third-party CJS dependencies into the published dist. This avoids inlined `require("react")` / `require("react-dom")` calls that cause `Calling \`require\` for "react" in an environment that doesn't expose the \`require\` function` runtime errors when consumer apps re-bundle the published dist.
1488
2148
 
1489
2149
  Specifically fixes:
2150
+
1490
2151
  - `@object-ui/plugin-dashboard` no longer inlines `react-grid-layout` (and its transitive `react-draggable` / `react-resizable` CJS bundles). `react-grid-layout` is now declared as a peer dependency so consumers install a single ESM-friendly copy.
1491
2152
  - `@object-ui/components`, `@object-ui/plugin-calendar`, `@object-ui/plugin-charts`, `@object-ui/plugin-designer` no longer inline `react-i18next` / `i18next` / `use-sync-external-store` CJS shims.
1492
2153
  - All plugin packages now use a unified `external: (id) => !/^[./]/.test(id) && !id.startsWith(__dirname)` rule, ensuring future additions of CJS deps are automatically externalized.
@@ -1545,24 +2206,29 @@ fields` formatters) and feeds `GanttView` a `task.fields` array that replaces
1545
2206
  - a2d7023: End-user feature batch — forms, designer history, import/export, and PWA offline sync.
1546
2207
 
1547
2208
  **Forms (`@object-ui/fields`, `@object-ui/providers`)**
2209
+
1548
2210
  - `FileField`: native `<input capture="environment">` camera capture for mobile devices, plus a uploading-progress indicator driven by `UploadProvider`.
1549
2211
  - `ImageField`: per-image inline crop/rotate via the lazy-loaded `ImageCropperDialog` (canvas-based, zero new deps).
1550
2212
  - New `UploadProvider` in `@object-ui/providers` with pluggable adapters for S3 and Azure Blob (plus the default object-URL adapter for local previews). XHR-based with progress, abort, and retry.
1551
2213
  - `LookupField`: `lookup.dependsOn: string | string[]` to chain dependent lookups (e.g. State depends on Country); the trigger is gated until parent values are present and the OData `$filter` is built automatically.
1552
2214
 
1553
2215
  **Container-aware widget widths (`@object-ui/components`)**
2216
+
1554
2217
  - New `useResizeObserver(ref)` hook exposing `{ width, height }` of any element. SSR-safe; reads the initial size via `getBoundingClientRect`.
1555
2218
  - `plugin-gantt` and `plugin-kanban` now react to their container size instead of `window.innerWidth`, so they behave correctly inside split panels and dashboards.
1556
2219
 
1557
2220
  **Designer history (`@object-ui/plugin-designer`)**
2221
+
1558
2222
  - `useUndoRedo` (and therefore `useDesignerHistory`) gains `persistKey` + `storage` options to round-trip the undo/redo stack through `sessionStorage`, plus a `clearPersisted()` cleanup helper. Drafts now survive accidental tab refreshes.
1559
2223
  - New `<HistoryPanel>` component renders the timeline visually with one-click jump-to-checkpoint via the new `jumpTo(index)` API.
1560
2224
 
1561
2225
  **Import wizard (`@object-ui/plugin-grid`)**
2226
+
1562
2227
  - Saved column-mapping templates: name, save, re-apply, and delete via a new template bar in the mapping step. Persisted under `objectui:import-templates:${objectName}` (override via `templateStorageKey` / `templateStorage`).
1563
2228
  - Inline validation correction: cells with errors in the preview step are now editable; corrections feed straight into the import without requiring a re-upload, with green-bar status indicators for fixed rows.
1564
2229
 
1565
2230
  **PWA offline sync (`@object-ui/mobile`)**
2231
+
1566
2232
  - New `MemoryOfflineQueue` / `IndexedDbOfflineQueue` (`createOfflineQueue()` picks the best backend) backed by IndexedDB.
1567
2233
  - `createOfflineDataSource(inner, { queue })` wraps any DataSource so mutations issued while offline (or that fail with a network-style error) are queued and replayed in order on reconnect. Includes `replay()`, `drop()`, `clear()`, `pending()`, an `onChange` notifier, and an opt-in `resolveConflict` hook for stale-write conflicts.
1568
2234
  - New `useOfflineSync(source)` hook exposes `{ isOnline, pending, isReplaying, replay, drop, clear }` and auto-replays on the browser's `online` event.
@@ -1571,11 +2237,13 @@ fields` formatters) and feeds `GanttView` a `task.fields` array that replaces
1571
2237
  - e93fe35: Mobile UX round 3 — Gantt and Map
1572
2238
 
1573
2239
  **@object-ui/plugin-gantt**
2240
+
1574
2241
  - Added a sticky vertical "Today" marker on the timeline plus a one-tap **Jump to Today** toolbar button so on-call users can re-orient the view instantly on small screens.
1575
2242
  - Added a **collapsible task list** (toolbar toggle + auto-collapse on the first narrow render) so the timeline area gets the full viewport on phones.
1576
2243
  - Added **pinch-to-zoom** touch gestures on the timeline; wired `columnWidthOverride` state so the existing zoom buttons also respond (previously a no-op).
1577
2244
 
1578
2245
  **@object-ui/plugin-map**
2246
+
1579
2247
  - Added a **geolocate button** with the standard `navigator.geolocation.getCurrentPosition` permission flow, an inline error banner, a busy state, and a **user-location marker** (blue dot) the map flies to on success.
1580
2248
  - **Cluster tap-through**: tapping a cluster now flies the map in (zoom + 2, capped at 20) instead of just sitting there.
1581
2249
  - On mobile, the desktop popup is replaced by a **bottom-sheet record card** with safe-area padding and an explicit close button. Desktop continues to use the popup.