@open-mercato/ui 0.6.5-develop.4674.1.bf258550ce → 0.6.5-develop.4695.1.42ee0ddf0e

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/ui",
3
- "version": "0.6.5-develop.4674.1.bf258550ce",
3
+ "version": "0.6.5-develop.4695.1.42ee0ddf0e",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -149,13 +149,13 @@
149
149
  "remark-gfm": "^4.0.1"
150
150
  },
151
151
  "peerDependencies": {
152
- "@open-mercato/shared": "0.6.5-develop.4674.1.bf258550ce",
152
+ "@open-mercato/shared": "0.6.5-develop.4695.1.42ee0ddf0e",
153
153
  "react": ">=18.0.0",
154
154
  "react-dom": ">=18.0.0",
155
155
  "react-is": ">=18.0.0"
156
156
  },
157
157
  "devDependencies": {
158
- "@open-mercato/shared": "0.6.5-develop.4674.1.bf258550ce",
158
+ "@open-mercato/shared": "0.6.5-develop.4695.1.42ee0ddf0e",
159
159
  "@testing-library/dom": "^10.4.1",
160
160
  "@testing-library/jest-dom": "^6.9.1",
161
161
  "@testing-library/react": "^16.3.1",
@@ -2321,7 +2321,18 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2321
2321
  appliedInitialValuesSnapshotRef.current = snapshot
2322
2322
  const initialRecord = initialValues as Record<string, unknown>
2323
2323
  let mergedValues: CrudFormValues<TValues> | null = null
2324
+ // Whether the user already has unsaved edits relative to the load-time baseline,
2325
+ // computed from `prev` (React's latest committed values) — NOT from isDirtyRef or
2326
+ // valuesRef, which are both updated in post-render effects and therefore still
2327
+ // hold stale values when this layout effect runs in the SAME render that async
2328
+ // custom-field / injected-field definitions arrive. That lag was the residual
2329
+ // race that let the baseline absorb an in-progress edit under load.
2330
+ let hadUnsavedEdits = false
2324
2331
  setValues((prev) => {
2332
+ const priorBaseline = dirtyBaselineSnapshotRef.current
2333
+ hadUnsavedEdits =
2334
+ priorBaseline !== undefined &&
2335
+ createDirtySnapshot(prev as Record<string, unknown>) !== priorBaseline
2325
2336
  const merged = { ...prev, ...initialValues } as CrudFormValues<TValues>
2326
2337
  for (const definition of injectedFieldDefinitions) {
2327
2338
  if (merged[definition.id] !== undefined) continue
@@ -2348,7 +2359,17 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2348
2359
  mergedValues = merged
2349
2360
  return mergedValues
2350
2361
  })
2351
- if (mergedValues) {
2362
+ if (mergedValues && !hadUnsavedEdits) {
2363
+ // Do not absorb an in-progress edit into the pristine baseline. This effect
2364
+ // re-runs whenever the snapshot changes — which includes custom-field
2365
+ // definitions and injected fields loading ASYNCHRONOUSLY after mount. If the
2366
+ // user has already started editing by the time they arrive, recomputing the
2367
+ // baseline from the current (merged) values would set the baseline equal to
2368
+ // the edited values, silently clearing dirty (header Save disables, the edit
2369
+ // looks pristine) and discarding the unsaved change. Re-establish the baseline
2370
+ // only when there are no pending edits; while dirty, keep the load-time
2371
+ // baseline so the edit stays dirty until save/discard. Root cause of the flaky
2372
+ // optimistic-lock stale-edit conflicts (#2055 / TC-LOCK-OSS-015 / TC-LOCK-OSS-029).
2352
2373
  dirtyBaselineSnapshotRef.current = createDirtySnapshot(mergedValues as Record<string, unknown>)
2353
2374
  }
2354
2375
  if (!extendedInjectionEventsEnabled || !mergedValues) return
@@ -2362,6 +2383,9 @@ export function CrudForm<TValues extends Record<string, unknown>>({
2362
2383
  )
2363
2384
  const transformed = result.data
2364
2385
  if (cancelled || !transformed) return
2386
+ // As above: never re-apply transformed display data over an in-progress
2387
+ // edit — it would overwrite the user's unsaved changes and reset dirty.
2388
+ if (isDirtyRef.current) return
2365
2389
  dirtyBaselineSnapshotRef.current = createDirtySnapshot(transformed as Record<string, unknown>)
2366
2390
  setValues(transformed as CrudFormValues<TValues>)
2367
2391
  } catch (err) {