{"record":{"id":"d8397120e51e6b8c","repo":"nocobase/nocobase","slug":"failed-to-copy-popup-from-template","errorCode":null,"errorMessage":"Failed to copy popup from template","messagePattern":"Failed to copy popup from template","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/menuExtensions.tsx","lineNumber":893,"sourceCode":"    const row = unwrap(res);\n    return row && typeof row === 'object' ? (row as Record<string, any>) : null;\n  };\n\n  const inferFromTemplateRow = (tplRow: Record<string, any>): PopupTemplateContextFlags => {\n    const scene = resolveActionScene((use: string) => model.flowEngine?.getModelClass?.(use), tplRow?.useModel);\n    return inferPopupTemplateContextFlags(scene, tplRow?.filterByTk, tplRow?.sourceId);\n  };\n\n  const doConvert = async () => {\n    const tplRow = await fetchTemplateRow();\n    const targetUid = normalizeStr(tplRow?.targetUid) || normalizeStr(openViewParams?.uid);\n    if (!targetUid) {\n      throw new Error(tNs('Popup template not found'));\n    }\n    const duplicated = await model.flowEngine.duplicateModel(targetUid);\n    const newUid = duplicated?.uid || duplicated?.data?.uid || duplicated?.data?.data?.uid;\n    if (!newUid) {\n      throw new Error(tNs('Failed to copy popup from template'));\n    }\n    const inferred: PopupTemplateContextFlags = tplRow\n      ? inferFromTemplateRow(tplRow)\n      : extractPopupTemplateContextFlagsFromParams(openViewParams);\n\n    const nextOpenView: any = { ...(openViewParams || {}), uid: newUid };\n    delete (nextOpenView as any).popupTemplateUid;\n    nextOpenView.popupTemplateContext = true;\n    delete (nextOpenView as any).popupTemplateHasFilterByTk;\n    delete (nextOpenView as any).popupTemplateHasSourceId;\n    // 同步清理 params 侧的 filterByTk/sourceId，避免 record action 复用 collection 弹窗时泄漏 filterByTk\n    if (!inferred.hasFilterByTk && 'filterByTk' in nextOpenView) {\n      delete nextOpenView.filterByTk;\n    }\n    if (!inferred.hasSourceId && 'sourceId' in nextOpenView) {\n      delete nextOpenView.sourceId;\n    }\n    model.setStepParams('popupSettings', { [openViewStepKey]: nextOpenView });","sourceCodeStart":875,"sourceCodeEnd":911,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/menuExtensions.tsx#L875-L911","documentation":"Immediately after duplicating the template's popup model in doConvert, the plugin unwraps the new uid from the duplicateModel response (duplicated?.uid, data.uid, data.data.uid). If no uid can be extracted, the conversion cannot proceed (nothing to point the new open-view at), so this localized error is thrown.","triggerScenarios":"Calling the 'convert popup template reference to independent copy' action when flowEngine.duplicateModel(targetUid) resolves but its response carries no uid in any nested data layer — server-side duplication failed partially, response shape mismatch, or the request was intercepted/short-circuited.","commonSituations":"Client (client-v2 / flow-engine) and server version mismatch changing the duplicateModel response envelope; API middleware stripping or reshaping the response; server error returned as an empty object after a failed deep copy; rate-limit or auth expiry producing an empty 2xx-style response.","solutions":["Inspect the duplicateModel request/response in devtools; fix the server-side duplication failure (permissions, resource limits).","Align @nocobase/client-v2 and flow-engine versions with the server so response unwrapping matches.","Retry the conversion action after any transient network/API failure.","As a workaround, manually duplicate the popup block and re-link the reference to the new copy."],"exampleFix":"// before: response envelope mismatch\nconst newUid = duplicated?.uid || duplicated?.data?.uid; // undefined -> throws\n// after: ensure client/server versions match so envelope is { data: { uid } }\n// yarn why @nocobase/client-v2  # verify single consistent version","handlingStrategy":"try-catch","validationCode":"const duplicated = await model.flowEngine.duplicateModel(targetUid);\nconst newUid = duplicated?.uid || duplicated?.data?.uid || duplicated?.data?.data?.uid;\nif (!newUid) {\n  // duplication succeeded shape-wise but returned no uid — treat as failure, do not save template\n  return message.error('Copy failed; check server duplicate endpoint response.');\n}","typeGuard":"function extractUid(v: unknown): string | null {\n  for (const o = v; o && typeof o === 'object'; ) {\n    if (typeof (o as any).uid === 'string' && (o as any).uid) return (o as any).uid;\n    o = (o as any).data;\n  }\n  return null;\n}","tryCatchPattern":"try {\n  const newUid = await convertFromTemplate(targetUid);\n} catch (e) {\n  if (e.message.includes('Failed to copy popup from template')) {\n    // inspect duplicateModel response in devtools; check server logs\n  } else throw e;\n}","preventionTips":["Verify duplicateModel response envelope after any flow-engine upgrade.","Ensure no API middleware rewrites resource response shapes.","Retry conversions after transient network failures."],"tags":["ui-templates","flow-engine","duplicate-model","response-shape"],"backgroundTag":"duplicate-model-returned-no-uid","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}