{"record":{"id":"1c77f32eecc65c98","repo":"louis-e/arnis","slug":"unexpected-preview-payload-format","errorCode":null,"errorMessage":"Unexpected preview payload format","messagePattern":"Unexpected preview payload format","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/gui/js/preview3d.js","lineNumber":68,"sourceCode":"  let generationRunning = false;\n  let protocolRegistered = false;\n\n  // ---------- payload parsing and DEM tile synthesis ----------\n\n  // Normalizes a Tauri invoke result to an aligned ArrayBuffer and validates\n  // the payload magic; every payload layout starts with magic + grid dims.\n  function readPayloadHeader(buffer, expectedMagic) {\n    if (buffer instanceof Uint8Array) {\n      buffer =\n        buffer.byteOffset === 0 && buffer.byteLength === buffer.buffer.byteLength\n          ? buffer.buffer\n          : buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);\n    } else if (Array.isArray(buffer)) {\n      buffer = new Uint8Array(buffer).buffer;\n    }\n    const dv = new DataView(buffer);\n    const magic = String.fromCharCode(dv.getUint8(0), dv.getUint8(1), dv.getUint8(2), dv.getUint8(3));\n    if (magic !== expectedMagic) throw new Error(\"Unexpected preview payload format\");\n    return { buffer: buffer, dv: dv, gw: dv.getUint32(4, true), gh: dv.getUint32(8, true) };\n  }\n\n  function parsePayload(raw) {\n    const { buffer, dv, gw, gh } = readPayloadHeader(raw, \"APV1\");\n    const d = {\n      gw,\n      gh,\n      minLat: dv.getFloat64(12, true),\n      minLng: dv.getFloat64(20, true),\n      maxLat: dv.getFloat64(28, true),\n      maxLng: dv.getFloat64(36, true),\n      minElev: dv.getFloat32(44, true),\n      maxElev: dv.getFloat32(48, true),\n      heights: new Uint16Array(buffer, 64, gw * gh),\n    };\n    d.mScale = d.maxElev > d.minElev ? (d.maxElev - d.minElev) / 65535 : 0;\n    // Barely below the lowest terrain: the surroundings read as a flat plane","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/louis-e/arnis/blob/34048924d9365795fb0d832e76140a3fbdc413d9/src/gui/js/preview3d.js#L50-L86","documentation":"readPayloadHeader normalizes a Tauri invoke result into an ArrayBuffer and checks the 4-byte magic prefix against the expected value ('APV1' for DEM payloads, 'APL1' for land cover). If the first four bytes do not match, the payload is not the expected binary format — it may be an error blob, a different version, or garbage — so this Error is thrown before any DataView field parsing happens.","triggerScenarios":"Calling gui_get_preview_dem (or gui_get_preview_landcover) via window.__TAURI__.core.invoke and passing the raw result to parsePayload/readPayloadHeader when: the Rust command returned a legacy or different-magic payload version, the response was truncated to fewer than 4 bytes, an error string/object was returned instead of bytes, or the invoke result was base64-encoded/JSON-wrapped text rather than raw bytes.","commonSituations":"Frontend and backend out of sync after a partial deploy (old JS expecting APV1, Rust rebuilt with a new magic or layout); calling the command on a Tauri version or transport that serializes the byte array differently; a stale cached preview written by an older build; invoking the wrong command and feeding its output into parsePayload.","solutions":["Log the first bytes (or hex) of the returned payload and compare against the expected magic 'APV1'/'APL1' to identify what was actually returned.","Rebuild/redeploy the Rust backend and GUI together so both sides agree on the payload magic and layout.","Check the invoke call for typos in the command name and confirm the Rust command returns raw bytes (Vec<u8>), not a base64 string or JSON envelope.","Add a version byte or handle multiple magics in readPayloadHeader if older payloads must still be supported.","Catch this error at the call site and show a 'regenerate preview' affordance so stale cached payloads get refetched."],"exampleFix":"// before\nconst raw = await window.__TAURI__.core.invoke(\"gui_get_preview_dem\", { bboxText });\nconst data = parsePayload(raw); // throws if magic differs\n// after\nconst raw = await window.__TAURI__.core.invoke(\"gui_get_preview_dem\", { bboxText });\ntry {\n  const data = parsePayload(raw);\n} catch (e) {\n  if (String(e.message).includes(\"Unexpected preview payload format\")) {\n    console.error(\"payload magic mismatch, head:\", new TextDecoder().decode(raw.slice(0, 16)));\n    invalidatePreviewCache();\n    return null;\n  }\n  throw e;\n}","handlingStrategy":"validation","validationCode":"function hasValidMagic(raw, magic) {\n  const bytes = raw instanceof Uint8Array ? raw : new Uint8Array(raw);\n  if (bytes.length < 4) return false;\n  return String.fromCharCode(bytes[0], bytes[1], bytes[2], bytes[3]) === magic;\n}\nif (!hasValidMagic(raw, \"APV1\")) { refetchOrRegenerate(); return; }\nconst data = parsePayload(raw);","typeGuard":"function isBinaryPayload(v) { return v instanceof ArrayBuffer || v instanceof Uint8Array || Array.isArray(v); }","tryCatchPattern":"try {\n  const data = parsePayload(raw);\n} catch (e) {\n  if (e.message === \"Unexpected preview payload format\") {\n    console.error(\"magic head:\", new TextDecoder().decode(raw.slice(0, 4)));\n  } else { throw e; }\n}","preventionTips":["Deploy frontend and Rust backend atomically so payload layouts never drift.","Log the first 4-16 bytes of unexpected payloads to diagnose version drift.","Keep a version/magic constant shared (or code-generated) between Rust and JS.","Cache invalidation: drop cached payloads whenever either side is rebuilt."],"tags":["binary-format","tauri","payload-validation","frontend"],"backgroundTag":"payload-magic-mismatch","analyzedSha":"34048924d9365795fb0d832e76140a3fbdc413d9","analyzedAt":"2026-09-03T14:05:17.283Z","contentChangedAt":"2026-09-03T14:05:17.283Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}