{"record":{"id":"0ca8142b9d74fc65","repo":"moeru-ai/airi","slug":"csv-export-is-only-supported-in-browser-environm","errorCode":null,"errorMessage":"[CSV] Export is only supported in browser environments","messagePattern":"\\[CSV\\] Export is only supported in browser environments","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/stage-shared/src/export-csv.ts","lineNumber":16,"sourceCode":"function quoteField(field: unknown): string {\n  return `\"${String(field).replace(/\"/g, '\"\"')}\"`\n}\n\nfunction toCsv(rows: Array<Array<unknown>>): string {\n  return rows\n    .map(row => row.map(quoteField).join(','))\n    .join('\\n')\n}\n\nexport function exportCsv(rows: Array<Array<unknown>>, basename: string) {\n  if (!rows.length)\n    return\n\n  if (typeof Blob === 'undefined' || typeof document === 'undefined' || typeof URL === 'undefined') {\n    console.warn('[CSV] Export is only supported in browser environments')\n    return\n  }\n\n  const csv = toCsv(rows)\n  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })\n  const url = URL.createObjectURL(blob)\n  const link = document.createElement('a')\n  link.href = url\n  link.download = `${basename}-${Date.now()}.csv`\n  link.click()\n  URL.revokeObjectURL(url)\n}\n","sourceCodeStart":1,"sourceCodeEnd":29,"githubUrl":"https://github.com/moeru-ai/airi/blob/677329427f32468c74b17f3ec47eeca4e05bec65/packages/stage-shared/src/export-csv.ts#L1-L29","documentation":"exportCsv implements the browser download flow: Blob + URL.createObjectURL + a synthetic anchor click. It feature-detects Blob, document, and URL first; in an environment missing any of them (Node, SSR/prerender, some workers) it warns and returns without producing a file.","triggerScenarios":"Calling exportCsv during SSR/prerender, in the Electron main process, in a Node script or test runner without DOM globals.","commonSituations":"A shared utility invoked from a server route during prerender; unit tests executing the module in pure Node; main-process export code reusing a shared helper.","solutions":["Only call exportCsv from browser/user-gesture code paths (click handlers in the renderer)","For Node or Electron main, write the CSV with node:fs instead of the Blob download flow","Feature-detect in the caller and hide the export button where unsupported, so users never trigger the silent no-op"],"exampleFix":"// before (shared code, runs everywhere)\nexportCsv(rows, 'messages')\n\n// after (branch by environment)\nimport { isBrowser } from './env'\nif (isBrowser) {\n  exportCsv(rows, 'messages')\n}\nelse {\n  await writeFile('messages.csv', toCsvText(rows), 'utf8')\n}","handlingStrategy":"validation","validationCode":"const canDownloadInBrowser = typeof Blob !== 'undefined' && typeof document !== 'undefined' && typeof URL !== 'undefined'\nif (canDownloadInBrowser) {\n  exportCsv(rows, 'messages')\n}\nelse {\n  console.warn('CSV download unavailable in this environment')\n}","typeGuard":"function isBrowserDownloadEnv(): boolean {\n  return typeof Blob !== 'undefined' && typeof document !== 'undefined' && typeof URL !== 'undefined'\n}","tryCatchPattern":null,"preventionTips":["Gate export UI (buttons/menu items) on browser environment checks","Use node:fs writers for main-process/Node export paths","Cover the export utility with a jsdom test so env regressions surface in CI"],"tags":["csv","export","browser-only","ssr","environment-detection"],"backgroundTag":"browser-only-api-in-node","analyzedSha":"677329427f32468c74b17f3ec47eeca4e05bec65","analyzedAt":"2026-08-18T17:29:58.153Z","contentChangedAt":"2026-08-18T17:29:58.153Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}