@livenetworks/ashlar 2.1.12 → 2.2.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.
Files changed (108) hide show
  1. package/README.md +25 -2
  2. package/components/COMPONENTS.md +29 -25
  3. package/components/ln-api-connector/ln-api-connector.schema.json +105 -105
  4. package/components/ln-chart/ln-chart-dev.scss +0 -4
  5. package/components/ln-core/README.md +16 -60
  6. package/components/ln-core/index.js +1 -1
  7. package/components/ln-core/ln-core.schema.json +120 -120
  8. package/components/ln-core/query.js +113 -113
  9. package/components/ln-core/reactive.js +0 -59
  10. package/components/ln-data-coordinator/README.md +2 -0
  11. package/components/ln-data-coordinator/ln-data-coordinator.js +1 -1
  12. package/components/ln-data-coordinator/ln-data-coordinator.schema.json +196 -196
  13. package/components/ln-data-coordinator/src/ln-data-coordinator.js +457 -432
  14. package/components/ln-data-store/ln-data-store.schema.json +92 -92
  15. package/components/ln-date/ln-date.js +1 -1
  16. package/components/ln-date/src/ln-date.js +1 -1
  17. package/components/ln-debug/ln-debug.js +1 -1
  18. package/components/ln-debug/ln-debug.schema.json +28 -28
  19. package/components/ln-debug/src/generated-attributes.js +304 -302
  20. package/components/ln-dropdown/ln-dropdown.schema.json +90 -90
  21. package/components/ln-external-links/README.md +19 -19
  22. package/components/ln-external-links/ln-external-links.js +1 -1
  23. package/components/ln-external-links/ln-external-links.schema.json +9 -0
  24. package/components/ln-external-links/ln-external-links.scss +12 -0
  25. package/components/ln-external-links/src/ln-external-links.js +3 -2
  26. package/components/ln-filter/README.md +2 -1
  27. package/components/ln-filter/ln-filter.js +1 -1
  28. package/components/ln-filter/ln-filter.schema.json +157 -169
  29. package/components/ln-filter/src/ln-filter.js +4 -4
  30. package/components/ln-form/ln-form.schema.json +57 -57
  31. package/components/ln-key/ln-key.js +1 -1
  32. package/components/ln-key/ln-key.schema.json +8 -0
  33. package/components/ln-key/src/ln-key.js +8 -5
  34. package/components/ln-obfuscator/ln-obfuscator.js +1 -1
  35. package/components/ln-obfuscator/ln-obfuscator.schema.json +6 -0
  36. package/components/ln-obfuscator/src/ln-obfuscator.js +2 -2
  37. package/components/ln-options/ln-options.schema.json +47 -47
  38. package/components/ln-popover/ln-popover.schema.json +82 -82
  39. package/components/ln-progress/ln-progress.schema.json +0 -13
  40. package/components/ln-search/ln-search.schema.json +111 -111
  41. package/components/ln-sort/README.md +1 -0
  42. package/components/ln-sort/ln-sort.js +1 -1
  43. package/components/ln-sort/ln-sort.schema.json +100 -112
  44. package/components/ln-sort/src/sort-model.js +1 -6
  45. package/components/ln-sortable/ln-sortable.schema.json +38 -38
  46. package/components/ln-sortable/ln-sortable.scss +25 -25
  47. package/components/ln-table/README.md +8 -1
  48. package/components/ln-table/ln-table.js +1 -1
  49. package/components/ln-table/ln-table.schema.json +195 -195
  50. package/components/ln-table/src/ln-table.js +43 -7
  51. package/components/ln-table-coordinator/ln-table-coordinator.js +1 -1
  52. package/components/ln-table-coordinator/ln-table-coordinator.schema.json +18 -0
  53. package/components/ln-table-coordinator/src/ln-table-coordinator.js +104 -2
  54. package/components/ln-toast/README.md +1 -1
  55. package/components/ln-toast/ln-toast.js +1 -1
  56. package/components/ln-toast/ln-toast.schema.json +6 -0
  57. package/components/ln-toast/src/ln-toast.js +1 -1
  58. package/components/ln-validate/README.md +6 -6
  59. package/components/ln-validate/ln-validate-dev.scss +0 -5
  60. package/components/ln-validate/ln-validate.js +1 -1
  61. package/components/ln-validate/ln-validate.schema.json +0 -6
  62. package/components/ln-validate/src/ln-validate.js +48 -29
  63. package/ln-ashlar.html-data.json +1824 -1814
  64. package/package.json +15 -5
  65. package/theme/README.md +74 -0
  66. package/theme/base/_global.scss +2 -2
  67. package/theme/base/_reset.scss +11 -0
  68. package/theme/components/_app-shell.scss +0 -8
  69. package/theme/components/_form.scss +6 -6
  70. package/theme/components/_ln-table.scss +6 -3
  71. package/theme/components/_scrollbar.scss +5 -4
  72. package/theme/components/_sortable.scss +19 -19
  73. package/theme/components/_stat-card.scss +1 -0
  74. package/theme/components/_table.scss +11 -35
  75. package/theme/config/_density.scss +110 -65
  76. package/theme/config/_palette.scss +71 -49
  77. package/theme/config/_theme.scss +27 -31
  78. package/theme/config/_tokens.scss +28 -26
  79. package/theme/config/mixins/_app-shell.scss +17 -21
  80. package/theme/config/mixins/_avatar.scss +2 -2
  81. package/theme/config/mixins/_btn.scss +4 -2
  82. package/theme/config/mixins/_card.scss +88 -71
  83. package/theme/config/mixins/_colors.scss +2 -1
  84. package/theme/config/mixins/_display.scss +12 -0
  85. package/theme/config/mixins/_footer.scss +3 -1
  86. package/theme/config/mixins/_form.scss +74 -55
  87. package/theme/config/mixins/_layout.scss +9 -0
  88. package/theme/config/mixins/_ln-table.scss +22 -15
  89. package/theme/config/mixins/_modal.scss +3 -0
  90. package/theme/config/mixins/_nav.scss +31 -8
  91. package/theme/config/mixins/_sidebar.scss +2 -1
  92. package/theme/config/mixins/_spacing.scss +4 -4
  93. package/theme/config/mixins/_stat-card.scss +33 -2
  94. package/theme/config/mixins/_status-badge.scss +1 -0
  95. package/theme/config/mixins/_table.scss +87 -69
  96. package/theme/config/mixins/_tabs.scss +31 -0
  97. package/theme/config/mixins/_toast.scss +0 -1
  98. package/theme/designs/relaxed/_card.scss +12 -0
  99. package/theme/designs/relaxed/_layout.scss +24 -0
  100. package/theme/ln-ashlar-core.scss +1 -0
  101. package/theme/ln-ashlar-dev.scss +0 -1
  102. package/theme/ln-ashlar-relaxed.scss +67 -0
  103. package/theme/tailwind/components.css +2198 -0
  104. package/theme/tailwind/entry.css +3 -0
  105. package/theme/tailwind/theme.css +23 -0
  106. package/theme/utilities/_utilities.scss +1 -11
  107. package/web-types.json +2351 -2337
  108. package/components/ln-progress/ln-progress-dev.scss +0 -8
