tsgrid-ui 2.6.0 → 2.7.1

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
@@ -2,6 +2,125 @@
2
2
 
3
3
  All notable changes to **TsGrid UI** will be documented in this file.
4
4
 
5
+ ## v2.7.1 — 2026-05-14
6
+
7
+ ### Fixed
8
+
9
+ - **R-LOC-V26-PRESERVED — `keepPhrases=false` now actually clears phrases** (`src/tsutils-locale.ts:133`, string branch): The v2.6.0 / v2.7.0 implementation `deps.extend({}, settings, TsLocale, { phrases: {} }, data)` was a deep-merge no-op on the phrases sub-tree — `extend` is deep and `extend(existingPhrases, {})` iterates `Object.keys({})` → zero keys, leaving pre-existing phrase keys intact. v2.7.1 replaces this with the spread-override-before-extend idiom: `const phrasesCleared = { ...settings, phrases: {} }; deps.extend({}, phrasesCleared, TsLocale, data)`. Pre-existing `settings.phrases` keys are NOW cleared before the `TsLocale` + `data` phrase-merge, aligning with the original code comment "clear phrases from language before merging" (in place since v2.6.0). Covered by `T-LOC-5` (flipped) + `INV-L7-PHRASES-CLEAR` (string clause). See engram #925 (discovery).
10
+ - **R-LOC-2 — `_locale()` no longer mutates the caller's input array** (`src/tsutils-locale.ts:80,86`, array branch): Calling `await Utils.locale(['en-us', 'ru-ru'])` previously rewrote the caller's array in-place to `['locale/en-us.json', 'locale/ru-ru.json']` (alias mutation via `const localeArr = locale as string[]` + `localeArr[ind] = file`). v2.7.1 uses `.map()` upfront for path expansion, producing a fresh internal array; the caller's original array reference and contents are byte-identical post-call. Covered by new `T-LOC-11` + `INV-L7-IMMUTABLE-INPUT`.
11
+ - **R-V271-4 — Array-branch `mergedSettings` init now actually pre-resets phrases** (`src/tsutils-locale.ts:79`, array branch): `let mergedSettings = deps.extend({}, settings, { phrases: {} })` had the same deep-merge no-op as R-LOC-V26-PRESERVED — pre-existing `settings.phrases` keys leaked into the initial fanout accumulator. v2.7.1 replaces it with `let mergedSettings = deps.extend({}, { ...settings, phrases: {} })`, aligning with the comment "Pre-reset phrases before fanout" (in place since v2.7.0). Covered by new `T-LOC-12` + `INV-L7-PHRASES-CLEAR` (array clause).
12
+
13
+ ### Tests
14
+
15
+ - `T-LOC-5` flipped + header rewritten: was `expect(phrases).toHaveProperty('stale', 'remove-me')`; now `expect(phrases).not.toHaveProperty('stale')`. Positive assertion `expect(phrases).toHaveProperty('Add new record')` retained (proves the `keepPhrases=false` branch still merges TsLocale defaults). Comment block removes the `R-LOC-V26-PRESERVED` preservation note and adds the `INV-L7-PHRASES-CLEAR` reference + "v2.7.1 PATCH: phrases-clear now works as documented" one-liner.
16
+ - `T-LOC-11` NEW: array-input immutability gate (`INV-L7-IMMUTABLE-INPUT`). Snapshots the caller's input array, awaits `_locale(input, ...)`, asserts `expect(input).toEqual(snapshot)` AND `expect(input).toBe(inputRef)`. Sub-assertion: `deps.fetch` was still called with expanded paths (`'locale/en-us.json'`, `'locale/ru-ru.json'`).
17
+ - `T-LOC-12` NEW: array-branch `mergedSettings` phrases-clear gate (`INV-L7-PHRASES-CLEAR`, array clause). Sets `TsUtils.settings.phrases = { stale: 'remove-me' }` pre-call; asserts post-call `result.settings.phrases` does NOT contain `stale`.
18
+ - Total Vitest tests: **299/299 GREEN** (v2.7.0 baseline: 297/297).
19
+
20
+ ### Bundle
21
+
22
+ Delta vs v2.7.0 baseline (`e8d9a74e`):
23
+
24
+ | Artifact | v2.7.0 | v2.7.1 | Δ bytes | Δ % |
25
+ |----------|--------|--------|---------|-----|
26
+ | `dist/tsgrid-ui.js` (CJS) | 947,274 B | 947,277 B | +3 B | +0.0003% |
27
+ | `dist/tsgrid-ui.es6.js` (ESM) | 945,466 B | 945,470 B | +4 B | +0.0004% |
28
+ | `dist/tsgrid-ui.min.js` (CJS min) | 509,260 B | 509,263 B | +3 B | +0.0003% |
29
+ | `dist/tsgrid-ui.es6.min.js` (ESM min) | 508,125 B | 508,129 B | +4 B | +0.0004% |
30
+ | `dist/tsgrid-ui.d.ts` | 94,446 B | 94,446 B | 0 B | 0.0000% |
31
+ | `dist/tsgrid-ui.css` | 246,980 B | 246,979 B | -1 B | -0.0004% |
32
+ | `dist/tsgrid-ui.min.css` | 229,707 B | 229,706 B | -1 B | -0.0004% |
33
+
34
+ All within ±2% gate. PASSED. JS bundles grew by +3–4 B (3 small fixes + new INV-L7-* inline comments — Terser strips comments in min bundles so min delta is tiny). The `d.ts` is **byte-identical** to v2.7.0 (INV-L7-API PASS — `locale(` signature unchanged). CSS delta is icon-noise (non-deterministic gulp regen, ±1 B).
35
+
36
+ ### BC
37
+
38
+ - `TsUtils.locale(locale, keepPhrases?, noMerge?): Promise<{ file: string; data: unknown } | void>` — signature **BYTE-IDENTICAL** to v2.7.0. Public API surface unchanged. `dist/tsgrid-ui.d.ts` `locale(` line unchanged (INV-L7-API PASS).
39
+ - **Behavior corrections** (PATCH-classifiable per SemVer §6 — implementation now matches documented intent in all three cases):
40
+ - `await Utils.locale(lang, false /* keepPhrases */)` no longer preserves pre-existing `settings.phrases` keys (now matches the in-code comment "clear phrases from language before merging").
41
+ - `await Utils.locale([...])` no longer mutates the input array (caller's array reference + contents are byte-identical post-call).
42
+ - The internal array-branch `mergedSettings` accumulator no longer leaks pre-existing phrase keys into the fanout.
43
+ - `src/index.ts` barrel — **byte-identical** to v2.7.0 (INV-7 PASS).
44
+ - SEMVER PATCH. BC verdict: NONE for consumers using the documented behavior; latent-bug-correction for consumers depending on the never-documented broken behavior (none identified in repo audit).
45
+
46
+ ### Known issues
47
+
48
+ - **R-LOC-3** (carry-forward from v2.6 / v2.7): Locale extraction does NOT directly unblock formatter extraction. The `format()` family still has its own deps-injection requirement; will be tackled separately in v2.8 via a `FormatterDeps` cluster.
49
+ - `R-LOC-V26-PRESERVED`, `R-LOC-2`, `R-V271-4` — **REMOVED** (now fixed; see §Fixed above).
50
+
51
+ ### Internal
52
+
53
+ - `INV-L7-PHRASES-CLEAR` (NEW invariant): When `keepPhrases=false` (string branch) OR array-branch init, the resulting `settings.phrases` MUST NOT contain keys from the pre-call `settings.phrases` that are absent from `TsLocale.phrases` (and `data.phrases` for the string branch). Gated by `T-LOC-5` (flipped) + `T-LOC-12` (new).
54
+ - `INV-L7-IMMUTABLE-INPUT` (NEW invariant): `_locale()` MUST NOT mutate the `locale` argument when it is an array. Caller's array reference + contents are byte-identical post-call. Gated by `T-LOC-11` (new).
55
+ - `INV-L7-LEAF` (carry, PASS): `tsutils-locale.ts` runtime imports limited to `./tslocale.js`; type-only from `./tsutils.js`. No tsbase/tsutils runtime imports.
56
+ - `INV-9` (carry, PASS): zero `this.X` references in `_locale()` body.
57
+ - `INV-LINT-INV8` canary (carry, PASS): `src/tsutils-*.ts` ESLint glob covers `tsutils-locale.ts`.
58
+
59
+ ---
60
+
61
+ ## v2.7.0 — 2026-05-14
62
+
63
+ ### Refactor
64
+
65
+ - **`locale()` extraction — `LocaleDeps` deps-injection pattern**: Extracted the `TsUtils.locale()` method body (62 LOC, the largest remaining cluster in `tsutils.ts`) into `_locale()` in `src/tsutils-locale.ts` via a new minimal `LocaleDeps` interface (`{ extend: (target, ...sources) => object; fetch: (url, init?) => Promise<Response> }`). Class method `locale()` becomes a 4-line delegator: `return _locale(locale, keepPhrases, noMerge, this.settings, { extend: this.extend.bind(this), fetch: globalThis.fetch.bind(globalThis) }).then(result => { if (result.settings) this.settings = result.settings; return result.kind === 'load' ? { file: result.file, data: result.data } : undefined })`. Public signature `TsUtils.locale(locale, keepPhrases?, noMerge?): Promise<{ file; data } | void>` is **byte-identical** to v2.6.0. `vi.spyOn(TsUtils.prototype, 'locale')` continues to work (prototype delegator — INV-SPY PASS). Follows the v2.3 `MessageDeps` / v2.6 `DateDeps` canonical patterns. Array-form branch uses **direct `_locale()` recursion** (not delegator round-trip — design OQ-2/D2) to keep the internal call graph leaf-friendly.
66
+ - `LocaleDeps` / `LocaleResult` are **internal** types backing the deps-injection delegation pattern. They are NOT exported through `src/index.ts` barrel (INV-L7-DEPS-INTERNAL PASS; INV-7 byte-identical barrel maintained). Consumers of `TsUtils.locale()` are unaffected.
67
+
68
+ ### Fixed
69
+
70
+ - **Object-form `Utils.locale({...})` Promise hang (R-LOC-4 / INV-L7-OBJFIX)**: The v2.6.0 `locale()` body had `if (typeof locale === 'object') { this.settings = this.extend({}, this.settings, TsLocale, locale); return }` — the bare `return` inside the `new Promise((resolve, reject) => {...})` executor neither called `resolve()` nor `reject()`, so the Promise hung **forever** for any object-form input. v2.7.0 replaces the bare `return` with a value-returning path: the extracted `async function _locale()` returns `{ kind: 'merge', settings: mergedSettings }` for the object branch; the delegator unwraps to `undefined` (matching the original public contract for object form). `await Utils.locale({ dateFormat: 'dd.mm.yyyy' })` now resolves within ~1 ms (was: pending forever). Covered by test `T-LOC-1` with `vitest timeout: 1000`.
71
+
72
+ ### Tests
73
+
74
+ - Added 10-case safety-net suite for `_locale()` in `test/unit/tsutils-locale.test.ts` committed as Phase 4 RED before extraction (INV-TDD PASS — `c7e64b43` < `dda197ad` < `32538179`):
75
+ - **T-LOC-1** — object-form `_locale({ dateFormat: 'dd.mm.yyyy' })` resolves within 1 s with `{ kind: 'merge', settings.dateFormat: 'dd.mm.yyyy' }` (INV-L7-OBJFIX gate)
76
+ - **T-LOC-2** — 5-char string `'ru-ru'` auto-expanded to `'locale/ru-ru.json'` before `deps.fetch`
77
+ - **T-LOC-3** — Full-path string `'locale/ru-ru.json'` passed verbatim (no double-expansion)
78
+ - **T-LOC-4** — `keepPhrases=true` preserves pre-existing phrase keys in returned settings
79
+ - **T-LOC-5** — `keepPhrases=false` (default) executes the TsLocale-merge branch; documents `R-LOC-V26-PRESERVED` (see Known Issues)
80
+ - **T-LOC-6** — `noMerge=true` returns `{ kind: 'load', file, data }` without including merged settings
81
+ - **T-LOC-7** — Array form `['en-us', 'ru-ru']` triggers N fetch calls and resolves `{ kind: 'void' }` (delegator maps to `undefined`)
82
+ - **T-LOC-8** — `deps.fetch` rejection re-throws and rejects the returned Promise
83
+ - **T-LOC-9** — Design constraint: T-LOC-2..T-LOC-8 pass in jsdom **without** a global `fetch` polyfill (proven by T-LOC-2..T-LOC-8 collectively)
84
+ - **T-LOC-10** — `vi.spyOn(TsUtils, 'locale')` observes exactly one call when `TsUtils.locale('en-us')` is invoked (INV-SPY / INV-L7-DELEGATOR gate)
85
+ - Total Vitest tests: **297/297 GREEN** (v2.6.0 baseline: 288/288).
86
+
87
+ ### Bundle
88
+
89
+ Delta vs v2.6.0 baseline:
90
+
91
+ | Artifact | v2.6.0 | v2.7.0 | Δ bytes | Δ % |
92
+ |----------|--------|--------|---------|-----|
93
+ | `dist/tsgrid-ui.js` (CJS) | 946,686 B | 947,274 B | +588 B | +0.0621% |
94
+ | `dist/tsgrid-ui.es6.js` (ESM) | 944,878 B | 945,466 B | +588 B | +0.0622% |
95
+ | `dist/tsgrid-ui.min.js` (CJS min) | 508,954 B | 509,260 B | +306 B | +0.0601% |
96
+ | `dist/tsgrid-ui.es6.min.js` (ESM min) | 507,819 B | 508,125 B | +306 B | +0.0603% |
97
+ | `dist/tsgrid-ui.d.ts` | 94,446 B | 94,446 B | 0 B | 0.0000% |
98
+ | `dist/tsgrid-ui.css` | 246,980 B | 246,980 B | 0 B | 0.0000% |
99
+ | `dist/tsgrid-ui.min.css` | 229,707 B | 229,707 B | 0 B | 0.0000% |
100
+
101
+ All within ±2% gate. PASSED. The JS bundles grew by ~0.06% (the new `_locale` function body + deps wiring); the `d.ts` is **byte-identical** to v2.6.0 (INV-L7-API PASS — `locale(` signature unchanged, `LocaleDeps`/`LocaleResult` declared `@internal` and stripped by tsup `stripInternal: true`). CSS/min.css unchanged (no css edits in v2.7).
102
+
103
+ ### BC
104
+
105
+ - `TsUtils.locale(locale, keepPhrases?, noMerge?): Promise<{ file: string; data: unknown } | void>` — signature and (now-resolving) runtime behavior **BYTE-IDENTICAL** to v2.6.0 for all string/array inputs. **Object-form input changes**: previously hung forever (effectively unusable); now resolves with `undefined` after merging. Existing call sites using string/array inputs work unchanged; existing call sites using object-form were necessarily broken (Promise never settled) and now begin working.
106
+ - `src/index.ts` barrel — **byte-identical** to v2.6.0 (INV-7 PASS).
107
+ - `LocaleDeps` / `LocaleResult` — NEW **internal** types. NOT exported through `src/index.ts` barrel (INV-L7-DEPS-INTERNAL PASS). `LocaleResult` declared `/** @internal */` and stripped from the rolled d.ts. `LocaleDeps` is type-only at the delegator call site and does not affect the d.ts surface.
108
+ - SEMVER MINOR. BC verdict: NONE for string/array inputs; BUGFIX for object-form input (R-LOC-4).
109
+
110
+ ### Known issues
111
+
112
+ - **R-LOC-2** (deferred from v2.6 — confirmed still present): Array-form input `await Utils.locale(['en-us', 'ru-ru'])` MUTATES the caller's input array (each 5-char code is rewritten in-place to `'locale/xx-xx.json'`). Preserved verbatim in v2.7. Candidate fix in v2.8.
113
+ - **R-LOC-V26-PRESERVED** (newly documented): `keepPhrases=false` branch in v2.6.0 had comment "clear phrases from language before merging" but the implementation `extend({}, settings, TsLocale, { phrases: {} }, data)` does **not** actually clear pre-existing phrase keys — `extend` is deep, and `extend(existingPhrases, {})` is a no-op. Pre-existing phrase keys survive the merge in both v2.6.0 and v2.7.0. v2.7 scope was constrained to **one** behavior change (R-LOC-4 — object-form hang), so this latent behavior is preserved verbatim with `T-LOC-5` documenting the actual semantics. Candidate fix in v2.8.
114
+ - **R-LOC-3** (carry-forward from v2.6): Locale extraction does NOT unblock formatter extraction. The `format()` family still has its own deps-injection requirement; will be tackled separately in v2.8 via a `FormatterDeps` cluster.
115
+
116
+ ### Internal
117
+
118
+ - `src/tsutils-locale.ts` is a **leaf module** (INV-L7-LEAF PASS): zero runtime imports from `./tsbase.js` or `./tsutils.js`. Only `import type { TsUISettings } from './tsutils.js'` (erased at emit) and `import { TsLocale } from './tslocale.js'` (zero-dep data leaf). DAG depth unchanged.
119
+ - INV-9 (zero `this.` in extracted function body) PASS.
120
+ - INV-LINT-INV8 canary: `src/tsutils-*.ts` ESLint glob continues to cover the new file (verified by inserting `void arguments.length;` → `pnpm lint` EXIT 1 → revert → EXIT 0).
121
+
122
+ ---
123
+
5
124
  ## v2.6.0 — 2026-05-13
6
125
 
7
126
  ### Refactor
@@ -1,4 +1,4 @@
1
- /* tsgrid-ui 1.0.x (nightly) (5/13/2026, 10:54:27 PM) (c) 2014 vitmalina@gmail.com, (c) 2026 DaverSoGT — MIT */
1
+ /* tsgrid-ui 1.0.x (nightly) (5/14/2026, 1:17:19 AM) (c) 2014 vitmalina@gmail.com, (c) 2026 DaverSoGT — MIT */
2
2
  /**
3
3
  * TODO:
4
4
  * - remove default styling, only keep tsg-* styles
@@ -9,7 +9,7 @@
9
9
  */
10
10
  @font-face {
11
11
  font-family: "tsgrid-font";
12
- src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff");
12
+ src: url("data:application/x-font-woff;charset=utf-8;base64,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") format("woff");
13
13
  font-weight: normal;
14
14
  font-style: normal;
15
15
  }
@@ -3007,6 +3007,58 @@ function _date(dateStr, settings, deps) {
3007
3007
  return '<span title="' + dd1 + " " + time2 + '">' + dsp + "</span>";
3008
3008
  }
3009
3009
 
3010
+ // src/tsutils-locale.ts
3011
+ async function _locale(locale, keepPhrases, noMerge, settings, deps) {
3012
+ if (Array.isArray(locale)) {
3013
+ let mergedSettings = deps.extend({}, { ...settings, phrases: {} });
3014
+ const localeArr = locale.map(
3015
+ (f) => f.length === 5 ? "locale/" + f.toLowerCase() + ".json" : f
3016
+ );
3017
+ const proms = [];
3018
+ const files = {};
3019
+ localeArr.forEach((file) => {
3020
+ proms.push(_locale(file, true, false, mergedSettings, deps));
3021
+ });
3022
+ const res = await Promise.allSettled(proms);
3023
+ res.forEach((r) => {
3024
+ if (r.status === "fulfilled" && r.value.kind === "load") {
3025
+ files[r.value.file] = r.value.data;
3026
+ }
3027
+ });
3028
+ localeArr.forEach((file) => {
3029
+ mergedSettings = deps.extend({}, mergedSettings, files[file] ?? {});
3030
+ });
3031
+ return { kind: "void", settings: mergedSettings };
3032
+ }
3033
+ if (!locale) locale = "en-us";
3034
+ if (typeof locale === "object") {
3035
+ const mergedSettings = deps.extend({}, settings, TsLocale, locale);
3036
+ return { kind: "merge", settings: mergedSettings };
3037
+ }
3038
+ let localeStr = locale;
3039
+ if (localeStr.length === 5) {
3040
+ localeStr = "locale/" + localeStr.toLowerCase() + ".json";
3041
+ }
3042
+ try {
3043
+ const res = await deps.fetch(localeStr, { method: "GET" });
3044
+ const data = await res.json();
3045
+ if (noMerge !== true) {
3046
+ if (keepPhrases) {
3047
+ const newSettings = deps.extend({}, settings, data);
3048
+ return { kind: "load", file: localeStr, data, settings: newSettings };
3049
+ } else {
3050
+ const phrasesCleared = { ...settings, phrases: {} };
3051
+ const newSettings = deps.extend({}, phrasesCleared, TsLocale, data);
3052
+ return { kind: "load", file: localeStr, data, settings: newSettings };
3053
+ }
3054
+ }
3055
+ return { kind: "load", file: localeStr, data };
3056
+ } catch (err) {
3057
+ console.log("ERROR: Cannot load locale " + localeStr);
3058
+ throw err;
3059
+ }
3060
+ }
3061
+
3010
3062
  // src/tsutils.ts
3011
3063
  var query7 = query;
3012
3064
  var Utils = class {
@@ -3490,52 +3542,13 @@ var Utils = class {
3490
3542
  return this.execTemplate(translation, params);
3491
3543
  }
3492
3544
  locale(locale, keepPhrases, noMerge) {
3493
- return new Promise((resolve, reject) => {
3494
- if (Array.isArray(locale)) {
3495
- this.settings.phrases = {};
3496
- const proms = [];
3497
- const files = {};
3498
- const localeArr = locale;
3499
- localeArr.forEach((file, ind) => {
3500
- if (file.length === 5) {
3501
- file = "locale/" + file.toLowerCase() + ".json";
3502
- localeArr[ind] = file;
3503
- }
3504
- proms.push(this.locale(file, true, false));
3505
- });
3506
- Promise.allSettled(proms).then((res) => {
3507
- res.forEach((r) => {
3508
- if (r.value) files[r.value.file] = r.value.data;
3509
- });
3510
- localeArr.forEach((file) => {
3511
- this.settings = this.extend({}, this.settings, files[file]);
3512
- });
3513
- resolve();
3514
- });
3515
- return;
3516
- }
3517
- if (!locale) locale = "en-us";
3518
- if (typeof locale === "object") {
3519
- this.settings = this.extend({}, this.settings, TsLocale, locale);
3520
- return;
3521
- }
3522
- let localeStr = locale;
3523
- if (localeStr.length === 5) {
3524
- localeStr = "locale/" + localeStr.toLowerCase() + ".json";
3525
- }
3526
- fetch(localeStr, { method: "GET" }).then((res) => res.json()).then((data) => {
3527
- if (noMerge !== true) {
3528
- if (keepPhrases) {
3529
- this.settings = this.extend({}, this.settings, data);
3530
- } else {
3531
- this.settings = this.extend({}, this.settings, TsLocale, { phrases: {} }, data);
3532
- }
3533
- }
3534
- resolve({ file: localeStr, data });
3535
- }).catch((err) => {
3536
- console.log("ERROR: Cannot load locale " + localeStr);
3537
- reject(err);
3538
- });
3545
+ const deps = {
3546
+ extend: this.extend.bind(this),
3547
+ fetch: globalThis.fetch.bind(globalThis)
3548
+ };
3549
+ return _locale(locale, keepPhrases, noMerge, this.settings, deps).then((result) => {
3550
+ if (result.settings) this.settings = result.settings;
3551
+ return result.kind === "load" ? { file: result.file, data: result.data } : void 0;
3539
3552
  });
3540
3553
  }
3541
3554
  scrollBarSize() {