@mohasinac/appkit 4.63.1 → 4.63.3

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "stampVersion": 1,
3
- "version": "4.63.1",
4
- "head": "4851d3d585790224397f1dcb19e6dee274fe9d60",
3
+ "version": "4.63.3",
4
+ "head": "aea6affd6821cc7b43256c25614e42422df7177c",
5
5
  "branch": "main",
6
6
  "dirty": false,
7
- "builtAt": "2026-10-23T08:10:33.829Z"
7
+ "builtAt": "2026-10-23T10:12:11.248Z"
8
8
  }
@@ -10,7 +10,11 @@ import { normalizeError } from "../../../errors/normalize.js";
10
10
  import { DataListingView } from "../../admin/components/DataListingView.js";
11
11
  import { ACTIONS } from "../../../_internal/shared/actions/action-registry.js";
12
12
  import { DISPUTE_KIND, } from "../../../_internal/shared/features/orders/dispute-reasons.js";
13
- import { DISPUTE_REASON_LABEL_MAX, } from "../schemas/firestore.js";
13
+ import { DISPUTE_REASON_FIELDS, DISPUTE_REASON_LABEL_MAX, } from "../schemas/firestore.js";
14
+ /* šŸ›‘ The DEFINING module, never `@mohasinac/appkit/client` — in a view that is
15
+ * fine, but these are the canonical sort helpers and `audit-sieve-constants-views`
16
+ * is what blocks the raw `"label"` / `"-label"` literals below. */
17
+ import { sortBy } from "../../../constants/sort.js";
14
18
  /**
15
19
  * What each kind MEANS, in one place.
16
20
  *
@@ -104,7 +108,7 @@ export function AdminDisputeReasonsView({ endpoint, onSetActive, onCreate, }) {
104
108
  subtitle: "What buyers can pick when reporting a problem. A reason's kind decides which gate it inherits — the wording is yours.",
105
109
  endpoint: endpoint,
106
110
  queryKey: ["admin", "dispute-reasons"],
107
- defaultSort: "label",
111
+ defaultSort: sortBy(DISPUTE_REASON_FIELDS.LABEL, "ASC"),
108
112
  /*
109
113
  * šŸ›‘ REQUIRED on `ListingViewConfig`, and the default must be one of
110
114
  * these or the dropdown opens with nothing selected (Root Cause #63).
@@ -112,9 +116,9 @@ export function AdminDisputeReasonsView({ endpoint, onSetActive, onCreate, }) {
112
116
  * wording and which kind a reason belongs to.
113
117
  */
114
118
  sortOptions: [
115
- { value: "label", label: "Wording (A–Z)" },
116
- { value: "-label", label: "Wording (Z–A)" },
117
- { value: "kind", label: "Kind" },
119
+ { value: sortBy(DISPUTE_REASON_FIELDS.LABEL, "ASC"), label: "Wording (A–Z)" },
120
+ { value: sortBy(DISPUTE_REASON_FIELDS.LABEL, "DESC"), label: "Wording (Z–A)" },
121
+ { value: sortBy(DISPUTE_REASON_FIELDS.KIND, "ASC"), label: "Kind" },
118
122
  ],
119
123
  filterKeys: ["scope"],
120
124
  filterDefaults: { scope: "active" },
@@ -209,6 +209,13 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
209
209
  // validator on every render and re-fire the effect below in a loop.
210
210
  const onValidationChangeRef = useRef(onValidationChange);
211
211
  onValidationChangeRef.current = onValidationChange;
212
+ /**
213
+ * The last error map actually HANDED TO the caller.
214
+ *
215
+ * šŸ›‘ This is what stops an infinite render loop, and the loop froze the whole
216
+ * app — see `emitValidation` below.
217
+ */
218
+ const lastEmittedRef = useRef(null);
212
219
  /**
213
220
  * Are two field-error maps the same set of messages?
214
221
  *
@@ -232,6 +239,44 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
232
239
  .filter((s) => s.fields?.some((f) => keys.some((k) => k === f || k.startsWith(`${f}.`))))
233
240
  .map((s) => s.id);
234
241
  }, [ordered]);
242
+ /**
243
+ * Hand the result to the caller, but ONLY when it actually changed.
244
+ *
245
+ * ## šŸ›‘ The loop this closes froze every admin editor drawer
246
+ *
247
+ * `runValidation` runs on EVERY render by design: `values` is a fresh object
248
+ * each time, so `ordered` → `erroringSectionIds` → `runValidation` are all
249
+ * new identities and the effect below re-fires. The comment there argues
250
+ * that is safe, and for this component's OWN state it is — `sameErrors` and
251
+ * the `forceOpenIds` length check both return the previous object, so React
252
+ * bails out.
253
+ *
254
+ * It was NOT safe for the caller. `onValidationChange` was invoked
255
+ * unconditionally on every run, and the real call site is
256
+ * `onValidationChange={() => form.validate(draft)}` — which setStates. So:
257
+ * render → effect → emit → caller setState → render → … forever. React
258
+ * reported "Maximum update depth exceeded" ~4x/second.
259
+ *
260
+ * The visible symptom was not a validation bug at all. A React tree stuck in
261
+ * an update loop never commits a transition, and App Router navigation IS a
262
+ * transition — so with the drawer open, Cancel, the X, the sort dropdown and
263
+ * even plain nav links all did nothing. Measured on production: `router.replace`
264
+ * produced ZERO `history.replaceState` calls, no error, no failed request.
265
+ * The only escape was a full page reload.
266
+ *
267
+ * Emitting only on a real change is also what the prop NAME promises; firing
268
+ * it with an identical map on every render was never meaningful to a caller.
269
+ */
270
+ const emitValidation = useCallback((next, index) => {
271
+ const prev = lastEmittedRef.current;
272
+ if (prev && sameErrors(prev, next))
273
+ return;
274
+ lastEmittedRef.current = next;
275
+ onValidationChangeRef.current?.(next, index);
276
+ },
277
+ // `sameErrors` is a pure local helper; `lastEmittedRef`/`onValidationChangeRef`
278
+ // are refs. Nothing here needs to re-create this callback.
279
+ []);
235
280
  const runValidation = useCallback((nextValues) => {
236
281
  const activeSchema = schema ?? inheritedSchema;
237
282
  if (!activeSchema)
@@ -243,7 +288,7 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
243
288
  if (parsed.success) {
244
289
  setFieldErrors((prev) => (Object.keys(prev).length === 0 ? prev : {}));
245
290
  setForceOpenIds((prev) => (prev.length === 0 ? prev : []));
246
- onValidationChangeRef.current?.({}, fieldToSectionIndex);
291
+ emitValidation({}, fieldToSectionIndex);
247
292
  return;
248
293
  }
249
294
  const next = {};
@@ -266,7 +311,7 @@ export function SectionForm({ sections, values, onChange, onSubmit, submitLabel
266
311
  const kept = prev.filter((id) => stillBad.has(id));
267
312
  return kept.length === prev.length ? prev : kept;
268
313
  });
269
- onValidationChangeRef.current?.(next, fieldToSectionIndex);
314
+ emitValidation(next, fieldToSectionIndex);
270
315
  }, [schema, inheritedSchema, fieldToSectionIndex, erroringSectionIds]);
271
316
  /*
272
317
  * šŸ›‘ `values` MUST be a dependency, and leaving it out cost the entire admin
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mohasinac/appkit",
3
- "version": "4.63.1",
3
+ "version": "4.63.3",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"