{"record":{"id":"f5e2df86b3082d45","repo":"janhq/jan","slug":"res-status","errorCode":null,"errorMessage":"${res.status}","messagePattern":"\\$\\{res\\.status\\}","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"web-app/src/containers/CoworkPreviewPanel.tsx","lineNumber":107,"sourceCode":"      return\n    }\n    if (kind === 'file') {\n      setState({ status: 'unsupported', path })\n      return\n    }\n    setState({ status: 'loading', path })\n\n    void (async () => {\n      const url = getServiceHub().core().convertFileSrc(abs)\n      if (isAssetKind(kind)) {\n        // The element streams the file itself, so there is nothing to fetch and\n        // a large video costs nothing to \"open\".\n        if (alive) setState({ status: 'ready', path, kind, assetUrl: url })\n        return\n      }\n      try {\n        const res = await fetch(url)\n        if (!res.ok) throw new Error(String(res.status))\n        const size = Number(res.headers.get('content-length') ?? 0)\n        if (size > MAX_PREVIEW_BYTES) throw new Error(TOO_LARGE)\n        const content = await res.text()\n        if (content.length > MAX_PREVIEW_BYTES) throw new Error(TOO_LARGE)\n        if (!alive) return\n        setState({\n          status: 'ready',\n          path,\n          kind,\n          content,\n          unresolvedRefs: kind === 'html' ? unresolvedRefs(content) : undefined,\n          externalRefs: kind === 'html' ? externalRefs(content) : undefined,\n        })\n      } catch (e) {\n        if (!alive) return\n        setState({\n          status: 'failed',\n          path,","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/web-app/src/containers/CoworkPreviewPanel.tsx#L89-L125","documentation":"CoworkPreviewPanel loads a preview asset URL with fetch; if the response is not ok it throws new Error(String(res.status)) so the status code becomes the error message (e.g. '404', '403'). The catch sets the panel to an error state showing that status.","triggerScenarios":"The asset URL for a file preview resolves to 404 (file removed), 403 (asset scope/permission), or another 4xx/5xx from the local asset server.","commonSituations":"Previewing a file that was deleted or renamed after listing; Tauri asset protocol scope gaps; stale workspace file index pointing at old paths.","solutions":["Confirm the file exists at the given path before opening the preview.","Check assetProtocol scope configuration for the file's directory.","Refresh the file listing so the preview uses a current path.","Improve the error message to include statusText and the URL for debugging."],"exampleFix":"// before\nif (!res.ok) throw new Error(String(res.status))\n// after\nif (!res.ok) throw new Error(`Preview fetch failed: ${res.status} ${res.statusText} for ${url}`)","handlingStrategy":"try-catch","validationCode":"const head = await fetch(url, { method: 'HEAD' })\nif (!head.ok) throw new Error(`Preview asset unavailable: ${head.status}`)","typeGuard":"const isOkResponse = (r: Response): r is Response & { ok: true } => r.ok","tryCatchPattern":"try {\n  const res = await fetch(url)\n  if (!res.ok) throw new Error(String(res.status))\n  // ...load preview\n} catch (err) {\n  if (alive) setState({ status: 'error', message: `Preview failed (${err.message})` })\n}","preventionTips":["Re-check file existence when the preview path comes from a cached listing.","Handle 403/404 distinctly in the UI (missing vs blocked).","Keep assetProtocol scope updated for workspace directories.","Include statusText and URL in the error state for supportability."],"tags":["fetch","preview","http"],"backgroundTag":"http-non-200-response","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}