@open-mercato/ui 0.6.8-develop.6930.1.1e5976efc3 → 0.6.8-develop.6940.1.177ea30c6e

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.
@@ -777,6 +777,21 @@ export function CrudForm<TValues extends Record<string, unknown>>({
777
777
  () => ({ ...(initialValues ?? {}) } as CrudFormValues<TValues>)
778
778
  )
779
779
  const valuesRef = React.useRef(values)
780
+ const pendingFieldChangeEventsRef = React.useRef<Array<{
781
+ eventId: number
782
+ fieldId: string
783
+ fieldValue: unknown
784
+ fieldRevision: number
785
+ fieldRevisionsAtEnqueue: Map<string, number>
786
+ formGeneration: number
787
+ }>>([])
788
+ const nextFieldChangeEventIdRef = React.useRef(0)
789
+ const fieldChangeRevisionByFieldRef = React.useRef(new Map<string, number>())
790
+ const fieldChangeFormGenerationRef = React.useRef(0)
791
+ const fieldChangeFormSnapshotRef = React.useRef<string | undefined>(undefined)
792
+ const fieldChangeDispatcherMountedRef = React.useRef(false)
793
+ const activeFieldChangeDispatchRef = React.useRef<{ eventId: number; formGeneration: number } | null>(null)
794
+ const [fieldChangeDispatchVersion, setFieldChangeDispatchVersion] = React.useState(0)
780
795
  const [errors, setErrors] = React.useState<Record<string, string>>({})
781
796
  const [pending, setPending] = React.useState(false)
782
797
  // Synchronous guard against re-entrant submit/delete invocations (e.g. rapid
@@ -880,9 +895,32 @@ export function CrudForm<TValues extends Record<string, unknown>>({
880
895
  React.useEffect(() => {
881
896
  injectionContextRef.current = injectionContext
882
897
  }, [injectionContext])
883
- React.useEffect(() => {
898
+ React.useLayoutEffect(() => {
884
899
  valuesRef.current = values
885
900
  }, [values])
901
+ const fieldChangeFormSnapshot = React.useMemo(
902
+ () => createDirtySnapshot((initialValues ?? {}) as Record<string, unknown>),
903
+ [initialValues],
904
+ )
905
+ React.useLayoutEffect(() => {
906
+ const fieldChangeRevisions = fieldChangeRevisionByFieldRef.current
907
+ fieldChangeDispatcherMountedRef.current = true
908
+ return () => {
909
+ fieldChangeDispatcherMountedRef.current = false
910
+ fieldChangeFormGenerationRef.current += 1
911
+ pendingFieldChangeEventsRef.current = []
912
+ fieldChangeRevisions.clear()
913
+ activeFieldChangeDispatchRef.current = null
914
+ }
915
+ }, [])
916
+ React.useLayoutEffect(() => {
917
+ if (fieldChangeFormSnapshotRef.current === fieldChangeFormSnapshot) return
918
+ fieldChangeFormSnapshotRef.current = fieldChangeFormSnapshot
919
+ fieldChangeFormGenerationRef.current += 1
920
+ pendingFieldChangeEventsRef.current = []
921
+ fieldChangeRevisionByFieldRef.current.clear()
922
+ activeFieldChangeDispatchRef.current = null
923
+ }, [fieldChangeFormSnapshot])
886
924
 
887
925
  const isDirtyRef = React.useRef(false)
888
926
  const navigationPromptBypassRef = React.useRef(false)
@@ -957,6 +995,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
957
995
 
958
996
  const clearDirtyState = React.useCallback((snapshotSource?: Record<string, unknown>) => {
959
997
  const source = snapshotSource ?? (valuesRef.current as Record<string, unknown>)
998
+ pendingDirtyBaselineCommitRef.current = null
960
999
  dirtyBaselineSnapshotRef.current = createDirtySnapshot(source)
961
1000
  dirtyBaselineValuesRef.current = { ...source }
962
1001
  userEditedFieldIdsRef.current.clear()
@@ -2315,29 +2354,32 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2315
2354
  }, [])
2316
2355
 
2317
2356
  const setValue = React.useCallback((id: string, nextValue: unknown) => {
2318
- let nextData: CrudFormValues<TValues> | null = null
2319
- let nextDirty: boolean | null = null
2320
- const currentValue = (valuesRef.current as Record<string, unknown>)[id]
2321
- if (!Object.is(currentValue, nextValue)) {
2322
- updateEditedFieldMarker(id, nextValue)
2323
- }
2324
- setValues((prev) => {
2325
- if (Object.is(prev[id], nextValue)) return prev
2326
- const baselineSource = dirtyBaselineValuesRef.current ?? (prev as Record<string, unknown>)
2357
+ const currentValues = valuesRef.current
2358
+ if (!Object.is(currentValues[id], nextValue)) {
2359
+ pendingDirtyBaselineCommitRef.current = null
2360
+ const baselineSource = dirtyBaselineValuesRef.current ?? (currentValues as Record<string, unknown>)
2327
2361
  updateEditedFieldMarker(id, nextValue, baselineSource)
2328
- nextData = { ...prev, [id]: nextValue } as CrudFormValues<TValues>
2329
- valuesRef.current = nextData
2330
2362
  if (!(embedded && !trackDirtyWhenEmbedded)) {
2331
- const baseline = dirtyBaselineSnapshotRef.current ?? createDirtySnapshot(prev as Record<string, unknown>)
2332
- dirtyBaselineSnapshotRef.current = baseline
2363
+ dirtyBaselineSnapshotRef.current ??= createDirtySnapshot(currentValues as Record<string, unknown>)
2333
2364
  dirtyBaselineValuesRef.current = baselineSource
2334
- nextDirty = createDirtySnapshot(nextData as Record<string, unknown>) !== baseline
2335
2365
  }
2336
- return nextData
2337
- })
2338
- if (nextDirty !== null) {
2339
- isDirtyRef.current = nextDirty
2340
- setHasUnsavedChanges(nextDirty)
2366
+ if (extendedInjectionEventsEnabled) {
2367
+ const fieldRevision = (fieldChangeRevisionByFieldRef.current.get(id) ?? 0) + 1
2368
+ fieldChangeRevisionByFieldRef.current.set(id, fieldRevision)
2369
+ nextFieldChangeEventIdRef.current += 1
2370
+ pendingFieldChangeEventsRef.current.push({
2371
+ eventId: nextFieldChangeEventIdRef.current,
2372
+ fieldId: id,
2373
+ fieldValue: nextValue,
2374
+ fieldRevision,
2375
+ fieldRevisionsAtEnqueue: new Map(fieldChangeRevisionByFieldRef.current),
2376
+ formGeneration: fieldChangeFormGenerationRef.current,
2377
+ })
2378
+ setFieldChangeDispatchVersion((version) => version + 1)
2379
+ }
2380
+ setValues((prev) => Object.is(prev[id], nextValue)
2381
+ ? prev
2382
+ : ({ ...prev, [id]: nextValue } as CrudFormValues<TValues>))
2341
2383
  }
2342
2384
  const clearedMessages: string[] = []
2343
2385
  setErrors((prev) => {
@@ -2373,19 +2415,50 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2373
2415
  return prev
2374
2416
  })
2375
2417
  }
2376
- if (!nextData || !extendedInjectionEventsEnabled) return
2377
- void triggerInjectionEvent('onFieldChange', nextData as TValues, injectionContextRef.current, {
2378
- fieldId: id,
2379
- fieldValue: nextValue,
2418
+ }, [embedded, extendedInjectionEventsEnabled, t, trackDirtyWhenEmbedded, translateValidationMessage, updateEditedFieldMarker])
2419
+
2420
+ React.useEffect(() => {
2421
+ if (!extendedInjectionEventsEnabled) {
2422
+ pendingFieldChangeEventsRef.current = []
2423
+ return
2424
+ }
2425
+ if (!fieldChangeDispatcherMountedRef.current || activeFieldChangeDispatchRef.current) return
2426
+ let pendingEvent = pendingFieldChangeEventsRef.current.shift()
2427
+ while (pendingEvent && pendingEvent.formGeneration !== fieldChangeFormGenerationRef.current) {
2428
+ pendingEvent = pendingFieldChangeEventsRef.current.shift()
2429
+ }
2430
+ if (!pendingEvent) return
2431
+ const dispatchToken = {
2432
+ eventId: pendingEvent.eventId,
2433
+ formGeneration: pendingEvent.formGeneration,
2434
+ }
2435
+ activeFieldChangeDispatchRef.current = dispatchToken
2436
+ void triggerInjectionEvent('onFieldChange', values as TValues, injectionContextRef.current, {
2437
+ fieldId: pendingEvent.fieldId,
2438
+ fieldValue: pendingEvent.fieldValue,
2380
2439
  }).then((result) => {
2440
+ const isCurrentEvent =
2441
+ fieldChangeDispatcherMountedRef.current &&
2442
+ fieldChangeFormGenerationRef.current === pendingEvent.formGeneration &&
2443
+ fieldChangeRevisionByFieldRef.current.get(pendingEvent.fieldId) === pendingEvent.fieldRevision
2444
+ if (!isCurrentEvent) return
2381
2445
  if (!result.ok) return
2382
2446
  const change = result.fieldChange
2383
2447
  if (!change) return
2384
- const updates: Record<string, unknown> = { ...(change.sideEffects ?? {}) }
2448
+ const candidateUpdates: Record<string, unknown> = { ...(change.sideEffects ?? {}) }
2385
2449
  if (change.value !== undefined) {
2386
- updates[id] = change.value
2450
+ candidateUpdates[pendingEvent.fieldId] = change.value
2451
+ }
2452
+ const updates: Record<string, unknown> = {}
2453
+ for (const [fieldId, value] of Object.entries(candidateUpdates)) {
2454
+ if (
2455
+ fieldChangeRevisionByFieldRef.current.get(fieldId) !==
2456
+ pendingEvent.fieldRevisionsAtEnqueue.get(fieldId)
2457
+ ) continue
2458
+ updates[fieldId] = value
2387
2459
  }
2388
2460
  if (Object.keys(updates).length > 0) {
2461
+ pendingDirtyBaselineCommitRef.current = null
2389
2462
  setValues((prev) => {
2390
2463
  let changed = false
2391
2464
  const next = { ...prev } as Record<string, unknown>
@@ -2402,8 +2475,22 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2402
2475
  }
2403
2476
  }).catch((err) => {
2404
2477
  logger.error('Error in onFieldChange', { err })
2478
+ }).finally(() => {
2479
+ const activeDispatch = activeFieldChangeDispatchRef.current
2480
+ if (
2481
+ activeDispatch?.eventId === dispatchToken.eventId &&
2482
+ activeDispatch.formGeneration === dispatchToken.formGeneration
2483
+ ) {
2484
+ activeFieldChangeDispatchRef.current = null
2485
+ }
2486
+ if (
2487
+ fieldChangeDispatcherMountedRef.current &&
2488
+ fieldChangeFormGenerationRef.current === dispatchToken.formGeneration
2489
+ ) {
2490
+ setFieldChangeDispatchVersion((version) => version + 1)
2491
+ }
2405
2492
  })
2406
- }, [embedded, extendedInjectionEventsEnabled, flash, t, trackDirtyWhenEmbedded, translateValidationMessage, triggerInjectionEvent, updateEditedFieldMarker])
2493
+ }, [extendedInjectionEventsEnabled, fieldChangeDispatchVersion, triggerInjectionEvent, values])
2407
2494
 
2408
2495
  const onBlurRequest = React.useCallback((fieldId: string) => {
2409
2496
  void validateFieldOnBlur(fieldId)
@@ -2446,68 +2533,83 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2446
2533
  )
2447
2534
 
2448
2535
  const appliedInitialValuesSnapshotRef = React.useRef<string | undefined>(undefined)
2536
+ const initialValuesTransformMountedRef = React.useRef(true)
2449
2537
  const dirtyBaselineSnapshotRef = React.useRef<string | undefined>(undefined)
2450
2538
  const dirtyBaselineValuesRef = React.useRef<Record<string, unknown> | undefined>(undefined)
2539
+ const pendingDirtyBaselineCommitRef = React.useRef<{
2540
+ guardSnapshot?: string
2541
+ valuesSnapshot: string
2542
+ values: Record<string, unknown>
2543
+ } | null>(null)
2451
2544
  const userEditedFieldIdsRef = React.useRef<Set<string>>(new Set())
2452
2545
  const everEditedFieldIdsRef = React.useRef<Set<string>>(new Set())
2546
+ React.useLayoutEffect(() => {
2547
+ initialValuesTransformMountedRef.current = true
2548
+ return () => {
2549
+ initialValuesTransformMountedRef.current = false
2550
+ }
2551
+ }, [])
2552
+ React.useLayoutEffect(() => {
2553
+ const pendingBaseline = pendingDirtyBaselineCommitRef.current
2554
+ if (!pendingBaseline) return
2555
+ pendingDirtyBaselineCommitRef.current = null
2556
+ if (
2557
+ pendingBaseline.guardSnapshot !== undefined &&
2558
+ appliedInitialValuesSnapshotRef.current !== pendingBaseline.guardSnapshot
2559
+ ) return
2560
+ const committedValues = values as Record<string, unknown>
2561
+ if (createDirtySnapshot(committedValues) !== pendingBaseline.valuesSnapshot) return
2562
+ dirtyBaselineSnapshotRef.current = pendingBaseline.valuesSnapshot
2563
+ dirtyBaselineValuesRef.current = { ...pendingBaseline.values }
2564
+ }, [values])
2453
2565
  React.useLayoutEffect(() => {
2454
2566
  if (!initialValues) return
2455
2567
  const snapshot = JSON.stringify({
2456
2568
  initialValues,
2457
2569
  injectedFieldIds: injectedFieldDefinitions.map((definition) => definition.id),
2570
+ injectionWidgetIds: injectionWidgets.map((widget) => `${widget.moduleId}:${widget.widgetId}:${widget.key}`),
2458
2571
  customFieldMappings: cfDefinitions.map((definition) => definition.key),
2459
2572
  dotPathBaseFieldIds: Array.from(dotPathBaseFieldIds),
2460
2573
  })
2461
2574
  if (appliedInitialValuesSnapshotRef.current === snapshot) return
2462
2575
  appliedInitialValuesSnapshotRef.current = snapshot
2463
2576
  const initialRecord = initialValues as Record<string, unknown>
2464
- let mergedValues: CrudFormValues<TValues> | null = null
2465
- // Whether the user already has unsaved edits relative to the load-time baseline,
2466
- // computed from `prev` (React's latest committed values) — NOT from isDirtyRef or
2467
- // valuesRef, which are both updated in post-render effects and therefore still
2468
- // hold stale values when this layout effect runs in the SAME render that async
2469
- // custom-field / injected-field definitions arrive. That lag was the residual
2470
- // race that let the baseline absorb an in-progress edit under load.
2471
- let hadUnsavedEdits = false
2472
- setValues((prev) => {
2473
- const priorBaseline = dirtyBaselineSnapshotRef.current
2474
- const editedFieldIds = userEditedFieldIdsRef.current
2475
- hadUnsavedEdits =
2476
- editedFieldIds.size > 0 ||
2477
- priorBaseline !== undefined &&
2478
- createDirtySnapshot(prev as Record<string, unknown>) !== priorBaseline
2479
- const merged = { ...prev } as CrudFormValues<TValues>
2480
- const mergedRecord = merged as Record<string, unknown>
2481
- for (const [key, value] of Object.entries(initialValues as Record<string, unknown>)) {
2482
- if (editedFieldIds.has(key)) continue
2483
- mergedRecord[key] = value
2484
- }
2485
- for (const definition of injectedFieldDefinitions) {
2486
- if (merged[definition.id] !== undefined) continue
2487
- const extracted = readByDotPath(initialRecord, definition.id)
2488
- if (extracted !== undefined) {
2489
- ;(merged as Record<string, unknown>)[definition.id] = extracted
2490
- }
2577
+ const currentValues = valuesRef.current
2578
+ const priorBaseline = dirtyBaselineSnapshotRef.current
2579
+ const editedFieldIds = userEditedFieldIdsRef.current
2580
+ const hadUnsavedEdits =
2581
+ editedFieldIds.size > 0 ||
2582
+ priorBaseline !== undefined &&
2583
+ createDirtySnapshot(currentValues as Record<string, unknown>) !== priorBaseline
2584
+ const mergedValues = { ...currentValues } as CrudFormValues<TValues>
2585
+ const mergedRecord = mergedValues as Record<string, unknown>
2586
+ for (const [key, value] of Object.entries(initialValues as Record<string, unknown>)) {
2587
+ if (editedFieldIds.has(key)) continue
2588
+ mergedRecord[key] = value
2589
+ }
2590
+ for (const definition of injectedFieldDefinitions) {
2591
+ if (mergedValues[definition.id] !== undefined) continue
2592
+ const extracted = readByDotPath(initialRecord, definition.id)
2593
+ if (extracted !== undefined) {
2594
+ mergedRecord[definition.id] = extracted
2491
2595
  }
2492
- for (const definition of cfDefinitions) {
2493
- const targetId = customEntity ? definition.key : `cf_${definition.key}`
2494
- if (!targetId || merged[targetId] !== undefined) continue
2495
- const extracted = readInitialCustomFieldValue(initialRecord, definition.key)
2496
- if (extracted !== undefined) {
2497
- ;(merged as Record<string, unknown>)[targetId] = extracted
2498
- }
2596
+ }
2597
+ for (const definition of cfDefinitions) {
2598
+ const targetId = customEntity ? definition.key : `cf_${definition.key}`
2599
+ if (!targetId || mergedValues[targetId] !== undefined) continue
2600
+ const extracted = readInitialCustomFieldValue(initialRecord, definition.key)
2601
+ if (extracted !== undefined) {
2602
+ mergedRecord[targetId] = extracted
2499
2603
  }
2500
- for (const fieldId of dotPathBaseFieldIds) {
2501
- if (merged[fieldId] !== undefined) continue
2502
- const extracted = readByDotPath(initialRecord, fieldId)
2503
- if (extracted !== undefined) {
2504
- ;(merged as Record<string, unknown>)[fieldId] = extracted
2505
- }
2604
+ }
2605
+ for (const fieldId of dotPathBaseFieldIds) {
2606
+ if (mergedValues[fieldId] !== undefined) continue
2607
+ const extracted = readByDotPath(initialRecord, fieldId)
2608
+ if (extracted !== undefined) {
2609
+ mergedRecord[fieldId] = extracted
2506
2610
  }
2507
- mergedValues = merged
2508
- return mergedValues
2509
- })
2510
- if (mergedValues && !hadUnsavedEdits) {
2611
+ }
2612
+ if (!hadUnsavedEdits) {
2511
2613
  // Do not absorb an in-progress edit into the pristine baseline. This effect
2512
2614
  // re-runs whenever the snapshot changes — which includes custom-field
2513
2615
  // definitions and injected fields loading ASYNCHRONOUSLY after mount. If the
@@ -2518,11 +2620,17 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2518
2620
  // only when there are no pending edits; while dirty, keep the load-time
2519
2621
  // baseline so the edit stays dirty until save/discard. Root cause of the flaky
2520
2622
  // optimistic-lock stale-edit conflicts (#2055 / TC-LOCK-OSS-015 / TC-LOCK-OSS-029).
2521
- dirtyBaselineSnapshotRef.current = createDirtySnapshot(mergedValues as Record<string, unknown>)
2522
- dirtyBaselineValuesRef.current = { ...(mergedValues as Record<string, unknown>) }
2623
+ pendingDirtyBaselineCommitRef.current = {
2624
+ guardSnapshot: snapshot,
2625
+ valuesSnapshot: createDirtySnapshot(mergedValues as Record<string, unknown>),
2626
+ values: { ...(mergedValues as Record<string, unknown>) },
2627
+ }
2628
+ } else {
2629
+ pendingDirtyBaselineCommitRef.current = null
2523
2630
  }
2524
- if (!extendedInjectionEventsEnabled || !mergedValues) return
2525
- let cancelled = false
2631
+ setValues(mergedValues)
2632
+ if (!extendedInjectionEventsEnabled || hadUnsavedEdits) return
2633
+ const transformSourceSnapshot = createDirtySnapshot(mergedValues as Record<string, unknown>)
2526
2634
  const run = async () => {
2527
2635
  try {
2528
2636
  const result = await triggerInjectionEvent(
@@ -2531,21 +2639,27 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2531
2639
  injectionContextRef.current,
2532
2640
  )
2533
2641
  const transformed = result.data
2534
- if (cancelled || !transformed) return
2535
- // As above: never re-apply transformed display data over an in-progress
2536
- // edit — it would overwrite the user's unsaved changes and reset dirty.
2537
- if (isDirtyRef.current) return
2538
- dirtyBaselineSnapshotRef.current = createDirtySnapshot(transformed as Record<string, unknown>)
2539
- dirtyBaselineValuesRef.current = { ...(transformed as Record<string, unknown>) }
2540
- setValues(transformed as CrudFormValues<TValues>)
2642
+ if (
2643
+ !initialValuesTransformMountedRef.current ||
2644
+ appliedInitialValuesSnapshotRef.current !== snapshot ||
2645
+ !transformed
2646
+ ) return
2647
+ if (createDirtySnapshot(valuesRef.current as Record<string, unknown>) !== transformSourceSnapshot) return
2648
+ const transformedValues = transformed as CrudFormValues<TValues>
2649
+ pendingDirtyBaselineCommitRef.current = {
2650
+ guardSnapshot: snapshot,
2651
+ valuesSnapshot: createDirtySnapshot(transformedValues as Record<string, unknown>),
2652
+ values: { ...(transformedValues as Record<string, unknown>) },
2653
+ }
2654
+ setValues((current) => {
2655
+ if (createDirtySnapshot(current as Record<string, unknown>) !== transformSourceSnapshot) return current
2656
+ return transformedValues
2657
+ })
2541
2658
  } catch (err) {
2542
2659
  logger.error('Error in transformDisplayData', { err })
2543
2660
  }
2544
2661
  }
2545
2662
  void run()
2546
- return () => {
2547
- cancelled = true
2548
- }
2549
2663
  }, [
2550
2664
  cfDefinitions,
2551
2665
  customEntity,
@@ -2553,6 +2667,7 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2553
2667
  extendedInjectionEventsEnabled,
2554
2668
  initialValues,
2555
2669
  injectedFieldDefinitions,
2670
+ injectionWidgets,
2556
2671
  triggerInjectionEvent,
2557
2672
  ])
2558
2673
 
@@ -2589,26 +2704,24 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2589
2704
 
2590
2705
  if (Object.keys(defaults).length === 0) return
2591
2706
 
2592
- let mergedValues: CrudFormValues<TValues> | null = null
2593
- setValues((prev) => {
2594
- const merged = { ...prev } as CrudFormValues<TValues>
2595
- let applied = false
2596
- for (const [fieldId, defaultVal] of Object.entries(defaults)) {
2597
- // Skip if a value already exists (from initialValues or user input)
2598
- if (merged[fieldId] !== undefined) continue
2599
- ;(merged as Record<string, unknown>)[fieldId] = defaultVal
2600
- applied = true
2601
- }
2602
- if (!applied) return prev
2603
- mergedValues = merged
2604
- return mergedValues
2605
- })
2707
+ const currentValues = valuesRef.current
2708
+ const mergedValues = { ...currentValues } as CrudFormValues<TValues>
2709
+ let applied = false
2710
+ for (const [fieldId, defaultVal] of Object.entries(defaults)) {
2711
+ // Skip if a value already exists (from initialValues or user input)
2712
+ if (mergedValues[fieldId] !== undefined) continue
2713
+ ;(mergedValues as Record<string, unknown>)[fieldId] = defaultVal
2714
+ applied = true
2715
+ }
2716
+ if (!applied) return
2606
2717
 
2607
- // Update the dirty baseline so the form doesn't appear dirty from defaults
2608
- if (mergedValues) {
2609
- dirtyBaselineSnapshotRef.current = createDirtySnapshot(mergedValues as Record<string, unknown>)
2610
- dirtyBaselineValuesRef.current = { ...(mergedValues as Record<string, unknown>) }
2718
+ // Update the dirty baseline only after the defaults commit so an interrupted
2719
+ // render cannot make the navigation guard observe values the user never saw.
2720
+ pendingDirtyBaselineCommitRef.current = {
2721
+ valuesSnapshot: createDirtySnapshot(mergedValues as Record<string, unknown>),
2722
+ values: { ...(mergedValues as Record<string, unknown>) },
2611
2723
  }
2724
+ setValues(mergedValues)
2612
2725
  }, [isLoading, initialValuesHasId, cfDefinitions, customEntity])
2613
2726
 
2614
2727
  const markFormAsClean = React.useCallback((snapshotSource?: Record<string, unknown>) => {