{"record":{"id":"f9d008aa90579128","repo":"different-ai/openwork","slug":"tab-order-must-include-every-open-tab","errorCode":null,"errorMessage":"Tab order must include every open tab.","messagePattern":"Tab order must include every open tab\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/desktop/electron/browser-panel.mjs","lineNumber":682,"sourceCode":"    const tabsToClose = closedTabIds\n      .map((tabId) => browserTabs.get(tabId))\n      .filter(Boolean);\n    hideBrowserView();\n    browserTabs.clear();\n    browserTabOrder = [];\n    activeBrowserTabId = null;\n    for (const tab of tabsToClose) {\n      try { tab.view.webContents.close(); } catch { /* already destroyed */ }\n    }\n    sendToRenderer(\"openwork:browser:panel-closed\");\n    sendBrowserState();\n    return closedTabIds;\n  }\n\n  function reorderBrowserTabs(tabIds) {\n    const nextOrder = Array.isArray(tabIds) ? tabIds.map(String) : [];\n    if (nextOrder.length !== browserTabOrder.length) {\n      throw new Error(\"Tab order must include every open tab.\");\n    }\n    if (new Set(nextOrder).size !== nextOrder.length) {\n      throw new Error(\"Tab order must not contain duplicate tabs.\");\n    }\n    const current = new Set(browserTabOrder);\n    if (nextOrder.some((tabId) => !current.has(tabId))) {\n      throw new Error(\"Tab order contains an unknown tab.\");\n    }\n    browserTabOrder = nextOrder;\n    sendBrowserState();\n    return listBrowserTabs();\n  }\n\n  function sendBrowserState() {\n    sendToRenderer(\"openwork:browser:state\", browserStatePayload());\n  }\n\n  /**","sourceCodeStart":664,"sourceCodeEnd":700,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/desktop/electron/browser-panel.mjs#L664-L700","documentation":"reorderBrowserTabs validates that the proposed order array has exactly the same length as the current browserTabOrder; anything shorter or longer throws this error, since a reorder must be a full permutation of open tabs, not a partial or extended list.","triggerScenarios":"Calling reorder with a subset of tab IDs (e.g. only the dragged tab), an empty array when tabs are open, or an array that includes extra IDs beyond the open count (possibly from a stale UI snapshot taken before/after another tab opened).","commonSituations":"UI sending only the moved tab's id; concurrent tab open/close racing the reorder request; client built the order from an outdated browser-state snapshot.","solutions":["Fetch the current tab order and send all open tab IDs in the desired new sequence","Build the reorder payload from a fresh listBrowserTabs() result rather than cached state","Reconcile the client snapshot with the server state when tab counts disagree, then retry"],"exampleFix":"// before\nreorderBrowserTabs([draggedTabId]);\n// after\nconst order = listBrowserTabs().map(t => t.tabId);\nreorderBrowserTabs([draggedTabId, ...order.filter(id => id !== draggedTabId)]);","handlingStrategy":"validation","validationCode":"function canReorder(tabIds, currentOrder) {\n  const next = Array.isArray(tabIds) ? tabIds.map(String) : [];\n  return next.length === currentOrder.length;\n}\nif (!canReorder(ids, listBrowserTabs().map(t => t.tabId))) throw new Error('order must contain every open tab');","typeGuard":"function isFullPermutation(ids, current) {\n  const a = ids.map(String), b = current.map(String);\n  return a.length === b.length && new Set(a).size === a.length && a.every(id => new Set(b).has(id));\n}","tryCatchPattern":"try {\n  reorderBrowserTabs(order);\n} catch (e) {\n  if (e.message === 'Tab order must include every open tab.') {\n    const fresh = listBrowserTabs().map(t => t.tabId);\n    reorderBrowserTabs(order.filter(id => fresh.includes(id)).concat(fresh.filter(id => !order.includes(id))));\n  } else throw e;\n}","preventionTips":["Always build reorder payloads from a fresh listBrowserTabs() snapshot","Never send partial orders (e.g. just the dragged tab)","Re-sync client tab state on open/close events before reordering","Validate length === open-tab count client-side first"],"tags":["validation","browser-tabs","array-length"],"backgroundTag":"partial-array-update","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}