{"record":{"id":"021ac01ac590ffc2","repo":"ZhuLinsen/daily_stock_analysis","slug":"desktop-share-images-require-the-configured-backen","errorCode":null,"errorMessage":"Desktop share images require the configured backend origin","messagePattern":"Desktop share images require the configured backend origin","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/dsa-desktop/main.js","lineNumber":1366,"sourceCode":"  }\n\n  let page;\n  try {\n    page = new URL(pageUrl);\n  } catch (_error) {\n    throw new Error('Desktop backend URL is unavailable');\n  }\n\n  let expectedOrigin = page.origin;\n  if (expectedBackendOrigin) {\n    try {\n      expectedOrigin = new URL(expectedBackendOrigin).origin;\n    } catch (_error) {\n      throw new Error('Desktop backend origin is invalid');\n    }\n  }\n  if (page.protocol !== 'http:' || !page.port || page.origin !== expectedOrigin) {\n    throw new Error('Desktop share images require the configured backend origin');\n  }\n\n  return new URL(\n    `/api/v1/history/${recordId}/share-image-html`,\n    page.origin\n  ).toString();\n}\n\nasync function renderDesktopShareImage(\n  recordId,\n  {\n    sourceWindow = mainWindow,\n    BrowserWindowClass = BrowserWindow,\n    backendOrigin = '',\n  } = {}\n) {\n  if (!sourceWindow || sourceWindow.isDestroyed() || !sourceWindow.webContents) {\n    throw new Error('Desktop window is unavailable');","sourceCodeStart":1348,"sourceCodeEnd":1384,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/apps/dsa-desktop/main.js#L1348-L1384","documentation":"Raised by PortfolioService.query_cash_ledger when both date_from and date_to are given and date_from > date_to. Same contract as the trades query: a pre-repository guard, mapped to validation_error, equal dates allowed.","triggerScenarios":"GET /portfolio/cash-ledger?date_from=2025-12-31&date_to=2025-01-01, or query_cash_ledger(date_from=later, date_to=earlier). Only raises when both bounds are non-None and strictly inverted.","commonSituations":"Reused date-range widget wired to the cash-ledger endpoint with swapped field names; 'month to date' logic computing start/end backwards after a refactor; manual API testing with hardcoded dates in the wrong order.","solutions":["Swap or reject inverted ranges in the caller before the request","Fix the off-by-order bug in the range computation (start = min, end = max)","Add a client-side assert so inverted ranges fail fast with a clearer message","Add UI validation that disables the query button when from > to"],"exampleFix":"# before\nsvc.query_cash_ledger(date_from=period_end, date_to=period_start)\n# after\nsvc.query_cash_ledger(date_from=min(period_start, period_end), date_to=max(period_start, period_end))","handlingStrategy":"validation","validationCode":"def safe_range(date_from, date_to):\n    if date_from and date_to and date_from > date_to:\n        date_from, date_to = date_to, date_from\n    return date_from, date_to","typeGuard":"from datetime import date\n\ndef is_valid_date_range(d_from: date | None, d_to: date | None) -> bool:\n    return d_from is None or d_to is None or d_from <= d_to","tryCatchPattern":"try:\n    svc.query_cash_ledger(date_from=f, date_to=t)\nexcept ValueError as exc:\n    if \"date_from must be <= date_to\" in str(exc):\n        raise HTTPBadRequest(detail=\"inverted date range\") from exc\n    raise","preventionTips":["Use one shared range-normalizing helper for all three query endpoints","Never pass (end, start) positional tuples directly into keyword args","Add inverted-range unit tests for every new query wrapper"],"tags":["portfolio","validation","date-range","cash-ledger"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}