uniweb 0.18.0 → 0.19.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,13 +41,13 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/core": "^0.8.5",
45
44
  "@uniweb/kit": "^0.11.3",
45
+ "@uniweb/core": "^0.8.5",
46
46
  "@uniweb/runtime": "^0.11.7"
47
47
  },
48
48
  "peerDependencies": {
49
- "@uniweb/build": "^0.18.5",
50
49
  "@uniweb/content-reader": "^1.2.2",
50
+ "@uniweb/build": "^0.19.0",
51
51
  "@uniweb/semantic-parser": "^1.2.2"
52
52
  },
53
53
  "peerDependenciesMeta": {
@@ -101,6 +101,15 @@ export function extractFinalized(payload) {
101
101
  // resubmit returns the value we already hold, and anything else is theirs
102
102
  // to report, not ours to derive.
103
103
  version: typeof d?.version === 'string' ? d.version : null,
104
+ // Per-item post-write tokens, `{ recordUuid: <opaque> }` (backend `d7e46335`).
105
+ // Same map shape and same key name the PULL manifest entry stamps, deliberately
106
+ // — one kind of value, cached from whichever lane last handed it over, echoed
107
+ // as `item_base_versions`. Absent on an older backend ⇒ null ⇒ the cached
108
+ // tokens are left alone and gating degrades to the entity grain.
109
+ itemVersions:
110
+ d?.item_versions && typeof d.item_versions === 'object'
111
+ ? d.item_versions
112
+ : null,
104
113
  document: d?.document ?? null
105
114
  }))
106
115
  .filter((e) => Number.isInteger(e.index) && e.uuid)
@@ -1060,9 +1069,37 @@ export async function pushSyncPackages({
1060
1069
  // otherwise the next attempt would re-send lane 1 with a base the backend has
1061
1070
  // already moved past, and refuse a push the user just made.
1062
1071
  const newVersions = {}
1072
+ // Per-item tokens from the SAME response. Keyed by record `$uuid` and flat
1073
+ // across entities (the cache is a single map), unlike `newVersions`, which is
1074
+ // keyed by entity uuid.
1075
+ //
1076
+ // ⛔ Both grains must be re-armed from the push, for one reason: a push writes,
1077
+ // so every token this clone holds for a record it just changed is now stale. Read
1078
+ // on pull only, push 2 classifies that record `pkg != base, host != base` and the
1079
+ // backend refuses it — a conflict naming records nobody else touched, on a second
1080
+ // push with no edit in between. And it is unrecoverable locally, because the
1081
+ // tokens are opaque by contract: a pull would be the only other source, and a
1082
+ // pull rewrites the working tree. That is precisely the `--force` habit the gate
1083
+ // exists to prevent. Returning the entity `version` on push was added to fix this
1084
+ // exact shape one grain up (see delivery-lane.md "Both feed directions are
1085
+ // load-bearing"); the item grain had the same hole until backend `d7e46335`
1086
+ // started echoing `item_versions` here.
1087
+ const newItemVersions = {}
1063
1088
  const harvest = (finalized) => {
1064
- for (const f of finalized || [])
1089
+ for (const f of finalized || []) {
1065
1090
  if (f.uuid && f.version) newVersions[f.uuid] = f.version
1091
+ // Unconditionally, and NOT gated on `changed` — same rule as the entity
1092
+ // token: the backend pins "zero-write ⇒ version unmoved", so a no-op
1093
+ // resubmit hands back the value we already hold. An older backend omits the
1094
+ // field entirely, which leaves the cached tokens alone and degrades to the
1095
+ // entity grain, exactly as before.
1096
+ if (f.itemVersions) Object.assign(newItemVersions, f.itemVersions)
1097
+ }
1098
+ }
1099
+ // Both grains land together, at every point the old code banked the entity one.
1100
+ const mergeHarvested = () => {
1101
+ mergeBaseVersions(siteDir, newVersions)
1102
+ mergeItemBaseVersions(siteDir, newItemVersions)
1066
1103
  }
1067
1104
  // The backend's post-write copy of the site-content document, kept for the
1068
1105
  // remote-side unit base (see writeUnitBases).
@@ -1085,7 +1122,7 @@ export async function pushSyncPackages({
1085
1122
  { boundUuid: siteContentUuid }
1086
1123
  )
1087
1124
  if (!finalized) {
1088
- mergeBaseVersions(siteDir, newVersions)
1125
+ mergeHarvested()
1089
1126
  return { exitCode: 1, finalizedTotal, wrote }
1090
1127
  }
1091
1128
  harvest(finalized)
@@ -1147,7 +1184,7 @@ export async function pushSyncPackages({
1147
1184
  { boundUuid: boundSiteUuid }
1148
1185
  )
1149
1186
  if (!finalized) {
1150
- mergeBaseVersions(siteDir, newVersions)
1187
+ mergeHarvested()
1151
1188
  return { exitCode: 1, finalizedTotal, wrote }
1152
1189
  }
1153
1190
  harvest(finalized)
@@ -1164,7 +1201,7 @@ export async function pushSyncPackages({
1164
1201
  // Entities absent from finalized[] (skipped, or not editable) keep their cached
1165
1202
  // value — absence is not invalidation.
1166
1203
  writeSyncCache(siteDir, hashes)
1167
- mergeBaseVersions(siteDir, newVersions)
1204
+ mergeHarvested()
1168
1205
  // Re-base the page attribution: our emitted document and the backend's post-write
1169
1206
  // copy of it are the two sides' new agreed state. Only when the site-content lane
1170
1207
  // actually shipped — a push that skipped it left that state where it was.
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-08-12T23:11:50.036Z",
3
+ "generatedAt": "2026-08-12T23:32:45.161Z",
4
4
  "packages": {
5
5
  "@uniweb/build": {
6
- "version": "0.18.5",
6
+ "version": "0.19.0",
7
7
  "path": "framework/build",
8
8
  "deps": [
9
9
  "@uniweb/content-reader",