@@ -1,120 +1,120 @@
1
- {
2
- "$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
3
- "component": "ln-core",
4
- "generator": "scripts/sync-ln-schemas.mjs",
5
- "attributes": {
6
- "data-ln-attr": {
7
- "direction": "author",
8
- "sources": [
9
- "template.js"
10
- ]
11
- },
12
- "data-ln-class": {
13
- "direction": "author",
14
- "sources": [
15
- "template.js"
16
- ]
17
- },
18
- "data-ln-debug": {
19
- "direction": "author",
20
- "sources": [
21
- "events.js"
22
- ]
23
- },
24
- "data-ln-field": {
25
- "direction": "author",
26
- "sources": [
27
- "template.js"
28
- ]
29
- },
30
- "data-ln-fill-as": {
31
- "direction": "author",
32
- "sources": [
33
- "form.js"
34
- ]
35
- },
36
- "data-ln-fillable": {
37
- "direction": "author",
38
- "sources": [
39
- "template.js"
40
- ]
41
- },
42
- "data-ln-filter": {
43
- "direction": "author",
44
- "sources": [
45
- "hash.js",
46
- "query.js"
47
- ]
48
- },
49
- "data-ln-form": {
50
- "direction": "author",
51
- "sources": [
52
- "template.js"
53
- ]
54
- },
55
- "data-ln-hash": {
56
- "direction": "author",
57
- "sources": [
58
- "hash.js"
59
- ]
60
- },
61
- "data-ln-persist": {
62
- "direction": "author",
63
- "sources": [
64
- "lifecycle.js"
65
- ]
66
- },
67
- "data-ln-query": {
68
- "direction": "author",
69
- "sources": [
70
- "query.js"
71
- ]
72
- },
73
- "data-ln-render-key": {
74
- "direction": "both",
75
- "sources": [
76
- "template.js"
77
- ]
78
- },
79
- "data-ln-search": {
80
- "direction": "author",
81
- "sources": [
82
- "hash.js",
83
- "query.js"
84
- ]
85
- },
86
- "data-ln-search-for": {
87
- "direction": "author",
88
- "sources": [
89
- "hash.js",
90
- "query.js"
91
- ]
92
- },
93
- "data-ln-show": {
94
- "direction": "author",
95
- "sources": [
96
- "template.js"
97
- ]
98
- },
99
- "data-ln-sort": {
100
- "direction": "author",
101
- "sources": [
102
- "hash.js",
103
- "query.js"
104
- ]
105
- },
106
- "data-ln-template": {
107
- "direction": "author",
108
- "sources": [
109
- "template.js"
110
- ]
111
- },
112
- "data-ln-value": {
113
- "direction": "both",
114
- "sources": [
115
- "dom.js",
116
- "form.js"
117
- ]
118
- }
119
- }
120
- }
1
+ {
2
+ "$comment": "This file is partly generated. The $comment, component, generator, and each attribute's direction, sources, type, values, fallback, and description keys are owned by the generator and synchronized from JS source code. All other keys are preserved and safe to hand-author. Regenerate with `npm run sync:ln-schemas`.",
3
+ "component": "ln-core",
4
+ "generator": "scripts/sync-ln-schemas.mjs",
5
+ "attributes": {
6
+ "data-ln-attr": {
7
+ "direction": "author",
8
+ "sources": [
9
+ "template.js"
10
+ ]
11
+ },
12
+ "data-ln-class": {
13
+ "direction": "author",
14
+ "sources": [
15
+ "template.js"
16
+ ]
17
+ },
18
+ "data-ln-debug": {
19
+ "direction": "author",
20
+ "sources": [
21
+ "events.js"
22
+ ]
23
+ },
24
+ "data-ln-field": {
25
+ "direction": "author",
26
+ "sources": [
27
+ "template.js"
28
+ ]
29
+ },
30
+ "data-ln-fill-as": {
31
+ "direction": "author",
32
+ "sources": [
33
+ "form.js"
34
+ ]
35
+ },
36
+ "data-ln-fillable": {
37
+ "direction": "author",
38
+ "sources": [
39
+ "template.js"
40
+ ]
41
+ },
42
+ "data-ln-filter": {
43
+ "direction": "author",
44
+ "sources": [
45
+ "hash.js",
46
+ "query.js"
47
+ ]
48
+ },
49
+ "data-ln-form": {
50
+ "direction": "author",
51
+ "sources": [
52
+ "template.js"
53
+ ]
54
+ },
55
+ "data-ln-hash": {
56
+ "direction": "author",
57
+ "sources": [
58
+ "hash.js"
59
+ ]
60
+ },
61
+ "data-ln-persist": {
62
+ "direction": "author",
63
+ "sources": [
64
+ "lifecycle.js"
65
+ ]
66
+ },
67
+ "data-ln-query": {
68
+ "direction": "author",
69
+ "sources": [
70
+ "query.js"
71
+ ]
72
+ },
73
+ "data-ln-render-key": {
74
+ "direction": "both",
75
+ "sources": [
76
+ "template.js"
77
+ ]
78
+ },
79
+ "data-ln-search": {
80
+ "direction": "author",
81
+ "sources": [
82
+ "hash.js",
83
+ "query.js"
84
+ ]
85
+ },
86
+ "data-ln-search-for": {
87
+ "direction": "author",
88
+ "sources": [
89
+ "hash.js",
90
+ "query.js"
91
+ ]
92
+ },
93
+ "data-ln-show": {
94
+ "direction": "author",
95
+ "sources": [
96
+ "template.js"
97
+ ]
98
+ },
99
+ "data-ln-sort": {
100
+ "direction": "author",
101
+ "sources": [
102
+ "hash.js",
103
+ "query.js"
104
+ ]
105
+ },
106
+ "data-ln-template": {
107
+ "direction": "author",
108
+ "sources": [
109
+ "template.js"
110
+ ]
111
+ },
112
+ "data-ln-value": {
113
+ "direction": "both",
114
+ "sources": [
115
+ "dom.js",
116
+ "form.js"
117
+ ]
118
+ }
119
+ }
120
+ }
@@ -1,113 +1,113 @@
1
- // ─── Query String codec (URL search params as reactive state) ──
2
- //
3
- // Allows components (ln-search, ln-filter, ln-sort) to bind to URL search
4
- // parameters (?search=foo&status=active&sort=amount.desc) in SPAs without
5
- // polluting or colliding with the URL hash fragment (#modal, #tab).
6
- //
7
- // Write mechanism: `history.replaceState` (does not reload the page or pollute history).
8
- // Read mechanism: `URLSearchParams` on window.location.search.
9
- // Navigation: Reacts to `popstate` events on browser Back/Forward navigation.
10
-
11
- function _stripSearch(str) {
12
- return (str || '').replace(/^\?/, '');
13
- }
14
-
15
- export function queryParse(searchStr) {
16
- const raw = (searchStr === undefined)
17
- ? (typeof window !== 'undefined' ? window.location.search : '')
18
- : searchStr;
19
- const map = {};
20
- const s = _stripSearch(raw);
21
- if (!s) return map;
22
- const params = new URLSearchParams(s);
23
- for (const [key, val] of params.entries()) {
24
- map[key] = val;
25
- }
26
- return map;
27
- }
28
-
29
- export function queryGet(key) {
30
- if (!key || typeof window === 'undefined') return null;
31
- const params = new URLSearchParams(window.location.search);
32
- return params.has(key) ? params.get(key) : null;
33
- }
34
-
35
- export function querySet(key, value) {
36
- if (!key || typeof window === 'undefined') return;
37
- const url = new URL(window.location.href);
38
-
39
- if (value === null || value === undefined || value === '') {
40
- url.searchParams.delete(key);
41
- } else {
42
- url.searchParams.set(key, String(value));
43
- }
44
-
45
- if (url.search === window.location.search) return;
46
-
47
- const nextUrl = url.pathname + url.search + url.hash;
48
- window.history.replaceState(window.history.state, '', nextUrl);
49
- }
50
-
51
- export function resolveQueryParam(el, defaultSuffix) {
52
- if (!el || !el.hasAttribute('data-ln-query')) return null;
53
- const explicit = el.getAttribute('data-ln-query');
54
- if (explicit && explicit.trim() !== '') return explicit.trim();
55
- const targetId = el.getAttribute('data-ln-sort')
56
- || el.getAttribute('data-ln-search-for')
57
- || el.getAttribute('data-ln-search')
58
- || el.getAttribute('data-ln-filter')
59
- || el.id;
60
- if (targetId) {
61
- return defaultSuffix ? (targetId + '_' + defaultSuffix) : targetId;
62
- }
63
- return defaultSuffix || null;
64
- }
65
-
66
- export function querySortEncode(fieldOrColumn, direction) {
67
- if (!direction || direction === 'none' || fieldOrColumn === null || fieldOrColumn === undefined) return null;
68
- return String(fieldOrColumn) + '.' + direction;
69
- }
70
-
71
- export function querySortDecode(val, queryParams) {
72
- if (val && typeof val === 'string') {
73
- if (val.endsWith('.asc')) {
74
- return { fieldOrColumn: val.slice(0, -4), direction: 'asc' };
75
- }
76
- if (val.endsWith('.desc')) {
77
- return { fieldOrColumn: val.slice(0, -5), direction: 'desc' };
78
- }
79
- }
80
- if (queryParams && typeof queryParams === 'object') {
81
- const field = queryParams.sort_field || queryParams.sort_by;
82
- if (field) {
83
- const dir = (queryParams.sort_dir && String(queryParams.sort_dir).toLowerCase() === 'desc') ? 'desc' : 'asc';
84
- return { fieldOrColumn: field, direction: dir };
85
- }
86
- }
87
- return null;
88
- }
89
-
90
- export function queryFilterEncode(values) {
91
- if (!Array.isArray(values) || values.length === 0) return null;
92
- return values.map(encodeURIComponent).join(',');
93
- }
94
-
95
- export function queryFilterDecode(val) {
96
- if (!val || typeof val !== 'string') return [];
97
- return val.split(',').map(function (v) {
98
- try { return decodeURIComponent(v.trim()); }
99
- catch (e) { return v.trim(); }
100
- }).filter(Boolean);
101
- }
102
-
103
- if (typeof window !== 'undefined') {
104
- window.lnCore = window.lnCore || {};
105
- window.lnCore.queryParse = queryParse;
106
- window.lnCore.queryGet = queryGet;
107
- window.lnCore.querySet = querySet;
108
- window.lnCore.resolveQueryParam = resolveQueryParam;
109
- window.lnCore.querySortEncode = querySortEncode;
110
- window.lnCore.querySortDecode = querySortDecode;
111
- window.lnCore.queryFilterEncode = queryFilterEncode;
112
- window.lnCore.queryFilterDecode = queryFilterDecode;
113
- }
1
+ // ─── Query String codec (URL search params as reactive state) ──
2
+ //
3
+ // Allows components (ln-search, ln-filter, ln-sort) to bind to URL search
4
+ // parameters (?search=foo&status=active&sort=amount.desc) in SPAs without
5
+ // polluting or colliding with the URL hash fragment (#modal, #tab).
6
+ //
7
+ // Write mechanism: `history.replaceState` (does not reload the page or pollute history).
8
+ // Read mechanism: `URLSearchParams` on window.location.search.
9
+ // Navigation: Reacts to `popstate` events on browser Back/Forward navigation.
10
+
11
+ function _stripSearch(str) {
12
+ return (str || '').replace(/^\?/, '');
13
+ }
14
+
15
+ export function queryParse(searchStr) {
16
+ const raw = (searchStr === undefined)
17
+ ? (typeof window !== 'undefined' ? window.location.search : '')
18
+ : searchStr;
19
+ const map = {};
20
+ const s = _stripSearch(raw);
21
+ if (!s) return map;
22
+ const params = new URLSearchParams(s);
23
+ for (const [key, val] of params.entries()) {
24
+ map[key] = val;
25
+ }
26
+ return map;
27
+ }
28
+
29
+ export function queryGet(key) {
30
+ if (!key || typeof window === 'undefined') return null;
31
+ const params = new URLSearchParams(window.location.search);
32
+ return params.has(key) ? params.get(key) : null;
33
+ }
34
+
35
+ export function querySet(key, value) {
36
+ if (!key || typeof window === 'undefined') return;
37
+ const url = new URL(window.location.href);
38
+
39
+ if (value === null || value === undefined || value === '') {
40
+ url.searchParams.delete(key);
41
+ } else {
42
+ url.searchParams.set(key, String(value));
43
+ }
44
+
45
+ if (url.search === window.location.search) return;
46
+
47
+ const nextUrl = url.pathname + url.search + url.hash;
48
+ window.history.replaceState(window.history.state, '', nextUrl);
49
+ }
50
+
51
+ export function resolveQueryParam(el, defaultSuffix) {
52
+ if (!el || !el.hasAttribute('data-ln-query')) return null;
53
+ const explicit = el.getAttribute('data-ln-query');
54
+ if (explicit && explicit.trim() !== '') return explicit.trim();
55
+ const targetId = el.getAttribute('data-ln-sort')
56
+ || el.getAttribute('data-ln-search-for')
57
+ || el.getAttribute('data-ln-search')
58
+ || el.getAttribute('data-ln-filter')
59
+ || el.id;
60
+ if (targetId) {
61
+ return defaultSuffix ? (targetId + '_' + defaultSuffix) : targetId;
62
+ }
63
+ return defaultSuffix || null;
64
+ }
65
+
66
+ export function querySortEncode(fieldOrColumn, direction) {
67
+ if (!direction || direction === 'none' || fieldOrColumn === null || fieldOrColumn === undefined) return null;
68
+ return String(fieldOrColumn) + '.' + direction;
69
+ }
70
+
71
+ export function querySortDecode(val, queryParams) {
72
+ if (val && typeof val === 'string') {
73
+ if (val.endsWith('.asc')) {
74
+ return { fieldOrColumn: val.slice(0, -4), direction: 'asc' };
75
+ }
76
+ if (val.endsWith('.desc')) {
77
+ return { fieldOrColumn: val.slice(0, -5), direction: 'desc' };
78
+ }
79
+ }
80
+ if (queryParams && typeof queryParams === 'object') {
81
+ const field = queryParams.sort_field || queryParams.sort_by;
82
+ if (field) {
83
+ const dir = (queryParams.sort_dir && String(queryParams.sort_dir).toLowerCase() === 'desc') ? 'desc' : 'asc';
84
+ return { fieldOrColumn: field, direction: dir };
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+
90
+ export function queryFilterEncode(values) {
91
+ if (!Array.isArray(values) || values.length === 0) return null;
92
+ return values.map(encodeURIComponent).join(',');
93
+ }
94
+
95
+ export function queryFilterDecode(val) {
96
+ if (!val || typeof val !== 'string') return [];
97
+ return val.split(',').map(function (v) {
98
+ try { return decodeURIComponent(v.trim()); }
99
+ catch (e) { return v.trim(); }
100
+ }).filter(Boolean);
101
+ }
102
+
103
+ if (typeof window !== 'undefined') {
104
+ window.lnCore = window.lnCore || {};
105
+ window.lnCore.queryParse = queryParse;
106
+ window.lnCore.queryGet = queryGet;
107
+ window.lnCore.querySet = querySet;
108
+ window.lnCore.resolveQueryParam = resolveQueryParam;
109
+ window.lnCore.querySortEncode = querySortEncode;
110
+ window.lnCore.querySortDecode = querySortDecode;
111
+ window.lnCore.queryFilterEncode = queryFilterEncode;
112
+ window.lnCore.queryFilterDecode = queryFilterDecode;
113
+ }
@@ -1,62 +1,3 @@
1
- // ─── Shallow Reactive ──────────────────────────────────────
2
-
3
- export function reactiveState(initial, onChange) {
4
- return new Proxy(Object.assign({}, initial), {
5
- set(target, prop, value) {
6
- const old = target[prop];
7
- if (old === value) return true;
8
- target[prop] = value;
9
- onChange(prop, value, old);
10
- return true;
11
- }
12
- });
13
- }
14
-
15
- // ─── Deep Reactive ─────────────────────────────────────────
16
-
17
- const PROXY_FLAG = Symbol('deepReactive');
18
-
19
- export function deepReactive(obj, onChange) {
20
- function wrap(target) {
21
- if (target === null || typeof target !== 'object') return target;
22
- if (target[PROXY_FLAG]) return target;
23
-
24
- // Recursively wrap existing nested objects/arrays
25
- const keys = Object.keys(target);
26
- for (let i = 0; i < keys.length; i++) {
27
- const val = target[keys[i]];
28
- if (val !== null && typeof val === 'object') {
29
- target[keys[i]] = wrap(val);
30
- }
31
- }
32
-
33
- return new Proxy(target, {
34
- get(t, prop) {
35
- if (prop === PROXY_FLAG) return true;
36
- return t[prop];
37
- },
38
- set(t, prop, value) {
39
- const old = t[prop];
40
- if (value !== null && typeof value === 'object') {
41
- value = wrap(value);
42
- }
43
- t[prop] = value;
44
- if (old !== value) onChange();
45
- return true;
46
- },
47
- deleteProperty(t, prop) {
48
- if (prop in t) {
49
- delete t[prop];
50
- onChange();
51
- }
52
- return true;
53
- }
54
- });
55
- }
56
-
57
- return wrap(obj);
58
- }
59
-
60
1
  // ─── Render Batcher ────────────────────────────────────────
61
2
 
62
3
  export function createBatcher(renderFn, afterRender) {
@@ -446,6 +446,8 @@ There is no `ln-data-store:sync-conflict` event and no `forceSync()` error
446
446
  backstop — both were removed as part of this refactor (no grep-verified
447
447
  consumer existed for either).
448
448
 
449
+ **Non-queued direct connector rollback (`!meta.queued`)** — When an offline queue is not present and a direct connector request fails, `ln-data-coordinator` automatically rolls back the optimistic local store mutation to keep the client cache consistent: an `update` failure restores the pre-mutation `meta.snapshot` (`ln-data-store:request-update`), a `create` failure deletes the optimistic record via `meta.tempId` (`ln-data-store:request-delete`), and a `delete` failure re-creates the deleted record from `meta.snapshot` (`ln-data-store:request-create`).
450
+
449
451
  ---
450
452
 
451
453
  ---