diff --git a/tests/strategy_switch_worker_validation.mjs b/tests/strategy_switch_worker_validation.mjs index 5eba1e7..0c09f75 100644 --- a/tests/strategy_switch_worker_validation.mjs +++ b/tests/strategy_switch_worker_validation.mjs @@ -58,7 +58,12 @@ assert.equal(indexHtml.includes(".innerHTML"), false); assert.ok(indexHtml.includes('
')); assert.ok(indexHtml.includes('id="boot-screen"')); assert.ok(indexHtml.includes('id="app-shell"')); -assert.ok(indexHtml.includes('id="runtime-authority-status"')); +assert.equal(indexHtml.includes('runtime-authority-status'), false); +assert.equal(indexHtml.includes('runtime-authority-notice'), false); +assert.equal(indexHtml.includes('P0–P6'), false); +assert.equal(indexHtml.includes('GitHub OAuth 保护'), false); +assert.equal(indexHtml.includes('Worker 端触发'), false); +assert.equal(indexHtml.includes('令牌保留在服务端'), false); assert.ok(indexHtml.includes('id="control-plane-view-button"')); assert.ok(indexHtml.includes('id="control-plane-view"')); assert.ok(indexHtml.includes('id="control-plane-list"')); @@ -84,10 +89,10 @@ assert.ok(indexHtml.includes('id="reconciliation-recovery-list"')); assert.ok(indexHtml.includes('id="reconciliation-recovery-notice"')); assert.ok(indexHtml.includes('requestJson("/api/reconciliation-recovery")')); assert.ok(indexHtml.includes('data-reconciliation-recovery-confirm')); -assert.ok(indexHtml.includes('P0_CONTROL_PLANE_NOT_RUNTIME_WIRED')); -assert.ok(indexHtml.includes('window.__QSL_RUNTIME_AUTHORITY_STATUS__')); -assert.ok(indexHtml.includes('execution_metadata_is_runtime_authority')); -assert.ok(indexHtml.includes('P1–P3 non-live 数据获取仍需独立、精确的契约')); +assert.equal(indexHtml.includes('P0_CONTROL_PLANE_NOT_RUNTIME_WIRED'), false); +assert.equal(indexHtml.includes('window.__QSL_RUNTIME_AUTHORITY_STATUS__'), false); +assert.equal(indexHtml.includes('execution_metadata_is_runtime_authority'), false); +assert.equal(indexHtml.includes('P1–P3 non-live 数据获取仍需独立、精确的契约'), false); assert.ok(indexHtml.includes('requestJson("/api/execution-evidence")')); assert.ok(indexHtml.includes('requestJson("/api/adaptive-selection")')); assert.equal(indexHtml.includes('missing_current_promotion_evidence_and_human_acceptance'), false); @@ -212,8 +217,8 @@ assert.equal(indexHtml.includes('id="ibit-zscore-exit-mode-select"'), false); assert.equal(indexHtml.includes("ibitZscoreExit"), false); assert.equal(indexHtml.includes("ibit_zscore_exit_mode"), false); assert.ok(indexHtml.includes('reservedCashDefault')); -assert.ok(indexHtml.includes('dryRun: "演练(不下单)"')); -assert.ok(indexHtml.includes('dryRun: "Dry run (no orders)"')); +assert.ok(indexHtml.includes('dryRun: "模拟运行"')); +assert.ok(indexHtml.includes('dryRun: "Simulated run"')); assert.ok(indexHtml.includes('平台默认:0 {currency} / 0%')); assert.equal(indexHtml.includes('比例沿用策略默认,通常 3%'), false); assert.equal(indexHtml.includes('平台默认:max(0 {currency}, 3%)'), false); @@ -262,6 +267,7 @@ assert.equal(bootstrapConfigResponse.headers.get("Content-Type"), "application/j assert.equal(bootstrapConfigResponse.headers.get("X-Content-Type-Options"), "nosniff"); assert.ok(bootstrapConfigJs.includes("window.__PLATFORM_CONFIG__")); assert.ok(bootstrapConfigJs.includes("window.__DEFAULT_STRATEGY_PROFILES__")); +assert.equal(bootstrapConfigJs.includes("window.__QSL_RUNTIME_AUTHORITY_STATUS__"), false); assert.equal(bootstrapConfigJs.includes("ibkr-primary"), false); assert.equal(bootstrapConfigJs.includes("longbridge-quant-sg-service"), false); assert.equal(bootstrapConfigJs.includes('account_selector: "SG"'), false); diff --git a/web/strategy-switch-console/app.css b/web/strategy-switch-console/app.css index 1cea119..f51e596 100644 --- a/web/strategy-switch-console/app.css +++ b/web/strategy-switch-console/app.css @@ -137,36 +137,6 @@ background: rgba(255, 255, 255, 0.7); } - .runtime-authority-notice { - display: grid; - grid-template-columns: minmax(210px, 0.42fr) minmax(0, 0.9fr) minmax(0, 1fr); - gap: 12px 18px; - align-items: center; - margin-bottom: 14px; - padding: 14px 16px; - border: 1px solid rgba(186, 146, 59, 0.34); - border-radius: var(--radius-md, 12px); - background: linear-gradient(90deg, rgba(255, 248, 223, 0.92), rgba(255, 255, 255, 0.9)); - color: #5d4617; - } - - .runtime-authority-notice > div { - display: grid; - gap: 4px; - } - - .runtime-authority-notice strong { - color: #8a5a00; - font-size: 13px; - overflow-wrap: anywhere; - } - - .runtime-authority-notice p { - margin: 0; - font-size: 12px; - line-height: 1.5; - } - .console-nav__button { min-height: 38px; padding: 0 15px; @@ -1558,8 +1528,7 @@ .platform-strip, .quick-form, - .form-section, - .runtime-authority-notice { + .form-section { grid-template-columns: 1fr; } @@ -1729,14 +1698,6 @@ box-shadow: inset 0 0 0 1px rgba(116, 167, 193, 0.2); } - .runtime-authority-notice { - border-color: rgba(231, 181, 82, 0.42); - background: linear-gradient(90deg, rgba(80, 60, 24, 0.5), rgba(13, 27, 43, 0.85)); - color: #f0d595; - } - - .runtime-authority-notice strong { color: #ffd16d; } - .health-head { align-items: flex-start; padding: 8px 2px 2px; @@ -1888,10 +1849,6 @@ align-items: start; } - .runtime-authority-notice { - grid-column: 1 / -1; - } - .console-nav { position: sticky; top: 98px; @@ -1979,17 +1936,6 @@ padding: 26px 0 44px; } - .runtime-authority-notice { - border-color: #e7cf95; - background: #fffbef; - color: #705c2e; - box-shadow: none; - } - - .runtime-authority-notice strong { - color: #8b6419; - } - .console-nav { width: fit-content; padding: 3px; @@ -2629,7 +2575,7 @@ .decision-board { display: grid; - grid-template-columns: minmax(0, 1fr) minmax(240px, 0.34fr); + grid-template-columns: minmax(0, 1fr); gap: 18px; align-items: start; } @@ -2804,12 +2750,6 @@ top: 92px; } - .runtime-authority-notice { - padding: 11px 13px; - border-radius: 9px; - font-size: 12px; - } - @media (min-width: 900px) { .decision-view, #switch-view, diff --git a/web/strategy-switch-console/app.js b/web/strategy-switch-console/app.js index 0d7e567..43f2602 100644 --- a/web/strategy-switch-console/app.js +++ b/web/strategy-switch-console/app.js @@ -104,7 +104,6 @@ default_execution_mode: "live" }, }; - const runtimeAuthorityStatus = window.__QSL_RUNTIME_AUTHORITY_STATUS__ || {}; @@ -811,79 +810,75 @@ const copy = { zh: { appTitle: "QuantStrategyLab", - appSubtitle: "已验证证据、运行许可和所有者决策。", + appSubtitle: "策略与平台的日常管理", controlPlaneView: "决策", healthView: "系统状态", switchView: "策略设置", - controlPlaneEyebrow: "人工决策台 / 只读", - controlPlaneTitle: "只看需要你判断的事。", - controlPlaneSubtitle: "系统会持续监测与筛选;这里仅保留需要你确认的风险边界和恢复决定。", - controlCandidateTotal: "候选总数", - controlDeferred: "本次延期", - controlParked: "已停车", - controlOwnerDecision: "等待我决定", - controlQueueEyebrow: "优先队列", - controlQueueHint: "只显示可操作事项", - controlCandidateBoard: "需要你确认", - decisionGuideEyebrow: "怎么判断", - decisionGuideTitle: "先确认事实,再记录意图。", - decisionGuideStepOne: "确认券商账户、目标策略和当前状态是否正确。", - decisionGuideStepTwo: "看证据是否新鲜,是否仍有阻断项。", - decisionGuideStepThree: "只在你认可时记录“继续 / 保持暂停 / 退役”。", - decisionGuideBoundary: "网页记录的是可审计意图,不会下单、改资金或自动开启实盘。", - controlDataReady: "快照已同步(不代表策略可运行)", - controlDataStale: "快照已过期", - controlDataUnavailable: "等待可用快照", - controlAttentionResearchOnly: "仅研究,未授予执行权限", - controlAttentionRequired: "研究状态需要关注", - controlAttentionUnavailable: "尚无已验证研究状态", - controlComputedAt: "最近计算:{time}", - controlLoginNotice: "登录后读取私有全局快照;没有快照时不会展示虚构信息。", - controlStaleNotice: "全局快照已超过允许的新鲜度窗口;页面保留原始记录,但不把它作为当前结论。", - controlUnavailableNotice: "还没有可用的全局快照;当前页面保持 fail-closed 空状态。", - controlUpstreamNotice: "快照已加载,但有 {count} 个上游提示;缺失信息不会被补成推断结论。", - controlAttentionNotice: "研究状态需要关注(延期 {deferred}、停车 {parked}、信号 {signals})。快照送达不等于策略验证或执行授权;系统保持不交易。", - controlEmptyCandidates: "暂无可展示的候选快照。", - controlNoRecommendation: "没有可用的机器建议。", - controlStageMeta: "阶段:{stage} · 状态:{status} · 证据:{freshness}", - controlNext: "下一步", - ownerDecisionTitle: "所有者决定", - ownerDecisionAdminOnly: "只有控制台管理员可以记录决定。", - ownerDecisionReady: "当前证据已绑定;请人工选择后记录不可执行意图。", - ownerDecisionRecorded: "已记录:{decision}(仅意图,不会启用实盘)", - ownerDecisionApprove: "批准受限试运行意图", + controlPlaneEyebrow: "待处理事项", + controlPlaneTitle: "今天需要你处理什么?", + controlPlaneSubtitle: "系统正常时这里保持为空;出现风险、异常或恢复事项时,按优先级显示。", + controlCandidateTotal: "监控对象", + controlDeferred: "待复核", + controlParked: "暂停中", + controlOwnerDecision: "待处理", + controlQueueEyebrow: "优先处理", + controlQueueHint: "有风险或需要确认时才出现", + controlCandidateBoard: "需要你处理", + controlDataReady: "已更新", + controlDataStale: "更新延迟", + controlDataUnavailable: "暂时无法读取", + controlAttentionResearchOnly: "暂无待处理事项", + controlAttentionRequired: "需要关注", + controlAttentionUnavailable: "等待数据", + controlComputedAt: "更新于:{time}", + controlLoginNotice: "登录后查看待处理事项和运行状态。", + controlStaleNotice: "数据更新延迟,暂不建议据此做新决定。", + controlUnavailableNotice: "暂时无法读取最新状态,请稍后刷新。", + controlUpstreamNotice: "部分数据暂不可用,请稍后重试。", + controlAttentionNotice: "有 {deferred} 项待复核,{parked} 项已暂停。", + controlNormalNotice: "当前没有需要你处理的事项。", + controlEmptyCandidates: "当前没有待处理事项。", + controlNoRecommendation: "暂未给出处理建议。", + controlItemMeta: "{kind} · {domain} · 最近更新:{freshness}", + controlNext: "处理建议", + controlStatus: "当前状态", + ownerDecisionTitle: "请选择下一步", + ownerDecisionAdminOnly: "请由管理员确认。", + ownerDecisionReady: "相关信息已就绪,请选择下一步。", + ownerDecisionRecorded: "已保存:{decision}", + ownerDecisionApprove: "确认试运行", ownerDecisionPark: "保持暂停", - ownerDecisionRetire: "退役候选", - ownerDecisionConfirm: "这只会记录不可执行的所有者决定意图,不会下单、改资金或启用实盘。继续吗?", - ownerDecisionSubmitting: "正在记录决定…", - ownerDecisionSuccess: "所有者决定已记录;它不会自动启用实盘。", - ownerDecisionFailed: "无法记录所有者决定", - reconciliationRecoveryBoard: "恢复核验(仅已存在实盘)", - reconciliationRecoveryLoginNotice: "登录后读取私有恢复核验;缺失快照不会被推断为可以恢复。", - reconciliationRecoveryStaleNotice: "恢复核验已过期;请等待平台重新采样和双审,不能确认。", - reconciliationRecoveryUnavailableNotice: "暂无可用恢复核验;页面保持 fail-closed 空状态。", - reconciliationRecoveryUpstreamNotice: "恢复核验已加载,但有 {count} 个上游提示;请先处理阻断项。", - reconciliationRecoveryEmpty: "没有待恢复的既有实盘目标。", - reconciliationRecoveryMeta: "{platform} · {strategy} · 仅恢复现有执行", - reconciliationRecoveryDetail: "状态:{state} · 只读样本:{samples} · 双审:{review} · 窗口截止:{lastObserved}", - reconciliationRecoveryBlocked: "仍被阻断:{blockers}", - reconciliationRecoveryReady: "两次对账一致且双审绑定当前摘要;需要管理员确认。", - reconciliationRecoveryAdminOnly: "只有控制台管理员可以确认恢复意图。", - reconciliationRecoveryConfirmed: "已记录恢复确认(仍未恢复运行)。", - reconciliationRecoveryConfirm: "确认恢复既有执行意图", - reconciliationRecoveryConfirmPrompt: "这只会记录与当前对账/双审摘要绑定的恢复意图;不会下单、修改资金或直接启用运行。继续吗?", - reconciliationRecoverySubmitting: "正在记录确认…", - reconciliationRecoverySuccess: "恢复确认已记录;私有控制器仍须重新核验后才可恢复既有执行。", - reconciliationRecoveryFailed: "无法记录恢复确认", - reconciliationRecoveryNoOrder: "固定边界:控制台不持有券商凭证,也不直接恢复、下单或启用实盘。", - executionEvidenceBoard: "按需查看策略 × 平台证据覆盖", - executionEvidenceLoginNotice: "登录后读取私有执行证据快照;缺失来源不会被推断成 paper 或 live 状态。", - executionEvidenceStaleNotice: "执行证据已超过允许的新鲜度窗口;它保留为历史记录,不能作为当前运行或 P6 结论。", - executionEvidenceUnavailableNotice: "还没有可用的执行证据来源;当前列表保持 fail-closed 空状态。", - executionEvidenceUpstreamNotice: "执行证据已加载,但有 {count} 个上游提示;页面不会补造平台能力或运行资格。", - executionEvidenceEmpty: "暂无可展示的策略 × 平台执行证据。", + ownerDecisionRetire: "停止跟踪", + ownerDecisionConfirm: "确认保存此项决定?", + ownerDecisionSubmitting: "正在保存…", + ownerDecisionSuccess: "决定已保存。", + ownerDecisionFailed: "无法保存决定", + reconciliationRecoveryBoard: "恢复事项", + reconciliationRecoveryLoginNotice: "登录后查看需要恢复或复核的事项。", + reconciliationRecoveryStaleNotice: "恢复信息更新延迟,请等待下一次检查。", + reconciliationRecoveryUnavailableNotice: "暂时没有可用的恢复信息。", + reconciliationRecoveryUpstreamNotice: "部分恢复信息暂不可用,请先查看阻断原因。", + reconciliationRecoveryEmpty: "当前没有需要恢复的事项。", + reconciliationRecoveryMeta: "{platform} · {strategy}", + reconciliationRecoveryDetail: "状态:{state} · 样本:{samples} · 复核:{review} · 最近更新:{lastObserved}", + reconciliationRecoveryBlocked: "仍需处理:{blockers}", + reconciliationRecoveryReady: "相关核对已完成,等待确认。", + reconciliationRecoveryAdminOnly: "请由管理员确认。", + reconciliationRecoveryConfirmed: "恢复确认已保存。", + reconciliationRecoveryConfirm: "确认恢复", + reconciliationRecoveryConfirmPrompt: "确认保存恢复决定?", + reconciliationRecoverySubmitting: "正在保存…", + reconciliationRecoverySuccess: "恢复决定已保存。", + reconciliationRecoveryFailed: "无法保存恢复决定", + reconciliationRecoveryNoOrder: "查看当前恢复状态。", + executionEvidenceBoard: "执行与成交记录", + executionEvidenceLoginNotice: "登录后查看执行和成交记录。", + executionEvidenceStaleNotice: "执行记录更新延迟,请结合最新平台状态判断。", + executionEvidenceUnavailableNotice: "暂时没有可用的执行记录。", + executionEvidenceUpstreamNotice: "部分执行记录暂不可用,请稍后重试。", + executionEvidenceEmpty: "当前没有可展示的执行记录。", executionEvidenceMeta: "{platform} · 当前通道:{environment} · 来源:{source}", - executionEvidenceDetail: "策略:{strategy} · 数据:{data} · 执行:{execution} · Shadow:{shadow} · Paper:{paper}", + executionEvidenceDetail: "策略:{strategy} · 数据:{data} · 执行:{execution} · 观察:{shadow} · 模拟:{paper}", executionEvidenceReceipt: "执行回执:{outcome} · 券商确认:{confirmation}", executionEvidenceReceiptMissing: "执行回执:未采集", executionEvidenceReceiptNotDue: "未到应交易窗口", @@ -901,19 +896,19 @@ executionEvidenceConfirmationPartiallyFilled: "部分成交", executionEvidenceConfirmationFilled: "已成交", executionEvidenceConfirmationReconciliation: "需对账", - executionEvidenceNoOrder: "固定边界:只读证据;不包含账户、订单、资金或 P6 实盘授权。", - executionEvidenceNext: "下一步", - runtimeTargetLifecycleBoard: "按需查看平台运行状态", - runtimeTargetLifecycleLoginNotice: "登录后读取私有平台运行状态;页面不会改变启用/停用或提交订单。", - runtimeTargetLifecycleStaleNotice: "平台运行状态已超过允许的新鲜度窗口;请先检查监测链路。", - runtimeTargetLifecycleUnavailableNotice: "还没有可用的平台运行状态;缺失数据不会被推断成已停用或正常。", - runtimeTargetLifecycleUpstreamNotice: "平台运行状态已加载,但有 {count} 个上游提示;请先核对。", - runtimeTargetLifecycleEmpty: "暂无可展示的平台运行状态。", + executionEvidenceNoOrder: "查看当前执行和成交结果。", + executionEvidenceNext: "处理建议", + runtimeTargetLifecycleBoard: "平台运行状态", + runtimeTargetLifecycleLoginNotice: "登录后查看平台运行状态。", + runtimeTargetLifecycleStaleNotice: "平台状态更新延迟,请先检查运行情况。", + runtimeTargetLifecycleUnavailableNotice: "暂时没有可用的平台状态。", + runtimeTargetLifecycleUpstreamNotice: "部分平台状态暂不可用,请先核对。", + runtimeTargetLifecycleEmpty: "当前没有可展示的平台状态。", runtimeTargetLifecycleMeta: "{platform} · {state} · 通道:{mode}", runtimeTargetLifecycleDetail: "运行监测:{guard} · 执行心跳:{heartbeat}", runtimeTargetLifecycleObservation: "本次执行:{observation} · 成交证据:{evidence}", - runtimeTargetLifecycleNoOrder: "固定边界:只读状态;不会启用目标、变更策略或提交订单。", - runtimeTargetLifecycleNext: "当前处理", + runtimeTargetLifecycleNoOrder: "查看当前平台运行情况。", + runtimeTargetLifecycleNext: "当前建议", runtimeTargetLifecycleStateEnabled: "已启用", runtimeTargetLifecycleStateDisabled: "已停用", runtimeTargetLifecycleCheckPass: "通过", @@ -935,45 +930,42 @@ runtimeTargetLifecycleReasonRuntimeGuard: "运行监测需要复核", runtimeTargetLifecycleReasonHeartbeat: "执行心跳需要复核", runtimeTargetLifecycleReasonUnavailable: "监测数据不可用", - m0ResearchBoard: "按需查看 M0 研究观察", - m0ResearchLoginNotice: "登录后读取私有 M0 研究台账;缺失台账不会被补成研究结论。", - m0ResearchStaleNotice: "M0 研究观察已超过有效期,仅保留为历史上下文。", - m0ResearchUnavailableNotice: "还没有可用的 M0 研究台账;页面保持 fail-closed 空状态。", - m0ResearchUpstreamNotice: "M0 研究台账已加载,但有 {count} 个上游提示;页面不会补造研究结论。", - m0ResearchEmpty: "暂无可展示的已验证 M0 研究观察。", - m0ResearchMore: "仅显示前 {count} 条研究观察;其余仍保留在只读台账中。", + m0ResearchBoard: "市场研究", + m0ResearchLoginNotice: "登录后查看市场研究和观察记录。", + m0ResearchStaleNotice: "研究内容更新延迟,仅供历史参考。", + m0ResearchUnavailableNotice: "暂时没有可用的市场研究。", + m0ResearchUpstreamNotice: "部分研究内容暂不可用,请稍后重试。", + m0ResearchEmpty: "当前没有可展示的市场研究。", + m0ResearchMore: "显示前 {count} 条研究观察。", m0ResearchMeta: "{kind} · 查看于:{viewed}", m0ResearchStateFreshness: "状态:{state} · 新鲜度:{freshness}", m0ResearchHorizons: "主要观察期:{primary} · 适用观察期:{suitable}", m0ResearchEvidence: "置信:{confidence} · 风格:{style} · 资料摘要:{digest}", m0ResearchConsistency: "当前周期分歧:{conflict} · 历史失效漂移:{drift}", - m0ResearchNoOrder: "固定边界:仅研究、无订单;不产生任何运行动作。", - adaptiveSelectionBoard: "按需查看 M1 Shadow 建议", - adaptiveSelectionLoginNotice: "登录后读取私有 Shadow 建议;缺失来源不会被补成策略或订单。", - adaptiveSelectionStaleNotice: "此 Shadow 建议已超出新鲜度窗口,仅保留为历史上下文。", - adaptiveSelectionUnavailableNotice: "还没有可用的 M1 Shadow 建议;页面保持 fail-closed 空状态。", - adaptiveSelectionUpstreamNotice: "Shadow 建议带有 {count} 个上游提示;页面不会推断策略、平台或订单。", - adaptiveSelectionEmpty: "暂无可展示的 Shadow 建议。", + m0ResearchNoOrder: "查看当前研究摘要。", + adaptiveSelectionBoard: "系统建议", + adaptiveSelectionLoginNotice: "登录后查看系统建议。", + adaptiveSelectionStaleNotice: "建议更新延迟,仅供历史参考。", + adaptiveSelectionUnavailableNotice: "暂时没有可用的系统建议。", + adaptiveSelectionUpstreamNotice: "部分建议暂不可用,请稍后重试。", + adaptiveSelectionEmpty: "当前没有可展示的系统建议。", adaptiveSelectionMeta: "{source} · {domain} · 市场截至 {asOf}", adaptiveSelectionRecommended: "建议观察", - adaptiveSelectionNoCandidate: "无合格 Shadow 候选", + adaptiveSelectionNoCandidate: "暂无合适建议", adaptiveSelectionReason: "原因:{reasons}", - adaptiveSelectionNoOrder: "固定边界:仅供人工查看,零建议权重,不改策略、平台、资金、运行状态或订单。", - researchTaskBoard: "按需查看研究任务队列", - researchTaskLoginNotice: "登录后读取私有研究任务索引;没有来源快照时不会展示虚构任务。", - researchTaskStaleNotice: "研究任务来源已超过允许的新鲜度窗口;任务保留为历史记录,但不作为当前工作指令。", - researchTaskUnavailableNotice: "还没有可用的研究任务来源;当前队列保持 fail-closed 空状态。", - researchTaskUpstreamNotice: "研究任务索引已加载,但有 {count} 个上游提示;页面不会补造任务。", - researchTaskEmpty: "暂无可展示的已验证研究任务。", + adaptiveSelectionNoOrder: "查看系统的当前建议。", + researchTaskBoard: "自动化任务", + researchTaskLoginNotice: "登录后查看自动化任务。", + researchTaskStaleNotice: "任务信息更新延迟,仅供历史参考。", + researchTaskUnavailableNotice: "暂时没有可用的自动化任务。", + researchTaskUpstreamNotice: "部分任务信息暂不可用,请稍后重试。", + researchTaskEmpty: "当前没有可展示的自动化任务。", researchTaskMeta: "{type} · {domain} · 创建于:{created}", - researchTaskLimits: "离线研究:最多 {runs} 次 / {seconds} 秒 · P1 {p1} · P2 {p2} · P3 {p3}", - researchTaskNoOrder: "固定边界:仅研究、零仓位、无订单;不进入 P4/P5/P6。", - healthEyebrow: "运行保障 / 只读", - healthTitle: "先看机器结论,再决定是否介入。", - healthSubtitle: "健康不是实盘许可;它只告诉你是否要复核、降级或保持观察。", - runtimeAuthorityTitle: "P0–P6 控制面授权状态", - runtimeAuthorityNotice: "这里的 live/default_execution_mode/live_configured 是历史配置元数据,不是当前运行、订单、资金或阶段升级授权。", - runtimeAuthorityAsOf: "截至:{asOf} · P1–P3 non-live 数据获取仍需独立、精确的契约;P4–P6 尚未定义。", + researchTaskLimits: "研究预算:最多 {runs} 次 / {seconds} 秒", + researchTaskNoOrder: "查看当前自动化任务。", + healthEyebrow: "系统状态", + healthTitle: "运行情况", + healthSubtitle: "只突出需要关注的风险、延迟和恢复状态。", healthTotal: "策略总数", healthHealthy: "健康", healthWatch: "观察", @@ -991,11 +983,11 @@ login: "登录", logout: "退出", signedInAs: "已登录 {login}", - planEyebrow: "运行计划 / 受限变更", - planTitle: "先定义边界,再提交配置。", - planSubtitle: "一次只为一个平台和账号准备一个可审计的运行计划。页面只提交配置意图;运行、订单和实盘授权由独立的证据与阶段门槛决定。", - planAdvancedSummary: "更多风险边界(按需设置)", - planAdvancedHint: "运行状态、插件、现金、收入层和定投通常沿用当前已验证配置;只在需要变更时展开。", + planEyebrow: "策略设置", + planTitle: "修改策略设置", + planSubtitle: "选择平台、账户、策略和运行环境;保存前核对本次改动。", + planAdvancedSummary: "高级设置", + planAdvancedHint: "运行状态、插件、现金、收入层和定投通常沿用当前配置;需要改动时再展开。", planScopeTitle: "选择范围", planScopeSubtitle: "平台、账号、策略与目标环境", planRuntimeTitle: "运行保护", @@ -1007,10 +999,10 @@ account: "目标账号", strategy: "策略", mode: "运行环境", - live: "实盘(需独立授权)", + live: "实盘", paper: "旧版非实盘", - dryRun: "演练(不下单)", - liveModeUnavailable: "当前没有同时满足策略发布、运行许可与证据门槛的实盘候选;请保持非实盘。", + dryRun: "模拟运行", + liveModeUnavailable: "该策略暂不支持实盘,请选择非实盘。", runtimeTargetMode: "账号运行状态", runtimeSectionTitle: "运行与插件", runtimeTargetCurrent: "沿用当前状态", @@ -1059,7 +1051,7 @@ executionCashMarginBlocksReserve: "已选允许融资;提交时会清空预留现金覆盖。", executionCashReserveBlocksMargin: "已设预留现金覆盖;提交时会强制不允许融资。", qmtPlatformCashNote: "A 股 QMT 不使用 margin / 平台预留现金;现金约束在策略参数 execution_cash_reserve_ratio 内配置。", - qmtDryRunOnlyNote: "当前平台仅支持不下单演练,尚无可用的实盘执行通道。", + qmtDryRunOnlyNote: "当前平台仅支持模拟运行。", binancePlatformNote: "Binance 平台不使用券商级收入层与期权层;相关功能由策略内部实现。", invalidExecutionCashPolicyNote: "允许融资与预留现金覆盖冲突,请只保留一种约束。", dcaMode: "定投模式", @@ -1093,37 +1085,31 @@ planReadinessTitle: "提交前核对", planCheckAccount: "账号与作用范围", planCheckStrategy: "策略与目标环境", - planCheckRisk: "现金与风险边界", - planCheckAuthority: "实盘执行授权", + planCheckRisk: "现金与风险设置", + planCheckAuthority: "实盘状态", planCheckWaiting: "等待配置", planCheckSelected: "已读取", planCheckValid: "已校验", planCheckFix: "需修正", planCheckNonLive: "非实盘", - planCheckNoAuthority: "未授予", - planAuditNote: "提交记录会写入审计链;它不等同于订单、资金或实盘许可。", + planCheckNoAuthority: "未就绪", + planAuditNote: "本次修改会保存到变更记录。", copySummary: "复制状态", loginToRun: "登录后提交计划", loadingConfig: "读取配置中", configureAccounts: "配置账号后切换", - runSwitch: "提交受限变更", + runSwitch: "保存设置", noChanges: "无变更", - readonlyNote: "登录后才可提交受限配置。", + readonlyNote: "登录后可保存设置。", publicReadonly: "登录后查看账号配置。", loadingConfigNote: "正在读取账号配置和当前状态。", missingConfigNote: "账号配置未加载,暂时不能执行。", - readyNote: "提交的是受限配置变更;它不授予订单、资金或实盘权限。", + readyNote: "请核对上方改动后保存。", invalidStrategyNote: "当前账号没有可执行策略,暂时不能切换。", invalidReservePolicyNote: "请为当前预留现金策略填写有效金额或比例。", invalidIncomeLayerNote: "请填写有效的收入层起始金额和最高比例。", invalidOptionOverlayNote: "当前策略未定义可启用的期权层。", invalidDcaNote: "请填写有效的定投模式和基准金额。", - publicOauthTitle: "GitHub OAuth 保护", - publicOauthText: "只允许白名单账号进入私有配置。", - publicWorkerTitle: "Worker 端触发", - publicWorkerText: "令牌保留在服务端,浏览器只提交切换意图。", - publicAuditTitle: "变更可回溯", - publicAuditText: "受限配置变更由 GitHub Actions 执行,便于审计和回滚。", noAccount: "没有账号选项", noStrategy: "没有支持的策略", repository: "平台仓库", @@ -1170,79 +1156,75 @@ }, en: { appTitle: "QuantStrategyLab", - appSubtitle: "Verified evidence, runtime eligibility, and owner decisions.", + appSubtitle: "Daily management for strategies and platforms", controlPlaneView: "Decisions", healthView: "System status", switchView: "Strategy settings", - controlPlaneEyebrow: "Owner decision desk / read only", - controlPlaneTitle: "See only what needs your judgment.", - controlPlaneSubtitle: "The system keeps monitoring and screening. This desk keeps only risk boundaries and recovery decisions for you to confirm.", - controlCandidateTotal: "Candidates", - controlDeferred: "Deferred now", - controlParked: "Parked", - controlOwnerDecision: "Needs my decision", - controlQueueEyebrow: "Priority queue", - controlQueueHint: "Actionable items only", - controlCandidateBoard: "Needs your confirmation", - decisionGuideEyebrow: "How to decide", - decisionGuideTitle: "Confirm facts, then record intent.", - decisionGuideStepOne: "Check that the broker account, target strategy, and current state are correct.", - decisionGuideStepTwo: "Check whether evidence is fresh and whether blockers remain.", - decisionGuideStepThree: "Record continue, stay parked, or retire only when you agree.", - decisionGuideBoundary: "This page records auditable intent. It cannot place orders, change funds, or enable Live automatically.", - controlDataReady: "Snapshot synced (not strategy readiness)", - controlDataStale: "Snapshot is stale", - controlDataUnavailable: "Waiting for a usable snapshot", - controlAttentionResearchOnly: "Research only; no execution authority", - controlAttentionRequired: "Research state needs attention", - controlAttentionUnavailable: "No verified research state", - controlComputedAt: "Last computed: {time}", - controlLoginNotice: "Sign in to read the private global snapshot. Missing data is never invented.", - controlStaleNotice: "This snapshot is outside its freshness window. The record remains visible, but is not a current conclusion.", - controlUnavailableNotice: "No usable global snapshot is available yet. This page remains fail-closed and empty.", - controlUpstreamNotice: "The snapshot loaded with {count} upstream notice(s); missing information is never inferred.", - controlAttentionNotice: "Research needs attention ({deferred} deferred, {parked} parked, {signals} signal(s)). Snapshot delivery is not strategy validation or execution authority; the system remains no-order.", - controlEmptyCandidates: "No candidate snapshot is available to display.", - controlNoRecommendation: "No machine recommendation is available.", - controlStageMeta: "Stage: {stage} · status: {status} · evidence: {freshness}", - controlNext: "NEXT", - ownerDecisionTitle: "Owner decision", - ownerDecisionAdminOnly: "Only console administrators can record a decision.", - ownerDecisionReady: "Current evidence is bound; choose an action to record a non-executable intent.", - ownerDecisionRecorded: "Recorded: {decision} (intent only; Live remains disabled)", - ownerDecisionApprove: "Approve limited-canary intent", + controlPlaneEyebrow: "To do", + controlPlaneTitle: "What needs your attention today?", + controlPlaneSubtitle: "This stays empty when everything is normal. Risks, exceptions, and recovery items appear in priority order.", + controlCandidateTotal: "Monitored items", + controlDeferred: "To review", + controlParked: "Paused", + controlOwnerDecision: "To do", + controlQueueEyebrow: "Priority", + controlQueueHint: "Only appears when action is needed", + controlCandidateBoard: "Needs your attention", + controlDataReady: "Up to date", + controlDataStale: "Update delayed", + controlDataUnavailable: "Unavailable", + controlAttentionResearchOnly: "Nothing to do", + controlAttentionRequired: "Needs attention", + controlAttentionUnavailable: "Waiting for data", + controlComputedAt: "Updated: {time}", + controlLoginNotice: "Sign in to see your to-do items and runtime status.", + controlStaleNotice: "Data is delayed. Avoid making a new decision from it for now.", + controlUnavailableNotice: "The latest status is temporarily unavailable. Please refresh later.", + controlUpstreamNotice: "Some data is temporarily unavailable. Please retry later.", + controlAttentionNotice: "{deferred} item(s) need review and {parked} are paused.", + controlNormalNotice: "There is nothing you need to handle right now.", + controlEmptyCandidates: "There is nothing to handle right now.", + controlNoRecommendation: "No action is recommended yet.", + controlItemMeta: "{kind} · {domain} · updated {freshness}", + controlNext: "Recommended action", + controlStatus: "Current status", + ownerDecisionTitle: "Choose the next step", + ownerDecisionAdminOnly: "An administrator needs to confirm this.", + ownerDecisionReady: "The relevant information is ready. Choose the next step.", + ownerDecisionRecorded: "Saved: {decision}", + ownerDecisionApprove: "Confirm trial run", ownerDecisionPark: "Keep parked", - ownerDecisionRetire: "Retire candidate", - ownerDecisionConfirm: "This records a non-executable owner intent only. It will not place orders, change funds, or enable Live. Continue?", - ownerDecisionSubmitting: "Recording decision…", - ownerDecisionSuccess: "Owner decision recorded; it does not enable Live automatically.", - ownerDecisionFailed: "Could not record owner decision", - reconciliationRecoveryBoard: "Recovery verification (existing live only)", - reconciliationRecoveryLoginNotice: "Sign in to read private recovery verification. Missing snapshots never imply that recovery is allowed.", - reconciliationRecoveryStaleNotice: "Recovery verification is stale. Wait for fresh platform sampling and dual review before confirming.", - reconciliationRecoveryUnavailableNotice: "No usable recovery verification is available. This view remains fail-closed and empty.", - reconciliationRecoveryUpstreamNotice: "Recovery verification loaded with {count} upstream notice(s). Resolve blockers first.", - reconciliationRecoveryEmpty: "No existing live target is awaiting recovery.", - reconciliationRecoveryMeta: "{platform} · {strategy} · existing-execution recovery only", - reconciliationRecoveryDetail: "state: {state} · read-only samples: {samples} · dual review: {review} · window ends: {lastObserved}", - reconciliationRecoveryBlocked: "Still blocked: {blockers}", - reconciliationRecoveryReady: "Two reconciliations match and dual review is bound to the current digest; an admin confirmation is required.", - reconciliationRecoveryAdminOnly: "Only console administrators can record a recovery confirmation.", - reconciliationRecoveryConfirmed: "Recovery confirmation recorded (runtime is still not restored).", - reconciliationRecoveryConfirm: "Record existing-execution recovery intent", - reconciliationRecoveryConfirmPrompt: "This only records a recovery intent bound to the current reconciliation and dual-review digest. It will not place orders, change funds, or directly enable runtime. Continue?", - reconciliationRecoverySubmitting: "Recording confirmation…", - reconciliationRecoverySuccess: "Recovery confirmation recorded. A private controller must re-verify before it can restore existing execution.", - reconciliationRecoveryFailed: "Could not record recovery confirmation", - reconciliationRecoveryNoOrder: "Fixed boundary: the console holds no broker credentials and cannot directly recover, trade, or enable Live.", - executionEvidenceBoard: "View strategy × platform evidence on demand", - executionEvidenceLoginNotice: "Sign in to read private execution-evidence snapshots. Missing sources never imply a paper or live state.", - executionEvidenceStaleNotice: "This execution evidence is beyond its freshness window. It remains historical context, not a current runtime or P6 conclusion.", - executionEvidenceUnavailableNotice: "No usable execution-evidence source is available yet. This list remains fail-closed and empty.", - executionEvidenceUpstreamNotice: "Execution evidence loaded with {count} upstream notice(s); no platform capability or runtime authority is inferred.", - executionEvidenceEmpty: "No strategy × platform execution evidence is available to display.", + ownerDecisionRetire: "Stop tracking", + ownerDecisionConfirm: "Save this decision?", + ownerDecisionSubmitting: "Saving…", + ownerDecisionSuccess: "Decision saved.", + ownerDecisionFailed: "Could not save the decision", + reconciliationRecoveryBoard: "Recovery items", + reconciliationRecoveryLoginNotice: "Sign in to see recovery and review items.", + reconciliationRecoveryStaleNotice: "Recovery information is delayed. Wait for the next check.", + reconciliationRecoveryUnavailableNotice: "Recovery information is temporarily unavailable.", + reconciliationRecoveryUpstreamNotice: "Some recovery information is unavailable. Review the blockers first.", + reconciliationRecoveryEmpty: "There are no recovery items right now.", + reconciliationRecoveryMeta: "{platform} · {strategy}", + reconciliationRecoveryDetail: "status: {state} · samples: {samples} · review: {review} · updated: {lastObserved}", + reconciliationRecoveryBlocked: "Still needed: {blockers}", + reconciliationRecoveryReady: "The relevant checks are complete and await confirmation.", + reconciliationRecoveryAdminOnly: "An administrator needs to confirm this.", + reconciliationRecoveryConfirmed: "Recovery confirmation saved.", + reconciliationRecoveryConfirm: "Confirm recovery", + reconciliationRecoveryConfirmPrompt: "Save this recovery decision?", + reconciliationRecoverySubmitting: "Saving…", + reconciliationRecoverySuccess: "Recovery decision saved.", + reconciliationRecoveryFailed: "Could not save the recovery decision", + reconciliationRecoveryNoOrder: "View the current recovery status.", + executionEvidenceBoard: "Execution and fills", + executionEvidenceLoginNotice: "Sign in to see execution and fill records.", + executionEvidenceStaleNotice: "Execution records are delayed. Check the latest platform status as well.", + executionEvidenceUnavailableNotice: "Execution records are temporarily unavailable.", + executionEvidenceUpstreamNotice: "Some execution records are unavailable. Please retry later.", + executionEvidenceEmpty: "There are no execution records to display.", executionEvidenceMeta: "{platform} · current lane: {environment} · source: {source}", - executionEvidenceDetail: "strategy: {strategy} · data: {data} · execution: {execution} · shadow: {shadow} · paper: {paper}", + executionEvidenceDetail: "strategy: {strategy} · data: {data} · execution: {execution} · observe: {shadow} · simulated: {paper}", executionEvidenceReceipt: "execution receipt: {outcome} · broker confirmation: {confirmation}", executionEvidenceReceiptMissing: "execution receipt: not collected", executionEvidenceReceiptNotDue: "not in a due window", @@ -1260,19 +1242,19 @@ executionEvidenceConfirmationPartiallyFilled: "partially filled", executionEvidenceConfirmationFilled: "filled", executionEvidenceConfirmationReconciliation: "reconciliation required", - executionEvidenceNoOrder: "Fixed boundary: read-only evidence; no account, order, funds, or P6 live authority.", - executionEvidenceNext: "NEXT", - runtimeTargetLifecycleBoard: "View platform runtime status on demand", - runtimeTargetLifecycleLoginNotice: "Sign in to read private platform runtime status. This page cannot change target state or place orders.", - runtimeTargetLifecycleStaleNotice: "Platform runtime status is outside its freshness window. Check the monitoring path first.", - runtimeTargetLifecycleUnavailableNotice: "No usable platform runtime status is available. Missing data never implies a disabled or healthy target.", - runtimeTargetLifecycleUpstreamNotice: "Platform runtime status loaded with {count} upstream notice(s); review them first.", - runtimeTargetLifecycleEmpty: "No platform runtime status is available to display.", + executionEvidenceNoOrder: "View the current execution and fill results.", + executionEvidenceNext: "Recommended action", + runtimeTargetLifecycleBoard: "Platform status", + runtimeTargetLifecycleLoginNotice: "Sign in to see platform status.", + runtimeTargetLifecycleStaleNotice: "Platform status is delayed. Check runtime first.", + runtimeTargetLifecycleUnavailableNotice: "Platform status is temporarily unavailable.", + runtimeTargetLifecycleUpstreamNotice: "Some platform status is unavailable. Please review it first.", + runtimeTargetLifecycleEmpty: "There is no platform status to display.", runtimeTargetLifecycleMeta: "{platform} · {state} · lane: {mode}", runtimeTargetLifecycleDetail: "runtime guard: {guard} · execution heartbeat: {heartbeat}", runtimeTargetLifecycleObservation: "execution: {observation} · order/fill evidence: {evidence}", - runtimeTargetLifecycleNoOrder: "Fixed boundary: read-only status; it cannot enable a target, change a strategy, or place an order.", - runtimeTargetLifecycleNext: "CURRENT DISPOSITION", + runtimeTargetLifecycleNoOrder: "View the current platform runtime status.", + runtimeTargetLifecycleNext: "Current recommendation", runtimeTargetLifecycleStateEnabled: "enabled", runtimeTargetLifecycleStateDisabled: "disabled", runtimeTargetLifecycleCheckPass: "pass", @@ -1294,45 +1276,42 @@ runtimeTargetLifecycleReasonRuntimeGuard: "runtime guard needs review", runtimeTargetLifecycleReasonHeartbeat: "execution heartbeat needs review", runtimeTargetLifecycleReasonUnavailable: "monitoring data unavailable", - m0ResearchBoard: "View M0 research observations on demand", - m0ResearchLoginNotice: "Sign in to read the private M0 research ledger. A missing ledger never implies a research conclusion.", - m0ResearchStaleNotice: "These M0 research observations are beyond their validity window and remain historical context only.", - m0ResearchUnavailableNotice: "No usable M0 research ledger is available yet. This view remains fail-closed and empty.", - m0ResearchUpstreamNotice: "The M0 research ledger loaded with {count} upstream notice(s); no research conclusion is invented.", - m0ResearchEmpty: "No verified M0 research observation is available to display.", - m0ResearchMore: "Showing the first {count} research observations; the remainder stays in the read-only ledger.", + m0ResearchBoard: "Market research", + m0ResearchLoginNotice: "Sign in to see market research and observations.", + m0ResearchStaleNotice: "Research is delayed and is provided for historical context only.", + m0ResearchUnavailableNotice: "Market research is temporarily unavailable.", + m0ResearchUpstreamNotice: "Some research content is unavailable. Please retry later.", + m0ResearchEmpty: "There is no market research to display.", + m0ResearchMore: "Showing the first {count} research observations.", m0ResearchMeta: "{kind} · viewed {viewed}", m0ResearchStateFreshness: "State: {state} · freshness: {freshness}", m0ResearchHorizons: "Primary horizon: {primary} · suitable horizons: {suitable}", m0ResearchEvidence: "Confidence: {confidence} · style: {style} · source digest: {digest}", m0ResearchConsistency: "Current horizon conflict: {conflict} · historical stale drift: {drift}", - m0ResearchNoOrder: "Fixed boundary: research only, no order, and no runtime action.", - adaptiveSelectionBoard: "View M1 Shadow suggestions on demand", - adaptiveSelectionLoginNotice: "Sign in to read private Shadow suggestions. Missing sources never imply a strategy or order.", - adaptiveSelectionStaleNotice: "This Shadow suggestion is beyond its freshness window and remains historical context only.", - adaptiveSelectionUnavailableNotice: "No usable M1 Shadow suggestion is available yet. This view remains fail-closed and empty.", - adaptiveSelectionUpstreamNotice: "Shadow suggestions include {count} upstream notice(s); no strategy, platform, or order is inferred.", - adaptiveSelectionEmpty: "No Shadow suggestion is available to display.", + m0ResearchNoOrder: "View the current research summary.", + adaptiveSelectionBoard: "System suggestions", + adaptiveSelectionLoginNotice: "Sign in to see system suggestions.", + adaptiveSelectionStaleNotice: "Suggestions are delayed and are provided for historical context only.", + adaptiveSelectionUnavailableNotice: "System suggestions are temporarily unavailable.", + adaptiveSelectionUpstreamNotice: "Some suggestions are unavailable. Please retry later.", + adaptiveSelectionEmpty: "There are no system suggestions to display.", adaptiveSelectionMeta: "{source} · {domain} · market as of {asOf}", adaptiveSelectionRecommended: "Observe suggestion", - adaptiveSelectionNoCandidate: "No eligible Shadow candidate", + adaptiveSelectionNoCandidate: "No suitable suggestion yet", adaptiveSelectionReason: "Reasons: {reasons}", - adaptiveSelectionNoOrder: "Fixed boundary: human-readable only, zero proposed weight, no strategy, platform, funds, runtime, or order change.", - researchTaskBoard: "View research queue on demand", - researchTaskLoginNotice: "Sign in to read the private research task index. Missing source snapshots never imply a task.", - researchTaskStaleNotice: "The research task source is beyond its freshness window. It remains historical context, not a current instruction.", - researchTaskUnavailableNotice: "No research task source is available yet. The queue remains fail-closed and empty.", - researchTaskUpstreamNotice: "The research task index loaded with {count} upstream notices; no task is inferred from missing data.", - researchTaskEmpty: "No verified research task is available to display.", + adaptiveSelectionNoOrder: "View the system's current suggestions.", + researchTaskBoard: "Automation tasks", + researchTaskLoginNotice: "Sign in to see automation tasks.", + researchTaskStaleNotice: "Task information is delayed and is provided for historical context only.", + researchTaskUnavailableNotice: "Automation tasks are temporarily unavailable.", + researchTaskUpstreamNotice: "Some task information is unavailable. Please retry later.", + researchTaskEmpty: "There are no automation tasks to display.", researchTaskMeta: "{type} · {domain} · created {created}", - researchTaskLimits: "Offline research: up to {runs} run(s) / {seconds}s · P1 {p1} · P2 {p2} · P3 {p3}", - researchTaskNoOrder: "Fixed boundary: research only, zero size, no order; never P4/P5/P6.", - healthEyebrow: "Runtime guard / read only", - healthTitle: "Read the machine conclusion before deciding to intervene.", - healthSubtitle: "Health is not live approval. It only tells you whether to review, reduce, or keep watching.", - runtimeAuthorityTitle: "P0–P6 control-plane authority", - runtimeAuthorityNotice: "live/default_execution_mode/live_configured are legacy configuration metadata, not authorization for current runtime, orders, funds, or stage changes.", - runtimeAuthorityAsOf: "As of {asOf} · P1–P3 non-live data acquisition still needs its own precise contract; P4–P6 are undefined.", + researchTaskLimits: "Research budget: up to {runs} run(s) / {seconds}s", + researchTaskNoOrder: "View the current automation tasks.", + healthEyebrow: "System status", + healthTitle: "Runtime status", + healthSubtitle: "Highlights only the risks, delays, and recovery states that need attention.", healthTotal: "Strategies", healthHealthy: "Healthy", healthWatch: "Watch", @@ -1350,11 +1329,11 @@ login: "Sign in", logout: "Sign out", signedInAs: "Signed in as {login}", - planEyebrow: "Run plan / bounded change", - planTitle: "Set the boundary before submitting configuration.", - planSubtitle: "Prepare one auditable plan for one platform and account. This page submits configuration intent only; runtime, order, and live authority remain governed by independent evidence and lifecycle gates.", - planAdvancedSummary: "More risk boundaries (only when needed)", - planAdvancedHint: "Runtime, plugins, cash, overlays, and DCA normally retain the current verified setup. Expand only when changing one.", + planEyebrow: "Strategy settings", + planTitle: "Change strategy settings", + planSubtitle: "Choose a platform, account, strategy, and environment; review this change before saving.", + planAdvancedSummary: "Advanced settings", + planAdvancedHint: "Runtime, plugins, cash, overlays, and DCA normally retain their current setup. Expand only when changing one.", planScopeTitle: "Choose scope", planScopeSubtitle: "Platform, account, strategy, and target environment", planRuntimeTitle: "Runtime protection", @@ -1366,10 +1345,10 @@ account: "Target account", strategy: "Strategy", mode: "Target environment", - live: "Live (independent approval required)", + live: "Live", paper: "Legacy non-live", - dryRun: "Dry run (no orders)", - liveModeUnavailable: "No candidate currently meets the strategy-release, runtime-eligibility, and evidence gates for Live. Keep this plan non-live.", + dryRun: "Simulated run", + liveModeUnavailable: "This strategy is not ready for Live. Choose a non-live environment.", runtimeTargetMode: "Account status", runtimeSectionTitle: "Runtime and plugins", runtimeTargetCurrent: "Keep current status", @@ -1452,37 +1431,31 @@ planReadinessTitle: "Before submission", planCheckAccount: "Account and scope", planCheckStrategy: "Strategy and environment", - planCheckRisk: "Cash and risk boundary", - planCheckAuthority: "Live execution authority", + planCheckRisk: "Cash and risk settings", + planCheckAuthority: "Live status", planCheckWaiting: "Waiting", planCheckSelected: "Read", planCheckValid: "Checked", planCheckFix: "Needs correction", planCheckNonLive: "Non-live", - planCheckNoAuthority: "Not granted", - planAuditNote: "A submission creates an audit record; it is not an order, funding, or live permission.", + planCheckNoAuthority: "Not ready", + planAuditNote: "This change is saved in the change history.", copySummary: "Copy state", loginToRun: "Sign in to submit a plan", loadingConfig: "Loading config", configureAccounts: "Configure accounts", - runSwitch: "Submit bounded change", + runSwitch: "Save settings", noChanges: "No changes", - readonlyNote: "Sign in to submit a bounded configuration.", + readonlyNote: "Sign in to save settings.", publicReadonly: "Sign in to view account config.", loadingConfigNote: "Reading account config and current state.", missingConfigNote: "Account config is not loaded, so switching is disabled.", - readyNote: "This submits a bounded configuration change; it grants no order, funding, or live authority.", + readyNote: "Review the changes above, then save.", invalidStrategyNote: "This account has no runnable strategy, so switching is disabled.", invalidReservePolicyNote: "Enter a valid amount or ratio for the selected reserved-cash policy.", invalidIncomeLayerNote: "Enter a valid income layer start amount and max ratio.", invalidOptionOverlayNote: "This strategy does not define an option layer to enable.", invalidDcaNote: "Enter a valid DCA mode and base amount.", - publicOauthTitle: "Protected by GitHub OAuth", - publicOauthText: "Only allowlisted accounts can open private config.", - publicWorkerTitle: "Worker-side dispatch", - publicWorkerText: "Tokens stay server-side; the browser submits intent only.", - publicAuditTitle: "Traceable changes", - publicAuditText: "Bounded configuration changes run through GitHub Actions for audit and rollback.", noAccount: "No accounts", noStrategy: "No supported strategies", repository: "Repository", @@ -3310,13 +3283,6 @@ el("lang-button").textContent = state.lang === "zh" ? "EN" : "中"; } - function renderRuntimeAuthorityStatus() { - const status = String(runtimeAuthorityStatus.status || "UNKNOWN_RUNTIME_AUTHORITY_STATUS"); - const asOf = String(runtimeAuthorityStatus.status_as_of || "—"); - el("runtime-authority-status").textContent = status; - el("runtime-authority-as-of").textContent = t("runtimeAuthorityAsOf").replace("{asOf}", asOf); - } - function renderPlatforms() { const strip = el("platform-strip"); strip.replaceChildren(); @@ -3384,7 +3350,6 @@ el("quick-form").hidden = !showPrivateControls; el("run-area").hidden = !showPrivateControls; el("public-note").hidden = showPrivateControls; - el("public-preview").hidden = showPrivateControls; el("public-note").textContent = state.auth.allowed ? t("missingConfigNote") : t("publicReadonly"); if (!showPrivateControls) { @@ -3813,6 +3778,44 @@ }[decision] || String(decision || "—"); } + const operatorLabels = { + kind: { + individual: { zh: "策略", en: "Strategy" }, + portfolio: { zh: "组合", en: "Portfolio" }, + plugin: { zh: "插件", en: "Plugin" }, + }, + status: { + research: { zh: "观察中", en: "Monitoring" }, + evidence_pending: { zh: "待复核", en: "To review" }, + verified: { zh: "正常", en: "Normal" }, + deferred: { zh: "待复核", en: "To review" }, + parked: { zh: "已暂停", en: "Paused" }, + paper: { zh: "测试中", en: "Testing" }, + shadow: { zh: "观察中", en: "Monitoring" }, + owner_decision_required: { zh: "等待处理", en: "Needs attention" }, + }, + action: { + keep_research: { zh: "继续观察", en: "Keep monitoring" }, + defer: { zh: "稍后复核", en: "Review later" }, + park: { zh: "保持暂停", en: "Keep paused" }, + auto_paper_evaluation: { zh: "继续测试", en: "Continue testing" }, + auto_shadow_evaluation: { zh: "继续观察", en: "Keep monitoring" }, + owner_live_decision: { zh: "需要确认", en: "Confirmation needed" }, + none: { zh: "暂无操作", en: "No action" }, + }, + freshness: { + fresh: { zh: "最新", en: "Current" }, + stale: { zh: "更新延迟", en: "Delayed" }, + unavailable: { zh: "暂不可用", en: "Unavailable" }, + unknown: { zh: "暂不可用", en: "Unavailable" }, + }, + }; + + function operatorLabel(group, value) { + const label = operatorLabels[group]?.[value]; + return label ? (state.lang === "zh" ? label.zh : label.en) : "—"; + } + function controlPlaneDataStatusText(status) { return status === "ready" ? t("controlDataReady") @@ -3853,7 +3856,7 @@ } else if (payload.errors?.length) { notice.textContent = t("controlUpstreamNotice").replace("{count}", payload.errors.length); } else { - notice.textContent = payload.policy?.notice || "live 仍需所有者明确决定。"; + notice.textContent = t("controlNormalNotice"); } const list = el("control-plane-list"); @@ -3872,7 +3875,7 @@ main.className = "health-card__main"; const meta = document.createElement("div"); meta.className = "health-card__meta"; - meta.textContent = `${item.candidate_kind || "candidate"} · ${domainLabel(item.domain || "")}`; + meta.textContent = `${operatorLabel("kind", item.candidate_kind)} · ${domainLabel(item.domain || "")}`; const title = document.createElement("h4"); title.className = "health-card__title"; title.textContent = String(item.candidate_id || "unknown"); @@ -3881,19 +3884,19 @@ reason.textContent = item.recommendation?.reason || t("controlNoRecommendation"); const detail = document.createElement("div"); detail.className = "health-card__meta"; - detail.textContent = t("controlStageMeta") - .replace("{stage}", item.lifecycle?.stage || "—") - .replace("{status}", item.lifecycle?.status || "unknown") - .replace("{freshness}", item.freshness?.status || "unknown"); + detail.textContent = t("controlItemMeta") + .replace("{kind}", operatorLabel("status", item.lifecycle?.status)) + .replace("{domain}", domainLabel(item.domain || "")) + .replace("{freshness}", operatorLabel("freshness", item.freshness?.status || "unknown")); main.append(meta, title, reason, detail); const stateBlock = document.createElement("div"); stateBlock.className = "health-card__score"; const label = document.createElement("small"); label.textContent = t("controlNext"); const stage = document.createElement("strong"); - stage.textContent = item.lifecycle?.stage || "—"; + stage.textContent = operatorLabel("action", item.recommendation?.code || "none"); const recommendation = document.createElement("small"); - recommendation.textContent = item.recommendation?.code || "none"; + recommendation.textContent = `${t("controlStatus")}:${operatorLabel("status", item.lifecycle?.status)}`; stateBlock.append(label, stage, recommendation); const ownerEntry = ownerDecisionEntry(item.candidate_id); if (ownerEntry) { @@ -4222,7 +4225,7 @@ const status = document.createElement("div"); status.className = "health-card__score"; const label = document.createElement("small"); - label.textContent = "M0"; + label.textContent = state.lang === "zh" ? "研究" : "Research"; const freshness = document.createElement("strong"); freshness.textContent = m0ResearchLabel("freshness", observation.freshness?.status); const stateLabel = document.createElement("small"); @@ -4305,7 +4308,7 @@ const status = document.createElement("div"); status.className = "health-card__score"; const label = document.createElement("small"); - label.textContent = "NO ORDER"; + label.textContent = state.lang === "zh" ? "研究次数" : "Research runs"; const score = document.createElement("strong"); score.textContent = typeof recommended?.score === "number" ? recommended.score.toFixed(3) : "—"; const freshness = document.createElement("small"); @@ -4682,12 +4685,12 @@ const payload = state.health.payload; const summary = payload.summary || {}; const statusText = payload.data_status === "ready" - ? "快照已加载" - : (payload.data_status === "stale" ? "快照已过期" : "等待可用快照"); + ? "已更新" + : (payload.data_status === "stale" ? "更新延迟" : "暂时无法读取"); el("health-status").textContent = statusText; el("health-computed-at").textContent = payload.computed_at - ? `最近计算:${new Date(payload.computed_at).toLocaleString()}` - : "最近计算:—"; + ? `更新于:${new Date(payload.computed_at).toLocaleString()}` + : "更新于:—"; el("health-count-total").textContent = String(Number(summary.strategy_count) || 0); el("health-count-healthy").textContent = String(Number(summary.healthy) || 0); el("health-count-watch").textContent = String(Number(summary.watch) || 0); @@ -4696,15 +4699,15 @@ const notice = el("health-notice"); if (!state.auth.allowed) { - notice.textContent = "登录后读取私有策略健康快照;没有快照时不会展示虚构指标。"; + notice.textContent = "登录后查看策略状态。"; } else if (payload.data_status === "stale") { - notice.textContent = "健康快照已超过允许的新鲜度窗口;页面保留原始状态,但不会把它当作当前健康结论。"; + notice.textContent = "状态更新延迟,请稍后刷新。"; } else if (payload.data_status !== "ready") { - notice.textContent = "还没有可用的策略健康快照;当前页面保持 fail-closed 空状态。"; + notice.textContent = "暂时无法读取策略状态,请稍后刷新。"; } else if (payload.errors?.length) { - notice.textContent = `快照已加载,但有 ${payload.errors.length} 个上游提示;缺失数据不会被替换成虚构指标。`; + notice.textContent = "部分策略状态暂不可用,请稍后重试。"; } else { - notice.textContent = "健康不等于已批准 live;正常实盘、资金和杠杆变更仍需人工确认。"; + notice.textContent = "当前策略状态已更新。"; } const list = el("health-list"); @@ -4713,7 +4716,7 @@ if (!strategies.length) { const empty = document.createElement("div"); empty.className = "health-card__empty"; - empty.textContent = "暂无可展示的策略健康快照。"; + empty.textContent = "当前没有可展示的策略状态。"; list.appendChild(empty); return; } @@ -4765,7 +4768,6 @@ function render() { applyLanguage(); - renderRuntimeAuthorityStatus(); renderConsoleView(); renderControlPlane(); renderM0Research(); diff --git a/web/strategy-switch-console/app_css.js b/web/strategy-switch-console/app_css.js index 024ea0c..6b7db34 100644 --- a/web/strategy-switch-console/app_css.js +++ b/web/strategy-switch-console/app_css.js @@ -1,2 +1,2 @@ // Generated — CSS asset -export const APP_CSS = ":root {\n --bg: #f5f6f8;\n --surface: #ffffff;\n --soft: #eef1f4;\n --ink: #16191f;\n --muted: #66707c;\n --line: #dce1e7;\n --line-strong: #b8c2cc;\n --accent: #136f63;\n --accent-ink: #ffffff;\n --danger: #b54137;\n --lb: #0f766e;\n --ib: #2f5f9f;\n --sw: #9b4153;\n --ft: #94651f;\n --qmt: #b45309;\n --bn: #f0b90b;\n font-family:\n Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,\n \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif;\n }\n\n * {\n box-sizing: border-box;\n }\n\n html {\n min-height: 100%;\n background: var(--bg);\n }\n\n body {\n margin: 0;\n min-height: 100svh;\n background:\n linear-gradient(180deg, #fbfcfd 0, #eef2f5 100%);\n color: var(--ink);\n letter-spacing: 0;\n }\n\n button,\n select,\n input {\n font: inherit;\n letter-spacing: 0;\n }\n\n button {\n cursor: pointer;\n }\n\n a {\n color: inherit;\n }\n\n button:focus-visible,\n select:focus-visible,\n input:focus-visible,\n a:focus-visible {\n outline: 3px solid color-mix(in srgb, var(--platform-color, var(--accent)) 24%, transparent);\n outline-offset: 2px;\n }\n\n [hidden] {\n display: none !important;\n }\n\n h1,\n h2,\n h3,\n p {\n margin: 0;\n }\n\n .topbar {\n min-height: 72px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n padding: 17px 28px;\n border-bottom: 1px solid var(--line);\n background: rgba(250, 251, 252, 0.88);\n backdrop-filter: blur(14px);\n position: sticky;\n top: 0;\n z-index: 20;\n }\n\n .brand {\n display: grid;\n gap: 3px;\n min-width: 0;\n }\n\n h1 {\n font-size: 22px;\n line-height: 1.12;\n font-weight: 780;\n overflow-wrap: anywhere;\n }\n\n .brand p {\n color: var(--muted);\n font-size: 13px;\n line-height: 1.4;\n }\n\n .top-actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n flex-wrap: wrap;\n }\n\n .auth-status {\n max-width: 260px;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .shell {\n width: min(1160px, calc(100vw - 36px));\n margin: 0 auto;\n padding: 22px 0 32px;\n display: grid;\n gap: 16px;\n }\n\n .console-nav {\n display: flex;\n gap: 8px;\n padding: 4px;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.7);\n }\n\n .runtime-authority-notice {\n display: grid;\n grid-template-columns: minmax(210px, 0.42fr) minmax(0, 0.9fr) minmax(0, 1fr);\n gap: 12px 18px;\n align-items: center;\n margin-bottom: 14px;\n padding: 14px 16px;\n border: 1px solid rgba(186, 146, 59, 0.34);\n border-radius: var(--radius-md, 12px);\n background: linear-gradient(90deg, rgba(255, 248, 223, 0.92), rgba(255, 255, 255, 0.9));\n color: #5d4617;\n }\n\n .runtime-authority-notice > div {\n display: grid;\n gap: 4px;\n }\n\n .runtime-authority-notice strong {\n color: #8a5a00;\n font-size: 13px;\n overflow-wrap: anywhere;\n }\n\n .runtime-authority-notice p {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n }\n\n .console-nav__button {\n min-height: 38px;\n padding: 0 15px;\n border: 0;\n border-radius: 7px;\n background: transparent;\n color: var(--muted);\n font-size: 13px;\n font-weight: 780;\n }\n\n .console-nav__button.active {\n background: var(--ink);\n color: #ffffff;\n }\n\n .health-view {\n display: grid;\n gap: 18px;\n }\n\n .lifecycle-view {\n display: grid;\n gap: 18px;\n }\n\n .lifecycle-table-wrap {\n overflow-x: auto;\n border: 1px solid var(--line);\n border-radius: 8px;\n background: var(--surface);\n box-shadow: 0 10px 26px rgba(22, 25, 31, 0.05);\n }\n\n .lifecycle-table {\n width: 100%;\n min-width: 980px;\n border-collapse: collapse;\n font-size: 12px;\n }\n\n .lifecycle-table th,\n .lifecycle-table td {\n padding: 12px 10px;\n border-bottom: 1px solid var(--line);\n text-align: left;\n vertical-align: top;\n white-space: nowrap;\n }\n\n .lifecycle-table thead th { color: var(--muted); font-size: 11px; font-weight: 760; background: var(--soft); }\n .lifecycle-table tbody th { max-width: 220px; white-space: normal; font-weight: 720; }\n .lifecycle-table tbody tr:last-child th,\n .lifecycle-table tbody tr:last-child td { border-bottom: 0; }\n .lifecycle-parked, .lifecycle-deferred { color: var(--danger); }\n .lifecycle-not_started { color: var(--muted); }\n\n .health-head,\n .health-board-head {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: 20px;\n }\n\n .health-head {\n padding: 22px 2px 4px;\n }\n\n .health-head h2 {\n margin-top: 7px;\n font-size: clamp(28px, 5vw, 48px);\n line-height: 1.05;\n letter-spacing: -0.04em;\n }\n\n .health-head p {\n max-width: 620px;\n margin-top: 12px;\n color: var(--muted);\n font-size: 14px;\n line-height: 1.6;\n }\n\n .health-meta {\n display: grid;\n justify-items: end;\n gap: 8px;\n color: var(--muted);\n font-size: 12px;\n white-space: nowrap;\n }\n\n .health-notice {\n padding: 13px 15px;\n border: 1px solid rgba(180, 83, 9, 0.22);\n border-radius: 8px;\n background: #fffaf2;\n color: #87520d;\n font-size: 12px;\n line-height: 1.5;\n }\n\n .health-summary {\n display: grid;\n grid-template-columns: repeat(5, minmax(0, 1fr));\n gap: 10px;\n }\n\n .health-summary article,\n .health-card {\n border: 1px solid var(--line);\n border-radius: 8px;\n background: var(--surface);\n box-shadow: 0 10px 26px rgba(22, 25, 31, 0.05);\n }\n\n .health-summary article {\n min-height: 104px;\n padding: 17px;\n }\n\n .health-summary span,\n .health-card__meta,\n .health-card__score small {\n color: var(--muted);\n font-size: 11px;\n }\n\n .health-summary strong {\n display: block;\n margin-top: 12px;\n color: var(--accent);\n font-size: 28px;\n }\n\n .health-board-head {\n margin-top: 14px;\n }\n\n .health-board-head h3 {\n font-size: 21px;\n }\n\n .health-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n\n .health-filter {\n padding: 7px 10px;\n border: 1px solid var(--line);\n border-radius: 999px;\n background: var(--surface);\n color: var(--muted);\n font-size: 11px;\n }\n\n .health-filter.active {\n border-color: var(--accent);\n color: var(--accent);\n background: #eff9f6;\n }\n\n .health-list {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n }\n\n .health-card {\n display: flex;\n justify-content: space-between;\n gap: 18px;\n min-width: 0;\n padding: 18px;\n }\n\n .health-card__main {\n min-width: 0;\n }\n\n .health-card__title {\n margin: 10px 0 7px;\n font-size: 16px;\n overflow-wrap: anywhere;\n }\n\n .health-card__reason {\n margin: 0;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.55;\n }\n\n .health-card__meta {\n margin-top: 13px;\n line-height: 1.5;\n }\n\n .health-card__score {\n flex: 0 0 72px;\n padding-left: 14px;\n border-left: 1px solid var(--line);\n text-align: right;\n }\n\n .health-card__score strong {\n display: block;\n margin: 7px 0;\n color: var(--accent);\n font-size: 27px;\n }\n\n .owner-decision {\n display: grid;\n gap: 7px;\n margin-top: 14px;\n padding-top: 13px;\n border-top: 1px solid var(--line);\n }\n\n .owner-decision strong {\n font-size: 12px;\n }\n\n .owner-decision small {\n color: var(--muted);\n font-size: 11px;\n line-height: 1.5;\n }\n\n .owner-decision__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 7px;\n }\n\n .owner-decision__button {\n padding: 7px 9px;\n border: 1px solid var(--line-strong);\n border-radius: 7px;\n background: var(--surface);\n color: var(--ink);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n }\n\n .owner-decision__button:hover:not(:disabled) {\n border-color: var(--accent);\n color: var(--accent);\n }\n\n .owner-decision__button:disabled {\n cursor: wait;\n opacity: 0.62;\n }\n\n .health-card__empty {\n grid-column: 1 / -1;\n padding: 30px 18px;\n border: 1px dashed var(--line-strong);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.58);\n color: var(--muted);\n font-size: 13px;\n line-height: 1.6;\n text-align: center;\n }\n\n body.app-loading .shell {\n display: none;\n }\n\n body:not(.app-loading) .boot-screen {\n display: none;\n }\n\n .boot-screen {\n width: min(680px, calc(100vw - 36px));\n min-height: calc(100svh - 72px);\n margin: 0 auto;\n display: grid;\n align-items: center;\n padding: 34px 0;\n }\n\n .boot-panel {\n display: grid;\n gap: 13px;\n padding: 8px 0;\n }\n\n .boot-kicker {\n color: var(--accent);\n font-size: 12px;\n line-height: 1.2;\n font-weight: 800;\n text-transform: uppercase;\n }\n\n .boot-panel h2 {\n color: var(--ink);\n font-size: clamp(28px, 5vw, 44px);\n line-height: 1.04;\n font-weight: 820;\n }\n\n .boot-panel p {\n color: var(--muted);\n font-size: 14px;\n line-height: 1.5;\n max-width: 520px;\n }\n\n .loading-track {\n width: 100%;\n height: 5px;\n overflow: hidden;\n border-radius: 999px;\n background: #dde4ea;\n }\n\n .loading-track::before {\n content: \"\";\n display: block;\n width: 42%;\n height: 100%;\n border-radius: inherit;\n background: var(--accent);\n animation: loading-sweep 1.05s ease-in-out infinite;\n }\n\n .pill,\n .btn {\n min-height: 36px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 7px;\n border: 1px solid var(--line);\n background: var(--surface);\n color: var(--muted);\n text-decoration: none;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 740;\n box-shadow: 0 1px 0 rgba(22, 25, 31, 0.03);\n }\n\n .pill {\n padding: 0 12px;\n border-radius: 999px;\n }\n\n .pill.ready {\n border-color: rgba(19, 111, 99, 0.28);\n background: #eff9f6;\n color: var(--accent);\n }\n\n .btn {\n padding: 0 13px;\n border-radius: 8px;\n transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;\n }\n\n .btn:hover {\n transform: translateY(-1px);\n border-color: var(--line-strong);\n background: #fafbfc;\n }\n\n .lang-toggle {\n min-width: 42px;\n padding: 0 10px;\n }\n\n .mode button {\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--muted);\n font-weight: 760;\n transition: background 150ms ease, color 150ms ease;\n }\n\n .mode button.active {\n background: #ffffff;\n color: var(--ink);\n box-shadow: 0 1px 7px rgba(22, 25, 31, 0.08);\n }\n\n .platform-strip {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n }\n\n .platform-button {\n min-width: 0;\n min-height: 52px;\n display: grid;\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border: 1px solid var(--line);\n border-left: 4px solid var(--platform-color);\n border-radius: 8px;\n background: var(--surface);\n color: var(--ink);\n text-align: left;\n transition:\n transform 150ms ease,\n border-color 150ms ease,\n background 150ms ease,\n box-shadow 150ms ease;\n }\n\n .platform-button:hover {\n transform: translateY(-1px);\n border-color: var(--line-strong);\n }\n\n .platform-button.active {\n border-color: var(--platform-color);\n background:\n linear-gradient(180deg, color-mix(in srgb, var(--platform-color) 7%, white), #ffffff 68%);\n box-shadow: 0 14px 30px rgba(22, 25, 31, 0.09);\n }\n\n .platform-button[data-platform=\"longbridge\"] {\n --platform-color: var(--lb);\n }\n\n .platform-button[data-platform=\"ibkr\"] {\n --platform-color: var(--ib);\n }\n\n .platform-button[data-platform=\"schwab\"] {\n --platform-color: var(--sw);\n }\n\n .platform-button[data-platform=\"firstrade\"] {\n --platform-color: var(--ft);\n }\n\n .platform-button[data-platform=\"qmt\"] {\n --platform-color: var(--qmt);\n }\n\n .platform-button[data-platform=\"binance\"] {\n --platform-color: var(--bn);\n }\n\n .execution-cash-policy-section {\n grid-template-columns: 1fr;\n }\n\n .execution-cash-policy-heading {\n grid-column: 1 / -1;\n margin: 0;\n font-size: 13px;\n font-weight: 780;\n color: var(--ink);\n }\n\n .execution-cash-policy-note {\n grid-column: 1 / -1;\n margin: 0;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.45;\n }\n\n .execution-cash-policy-grid {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 14px;\n align-items: start;\n }\n\n .policy-block-muted .selection-meta {\n color: var(--danger);\n }\n\n @media (max-width: 720px) {\n .health-head,\n .health-board-head {\n align-items: flex-start;\n flex-direction: column;\n }\n\n .health-meta {\n justify-items: start;\n }\n\n .health-summary {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .health-list {\n grid-template-columns: 1fr;\n }\n\n .execution-cash-policy-grid {\n grid-template-columns: 1fr;\n }\n }\n\n .mark {\n width: 32px;\n height: 32px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--platform-color) 12%, white);\n color: var(--platform-color);\n font-size: 11px;\n font-weight: 840;\n transition: background 150ms ease, color 150ms ease;\n }\n\n .platform-button.active .mark {\n background: var(--platform-color);\n color: #ffffff;\n }\n\n .platform-copy {\n min-width: 0;\n display: grid;\n gap: 3px;\n }\n\n .platform-copy strong {\n font-size: 13px;\n line-height: 1.18;\n overflow-wrap: anywhere;\n }\n\n .platform-strip .platform-copy span,\n .platform-strip .platform-copy small {\n display: none;\n }\n\n .platform-copy span {\n color: var(--muted);\n font-size: 12px;\n line-height: 1.35;\n overflow-wrap: anywhere;\n }\n\n .platform-copy small {\n color: var(--platform-color);\n font-size: 11px;\n line-height: 1.25;\n font-weight: 780;\n overflow-wrap: anywhere;\n }\n\n .switch-surface {\n display: grid;\n grid-template-columns: minmax(0, 1.04fr) minmax(320px, 0.96fr);\n gap: 18px;\n align-items: stretch;\n }\n\n .switch-surface.summary-hidden {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .switch-panel,\n .summary-panel {\n min-width: 0;\n border: 1px solid var(--line);\n border-radius: 8px;\n background: var(--surface);\n box-shadow: 0 14px 34px rgba(22, 25, 31, 0.07);\n }\n\n .switch-panel {\n display: grid;\n gap: 20px;\n padding: 22px;\n }\n\n .panel-head {\n display: grid;\n gap: 5px;\n }\n\n .eyebrow {\n color: var(--muted);\n font-size: 12px;\n line-height: 1.2;\n font-weight: 780;\n text-transform: uppercase;\n }\n\n .panel-head h2 {\n font-size: clamp(24px, 3vw, 34px);\n line-height: 1.08;\n font-weight: 820;\n overflow-wrap: anywhere;\n }\n\n .quick-form {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n align-items: start;\n gap: 16px 14px;\n }\n\n .control-block {\n display: grid;\n grid-template-rows: auto 48px minmax(20px, auto);\n align-content: start;\n gap: 8px;\n min-width: 0;\n color: var(--muted);\n font-size: 12px;\n font-weight: 760;\n }\n\n .form-section {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n align-items: start;\n gap: 14px;\n }\n\n .form-section + .form-section {\n padding-top: 16px;\n border-top: 1px solid var(--line);\n }\n\n .section-wide {\n grid-column: 1 / -1;\n }\n\n .mode-block {\n grid-column: 1 / -1;\n }\n\n select,\n input {\n width: 100%;\n min-width: 0;\n height: 48px;\n padding: 0 13px;\n border: 1px solid var(--line);\n border-radius: 8px;\n background: #ffffff;\n color: var(--ink);\n outline: none;\n font-size: 14px;\n font-weight: 680;\n overflow: hidden;\n padding-right: 42px;\n text-overflow: ellipsis;\n transition: border-color 150ms ease, box-shadow 150ms ease;\n }\n\n select:focus,\n input:focus {\n border-color: var(--platform-color, var(--accent));\n box-shadow: 0 0 0 3px rgba(19, 111, 99, 0.12);\n }\n\n input::placeholder {\n color: #9aa3ad;\n opacity: 1;\n }\n\n .selection-meta {\n min-height: 20px;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.45;\n white-space: pre-line;\n overflow-wrap: anywhere;\n }\n\n .mode {\n height: 48px;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 4px;\n padding: 4px;\n border: 1px solid var(--line);\n border-radius: 8px;\n background: var(--soft);\n }\n\n .mode button {\n font-size: 13px;\n }\n\n .run-area {\n display: grid;\n gap: 10px;\n padding-top: 2px;\n }\n\n .public-note {\n color: var(--muted);\n font-size: 14px;\n line-height: 1.55;\n max-width: 560px;\n }\n\n .run-button {\n min-height: 56px;\n border: 0;\n border-radius: 8px;\n background: var(--platform-color, var(--accent));\n color: var(--accent-ink);\n font-size: 16px;\n font-weight: 820;\n transition: transform 150ms ease, filter 150ms ease, opacity 150ms ease;\n }\n\n .run-button:hover:not(:disabled) {\n transform: translateY(-1px);\n filter: brightness(0.92);\n }\n\n .run-button:disabled {\n opacity: 0.48;\n cursor: not-allowed;\n transform: none;\n }\n\n .action-note {\n min-height: 20px;\n color: var(--muted);\n font-size: 13px;\n line-height: 1.45;\n white-space: pre-line;\n }\n\n .action-note.warning {\n color: var(--danger);\n font-weight: 720;\n }\n\n .summary-panel {\n display: grid;\n grid-template-rows: auto 1fr auto;\n overflow: hidden;\n }\n\n .summary-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 16px;\n border-bottom: 1px solid var(--line);\n }\n\n .summary-head h2 {\n font-size: 15px;\n line-height: 1.2;\n font-weight: 800;\n }\n\n .summary-list {\n margin: 0;\n padding: 8px 16px;\n display: grid;\n gap: 0;\n }\n\n .summary-row {\n display: grid;\n grid-template-columns: 128px minmax(0, 1fr);\n gap: 12px;\n padding: 10px 0;\n border-bottom: 1px solid #eef1f4;\n align-items: start;\n }\n\n .summary-row:last-child {\n border-bottom: 0;\n }\n\n .summary-row.pending {\n margin: 0 -16px;\n padding-inline: 13px 16px;\n border-left: 3px solid var(--platform-color, var(--accent));\n background: color-mix(in srgb, var(--platform-color, var(--accent)) 6%, #ffffff);\n }\n\n .summary-label {\n color: var(--muted);\n font-size: 12px;\n line-height: 1.4;\n font-weight: 760;\n }\n\n .summary-value {\n margin: 0;\n min-width: 0;\n color: var(--ink);\n font-size: 13px;\n line-height: 1.45;\n font-weight: 680;\n overflow-wrap: anywhere;\n }\n\n .summary-row.pending .summary-value {\n color: var(--platform-color, var(--accent));\n font-weight: 800;\n }\n\n .summary-status {\n min-height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0 9px;\n border: 1px solid rgba(19, 111, 99, 0.28);\n border-radius: 999px;\n background: #eff9f6;\n color: var(--accent);\n font-size: 12px;\n line-height: 1.2;\n font-weight: 800;\n white-space: nowrap;\n }\n\n .summary-status.disabled {\n border-color: rgba(181, 65, 55, 0.32);\n background: #fff1ef;\n color: var(--danger);\n }\n\n .summary-status.neutral {\n border-color: var(--line);\n background: #f6f7f8;\n color: var(--muted);\n }\n\n .summary-row.pending .summary-status {\n font-weight: 840;\n }\n\n .summary-actions {\n display: flex;\n gap: 10px;\n flex-wrap: wrap;\n padding: 15px 16px;\n border-top: 1px solid var(--line);\n background: #fbfcfd;\n }\n\n .toast {\n min-height: 20px;\n color: var(--accent);\n font-size: 13px;\n line-height: 1.45;\n }\n\n .quiet {\n color: var(--muted);\n }\n\n /* Modern visual polish layer: keeps the existing markup and behavior intact. */\n :root {\n --bg: #f4f8fb;\n --surface: rgba(255, 255, 255, 0.94);\n --soft: #f1f5f8;\n --ink: #101725;\n --muted: #617083;\n --line: #d9e2ea;\n --line-strong: #b8c7d4;\n --accent: #08796f;\n --accent-ink: #ffffff;\n --danger: #b54137;\n --radius-lg: 22px;\n --radius-md: 16px;\n --shadow-soft: 0 18px 48px rgba(16, 23, 37, 0.08);\n --shadow-panel: 0 22px 70px rgba(25, 45, 68, 0.11);\n }\n\n body {\n position: relative;\n overflow-x: hidden;\n background:\n radial-gradient(circle at 14% 0%, rgba(8, 121, 111, 0.12), transparent 34%),\n radial-gradient(circle at 86% 4%, rgba(47, 95, 159, 0.11), transparent 32%),\n linear-gradient(180deg, #fbfdff 0%, var(--bg) 55%, #eef4f8 100%);\n }\n\n body::before {\n content: \"\";\n position: fixed;\n inset: 0;\n z-index: -1;\n pointer-events: none;\n opacity: 0.42;\n background-image:\n linear-gradient(rgba(8, 121, 111, 0.07) 1px, transparent 1px),\n linear-gradient(90deg, rgba(47, 95, 159, 0.055) 1px, transparent 1px),\n linear-gradient(135deg, transparent 0 44%, rgba(8, 121, 111, 0.08) 45% 46%, transparent 47% 100%);\n background-size: 72px 72px, 72px 72px, 340px 190px;\n -webkit-mask-image: linear-gradient(180deg, #000 0, rgba(0, 0, 0, 0.72) 260px, transparent 680px);\n mask-image: linear-gradient(180deg, #000 0, rgba(0, 0, 0, 0.72) 260px, transparent 680px);\n }\n\n .topbar {\n width: min(1400px, calc(100vw - 28px));\n min-height: 88px;\n margin: 10px auto 0;\n padding: 18px 22px;\n top: 10px;\n border: 1px solid rgba(210, 222, 232, 0.9);\n border-radius: var(--radius-lg);\n background: rgba(255, 255, 255, 0.82);\n box-shadow: 0 14px 42px rgba(22, 35, 53, 0.08);\n backdrop-filter: blur(22px);\n }\n\n .brand {\n grid-template-columns: 46px minmax(0, 1fr);\n column-gap: 12px;\n align-items: center;\n }\n\n .brand::before {\n content: \"↗\";\n grid-row: 1 / 3;\n width: 44px;\n height: 44px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n background:\n radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.88), transparent 28%),\n linear-gradient(135deg, #0b8a80, #075f62 72%);\n color: #ffffff;\n font-size: 22px;\n font-weight: 860;\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25), 0 10px 24px rgba(8, 121, 111, 0.22);\n }\n\n h1 {\n font-size: clamp(24px, 2vw, 30px);\n letter-spacing: -0.03em;\n }\n\n .brand p {\n color: #66758a;\n font-size: 13px;\n }\n\n .btn,\n .pill {\n min-height: 40px;\n border-color: rgba(203, 215, 226, 0.9);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.82);\n color: #45566b;\n box-shadow: 0 1px 0 rgba(255, 255, 255, 0.75), 0 8px 20px rgba(22, 35, 53, 0.05);\n }\n\n .btn:hover {\n border-color: rgba(145, 163, 181, 0.86);\n background: #ffffff;\n box-shadow: 0 12px 24px rgba(22, 35, 53, 0.08);\n }\n\n .shell {\n width: min(1400px, calc(100vw - 36px));\n padding: 12px 0 42px;\n gap: 12px;\n }\n\n .platform-strip {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n padding: 10px 12px;\n border: 1px solid rgba(210, 222, 232, 0.85);\n border-radius: var(--radius-lg);\n background: rgba(255, 255, 255, 0.74);\n box-shadow: var(--shadow-soft);\n backdrop-filter: blur(16px);\n }\n\n .platform-button {\n position: relative;\n min-height: 52px;\n gap: 8px;\n padding: 8px 28px 8px 8px;\n border: 1px solid rgba(207, 218, 229, 0.9);\n border-left: 1px solid rgba(207, 218, 229, 0.9);\n border-radius: var(--radius-md);\n background:\n linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(250, 252, 254, 0.9));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85);\n }\n\n .platform-button:hover {\n transform: translateY(-2px);\n border-color: color-mix(in srgb, var(--platform-color) 34%, var(--line));\n box-shadow: 0 14px 28px rgba(22, 35, 53, 0.08);\n }\n\n .platform-button.active {\n border-color: color-mix(in srgb, var(--platform-color) 58%, var(--line));\n background:\n radial-gradient(circle at 12% 18%, color-mix(in srgb, var(--platform-color) 16%, transparent), transparent 42%),\n linear-gradient(180deg, #ffffff, color-mix(in srgb, var(--platform-color) 5%, #ffffff));\n box-shadow: 0 16px 34px color-mix(in srgb, var(--platform-color) 18%, transparent);\n }\n\n .platform-button.active::after {\n content: \"✓\";\n position: absolute;\n top: 50%;\n right: 8px;\n width: 18px;\n height: 18px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n background: var(--platform-color);\n color: #ffffff;\n font-size: 11px;\n font-weight: 880;\n transform: translateY(-50%);\n box-shadow: 0 8px 18px color-mix(in srgb, var(--platform-color) 24%, transparent);\n }\n\n .mark {\n width: 32px;\n height: 32px;\n border-radius: 10px;\n background: color-mix(in srgb, var(--platform-color) 10%, #f7fafc);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--platform-color) 10%, transparent);\n }\n\n .platform-button.active .mark {\n background: linear-gradient(135deg, var(--platform-color), color-mix(in srgb, var(--platform-color) 72%, #052f36));\n box-shadow: 0 12px 24px color-mix(in srgb, var(--platform-color) 22%, transparent);\n }\n\n .platform-copy strong {\n color: #121a28;\n font-size: 13px;\n letter-spacing: -0.015em;\n }\n\n .platform-copy span,\n .platform-copy small {\n line-height: 1.35;\n }\n\n .switch-surface {\n grid-template-columns: minmax(0, 1.56fr) minmax(390px, 0.94fr);\n gap: 14px;\n align-items: start;\n }\n\n .switch-panel,\n .summary-panel {\n border: 1px solid rgba(210, 222, 232, 0.92);\n border-radius: var(--radius-lg);\n background: rgba(255, 255, 255, 0.86);\n box-shadow: var(--shadow-panel);\n backdrop-filter: blur(18px);\n }\n\n .switch-panel {\n gap: 18px;\n padding: 24px;\n }\n\n .panel-head {\n padding-bottom: 2px;\n }\n\n .eyebrow {\n color: #64748b;\n font-size: 12px;\n letter-spacing: 0.04em;\n }\n\n .panel-head h2 {\n color: #111827;\n font-size: clamp(31px, 3vw, 42px);\n letter-spacing: -0.045em;\n }\n\n .public-note {\n max-width: 720px;\n color: #53657a;\n font-size: 15px;\n }\n\n .public-preview {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n margin-top: 4px;\n }\n\n .public-preview-card {\n min-width: 0;\n display: grid;\n gap: 6px;\n padding: 16px;\n border: 1px solid rgba(212, 223, 234, 0.86);\n border-radius: var(--radius-md);\n background:\n linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(247, 250, 252, 0.88));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86);\n }\n\n .public-preview-card strong {\n color: #152033;\n font-size: 14px;\n line-height: 1.3;\n font-weight: 800;\n }\n\n .public-preview-card span {\n color: #64748b;\n font-size: 12px;\n line-height: 1.5;\n }\n\n .quick-form {\n gap: 14px;\n }\n\n .form-section {\n position: relative;\n gap: 14px 16px;\n padding: 16px;\n border: 1px solid rgba(215, 225, 235, 0.86);\n border-radius: var(--radius-md);\n background:\n linear-gradient(90deg, color-mix(in srgb, var(--platform-color, var(--accent)) 5%, transparent), transparent 32%),\n linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(250, 252, 254, 0.88));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.84);\n }\n\n .form-section + .form-section {\n padding-top: 16px;\n border-top: 1px solid rgba(215, 225, 235, 0.86);\n }\n\n .control-block {\n grid-template-rows: auto 46px minmax(20px, auto);\n gap: 8px;\n color: #53657a;\n font-size: 12px;\n line-height: 1.25;\n font-weight: 790;\n }\n\n .control-block > span:first-child {\n color: #293548;\n }\n\n select,\n input,\n .mode {\n min-height: 46px;\n border-color: rgba(207, 219, 230, 0.92);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.92);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 8px 20px rgba(22, 35, 53, 0.035);\n }\n\n select,\n input {\n height: 46px;\n color: #172033;\n font-size: 14px;\n font-weight: 700;\n }\n\n select:disabled,\n input:disabled {\n background: rgba(241, 245, 249, 0.72);\n color: #7d8da1;\n box-shadow: none;\n }\n\n .mode {\n height: 46px;\n background: rgba(238, 244, 248, 0.9);\n }\n\n .mode button {\n border-radius: 9px;\n color: #5c6e84;\n font-size: 13px;\n font-weight: 800;\n }\n\n .mode button.active {\n background: #ffffff;\n color: var(--platform-color, var(--accent));\n box-shadow: 0 10px 20px rgba(22, 35, 53, 0.08);\n }\n\n .selection-meta {\n color: #758397;\n font-size: 12px;\n }\n\n .run-area {\n gap: 10px;\n padding-top: 2px;\n }\n\n .run-button {\n width: 100%;\n min-height: 58px;\n border-radius: 12px;\n background:\n linear-gradient(135deg, var(--platform-color, var(--accent)), color-mix(in srgb, var(--platform-color, var(--accent)) 76%, #042e35));\n box-shadow: 0 18px 36px color-mix(in srgb, var(--platform-color, var(--accent)) 24%, transparent);\n letter-spacing: 0.02em;\n }\n\n .run-button:hover:not(:disabled) {\n filter: none;\n box-shadow: 0 22px 42px color-mix(in srgb, var(--platform-color, var(--accent)) 30%, transparent);\n }\n\n .run-button:disabled {\n box-shadow: none;\n }\n\n .action-note {\n color: #53657a;\n font-size: 13px;\n }\n\n .toast {\n grid-column: 1 / -1;\n color: var(--platform-color, var(--accent));\n font-weight: 720;\n }\n\n .summary-panel {\n position: sticky;\n top: 112px;\n overflow: hidden;\n }\n\n .summary-head {\n padding: 24px 24px 18px;\n border-bottom-color: rgba(218, 227, 236, 0.9);\n }\n\n .summary-head h2 {\n color: #111827;\n font-size: 22px;\n letter-spacing: -0.03em;\n }\n\n .summary-list {\n padding: 4px 24px 18px;\n }\n\n .summary-row {\n grid-template-columns: 132px minmax(0, 1fr);\n gap: 14px;\n padding: 14px 0;\n border-bottom-color: rgba(229, 236, 242, 0.96);\n }\n\n .summary-label {\n color: #66758a;\n font-size: 12px;\n font-weight: 790;\n }\n\n .summary-value {\n color: #263244;\n font-size: 13px;\n font-weight: 700;\n }\n\n .summary-row.pending {\n margin: 0 -24px;\n padding-inline: 21px 24px;\n border-left: 3px solid var(--platform-color, var(--accent));\n background:\n linear-gradient(90deg, color-mix(in srgb, var(--platform-color, var(--accent)) 9%, #ffffff), rgba(255, 255, 255, 0.72));\n }\n\n .summary-status {\n min-height: 26px;\n border-color: color-mix(in srgb, var(--platform-color, var(--accent)) 26%, #d9e2ea);\n background: color-mix(in srgb, var(--platform-color, var(--accent)) 9%, #ffffff);\n color: var(--platform-color, var(--accent));\n }\n\n .summary-actions {\n padding: 16px 24px 24px;\n border-top-color: rgba(218, 227, 236, 0.9);\n background: transparent;\n }\n\n @media (max-width: 820px) {\n .platform-strip {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 6px;\n padding: 8px 10px;\n }\n\n .platform-copy strong {\n font-size: 12px;\n }\n\n .switch-surface {\n grid-template-columns: 1fr;\n }\n\n .summary-panel {\n position: static;\n }\n\n .public-preview {\n grid-template-columns: 1fr;\n }\n }\n\n\n @media (max-width: 540px) {\n .platform-strip {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 5px;\n padding: 6px 8px;\n }\n .platform-button {\n min-height: 40px;\n gap: 5px;\n padding: 5px 7px;\n }\n .mark {\n width: 26px;\n height: 26px;\n font-size: 10px;\n }\n .platform-copy strong {\n font-size: 11px;\n }\n }\n\n @media (max-width: 680px) {\n .topbar {\n position: static;\n align-items: flex-start;\n flex-direction: column;\n padding: 18px;\n }\n\n .top-actions {\n justify-content: flex-start;\n width: 100%;\n }\n\n .boot-screen {\n width: min(100% - 28px, 680px);\n min-height: calc(100svh - 122px);\n padding-top: 22px;\n }\n\n .shell {\n width: min(100% - 28px, 1160px);\n padding-top: 18px;\n }\n\n .platform-strip,\n .quick-form,\n .form-section,\n .runtime-authority-notice {\n grid-template-columns: 1fr;\n }\n\n .platform-strip {\n grid-template-columns: repeat(5, minmax(68px, 1fr));\n gap: 6px;\n padding: 8px;\n overflow-x: auto;\n }\n\n .platform-button {\n min-height: 48px;\n padding-inline: 8px 24px;\n }\n\n .platform-copy strong {\n font-size: 11px;\n }\n\n .mode-block,\n .section-wide {\n grid-column: auto;\n }\n\n .switch-panel {\n padding: 18px;\n }\n\n .summary-row {\n grid-template-columns: 1fr;\n gap: 3px;\n }\n\n .run-area {\n grid-template-columns: 1fr;\n }\n\n select {\n font-size: 13px;\n }\n }\n\n @media (prefers-reduced-motion: no-preference) {\n .platform-strip,\n .switch-surface {\n animation: rise-in 260ms ease both;\n }\n\n .switch-surface {\n animation-delay: 60ms;\n }\n\n @keyframes rise-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n @keyframes loading-sweep {\n from {\n transform: translateX(-110%);\n }\n to {\n transform: translateX(260%);\n }\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .loading-track::before {\n animation: none;\n transform: translateX(0);\n }\n }\n\n /* Operator console visual system: evidence first, authority always explicit. */\n :root {\n --bg: #07111f;\n --surface: #0d1b2b;\n --soft: #112238;\n --ink: #edf4fb;\n --muted: #98a9bc;\n --line: #263a52;\n --line-strong: #405875;\n --accent: #42c7b6;\n --accent-ink: #04110f;\n --danger: #ff8177;\n }\n\n html {\n background: var(--bg);\n color-scheme: dark;\n }\n\n body {\n background: var(--bg);\n color: var(--ink);\n }\n\n body::before {\n content: \"\";\n position: fixed;\n inset: 0;\n z-index: -1;\n pointer-events: none;\n background:\n radial-gradient(circle at 12% -20%, rgba(49, 144, 155, 0.14), transparent 34%),\n linear-gradient(180deg, #091626 0%, #07111f 58%, #081423 100%);\n }\n\n .topbar {\n min-height: 76px;\n padding: 18px max(24px, calc((100vw - 1280px) / 2));\n border-color: var(--line);\n background: rgba(7, 17, 31, 0.9);\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.16);\n border-inline: 0;\n border-radius: 0;\n }\n\n h1 {\n font-size: 23px;\n letter-spacing: -0.035em;\n }\n\n .brand p,\n .health-head p,\n .health-meta,\n .selection-meta,\n .action-note,\n .summary-label,\n .health-card__meta,\n .health-card__reason,\n .owner-decision small {\n color: var(--muted);\n }\n\n .shell {\n width: min(1280px, calc(100vw - 44px));\n gap: 20px;\n padding-top: 28px;\n }\n\n .console-nav {\n width: fit-content;\n max-width: 100%;\n padding: 3px;\n border-color: var(--line);\n border-radius: 9px;\n background: rgba(13, 27, 43, 0.82);\n box-shadow: inset 0 1px rgba(255, 255, 255, 0.035);\n }\n\n .console-nav__button {\n min-height: 40px;\n color: var(--muted);\n }\n\n .console-nav__button.active {\n background: #1a3049;\n color: #f4f8fc;\n box-shadow: inset 0 0 0 1px rgba(116, 167, 193, 0.2);\n }\n\n .runtime-authority-notice {\n border-color: rgba(231, 181, 82, 0.42);\n background: linear-gradient(90deg, rgba(80, 60, 24, 0.5), rgba(13, 27, 43, 0.85));\n color: #f0d595;\n }\n\n .runtime-authority-notice strong { color: #ffd16d; }\n\n .health-head {\n align-items: flex-start;\n padding: 8px 2px 2px;\n }\n\n .health-head h2 {\n max-width: 780px;\n font-size: clamp(31px, 4.6vw, 52px);\n font-weight: 760;\n letter-spacing: -0.055em;\n }\n\n .health-head p { max-width: 700px; }\n\n .health-notice {\n border-color: rgba(236, 177, 69, 0.38);\n background: rgba(75, 55, 24, 0.38);\n color: #f2d193;\n }\n\n .health-summary {\n gap: 0;\n overflow: hidden;\n border-top: 1px solid var(--line);\n border-bottom: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n }\n\n .health-summary article {\n min-height: 88px;\n padding: 15px 18px;\n border: 0;\n border-right: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n }\n\n .health-summary article:last-child { border-right: 0; }\n\n .health-summary strong,\n .health-card__score strong { color: var(--accent); }\n\n .health-board-head { margin-top: 20px; }\n .health-board-head h3 { font-size: 17px; letter-spacing: -0.02em; }\n\n .health-list {\n grid-template-columns: 1fr;\n gap: 8px;\n }\n\n .health-card,\n .lifecycle-table-wrap,\n .switch-panel,\n .summary-panel {\n border-color: var(--line);\n background: rgba(13, 27, 43, 0.94);\n box-shadow: 0 16px 30px rgba(0, 0, 0, 0.16);\n }\n\n .health-card {\n min-height: 108px;\n padding: 17px 18px;\n border-radius: 10px;\n }\n\n .health-card:hover {\n border-color: #466481;\n background: #102238;\n }\n\n .health-card__title { color: var(--ink); }\n .health-card__score { border-color: var(--line); }\n\n .health-filter,\n .owner-decision__button,\n .btn {\n border-color: var(--line-strong);\n background: #112238;\n color: var(--ink);\n }\n\n .pill,\n .status-pill {\n background: #112238;\n color: #c8d7e7;\n }\n\n .health-filter.active {\n border-color: var(--accent);\n background: rgba(66, 199, 182, 0.12);\n color: #90f0df;\n }\n\n select,\n input {\n border-color: var(--line);\n background: #0a1828;\n color: var(--ink);\n }\n\n select:focus,\n input:focus {\n border-color: var(--accent);\n box-shadow: 0 0 0 3px rgba(66, 199, 182, 0.16);\n }\n\n input::placeholder { color: #71839a; }\n .mode { border-color: var(--line); background: #0a1828; }\n .mode button.active { background: #24465a; color: #eafbf8; }\n\n .summary-head,\n .summary-row,\n .form-section + .form-section {\n border-color: var(--line);\n }\n\n .summary-row.pending {\n background: rgba(66, 199, 182, 0.09);\n }\n\n .summary-value { color: var(--ink); }\n\n .run-button {\n background: var(--platform-color, var(--accent));\n color: var(--accent-ink);\n }\n\n .public-preview-card,\n .empty-state,\n .health-card__empty {\n border-color: var(--line);\n background: rgba(13, 27, 43, 0.8);\n color: var(--muted);\n }\n\n .audit-log,\n .audit-row,\n .preview-panel,\n .status-pill,\n .pill {\n border-color: var(--line);\n }\n\n @media (min-width: 900px) {\n .shell {\n grid-template-columns: 206px minmax(0, 1fr);\n align-items: start;\n }\n\n .runtime-authority-notice {\n grid-column: 1 / -1;\n }\n\n .console-nav {\n position: sticky;\n top: 98px;\n display: grid;\n width: 100%;\n gap: 3px;\n padding: 7px;\n }\n\n .console-nav__button {\n text-align: left;\n padding-inline: 13px;\n }\n\n .health-view,\n #switch-view {\n grid-column: 2;\n }\n }\n\n @media (max-width: 720px) {\n .topbar { padding-inline: 18px; }\n .shell { width: min(100% - 28px, 1280px); padding-top: 18px; }\n .health-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n .health-summary article:nth-child(2n) { border-right: 0; }\n .health-summary article:nth-child(n + 3) { border-top: 1px solid var(--line); }\n }\n\n /* Low-frequency operator console: boundaries before controls. */\n :root {\n --bg: #f5f7f8;\n --surface: #ffffff;\n --soft: #eef2f3;\n --ink: #172022;\n --muted: #657276;\n --line: #dbe3e4;\n --line-strong: #b7c7c8;\n --accent: #09665f;\n --accent-ink: #ffffff;\n --danger: #a6463f;\n }\n\n html {\n background: var(--bg);\n color-scheme: light;\n }\n\n body {\n background: var(--bg);\n color: var(--ink);\n }\n\n body::before {\n display: none;\n }\n\n .topbar {\n min-height: 68px;\n padding: 14px max(22px, calc((100vw - 1320px) / 2));\n border-color: var(--line);\n background: rgba(255, 255, 255, 0.94);\n box-shadow: none;\n }\n\n .brand {\n grid-template-columns: auto minmax(0, 1fr);\n align-items: baseline;\n column-gap: 13px;\n }\n\n .brand h1 {\n font-size: 21px;\n letter-spacing: -0.03em;\n }\n\n .brand p {\n padding-left: 13px;\n border-left: 1px solid var(--line);\n font-size: 12px;\n }\n\n .shell {\n width: min(1280px, calc(100vw - 44px));\n gap: 24px;\n padding: 26px 0 44px;\n }\n\n .runtime-authority-notice {\n border-color: #e7cf95;\n background: #fffbef;\n color: #705c2e;\n box-shadow: none;\n }\n\n .runtime-authority-notice strong {\n color: #8b6419;\n }\n\n .console-nav {\n width: fit-content;\n padding: 3px;\n border-radius: 9px;\n border-color: var(--line);\n background: #ffffff;\n box-shadow: none;\n }\n\n .console-nav__button {\n min-height: 38px;\n color: var(--muted);\n font-weight: 720;\n }\n\n .console-nav__button.active {\n background: var(--accent);\n color: #ffffff;\n box-shadow: none;\n }\n\n .health-head {\n align-items: flex-start;\n padding: 4px 0 0;\n }\n\n .health-head h2,\n .plan-intro h2 {\n max-width: 780px;\n font-size: clamp(30px, 4.4vw, 48px);\n font-weight: 760;\n letter-spacing: -0.052em;\n }\n\n .health-head p,\n .plan-intro p {\n max-width: 740px;\n color: var(--muted);\n font-size: 14px;\n line-height: 1.65;\n }\n\n .health-notice {\n border-color: #eed9b0;\n background: #fffbf3;\n color: #765b25;\n }\n\n .health-summary {\n gap: 0;\n overflow: hidden;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: var(--surface);\n }\n\n .health-summary article {\n min-height: 86px;\n padding: 16px 18px;\n border: 0;\n border-right: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n }\n\n .health-summary article:last-child {\n border-right: 0;\n }\n\n .health-summary strong,\n .health-card__score strong {\n color: var(--accent);\n }\n\n .health-board-head {\n margin-top: 16px;\n }\n\n .health-board-head h3 {\n font-size: 16px;\n letter-spacing: -0.018em;\n }\n\n .health-list {\n grid-template-columns: 1fr;\n gap: 8px;\n }\n\n .health-card,\n .lifecycle-table-wrap,\n .switch-panel,\n .summary-panel {\n border-color: var(--line);\n background: var(--surface);\n box-shadow: none;\n }\n\n .health-card {\n min-height: 94px;\n padding: 16px 18px;\n border-radius: 9px;\n }\n\n .health-card:hover {\n border-color: var(--line-strong);\n background: #fcfdfd;\n }\n\n .health-card__empty,\n .empty-state {\n border-color: var(--line);\n background: #fbfcfc;\n color: var(--muted);\n }\n\n .health-card__score {\n border-color: var(--line);\n }\n\n .control-disclosure {\n border-top: 1px solid var(--line);\n padding-top: 15px;\n }\n\n .control-disclosure summary {\n cursor: pointer;\n color: var(--ink);\n font-size: 14px;\n font-weight: 740;\n list-style-position: inside;\n }\n\n .control-disclosure[open] summary {\n margin-bottom: 12px;\n }\n\n .plan-intro {\n display: grid;\n gap: 9px;\n padding: 5px 0 0;\n }\n\n .plan-intro .eyebrow {\n color: var(--accent);\n }\n\n .platform-strip {\n grid-template-columns: repeat(6, minmax(0, 1fr));\n gap: 7px;\n }\n\n .platform-button {\n min-height: 54px;\n padding: 7px 9px;\n border-left-width: 3px;\n border-color: var(--line);\n background: #ffffff;\n box-shadow: none;\n }\n\n .platform-button:hover {\n transform: none;\n border-color: var(--line-strong);\n }\n\n .platform-button.active {\n border-color: var(--platform-color);\n background: #f7fbfa;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--platform-color) 17%, transparent);\n }\n\n .public-preview-card {\n border-color: var(--line);\n background: #fbfcfc;\n color: var(--muted);\n box-shadow: none;\n }\n\n .public-preview-card strong {\n color: var(--ink);\n }\n\n .btn,\n .health-filter,\n .owner-decision__button {\n border-color: var(--line-strong);\n background: #ffffff;\n color: var(--ink);\n }\n\n .btn:hover,\n .health-filter:hover,\n .owner-decision__button:hover {\n background: #f7fbfa;\n border-color: var(--accent);\n }\n\n .pill,\n .status-pill {\n border-color: var(--line);\n background: #f7f9f9;\n color: var(--muted);\n }\n\n .switch-surface {\n grid-template-columns: minmax(0, 1.2fr) minmax(330px, 0.8fr);\n gap: 18px;\n align-items: start;\n }\n\n .switch-panel {\n gap: 22px;\n padding: 23px;\n border-radius: 10px;\n }\n\n .panel-head {\n padding-bottom: 17px;\n border-bottom: 1px solid var(--line);\n }\n\n .panel-head h2 {\n font-size: 28px;\n letter-spacing: -0.038em;\n }\n\n .quick-form {\n gap: 17px 14px;\n }\n\n .form-step,\n .form-section-heading {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: 28px minmax(0, 1fr);\n align-items: start;\n gap: 10px;\n margin-bottom: 1px;\n }\n\n .form-step > span,\n .form-section-heading > span {\n width: 24px;\n height: 24px;\n display: inline-grid;\n place-items: center;\n border-radius: 50%;\n background: #e7f2f0;\n color: var(--accent);\n font-size: 11px;\n font-weight: 820;\n }\n\n .form-step strong,\n .form-section-heading strong {\n display: block;\n color: var(--ink);\n font-size: 14px;\n line-height: 1.35;\n }\n\n .form-step small,\n .form-section-heading small {\n display: block;\n margin-top: 2px;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.45;\n }\n\n .form-section {\n gap: 15px 14px;\n }\n\n .form-section + .form-section {\n padding-top: 19px;\n border-color: var(--line);\n }\n\n .execution-cash-policy-section {\n gap: 12px;\n }\n\n .execution-cash-policy-heading {\n font-size: 14px;\n }\n\n .execution-cash-policy-note {\n margin-left: 38px;\n }\n\n select,\n input,\n .mode {\n border-color: var(--line);\n background: #ffffff;\n color: var(--ink);\n }\n\n select:focus,\n input:focus {\n border-color: var(--accent);\n box-shadow: 0 0 0 3px rgba(9, 102, 95, 0.13);\n }\n\n .mode button.active {\n background: #e9f4f1;\n color: var(--accent);\n box-shadow: none;\n }\n\n .mode button:disabled {\n color: #a7b1b3;\n }\n\n .run-area {\n gap: 8px;\n padding-top: 4px;\n }\n\n .run-button {\n min-height: 52px;\n background: var(--accent);\n color: #ffffff;\n border-radius: 8px;\n font-size: 14px;\n }\n\n .summary-panel {\n position: sticky;\n top: 92px;\n border-radius: 10px;\n grid-template-rows: auto auto auto auto;\n }\n\n .summary-head {\n padding: 17px 18px;\n background: #f8fbfa;\n border-color: var(--line);\n }\n\n .summary-list {\n padding: 8px 18px 12px;\n }\n\n .summary-section-title {\n margin: 10px 0 2px;\n color: var(--muted);\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n }\n\n .summary-row {\n grid-template-columns: 126px minmax(0, 1fr);\n padding: 10px 0;\n border-color: var(--line);\n }\n\n .summary-row.pending {\n margin: 0 -18px;\n padding-inline: 15px 18px;\n border-left-color: var(--accent);\n background: #f1f8f6;\n }\n\n .summary-value {\n color: var(--ink);\n }\n\n .plan-readiness {\n margin: 0 18px 16px;\n border: 1px solid var(--line);\n border-radius: 8px;\n overflow: hidden;\n }\n\n .plan-readiness h3 {\n margin: 0;\n padding: 11px 13px;\n border-bottom: 1px solid var(--line);\n background: #fbfcfc;\n font-size: 13px;\n }\n\n .plan-readiness ul {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n .plan-readiness li {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 10px 13px;\n border-bottom: 1px solid var(--line);\n color: var(--muted);\n font-size: 12px;\n }\n\n .plan-readiness li:last-child {\n border-bottom: 0;\n }\n\n .plan-readiness strong {\n color: var(--muted);\n font-size: 12px;\n white-space: nowrap;\n }\n\n .plan-readiness strong[data-tone=\"ready\"] {\n color: var(--accent);\n }\n\n .plan-readiness strong[data-tone=\"warning\"] {\n color: var(--danger);\n }\n\n .summary-actions {\n display: grid;\n gap: 8px;\n padding: 14px 18px;\n border-color: var(--line);\n background: #fbfcfc;\n }\n\n .summary-actions p {\n margin: 0;\n color: var(--muted);\n font-size: 11px;\n line-height: 1.5;\n }\n\n @media (min-width: 900px) {\n .shell {\n grid-template-columns: 184px minmax(0, 1fr);\n }\n\n .console-nav {\n position: sticky;\n top: 91px;\n display: grid;\n width: 100%;\n gap: 4px;\n padding: 6px;\n }\n\n .console-nav__button {\n padding-inline: 12px;\n text-align: left;\n }\n }\n\n @media (max-width: 900px) {\n .platform-strip {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n }\n\n .summary-panel {\n position: static;\n }\n }\n\n @media (max-width: 640px) {\n .brand {\n display: grid;\n grid-template-columns: 1fr;\n gap: 2px;\n }\n\n .brand p {\n padding-left: 0;\n border-left: 0;\n }\n\n .topbar {\n align-items: flex-start;\n }\n\n .platform-strip {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .execution-cash-policy-note {\n margin-left: 0;\n }\n\n .plan-readiness {\n margin-inline: 14px;\n }\n }\n\n /* Decision-first console: surface only the low-frequency choices an owner makes. */\n :root {\n --bg: #ffffff;\n --surface: #ffffff;\n --soft: #f7f8f8;\n --ink: #182326;\n --muted: #647174;\n --line: #dbe2e2;\n --line-strong: #b8c6c5;\n --accent: #0f766e;\n --accent-soft: #edf8f6;\n --warning-soft: #fff9ed;\n --warning-line: #ead6a6;\n }\n\n body {\n background: var(--bg);\n }\n\n .topbar {\n min-height: 64px;\n border-bottom: 1px solid var(--line);\n background: rgba(255, 255, 255, 0.96);\n }\n\n .shell {\n width: min(1240px, calc(100vw - 44px));\n gap: 28px;\n padding-top: 28px;\n }\n\n .console-nav {\n border-radius: 10px;\n background: #ffffff;\n }\n\n .console-nav__button {\n min-height: 40px;\n border-radius: 7px;\n font-size: 13px;\n font-weight: 720;\n }\n\n .console-nav__button.active {\n background: var(--accent);\n }\n\n .decision-view {\n display: grid;\n gap: 18px;\n }\n\n .decision-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 28px;\n padding: 4px 0 2px;\n }\n\n .decision-head h2 {\n max-width: 700px;\n margin-top: 7px;\n font-size: clamp(30px, 4vw, 46px);\n line-height: 1.08;\n letter-spacing: -0.052em;\n }\n\n .decision-head p {\n max-width: 680px;\n margin-top: 12px;\n color: var(--muted);\n font-size: 14px;\n line-height: 1.65;\n }\n\n .decision-freshness {\n display: grid;\n justify-items: end;\n gap: 8px;\n min-width: 250px;\n padding-top: 5px;\n color: var(--muted);\n font-size: 12px;\n text-align: right;\n }\n\n .decision-freshness .pill {\n max-width: 300px;\n border-color: var(--line);\n background: var(--soft);\n color: var(--muted);\n font-weight: 650;\n white-space: normal;\n text-align: right;\n }\n\n .decision-notice {\n padding: 12px 14px;\n border: 1px solid var(--warning-line);\n border-radius: 9px;\n background: var(--warning-soft);\n color: #765b25;\n font-size: 12px;\n line-height: 1.55;\n }\n\n .decision-stats {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n overflow: hidden;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: #ffffff;\n }\n\n .decision-stats article {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n min-height: 60px;\n padding: 16px;\n border-right: 1px solid var(--line);\n }\n\n .decision-stats article:last-child {\n border-right: 0;\n }\n\n .decision-stats span {\n color: var(--muted);\n font-size: 12px;\n }\n\n .decision-stats strong {\n color: var(--accent);\n font-size: 24px;\n letter-spacing: -0.04em;\n }\n\n .decision-board {\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(240px, 0.34fr);\n gap: 18px;\n align-items: start;\n }\n\n .decision-queue {\n min-width: 0;\n }\n\n .decision-board-head {\n display: flex;\n align-items: end;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 10px;\n }\n\n .decision-board-head h3,\n .decision-guide h3 {\n margin-top: 5px;\n font-size: 17px;\n letter-spacing: -0.02em;\n }\n\n .decision-board-head > span {\n color: var(--muted);\n font-size: 12px;\n }\n\n .decision-queue .health-list {\n gap: 8px;\n }\n\n .decision-queue .health-card {\n min-height: 0;\n padding: 15px 16px;\n border-radius: 9px;\n box-shadow: none;\n }\n\n .decision-queue .health-card__title {\n margin: 6px 0;\n font-size: 15px;\n }\n\n .decision-queue .health-card__reason {\n font-size: 13px;\n line-height: 1.52;\n }\n\n .decision-queue .health-card__score {\n align-self: stretch;\n min-width: 92px;\n padding-left: 15px;\n }\n\n .decision-guide {\n display: grid;\n gap: 12px;\n padding: 18px;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: var(--soft);\n }\n\n .decision-guide ol {\n display: grid;\n gap: 10px;\n margin: 0;\n padding: 0;\n list-style: none;\n counter-reset: guide;\n }\n\n .decision-guide li {\n display: grid;\n grid-template-columns: 20px minmax(0, 1fr);\n gap: 8px;\n color: var(--ink);\n font-size: 12px;\n line-height: 1.55;\n }\n\n .decision-guide li::before {\n counter-increment: guide;\n content: counter(guide);\n display: grid;\n place-items: center;\n width: 20px;\n height: 20px;\n border-radius: 999px;\n background: #d9ece8;\n color: var(--accent);\n font-size: 11px;\n font-weight: 800;\n }\n\n .decision-guide p {\n padding-top: 11px;\n border-top: 1px solid var(--line);\n color: var(--muted);\n font-size: 11px;\n line-height: 1.55;\n }\n\n .decision-details {\n display: grid;\n gap: 0;\n border-top: 1px solid var(--line);\n }\n\n .decision-details .control-disclosure {\n margin: 0;\n border-bottom: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n }\n\n .decision-details .control-disclosure summary {\n min-height: 50px;\n padding: 0 2px;\n color: var(--ink);\n font-size: 13px;\n font-weight: 720;\n }\n\n .decision-details .control-disclosure[open] {\n padding-bottom: 15px;\n background: #ffffff;\n }\n\n .decision-details .health-notice {\n margin: 0 0 10px;\n }\n\n .plan-advanced {\n display: grid;\n gap: 16px;\n margin-top: 8px;\n padding-top: 16px;\n border-top: 1px solid var(--line);\n }\n\n .plan-advanced summary {\n color: var(--ink);\n font-size: 13px;\n font-weight: 760;\n }\n\n .plan-advanced > p {\n max-width: 640px;\n margin: -6px 0 0;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.55;\n }\n\n .plan-advanced:not([open]) > :not(summary) {\n display: none;\n }\n\n .plan-intro h2 {\n font-size: clamp(28px, 3.4vw, 40px);\n }\n\n .switch-panel,\n .summary-panel {\n border-radius: 10px;\n box-shadow: none;\n }\n\n .summary-panel {\n top: 92px;\n }\n\n .runtime-authority-notice {\n padding: 11px 13px;\n border-radius: 9px;\n font-size: 12px;\n }\n\n @media (min-width: 900px) {\n .decision-view,\n #switch-view,\n .health-view {\n grid-column: 2;\n }\n }\n\n @media (max-width: 900px) {\n .decision-board {\n grid-template-columns: 1fr;\n }\n\n .decision-guide {\n grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);\n column-gap: 22px;\n }\n\n .decision-guide > :first-child,\n .decision-guide h3 {\n grid-column: 1 / -1;\n }\n\n .decision-guide p {\n align-self: end;\n }\n }\n\n @media (max-width: 640px) {\n .shell {\n width: min(100% - 28px, 1240px);\n gap: 18px;\n padding-top: 18px;\n }\n\n .decision-head {\n display: grid;\n gap: 14px;\n }\n\n .decision-freshness {\n justify-items: start;\n min-width: 0;\n text-align: left;\n }\n\n .decision-stats {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .decision-stats article:nth-child(2) {\n border-right: 0;\n }\n\n .decision-stats article:nth-child(n + 3) {\n border-top: 1px solid var(--line);\n }\n\n .decision-queue .health-card {\n display: grid;\n grid-template-columns: 1fr;\n }\n\n .decision-queue .health-card__score {\n display: flex;\n min-width: 0;\n padding: 10px 0 0;\n border-top: 1px solid var(--line);\n border-left: 0;\n }\n\n .decision-guide {\n grid-template-columns: 1fr;\n }\n\n .decision-guide > :first-child,\n .decision-guide h3 {\n grid-column: auto;\n }\n }\n"; +export const APP_CSS = ":root {\n --bg: #f5f6f8;\n --surface: #ffffff;\n --soft: #eef1f4;\n --ink: #16191f;\n --muted: #66707c;\n --line: #dce1e7;\n --line-strong: #b8c2cc;\n --accent: #136f63;\n --accent-ink: #ffffff;\n --danger: #b54137;\n --lb: #0f766e;\n --ib: #2f5f9f;\n --sw: #9b4153;\n --ft: #94651f;\n --qmt: #b45309;\n --bn: #f0b90b;\n font-family:\n Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,\n \"Segoe UI\", \"PingFang SC\", \"Microsoft YaHei\", sans-serif;\n }\n\n * {\n box-sizing: border-box;\n }\n\n html {\n min-height: 100%;\n background: var(--bg);\n }\n\n body {\n margin: 0;\n min-height: 100svh;\n background:\n linear-gradient(180deg, #fbfcfd 0, #eef2f5 100%);\n color: var(--ink);\n letter-spacing: 0;\n }\n\n button,\n select,\n input {\n font: inherit;\n letter-spacing: 0;\n }\n\n button {\n cursor: pointer;\n }\n\n a {\n color: inherit;\n }\n\n button:focus-visible,\n select:focus-visible,\n input:focus-visible,\n a:focus-visible {\n outline: 3px solid color-mix(in srgb, var(--platform-color, var(--accent)) 24%, transparent);\n outline-offset: 2px;\n }\n\n [hidden] {\n display: none !important;\n }\n\n h1,\n h2,\n h3,\n p {\n margin: 0;\n }\n\n .topbar {\n min-height: 72px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 20px;\n padding: 17px 28px;\n border-bottom: 1px solid var(--line);\n background: rgba(250, 251, 252, 0.88);\n backdrop-filter: blur(14px);\n position: sticky;\n top: 0;\n z-index: 20;\n }\n\n .brand {\n display: grid;\n gap: 3px;\n min-width: 0;\n }\n\n h1 {\n font-size: 22px;\n line-height: 1.12;\n font-weight: 780;\n overflow-wrap: anywhere;\n }\n\n .brand p {\n color: var(--muted);\n font-size: 13px;\n line-height: 1.4;\n }\n\n .top-actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n flex-wrap: wrap;\n }\n\n .auth-status {\n max-width: 260px;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .shell {\n width: min(1160px, calc(100vw - 36px));\n margin: 0 auto;\n padding: 22px 0 32px;\n display: grid;\n gap: 16px;\n }\n\n .console-nav {\n display: flex;\n gap: 8px;\n padding: 4px;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: rgba(255, 255, 255, 0.7);\n }\n\n .console-nav__button {\n min-height: 38px;\n padding: 0 15px;\n border: 0;\n border-radius: 7px;\n background: transparent;\n color: var(--muted);\n font-size: 13px;\n font-weight: 780;\n }\n\n .console-nav__button.active {\n background: var(--ink);\n color: #ffffff;\n }\n\n .health-view {\n display: grid;\n gap: 18px;\n }\n\n .lifecycle-view {\n display: grid;\n gap: 18px;\n }\n\n .lifecycle-table-wrap {\n overflow-x: auto;\n border: 1px solid var(--line);\n border-radius: 8px;\n background: var(--surface);\n box-shadow: 0 10px 26px rgba(22, 25, 31, 0.05);\n }\n\n .lifecycle-table {\n width: 100%;\n min-width: 980px;\n border-collapse: collapse;\n font-size: 12px;\n }\n\n .lifecycle-table th,\n .lifecycle-table td {\n padding: 12px 10px;\n border-bottom: 1px solid var(--line);\n text-align: left;\n vertical-align: top;\n white-space: nowrap;\n }\n\n .lifecycle-table thead th { color: var(--muted); font-size: 11px; font-weight: 760; background: var(--soft); }\n .lifecycle-table tbody th { max-width: 220px; white-space: normal; font-weight: 720; }\n .lifecycle-table tbody tr:last-child th,\n .lifecycle-table tbody tr:last-child td { border-bottom: 0; }\n .lifecycle-parked, .lifecycle-deferred { color: var(--danger); }\n .lifecycle-not_started { color: var(--muted); }\n\n .health-head,\n .health-board-head {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: 20px;\n }\n\n .health-head {\n padding: 22px 2px 4px;\n }\n\n .health-head h2 {\n margin-top: 7px;\n font-size: clamp(28px, 5vw, 48px);\n line-height: 1.05;\n letter-spacing: -0.04em;\n }\n\n .health-head p {\n max-width: 620px;\n margin-top: 12px;\n color: var(--muted);\n font-size: 14px;\n line-height: 1.6;\n }\n\n .health-meta {\n display: grid;\n justify-items: end;\n gap: 8px;\n color: var(--muted);\n font-size: 12px;\n white-space: nowrap;\n }\n\n .health-notice {\n padding: 13px 15px;\n border: 1px solid rgba(180, 83, 9, 0.22);\n border-radius: 8px;\n background: #fffaf2;\n color: #87520d;\n font-size: 12px;\n line-height: 1.5;\n }\n\n .health-summary {\n display: grid;\n grid-template-columns: repeat(5, minmax(0, 1fr));\n gap: 10px;\n }\n\n .health-summary article,\n .health-card {\n border: 1px solid var(--line);\n border-radius: 8px;\n background: var(--surface);\n box-shadow: 0 10px 26px rgba(22, 25, 31, 0.05);\n }\n\n .health-summary article {\n min-height: 104px;\n padding: 17px;\n }\n\n .health-summary span,\n .health-card__meta,\n .health-card__score small {\n color: var(--muted);\n font-size: 11px;\n }\n\n .health-summary strong {\n display: block;\n margin-top: 12px;\n color: var(--accent);\n font-size: 28px;\n }\n\n .health-board-head {\n margin-top: 14px;\n }\n\n .health-board-head h3 {\n font-size: 21px;\n }\n\n .health-filters {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n }\n\n .health-filter {\n padding: 7px 10px;\n border: 1px solid var(--line);\n border-radius: 999px;\n background: var(--surface);\n color: var(--muted);\n font-size: 11px;\n }\n\n .health-filter.active {\n border-color: var(--accent);\n color: var(--accent);\n background: #eff9f6;\n }\n\n .health-list {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 10px;\n }\n\n .health-card {\n display: flex;\n justify-content: space-between;\n gap: 18px;\n min-width: 0;\n padding: 18px;\n }\n\n .health-card__main {\n min-width: 0;\n }\n\n .health-card__title {\n margin: 10px 0 7px;\n font-size: 16px;\n overflow-wrap: anywhere;\n }\n\n .health-card__reason {\n margin: 0;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.55;\n }\n\n .health-card__meta {\n margin-top: 13px;\n line-height: 1.5;\n }\n\n .health-card__score {\n flex: 0 0 72px;\n padding-left: 14px;\n border-left: 1px solid var(--line);\n text-align: right;\n }\n\n .health-card__score strong {\n display: block;\n margin: 7px 0;\n color: var(--accent);\n font-size: 27px;\n }\n\n .owner-decision {\n display: grid;\n gap: 7px;\n margin-top: 14px;\n padding-top: 13px;\n border-top: 1px solid var(--line);\n }\n\n .owner-decision strong {\n font-size: 12px;\n }\n\n .owner-decision small {\n color: var(--muted);\n font-size: 11px;\n line-height: 1.5;\n }\n\n .owner-decision__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 7px;\n }\n\n .owner-decision__button {\n padding: 7px 9px;\n border: 1px solid var(--line-strong);\n border-radius: 7px;\n background: var(--surface);\n color: var(--ink);\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n }\n\n .owner-decision__button:hover:not(:disabled) {\n border-color: var(--accent);\n color: var(--accent);\n }\n\n .owner-decision__button:disabled {\n cursor: wait;\n opacity: 0.62;\n }\n\n .health-card__empty {\n grid-column: 1 / -1;\n padding: 30px 18px;\n border: 1px dashed var(--line-strong);\n border-radius: 8px;\n background: rgba(255, 255, 255, 0.58);\n color: var(--muted);\n font-size: 13px;\n line-height: 1.6;\n text-align: center;\n }\n\n body.app-loading .shell {\n display: none;\n }\n\n body:not(.app-loading) .boot-screen {\n display: none;\n }\n\n .boot-screen {\n width: min(680px, calc(100vw - 36px));\n min-height: calc(100svh - 72px);\n margin: 0 auto;\n display: grid;\n align-items: center;\n padding: 34px 0;\n }\n\n .boot-panel {\n display: grid;\n gap: 13px;\n padding: 8px 0;\n }\n\n .boot-kicker {\n color: var(--accent);\n font-size: 12px;\n line-height: 1.2;\n font-weight: 800;\n text-transform: uppercase;\n }\n\n .boot-panel h2 {\n color: var(--ink);\n font-size: clamp(28px, 5vw, 44px);\n line-height: 1.04;\n font-weight: 820;\n }\n\n .boot-panel p {\n color: var(--muted);\n font-size: 14px;\n line-height: 1.5;\n max-width: 520px;\n }\n\n .loading-track {\n width: 100%;\n height: 5px;\n overflow: hidden;\n border-radius: 999px;\n background: #dde4ea;\n }\n\n .loading-track::before {\n content: \"\";\n display: block;\n width: 42%;\n height: 100%;\n border-radius: inherit;\n background: var(--accent);\n animation: loading-sweep 1.05s ease-in-out infinite;\n }\n\n .pill,\n .btn {\n min-height: 36px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 7px;\n border: 1px solid var(--line);\n background: var(--surface);\n color: var(--muted);\n text-decoration: none;\n white-space: nowrap;\n font-size: 13px;\n font-weight: 740;\n box-shadow: 0 1px 0 rgba(22, 25, 31, 0.03);\n }\n\n .pill {\n padding: 0 12px;\n border-radius: 999px;\n }\n\n .pill.ready {\n border-color: rgba(19, 111, 99, 0.28);\n background: #eff9f6;\n color: var(--accent);\n }\n\n .btn {\n padding: 0 13px;\n border-radius: 8px;\n transition: border-color 150ms ease, background 150ms ease, transform 150ms ease;\n }\n\n .btn:hover {\n transform: translateY(-1px);\n border-color: var(--line-strong);\n background: #fafbfc;\n }\n\n .lang-toggle {\n min-width: 42px;\n padding: 0 10px;\n }\n\n .mode button {\n border: 0;\n border-radius: 6px;\n background: transparent;\n color: var(--muted);\n font-weight: 760;\n transition: background 150ms ease, color 150ms ease;\n }\n\n .mode button.active {\n background: #ffffff;\n color: var(--ink);\n box-shadow: 0 1px 7px rgba(22, 25, 31, 0.08);\n }\n\n .platform-strip {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 8px;\n }\n\n .platform-button {\n min-width: 0;\n min-height: 52px;\n display: grid;\n grid-template-columns: auto 1fr;\n align-items: center;\n gap: 8px;\n padding: 8px 10px;\n border: 1px solid var(--line);\n border-left: 4px solid var(--platform-color);\n border-radius: 8px;\n background: var(--surface);\n color: var(--ink);\n text-align: left;\n transition:\n transform 150ms ease,\n border-color 150ms ease,\n background 150ms ease,\n box-shadow 150ms ease;\n }\n\n .platform-button:hover {\n transform: translateY(-1px);\n border-color: var(--line-strong);\n }\n\n .platform-button.active {\n border-color: var(--platform-color);\n background:\n linear-gradient(180deg, color-mix(in srgb, var(--platform-color) 7%, white), #ffffff 68%);\n box-shadow: 0 14px 30px rgba(22, 25, 31, 0.09);\n }\n\n .platform-button[data-platform=\"longbridge\"] {\n --platform-color: var(--lb);\n }\n\n .platform-button[data-platform=\"ibkr\"] {\n --platform-color: var(--ib);\n }\n\n .platform-button[data-platform=\"schwab\"] {\n --platform-color: var(--sw);\n }\n\n .platform-button[data-platform=\"firstrade\"] {\n --platform-color: var(--ft);\n }\n\n .platform-button[data-platform=\"qmt\"] {\n --platform-color: var(--qmt);\n }\n\n .platform-button[data-platform=\"binance\"] {\n --platform-color: var(--bn);\n }\n\n .execution-cash-policy-section {\n grid-template-columns: 1fr;\n }\n\n .execution-cash-policy-heading {\n grid-column: 1 / -1;\n margin: 0;\n font-size: 13px;\n font-weight: 780;\n color: var(--ink);\n }\n\n .execution-cash-policy-note {\n grid-column: 1 / -1;\n margin: 0;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.45;\n }\n\n .execution-cash-policy-grid {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 14px;\n align-items: start;\n }\n\n .policy-block-muted .selection-meta {\n color: var(--danger);\n }\n\n @media (max-width: 720px) {\n .health-head,\n .health-board-head {\n align-items: flex-start;\n flex-direction: column;\n }\n\n .health-meta {\n justify-items: start;\n }\n\n .health-summary {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .health-list {\n grid-template-columns: 1fr;\n }\n\n .execution-cash-policy-grid {\n grid-template-columns: 1fr;\n }\n }\n\n .mark {\n width: 32px;\n height: 32px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 8px;\n background: color-mix(in srgb, var(--platform-color) 12%, white);\n color: var(--platform-color);\n font-size: 11px;\n font-weight: 840;\n transition: background 150ms ease, color 150ms ease;\n }\n\n .platform-button.active .mark {\n background: var(--platform-color);\n color: #ffffff;\n }\n\n .platform-copy {\n min-width: 0;\n display: grid;\n gap: 3px;\n }\n\n .platform-copy strong {\n font-size: 13px;\n line-height: 1.18;\n overflow-wrap: anywhere;\n }\n\n .platform-strip .platform-copy span,\n .platform-strip .platform-copy small {\n display: none;\n }\n\n .platform-copy span {\n color: var(--muted);\n font-size: 12px;\n line-height: 1.35;\n overflow-wrap: anywhere;\n }\n\n .platform-copy small {\n color: var(--platform-color);\n font-size: 11px;\n line-height: 1.25;\n font-weight: 780;\n overflow-wrap: anywhere;\n }\n\n .switch-surface {\n display: grid;\n grid-template-columns: minmax(0, 1.04fr) minmax(320px, 0.96fr);\n gap: 18px;\n align-items: stretch;\n }\n\n .switch-surface.summary-hidden {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .switch-panel,\n .summary-panel {\n min-width: 0;\n border: 1px solid var(--line);\n border-radius: 8px;\n background: var(--surface);\n box-shadow: 0 14px 34px rgba(22, 25, 31, 0.07);\n }\n\n .switch-panel {\n display: grid;\n gap: 20px;\n padding: 22px;\n }\n\n .panel-head {\n display: grid;\n gap: 5px;\n }\n\n .eyebrow {\n color: var(--muted);\n font-size: 12px;\n line-height: 1.2;\n font-weight: 780;\n text-transform: uppercase;\n }\n\n .panel-head h2 {\n font-size: clamp(24px, 3vw, 34px);\n line-height: 1.08;\n font-weight: 820;\n overflow-wrap: anywhere;\n }\n\n .quick-form {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n align-items: start;\n gap: 16px 14px;\n }\n\n .control-block {\n display: grid;\n grid-template-rows: auto 48px minmax(20px, auto);\n align-content: start;\n gap: 8px;\n min-width: 0;\n color: var(--muted);\n font-size: 12px;\n font-weight: 760;\n }\n\n .form-section {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n align-items: start;\n gap: 14px;\n }\n\n .form-section + .form-section {\n padding-top: 16px;\n border-top: 1px solid var(--line);\n }\n\n .section-wide {\n grid-column: 1 / -1;\n }\n\n .mode-block {\n grid-column: 1 / -1;\n }\n\n select,\n input {\n width: 100%;\n min-width: 0;\n height: 48px;\n padding: 0 13px;\n border: 1px solid var(--line);\n border-radius: 8px;\n background: #ffffff;\n color: var(--ink);\n outline: none;\n font-size: 14px;\n font-weight: 680;\n overflow: hidden;\n padding-right: 42px;\n text-overflow: ellipsis;\n transition: border-color 150ms ease, box-shadow 150ms ease;\n }\n\n select:focus,\n input:focus {\n border-color: var(--platform-color, var(--accent));\n box-shadow: 0 0 0 3px rgba(19, 111, 99, 0.12);\n }\n\n input::placeholder {\n color: #9aa3ad;\n opacity: 1;\n }\n\n .selection-meta {\n min-height: 20px;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.45;\n white-space: pre-line;\n overflow-wrap: anywhere;\n }\n\n .mode {\n height: 48px;\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 4px;\n padding: 4px;\n border: 1px solid var(--line);\n border-radius: 8px;\n background: var(--soft);\n }\n\n .mode button {\n font-size: 13px;\n }\n\n .run-area {\n display: grid;\n gap: 10px;\n padding-top: 2px;\n }\n\n .public-note {\n color: var(--muted);\n font-size: 14px;\n line-height: 1.55;\n max-width: 560px;\n }\n\n .run-button {\n min-height: 56px;\n border: 0;\n border-radius: 8px;\n background: var(--platform-color, var(--accent));\n color: var(--accent-ink);\n font-size: 16px;\n font-weight: 820;\n transition: transform 150ms ease, filter 150ms ease, opacity 150ms ease;\n }\n\n .run-button:hover:not(:disabled) {\n transform: translateY(-1px);\n filter: brightness(0.92);\n }\n\n .run-button:disabled {\n opacity: 0.48;\n cursor: not-allowed;\n transform: none;\n }\n\n .action-note {\n min-height: 20px;\n color: var(--muted);\n font-size: 13px;\n line-height: 1.45;\n white-space: pre-line;\n }\n\n .action-note.warning {\n color: var(--danger);\n font-weight: 720;\n }\n\n .summary-panel {\n display: grid;\n grid-template-rows: auto 1fr auto;\n overflow: hidden;\n }\n\n .summary-head {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 16px;\n border-bottom: 1px solid var(--line);\n }\n\n .summary-head h2 {\n font-size: 15px;\n line-height: 1.2;\n font-weight: 800;\n }\n\n .summary-list {\n margin: 0;\n padding: 8px 16px;\n display: grid;\n gap: 0;\n }\n\n .summary-row {\n display: grid;\n grid-template-columns: 128px minmax(0, 1fr);\n gap: 12px;\n padding: 10px 0;\n border-bottom: 1px solid #eef1f4;\n align-items: start;\n }\n\n .summary-row:last-child {\n border-bottom: 0;\n }\n\n .summary-row.pending {\n margin: 0 -16px;\n padding-inline: 13px 16px;\n border-left: 3px solid var(--platform-color, var(--accent));\n background: color-mix(in srgb, var(--platform-color, var(--accent)) 6%, #ffffff);\n }\n\n .summary-label {\n color: var(--muted);\n font-size: 12px;\n line-height: 1.4;\n font-weight: 760;\n }\n\n .summary-value {\n margin: 0;\n min-width: 0;\n color: var(--ink);\n font-size: 13px;\n line-height: 1.45;\n font-weight: 680;\n overflow-wrap: anywhere;\n }\n\n .summary-row.pending .summary-value {\n color: var(--platform-color, var(--accent));\n font-weight: 800;\n }\n\n .summary-status {\n min-height: 24px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0 9px;\n border: 1px solid rgba(19, 111, 99, 0.28);\n border-radius: 999px;\n background: #eff9f6;\n color: var(--accent);\n font-size: 12px;\n line-height: 1.2;\n font-weight: 800;\n white-space: nowrap;\n }\n\n .summary-status.disabled {\n border-color: rgba(181, 65, 55, 0.32);\n background: #fff1ef;\n color: var(--danger);\n }\n\n .summary-status.neutral {\n border-color: var(--line);\n background: #f6f7f8;\n color: var(--muted);\n }\n\n .summary-row.pending .summary-status {\n font-weight: 840;\n }\n\n .summary-actions {\n display: flex;\n gap: 10px;\n flex-wrap: wrap;\n padding: 15px 16px;\n border-top: 1px solid var(--line);\n background: #fbfcfd;\n }\n\n .toast {\n min-height: 20px;\n color: var(--accent);\n font-size: 13px;\n line-height: 1.45;\n }\n\n .quiet {\n color: var(--muted);\n }\n\n /* Modern visual polish layer: keeps the existing markup and behavior intact. */\n :root {\n --bg: #f4f8fb;\n --surface: rgba(255, 255, 255, 0.94);\n --soft: #f1f5f8;\n --ink: #101725;\n --muted: #617083;\n --line: #d9e2ea;\n --line-strong: #b8c7d4;\n --accent: #08796f;\n --accent-ink: #ffffff;\n --danger: #b54137;\n --radius-lg: 22px;\n --radius-md: 16px;\n --shadow-soft: 0 18px 48px rgba(16, 23, 37, 0.08);\n --shadow-panel: 0 22px 70px rgba(25, 45, 68, 0.11);\n }\n\n body {\n position: relative;\n overflow-x: hidden;\n background:\n radial-gradient(circle at 14% 0%, rgba(8, 121, 111, 0.12), transparent 34%),\n radial-gradient(circle at 86% 4%, rgba(47, 95, 159, 0.11), transparent 32%),\n linear-gradient(180deg, #fbfdff 0%, var(--bg) 55%, #eef4f8 100%);\n }\n\n body::before {\n content: \"\";\n position: fixed;\n inset: 0;\n z-index: -1;\n pointer-events: none;\n opacity: 0.42;\n background-image:\n linear-gradient(rgba(8, 121, 111, 0.07) 1px, transparent 1px),\n linear-gradient(90deg, rgba(47, 95, 159, 0.055) 1px, transparent 1px),\n linear-gradient(135deg, transparent 0 44%, rgba(8, 121, 111, 0.08) 45% 46%, transparent 47% 100%);\n background-size: 72px 72px, 72px 72px, 340px 190px;\n -webkit-mask-image: linear-gradient(180deg, #000 0, rgba(0, 0, 0, 0.72) 260px, transparent 680px);\n mask-image: linear-gradient(180deg, #000 0, rgba(0, 0, 0, 0.72) 260px, transparent 680px);\n }\n\n .topbar {\n width: min(1400px, calc(100vw - 28px));\n min-height: 88px;\n margin: 10px auto 0;\n padding: 18px 22px;\n top: 10px;\n border: 1px solid rgba(210, 222, 232, 0.9);\n border-radius: var(--radius-lg);\n background: rgba(255, 255, 255, 0.82);\n box-shadow: 0 14px 42px rgba(22, 35, 53, 0.08);\n backdrop-filter: blur(22px);\n }\n\n .brand {\n grid-template-columns: 46px minmax(0, 1fr);\n column-gap: 12px;\n align-items: center;\n }\n\n .brand::before {\n content: \"↗\";\n grid-row: 1 / 3;\n width: 44px;\n height: 44px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n background:\n radial-gradient(circle at 34% 28%, rgba(255, 255, 255, 0.88), transparent 28%),\n linear-gradient(135deg, #0b8a80, #075f62 72%);\n color: #ffffff;\n font-size: 22px;\n font-weight: 860;\n box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.25), 0 10px 24px rgba(8, 121, 111, 0.22);\n }\n\n h1 {\n font-size: clamp(24px, 2vw, 30px);\n letter-spacing: -0.03em;\n }\n\n .brand p {\n color: #66758a;\n font-size: 13px;\n }\n\n .btn,\n .pill {\n min-height: 40px;\n border-color: rgba(203, 215, 226, 0.9);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.82);\n color: #45566b;\n box-shadow: 0 1px 0 rgba(255, 255, 255, 0.75), 0 8px 20px rgba(22, 35, 53, 0.05);\n }\n\n .btn:hover {\n border-color: rgba(145, 163, 181, 0.86);\n background: #ffffff;\n box-shadow: 0 12px 24px rgba(22, 35, 53, 0.08);\n }\n\n .shell {\n width: min(1400px, calc(100vw - 36px));\n padding: 12px 0 42px;\n gap: 12px;\n }\n\n .platform-strip {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 8px;\n padding: 10px 12px;\n border: 1px solid rgba(210, 222, 232, 0.85);\n border-radius: var(--radius-lg);\n background: rgba(255, 255, 255, 0.74);\n box-shadow: var(--shadow-soft);\n backdrop-filter: blur(16px);\n }\n\n .platform-button {\n position: relative;\n min-height: 52px;\n gap: 8px;\n padding: 8px 28px 8px 8px;\n border: 1px solid rgba(207, 218, 229, 0.9);\n border-left: 1px solid rgba(207, 218, 229, 0.9);\n border-radius: var(--radius-md);\n background:\n linear-gradient(180deg, rgba(255, 255, 255, 0.98), rgba(250, 252, 254, 0.9));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.85);\n }\n\n .platform-button:hover {\n transform: translateY(-2px);\n border-color: color-mix(in srgb, var(--platform-color) 34%, var(--line));\n box-shadow: 0 14px 28px rgba(22, 35, 53, 0.08);\n }\n\n .platform-button.active {\n border-color: color-mix(in srgb, var(--platform-color) 58%, var(--line));\n background:\n radial-gradient(circle at 12% 18%, color-mix(in srgb, var(--platform-color) 16%, transparent), transparent 42%),\n linear-gradient(180deg, #ffffff, color-mix(in srgb, var(--platform-color) 5%, #ffffff));\n box-shadow: 0 16px 34px color-mix(in srgb, var(--platform-color) 18%, transparent);\n }\n\n .platform-button.active::after {\n content: \"✓\";\n position: absolute;\n top: 50%;\n right: 8px;\n width: 18px;\n height: 18px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n background: var(--platform-color);\n color: #ffffff;\n font-size: 11px;\n font-weight: 880;\n transform: translateY(-50%);\n box-shadow: 0 8px 18px color-mix(in srgb, var(--platform-color) 24%, transparent);\n }\n\n .mark {\n width: 32px;\n height: 32px;\n border-radius: 10px;\n background: color-mix(in srgb, var(--platform-color) 10%, #f7fafc);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--platform-color) 10%, transparent);\n }\n\n .platform-button.active .mark {\n background: linear-gradient(135deg, var(--platform-color), color-mix(in srgb, var(--platform-color) 72%, #052f36));\n box-shadow: 0 12px 24px color-mix(in srgb, var(--platform-color) 22%, transparent);\n }\n\n .platform-copy strong {\n color: #121a28;\n font-size: 13px;\n letter-spacing: -0.015em;\n }\n\n .platform-copy span,\n .platform-copy small {\n line-height: 1.35;\n }\n\n .switch-surface {\n grid-template-columns: minmax(0, 1.56fr) minmax(390px, 0.94fr);\n gap: 14px;\n align-items: start;\n }\n\n .switch-panel,\n .summary-panel {\n border: 1px solid rgba(210, 222, 232, 0.92);\n border-radius: var(--radius-lg);\n background: rgba(255, 255, 255, 0.86);\n box-shadow: var(--shadow-panel);\n backdrop-filter: blur(18px);\n }\n\n .switch-panel {\n gap: 18px;\n padding: 24px;\n }\n\n .panel-head {\n padding-bottom: 2px;\n }\n\n .eyebrow {\n color: #64748b;\n font-size: 12px;\n letter-spacing: 0.04em;\n }\n\n .panel-head h2 {\n color: #111827;\n font-size: clamp(31px, 3vw, 42px);\n letter-spacing: -0.045em;\n }\n\n .public-note {\n max-width: 720px;\n color: #53657a;\n font-size: 15px;\n }\n\n .public-preview {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 12px;\n margin-top: 4px;\n }\n\n .public-preview-card {\n min-width: 0;\n display: grid;\n gap: 6px;\n padding: 16px;\n border: 1px solid rgba(212, 223, 234, 0.86);\n border-radius: var(--radius-md);\n background:\n linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(247, 250, 252, 0.88));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.86);\n }\n\n .public-preview-card strong {\n color: #152033;\n font-size: 14px;\n line-height: 1.3;\n font-weight: 800;\n }\n\n .public-preview-card span {\n color: #64748b;\n font-size: 12px;\n line-height: 1.5;\n }\n\n .quick-form {\n gap: 14px;\n }\n\n .form-section {\n position: relative;\n gap: 14px 16px;\n padding: 16px;\n border: 1px solid rgba(215, 225, 235, 0.86);\n border-radius: var(--radius-md);\n background:\n linear-gradient(90deg, color-mix(in srgb, var(--platform-color, var(--accent)) 5%, transparent), transparent 32%),\n linear-gradient(180deg, rgba(255, 255, 255, 0.96), rgba(250, 252, 254, 0.88));\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.84);\n }\n\n .form-section + .form-section {\n padding-top: 16px;\n border-top: 1px solid rgba(215, 225, 235, 0.86);\n }\n\n .control-block {\n grid-template-rows: auto 46px minmax(20px, auto);\n gap: 8px;\n color: #53657a;\n font-size: 12px;\n line-height: 1.25;\n font-weight: 790;\n }\n\n .control-block > span:first-child {\n color: #293548;\n }\n\n select,\n input,\n .mode {\n min-height: 46px;\n border-color: rgba(207, 219, 230, 0.92);\n border-radius: 12px;\n background: rgba(255, 255, 255, 0.92);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.9), 0 8px 20px rgba(22, 35, 53, 0.035);\n }\n\n select,\n input {\n height: 46px;\n color: #172033;\n font-size: 14px;\n font-weight: 700;\n }\n\n select:disabled,\n input:disabled {\n background: rgba(241, 245, 249, 0.72);\n color: #7d8da1;\n box-shadow: none;\n }\n\n .mode {\n height: 46px;\n background: rgba(238, 244, 248, 0.9);\n }\n\n .mode button {\n border-radius: 9px;\n color: #5c6e84;\n font-size: 13px;\n font-weight: 800;\n }\n\n .mode button.active {\n background: #ffffff;\n color: var(--platform-color, var(--accent));\n box-shadow: 0 10px 20px rgba(22, 35, 53, 0.08);\n }\n\n .selection-meta {\n color: #758397;\n font-size: 12px;\n }\n\n .run-area {\n gap: 10px;\n padding-top: 2px;\n }\n\n .run-button {\n width: 100%;\n min-height: 58px;\n border-radius: 12px;\n background:\n linear-gradient(135deg, var(--platform-color, var(--accent)), color-mix(in srgb, var(--platform-color, var(--accent)) 76%, #042e35));\n box-shadow: 0 18px 36px color-mix(in srgb, var(--platform-color, var(--accent)) 24%, transparent);\n letter-spacing: 0.02em;\n }\n\n .run-button:hover:not(:disabled) {\n filter: none;\n box-shadow: 0 22px 42px color-mix(in srgb, var(--platform-color, var(--accent)) 30%, transparent);\n }\n\n .run-button:disabled {\n box-shadow: none;\n }\n\n .action-note {\n color: #53657a;\n font-size: 13px;\n }\n\n .toast {\n grid-column: 1 / -1;\n color: var(--platform-color, var(--accent));\n font-weight: 720;\n }\n\n .summary-panel {\n position: sticky;\n top: 112px;\n overflow: hidden;\n }\n\n .summary-head {\n padding: 24px 24px 18px;\n border-bottom-color: rgba(218, 227, 236, 0.9);\n }\n\n .summary-head h2 {\n color: #111827;\n font-size: 22px;\n letter-spacing: -0.03em;\n }\n\n .summary-list {\n padding: 4px 24px 18px;\n }\n\n .summary-row {\n grid-template-columns: 132px minmax(0, 1fr);\n gap: 14px;\n padding: 14px 0;\n border-bottom-color: rgba(229, 236, 242, 0.96);\n }\n\n .summary-label {\n color: #66758a;\n font-size: 12px;\n font-weight: 790;\n }\n\n .summary-value {\n color: #263244;\n font-size: 13px;\n font-weight: 700;\n }\n\n .summary-row.pending {\n margin: 0 -24px;\n padding-inline: 21px 24px;\n border-left: 3px solid var(--platform-color, var(--accent));\n background:\n linear-gradient(90deg, color-mix(in srgb, var(--platform-color, var(--accent)) 9%, #ffffff), rgba(255, 255, 255, 0.72));\n }\n\n .summary-status {\n min-height: 26px;\n border-color: color-mix(in srgb, var(--platform-color, var(--accent)) 26%, #d9e2ea);\n background: color-mix(in srgb, var(--platform-color, var(--accent)) 9%, #ffffff);\n color: var(--platform-color, var(--accent));\n }\n\n .summary-actions {\n padding: 16px 24px 24px;\n border-top-color: rgba(218, 227, 236, 0.9);\n background: transparent;\n }\n\n @media (max-width: 820px) {\n .platform-strip {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: 6px;\n padding: 8px 10px;\n }\n\n .platform-copy strong {\n font-size: 12px;\n }\n\n .switch-surface {\n grid-template-columns: 1fr;\n }\n\n .summary-panel {\n position: static;\n }\n\n .public-preview {\n grid-template-columns: 1fr;\n }\n }\n\n\n @media (max-width: 540px) {\n .platform-strip {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 5px;\n padding: 6px 8px;\n }\n .platform-button {\n min-height: 40px;\n gap: 5px;\n padding: 5px 7px;\n }\n .mark {\n width: 26px;\n height: 26px;\n font-size: 10px;\n }\n .platform-copy strong {\n font-size: 11px;\n }\n }\n\n @media (max-width: 680px) {\n .topbar {\n position: static;\n align-items: flex-start;\n flex-direction: column;\n padding: 18px;\n }\n\n .top-actions {\n justify-content: flex-start;\n width: 100%;\n }\n\n .boot-screen {\n width: min(100% - 28px, 680px);\n min-height: calc(100svh - 122px);\n padding-top: 22px;\n }\n\n .shell {\n width: min(100% - 28px, 1160px);\n padding-top: 18px;\n }\n\n .platform-strip,\n .quick-form,\n .form-section {\n grid-template-columns: 1fr;\n }\n\n .platform-strip {\n grid-template-columns: repeat(5, minmax(68px, 1fr));\n gap: 6px;\n padding: 8px;\n overflow-x: auto;\n }\n\n .platform-button {\n min-height: 48px;\n padding-inline: 8px 24px;\n }\n\n .platform-copy strong {\n font-size: 11px;\n }\n\n .mode-block,\n .section-wide {\n grid-column: auto;\n }\n\n .switch-panel {\n padding: 18px;\n }\n\n .summary-row {\n grid-template-columns: 1fr;\n gap: 3px;\n }\n\n .run-area {\n grid-template-columns: 1fr;\n }\n\n select {\n font-size: 13px;\n }\n }\n\n @media (prefers-reduced-motion: no-preference) {\n .platform-strip,\n .switch-surface {\n animation: rise-in 260ms ease both;\n }\n\n .switch-surface {\n animation-delay: 60ms;\n }\n\n @keyframes rise-in {\n from {\n opacity: 0;\n transform: translateY(8px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n }\n\n @keyframes loading-sweep {\n from {\n transform: translateX(-110%);\n }\n to {\n transform: translateX(260%);\n }\n }\n }\n\n @media (prefers-reduced-motion: reduce) {\n .loading-track::before {\n animation: none;\n transform: translateX(0);\n }\n }\n\n /* Operator console visual system: evidence first, authority always explicit. */\n :root {\n --bg: #07111f;\n --surface: #0d1b2b;\n --soft: #112238;\n --ink: #edf4fb;\n --muted: #98a9bc;\n --line: #263a52;\n --line-strong: #405875;\n --accent: #42c7b6;\n --accent-ink: #04110f;\n --danger: #ff8177;\n }\n\n html {\n background: var(--bg);\n color-scheme: dark;\n }\n\n body {\n background: var(--bg);\n color: var(--ink);\n }\n\n body::before {\n content: \"\";\n position: fixed;\n inset: 0;\n z-index: -1;\n pointer-events: none;\n background:\n radial-gradient(circle at 12% -20%, rgba(49, 144, 155, 0.14), transparent 34%),\n linear-gradient(180deg, #091626 0%, #07111f 58%, #081423 100%);\n }\n\n .topbar {\n min-height: 76px;\n padding: 18px max(24px, calc((100vw - 1280px) / 2));\n border-color: var(--line);\n background: rgba(7, 17, 31, 0.9);\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.16);\n border-inline: 0;\n border-radius: 0;\n }\n\n h1 {\n font-size: 23px;\n letter-spacing: -0.035em;\n }\n\n .brand p,\n .health-head p,\n .health-meta,\n .selection-meta,\n .action-note,\n .summary-label,\n .health-card__meta,\n .health-card__reason,\n .owner-decision small {\n color: var(--muted);\n }\n\n .shell {\n width: min(1280px, calc(100vw - 44px));\n gap: 20px;\n padding-top: 28px;\n }\n\n .console-nav {\n width: fit-content;\n max-width: 100%;\n padding: 3px;\n border-color: var(--line);\n border-radius: 9px;\n background: rgba(13, 27, 43, 0.82);\n box-shadow: inset 0 1px rgba(255, 255, 255, 0.035);\n }\n\n .console-nav__button {\n min-height: 40px;\n color: var(--muted);\n }\n\n .console-nav__button.active {\n background: #1a3049;\n color: #f4f8fc;\n box-shadow: inset 0 0 0 1px rgba(116, 167, 193, 0.2);\n }\n\n .health-head {\n align-items: flex-start;\n padding: 8px 2px 2px;\n }\n\n .health-head h2 {\n max-width: 780px;\n font-size: clamp(31px, 4.6vw, 52px);\n font-weight: 760;\n letter-spacing: -0.055em;\n }\n\n .health-head p { max-width: 700px; }\n\n .health-notice {\n border-color: rgba(236, 177, 69, 0.38);\n background: rgba(75, 55, 24, 0.38);\n color: #f2d193;\n }\n\n .health-summary {\n gap: 0;\n overflow: hidden;\n border-top: 1px solid var(--line);\n border-bottom: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n }\n\n .health-summary article {\n min-height: 88px;\n padding: 15px 18px;\n border: 0;\n border-right: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n }\n\n .health-summary article:last-child { border-right: 0; }\n\n .health-summary strong,\n .health-card__score strong { color: var(--accent); }\n\n .health-board-head { margin-top: 20px; }\n .health-board-head h3 { font-size: 17px; letter-spacing: -0.02em; }\n\n .health-list {\n grid-template-columns: 1fr;\n gap: 8px;\n }\n\n .health-card,\n .lifecycle-table-wrap,\n .switch-panel,\n .summary-panel {\n border-color: var(--line);\n background: rgba(13, 27, 43, 0.94);\n box-shadow: 0 16px 30px rgba(0, 0, 0, 0.16);\n }\n\n .health-card {\n min-height: 108px;\n padding: 17px 18px;\n border-radius: 10px;\n }\n\n .health-card:hover {\n border-color: #466481;\n background: #102238;\n }\n\n .health-card__title { color: var(--ink); }\n .health-card__score { border-color: var(--line); }\n\n .health-filter,\n .owner-decision__button,\n .btn {\n border-color: var(--line-strong);\n background: #112238;\n color: var(--ink);\n }\n\n .pill,\n .status-pill {\n background: #112238;\n color: #c8d7e7;\n }\n\n .health-filter.active {\n border-color: var(--accent);\n background: rgba(66, 199, 182, 0.12);\n color: #90f0df;\n }\n\n select,\n input {\n border-color: var(--line);\n background: #0a1828;\n color: var(--ink);\n }\n\n select:focus,\n input:focus {\n border-color: var(--accent);\n box-shadow: 0 0 0 3px rgba(66, 199, 182, 0.16);\n }\n\n input::placeholder { color: #71839a; }\n .mode { border-color: var(--line); background: #0a1828; }\n .mode button.active { background: #24465a; color: #eafbf8; }\n\n .summary-head,\n .summary-row,\n .form-section + .form-section {\n border-color: var(--line);\n }\n\n .summary-row.pending {\n background: rgba(66, 199, 182, 0.09);\n }\n\n .summary-value { color: var(--ink); }\n\n .run-button {\n background: var(--platform-color, var(--accent));\n color: var(--accent-ink);\n }\n\n .public-preview-card,\n .empty-state,\n .health-card__empty {\n border-color: var(--line);\n background: rgba(13, 27, 43, 0.8);\n color: var(--muted);\n }\n\n .audit-log,\n .audit-row,\n .preview-panel,\n .status-pill,\n .pill {\n border-color: var(--line);\n }\n\n @media (min-width: 900px) {\n .shell {\n grid-template-columns: 206px minmax(0, 1fr);\n align-items: start;\n }\n\n .console-nav {\n position: sticky;\n top: 98px;\n display: grid;\n width: 100%;\n gap: 3px;\n padding: 7px;\n }\n\n .console-nav__button {\n text-align: left;\n padding-inline: 13px;\n }\n\n .health-view,\n #switch-view {\n grid-column: 2;\n }\n }\n\n @media (max-width: 720px) {\n .topbar { padding-inline: 18px; }\n .shell { width: min(100% - 28px, 1280px); padding-top: 18px; }\n .health-summary { grid-template-columns: repeat(2, minmax(0, 1fr)); }\n .health-summary article:nth-child(2n) { border-right: 0; }\n .health-summary article:nth-child(n + 3) { border-top: 1px solid var(--line); }\n }\n\n /* Low-frequency operator console: boundaries before controls. */\n :root {\n --bg: #f5f7f8;\n --surface: #ffffff;\n --soft: #eef2f3;\n --ink: #172022;\n --muted: #657276;\n --line: #dbe3e4;\n --line-strong: #b7c7c8;\n --accent: #09665f;\n --accent-ink: #ffffff;\n --danger: #a6463f;\n }\n\n html {\n background: var(--bg);\n color-scheme: light;\n }\n\n body {\n background: var(--bg);\n color: var(--ink);\n }\n\n body::before {\n display: none;\n }\n\n .topbar {\n min-height: 68px;\n padding: 14px max(22px, calc((100vw - 1320px) / 2));\n border-color: var(--line);\n background: rgba(255, 255, 255, 0.94);\n box-shadow: none;\n }\n\n .brand {\n grid-template-columns: auto minmax(0, 1fr);\n align-items: baseline;\n column-gap: 13px;\n }\n\n .brand h1 {\n font-size: 21px;\n letter-spacing: -0.03em;\n }\n\n .brand p {\n padding-left: 13px;\n border-left: 1px solid var(--line);\n font-size: 12px;\n }\n\n .shell {\n width: min(1280px, calc(100vw - 44px));\n gap: 24px;\n padding: 26px 0 44px;\n }\n\n .console-nav {\n width: fit-content;\n padding: 3px;\n border-radius: 9px;\n border-color: var(--line);\n background: #ffffff;\n box-shadow: none;\n }\n\n .console-nav__button {\n min-height: 38px;\n color: var(--muted);\n font-weight: 720;\n }\n\n .console-nav__button.active {\n background: var(--accent);\n color: #ffffff;\n box-shadow: none;\n }\n\n .health-head {\n align-items: flex-start;\n padding: 4px 0 0;\n }\n\n .health-head h2,\n .plan-intro h2 {\n max-width: 780px;\n font-size: clamp(30px, 4.4vw, 48px);\n font-weight: 760;\n letter-spacing: -0.052em;\n }\n\n .health-head p,\n .plan-intro p {\n max-width: 740px;\n color: var(--muted);\n font-size: 14px;\n line-height: 1.65;\n }\n\n .health-notice {\n border-color: #eed9b0;\n background: #fffbf3;\n color: #765b25;\n }\n\n .health-summary {\n gap: 0;\n overflow: hidden;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: var(--surface);\n }\n\n .health-summary article {\n min-height: 86px;\n padding: 16px 18px;\n border: 0;\n border-right: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n }\n\n .health-summary article:last-child {\n border-right: 0;\n }\n\n .health-summary strong,\n .health-card__score strong {\n color: var(--accent);\n }\n\n .health-board-head {\n margin-top: 16px;\n }\n\n .health-board-head h3 {\n font-size: 16px;\n letter-spacing: -0.018em;\n }\n\n .health-list {\n grid-template-columns: 1fr;\n gap: 8px;\n }\n\n .health-card,\n .lifecycle-table-wrap,\n .switch-panel,\n .summary-panel {\n border-color: var(--line);\n background: var(--surface);\n box-shadow: none;\n }\n\n .health-card {\n min-height: 94px;\n padding: 16px 18px;\n border-radius: 9px;\n }\n\n .health-card:hover {\n border-color: var(--line-strong);\n background: #fcfdfd;\n }\n\n .health-card__empty,\n .empty-state {\n border-color: var(--line);\n background: #fbfcfc;\n color: var(--muted);\n }\n\n .health-card__score {\n border-color: var(--line);\n }\n\n .control-disclosure {\n border-top: 1px solid var(--line);\n padding-top: 15px;\n }\n\n .control-disclosure summary {\n cursor: pointer;\n color: var(--ink);\n font-size: 14px;\n font-weight: 740;\n list-style-position: inside;\n }\n\n .control-disclosure[open] summary {\n margin-bottom: 12px;\n }\n\n .plan-intro {\n display: grid;\n gap: 9px;\n padding: 5px 0 0;\n }\n\n .plan-intro .eyebrow {\n color: var(--accent);\n }\n\n .platform-strip {\n grid-template-columns: repeat(6, minmax(0, 1fr));\n gap: 7px;\n }\n\n .platform-button {\n min-height: 54px;\n padding: 7px 9px;\n border-left-width: 3px;\n border-color: var(--line);\n background: #ffffff;\n box-shadow: none;\n }\n\n .platform-button:hover {\n transform: none;\n border-color: var(--line-strong);\n }\n\n .platform-button.active {\n border-color: var(--platform-color);\n background: #f7fbfa;\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--platform-color) 17%, transparent);\n }\n\n .public-preview-card {\n border-color: var(--line);\n background: #fbfcfc;\n color: var(--muted);\n box-shadow: none;\n }\n\n .public-preview-card strong {\n color: var(--ink);\n }\n\n .btn,\n .health-filter,\n .owner-decision__button {\n border-color: var(--line-strong);\n background: #ffffff;\n color: var(--ink);\n }\n\n .btn:hover,\n .health-filter:hover,\n .owner-decision__button:hover {\n background: #f7fbfa;\n border-color: var(--accent);\n }\n\n .pill,\n .status-pill {\n border-color: var(--line);\n background: #f7f9f9;\n color: var(--muted);\n }\n\n .switch-surface {\n grid-template-columns: minmax(0, 1.2fr) minmax(330px, 0.8fr);\n gap: 18px;\n align-items: start;\n }\n\n .switch-panel {\n gap: 22px;\n padding: 23px;\n border-radius: 10px;\n }\n\n .panel-head {\n padding-bottom: 17px;\n border-bottom: 1px solid var(--line);\n }\n\n .panel-head h2 {\n font-size: 28px;\n letter-spacing: -0.038em;\n }\n\n .quick-form {\n gap: 17px 14px;\n }\n\n .form-step,\n .form-section-heading {\n grid-column: 1 / -1;\n display: grid;\n grid-template-columns: 28px minmax(0, 1fr);\n align-items: start;\n gap: 10px;\n margin-bottom: 1px;\n }\n\n .form-step > span,\n .form-section-heading > span {\n width: 24px;\n height: 24px;\n display: inline-grid;\n place-items: center;\n border-radius: 50%;\n background: #e7f2f0;\n color: var(--accent);\n font-size: 11px;\n font-weight: 820;\n }\n\n .form-step strong,\n .form-section-heading strong {\n display: block;\n color: var(--ink);\n font-size: 14px;\n line-height: 1.35;\n }\n\n .form-step small,\n .form-section-heading small {\n display: block;\n margin-top: 2px;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.45;\n }\n\n .form-section {\n gap: 15px 14px;\n }\n\n .form-section + .form-section {\n padding-top: 19px;\n border-color: var(--line);\n }\n\n .execution-cash-policy-section {\n gap: 12px;\n }\n\n .execution-cash-policy-heading {\n font-size: 14px;\n }\n\n .execution-cash-policy-note {\n margin-left: 38px;\n }\n\n select,\n input,\n .mode {\n border-color: var(--line);\n background: #ffffff;\n color: var(--ink);\n }\n\n select:focus,\n input:focus {\n border-color: var(--accent);\n box-shadow: 0 0 0 3px rgba(9, 102, 95, 0.13);\n }\n\n .mode button.active {\n background: #e9f4f1;\n color: var(--accent);\n box-shadow: none;\n }\n\n .mode button:disabled {\n color: #a7b1b3;\n }\n\n .run-area {\n gap: 8px;\n padding-top: 4px;\n }\n\n .run-button {\n min-height: 52px;\n background: var(--accent);\n color: #ffffff;\n border-radius: 8px;\n font-size: 14px;\n }\n\n .summary-panel {\n position: sticky;\n top: 92px;\n border-radius: 10px;\n grid-template-rows: auto auto auto auto;\n }\n\n .summary-head {\n padding: 17px 18px;\n background: #f8fbfa;\n border-color: var(--line);\n }\n\n .summary-list {\n padding: 8px 18px 12px;\n }\n\n .summary-section-title {\n margin: 10px 0 2px;\n color: var(--muted);\n font-size: 11px;\n font-weight: 800;\n letter-spacing: 0.04em;\n text-transform: uppercase;\n }\n\n .summary-row {\n grid-template-columns: 126px minmax(0, 1fr);\n padding: 10px 0;\n border-color: var(--line);\n }\n\n .summary-row.pending {\n margin: 0 -18px;\n padding-inline: 15px 18px;\n border-left-color: var(--accent);\n background: #f1f8f6;\n }\n\n .summary-value {\n color: var(--ink);\n }\n\n .plan-readiness {\n margin: 0 18px 16px;\n border: 1px solid var(--line);\n border-radius: 8px;\n overflow: hidden;\n }\n\n .plan-readiness h3 {\n margin: 0;\n padding: 11px 13px;\n border-bottom: 1px solid var(--line);\n background: #fbfcfc;\n font-size: 13px;\n }\n\n .plan-readiness ul {\n margin: 0;\n padding: 0;\n list-style: none;\n }\n\n .plan-readiness li {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 10px 13px;\n border-bottom: 1px solid var(--line);\n color: var(--muted);\n font-size: 12px;\n }\n\n .plan-readiness li:last-child {\n border-bottom: 0;\n }\n\n .plan-readiness strong {\n color: var(--muted);\n font-size: 12px;\n white-space: nowrap;\n }\n\n .plan-readiness strong[data-tone=\"ready\"] {\n color: var(--accent);\n }\n\n .plan-readiness strong[data-tone=\"warning\"] {\n color: var(--danger);\n }\n\n .summary-actions {\n display: grid;\n gap: 8px;\n padding: 14px 18px;\n border-color: var(--line);\n background: #fbfcfc;\n }\n\n .summary-actions p {\n margin: 0;\n color: var(--muted);\n font-size: 11px;\n line-height: 1.5;\n }\n\n @media (min-width: 900px) {\n .shell {\n grid-template-columns: 184px minmax(0, 1fr);\n }\n\n .console-nav {\n position: sticky;\n top: 91px;\n display: grid;\n width: 100%;\n gap: 4px;\n padding: 6px;\n }\n\n .console-nav__button {\n padding-inline: 12px;\n text-align: left;\n }\n }\n\n @media (max-width: 900px) {\n .platform-strip {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n }\n\n .summary-panel {\n position: static;\n }\n }\n\n @media (max-width: 640px) {\n .brand {\n display: grid;\n grid-template-columns: 1fr;\n gap: 2px;\n }\n\n .brand p {\n padding-left: 0;\n border-left: 0;\n }\n\n .topbar {\n align-items: flex-start;\n }\n\n .platform-strip {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .execution-cash-policy-note {\n margin-left: 0;\n }\n\n .plan-readiness {\n margin-inline: 14px;\n }\n }\n\n /* Decision-first console: surface only the low-frequency choices an owner makes. */\n :root {\n --bg: #ffffff;\n --surface: #ffffff;\n --soft: #f7f8f8;\n --ink: #182326;\n --muted: #647174;\n --line: #dbe2e2;\n --line-strong: #b8c6c5;\n --accent: #0f766e;\n --accent-soft: #edf8f6;\n --warning-soft: #fff9ed;\n --warning-line: #ead6a6;\n }\n\n body {\n background: var(--bg);\n }\n\n .topbar {\n min-height: 64px;\n border-bottom: 1px solid var(--line);\n background: rgba(255, 255, 255, 0.96);\n }\n\n .shell {\n width: min(1240px, calc(100vw - 44px));\n gap: 28px;\n padding-top: 28px;\n }\n\n .console-nav {\n border-radius: 10px;\n background: #ffffff;\n }\n\n .console-nav__button {\n min-height: 40px;\n border-radius: 7px;\n font-size: 13px;\n font-weight: 720;\n }\n\n .console-nav__button.active {\n background: var(--accent);\n }\n\n .decision-view {\n display: grid;\n gap: 18px;\n }\n\n .decision-head {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: 28px;\n padding: 4px 0 2px;\n }\n\n .decision-head h2 {\n max-width: 700px;\n margin-top: 7px;\n font-size: clamp(30px, 4vw, 46px);\n line-height: 1.08;\n letter-spacing: -0.052em;\n }\n\n .decision-head p {\n max-width: 680px;\n margin-top: 12px;\n color: var(--muted);\n font-size: 14px;\n line-height: 1.65;\n }\n\n .decision-freshness {\n display: grid;\n justify-items: end;\n gap: 8px;\n min-width: 250px;\n padding-top: 5px;\n color: var(--muted);\n font-size: 12px;\n text-align: right;\n }\n\n .decision-freshness .pill {\n max-width: 300px;\n border-color: var(--line);\n background: var(--soft);\n color: var(--muted);\n font-weight: 650;\n white-space: normal;\n text-align: right;\n }\n\n .decision-notice {\n padding: 12px 14px;\n border: 1px solid var(--warning-line);\n border-radius: 9px;\n background: var(--warning-soft);\n color: #765b25;\n font-size: 12px;\n line-height: 1.55;\n }\n\n .decision-stats {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n overflow: hidden;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: #ffffff;\n }\n\n .decision-stats article {\n display: flex;\n align-items: baseline;\n justify-content: space-between;\n gap: 12px;\n min-height: 60px;\n padding: 16px;\n border-right: 1px solid var(--line);\n }\n\n .decision-stats article:last-child {\n border-right: 0;\n }\n\n .decision-stats span {\n color: var(--muted);\n font-size: 12px;\n }\n\n .decision-stats strong {\n color: var(--accent);\n font-size: 24px;\n letter-spacing: -0.04em;\n }\n\n .decision-board {\n display: grid;\n grid-template-columns: minmax(0, 1fr);\n gap: 18px;\n align-items: start;\n }\n\n .decision-queue {\n min-width: 0;\n }\n\n .decision-board-head {\n display: flex;\n align-items: end;\n justify-content: space-between;\n gap: 16px;\n margin-bottom: 10px;\n }\n\n .decision-board-head h3,\n .decision-guide h3 {\n margin-top: 5px;\n font-size: 17px;\n letter-spacing: -0.02em;\n }\n\n .decision-board-head > span {\n color: var(--muted);\n font-size: 12px;\n }\n\n .decision-queue .health-list {\n gap: 8px;\n }\n\n .decision-queue .health-card {\n min-height: 0;\n padding: 15px 16px;\n border-radius: 9px;\n box-shadow: none;\n }\n\n .decision-queue .health-card__title {\n margin: 6px 0;\n font-size: 15px;\n }\n\n .decision-queue .health-card__reason {\n font-size: 13px;\n line-height: 1.52;\n }\n\n .decision-queue .health-card__score {\n align-self: stretch;\n min-width: 92px;\n padding-left: 15px;\n }\n\n .decision-guide {\n display: grid;\n gap: 12px;\n padding: 18px;\n border: 1px solid var(--line);\n border-radius: 10px;\n background: var(--soft);\n }\n\n .decision-guide ol {\n display: grid;\n gap: 10px;\n margin: 0;\n padding: 0;\n list-style: none;\n counter-reset: guide;\n }\n\n .decision-guide li {\n display: grid;\n grid-template-columns: 20px minmax(0, 1fr);\n gap: 8px;\n color: var(--ink);\n font-size: 12px;\n line-height: 1.55;\n }\n\n .decision-guide li::before {\n counter-increment: guide;\n content: counter(guide);\n display: grid;\n place-items: center;\n width: 20px;\n height: 20px;\n border-radius: 999px;\n background: #d9ece8;\n color: var(--accent);\n font-size: 11px;\n font-weight: 800;\n }\n\n .decision-guide p {\n padding-top: 11px;\n border-top: 1px solid var(--line);\n color: var(--muted);\n font-size: 11px;\n line-height: 1.55;\n }\n\n .decision-details {\n display: grid;\n gap: 0;\n border-top: 1px solid var(--line);\n }\n\n .decision-details .control-disclosure {\n margin: 0;\n border-bottom: 1px solid var(--line);\n border-radius: 0;\n background: transparent;\n }\n\n .decision-details .control-disclosure summary {\n min-height: 50px;\n padding: 0 2px;\n color: var(--ink);\n font-size: 13px;\n font-weight: 720;\n }\n\n .decision-details .control-disclosure[open] {\n padding-bottom: 15px;\n background: #ffffff;\n }\n\n .decision-details .health-notice {\n margin: 0 0 10px;\n }\n\n .plan-advanced {\n display: grid;\n gap: 16px;\n margin-top: 8px;\n padding-top: 16px;\n border-top: 1px solid var(--line);\n }\n\n .plan-advanced summary {\n color: var(--ink);\n font-size: 13px;\n font-weight: 760;\n }\n\n .plan-advanced > p {\n max-width: 640px;\n margin: -6px 0 0;\n color: var(--muted);\n font-size: 12px;\n line-height: 1.55;\n }\n\n .plan-advanced:not([open]) > :not(summary) {\n display: none;\n }\n\n .plan-intro h2 {\n font-size: clamp(28px, 3.4vw, 40px);\n }\n\n .switch-panel,\n .summary-panel {\n border-radius: 10px;\n box-shadow: none;\n }\n\n .summary-panel {\n top: 92px;\n }\n\n @media (min-width: 900px) {\n .decision-view,\n #switch-view,\n .health-view {\n grid-column: 2;\n }\n }\n\n @media (max-width: 900px) {\n .decision-board {\n grid-template-columns: 1fr;\n }\n\n .decision-guide {\n grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr);\n column-gap: 22px;\n }\n\n .decision-guide > :first-child,\n .decision-guide h3 {\n grid-column: 1 / -1;\n }\n\n .decision-guide p {\n align-self: end;\n }\n }\n\n @media (max-width: 640px) {\n .shell {\n width: min(100% - 28px, 1240px);\n gap: 18px;\n padding-top: 18px;\n }\n\n .decision-head {\n display: grid;\n gap: 14px;\n }\n\n .decision-freshness {\n justify-items: start;\n min-width: 0;\n text-align: left;\n }\n\n .decision-stats {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .decision-stats article:nth-child(2) {\n border-right: 0;\n }\n\n .decision-stats article:nth-child(n + 3) {\n border-top: 1px solid var(--line);\n }\n\n .decision-queue .health-card {\n display: grid;\n grid-template-columns: 1fr;\n }\n\n .decision-queue .health-card__score {\n display: flex;\n min-width: 0;\n padding: 10px 0 0;\n border-top: 1px solid var(--line);\n border-left: 0;\n }\n\n .decision-guide {\n grid-template-columns: 1fr;\n }\n\n .decision-guide > :first-child,\n .decision-guide h3 {\n grid-column: auto;\n }\n }\n"; diff --git a/web/strategy-switch-console/app_js.js b/web/strategy-switch-console/app_js.js index 2831b87..83efbfd 100644 --- a/web/strategy-switch-console/app_js.js +++ b/web/strategy-switch-console/app_js.js @@ -1,2 +1,2 @@ // Generated — JS asset -export const APP_JS = "\n\n let platformMeta = {\n binance: { label: \"Binance\", code: \"BN\", accent: \"var(--bn)\" },\n firstrade: { label: \"Firstrade\", code: \"FT\", accent: \"var(--ft)\" },\n ibkr: { label: \"IBKR\", code: \"IB\", accent: \"var(--ib)\" },\n longbridge: { label: \"LongBridge\", code: \"LB\", accent: \"var(--lb)\" },\n qmt: { label: \"QMT\", code: \"QM\", accent: \"var(--qmt)\" },\n schwab: { label: \"Schwab\", code: \"SW\", accent: \"var(--sw)\" },\n };\n\n const platformRepositories = {\n binance: \"QuantStrategyLab/BinancePlatform\",\n firstrade: \"QuantStrategyLab/FirstradePlatform\",\n ibkr: \"QuantStrategyLab/InteractiveBrokersPlatform\",\n longbridge: \"QuantStrategyLab/LongBridgePlatform\",\n qmt: \"QuantStrategyLab/QmtPlatform\",\n schwab: \"QuantStrategyLab/CharlesSchwabPlatform\",\n };\n // Alias for backward compatibility\n const defaultRepositories = platformRepositories;\n\n const defaultAccountOptions = window.__DEFAULT_ACCOUNT_OPTIONS__ || {\n binance: [{\"key\": \"default\", \"label\": \"Binance\", \"target_name\": \"default\", \"cash_currency\": \"USD\", \"supported_domains\": [\"crypto\"]}],\n firstrade: [{\"key\": \"preview\", \"label\": \"Firstrade\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\", \"service_name\": \"firstrade-quant-service\"}],\n ibkr: [{\"key\": \"preview\", \"label\": \"IBKR\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\", \"hk_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\"}],\n longbridge: [{\"key\": \"preview\", \"label\": \"LongBridge\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\", \"hk_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\"}],\n qmt: [{\"key\": \"default\", \"label\": \"QMT\", \"target_name\": \"default\", \"cash_currency\": \"CNY\", \"supported_domains\": [\"cn_equity\"], \"service_name\": \"qmt-quant-service\"}],\n schwab: [{\"key\": \"preview\", \"label\": \"Schwab\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\", \"service_name\": \"charles-schwab-quant-service\"}],\n };\n\n const domainLabels = window.__DOMAIN_LABELS__ || {\n cn_equity: { zh: \"A股\", en: \"CN A-share\" },\n crypto: { zh: \"加密\", en: \"Crypto\" },\n hk_equity: { zh: \"港股\", en: \"HK Equity\" },\n us_equity: { zh: \"美股\", en: \"US Equity\" },\n };\n\n const platformConfig = window.__PLATFORM_CONFIG__ || {\n binance: {\n dry_run_only: false,\n margin_policy: false,\n reserved_cash: false,\n income_layer: false,\n option_overlay: false,\n dca: false,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n firstrade: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"firstrade-quant-service\",\n default_execution_mode: \"live\"\n },\n ibkr: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n longbridge: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n qmt: {\n dry_run_only: true,\n margin_policy: false,\n reserved_cash: false,\n income_layer: false,\n option_overlay: false,\n dca: false,\n execution_mode: \"paper\",\n service_name: \"qmt-quant-service\",\n default_execution_mode: \"paper\"\n },\n schwab: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"charles-schwab-quant-service\",\n default_execution_mode: \"live\"\n },\n };\n const runtimeAuthorityStatus = window.__QSL_RUNTIME_AUTHORITY_STATUS__ || {};\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n const reservePolicyModes = [\"none\", \"ratio\", \"floor\", \"max\"];\n const incomeLayerModes = [\"enabled\", \"disabled\"];\n const optionOverlayModes = [\"enabled\", \"disabled\"];\n const cashOnlyExecutionModes = [\"enabled\", \"disabled\"];\n const runtimeTargetModes = [\"enabled\", \"disabled\"];\n const pluginModes = [\"none\"];\n const dcaModes = [\"fixed\", \"smart\"];\n const runtimeTargetEnabledVariable = \"RUNTIME_TARGET_ENABLED\";\n const incomeLayerEnabledVariable = \"INCOME_LAYER_ENABLED\";\n const incomeLayerStartUsdVariable = \"INCOME_LAYER_START_USD\";\n const incomeLayerMaxRatioVariable = \"INCOME_LAYER_MAX_RATIO\";\n const dcaProfileDefaults = window.__DCA_PROFILE_DEFAULTS__ || {\n nasdaq_sp500_smart_dca: { defaultMode: \"fixed\", defaultBaseInvestmentUsd: \"1000\" },\n ibit_smart_dca: { defaultMode: \"fixed\", defaultBaseInvestmentUsd: \"1000\" },\n };\n const APP_BOOT_TIMEOUT_MS = 15000;\n const platformMinReservedCashVariables = {\n longbridge: \"LONGBRIDGE_MIN_RESERVED_CASH_USD\",\n ibkr: \"IBKR_MIN_RESERVED_CASH_USD\",\n schwab: \"SCHWAB_MIN_RESERVED_CASH_USD\",\n firstrade: \"FIRSTRADE_MIN_RESERVED_CASH_USD\",\n };\n const platformReservedCashRatioVariables = {\n longbridge: \"LONGBRIDGE_RESERVED_CASH_RATIO\",\n ibkr: \"IBKR_RESERVED_CASH_RATIO\",\n schwab: \"SCHWAB_RESERVED_CASH_RATIO\",\n firstrade: \"FIRSTRADE_RESERVED_CASH_RATIO\",\n };\n\n const defaultStrategyProfiles = window.__DEFAULT_STRATEGY_PROFILES__ || [\n {\n \"profile\": \"tqqq_growth_income\",\n \"label\": \"纳斯达克增长收益\",\n \"label_en\": \"NASDAQ Growth Income\",\n \"label_zh\": \"纳斯达克增长收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"250000\",\n \"income_layer_max_ratio\": \"0.55\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.3,\n \"DGRO\": 0.2,\n \"SGOV\": 0.4,\n \"SPYI\": 0.08,\n \"QQQI\": 0.02\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"tqqq_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"250000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.03\"\n },\n {\n \"profile\": \"soxl_soxx_trend_income\",\n \"label\": \"半导体趋势收益\",\n \"label_en\": \"Semiconductor Trend Income\",\n \"label_zh\": \"半导体趋势收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"150000\",\n \"income_layer_max_ratio\": \"0.95\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.15,\n \"DGRO\": 0.1,\n \"SGOV\": 0.7,\n \"SPYI\": 0.04,\n \"QQQI\": 0.01\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_income_overlay_enabled\": true,\n \"option_income_overlay_recipe\": \"soxx_put_credit_spread_income_v1\",\n \"option_income_overlay_start_usd\": \"150000\",\n \"option_income_overlay_nav_risk_ratio\": \"0.01\"\n },\n {\n \"profile\": \"nasdaq_sp500_smart_dca\",\n \"label\": \"纳指标普定投\",\n \"label_en\": \"NASDAQ/S&P 500 DCA\",\n \"label_zh\": \"纳指标普定投\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"dca_enabled\": true,\n \"dca_default_mode\": \"fixed\",\n \"dca_default_base_investment_usd\": \"1000\"\n },\n {\n \"profile\": \"ibit_smart_dca\",\n \"label\": \"IBIT比特币定投\",\n \"label_en\": \"IBIT Bitcoin DCA\",\n \"label_zh\": \"IBIT比特币定投\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"dca_enabled\": true,\n \"dca_default_mode\": \"fixed\",\n \"dca_default_base_investment_usd\": \"1000\"\n },\n {\n \"profile\": \"global_etf_rotation\",\n \"label\": \"全球ETF轮动\",\n \"label_en\": \"Global ETF Rotation\",\n \"label_zh\": \"全球ETF轮动\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"income_layer_start_usd\": \"500000\",\n \"income_layer_max_ratio\": \"0.15\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.4,\n \"DGRO\": 0.25,\n \"SGOV\": 0.3,\n \"SPYI\": 0.05\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"500000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"russell_top50_leader_rotation\",\n \"label\": \"罗素Top50领涨\",\n \"label_en\": \"Russell Top50 Leaders\",\n \"label_zh\": \"罗素Top50领涨\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"300000\",\n \"income_layer_max_ratio\": \"0.25\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.45,\n \"DGRO\": 0.3,\n \"SGOV\": 0.25\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"300000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"tecl_xlk_trend_income\",\n \"label\": \"TECL/XLK趋势收益\",\n \"label_en\": \"TECL/XLK Trend Income\",\n \"label_zh\": \"TECL/XLK趋势收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"failed_promotion_vs_live_profiles\"\n },\n {\n \"profile\": \"us_equity_combo\",\n \"label\": \"美股核心组合\",\n \"label_en\": \"US Core Combo\",\n \"label_zh\": \"美股核心组合\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\",\n \"income_layer_start_usd\": \"300000\",\n \"income_layer_max_ratio\": \"0.25\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.25,\n \"DGRO\": 0.25,\n \"SGOV\": 0.2,\n \"SPYI\": 0.15,\n \"QQQI\": 0.15\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"300000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"us_equity_combo_core\",\n \"label\": \"美股核心组合影子\",\n \"label_en\": \"US Core Combo Shadow\",\n \"label_zh\": \"美股核心组合影子\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"us_equity_combo_leveraged\",\n \"label\": \"美股加速组合\",\n \"label_en\": \"US Alpha Combo\",\n \"label_zh\": \"美股加速组合\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"hk_global_etf_tactical_rotation\",\n \"label\": \"港股ETF战术轮动\",\n \"label_en\": \"HK ETF Tactical Rotation\",\n \"label_zh\": \"港股ETF战术轮动\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"hk_low_vol_dividend_quality_snapshot\",\n \"label\": \"港股红利质量\",\n \"label_en\": \"HK Dividend Quality\",\n \"label_zh\": \"港股红利质量\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"hk_equity_combo\",\n \"label\": \"港股恒生组合\",\n \"label_en\": \"HK Core Combo\",\n \"label_zh\": \"港股恒生组合\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"cn_industry_etf_rotation\",\n \"label\": \"A股行业ETF轮动\",\n \"label_en\": \"CN Industry ETF Rotation\",\n \"label_zh\": \"A股行业ETF轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"cn_industry_etf_rotation_aggressive\",\n \"label\": \"A股ETF轮动\",\n \"label_en\": \"CN ETF Rotation\",\n \"label_zh\": \"A股ETF轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"live_candidate\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"live_candidate_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_index_etf_tactical_rotation\",\n \"label\": \"A股宽基ETF战术轮动\",\n \"label_en\": \"CN Index ETF Tactical Rotation\",\n \"label_zh\": \"A股宽基ETF战术轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_tactical_rotation\",\n \"label\": \"创业板战术轮动\",\n \"label_en\": \"CN ChiNext Tactical Rotation\",\n \"label_zh\": \"创业板战术轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_growth_momentum_quality\",\n \"label\": \"创业板成长动量质量\",\n \"label_en\": \"CN ChiNext Growth Momentum Quality\",\n \"label_zh\": \"创业板成长动量质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_dividend_quality_snapshot\",\n \"label\": \"A股红利质量\",\n \"label_en\": \"CN Dividend Quality\",\n \"label_zh\": \"A股红利质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_growth_momentum_quality_snapshot\",\n \"label\": \"创业板成长质量快照\",\n \"label_en\": \"CN ChiNext Growth Quality Snapshot\",\n \"label_zh\": \"创业板成长质量快照\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_star_growth_momentum_quality\",\n \"label\": \"科创板成长动量质量\",\n \"label_en\": \"CN STAR Growth Momentum Quality\",\n \"label_zh\": \"科创板成长动量质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_equity_combo\",\n \"label\": \"A股进取组合\",\n \"label_en\": \"CN Alpha Combo\",\n \"label_zh\": \"A股进取组合\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"crypto_live_pool_rotation\",\n \"label\": \"加密实时池轮动\",\n \"label_en\": \"Crypto Live Pool Rotation\",\n \"label_zh\": \"加密实时池轮动\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"crypto_btc_dca\",\n \"label\": \"BTC定投\",\n \"label_en\": \"BTC DCA\",\n \"label_zh\": \"BTC定投\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\"\n },\n {\n \"profile\": \"crypto_trend_rotation\",\n \"label\": \"山寨趋势轮动\",\n \"label_en\": \"Altcoin Trend\",\n \"label_zh\": \"山寨趋势轮动\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"crypto_equity_combo\",\n \"label\": \"加密动量组合\",\n \"label_en\": \"Crypto Core Combo\",\n \"label_zh\": \"加密动量组合\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n }\n ];\n\n const localStrategyLabels = {\n tqqq_growth_income: { zh: \"纳斯达克增长收益\", en: \"NASDAQ Growth Income\" },\n soxl_soxx_trend_income: { zh: \"半导体趋势收益\", en: \"Semiconductor Trend Income\" },\n nasdaq_sp500_smart_dca: { zh: \"纳指标普定投\", en: \"NASDAQ/S&P 500 DCA\" },\n ibit_smart_dca: { zh: \"IBIT比特币定投\", en: \"IBIT Bitcoin DCA\" },\n global_etf_rotation: { zh: \"全球ETF轮动\", en: \"Global ETF Rotation\" },\n russell_top50_leader_rotation: { zh: \"罗素Top50领涨\", en: \"Russell Top50 Leaders\" },\n hk_global_etf_tactical_rotation: { zh: \"港股ETF战术轮动\", en: \"HK ETF Tactical Rotation\" },\n hk_low_vol_dividend_quality_snapshot: { zh: \"港股红利质量\", en: \"HK Dividend Quality\" },\n cn_industry_etf_rotation: { zh: \"A股行业ETF轮动\", en: \"CN Industry ETF Rotation\" },\n cn_dividend_quality_snapshot: { zh: \"A股红利质量\", en: \"CN Dividend Quality\" },\n us_equity_combo: { zh: \"美股核心组合\", en: \"US Core Combo\" },\n us_equity_combo_leveraged: { zh: \"美股加速组合\", en: \"US Alpha Combo\" },\n hk_equity_combo: { zh: \"港股恒生组合\", en: \"HK Core Combo\" },\n cn_industry_etf_rotation_aggressive: { zh: \"A股ETF轮动\", en: \"CN ETF Rotation\" },\n cn_stock_momentum_rotation: { zh: \"A股个股动量\", en: \"CN Stock Momentum\" },\n cn_equity_combo: { zh: \"A股进取组合\", en: \"CN Alpha Combo\" },\n crypto_btc_dca: { zh: \"BTC定投\", en: \"BTC DCA\" },\n crypto_trend_rotation: { zh: \"山寨趋势轮动\", en: \"Altcoin Trend\" },\n crypto_equity_combo: { zh: \"加密动量组合\", en: \"Crypto Core Combo\" },\n };\n\n const fallbackIncomeLayerDefaults = window.__INCOME_LAYER_DEFAULTS__ || {\n tqqq_growth_income: {\n startUsd: 250000,\n maxRatio: \"0.55\",\n allocations: { SCHD: 0.30, DGRO: 0.20, SGOV: 0.40, SPYI: 0.08, QQQI: 0.02 },\n },\n soxl_soxx_trend_income: {\n startUsd: 150000,\n maxRatio: \"0.95\",\n allocations: { SCHD: 0.15, DGRO: 0.10, SGOV: 0.70, SPYI: 0.04, QQQI: 0.01 },\n },\n global_etf_rotation: {\n startUsd: 500000,\n maxRatio: \"0.15\",\n allocations: { SCHD: 0.40, DGRO: 0.25, SGOV: 0.30, SPYI: 0.05 },\n },\n russell_top50_leader_rotation: {\n startUsd: 300000,\n maxRatio: \"0.25\",\n allocations: { SCHD: 0.45, DGRO: 0.30, SGOV: 0.25 },\n },\n us_equity_combo: {\n startUsd: 300000,\n maxRatio: \"0.25\",\n allocations: { SCHD: 0.25, DGRO: 0.25, SGOV: 0.20, SPYI: 0.15, QQQI: 0.15 },\n }};\n let incomeLayerDefaults = {};\n const fallbackOptionOverlayDefaults = window.__OPTION_OVERLAY_DEFAULTS__ || {\n tqqq_growth_income: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"tqqq_leaps_growth_v1\", startUsd: \"250000\", ratio: \"0.03\", ratioKind: \"budget\" },\n ],\n },\n soxl_soxx_trend_income: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"income\", recipe: \"soxx_put_credit_spread_income_v1\", startUsd: \"150000\", ratio: \"0.01\", ratioKind: \"risk\" },\n ],\n },\n global_etf_rotation: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"500000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n russell_top50_leader_rotation: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"300000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n us_equity_combo: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"300000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n us_equity_combo_leveraged: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [],\n }};\n let optionOverlayDefaults = {};\n\n const strategyDomains = [\"us_equity\", \"hk_equity\", \"cn_equity\", \"crypto\"];\n let strategyOptions = [];\n let strategyLabels = {};\n let strategyCatalog = {};\n\n\n const copy = {\n zh: {\n appTitle: \"QuantStrategyLab\",\n appSubtitle: \"已验证证据、运行许可和所有者决策。\",\n controlPlaneView: \"决策\",\n healthView: \"系统状态\",\n switchView: \"策略设置\",\n controlPlaneEyebrow: \"人工决策台 / 只读\",\n controlPlaneTitle: \"只看需要你判断的事。\",\n controlPlaneSubtitle: \"系统会持续监测与筛选;这里仅保留需要你确认的风险边界和恢复决定。\",\n controlCandidateTotal: \"候选总数\",\n controlDeferred: \"本次延期\",\n controlParked: \"已停车\",\n controlOwnerDecision: \"等待我决定\",\n controlQueueEyebrow: \"优先队列\",\n controlQueueHint: \"只显示可操作事项\",\n controlCandidateBoard: \"需要你确认\",\n decisionGuideEyebrow: \"怎么判断\",\n decisionGuideTitle: \"先确认事实,再记录意图。\",\n decisionGuideStepOne: \"确认券商账户、目标策略和当前状态是否正确。\",\n decisionGuideStepTwo: \"看证据是否新鲜,是否仍有阻断项。\",\n decisionGuideStepThree: \"只在你认可时记录“继续 / 保持暂停 / 退役”。\",\n decisionGuideBoundary: \"网页记录的是可审计意图,不会下单、改资金或自动开启实盘。\",\n controlDataReady: \"快照已同步(不代表策略可运行)\",\n controlDataStale: \"快照已过期\",\n controlDataUnavailable: \"等待可用快照\",\n controlAttentionResearchOnly: \"仅研究,未授予执行权限\",\n controlAttentionRequired: \"研究状态需要关注\",\n controlAttentionUnavailable: \"尚无已验证研究状态\",\n controlComputedAt: \"最近计算:{time}\",\n controlLoginNotice: \"登录后读取私有全局快照;没有快照时不会展示虚构信息。\",\n controlStaleNotice: \"全局快照已超过允许的新鲜度窗口;页面保留原始记录,但不把它作为当前结论。\",\n controlUnavailableNotice: \"还没有可用的全局快照;当前页面保持 fail-closed 空状态。\",\n controlUpstreamNotice: \"快照已加载,但有 {count} 个上游提示;缺失信息不会被补成推断结论。\",\n controlAttentionNotice: \"研究状态需要关注(延期 {deferred}、停车 {parked}、信号 {signals})。快照送达不等于策略验证或执行授权;系统保持不交易。\",\n controlEmptyCandidates: \"暂无可展示的候选快照。\",\n controlNoRecommendation: \"没有可用的机器建议。\",\n controlStageMeta: \"阶段:{stage} · 状态:{status} · 证据:{freshness}\",\n controlNext: \"下一步\",\n ownerDecisionTitle: \"所有者决定\",\n ownerDecisionAdminOnly: \"只有控制台管理员可以记录决定。\",\n ownerDecisionReady: \"当前证据已绑定;请人工选择后记录不可执行意图。\",\n ownerDecisionRecorded: \"已记录:{decision}(仅意图,不会启用实盘)\",\n ownerDecisionApprove: \"批准受限试运行意图\",\n ownerDecisionPark: \"保持暂停\",\n ownerDecisionRetire: \"退役候选\",\n ownerDecisionConfirm: \"这只会记录不可执行的所有者决定意图,不会下单、改资金或启用实盘。继续吗?\",\n ownerDecisionSubmitting: \"正在记录决定…\",\n ownerDecisionSuccess: \"所有者决定已记录;它不会自动启用实盘。\",\n ownerDecisionFailed: \"无法记录所有者决定\",\n reconciliationRecoveryBoard: \"恢复核验(仅已存在实盘)\",\n reconciliationRecoveryLoginNotice: \"登录后读取私有恢复核验;缺失快照不会被推断为可以恢复。\",\n reconciliationRecoveryStaleNotice: \"恢复核验已过期;请等待平台重新采样和双审,不能确认。\",\n reconciliationRecoveryUnavailableNotice: \"暂无可用恢复核验;页面保持 fail-closed 空状态。\",\n reconciliationRecoveryUpstreamNotice: \"恢复核验已加载,但有 {count} 个上游提示;请先处理阻断项。\",\n reconciliationRecoveryEmpty: \"没有待恢复的既有实盘目标。\",\n reconciliationRecoveryMeta: \"{platform} · {strategy} · 仅恢复现有执行\",\n reconciliationRecoveryDetail: \"状态:{state} · 只读样本:{samples} · 双审:{review} · 窗口截止:{lastObserved}\",\n reconciliationRecoveryBlocked: \"仍被阻断:{blockers}\",\n reconciliationRecoveryReady: \"两次对账一致且双审绑定当前摘要;需要管理员确认。\",\n reconciliationRecoveryAdminOnly: \"只有控制台管理员可以确认恢复意图。\",\n reconciliationRecoveryConfirmed: \"已记录恢复确认(仍未恢复运行)。\",\n reconciliationRecoveryConfirm: \"确认恢复既有执行意图\",\n reconciliationRecoveryConfirmPrompt: \"这只会记录与当前对账/双审摘要绑定的恢复意图;不会下单、修改资金或直接启用运行。继续吗?\",\n reconciliationRecoverySubmitting: \"正在记录确认…\",\n reconciliationRecoverySuccess: \"恢复确认已记录;私有控制器仍须重新核验后才可恢复既有执行。\",\n reconciliationRecoveryFailed: \"无法记录恢复确认\",\n reconciliationRecoveryNoOrder: \"固定边界:控制台不持有券商凭证,也不直接恢复、下单或启用实盘。\",\n executionEvidenceBoard: \"按需查看策略 × 平台证据覆盖\",\n executionEvidenceLoginNotice: \"登录后读取私有执行证据快照;缺失来源不会被推断成 paper 或 live 状态。\",\n executionEvidenceStaleNotice: \"执行证据已超过允许的新鲜度窗口;它保留为历史记录,不能作为当前运行或 P6 结论。\",\n executionEvidenceUnavailableNotice: \"还没有可用的执行证据来源;当前列表保持 fail-closed 空状态。\",\n executionEvidenceUpstreamNotice: \"执行证据已加载,但有 {count} 个上游提示;页面不会补造平台能力或运行资格。\",\n executionEvidenceEmpty: \"暂无可展示的策略 × 平台执行证据。\",\n executionEvidenceMeta: \"{platform} · 当前通道:{environment} · 来源:{source}\",\n executionEvidenceDetail: \"策略:{strategy} · 数据:{data} · 执行:{execution} · Shadow:{shadow} · Paper:{paper}\",\n executionEvidenceReceipt: \"执行回执:{outcome} · 券商确认:{confirmation}\",\n executionEvidenceReceiptMissing: \"执行回执:未采集\",\n executionEvidenceReceiptNotDue: \"未到应交易窗口\",\n executionEvidenceReceiptNoAction: \"策略未产生订单\",\n executionEvidenceReceiptRiskBlocked: \"已被风控拦截\",\n executionEvidenceReceiptSubmitted: \"已提交,尚未确认\",\n executionEvidenceReceiptAcknowledged: \"券商已确认\",\n executionEvidenceReceiptPartiallyFilled: \"部分成交\",\n executionEvidenceReceiptFilled: \"已成交\",\n executionEvidenceReceiptReconciliation: \"需要对账\",\n executionEvidenceReceiptFailed: \"执行失败,需复核\",\n executionEvidenceConfirmationNotApplicable: \"不适用\",\n executionEvidenceConfirmationNotObserved: \"未观察到确认\",\n executionEvidenceConfirmationAcknowledged: \"已确认\",\n executionEvidenceConfirmationPartiallyFilled: \"部分成交\",\n executionEvidenceConfirmationFilled: \"已成交\",\n executionEvidenceConfirmationReconciliation: \"需对账\",\n executionEvidenceNoOrder: \"固定边界:只读证据;不包含账户、订单、资金或 P6 实盘授权。\",\n executionEvidenceNext: \"下一步\",\n runtimeTargetLifecycleBoard: \"按需查看平台运行状态\",\n runtimeTargetLifecycleLoginNotice: \"登录后读取私有平台运行状态;页面不会改变启用/停用或提交订单。\",\n runtimeTargetLifecycleStaleNotice: \"平台运行状态已超过允许的新鲜度窗口;请先检查监测链路。\",\n runtimeTargetLifecycleUnavailableNotice: \"还没有可用的平台运行状态;缺失数据不会被推断成已停用或正常。\",\n runtimeTargetLifecycleUpstreamNotice: \"平台运行状态已加载,但有 {count} 个上游提示;请先核对。\",\n runtimeTargetLifecycleEmpty: \"暂无可展示的平台运行状态。\",\n runtimeTargetLifecycleMeta: \"{platform} · {state} · 通道:{mode}\",\n runtimeTargetLifecycleDetail: \"运行监测:{guard} · 执行心跳:{heartbeat}\",\n runtimeTargetLifecycleObservation: \"本次执行:{observation} · 成交证据:{evidence}\",\n runtimeTargetLifecycleNoOrder: \"固定边界:只读状态;不会启用目标、变更策略或提交订单。\",\n runtimeTargetLifecycleNext: \"当前处理\",\n runtimeTargetLifecycleStateEnabled: \"已启用\",\n runtimeTargetLifecycleStateDisabled: \"已停用\",\n runtimeTargetLifecycleCheckPass: \"通过\",\n runtimeTargetLifecycleCheckAttention: \"需处理\",\n runtimeTargetLifecycleCheckNotDue: \"未到检查时间\",\n runtimeTargetLifecycleCheckNotApplicable: \"不适用\",\n runtimeTargetLifecycleCheckUnavailable: \"不可用\",\n runtimeTargetLifecycleObservationNotDue: \"未到应交易窗口\",\n runtimeTargetLifecycleObservationMonitoringOnly: \"仅监测通过\",\n runtimeTargetLifecycleObservationNotApplicable: \"目标停用,不适用\",\n runtimeTargetLifecycleObservationAttention: \"需要复核\",\n runtimeTargetLifecycleObservationUnavailable: \"不可用\",\n runtimeTargetLifecycleOrderEvidenceNotCollected: \"未采集订单/成交回执\",\n runtimeTargetLifecycleDispositionEnabled: \"持续监控\",\n runtimeTargetLifecycleDispositionDisabled: \"无执行验证\",\n runtimeTargetLifecycleDispositionParked: \"已暂停\",\n runtimeTargetLifecycleReasonNone: \"监测正常\",\n runtimeTargetLifecycleReasonDisabled: \"按配置停用,仍持续验证\",\n runtimeTargetLifecycleReasonRuntimeGuard: \"运行监测需要复核\",\n runtimeTargetLifecycleReasonHeartbeat: \"执行心跳需要复核\",\n runtimeTargetLifecycleReasonUnavailable: \"监测数据不可用\",\n m0ResearchBoard: \"按需查看 M0 研究观察\",\n m0ResearchLoginNotice: \"登录后读取私有 M0 研究台账;缺失台账不会被补成研究结论。\",\n m0ResearchStaleNotice: \"M0 研究观察已超过有效期,仅保留为历史上下文。\",\n m0ResearchUnavailableNotice: \"还没有可用的 M0 研究台账;页面保持 fail-closed 空状态。\",\n m0ResearchUpstreamNotice: \"M0 研究台账已加载,但有 {count} 个上游提示;页面不会补造研究结论。\",\n m0ResearchEmpty: \"暂无可展示的已验证 M0 研究观察。\",\n m0ResearchMore: \"仅显示前 {count} 条研究观察;其余仍保留在只读台账中。\",\n m0ResearchMeta: \"{kind} · 查看于:{viewed}\",\n m0ResearchStateFreshness: \"状态:{state} · 新鲜度:{freshness}\",\n m0ResearchHorizons: \"主要观察期:{primary} · 适用观察期:{suitable}\",\n m0ResearchEvidence: \"置信:{confidence} · 风格:{style} · 资料摘要:{digest}\",\n m0ResearchConsistency: \"当前周期分歧:{conflict} · 历史失效漂移:{drift}\",\n m0ResearchNoOrder: \"固定边界:仅研究、无订单;不产生任何运行动作。\",\n adaptiveSelectionBoard: \"按需查看 M1 Shadow 建议\",\n adaptiveSelectionLoginNotice: \"登录后读取私有 Shadow 建议;缺失来源不会被补成策略或订单。\",\n adaptiveSelectionStaleNotice: \"此 Shadow 建议已超出新鲜度窗口,仅保留为历史上下文。\",\n adaptiveSelectionUnavailableNotice: \"还没有可用的 M1 Shadow 建议;页面保持 fail-closed 空状态。\",\n adaptiveSelectionUpstreamNotice: \"Shadow 建议带有 {count} 个上游提示;页面不会推断策略、平台或订单。\",\n adaptiveSelectionEmpty: \"暂无可展示的 Shadow 建议。\",\n adaptiveSelectionMeta: \"{source} · {domain} · 市场截至 {asOf}\",\n adaptiveSelectionRecommended: \"建议观察\",\n adaptiveSelectionNoCandidate: \"无合格 Shadow 候选\",\n adaptiveSelectionReason: \"原因:{reasons}\",\n adaptiveSelectionNoOrder: \"固定边界:仅供人工查看,零建议权重,不改策略、平台、资金、运行状态或订单。\",\n researchTaskBoard: \"按需查看研究任务队列\",\n researchTaskLoginNotice: \"登录后读取私有研究任务索引;没有来源快照时不会展示虚构任务。\",\n researchTaskStaleNotice: \"研究任务来源已超过允许的新鲜度窗口;任务保留为历史记录,但不作为当前工作指令。\",\n researchTaskUnavailableNotice: \"还没有可用的研究任务来源;当前队列保持 fail-closed 空状态。\",\n researchTaskUpstreamNotice: \"研究任务索引已加载,但有 {count} 个上游提示;页面不会补造任务。\",\n researchTaskEmpty: \"暂无可展示的已验证研究任务。\",\n researchTaskMeta: \"{type} · {domain} · 创建于:{created}\",\n researchTaskLimits: \"离线研究:最多 {runs} 次 / {seconds} 秒 · P1 {p1} · P2 {p2} · P3 {p3}\",\n researchTaskNoOrder: \"固定边界:仅研究、零仓位、无订单;不进入 P4/P5/P6。\",\n healthEyebrow: \"运行保障 / 只读\",\n healthTitle: \"先看机器结论,再决定是否介入。\",\n healthSubtitle: \"健康不是实盘许可;它只告诉你是否要复核、降级或保持观察。\",\n runtimeAuthorityTitle: \"P0–P6 控制面授权状态\",\n runtimeAuthorityNotice: \"这里的 live/default_execution_mode/live_configured 是历史配置元数据,不是当前运行、订单、资金或阶段升级授权。\",\n runtimeAuthorityAsOf: \"截至:{asOf} · P1–P3 non-live 数据获取仍需独立、精确的契约;P4–P6 尚未定义。\",\n healthTotal: \"策略总数\",\n healthHealthy: \"健康\",\n healthWatch: \"观察\",\n healthReview: \"需要复核\",\n healthCritical: \"严重\",\n healthBoard: \"策略状态\",\n bootKicker: \"初始化控制台\",\n bootTitle: \"读取策略配置\",\n bootMessage: \"正在读取登录状态、账号配置和当前状态。\",\n bootStrategy: \"正在读取策略目录。\",\n bootSession: \"正在验证登录状态。\",\n bootConfig: \"正在读取账号配置和当前状态。\",\n bootTimeout: \"加载超时,已切换到公开预览(登录后可重试)。\",\n bootPublic: \"公开预览已就绪。\",\n login: \"登录\",\n logout: \"退出\",\n signedInAs: \"已登录 {login}\",\n planEyebrow: \"运行计划 / 受限变更\",\n planTitle: \"先定义边界,再提交配置。\",\n planSubtitle: \"一次只为一个平台和账号准备一个可审计的运行计划。页面只提交配置意图;运行、订单和实盘授权由独立的证据与阶段门槛决定。\",\n planAdvancedSummary: \"更多风险边界(按需设置)\",\n planAdvancedHint: \"运行状态、插件、现金、收入层和定投通常沿用当前已验证配置;只在需要变更时展开。\",\n planScopeTitle: \"选择范围\",\n planScopeSubtitle: \"平台、账号、策略与目标环境\",\n planRuntimeTitle: \"运行保护\",\n planRuntimeSubtitle: \"停用、插件和附加层都受同一计划约束\",\n planOverlayTitle: \"策略附加层\",\n planOverlaySubtitle: \"仅使用策略已定义的默认边界\",\n planCashSubtitle: \"现金预留优先于融资;两者不能同时覆盖\",\n activePlatform: \"目标平台\",\n account: \"目标账号\",\n strategy: \"策略\",\n mode: \"运行环境\",\n live: \"实盘(需独立授权)\",\n paper: \"旧版非实盘\",\n dryRun: \"演练(不下单)\",\n liveModeUnavailable: \"当前没有同时满足策略发布、运行许可与证据门槛的实盘候选;请保持非实盘。\",\n runtimeTargetMode: \"账号运行状态\",\n runtimeSectionTitle: \"运行与插件\",\n runtimeTargetCurrent: \"沿用当前状态\",\n runtimeTargetEnabled: \"启用\",\n runtimeTargetDisabled: \"禁用\",\n runtimeTargetModeMeta: \"停用后正式运行会跳过,模拟运行和健康检查仍可用。\",\n pluginMode: \"插件状态\",\n pluginModeNone: \"不挂载旧插件\",\n pluginModeMeta: \"当前候选未绑定插件;旧插件不会自动挂载。\",\n incomeLayerMode: \"收入层状态\",\n incomeLayerSectionTitle: \"收入层\",\n incomeLayerCurrent: \"沿用当前配置\",\n incomeLayerEnabled: \"开启收入层\",\n incomeLayerDisabled: \"关闭收入层\",\n incomeLayerNotSupported: \"该策略未定义收入层\",\n incomeLayerStartUsd: \"收入层起始金额\",\n incomeLayerMaxRatio: \"收入层最高比例\",\n incomeLayerModeMeta: \"仅对已定义收入层的美股策略生效。\",\n incomeLayerDefaultMeta: \"策略默认:起始 {start},最高 {ratio}。\",\n incomeLayerAllocationMeta: \"默认分配:{allocations}。\",\n incomeLayerStartMeta: \"总资产达到该金额后启用收入层。\",\n incomeLayerRatioMeta: \"例如 0.55 表示最高 55%。\",\n optionOverlayMode: \"期权层状态\",\n optionOverlaySectionTitle: \"期权层\",\n optionOverlayCurrent: \"沿用当前配置\",\n optionOverlayEnabled: \"启用期权层\",\n optionOverlayDisabled: \"关闭期权层\",\n optionOverlayNotSupported: \"该策略未定义期权层\",\n optionOverlayModeMeta: \"启用时使用策略默认的最佳 recipe 和预算,不在这里手动调比例。\",\n optionOverlayDefaultMeta: \"{defaults}\",\n optionOverlayFamilyGrowth: \"增长\",\n optionOverlayFamilyIncome: \"收入\",\n optionOverlayBudgetRatio: \"预算 {ratio}\",\n optionOverlayRiskRatio: \"风险 {ratio}\",\n cashOnlyExecutionMode: \"允许融资\",\n cashOnlyExecutionCurrent: \"沿用当前配置\",\n cashOnlyExecutionYes: \"是\",\n cashOnlyExecutionNo: \"否\",\n cashOnlyExecutionModeMeta: \"选「否」时只按真实现金下单,不会动用 margin 购买力。\",\n cashOnlyExecutionValueYes: \"是\",\n cashOnlyExecutionValueNo: \"否\",\n currentCashOnlyExecution: \"当前允许融资\",\n pendingCashOnlyExecution: \"待提交允许融资\",\n executionCashPolicyTitle: \"资本边界\",\n executionCashPolicyNote: \"允许融资与预留现金覆盖不能同时生效;选「是」会清空预留覆盖,设预留覆盖会强制「否」。\",\n executionCashMarginBlocksReserve: \"已选允许融资;提交时会清空预留现金覆盖。\",\n executionCashReserveBlocksMargin: \"已设预留现金覆盖;提交时会强制不允许融资。\",\n qmtPlatformCashNote: \"A 股 QMT 不使用 margin / 平台预留现金;现金约束在策略参数 execution_cash_reserve_ratio 内配置。\",\n qmtDryRunOnlyNote: \"当前平台仅支持不下单演练,尚无可用的实盘执行通道。\",\n binancePlatformNote: \"Binance 平台不使用券商级收入层与期权层;相关功能由策略内部实现。\",\n invalidExecutionCashPolicyNote: \"允许融资与预留现金覆盖冲突,请只保留一种约束。\",\n dcaMode: \"定投模式\",\n dcaSectionTitle: \"定投\",\n dcaModeFixed: \"定额定投\",\n dcaModeSmart: \"智能定投\",\n dcaBaseInvestmentUsd: \"定投基准金额\",\n dcaModeMeta: \"仅定投策略可配置。\",\n dcaDefaultMeta: \"默认:{mode},基准金额 {amount}。\",\n dcaNotSupported: \"该策略不是定投策略\",\n dcaPlatformNotSupported: \"当前平台不支持定投策略\",\n currentDca: \"当前定投设置\",\n pendingDca: \"待提交定投设置\",\n dcaText: \"{mode},基准金额 {amount}\",\n minReservedCash: \"最小预留现金 ({currency})\",\n reservedCashRatio: \"预留现金比例\",\n reservedCashMode: \"预留现金策略\",\n reservePolicyCurrent: \"沿用当前配置\",\n reservePolicyNone: \"不设置平台预留现金\",\n reservePolicyRatio: \"仅按比例\",\n reservePolicyFloor: \"仅按固定金额\",\n reservePolicyMax: \"固定金额和比例取较大值\",\n reservedCashModeMeta: \"选择是否沿用、清空或覆盖平台预留现金。\",\n reservedCashNone: \"不设置\",\n reservedCashDefault: \"未配置(平台默认:0 {currency} / 0%)\",\n reservedCashMeta: \"固定金额下限,可单独设置或与比例取较大值。\",\n reservedCashRatioMeta: \"例如 0.03 表示 3%。\",\n summary: \"风险与变更摘要\",\n summaryCurrent: \"当前边界\",\n summaryPending: \"待提交变更\",\n planReadinessTitle: \"提交前核对\",\n planCheckAccount: \"账号与作用范围\",\n planCheckStrategy: \"策略与目标环境\",\n planCheckRisk: \"现金与风险边界\",\n planCheckAuthority: \"实盘执行授权\",\n planCheckWaiting: \"等待配置\",\n planCheckSelected: \"已读取\",\n planCheckValid: \"已校验\",\n planCheckFix: \"需修正\",\n planCheckNonLive: \"非实盘\",\n planCheckNoAuthority: \"未授予\",\n planAuditNote: \"提交记录会写入审计链;它不等同于订单、资金或实盘许可。\",\n copySummary: \"复制状态\",\n loginToRun: \"登录后提交计划\",\n loadingConfig: \"读取配置中\",\n configureAccounts: \"配置账号后切换\",\n runSwitch: \"提交受限变更\",\n noChanges: \"无变更\",\n readonlyNote: \"登录后才可提交受限配置。\",\n publicReadonly: \"登录后查看账号配置。\",\n loadingConfigNote: \"正在读取账号配置和当前状态。\",\n missingConfigNote: \"账号配置未加载,暂时不能执行。\",\n readyNote: \"提交的是受限配置变更;它不授予订单、资金或实盘权限。\",\n invalidStrategyNote: \"当前账号没有可执行策略,暂时不能切换。\",\n invalidReservePolicyNote: \"请为当前预留现金策略填写有效金额或比例。\",\n invalidIncomeLayerNote: \"请填写有效的收入层起始金额和最高比例。\",\n invalidOptionOverlayNote: \"当前策略未定义可启用的期权层。\",\n invalidDcaNote: \"请填写有效的定投模式和基准金额。\",\n publicOauthTitle: \"GitHub OAuth 保护\",\n publicOauthText: \"只允许白名单账号进入私有配置。\",\n publicWorkerTitle: \"Worker 端触发\",\n publicWorkerText: \"令牌保留在服务端,浏览器只提交切换意图。\",\n publicAuditTitle: \"变更可回溯\",\n publicAuditText: \"受限配置变更由 GitHub Actions 执行,便于审计和回滚。\",\n noAccount: \"没有账号选项\",\n noStrategy: \"没有支持的策略\",\n repository: \"平台仓库\",\n selectedAccount: \"账号\",\n selectedMarket: \"市场\",\n currentRuntimeTarget: \"当前账号状态\",\n pendingRuntimeTarget: \"待提交账号状态\",\n reservedCashPolicy: \"当前预留现金\",\n currentIncomeLayer: \"当前收入层\",\n pendingIncomeLayer: \"待提交收入层\",\n currentOptionOverlay: \"当前期权层\",\n pendingOptionOverlay: \"待提交期权层\",\n pendingReservedCashPolicy: \"待提交预留现金\",\n pendingMode: \"待提交模式\",\n currentPluginMode: \"当前插件范围\",\n pendingPluginMode: \"待提交插件范围\",\n unchanged: \"不变\",\n copied: \"已复制状态\",\n dispatching: \"正在触发工作流...\",\n dispatched: \"已触发工作流\",\n dispatchFailed: \"触发失败\",\n targetMeta: \"目标 {target} · 服务 {service} · 市场 {domains}\",\n strategyMeta: \"支持市场:{domains}\",\n strategyLifecycleMeta: \"当前门槛 {stage}\",\n strategyBlockedCountMeta: \"{count} 个策略未达 live 门槛\",\n strategyDefaultBlockedMeta: \"默认策略 {profile} 已阻断:{reason}\",\n usEquity: \"美股\",\n hkEquity: \"港股\",\n cnEquity: \"A股\",\n cryptoEquity: \"加密\",\n currentStrategy: \"当前策略\",\n nextStrategy: \"切换策略\",\n notRead: \"读取失败\",\n runtimeTargetOn: \"启用\",\n runtimeTargetOff: \"禁用\",\n incomeLayerDefault: \"开启,{start}起 {ratio}\",\n incomeLayerOff: \"关闭\",\n incomeLayerOn: \"开启,起始 {start},最高 {ratio}\",\n optionOverlayOff: \"关闭\",\n optionOverlayOn: \"开启\",\n optionOverlayDefaultSimple: \"开启\",\n optionOverlayDefault: \"开启,{detail}\",\n cashOnlyExecutionDefault: \"仅用现金\",\n },\n en: {\n appTitle: \"QuantStrategyLab\",\n appSubtitle: \"Verified evidence, runtime eligibility, and owner decisions.\",\n controlPlaneView: \"Decisions\",\n healthView: \"System status\",\n switchView: \"Strategy settings\",\n controlPlaneEyebrow: \"Owner decision desk / read only\",\n controlPlaneTitle: \"See only what needs your judgment.\",\n controlPlaneSubtitle: \"The system keeps monitoring and screening. This desk keeps only risk boundaries and recovery decisions for you to confirm.\",\n controlCandidateTotal: \"Candidates\",\n controlDeferred: \"Deferred now\",\n controlParked: \"Parked\",\n controlOwnerDecision: \"Needs my decision\",\n controlQueueEyebrow: \"Priority queue\",\n controlQueueHint: \"Actionable items only\",\n controlCandidateBoard: \"Needs your confirmation\",\n decisionGuideEyebrow: \"How to decide\",\n decisionGuideTitle: \"Confirm facts, then record intent.\",\n decisionGuideStepOne: \"Check that the broker account, target strategy, and current state are correct.\",\n decisionGuideStepTwo: \"Check whether evidence is fresh and whether blockers remain.\",\n decisionGuideStepThree: \"Record continue, stay parked, or retire only when you agree.\",\n decisionGuideBoundary: \"This page records auditable intent. It cannot place orders, change funds, or enable Live automatically.\",\n controlDataReady: \"Snapshot synced (not strategy readiness)\",\n controlDataStale: \"Snapshot is stale\",\n controlDataUnavailable: \"Waiting for a usable snapshot\",\n controlAttentionResearchOnly: \"Research only; no execution authority\",\n controlAttentionRequired: \"Research state needs attention\",\n controlAttentionUnavailable: \"No verified research state\",\n controlComputedAt: \"Last computed: {time}\",\n controlLoginNotice: \"Sign in to read the private global snapshot. Missing data is never invented.\",\n controlStaleNotice: \"This snapshot is outside its freshness window. The record remains visible, but is not a current conclusion.\",\n controlUnavailableNotice: \"No usable global snapshot is available yet. This page remains fail-closed and empty.\",\n controlUpstreamNotice: \"The snapshot loaded with {count} upstream notice(s); missing information is never inferred.\",\n controlAttentionNotice: \"Research needs attention ({deferred} deferred, {parked} parked, {signals} signal(s)). Snapshot delivery is not strategy validation or execution authority; the system remains no-order.\",\n controlEmptyCandidates: \"No candidate snapshot is available to display.\",\n controlNoRecommendation: \"No machine recommendation is available.\",\n controlStageMeta: \"Stage: {stage} · status: {status} · evidence: {freshness}\",\n controlNext: \"NEXT\",\n ownerDecisionTitle: \"Owner decision\",\n ownerDecisionAdminOnly: \"Only console administrators can record a decision.\",\n ownerDecisionReady: \"Current evidence is bound; choose an action to record a non-executable intent.\",\n ownerDecisionRecorded: \"Recorded: {decision} (intent only; Live remains disabled)\",\n ownerDecisionApprove: \"Approve limited-canary intent\",\n ownerDecisionPark: \"Keep parked\",\n ownerDecisionRetire: \"Retire candidate\",\n ownerDecisionConfirm: \"This records a non-executable owner intent only. It will not place orders, change funds, or enable Live. Continue?\",\n ownerDecisionSubmitting: \"Recording decision…\",\n ownerDecisionSuccess: \"Owner decision recorded; it does not enable Live automatically.\",\n ownerDecisionFailed: \"Could not record owner decision\",\n reconciliationRecoveryBoard: \"Recovery verification (existing live only)\",\n reconciliationRecoveryLoginNotice: \"Sign in to read private recovery verification. Missing snapshots never imply that recovery is allowed.\",\n reconciliationRecoveryStaleNotice: \"Recovery verification is stale. Wait for fresh platform sampling and dual review before confirming.\",\n reconciliationRecoveryUnavailableNotice: \"No usable recovery verification is available. This view remains fail-closed and empty.\",\n reconciliationRecoveryUpstreamNotice: \"Recovery verification loaded with {count} upstream notice(s). Resolve blockers first.\",\n reconciliationRecoveryEmpty: \"No existing live target is awaiting recovery.\",\n reconciliationRecoveryMeta: \"{platform} · {strategy} · existing-execution recovery only\",\n reconciliationRecoveryDetail: \"state: {state} · read-only samples: {samples} · dual review: {review} · window ends: {lastObserved}\",\n reconciliationRecoveryBlocked: \"Still blocked: {blockers}\",\n reconciliationRecoveryReady: \"Two reconciliations match and dual review is bound to the current digest; an admin confirmation is required.\",\n reconciliationRecoveryAdminOnly: \"Only console administrators can record a recovery confirmation.\",\n reconciliationRecoveryConfirmed: \"Recovery confirmation recorded (runtime is still not restored).\",\n reconciliationRecoveryConfirm: \"Record existing-execution recovery intent\",\n reconciliationRecoveryConfirmPrompt: \"This only records a recovery intent bound to the current reconciliation and dual-review digest. It will not place orders, change funds, or directly enable runtime. Continue?\",\n reconciliationRecoverySubmitting: \"Recording confirmation…\",\n reconciliationRecoverySuccess: \"Recovery confirmation recorded. A private controller must re-verify before it can restore existing execution.\",\n reconciliationRecoveryFailed: \"Could not record recovery confirmation\",\n reconciliationRecoveryNoOrder: \"Fixed boundary: the console holds no broker credentials and cannot directly recover, trade, or enable Live.\",\n executionEvidenceBoard: \"View strategy × platform evidence on demand\",\n executionEvidenceLoginNotice: \"Sign in to read private execution-evidence snapshots. Missing sources never imply a paper or live state.\",\n executionEvidenceStaleNotice: \"This execution evidence is beyond its freshness window. It remains historical context, not a current runtime or P6 conclusion.\",\n executionEvidenceUnavailableNotice: \"No usable execution-evidence source is available yet. This list remains fail-closed and empty.\",\n executionEvidenceUpstreamNotice: \"Execution evidence loaded with {count} upstream notice(s); no platform capability or runtime authority is inferred.\",\n executionEvidenceEmpty: \"No strategy × platform execution evidence is available to display.\",\n executionEvidenceMeta: \"{platform} · current lane: {environment} · source: {source}\",\n executionEvidenceDetail: \"strategy: {strategy} · data: {data} · execution: {execution} · shadow: {shadow} · paper: {paper}\",\n executionEvidenceReceipt: \"execution receipt: {outcome} · broker confirmation: {confirmation}\",\n executionEvidenceReceiptMissing: \"execution receipt: not collected\",\n executionEvidenceReceiptNotDue: \"not in a due window\",\n executionEvidenceReceiptNoAction: \"strategy produced no order\",\n executionEvidenceReceiptRiskBlocked: \"blocked by risk controls\",\n executionEvidenceReceiptSubmitted: \"submitted; not yet confirmed\",\n executionEvidenceReceiptAcknowledged: \"broker acknowledged\",\n executionEvidenceReceiptPartiallyFilled: \"partially filled\",\n executionEvidenceReceiptFilled: \"filled\",\n executionEvidenceReceiptReconciliation: \"reconciliation required\",\n executionEvidenceReceiptFailed: \"execution failed; review required\",\n executionEvidenceConfirmationNotApplicable: \"not applicable\",\n executionEvidenceConfirmationNotObserved: \"not observed\",\n executionEvidenceConfirmationAcknowledged: \"acknowledged\",\n executionEvidenceConfirmationPartiallyFilled: \"partially filled\",\n executionEvidenceConfirmationFilled: \"filled\",\n executionEvidenceConfirmationReconciliation: \"reconciliation required\",\n executionEvidenceNoOrder: \"Fixed boundary: read-only evidence; no account, order, funds, or P6 live authority.\",\n executionEvidenceNext: \"NEXT\",\n runtimeTargetLifecycleBoard: \"View platform runtime status on demand\",\n runtimeTargetLifecycleLoginNotice: \"Sign in to read private platform runtime status. This page cannot change target state or place orders.\",\n runtimeTargetLifecycleStaleNotice: \"Platform runtime status is outside its freshness window. Check the monitoring path first.\",\n runtimeTargetLifecycleUnavailableNotice: \"No usable platform runtime status is available. Missing data never implies a disabled or healthy target.\",\n runtimeTargetLifecycleUpstreamNotice: \"Platform runtime status loaded with {count} upstream notice(s); review them first.\",\n runtimeTargetLifecycleEmpty: \"No platform runtime status is available to display.\",\n runtimeTargetLifecycleMeta: \"{platform} · {state} · lane: {mode}\",\n runtimeTargetLifecycleDetail: \"runtime guard: {guard} · execution heartbeat: {heartbeat}\",\n runtimeTargetLifecycleObservation: \"execution: {observation} · order/fill evidence: {evidence}\",\n runtimeTargetLifecycleNoOrder: \"Fixed boundary: read-only status; it cannot enable a target, change a strategy, or place an order.\",\n runtimeTargetLifecycleNext: \"CURRENT DISPOSITION\",\n runtimeTargetLifecycleStateEnabled: \"enabled\",\n runtimeTargetLifecycleStateDisabled: \"disabled\",\n runtimeTargetLifecycleCheckPass: \"pass\",\n runtimeTargetLifecycleCheckAttention: \"attention\",\n runtimeTargetLifecycleCheckNotDue: \"not due\",\n runtimeTargetLifecycleCheckNotApplicable: \"not applicable\",\n runtimeTargetLifecycleCheckUnavailable: \"unavailable\",\n runtimeTargetLifecycleObservationNotDue: \"not in a due window\",\n runtimeTargetLifecycleObservationMonitoringOnly: \"monitoring only\",\n runtimeTargetLifecycleObservationNotApplicable: \"target disabled; not applicable\",\n runtimeTargetLifecycleObservationAttention: \"needs review\",\n runtimeTargetLifecycleObservationUnavailable: \"unavailable\",\n runtimeTargetLifecycleOrderEvidenceNotCollected: \"not collected\",\n runtimeTargetLifecycleDispositionEnabled: \"continue monitoring\",\n runtimeTargetLifecycleDispositionDisabled: \"disabled validation\",\n runtimeTargetLifecycleDispositionParked: \"parked\",\n runtimeTargetLifecycleReasonNone: \"monitoring normal\",\n runtimeTargetLifecycleReasonDisabled: \"intentionally disabled; validation continues\",\n runtimeTargetLifecycleReasonRuntimeGuard: \"runtime guard needs review\",\n runtimeTargetLifecycleReasonHeartbeat: \"execution heartbeat needs review\",\n runtimeTargetLifecycleReasonUnavailable: \"monitoring data unavailable\",\n m0ResearchBoard: \"View M0 research observations on demand\",\n m0ResearchLoginNotice: \"Sign in to read the private M0 research ledger. A missing ledger never implies a research conclusion.\",\n m0ResearchStaleNotice: \"These M0 research observations are beyond their validity window and remain historical context only.\",\n m0ResearchUnavailableNotice: \"No usable M0 research ledger is available yet. This view remains fail-closed and empty.\",\n m0ResearchUpstreamNotice: \"The M0 research ledger loaded with {count} upstream notice(s); no research conclusion is invented.\",\n m0ResearchEmpty: \"No verified M0 research observation is available to display.\",\n m0ResearchMore: \"Showing the first {count} research observations; the remainder stays in the read-only ledger.\",\n m0ResearchMeta: \"{kind} · viewed {viewed}\",\n m0ResearchStateFreshness: \"State: {state} · freshness: {freshness}\",\n m0ResearchHorizons: \"Primary horizon: {primary} · suitable horizons: {suitable}\",\n m0ResearchEvidence: \"Confidence: {confidence} · style: {style} · source digest: {digest}\",\n m0ResearchConsistency: \"Current horizon conflict: {conflict} · historical stale drift: {drift}\",\n m0ResearchNoOrder: \"Fixed boundary: research only, no order, and no runtime action.\",\n adaptiveSelectionBoard: \"View M1 Shadow suggestions on demand\",\n adaptiveSelectionLoginNotice: \"Sign in to read private Shadow suggestions. Missing sources never imply a strategy or order.\",\n adaptiveSelectionStaleNotice: \"This Shadow suggestion is beyond its freshness window and remains historical context only.\",\n adaptiveSelectionUnavailableNotice: \"No usable M1 Shadow suggestion is available yet. This view remains fail-closed and empty.\",\n adaptiveSelectionUpstreamNotice: \"Shadow suggestions include {count} upstream notice(s); no strategy, platform, or order is inferred.\",\n adaptiveSelectionEmpty: \"No Shadow suggestion is available to display.\",\n adaptiveSelectionMeta: \"{source} · {domain} · market as of {asOf}\",\n adaptiveSelectionRecommended: \"Observe suggestion\",\n adaptiveSelectionNoCandidate: \"No eligible Shadow candidate\",\n adaptiveSelectionReason: \"Reasons: {reasons}\",\n adaptiveSelectionNoOrder: \"Fixed boundary: human-readable only, zero proposed weight, no strategy, platform, funds, runtime, or order change.\",\n researchTaskBoard: \"View research queue on demand\",\n researchTaskLoginNotice: \"Sign in to read the private research task index. Missing source snapshots never imply a task.\",\n researchTaskStaleNotice: \"The research task source is beyond its freshness window. It remains historical context, not a current instruction.\",\n researchTaskUnavailableNotice: \"No research task source is available yet. The queue remains fail-closed and empty.\",\n researchTaskUpstreamNotice: \"The research task index loaded with {count} upstream notices; no task is inferred from missing data.\",\n researchTaskEmpty: \"No verified research task is available to display.\",\n researchTaskMeta: \"{type} · {domain} · created {created}\",\n researchTaskLimits: \"Offline research: up to {runs} run(s) / {seconds}s · P1 {p1} · P2 {p2} · P3 {p3}\",\n researchTaskNoOrder: \"Fixed boundary: research only, zero size, no order; never P4/P5/P6.\",\n healthEyebrow: \"Runtime guard / read only\",\n healthTitle: \"Read the machine conclusion before deciding to intervene.\",\n healthSubtitle: \"Health is not live approval. It only tells you whether to review, reduce, or keep watching.\",\n runtimeAuthorityTitle: \"P0–P6 control-plane authority\",\n runtimeAuthorityNotice: \"live/default_execution_mode/live_configured are legacy configuration metadata, not authorization for current runtime, orders, funds, or stage changes.\",\n runtimeAuthorityAsOf: \"As of {asOf} · P1–P3 non-live data acquisition still needs its own precise contract; P4–P6 are undefined.\",\n healthTotal: \"Strategies\",\n healthHealthy: \"Healthy\",\n healthWatch: \"Watch\",\n healthReview: \"Review\",\n healthCritical: \"Critical\",\n healthBoard: \"Strategy status\",\n bootKicker: \"Starting console\",\n bootTitle: \"Loading strategy config\",\n bootMessage: \"Reading session, account config, and current state.\",\n bootStrategy: \"Reading strategy catalog.\",\n bootSession: \"Checking sign-in status.\",\n bootConfig: \"Reading account config and current state.\",\n bootTimeout: \"Loading timed out; switched to public preview. Retry after signing in.\",\n bootPublic: \"Public preview is ready.\",\n login: \"Sign in\",\n logout: \"Sign out\",\n signedInAs: \"Signed in as {login}\",\n planEyebrow: \"Run plan / bounded change\",\n planTitle: \"Set the boundary before submitting configuration.\",\n planSubtitle: \"Prepare one auditable plan for one platform and account. This page submits configuration intent only; runtime, order, and live authority remain governed by independent evidence and lifecycle gates.\",\n planAdvancedSummary: \"More risk boundaries (only when needed)\",\n planAdvancedHint: \"Runtime, plugins, cash, overlays, and DCA normally retain the current verified setup. Expand only when changing one.\",\n planScopeTitle: \"Choose scope\",\n planScopeSubtitle: \"Platform, account, strategy, and target environment\",\n planRuntimeTitle: \"Runtime protection\",\n planRuntimeSubtitle: \"Disable state, plugins, and overlays share one plan boundary\",\n planOverlayTitle: \"Strategy overlays\",\n planOverlaySubtitle: \"Only strategy-defined default guardrails are available\",\n planCashSubtitle: \"Cash reserve takes precedence over margin; they cannot override together\",\n activePlatform: \"Target platform\",\n account: \"Target account\",\n strategy: \"Strategy\",\n mode: \"Target environment\",\n live: \"Live (independent approval required)\",\n paper: \"Legacy non-live\",\n dryRun: \"Dry run (no orders)\",\n liveModeUnavailable: \"No candidate currently meets the strategy-release, runtime-eligibility, and evidence gates for Live. Keep this plan non-live.\",\n runtimeTargetMode: \"Account status\",\n runtimeSectionTitle: \"Runtime and plugins\",\n runtimeTargetCurrent: \"Keep current status\",\n runtimeTargetEnabled: \"Enabled\",\n runtimeTargetDisabled: \"Disabled\",\n runtimeTargetModeMeta: \"Disabled accounts skip live runs; dry runs and health checks still work.\",\n pluginMode: \"Plugin status\",\n pluginModeNone: \"Do not mount legacy plugins\",\n pluginModeMeta: \"The current candidate has no bound plugin; legacy plugins are not auto-mounted.\",\n incomeLayerMode: \"Income layer\",\n incomeLayerSectionTitle: \"Income layer\",\n incomeLayerCurrent: \"Keep current config\",\n incomeLayerEnabled: \"Enable income layer\",\n incomeLayerDisabled: \"Disable income layer\",\n incomeLayerNotSupported: \"No income layer for this strategy\",\n incomeLayerStartUsd: \"Income layer start amount\",\n incomeLayerMaxRatio: \"Income layer max ratio\",\n incomeLayerModeMeta: \"Only applies to US equity strategies with an income layer.\",\n incomeLayerDefaultMeta: \"Strategy default: starts at {start}, max {ratio}.\",\n incomeLayerAllocationMeta: \"Default allocation: {allocations}.\",\n incomeLayerStartMeta: \"Income layer activates after total assets reach this amount.\",\n incomeLayerRatioMeta: \"Use 0.55 for a 55% cap.\",\n optionOverlayMode: \"Option layer\",\n optionOverlaySectionTitle: \"Option layer\",\n optionOverlayCurrent: \"Keep current config\",\n optionOverlayEnabled: \"Enable option layer\",\n optionOverlayDisabled: \"Disable option layer\",\n optionOverlayNotSupported: \"No option layer for this strategy\",\n optionOverlayModeMeta: \"Enabled mode uses the strategy's default recipe and budget; ratios are not edited here.\",\n optionOverlayDefaultMeta: \"{defaults}\",\n optionOverlayFamilyGrowth: \"Growth\",\n optionOverlayFamilyIncome: \"Income\",\n optionOverlayBudgetRatio: \"budget {ratio}\",\n optionOverlayRiskRatio: \"risk {ratio}\",\n cashOnlyExecutionMode: \"Allow margin\",\n cashOnlyExecutionCurrent: \"Keep current config\",\n cashOnlyExecutionYes: \"Yes\",\n cashOnlyExecutionNo: \"No\",\n cashOnlyExecutionModeMeta: \"Choose No to use available cash only and avoid margin buying power.\",\n cashOnlyExecutionValueYes: \"Yes\",\n cashOnlyExecutionValueNo: \"No\",\n currentCashOnlyExecution: \"Current allow margin\",\n pendingCashOnlyExecution: \"Pending allow margin\",\n executionCashPolicyTitle: \"Capital boundary\",\n executionCashPolicyNote: \"Allow margin and reserve-cash overrides cannot both apply. Yes clears reserve overrides; reserve overrides force No.\",\n executionCashMarginBlocksReserve: \"Allow margin is selected; submitting will clear reserve-cash overrides.\",\n executionCashReserveBlocksMargin: \"Reserve-cash override is active; submitting will force allow margin to No.\",\n qmtPlatformCashNote: \"QMT A-share does not use margin or platform reserve cash; cash constraints live in strategy execution_cash_reserve_ratio.\",\n qmtDryRunOnlyNote: \"This platform supports no-order dry runs only; no live execution route is configured.\",\n binancePlatformNote: \"Binance does not use broker-level income/option layers; features are implemented inside strategies.\",\n invalidExecutionCashPolicyNote: \"Allow margin and reserve-cash overrides conflict. Keep only one constraint.\",\n dcaMode: \"DCA mode\",\n dcaSectionTitle: \"DCA\",\n dcaModeFixed: \"Fixed DCA\",\n dcaModeSmart: \"Smart DCA\",\n dcaBaseInvestmentUsd: \"Base DCA amount\",\n dcaModeMeta: \"Only DCA strategies can use this.\",\n dcaDefaultMeta: \"Default: {mode}, base amount {amount}.\",\n dcaNotSupported: \"This is not a DCA strategy\",\n dcaPlatformNotSupported: \"DCA not supported on this platform\",\n currentDca: \"Current DCA settings\",\n pendingDca: \"Pending DCA settings\",\n dcaText: \"{mode}, base amount {amount}\",\n minReservedCash: \"Minimum reserved cash ({currency})\",\n reservedCashRatio: \"Reserved cash ratio\",\n reservedCashMode: \"Reserved cash policy\",\n reservePolicyCurrent: \"Keep current config\",\n reservePolicyNone: \"No platform reserve\",\n reservePolicyRatio: \"Ratio only\",\n reservePolicyFloor: \"Fixed amount only\",\n reservePolicyMax: \"Max of amount and ratio\",\n reservedCashModeMeta: \"Choose whether to keep, clear, or override platform reserved cash.\",\n reservedCashNone: \"None\",\n reservedCashDefault: \"Not configured (platform default: 0 {currency} / 0%)\",\n reservedCashMeta: \"Fixed cash floor. Use alone or with a ratio.\",\n reservedCashRatioMeta: \"Use 0.03 for 3%.\",\n summary: \"Risk and change summary\",\n summaryCurrent: \"Current boundary\",\n summaryPending: \"Pending change\",\n planReadinessTitle: \"Before submission\",\n planCheckAccount: \"Account and scope\",\n planCheckStrategy: \"Strategy and environment\",\n planCheckRisk: \"Cash and risk boundary\",\n planCheckAuthority: \"Live execution authority\",\n planCheckWaiting: \"Waiting\",\n planCheckSelected: \"Read\",\n planCheckValid: \"Checked\",\n planCheckFix: \"Needs correction\",\n planCheckNonLive: \"Non-live\",\n planCheckNoAuthority: \"Not granted\",\n planAuditNote: \"A submission creates an audit record; it is not an order, funding, or live permission.\",\n copySummary: \"Copy state\",\n loginToRun: \"Sign in to submit a plan\",\n loadingConfig: \"Loading config\",\n configureAccounts: \"Configure accounts\",\n runSwitch: \"Submit bounded change\",\n noChanges: \"No changes\",\n readonlyNote: \"Sign in to submit a bounded configuration.\",\n publicReadonly: \"Sign in to view account config.\",\n loadingConfigNote: \"Reading account config and current state.\",\n missingConfigNote: \"Account config is not loaded, so switching is disabled.\",\n readyNote: \"This submits a bounded configuration change; it grants no order, funding, or live authority.\",\n invalidStrategyNote: \"This account has no runnable strategy, so switching is disabled.\",\n invalidReservePolicyNote: \"Enter a valid amount or ratio for the selected reserved-cash policy.\",\n invalidIncomeLayerNote: \"Enter a valid income layer start amount and max ratio.\",\n invalidOptionOverlayNote: \"This strategy does not define an option layer to enable.\",\n invalidDcaNote: \"Enter a valid DCA mode and base amount.\",\n publicOauthTitle: \"Protected by GitHub OAuth\",\n publicOauthText: \"Only allowlisted accounts can open private config.\",\n publicWorkerTitle: \"Worker-side dispatch\",\n publicWorkerText: \"Tokens stay server-side; the browser submits intent only.\",\n publicAuditTitle: \"Traceable changes\",\n publicAuditText: \"Bounded configuration changes run through GitHub Actions for audit and rollback.\",\n noAccount: \"No accounts\",\n noStrategy: \"No supported strategies\",\n repository: \"Repository\",\n selectedAccount: \"Account\",\n selectedMarket: \"Market\",\n currentRuntimeTarget: \"Current account status\",\n pendingRuntimeTarget: \"Pending account status\",\n reservedCashPolicy: \"Current reserved cash\",\n currentIncomeLayer: \"Current income layer\",\n pendingIncomeLayer: \"Pending income layer\",\n currentOptionOverlay: \"Current option layer\",\n pendingOptionOverlay: \"Pending option layer\",\n pendingReservedCashPolicy: \"Pending reserved cash\",\n pendingMode: \"Pending mode\",\n currentPluginMode: \"Current plugin scope\",\n pendingPluginMode: \"Pending plugin scope\",\n unchanged: \"Unchanged\",\n copied: \"State copied\",\n dispatching: \"Dispatching workflow...\",\n dispatched: \"Workflow dispatched\",\n dispatchFailed: \"Dispatch failed\",\n targetMeta: \"target {target} · service {service} · market {domains}\",\n strategyMeta: \"Markets: {domains}\",\n strategyLifecycleMeta: \"current gate {stage}\",\n strategyBlockedCountMeta: \"{count} strategies are blocked from live\",\n strategyDefaultBlockedMeta: \"Default strategy {profile} is blocked: {reason}\",\n usEquity: \"US equity\",\n hkEquity: \"HK equity\",\n cnEquity: \"CN A-share\",\n cryptoEquity: \"Crypto\",\n currentStrategy: \"Current strategy\",\n nextStrategy: \"Switch strategy\",\n notRead: \"Not read\",\n runtimeTargetOn: \"Enabled\",\n runtimeTargetOff: \"Disabled\",\n incomeLayerDefault: \"Enabled, {start} start, {ratio} max\",\n incomeLayerOff: \"Disabled\",\n incomeLayerOn: \"Enabled, starts at {start}, max {ratio}\",\n optionOverlayOff: \"Disabled\",\n optionOverlayOn: \"Enabled\",\n optionOverlayDefaultSimple: \"Strategy default: enabled\",\n optionOverlayDefault: \"Enabled, {detail}\",\n cashOnlyExecutionDefault: \"Cash only\",\n },\n };\n\n const storedLang = localStorage.getItem(\"qsl-switch-lang\");\n const initialLang = storedLang === \"zh\" || storedLang === \"en\"\n ? storedLang\n : ((navigator.language || \"\").toLowerCase().startsWith(\"zh\") ? \"zh\" : \"en\");\n const clone = (value) => JSON.parse(JSON.stringify(value));\n const defaultReserveForm = () => ({\n reservePolicyMode: \"current\",\n minReservedCashUsd: \"\",\n reservedCashRatio: \"\",\n reservedCashTouched: false,\n incomeLayerMode: \"current\",\n incomeLayerStartUsd: \"\",\n incomeLayerMaxRatio: \"\",\n incomeLayerTouched: false,\n optionOverlayMode: \"current\",\n optionOverlayTouched: false,\n cashOnlyExecutionMode: \"current\",\n cashOnlyExecutionTouched: false,\n runtimeTargetMode: \"current\",\n runtimeTargetTouched: false,\n dcaMode: \"fixed\",\n dcaBaseInvestmentUsd: \"\",\n dcaTouched: false,\n strategyTouched: false,\n });\n\n const state = {\n selected: \"longbridge\",\n lang: initialLang,\n view: \"control\",\n appReady: false,\n bootMessageKey: \"bootMessage\",\n auth: { available: false, allowed: false, admin: false, login: null },\n accountOptions: clone(defaultAccountOptions),\n currentStrategies: {},\n health: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 },\n strategies: [],\n },\n filter: \"all\",\n },\n controlPlane: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 },\n attention: { status: \"unavailable\", reason_codes: [\"control_plane_source_unavailable\"] },\n candidates: [],\n policy: { p4_p5_automation: \"not_configured\", p6_owner_decision_required: true },\n errors: [],\n },\n },\n ownerDecisions: {\n data_status: \"unavailable\",\n candidates: [],\n errors: [],\n },\n reconciliationRecovery: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 },\n recoveries: [],\n policy: { human_confirmation_required: true, current_evidence_required: true, no_order: true, execution_authority_granted: false },\n errors: [],\n },\n submittingRecoveryId: null,\n },\n m0Research: {\n payload: {\n data_status: \"unavailable\",\n viewed_at: null,\n summary: {\n subject_count: 0,\n observation_count: 0,\n fresh_observation_count: 0,\n stale_observation_count: 0,\n unknown_observation_count: 0,\n horizon_conflict_count: 0,\n historical_stale_horizon_drift_count: 0,\n },\n subjects: [],\n policy: { authority: \"research_only\", no_order: true, permitted_next_step: \"research_validation_only\" },\n errors: [],\n },\n },\n adaptiveSelection: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 },\n selections: [],\n policy: { authority: \"shadow_only\", no_order: true, execution_authority_granted: false },\n errors: [],\n },\n },\n executionEvidence: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 },\n deployments: [],\n policy: { execution_evidence_read_only: true, p6_owner_decision_required: true, limited_live_canary_active: false },\n errors: [],\n },\n },\n runtimeTargetLifecycle: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 },\n targets: [],\n policy: { lifecycle_status_read_only: true, no_order: true },\n errors: [],\n },\n },\n researchTasks: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { task_count: 0 },\n tasks: [],\n policy: { research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false },\n errors: [],\n },\n },\n configSource: \"default\",\n repositories: clone(defaultRepositories),\n forms: {\n longbridge: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n ibkr: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n schwab: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n firstrade: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n qmt: { accountKey: \"preview\", strategy: \"\", executionMode: \"paper\", pluginMode: \"none\", ...defaultReserveForm() },\n binance: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\" },\n },\n };\n\n const el = (id) => document.getElementById(id);\n const t = (key) => copy[state.lang][key] || copy.en[key] || key;\n let toastTimer = null;\n\n function showToast(message, { duration = 4000 } = {}) {\n const node = el(\"toast\");\n if (toastTimer) {\n window.clearTimeout(toastTimer);\n toastTimer = null;\n }\n node.textContent = message || \"\";\n if (message && duration > 0) {\n toastTimer = window.setTimeout(() => {\n node.textContent = \"\";\n toastTimer = null;\n }, duration);\n }\n }\n\n async function fetchWithTimeout(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {\n const controller = new AbortController();\n const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (error) {\n if (error?.name === \"AbortError\") {\n throw new Error(\"request timeout\");\n }\n throw error;\n } finally {\n window.clearTimeout(timeoutId);\n }\n }\n\n async function requestJson(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {\n const response = await fetchWithTimeout(url, { ...init, cache: \"no-store\" }, timeoutMs);\n if (!response.ok) throw new Error(\"request failed\");\n return response.json();\n }\n\n function isRequestTimeoutError(error) {\n return String(error?.message || \"\").toLowerCase() === \"request timeout\";\n }\n\n function optionsFor(platform) {\n return state.accountOptions[platform] && state.accountOptions[platform].length\n ? state.accountOptions[platform]\n : defaultAccountOptions[platform];\n }\n\n function selectedAccount(platform = state.selected) {\n const options = optionsFor(platform);\n const form = state.forms[platform];\n return options.find((option) => option.key === form.accountKey) || options[0];\n }\n\n function hasPrivateConfig() {\n return Boolean(state.auth.allowed && state.configSource === \"private\");\n }\n\n function cleanStrategyProfile(value) {\n const profile = String(value || \"\").trim();\n return /^[a-z0-9._=-]{1,120}$/.test(profile) ? profile : \"\";\n }\n\n function cleanStrategyDomain(value) {\n const domain = String(value || \"\").trim();\n return strategyDomains.includes(domain) ? domain : \"\";\n }\n\n function domainLabel(domain) {\n const entry = domainLabels[domain];\n if (entry) return state.lang === \"zh\" ? entry.zh : entry.en;\n return domain;\n }\n\n function platformSupportsMarginPolicy(platform = state.selected) {\n return platformConfig[platform]?.margin_policy ?? true;\n }\n\n function platformSupportsReservedCashPolicy(platform = state.selected) {\n return platformConfig[platform]?.reserved_cash ?? true;\n }\n\n function platformDryRunOnly(platform = state.selected) {\n return platformConfig[platform]?.dry_run_only ?? false;\n }\n\n function supportedExecutionModesForPlatform(platform = state.selected) {\n const modes = platformConfig[platform]?.supported_execution_modes;\n return Array.isArray(modes) ? modes.filter((mode) => mode === \"live\" || mode === \"dry_run\") : [];\n }\n\n function allowMarginExplicitlySelected(form) {\n return normalizeCashOnlyExecutionMode(form?.cashOnlyExecutionMode) === \"disabled\";\n }\n\n function reserveCashOverrideActive(form) {\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n return mode === \"ratio\" || mode === \"floor\" || mode === \"max\";\n }\n\n function executionCashPolicyConflict(form) {\n return allowMarginExplicitlySelected(form) && reserveCashOverrideActive(form);\n }\n\n function reconcileExecutionCashPolicy(form, changed) {\n if (!form) return;\n if (changed === \"margin\" && allowMarginExplicitlySelected(form)) {\n if (form.reservePolicyMode !== \"none\" && (form.minReservedCashUsd || form.reservedCashRatio)) {\n form._prevReserve = {\n mode: form.reservePolicyMode,\n floor: form.minReservedCashUsd,\n ratio: form.reservedCashRatio,\n };\n }\n form.reservePolicyMode = \"none\";\n form.reservedCashTouched = true;\n } else if (changed === \"reserve\" && reserveCashOverrideActive(form)) {\n form.cashOnlyExecutionMode = \"enabled\";\n form.cashOnlyExecutionTouched = true;\n }\n }\n\n function restoreReserveAfterMarginDisabled(form) {\n if (!form || allowMarginExplicitlySelected(form) || !form._prevReserve) return;\n form.reservePolicyMode = form._prevReserve.mode;\n form.minReservedCashUsd = form._prevReserve.floor;\n form.reservedCashRatio = form._prevReserve.ratio;\n form.reservedCashTouched = true;\n delete form._prevReserve;\n }\n\n function strategyDomain(profile) {\n return strategyCatalog[profile]?.domain || \"\";\n }\n\n function selectedCashCurrency(platform = state.selected, account = selectedAccount(platform)) {\n const configured = String(account?.cash_currency || \"\").trim().toUpperCase();\n if (configured === \"USD\" || configured === \"HKD\" || configured === \"CNY\") return configured;\n const domain = strategyDomain(state.forms[platform]?.strategy);\n if (domain === \"hk_equity\") return \"HKD\";\n if (domain === \"cn_equity\") return \"CNY\";\n return \"USD\";\n }\n\n function applyStrategyProfiles(rawProfiles) {\n const profiles = Array.isArray(rawProfiles) && rawProfiles.length\n ? rawProfiles\n : defaultStrategyProfiles;\n const nextOptions = [];\n const nextLabels = {};\n const nextCatalog = {};\n const nextIncomeLayerDefaults = {};\n const nextOptionOverlayDefaults = {};\n for (const item of profiles) {\n const profile = cleanStrategyProfile(item?.profile || item?.strategy_profile);\n if (!profile || nextOptions.includes(profile)) continue;\n const domain = cleanStrategyDomain(item?.domain || \"us_equity\");\n if (!domain) continue;\n nextOptions.push(profile);\n nextLabels[profile] = strategyLabelSet(profile, item);\n nextCatalog[profile] = {\n profile,\n label: nextLabels[profile].en || nextLabels[profile].zh || profile,\n label_en: nextLabels[profile].en || \"\",\n label_zh: nextLabels[profile].zh || \"\",\n domain,\n runtime_enabled: cleanOptionalBoolean(item?.runtime_enabled ?? false) === true,\n };\n const lifecycleStage = normalizeLifecycleStage(item?.lifecycle_stage ?? item?.lifecycleStage);\n if (lifecycleStage) nextCatalog[profile].lifecycle_stage = lifecycleStage;\n const allowedExecutionModes = normalizeAllowedExecutionModes(item?.allowed_execution_modes);\n if (allowedExecutionModes.length) nextCatalog[profile].allowed_execution_modes = allowedExecutionModes;\n const canSwitchLive = cleanOptionalBoolean(item?.can_switch_live);\n if (canSwitchLive !== null) nextCatalog[profile].can_switch_live = canSwitchLive;\n const blockedLiveReason = cleanDisplayText(item?.blocked_live_reason);\n if (blockedLiveReason) nextCatalog[profile].blocked_live_reason = blockedLiveReason;\n const latestEvidenceStatus = cleanDisplayText(item?.latest_evidence_status);\n if (latestEvidenceStatus) nextCatalog[profile].latest_evidence_status = latestEvidenceStatus;\n const pluginGateStatus = cleanDisplayText(item?.plugin_gate_status);\n if (pluginGateStatus) nextCatalog[profile].plugin_gate_status = pluginGateStatus;\n const dcaDefaults = dcaProfileDefaults[profile] || null;\n if (dcaDefaults || item?.dca_enabled === true) {\n nextCatalog[profile].dca_enabled = true;\n nextCatalog[profile].dca_default_mode = normalizeDcaMode(\n item?.dca_default_mode || item?.default_dca_mode || dcaDefaults?.defaultMode || \"fixed\",\n );\n nextCatalog[profile].dca_default_base_investment_usd = cleanDisplayPositiveNumber(\n item?.dca_default_base_investment_usd ||\n item?.default_dca_base_investment_usd ||\n dcaDefaults?.defaultBaseInvestmentUsd ||\n \"1000\",\n ) || \"1000\";\n }\n const profileIncomeDefaults = incomeLayerDefaultsFromProfileItem(item);\n const incomeDefaults = profileIncomeDefaults === false\n ? null\n : (profileIncomeDefaults || fallbackIncomeLayerDefaults[profile] || null);\n if (incomeDefaults) {\n nextIncomeLayerDefaults[profile] = incomeDefaults;\n nextCatalog[profile].income_layer_enabled = true;\n nextCatalog[profile].income_layer_start_usd = String(incomeDefaults.startUsd);\n nextCatalog[profile].income_layer_max_ratio = incomeDefaults.maxRatio;\n nextCatalog[profile].income_layer_allocations = incomeDefaults.allocations;\n }\n const profileOptionDefaults = optionOverlayDefaultsFromProfileItem(item);\n const optionDefaults = profileOptionDefaults === false\n ? null\n : (profileOptionDefaults || fallbackOptionOverlayDefaults[profile] || null);\n if (optionDefaults) {\n nextOptionOverlayDefaults[profile] = optionDefaults;\n nextCatalog[profile].option_overlay_enabled = true;\n nextCatalog[profile].option_overlay_live_gate = optionDefaults.liveGate || \"\";\n nextCatalog[profile].option_overlay_live_status = optionDefaults.liveStatus || \"\";\n }\n }\n if (!nextOptions.length && profiles !== defaultStrategyProfiles) return applyStrategyProfiles(defaultStrategyProfiles);\n strategyOptions = nextOptions;\n strategyLabels = nextLabels;\n strategyCatalog = nextCatalog;\n incomeLayerDefaults = nextIncomeLayerDefaults;\n optionOverlayDefaults = nextOptionOverlayDefaults;\n }\n\n function incomeLayerDefaultsFromProfileItem(item) {\n const enabled = cleanOptionalBoolean(item?.income_layer_enabled);\n const hasConfig = enabled !== null ||\n item?.income_layer_start_usd !== undefined ||\n item?.income_layer_max_ratio !== undefined ||\n item?.income_layer_allocations !== undefined;\n if (!hasConfig) return null;\n if (enabled === false) return false;\n const startUsd = cleanDisplayNumber(item?.income_layer_start_usd);\n const maxRatio = cleanDisplayRatio(item?.income_layer_max_ratio);\n const allocations = cleanIncomeLayerAllocations(item?.income_layer_allocations);\n if (!startUsd || !maxRatio || !allocations) return null;\n return { startUsd, maxRatio, allocations };\n }\n\n function cleanIncomeLayerAllocations(value) {\n if (!value || Array.isArray(value) || typeof value !== \"object\") return null;\n const allocations = {};\n let total = 0;\n for (const [rawSymbol, rawRatio] of Object.entries(value)) {\n const symbol = String(rawSymbol || \"\").trim().toUpperCase();\n const ratio = cleanDisplayPositiveNumber(rawRatio);\n if (!/^[A-Z0-9.-]{1,12}$/.test(symbol) || !ratio) continue;\n allocations[symbol] = Number(ratio);\n total += Number(ratio);\n }\n return total > 0 && Object.keys(allocations).length ? allocations : null;\n }\n\n function optionOverlayDefaultsFromProfileItem(item) {\n const enabled = cleanOptionalBoolean(item?.option_overlay_enabled);\n const hasConfig = enabled !== null ||\n item?.option_growth_overlay_enabled !== undefined ||\n item?.option_income_overlay_enabled !== undefined ||\n item?.option_overlay_live_gate !== undefined ||\n item?.option_overlay_live_status !== undefined;\n if (!hasConfig) return null;\n if (enabled === false) return false;\n const families = [\n optionOverlayFamilyDefaultsFromProfileItem(item, \"growth\"),\n optionOverlayFamilyDefaultsFromProfileItem(item, \"income\"),\n ].filter(Boolean);\n if (!families.length) return null;\n return {\n liveGate: String(item?.option_overlay_live_gate || \"promotion_required\"),\n liveStatus: String(item?.option_overlay_live_status || \"research_only\"),\n families,\n };\n }\n\n function optionOverlayFamilyDefaultsFromProfileItem(item, family) {\n const prefix = `option_${family}_overlay`;\n const enabled = cleanOptionalBoolean(item?.[`${prefix}_enabled`]);\n if (enabled !== true) return null;\n const recipe = cleanStrategyProfile(item?.[`${prefix}_recipe`]);\n const startUsd = cleanDisplayNumber(item?.[`${prefix}_start_usd`]);\n const ratioField = family === \"growth\"\n ? \"option_growth_overlay_nav_budget_ratio\"\n : \"option_income_overlay_nav_risk_ratio\";\n const ratio = cleanDisplayRatio(item?.[ratioField]);\n if (!recipe || !startUsd || !ratio) return null;\n return {\n family,\n recipe,\n startUsd,\n ratio,\n ratioKind: family === \"growth\" ? \"budget\" : \"risk\",\n };\n }\n\n function supportedDomainsForAccount(platform, account) {\n if (Array.isArray(account?.supported_domains) && account.supported_domains.length) {\n const cleaned = account.supported_domains.map(cleanStrategyDomain).filter(Boolean);\n if (cleaned.length) return [...new Set(cleaned)];\n }\n return inferSupportedDomains(platform, account);\n }\n\n function inferSupportedDomains(platform, account) {\n void account;\n if (platform === \"qmt\") return [\"cn_equity\"];\n if (platform === \"longbridge\" || platform === \"ibkr\") return [\"us_equity\", \"hk_equity\"];\n return [\"us_equity\"];\n }\n\n function supportedDomainLabel(platform, account) {\n return supportedDomainsForAccount(platform, account).map(domainLabel).join(\" / \");\n }\n\n function platformSupportsDca(platform = state.selected) {\n return platformConfig[platform]?.dca ?? false;\n }\n\n function strategyCatalogEntry(profile) {\n return strategyCatalog[cleanStrategyProfile(profile)] || {};\n }\n\n function normalizeLifecycleStage(value) {\n const text = String(value || \"\").trim().toLowerCase();\n if (!text || text.length > 40 || !/^[a-z0-9._-]+$/.test(text)) return \"\";\n if ([\"research_backtest_only\", \"ai_monitored_candidate\"].includes(text)) return \"research_active\";\n if (text === \"shadow_candidate\") return \"shadow_active\";\n if (text === \"runtime_enabled\") return \"live_candidate\";\n return text;\n }\n\n function normalizeAllowedExecutionModes(value) {\n const items = Array.isArray(value)\n ? value\n : String(value || \"\").split(/[,\\s/|]+/);\n const modes = [];\n for (const item of items) {\n const mode = String(item || \"\").trim().toLowerCase();\n if (!mode || mode.length > 32 || !/^[a-z0-9._-]+$/.test(mode)) continue;\n if (!modes.includes(mode)) modes.push(mode);\n }\n return modes;\n }\n\n function cleanDisplayText(value) {\n const text = String(value ?? \"\").trim();\n if (!text || text.length > 120 || /[<>]/.test(text)) return \"\";\n return text;\n }\n\n function strategyCanSwitchLive(entry) {\n if (!entry || typeof entry !== \"object\") return false;\n if (entry.runtime_enabled !== true) return false;\n const allowedModes = normalizeAllowedExecutionModes(entry.allowed_execution_modes);\n if (!allowedModes.includes(\"live\")) return false;\n if (cleanOptionalBoolean(entry.can_switch_live) !== true) return false;\n const lifecycleStage = normalizeLifecycleStage(entry.lifecycle_stage);\n if (![\"live_enabled\", \"runtime_enabled\"].includes(lifecycleStage)) return false;\n const blockedReason = cleanDisplayText(entry.blocked_live_reason);\n if (blockedReason) return false;\n const evidenceStatus = cleanDisplayText(entry.latest_evidence_status);\n if (evidenceStatus && [\"research_only\", \"blocked\", \"pending\"].includes(evidenceStatus.toLowerCase())) return false;\n const pluginGateStatus = cleanDisplayText(entry.plugin_gate_status);\n if (pluginGateStatus && [\"blocked\", \"locked\", \"disabled\"].includes(pluginGateStatus.toLowerCase())) return false;\n return true;\n }\n\n function strategyDisplayMetaText(platform, account, profile) {\n void profile;\n return t(\"strategyMeta\").replace(\"{domains}\", supportedDomainLabel(platform, account));\n }\n\n function strategyActionNoteText(platform = state.selected, account = selectedAccount(platform)) {\n const profile = state.forms[platform]?.strategy || \"\";\n const meta = strategyDisplayMetaText(platform, account, profile);\n return meta ? `${t(\"invalidStrategyNote\")}\\n${meta}` : t(\"invalidStrategyNote\");\n }\n\n function strategyChoiceLabel(profile, platform = state.selected, account = selectedAccount(platform), executionMode = state.forms[platform]?.executionMode) {\n const label = strategyLabel(profile);\n const entry = strategyCatalogEntry(profile);\n const domain = entry.domain ? domainLabel(entry.domain) : \"\";\n if (!entry.profile) return label;\n if (normalizeExecutionMode(executionMode, false) === \"live\" && !strategyCanSwitchLive(entry)) {\n return domain ? `${label}(${domain})` : label;\n }\n if (entry.runtime_enabled === false) {\n return domain ? `${label}(${domain})` : label;\n }\n return label;\n }\n\n function strategyAllowedForAccount(platform, account, profile, executionMode = state.forms[platform]?.executionMode) {\n const cleanProfile = cleanStrategyProfile(profile);\n const catalogEntry = strategyCatalogEntry(cleanProfile);\n if (!catalogEntry.profile) return false;\n if (dcaConfigForStrategy(cleanProfile) && !platformSupportsDca(platform)) return false;\n if (!supportedDomainsForAccount(platform, account).includes(catalogEntry.domain)) return false;\n const mode = normalizeExecutionMode(executionMode, false);\n if (!supportedExecutionModesForPlatform(platform).includes(mode)) return false;\n if (mode === \"live\") return strategyCanSwitchLive(catalogEntry);\n const allowedModes = normalizeAllowedExecutionModes(catalogEntry.allowed_execution_modes);\n if (allowedModes.length && !allowedModes.includes(mode)) return false;\n return true;\n }\n\n function strategyChoicesForAccount(platform = state.selected, account = selectedAccount(platform), executionMode = state.forms[platform]?.executionMode) {\n const choices = strategyOptions.filter((profile) => strategyAllowedForAccount(platform, account, profile, executionMode));\n const addChoice = (value) => {\n const profile = cleanStrategyProfile(value);\n if (profile && !choices.includes(profile) && strategyAllowedForAccount(platform, account, profile, executionMode)) {\n choices.push(profile);\n }\n };\n return choices;\n }\n\n function hasLiveStrategyOption(platform = state.selected, account = selectedAccount(platform)) {\n return strategyOptions.some((profile) => strategyAllowedForAccount(platform, account, profile, \"live\"));\n }\n\n function strategyLabel(profile) {\n const labels = strategyLabels[profile] || localStrategyLabels[profile];\n if (!labels) return profile;\n return state.lang === \"zh\"\n ? (labels.zh || labels.en || profile)\n : (labels.en || labels.zh || profile);\n }\n\n function strategyLabelSet(profile, item) {\n const local = localStrategyLabels[profile] || {};\n const label = String(item?.label || item?.display_name || \"\").trim();\n const labelEn = String(item?.label_en || item?.display_name_en || \"\").trim();\n const labelZh = String(item?.label_zh || item?.display_name_zh || \"\").trim();\n return {\n zh: labelZh || local.zh || label || local.en || profile,\n en: labelEn || label || local.en || labelZh || local.zh || profile,\n };\n }\n\n function modeLabel(mode) {\n return mode === \"dry_run\" ? t(\"dryRun\") : mode === \"paper\" ? t(\"paper\") : t(\"live\");\n }\n\n function normalizePluginMode(value) {\n return pluginModes.includes(value) ? value : \"none\";\n }\n\n function pluginModeLabel(mode) {\n void mode;\n return t(\"pluginModeNone\");\n }\n\n function dcaConfigForStrategy(profile) {\n const cleanProfile = cleanStrategyProfile(profile);\n const catalog = strategyCatalog[cleanProfile] || {};\n if (catalog.dca_enabled === true) {\n return {\n defaultMode: normalizeDcaMode(catalog.dca_default_mode || \"fixed\"),\n defaultBaseInvestmentUsd: cleanDisplayPositiveNumber(catalog.dca_default_base_investment_usd) || \"1000\",\n };\n }\n return dcaProfileDefaults[cleanProfile] || null;\n }\n\n function dcaSupported(profile) {\n return Boolean(dcaConfigForStrategy(profile));\n }\n\n function normalizeDcaMode(value) {\n const mode = String(value || \"\").trim().toLowerCase();\n if (mode === \"smart_dca\") return \"smart\";\n if (mode === \"fixed_dca\" || mode === \"ordinary\" || mode === \"ordinary_dca\") return \"fixed\";\n return dcaModes.includes(mode) ? mode : \"fixed\";\n }\n\n function dcaModeLabel(mode) {\n return normalizeDcaMode(mode) === \"smart\" ? t(\"dcaModeSmart\") : t(\"dcaModeFixed\");\n }\n\n function normalizeRuntimeTargetMode(value) {\n return runtimeTargetModes.includes(value) ? value : \"current\";\n }\n\n function runtimeTargetModeLabel(mode) {\n if (mode === \"enabled\") return t(\"runtimeTargetEnabled\");\n if (mode === \"disabled\") return t(\"runtimeTargetDisabled\");\n return t(\"runtimeTargetCurrent\");\n }\n\n function runtimeTargetEnabledForAccount(platform, account) {\n return cleanOptionalBoolean(currentEntryForAccount(platform, account)?.runtime_target_enabled);\n }\n\n function runtimeTargetStateForAccount(platform = state.selected, account = selectedAccount(platform)) {\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return { known: false, enabled: null };\n const configured = cleanOptionalBoolean(entry.runtime_target_enabled);\n return { known: true, enabled: configured ?? true };\n }\n\n function runtimeTargetText(enabled) {\n return enabled ? t(\"runtimeTargetOn\") : t(\"runtimeTargetOff\");\n }\n\n function runtimeTargetTone(enabled) {\n return enabled ? \"enabled\" : \"disabled\";\n }\n\n function currentRuntimeTargetText(platform = state.selected, account = selectedAccount(platform)) {\n const target = runtimeTargetStateForAccount(platform, account);\n if (!target.known) return t(\"notRead\");\n return runtimeTargetText(target.enabled);\n }\n\n function currentRuntimeTargetTone(platform = state.selected, account = selectedAccount(platform)) {\n const target = runtimeTargetStateForAccount(platform, account);\n if (!target.known) return \"neutral\";\n return runtimeTargetTone(target.enabled);\n }\n\n function incomeLayerDefaultForStrategy(profile) {\n return incomeLayerDefaults[cleanStrategyProfile(profile)] || null;\n }\n\n function incomeLayerSupported(profile) {\n return Boolean(incomeLayerDefaultForStrategy(profile));\n }\n\n function normalizeIncomeLayerMode(value) {\n return incomeLayerModes.includes(value) ? value : \"current\";\n }\n\n function incomeLayerModeLabel(mode) {\n if (mode === \"enabled\") return t(\"incomeLayerEnabled\");\n if (mode === \"disabled\") return t(\"incomeLayerDisabled\");\n return t(\"incomeLayerCurrent\");\n }\n\n function optionOverlayDefaultForStrategy(profile) {\n return optionOverlayDefaults[cleanStrategyProfile(profile)] || null;\n }\n\n function optionOverlaySupported(profile) {\n return Boolean(optionOverlayDefaultForStrategy(profile));\n }\n\n function normalizeOptionOverlayMode(value) {\n return optionOverlayModes.includes(value) ? value : \"current\";\n }\n\n function optionOverlayModeLabel(mode) {\n if (mode === \"enabled\") return t(\"optionOverlayEnabled\");\n if (mode === \"disabled\") return t(\"optionOverlayDisabled\");\n return t(\"optionOverlayCurrent\");\n }\n\n function optionOverlayText(enabled) {\n return enabled ? t(\"optionOverlayOn\") : t(\"optionOverlayOff\");\n }\n\n function normalizeCashOnlyExecutionMode(value) {\n return cashOnlyExecutionModes.includes(value) ? value : \"current\";\n }\n\n function cashOnlyExecutionModeLabel(mode) {\n if (mode === \"enabled\") return t(\"cashOnlyExecutionNo\");\n if (mode === \"disabled\") return t(\"cashOnlyExecutionYes\");\n return t(\"cashOnlyExecutionCurrent\");\n }\n\n function cashOnlyExecutionText(enabled) {\n if (enabled === null) return t(\"notRead\");\n return enabled ? t(\"cashOnlyExecutionValueNo\") : t(\"cashOnlyExecutionValueYes\");\n }\n\n function platformCashOnlyExecutionDefault() {\n return true;\n }\n\n function effectiveCashOnlyExecutionForAccount(platform, account) {\n const configured = currentCashOnlyExecutionForAccount(platform, account);\n if (configured !== null) return configured;\n if (!platformSupportsMarginPolicy(platform)) return null;\n return platformCashOnlyExecutionDefault();\n }\n\n function currentCashOnlyExecutionForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n if (entry) {\n const val = cleanOptionalBoolean(entry.cash_only_execution);\n if (val !== null) return val;\n }\n return platformCashOnlyExecutionDefault();\n }\n\n function currentCashOnlyExecutionText(platform = state.selected, account = selectedAccount(platform)) {\n if (!platformSupportsMarginPolicy(platform)) return t(\"notRead\");\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const configured = cleanOptionalBoolean(entry.cash_only_execution);\n if (configured === null) return t(\"cashOnlyExecutionDefault\");\n return cashOnlyExecutionText(configured);\n }\n\n function currentOptionOverlayForAccount(platform, account) {\n return cleanOptionalBoolean(currentEntryForAccount(platform, account)?.option_overlay_enabled);\n }\n\n function effectiveOptionOverlayForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const configured = currentOptionOverlayForAccount(platform, account);\n if (configured !== null) return configured;\n if (!optionOverlaySupported(profile)) return null;\n return true;\n }\n\n function optionOverlayDefaultSummaryDetail(defaults) {\n if (!defaults?.families?.length) return \"\";\n return defaults.families.map((item) => {\n const family = item.family === \"income\" ? t(\"optionOverlayFamilyIncome\") : t(\"optionOverlayFamilyGrowth\");\n const ratioText = item.ratioKind === \"risk\"\n ? t(\"optionOverlayRiskRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio))\n : t(\"optionOverlayBudgetRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio));\n return `${family} ${ratioText}`;\n }).join(\" / \");\n }\n\n function optionOverlayDefaultText(profile) {\n const defaults = optionOverlayDefaultForStrategy(profile);\n if (!defaults) return t(\"optionOverlayNotSupported\");\n const detail = optionOverlayDefaultSummaryDetail(defaults);\n return detail ? t(\"optionOverlayDefault\").replace(\"{detail}\", detail) : t(\"optionOverlayDefaultSimple\");\n }\n\n function currentOptionOverlayText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const configured = cleanOptionalBoolean(entry.option_overlay_enabled);\n if (!optionOverlaySupported(profile)) {\n return configured === null ? t(\"optionOverlayNotSupported\") : optionOverlayText(configured);\n }\n if (configured === null) return optionOverlayDefaultText(profile);\n return optionOverlayText(configured);\n }\n\n function currentIncomeLayerForAccount(platform, account) {\n return incomeLayerFromEntry(currentEntryForAccount(platform, account));\n }\n\n function incomeLayerFromEntry(entry) {\n return {\n enabled: cleanOptionalBoolean(entry?.income_layer_enabled),\n startUsd: cleanDisplayNumber(entry?.income_layer_start_usd),\n maxRatio: cleanDisplayRatio(entry?.income_layer_max_ratio),\n };\n }\n\n function incomeLayerFieldsConfigured(entry) {\n const current = incomeLayerFromEntry(entry);\n return current.enabled !== null || Boolean(current.startUsd || current.maxRatio);\n }\n\n function effectiveIncomeLayerForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return null;\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return null;\n const current = incomeLayerFromEntry(entry);\n if (!incomeLayerFieldsConfigured(entry)) {\n return {\n enabled: true,\n startUsd: String(defaults.startUsd),\n maxRatio: defaults.maxRatio,\n };\n }\n return {\n enabled: current.enabled ?? true,\n startUsd: current.startUsd || String(defaults.startUsd),\n maxRatio: current.maxRatio || defaults.maxRatio,\n };\n }\n\n function currentDcaForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const defaults = dcaConfigForStrategy(profile);\n if (!defaults) return { supported: false, mode: \"\", baseInvestmentUsd: \"\" };\n const entry = currentEntryForAccount(platform, account);\n return {\n supported: true,\n mode: normalizeDcaMode(entry?.dca_mode || account?.dca_mode || defaults.defaultMode),\n baseInvestmentUsd: cleanDisplayPositiveNumber(entry?.dca_base_investment_usd) ||\n cleanDisplayPositiveNumber(account?.dca_base_investment_usd) ||\n defaults.defaultBaseInvestmentUsd,\n };\n }\n\n function normalizeAccountLookupKey(value) {\n return String(value || \"\").trim().toLowerCase();\n }\n\n function collectAccountLookupCandidates(keys) {\n const candidates = new Set();\n for (const rawKey of keys) {\n const key = normalizeAccountLookupKey(rawKey);\n if (!key) continue;\n\n candidates.add(key);\n\n const compact = key.replace(/[^a-z0-9]+/g, \"\");\n if (compact) candidates.add(compact);\n\n const parts = key.split(/[^a-z0-9]+/).filter(Boolean);\n if (parts.length > 1) candidates.add(parts[parts.length - 1]);\n }\n return [...candidates];\n }\n\n function resolveCurrentEntryByKey(byPlatform, keys) {\n const candidates = new Set(collectAccountLookupCandidates(keys));\n if (!candidates.size) return null;\n\n for (const key of keys) {\n const entry = byPlatform[key];\n if (currentEntryHasState(entry)) return entry;\n }\n\n for (const [rawKey, entry] of Object.entries(byPlatform)) {\n if (!currentEntryHasState(entry)) continue;\n const rawCandidates = collectAccountLookupCandidates([rawKey]);\n const hasMatch = rawCandidates.some((candidate) => candidates.has(candidate));\n if (hasMatch) return entry;\n }\n\n return null;\n }\n\n function currentEntryForAccount(platform, account) {\n const byPlatform = state.currentStrategies[platform] || {};\n const keys = [account?.key, account?.target_name, account?.label]\n .filter(Boolean)\n .map((value) => String(value));\n const entry = resolveCurrentEntryByKey(byPlatform, keys);\n if (entry) return entry;\n const globalDefaults = window.__DEFAULT_ACCOUNT_OPTIONS__?.[platform]?.[0] || {};\n const merged = { ...globalDefaults, ...(account || {}) };\n const synth = {\n strategy_profile: \"\",\n source: \"account_defaults\",\n };\n const cashMode = merged.cash_only_execution_mode;\n if (cashMode === \"enabled\") synth.cash_only_execution = true;\n else if (cashMode === \"disabled\") synth.cash_only_execution = false;\n else if (platformSupportsMarginPolicy(platform)) synth.cash_only_execution = true;\n if (merged.min_reserved_cash_usd) synth.min_reserved_cash_usd = merged.min_reserved_cash_usd;\n if (merged.reserved_cash_ratio) synth.reserved_cash_ratio = merged.reserved_cash_ratio;\n synth.runtime_target_enabled = merged.runtime_target_enabled !== false;\n const execMode = merged.default_execution_mode || platformConfig[platform]?.default_execution_mode || \"live\";\n synth.execution_mode = execMode;\n synth.dry_run_only = execMode === \"paper\";\n return synth;\n }\n\n function currentEntryHasState(entry) {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return false;\n return Boolean(\n cleanStrategyProfile(entry.strategy_profile) ||\n cleanDisplayNumber(entry.min_reserved_cash_usd ?? entry.reserved_cash_floor_usd) ||\n cleanDisplayRatio(entry.reserved_cash_ratio) ||\n cleanOptionalBoolean(entry.income_layer_enabled) !== null ||\n cleanDisplayNumber(entry.income_layer_start_usd) ||\n cleanDisplayRatio(entry.income_layer_max_ratio) ||\n cleanOptionalBoolean(entry.option_overlay_enabled) !== null ||\n cleanOptionalBoolean(entry.cash_only_execution) !== null ||\n cleanOptionalBoolean(entry.runtime_target_enabled) !== null ||\n normalizeDcaMode(entry.dca_mode || \"\") !== \"fixed\" ||\n cleanDisplayPositiveNumber(entry.dca_base_investment_usd) ||\n normalizeExecutionMode(entry.execution_mode, entry.dry_run_only),\n );\n }\n\n function currentStrategyForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n return cleanStrategyProfile(entry?.strategy_profile) || \"\";\n }\n\n function currentReservePolicyForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n return reservePolicyFromEntry(entry);\n }\n\n function currentPluginModeForAccount(platform, account) {\n void platform;\n return normalizePluginMode(account?.plugin_mode);\n }\n\n function reservePolicyFromEntry(entry) {\n return {\n minReservedCashUsd: cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd),\n reservedCashRatio: cleanDisplayRatio(entry?.reserved_cash_ratio),\n };\n }\n\n function cleanDisplayNumber(value) {\n const text = String(value ?? \"\").trim();\n if (!text || text.length > 32 || !/^(?:\\d+|\\d*\\.\\d+)$/.test(text)) return \"\";\n const numeric = Number(text);\n if (!Number.isFinite(numeric) || numeric < 0) return \"\";\n return text;\n }\n\n function cleanDisplayRatio(value) {\n const text = cleanDisplayNumber(value);\n if (!text) return \"\";\n const numeric = Number(text);\n return numeric >= 0 && numeric <= 1 ? text : \"\";\n }\n\n function cleanDisplayPositiveNumber(value) {\n const text = cleanDisplayNumber(value);\n return text && Number(text) > 0 ? text : \"\";\n }\n\n function normalizeExecutionMode(value, dryRunOnly) {\n const mode = String(value || \"\").trim().toLowerCase();\n if (mode === \"live\") return \"live\";\n if (mode === \"paper\" || mode === \"dry_run\" || mode === \"dry-run\") return \"dry_run\";\n if (dryRunOnly === true || dryRunOnly === \"true\" || dryRunOnly === \"1\" || dryRunOnly === 1) return \"dry_run\";\n if (dryRunOnly === false || dryRunOnly === \"false\" || dryRunOnly === \"0\" || dryRunOnly === 0) return \"live\";\n return \"\";\n }\n\n function cleanOptionalBoolean(value) {\n if (value === true || value === 1) return true;\n if (value === false || value === 0) return false;\n if (typeof value === \"string\") {\n const text = value.trim().toLowerCase();\n if (text === \"true\" || text === \"1\") return true;\n if (text === \"false\" || text === \"0\") return false;\n }\n return null;\n }\n\n function defaultExecutionModeForAccount(platform, account, fallback = \"live\") {\n if (platformDryRunOnly(platform)) return \"dry_run\";\n const currentMode = normalizeExecutionMode(\n currentEntryForAccount(platform, account)?.execution_mode,\n currentEntryForAccount(platform, account)?.dry_run_only,\n );\n if (currentMode) return currentMode;\n const hint = [\n account?.key,\n account?.label,\n account?.target_name,\n account?.deployment_selector,\n account?.account_scope,\n account?.service_name,\n ].join(\" \").toLowerCase();\n if (hint.split(/\\s+/).includes(\"paper\") || hint.includes(\"-paper\") || hint.includes(\"_paper\") || hint.includes(\"dry_run\") || hint.includes(\"dry-run\")) {\n return \"dry_run\";\n }\n return fallback;\n }\n\n function defaultStrategyForAccount(platform, account) {\n const currentProfile = currentStrategyForAccount(platform, account);\n if (currentProfile) return currentProfile;\n return \"\";\n }\n\n function syncStrategyForAccount(platform) {\n const account = selectedAccount(platform);\n if (!account) return;\n state.forms[platform].strategy = defaultStrategyForAccount(platform, account);\n state.forms[platform].executionMode = defaultExecutionModeForAccount(\n platform,\n account,\n );\n state.forms[platform].pluginMode = currentPluginModeForAccount(platform, account);\n syncRuntimeTargetForAccount(platform);\n syncReservePolicyForAccount(platform);\n syncIncomeLayerForAccount(platform);\n syncOptionOverlayForAccount(platform);\n syncCashOnlyExecutionForAccount(platform);\n reconcileExecutionCashPolicy(state.forms[platform], \"margin\");\n syncDcaForAccount(platform);\n }\n\n function syncRuntimeTargetForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.runtimeTargetTouched) return;\n const current = runtimeTargetEnabledForAccount(platform, selectedAccount(platform));\n form.runtimeTargetMode = current === false ? \"disabled\" : \"enabled\";\n }\n\n function syncReservePolicyForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.reservedCashTouched) return;\n const policy = currentReservePolicyForAccount(platform, selectedAccount(platform));\n const hasFloor = Boolean(policy.minReservedCashUsd);\n const hasRatio = Boolean(policy.reservedCashRatio);\n if (hasFloor && hasRatio) form.reservePolicyMode = \"max\";\n else if (hasFloor) form.reservePolicyMode = \"floor\";\n else if (hasRatio) form.reservePolicyMode = \"ratio\";\n else form.reservePolicyMode = \"none\";\n form.minReservedCashUsd = policy.minReservedCashUsd;\n form.reservedCashRatio = policy.reservedCashRatio;\n }\n\n function syncIncomeLayerForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.incomeLayerTouched) return;\n const defaults = incomeLayerDefaultForStrategy(form.strategy);\n const current = currentIncomeLayerForAccount(platform, selectedAccount(platform));\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n if (entry && incomeLayerFieldsConfigured(entry)) {\n form.incomeLayerMode = current.enabled === false ? \"disabled\" : \"enabled\";\n } else {\n form.incomeLayerMode = incomeLayerSupported(form.strategy) ? \"enabled\" : \"disabled\";\n }\n form.incomeLayerStartUsd = current.startUsd || String(defaults?.startUsd || \"\");\n form.incomeLayerMaxRatio = current.maxRatio || defaults?.maxRatio || \"\";\n }\n\n function syncOptionOverlayForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.optionOverlayTouched) return;\n const configured = normalizeOptionOverlayMode(selectedAccount(platform)?.option_overlay_mode);\n if (configured !== \"current\") {\n form.optionOverlayMode = configured;\n return;\n }\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n const rawValue = cleanOptionalBoolean(entry?.option_overlay_enabled);\n if (rawValue !== null) {\n form.optionOverlayMode = rawValue ? \"enabled\" : \"disabled\";\n return;\n }\n form.optionOverlayMode = optionOverlaySupported(form.strategy) ? \"enabled\" : \"disabled\";\n }\n\n function syncCashOnlyExecutionForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.cashOnlyExecutionTouched) return;\n const configured = normalizeCashOnlyExecutionMode(selectedAccount(platform)?.cash_only_execution_mode);\n if (configured !== \"current\") {\n form.cashOnlyExecutionMode = configured;\n return;\n }\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n const rawValue = cleanOptionalBoolean(entry?.cash_only_execution);\n if (rawValue !== null) {\n form.cashOnlyExecutionMode = rawValue ? \"enabled\" : \"disabled\";\n return;\n }\n // No explicit config — use platform default (cash-only for margin-capable platforms)\n form.cashOnlyExecutionMode = platformSupportsMarginPolicy(platform) ? \"enabled\" : \"disabled\";\n }\n\n function syncDcaForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.dcaTouched) return;\n const current = currentDcaForAccount(platform, selectedAccount(platform), form.strategy);\n form.dcaMode = current.supported ? current.mode : \"fixed\";\n form.dcaBaseInvestmentUsd = current.supported ? current.baseInvestmentUsd : \"\";\n }\n\n function ensureAccountSelection(platform) {\n const options = optionsFor(platform);\n if (!options.length) return;\n if (!options.some((option) => option.key === state.forms[platform].accountKey)) {\n state.forms[platform].accountKey = options[0].key;\n state.forms[platform].runtimeTargetTouched = false;\n state.forms[platform].reservedCashTouched = false;\n state.forms[platform].incomeLayerTouched = false;\n state.forms[platform].optionOverlayTouched = false;\n state.forms[platform].cashOnlyExecutionTouched = false;\n state.forms[platform].dcaTouched = false;\n state.forms[platform].strategy = defaultStrategyForAccount(platform, options[0]);\n state.forms[platform].pluginMode = currentPluginModeForAccount(platform, options[0]);\n syncRuntimeTargetForAccount(platform);\n syncReservePolicyForAccount(platform);\n syncIncomeLayerForAccount(platform);\n syncOptionOverlayForAccount(platform);\n syncCashOnlyExecutionForAccount(platform);\n syncDcaForAccount(platform);\n }\n }\n\n function derivedService(platform, targetName) {\n if (platform === \"longbridge\") return `longbridge-quant-${targetName.toLowerCase()}-service`;\n if (platform === \"ibkr\") return `interactive-brokers-${targetName.toLowerCase()}-service`;\n if (platform === \"schwab\") return \"charles-schwab-quant-service\";\n if (platform === \"firstrade\") return \"firstrade-quant-service\";\n if (platform === \"qmt\") return \"qmt-quant-service\";\n return \"\";\n }\n\n function accountMetaText(platform = state.selected) {\n const account = selectedAccount(platform);\n const targetName = account.target_name || account.key;\n const raw = account.service_name || derivedService(platform, targetName);\n const service = raw || (state.lang === \"zh\" ? \"无\" : \"-\");\n return t(\"targetMeta\")\n .replace(\"{target}\", targetName)\n .replace(\"{service}\", service)\n .replace(\"{domains}\", supportedDomainLabel(platform, account));\n }\n\n function hasRunnableStrategySelection(platform = state.selected) {\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n return Boolean(form?.strategy && account && strategyAllowedForAccount(platform, account, form.strategy, form.executionMode));\n }\n\n function hasValidReservePolicy(platform = state.selected) {\n if (!platformSupportsReservedCashPolicy(platform)) return true;\n const form = state.forms[platform];\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n if (mode === \"current\" || mode === \"none\") return true;\n return Boolean(reservePolicyOverrideForForm(form, platform));\n }\n\n function hasValidExecutionCashPolicy(platform = state.selected) {\n if (!platformSupportsMarginPolicy(platform) && !platformSupportsReservedCashPolicy(platform)) return true;\n const form = state.forms[platform];\n return !executionCashPolicyConflict(form) && hasValidReservePolicy(platform);\n }\n\n function hasValidIncomeLayerPolicy(platform = state.selected) {\n const form = state.forms[platform];\n if (!incomeLayerSupported(form?.strategy)) return true;\n const mode = normalizeIncomeLayerMode(form?.incomeLayerMode);\n if (mode === \"current\" || mode === \"disabled\") return true;\n const defaults = incomeLayerDefaultForStrategy(form?.strategy);\n const startUsd = cleanDisplayNumber(form?.incomeLayerStartUsd || defaults?.startUsd);\n const maxRatio = cleanDisplayRatio(form?.incomeLayerMaxRatio || defaults?.maxRatio);\n return Boolean(startUsd && maxRatio);\n }\n\n function hasValidOptionOverlayPolicy(platform = state.selected) {\n const form = state.forms[platform];\n const mode = normalizeOptionOverlayMode(form?.optionOverlayMode);\n return mode !== \"enabled\" || optionOverlaySupported(form?.strategy);\n }\n\n function hasValidDcaPolicy(platform = state.selected) {\n const form = state.forms[platform];\n if (!dcaSupported(form?.strategy) || !platformSupportsDca(platform)) return true;\n return Boolean(dcaModes.includes(normalizeDcaMode(form?.dcaMode)) && cleanDisplayPositiveNumber(form?.dcaBaseInvestmentUsd));\n }\n\n function hasValidStrategySelection(platform = state.selected) {\n return hasRunnableStrategySelection(platform) &&\n hasValidExecutionCashPolicy(platform) &&\n hasValidIncomeLayerPolicy(platform) &&\n hasValidOptionOverlayPolicy(platform) &&\n hasValidDcaPolicy(platform);\n }\n\n function normalizeReservePolicyMode(value) {\n return reservePolicyModes.includes(value) ? value : \"current\";\n }\n\n function reservePolicyOverrideForForm(form, platform = state.selected) {\n if (!platformSupportsReservedCashPolicy(platform)) return null;\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n const floor = cleanDisplayNumber(form?.minReservedCashUsd);\n const ratio = cleanDisplayRatio(form?.reservedCashRatio);\n const extraVariables = {};\n if (mode === \"current\") return null;\n if (mode === \"none\") {\n extraVariables[platformMinReservedCashVariables[platform]] = \"\";\n extraVariables[platformReservedCashRatioVariables[platform]] = \"\";\n return { inputs: {}, extraVariables };\n }\n if (mode === \"ratio\") {\n if (!ratio) return null;\n extraVariables[platformMinReservedCashVariables[platform]] = \"\";\n return { inputs: { reserved_cash_ratio: ratio }, extraVariables };\n }\n if (mode === \"floor\") {\n if (!floor) return null;\n extraVariables[platformReservedCashRatioVariables[platform]] = \"\";\n return { inputs: { min_reserved_cash_usd: floor }, extraVariables };\n }\n if (mode === \"max\") {\n if (!floor || !ratio) return null;\n return { inputs: { min_reserved_cash_usd: floor, reserved_cash_ratio: ratio }, extraVariables };\n }\n return null;\n }\n\n function runtimeTargetOverrideForForm(form) {\n const mode = normalizeRuntimeTargetMode(form?.runtimeTargetMode);\n if (mode === \"current\") return null;\n return {\n inputs: { runtime_target_enabled_mode: mode },\n extraVariables: { [runtimeTargetEnabledVariable]: mode === \"enabled\" ? \"true\" : \"false\" },\n };\n }\n\n function incomeLayerOverrideForForm(form) {\n const defaults = incomeLayerDefaultForStrategy(form?.strategy);\n if (!defaults) return null;\n const mode = normalizeIncomeLayerMode(form?.incomeLayerMode);\n if (mode === \"current\") return null;\n const extraVariables = {};\n if (mode === \"disabled\") {\n extraVariables[incomeLayerEnabledVariable] = \"false\";\n extraVariables[incomeLayerStartUsdVariable] = \"\";\n extraVariables[incomeLayerMaxRatioVariable] = \"\";\n return { inputs: { income_layer_mode: mode }, extraVariables };\n }\n const startUsd = cleanDisplayNumber(form?.incomeLayerStartUsd || defaults.startUsd);\n const maxRatio = cleanDisplayRatio(form?.incomeLayerMaxRatio || defaults.maxRatio);\n if (!startUsd || !maxRatio) return null;\n extraVariables[incomeLayerEnabledVariable] = \"true\";\n extraVariables[incomeLayerStartUsdVariable] = startUsd;\n extraVariables[incomeLayerMaxRatioVariable] = maxRatio;\n return { inputs: { income_layer_mode: mode, income_layer_start_usd: startUsd, income_layer_max_ratio: maxRatio }, extraVariables };\n }\n\n function optionOverlayOverrideForForm(form) {\n const mode = normalizeOptionOverlayMode(form?.optionOverlayMode);\n if (mode === \"current\") return null;\n if (mode === \"enabled\" && !optionOverlaySupported(form?.strategy)) return null;\n return { inputs: { option_overlay_mode: mode } };\n }\n\n function cashOnlyExecutionOverrideForForm(form, platform = state.selected) {\n if (!platformSupportsMarginPolicy(platform)) return null;\n const mode = normalizeCashOnlyExecutionMode(form?.cashOnlyExecutionMode);\n if (mode === \"current\") return null;\n return { inputs: { cash_only_execution_mode: mode } };\n }\n\n function dcaOverrideForForm(form) {\n if (!dcaSupported(form?.strategy) || !platformSupportsDca(state.selected)) return null;\n const mode = normalizeDcaMode(form?.dcaMode);\n const baseInvestmentUsd = cleanDisplayPositiveNumber(form?.dcaBaseInvestmentUsd);\n if (!baseInvestmentUsd) return null;\n return { inputs: { dca_mode: mode, dca_base_investment_usd: baseInvestmentUsd } };\n }\n\n function mergeExtraVariables(inputs, extraVariables) {\n if (!extraVariables || !Object.keys(extraVariables).length) return;\n const merged = inputs.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {};\n Object.assign(merged, extraVariables);\n inputs.extra_variables_json = JSON.stringify(merged);\n }\n\n function buildInputs(platform = state.selected) {\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n const targetName = account.target_name || account.key;\n const inputs = {\n platform,\n target_name: targetName,\n strategy_profile: form.strategy,\n execution_mode: form.executionMode,\n variable_scope: account.variable_scope || \"default\",\n plugin_mode: normalizePluginMode(form.pluginMode),\n service_targets_mode: \"auto\",\n apply: \"true\",\n trigger_platform_sync: \"true\",\n confirm_apply: \"APPLY_AND_SYNC\",\n platform_sync_workflow: \"sync-cloud-run-env.yml\",\n };\n for (const field of [\n \"github_environment\",\n \"deployment_selector\",\n \"account_selector\",\n \"account_scope\",\n \"service_name\",\n ]) {\n if (account[field]) inputs[field] = account[field];\n }\n const reserveOverride = platformSupportsReservedCashPolicy(platform)\n ? reservePolicyOverrideForForm(form, platform)\n : null;\n if (platformSupportsReservedCashPolicy(platform)) {\n inputs.reserved_cash_policy_mode = normalizeReservePolicyMode(form.reservePolicyMode);\n if (reserveOverride) {\n Object.assign(inputs, reserveOverride.inputs);\n mergeExtraVariables(inputs, reserveOverride.extraVariables);\n }\n }\n const runtimeTargetOverride = runtimeTargetOverrideForForm(form);\n inputs.runtime_target_enabled_mode = normalizeRuntimeTargetMode(form.runtimeTargetMode);\n if (runtimeTargetOverride) {\n Object.assign(inputs, runtimeTargetOverride.inputs);\n mergeExtraVariables(inputs, runtimeTargetOverride.extraVariables);\n }\n const incomeOverride = incomeLayerOverrideForForm(form);\n inputs.income_layer_mode = normalizeIncomeLayerMode(form.incomeLayerMode);\n if (incomeOverride) {\n Object.assign(inputs, incomeOverride.inputs);\n mergeExtraVariables(inputs, incomeOverride.extraVariables);\n }\n const optionOverlayOverride = optionOverlayOverrideForForm(form);\n inputs.option_overlay_mode = normalizeOptionOverlayMode(form.optionOverlayMode);\n if (optionOverlayOverride) {\n Object.assign(inputs, optionOverlayOverride.inputs);\n }\n const cashOnlyOverride = cashOnlyExecutionOverrideForForm(form, platform);\n if (platformSupportsMarginPolicy(platform)) {\n inputs.cash_only_execution_mode = normalizeCashOnlyExecutionMode(form.cashOnlyExecutionMode);\n if (cashOnlyOverride) {\n Object.assign(inputs, cashOnlyOverride.inputs);\n }\n }\n const dcaOverride = dcaOverrideForForm(form);\n if (dcaOverride) {\n Object.assign(inputs, dcaOverride.inputs);\n }\n return inputs;\n }\n\n function reservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform), fallback = t(\"unchanged\")) {\n if (inputs?.reserved_cash_policy_mode === \"none\") return t(\"reservedCashNone\");\n const floor = cleanDisplayNumber(inputs?.min_reserved_cash_usd);\n const ratio = cleanDisplayRatio(inputs?.reserved_cash_ratio);\n const currency = selectedCashCurrency(platform, account);\n const hasEffectiveFloor = Boolean(floor && !(ratio && Number(floor) === 0));\n if (!hasEffectiveFloor && !ratio) return fallback;\n if (hasEffectiveFloor && ratio) return `max(${floor} ${currency}, ${formatRatioPercent(ratio)})`;\n if (hasEffectiveFloor) return `${floor} ${currency}`;\n return formatRatioPercent(ratio);\n }\n\n function platformReservedCashDefaultText(platform = state.selected, account = selectedAccount(platform)) {\n return t(\"reservedCashDefault\").replace(\"{currency}\", selectedCashCurrency(platform, account));\n }\n\n function currentReservedCashPolicyText(platform = state.selected, account = selectedAccount(platform)) {\n const entry = currentEntryForAccount(platform, account);\n const policy = currentReservePolicyForAccount(platform, account);\n return reservedCashPolicyText(\n {\n min_reserved_cash_usd: policy.minReservedCashUsd,\n reserved_cash_ratio: policy.reservedCashRatio,\n },\n platform,\n account,\n entry ? platformReservedCashDefaultText(platform, account) : t(\"notRead\"),\n );\n }\n\n function pendingReservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n return reservedCashPolicyText(pendingReservePolicy(inputs, platform, account).inputs, platform, account, t(\"unchanged\"));\n }\n\n function pendingReservePolicy(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const current = currentReservePolicyForAccount(platform, account);\n const currentFloor = cleanDisplayNumber(current.minReservedCashUsd);\n const currentRatio = cleanDisplayRatio(current.reservedCashRatio);\n const mode = normalizeReservePolicyMode(inputs.reserved_cash_policy_mode);\n const next = {\n min_reserved_cash_usd: cleanDisplayNumber(inputs.min_reserved_cash_usd),\n reserved_cash_ratio: cleanDisplayRatio(inputs.reserved_cash_ratio),\n };\n if (mode === \"none\") {\n next.reserved_cash_policy_mode = \"none\";\n }\n const entry = currentEntryForAccount(platform, account);\n const changed = Boolean(entry && (\n next.min_reserved_cash_usd !== currentFloor ||\n next.reserved_cash_ratio !== currentRatio ||\n (mode === \"none\" && (currentFloor || currentRatio))\n ));\n return { changed, inputs: next };\n }\n\n function currentIncomeLayerText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return t(\"incomeLayerNotSupported\");\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const current = incomeLayerFromEntry(entry);\n if (!incomeLayerFieldsConfigured(entry)) {\n return t(\"incomeLayerDefault\")\n .replace(\"{start}\", formatUsd(defaults.startUsd))\n .replace(\"{ratio}\", formatRatioPercent(defaults.maxRatio));\n }\n const enabled = current.enabled ?? true;\n const startUsd = current.startUsd || String(defaults.startUsd);\n const ratio = current.maxRatio || defaults.maxRatio;\n return enabled\n ? t(\"incomeLayerOn\")\n .replace(\"{start}\", formatUsd(startUsd))\n .replace(\"{ratio}\", formatRatioPercent(ratio))\n : t(\"incomeLayerOff\");\n }\n\n function pendingIncomeLayerText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingIncomeLayer(inputs, platform, account);\n if (!pending.supported) return t(\"incomeLayerNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n if (pending.inputs.income_layer_enabled === false) return t(\"incomeLayerOff\");\n return t(\"incomeLayerOn\")\n .replace(\"{start}\", formatUsd(pending.inputs.income_layer_start_usd))\n .replace(\"{ratio}\", formatRatioPercent(pending.inputs.income_layer_max_ratio));\n }\n\n function pendingOptionOverlayText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingOptionOverlay(inputs, platform, account);\n if (!pending.supported && pending.inputs.option_overlay_enabled !== false) return t(\"optionOverlayNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n return optionOverlayText(pending.inputs.option_overlay_enabled);\n }\n\n function pendingCashOnlyExecutionText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingCashOnlyExecution(inputs, platform, account);\n if (!pending.changed) return t(\"unchanged\");\n return cashOnlyExecutionText(pending.inputs.cash_only_execution);\n }\n\n function pendingRuntimeTargetText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingRuntimeTarget(inputs, platform, account);\n if (!pending.changed) return t(\"unchanged\");\n return runtimeTargetText(pending.inputs.runtime_target_enabled);\n }\n\n function pendingRuntimeTargetTone(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingRuntimeTarget(inputs, platform, account);\n if (!pending.changed) return \"neutral\";\n return runtimeTargetTone(pending.inputs.runtime_target_enabled);\n }\n\n function currentDcaText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const current = currentDcaForAccount(platform, account, profile);\n if (!current.supported) return t(\"dcaNotSupported\");\n return t(\"dcaText\")\n .replace(\"{mode}\", dcaModeLabel(current.mode))\n .replace(\"{amount}\", formatUsd(current.baseInvestmentUsd));\n }\n\n function pendingDcaText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingDca(inputs, platform, account);\n if (!pending.supported) return t(\"dcaNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n return t(\"dcaText\")\n .replace(\"{mode}\", dcaModeLabel(pending.inputs.dca_mode))\n .replace(\"{amount}\", formatUsd(pending.inputs.dca_base_investment_usd));\n }\n\n function pendingRuntimeTarget(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const mode = normalizeRuntimeTargetMode(inputs.runtime_target_enabled_mode);\n if (mode === \"current\") {\n return {\n changed: false,\n inputs: { runtime_target_enabled: runtimeTargetEnabledForAccount(platform, account) ?? true },\n };\n }\n const current = runtimeTargetEnabledForAccount(platform, account);\n const currentEnabled = current ?? true;\n const nextEnabled = mode === \"enabled\";\n const entry = currentEntryForAccount(platform, account);\n return {\n changed: Boolean(entry && current !== null && currentEnabled !== nextEnabled),\n inputs: { runtime_target_enabled: nextEnabled },\n };\n }\n\n function pendingIncomeLayer(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return { supported: false, changed: false, inputs: {} };\n const mode = normalizeIncomeLayerMode(inputs.income_layer_mode);\n const entry = currentEntryForAccount(platform, account);\n const rawCurrent = currentIncomeLayerForAccount(platform, account);\n const effective = effectiveIncomeLayerForAccount(platform, account, profile);\n const currentEnabled = effective?.enabled ?? true;\n const currentStartUsd = effective?.startUsd ?? String(defaults.startUsd);\n const currentRatio = effective?.maxRatio ?? defaults.maxRatio;\n if (mode === \"current\") {\n return {\n supported: true,\n changed: false,\n inputs: {\n income_layer_enabled: rawCurrent.enabled,\n income_layer_start_usd: rawCurrent.startUsd,\n income_layer_max_ratio: rawCurrent.maxRatio,\n },\n };\n }\n if (mode === \"disabled\") {\n if (!entry) {\n return {\n supported: true,\n changed: false,\n inputs: {\n income_layer_enabled: false,\n income_layer_start_usd: \"\",\n income_layer_max_ratio: \"\",\n },\n };\n }\n return {\n supported: true,\n changed: currentEnabled !== false || Boolean(rawCurrent.startUsd || rawCurrent.maxRatio),\n inputs: {\n income_layer_enabled: false,\n income_layer_start_usd: \"\",\n income_layer_max_ratio: \"\",\n },\n };\n }\n const nextStartUsd = cleanDisplayNumber(inputs.income_layer_start_usd || defaults.startUsd);\n const nextRatio = cleanDisplayRatio(inputs.income_layer_max_ratio || defaults.maxRatio);\n return {\n supported: true,\n changed: Boolean(entry && (currentEnabled !== true || nextStartUsd !== currentStartUsd || nextRatio !== currentRatio)),\n inputs: {\n income_layer_enabled: true,\n income_layer_start_usd: nextStartUsd,\n income_layer_max_ratio: nextRatio,\n },\n };\n }\n\n function pendingOptionOverlay(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const supported = optionOverlaySupported(profile);\n const mode = normalizeOptionOverlayMode(inputs.option_overlay_mode);\n const current = effectiveOptionOverlayForAccount(platform, account, profile);\n if (mode === \"current\") {\n return {\n supported,\n changed: false,\n inputs: { option_overlay_enabled: currentOptionOverlayForAccount(platform, account) },\n };\n }\n if (mode === \"enabled\") {\n return {\n supported,\n changed: supported && current !== null && current !== true,\n inputs: { option_overlay_enabled: true },\n };\n }\n return {\n supported,\n changed: current === true,\n inputs: { option_overlay_enabled: false },\n };\n }\n\n function pendingCashOnlyExecution(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const mode = normalizeCashOnlyExecutionMode(inputs.cash_only_execution_mode);\n if (!platformSupportsMarginPolicy(platform) || mode === \"current\") {\n return { changed: false, inputs: {} };\n }\n const current = effectiveCashOnlyExecutionForAccount(platform, account);\n const nextEnabled = mode === \"enabled\";\n const entry = currentEntryForAccount(platform, account);\n return {\n changed: Boolean(entry && current !== null && current !== nextEnabled),\n inputs: { cash_only_execution: nextEnabled },\n };\n }\n\n function pendingDca(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const defaults = dcaConfigForStrategy(profile);\n if (!defaults) return { supported: false, changed: false, inputs: {} };\n const current = currentDcaForAccount(platform, account, profile);\n const nextMode = normalizeDcaMode(inputs.dca_mode || defaults.defaultMode);\n const nextBase = cleanDisplayPositiveNumber(inputs.dca_base_investment_usd || defaults.defaultBaseInvestmentUsd);\n return {\n supported: true,\n changed: Boolean(current.mode !== nextMode || current.baseInvestmentUsd !== nextBase),\n inputs: {\n dca_mode: nextMode,\n dca_base_investment_usd: nextBase,\n },\n };\n }\n\n function pendingChangeState(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const currentProfile = currentStrategyForAccount(platform, account);\n const nextProfile = cleanStrategyProfile(inputs.strategy_profile);\n const currentEntry = currentEntryForAccount(platform, account);\n const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);\n const currentPluginMode = currentPluginModeForAccount(platform, account);\n const nextPluginMode = normalizePluginMode(inputs.plugin_mode);\n const runtimeTarget = pendingRuntimeTarget(inputs, platform, account);\n const reserve = pendingReservePolicy(inputs, platform, account);\n const income = pendingIncomeLayer(inputs, platform, account);\n const optionOverlay = pendingOptionOverlay(inputs, platform, account);\n const cashOnly = pendingCashOnlyExecution(inputs, platform, account);\n const dca = pendingDca(inputs, platform, account);\n return {\n currentProfile,\n nextProfile,\n currentMode,\n currentPluginMode,\n nextPluginMode,\n strategyChanged: Boolean(nextProfile && ((state.forms[platform]?.strategyTouched) || (currentProfile && currentProfile !== nextProfile))),\n modeChanged: Boolean(inputs.execution_mode && currentMode && currentMode !== inputs.execution_mode),\n pluginModeChanged: Boolean(nextPluginMode && currentPluginMode && currentPluginMode !== nextPluginMode),\n runtimeTargetChanged: runtimeTarget.changed,\n reserveCashChanged: reserve.changed,\n incomeLayerChanged: income.changed,\n optionOverlayChanged: optionOverlay.changed,\n cashOnlyChanged: cashOnly.changed,\n dcaChanged: dca.changed,\n runtimeTarget,\n reserve,\n income,\n optionOverlay,\n cashOnly,\n dca,\n };\n }\n\n function hasPendingChanges(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const changes = pendingChangeState(inputs, platform, account);\n return Boolean(\n changes.strategyChanged ||\n changes.modeChanged ||\n changes.pluginModeChanged ||\n changes.runtimeTargetChanged ||\n changes.reserveCashChanged ||\n changes.incomeLayerChanged ||\n changes.optionOverlayChanged ||\n changes.cashOnlyChanged ||\n changes.dcaChanged\n );\n }\n\n function formatRatioPercent(value) {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n return `${(numeric * 100).toFixed(2).replace(/\\.?0+$/, \"\")}%`;\n }\n\n function formatUsd(value) {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n return `$${numeric.toLocaleString(\"en-US\", { maximumFractionDigits: 0 })}`;\n }\n\n function incomeLayerAllocationText(defaults) {\n if (!defaults?.allocations) return \"\";\n return Object.entries(defaults.allocations)\n .map(([symbol, ratio]) => `${symbol} ${formatRatioPercent(ratio)}`)\n .join(\" / \");\n }\n\n function incomeLayerDefaultMetaText(defaults) {\n if (!defaults) return t(\"incomeLayerModeMeta\");\n return t(\"incomeLayerDefaultMeta\")\n .replace(\"{start}\", formatUsd(defaults.startUsd))\n .replace(\"{ratio}\", formatRatioPercent(defaults.maxRatio));\n }\n\n function optionOverlayDefaultMetaText(defaults) {\n if (!defaults?.families?.length) return t(\"optionOverlayModeMeta\");\n const familyText = defaults.families.map((item) => {\n const family = item.family === \"income\" ? t(\"optionOverlayFamilyIncome\") : t(\"optionOverlayFamilyGrowth\");\n const ratioText = item.ratioKind === \"risk\"\n ? t(\"optionOverlayRiskRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio))\n : t(\"optionOverlayBudgetRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio));\n return `${family}: ${item.recipe}, ${formatUsd(item.startUsd)}, ${ratioText}`;\n }).join(\" / \");\n return t(\"optionOverlayDefaultMeta\").replace(\"{defaults}\", familyText);\n }\n\n function summaryRows(inputs) {\n const account = selectedAccount();\n const changes = pendingChangeState(inputs, state.selected, account);\n const currentStrategyText = changes.currentProfile ? strategyLabel(changes.currentProfile) : t(\"notRead\");\n const rows = [\n [t(\"repository\"), state.repositories[state.selected] || defaultRepositories[state.selected]],\n [t(\"selectedAccount\"), account.label],\n [t(\"currentStrategy\"), currentStrategyText],\n [t(\"selectedMarket\"), supportedDomainLabel(state.selected, account)],\n [\n t(\"currentRuntimeTarget\"),\n currentRuntimeTargetText(state.selected, account),\n \"\",\n currentRuntimeTargetTone(state.selected, account),\n ],\n [t(\"currentPluginMode\"), pluginModeLabel(changes.currentPluginMode)],\n [t(\"reservedCashPolicy\"), currentReservedCashPolicyText(state.selected, account)],\n ];\n if (platformSupportsMarginPolicy(state.selected)) {\n rows.push([t(\"currentCashOnlyExecution\"), currentCashOnlyExecutionText(state.selected, account)]);\n }\n if (incomeLayerSupported(inputs.strategy_profile)) {\n rows.push([t(\"currentIncomeLayer\"), currentIncomeLayerText(state.selected, account, inputs.strategy_profile)]);\n }\n if (optionOverlaySupported(inputs.strategy_profile) || changes.optionOverlayChanged) {\n rows.push([t(\"currentOptionOverlay\"), currentOptionOverlayText(state.selected, account, inputs.strategy_profile)]);\n }\n if (dcaSupported(inputs.strategy_profile)) {\n rows.push([t(\"currentDca\"), currentDcaText(state.selected, account, inputs.strategy_profile)]);\n }\n if (changes.reserveCashChanged) {\n rows.push([t(\"pendingReservedCashPolicy\"), pendingReservedCashPolicyText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.incomeLayerChanged) {\n rows.push([t(\"pendingIncomeLayer\"), pendingIncomeLayerText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.optionOverlayChanged) {\n rows.push([t(\"pendingOptionOverlay\"), pendingOptionOverlayText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.cashOnlyChanged) {\n rows.push([t(\"pendingCashOnlyExecution\"), pendingCashOnlyExecutionText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.dcaChanged) {\n rows.push([t(\"pendingDca\"), pendingDcaText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.modeChanged) {\n rows.push([t(\"pendingMode\"), modeLabel(inputs.execution_mode), \"pending\"]);\n }\n if (changes.pluginModeChanged) {\n rows.push([t(\"pendingPluginMode\"), pluginModeLabel(changes.nextPluginMode), \"pending\"]);\n }\n if (changes.runtimeTargetChanged) {\n rows.push([\n t(\"pendingRuntimeTarget\"),\n pendingRuntimeTargetText(inputs, state.selected, account),\n \"pending\",\n pendingRuntimeTargetTone(inputs, state.selected, account),\n ]);\n }\n if (changes.strategyChanged && changes.nextProfile) {\n rows.push([t(\"nextStrategy\"), strategyLabel(changes.nextProfile), \"pending\"]);\n }\n return rows;\n }\n\n function applyLanguage() {\n document.documentElement.lang = state.lang === \"zh\" ? \"zh-CN\" : \"en\";\n document.querySelectorAll(\"[data-i18n]\").forEach((node) => {\n node.textContent = t(node.dataset.i18n);\n });\n el(\"lang-button\").textContent = state.lang === \"zh\" ? \"EN\" : \"中\";\n }\n\n function renderRuntimeAuthorityStatus() {\n const status = String(runtimeAuthorityStatus.status || \"UNKNOWN_RUNTIME_AUTHORITY_STATUS\");\n const asOf = String(runtimeAuthorityStatus.status_as_of || \"—\");\n el(\"runtime-authority-status\").textContent = status;\n el(\"runtime-authority-as-of\").textContent = t(\"runtimeAuthorityAsOf\").replace(\"{asOf}\", asOf);\n }\n\n function renderPlatforms() {\n const strip = el(\"platform-strip\");\n strip.replaceChildren();\n const showPrivateConfig = hasPrivateConfig();\n for (const platform of Object.keys(platformMeta)) {\n ensureAccountSelection(platform);\n const meta = platformMeta[platform];\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n const button = document.createElement(\"button\");\n button.className = \"platform-button\";\n button.type = \"button\";\n button.dataset.platform = platform;\n button.classList.toggle(\"active\", platform === state.selected);\n const mark = document.createElement(\"span\");\n mark.className = \"mark\";\n mark.textContent = meta.code;\n const copyNode = document.createElement(\"span\");\n copyNode.className = \"platform-copy\";\n const labelNode = document.createElement(\"strong\");\n labelNode.textContent = meta.label;\n copyNode.append(labelNode);\n if (showPrivateConfig) {\n const accountNode = document.createElement(\"span\");\n accountNode.textContent = account.label;\n const strategyNode = document.createElement(\"small\");\n strategyNode.textContent = strategyLabel(form.strategy);\n copyNode.append(accountNode, strategyNode);\n }\n button.append(mark, copyNode);\n strip.appendChild(button);\n }\n }\n\n function renderControls() {\n const platform = state.selected;\n const meta = platformMeta[platform];\n const form = state.forms[platform];\n const accounts = optionsFor(platform);\n const account = selectedAccount(platform);\n const choices = strategyChoicesForAccount(platform, account, form.executionMode);\n const currentStrategy = currentStrategyForAccount(platform, account);\n const currentStrategyBlocked = Boolean(\n currentStrategy &&\n !strategyAllowedForAccount(platform, account, currentStrategy, form.executionMode),\n );\n const accountSelect = el(\"account-select\");\n const strategySelect = el(\"strategy-select\");\n const runtimeTargetEnabledSelect = el(\"runtime-target-enabled-select\");\n const pluginModeSelect = el(\"plugin-mode-select\");\n const incomeLayerModeSelect = el(\"income-layer-mode-select\");\n const incomeLayerStartUsdInput = el(\"income-layer-start-usd-input\");\n const incomeLayerMaxRatioInput = el(\"income-layer-max-ratio-input\");\n const optionOverlayModeSelect = el(\"option-overlay-mode-select\");\n const cashOnlyExecutionModeSelect = el(\"cash-only-execution-mode-select\");\n const dcaModeSelect = el(\"dca-mode-select\");\n const dcaBaseInvestmentUsdInput = el(\"dca-base-investment-usd-input\");\n const reservePolicyModeSelect = el(\"reserve-policy-mode-select\");\n const minReservedCashInput = el(\"min-reserved-cash-input\");\n const reservedCashRatioInput = el(\"reserved-cash-ratio-input\");\n const showPrivateControls = hasPrivateConfig();\n\n el(\"switch-panel\").style.setProperty(\"--platform-color\", meta.accent);\n el(\"platform-title\").textContent = meta.label;\n el(\"quick-form\").hidden = !showPrivateControls;\n el(\"run-area\").hidden = !showPrivateControls;\n el(\"public-note\").hidden = showPrivateControls;\n el(\"public-preview\").hidden = showPrivateControls;\n el(\"public-note\").textContent = state.auth.allowed ? t(\"missingConfigNote\") : t(\"publicReadonly\");\n\n if (!showPrivateControls) {\n accountSelect.replaceChildren();\n strategySelect.replaceChildren();\n runtimeTargetEnabledSelect.replaceChildren();\n pluginModeSelect.replaceChildren();\n incomeLayerModeSelect.replaceChildren();\n optionOverlayModeSelect.replaceChildren();\n cashOnlyExecutionModeSelect.replaceChildren();\n dcaModeSelect.replaceChildren();\n reservePolicyModeSelect.replaceChildren();\n incomeLayerStartUsdInput.value = \"\";\n incomeLayerMaxRatioInput.value = \"\";\n dcaBaseInvestmentUsdInput.value = \"\";\n minReservedCashInput.value = \"\";\n reservedCashRatioInput.value = \"\";\n el(\"account-meta\").textContent = \"\";\n el(\"strategy-meta\").textContent = \"\";\n el(\"income-layer-mode-meta\").textContent = \"\";\n el(\"income-layer-start-meta\").textContent = \"\";\n el(\"income-layer-ratio-meta\").textContent = \"\";\n el(\"option-overlay-mode-meta\").textContent = \"\";\n el(\"cash-only-execution-mode-meta\").textContent = \"\";\n el(\"dca-mode-meta\").textContent = \"\";\n el(\"dca-base-meta\").textContent = \"\";\n return;\n }\n\n accountSelect.replaceChildren();\n if (accounts.length) {\n for (const account of accounts) {\n accountSelect.append(new Option(account.label, account.key, false, account.key === form.accountKey));\n }\n } else {\n accountSelect.append(new Option(t(\"noAccount\"), \"\"));\n }\n el(\"account-meta\").textContent = accounts.length ? accountMetaText(platform) : \"\";\n\n if (choices.length && !choices.includes(form.strategy) && !currentStrategyBlocked) {\n form.strategy = choices[0];\n }\n strategySelect.disabled = !choices.length;\n strategySelect.replaceChildren();\n if (currentStrategyBlocked) {\n const blockedOption = new Option(\n strategyChoiceLabel(currentStrategy, platform, account, form.executionMode),\n currentStrategy,\n true,\n currentStrategy === form.strategy,\n );\n blockedOption.disabled = true;\n strategySelect.append(blockedOption);\n }\n if (choices.length) {\n for (const strategy of choices) {\n strategySelect.append(\n new Option(strategyChoiceLabel(strategy, platform, account, form.executionMode), strategy, false, strategy === form.strategy),\n );\n }\n } else {\n strategySelect.append(new Option(t(\"noStrategy\"), \"\"));\n }\n el(\"strategy-meta\").textContent = account\n ? strategyDisplayMetaText(platform, account, form.strategy)\n : \"\";\n runtimeTargetEnabledSelect.replaceChildren();\n for (const mode of runtimeTargetModes) {\n runtimeTargetEnabledSelect.append(\n new Option(runtimeTargetModeLabel(mode), mode, false, mode === normalizeRuntimeTargetMode(form.runtimeTargetMode)),\n );\n }\n pluginModeSelect.replaceChildren();\n for (const mode of pluginModes) {\n pluginModeSelect.append(new Option(pluginModeLabel(mode), mode, false, mode === normalizePluginMode(form.pluginMode)));\n }\n const incomeDefaults = incomeLayerDefaultForStrategy(form.strategy);\n el(\"income-layer-section\").hidden = false;\n el(\"option-overlay-section\").hidden = false;\n incomeLayerModeSelect.replaceChildren();\n if (incomeDefaults) {\n incomeLayerModeSelect.disabled = false;\n for (const mode of incomeLayerModes) {\n incomeLayerModeSelect.append(new Option(incomeLayerModeLabel(mode), mode, false, mode === normalizeIncomeLayerMode(form.incomeLayerMode)));\n }\n el(\"income-layer-mode-meta\").textContent = incomeLayerDefaultMetaText(incomeDefaults);\n el(\"income-layer-start-meta\").textContent = t(\"incomeLayerStartMeta\");\n el(\"income-layer-ratio-meta\").textContent = t(\"incomeLayerAllocationMeta\").replace(\n \"{allocations}\",\n incomeLayerAllocationText(incomeDefaults),\n );\n } else {\n incomeLayerModeSelect.disabled = true;\n incomeLayerModeSelect.append(new Option(t(\"incomeLayerNotSupported\"), \"current\"));\n el(\"income-layer-mode-meta\").textContent = t(\"incomeLayerModeMeta\");\n el(\"income-layer-start-meta\").textContent = t(\"incomeLayerStartMeta\");\n el(\"income-layer-ratio-meta\").textContent = t(\"incomeLayerRatioMeta\");\n }\n const supportsMargin = platformSupportsMarginPolicy(platform);\n const supportsReserve = platformSupportsReservedCashPolicy(platform);\n if (supportsMargin) syncCashOnlyExecutionForAccount(platform);\n reconcileExecutionCashPolicy(form, \"margin\");\n const executionCashPolicyGrid = el(\"execution-cash-policy-grid\");\n const qmtPlatformCashNote = el(\"qmt-platform-cash-note\");\n const executionCashPolicyNote = el(\"execution-cash-policy-note\");\n executionCashPolicyGrid.hidden = !supportsMargin && !supportsReserve;\n qmtPlatformCashNote.hidden = supportsMargin || supportsReserve || platform !== \"qmt\";\n executionCashPolicyNote.hidden = !supportsMargin || !supportsReserve;\n\n const marginBlocksReserve = supportsMargin && supportsReserve && allowMarginExplicitlySelected(form);\n const reserveBlocksMargin = supportsMargin && supportsReserve && reserveCashOverrideActive(form);\n\n if (supportsReserve) {\n reservePolicyModeSelect.replaceChildren();\n for (const mode of reservePolicyModes) {\n reservePolicyModeSelect.append(new Option(t(`reservePolicy${mode[0].toUpperCase()}${mode.slice(1)}`), mode, false, mode === normalizeReservePolicyMode(form.reservePolicyMode)));\n }\n const reserveMode = normalizeReservePolicyMode(form.reservePolicyMode);\n el(\"min-reserved-cash-label\").textContent = t(\"minReservedCash\").replace(\n \"{currency}\",\n selectedCashCurrency(platform, account),\n );\n reservePolicyModeSelect.disabled = false;\n minReservedCashInput.disabled = reserveMode === \"current\" || reserveMode === \"none\" || reserveMode === \"ratio\";\n reservedCashRatioInput.disabled = reserveMode === \"current\" || reserveMode === \"none\" || reserveMode === \"floor\";\n minReservedCashInput.value = reserveMode === \"ratio\" || reserveMode === \"none\" ? \"\" : form.minReservedCashUsd;\n reservedCashRatioInput.value = reserveMode === \"floor\" || reserveMode === \"none\" ? \"\" : form.reservedCashRatio;\n el(\"reserve-policy-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"min-reserve-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"reserve-ratio-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"reserve-policy-mode-meta\").textContent = marginBlocksReserve\n ? t(\"executionCashMarginBlocksReserve\")\n : t(\"reservedCashModeMeta\");\n } else {\n reservePolicyModeSelect.replaceChildren();\n minReservedCashInput.value = \"\";\n reservedCashRatioInput.value = \"\";\n }\n const incomeMode = normalizeIncomeLayerMode(form.incomeLayerMode);\n const incomeLayerInputsDisabled = !incomeDefaults || incomeMode === \"disabled\";\n incomeLayerStartUsdInput.disabled = incomeLayerInputsDisabled;\n incomeLayerMaxRatioInput.disabled = incomeLayerInputsDisabled;\n if (incomeDefaults && incomeMode !== \"disabled\" && !cleanDisplayNumber(form.incomeLayerStartUsd)) {\n form.incomeLayerStartUsd = String(incomeDefaults.startUsd);\n }\n if (incomeDefaults && incomeMode !== \"disabled\" && !cleanDisplayRatio(form.incomeLayerMaxRatio)) {\n form.incomeLayerMaxRatio = incomeDefaults.maxRatio;\n }\n incomeLayerStartUsdInput.value = incomeDefaults && incomeMode !== \"disabled\" ? form.incomeLayerStartUsd : \"\";\n incomeLayerMaxRatioInput.value = incomeDefaults && incomeMode !== \"disabled\" ? form.incomeLayerMaxRatio : \"\";\n\n const optionDefaults = optionOverlayDefaultForStrategy(form.strategy);\n optionOverlayModeSelect.replaceChildren();\n if (optionDefaults) {\n optionOverlayModeSelect.disabled = false;\n for (const mode of optionOverlayModes) {\n optionOverlayModeSelect.append(\n new Option(optionOverlayModeLabel(mode), mode, false, mode === normalizeOptionOverlayMode(form.optionOverlayMode)),\n );\n }\n el(\"option-overlay-mode-meta\").textContent = optionOverlayDefaultMetaText(optionDefaults);\n } else {\n optionOverlayModeSelect.disabled = true;\n optionOverlayModeSelect.append(new Option(t(\"optionOverlayNotSupported\"), \"current\"));\n el(\"option-overlay-mode-meta\").textContent = t(\"optionOverlayModeMeta\");\n }\n\n if (supportsMargin) {\n cashOnlyExecutionModeSelect.replaceChildren();\n for (const mode of cashOnlyExecutionModes) {\n const option = new Option(\n mode === \"enabled\" ? t(\"cashOnlyExecutionNo\") : t(\"cashOnlyExecutionYes\"),\n mode,\n false,\n mode === normalizeCashOnlyExecutionMode(form.cashOnlyExecutionMode),\n );\n cashOnlyExecutionModeSelect.append(option);\n }\n el(\"cash-only-policy-block\").classList.toggle(\"policy-block-muted\", reserveBlocksMargin);\n el(\"cash-only-execution-mode-meta\").textContent = reserveBlocksMargin\n ? t(\"executionCashReserveBlocksMargin\")\n : t(\"cashOnlyExecutionModeMeta\");\n } else {\n cashOnlyExecutionModeSelect.replaceChildren();\n el(\"cash-only-execution-mode-meta\").textContent = \"\";\n }\n\n const dcaDefaults = dcaConfigForStrategy(form.strategy);\n dcaModeSelect.replaceChildren();\n const dcaAllowed = Boolean(dcaDefaults) && platformSupportsDca(platform);\n if (dcaAllowed) {\n dcaModeSelect.disabled = false;\n for (const mode of dcaModes) {\n dcaModeSelect.append(new Option(dcaModeLabel(mode), mode, false, mode === normalizeDcaMode(form.dcaMode)));\n }\n if (!cleanDisplayPositiveNumber(form.dcaBaseInvestmentUsd)) {\n form.dcaBaseInvestmentUsd = dcaDefaults.defaultBaseInvestmentUsd;\n }\n dcaBaseInvestmentUsdInput.disabled = false;\n dcaBaseInvestmentUsdInput.value = form.dcaBaseInvestmentUsd;\n el(\"dca-mode-meta\").textContent = t(\"dcaDefaultMeta\")\n .replace(\"{mode}\", dcaModeLabel(dcaDefaults.defaultMode))\n .replace(\"{amount}\", formatUsd(dcaDefaults.defaultBaseInvestmentUsd));\n el(\"dca-base-meta\").textContent = t(\"dcaModeMeta\");\n } else {\n dcaModeSelect.disabled = true;\n dcaModeSelect.append(new Option(\n dcaDefaults && !platformSupportsDca(platform) ? t(\"dcaPlatformNotSupported\") : t(\"dcaNotSupported\"),\n \"fixed\",\n ));\n dcaBaseInvestmentUsdInput.disabled = true;\n dcaBaseInvestmentUsdInput.value = \"\";\n el(\"dca-mode-meta\").textContent = t(\"dcaModeMeta\");\n el(\"dca-base-meta\").textContent = t(\"dcaModeMeta\");\n }\n\n const supportedModes = supportedExecutionModesForPlatform(platform);\n const liveModeAvailable = supportedModes.includes(\"live\") && hasLiveStrategyOption(platform, account);\n if (!supportedModes.includes(form.executionMode)) form.executionMode = \"dry_run\";\n document.querySelectorAll(\"#mode-control [data-mode]\").forEach((button) => {\n button.disabled = !supportedModes.includes(button.dataset.mode) || (\n button.dataset.mode === \"live\" && !liveModeAvailable\n );\n button.classList.toggle(\"active\", button.dataset.mode === form.executionMode);\n });\n el(\"mode-meta\").textContent = !supportedModes.includes(\"live\")\n ? t(\"qmtDryRunOnlyNote\")\n : (!liveModeAvailable ? t(\"liveModeUnavailable\") : \"\");\n }\n\n function renderSummary() {\n const showSummary = hasPrivateConfig();\n const summaryPanel = document.querySelector(\".summary-panel\");\n const switchSurface = document.querySelector(\".switch-surface\");\n summaryPanel.hidden = !showSummary;\n switchSurface.classList.toggle(\"summary-hidden\", !showSummary);\n if (!showSummary) return;\n\n const inputs = buildInputs();\n const list = el(\"summary-list\");\n list.replaceChildren();\n document.querySelector(\".summary-head h2\").textContent = t(\"summary\");\n const currentHeading = document.createElement(\"p\");\n currentHeading.className = \"summary-section-title\";\n currentHeading.textContent = t(\"summaryCurrent\");\n list.appendChild(currentHeading);\n let pendingSectionInserted = false;\n for (const [label, value, rowClass, valueTone] of summaryRows(inputs)) {\n if (!pendingSectionInserted && rowClass === \"pending\") {\n const pendingHeading = document.createElement(\"p\");\n pendingHeading.className = \"summary-section-title\";\n pendingHeading.textContent = t(\"summaryPending\");\n list.appendChild(pendingHeading);\n pendingSectionInserted = true;\n }\n const row = document.createElement(\"div\");\n row.className = \"summary-row\";\n row.setAttribute(\"role\", \"listitem\");\n if (rowClass) row.classList.add(rowClass);\n const labelNode = document.createElement(\"div\");\n labelNode.className = \"summary-label\";\n labelNode.textContent = label;\n const valueNode = document.createElement(\"div\");\n valueNode.className = \"summary-value\";\n if (valueTone) {\n const badge = document.createElement(\"span\");\n badge.className = `summary-status ${valueTone}`;\n badge.textContent = value;\n valueNode.appendChild(badge);\n } else {\n valueNode.textContent = value;\n }\n row.append(labelNode, valueNode);\n list.appendChild(row);\n }\n\n const account = selectedAccount();\n const currentEntry = currentEntryForAccount(state.selected, account);\n const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);\n el(\"mode-pill\").textContent = currentMode ? modeLabel(currentMode) : t(\"notRead\");\n }\n\n function setPlanCheck(id, value, tone) {\n const node = el(id);\n if (!node) return;\n node.textContent = value;\n node.dataset.tone = tone || \"neutral\";\n }\n\n function renderPlanReadiness() {\n const platform = state.selected;\n const account = selectedAccount(platform);\n const configured = hasPrivateConfig() && Boolean(account?.key);\n const runnable = configured && hasRunnableStrategySelection(platform);\n const riskValid = configured && hasValidExecutionCashPolicy(platform) &&\n hasValidIncomeLayerPolicy(platform) && hasValidOptionOverlayPolicy(platform) && hasValidDcaPolicy(platform);\n const form = state.forms[platform];\n const isNonLive = normalizeExecutionMode(form?.executionMode, false) !== \"live\";\n setPlanCheck(\n \"plan-check-account\",\n configured ? t(\"planCheckSelected\") : t(\"planCheckWaiting\"),\n configured ? \"ready\" : \"neutral\",\n );\n setPlanCheck(\n \"plan-check-strategy\",\n runnable ? t(\"planCheckValid\") : (configured ? t(\"planCheckFix\") : t(\"planCheckWaiting\")),\n runnable ? \"ready\" : (configured ? \"warning\" : \"neutral\"),\n );\n setPlanCheck(\n \"plan-check-risk\",\n riskValid ? t(\"planCheckValid\") : (configured ? t(\"planCheckFix\") : t(\"planCheckWaiting\")),\n riskValid ? \"ready\" : (configured ? \"warning\" : \"neutral\"),\n );\n setPlanCheck(\n \"plan-check-authority\",\n isNonLive ? t(\"planCheckNonLive\") : t(\"planCheckNoAuthority\"),\n isNonLive ? \"ready\" : \"warning\",\n );\n }\n\n function renderAuth() {\n const status = el(\"auth-status\");\n const loginLink = el(\"login-link\");\n const logoutButton = el(\"logout-button\");\n const signedIn = Boolean(state.auth.allowed && state.auth.login);\n\n status.hidden = !signedIn;\n status.textContent = signedIn ? t(\"signedInAs\").replace(\"{login}\", state.auth.login) : \"\";\n loginLink.hidden = signedIn;\n loginLink.href = \"/login\";\n loginLink.textContent = t(\"login\");\n logoutButton.hidden = !signedIn;\n logoutButton.textContent = t(\"logout\");\n\n const dispatch = el(\"dispatch-button\");\n const hasPrivateAccounts = state.configSource === \"private\";\n const loadingConfig = state.configSource === \"loading\";\n const hasRunnableStrategy = hasRunnableStrategySelection();\n const hasValidReserve = hasValidExecutionCashPolicy();\n const hasValidIncomeLayer = hasValidIncomeLayerPolicy();\n const hasValidOptionOverlay = hasValidOptionOverlayPolicy();\n const hasValidDca = hasValidDcaPolicy();\n const hasValidStrategy = hasRunnableStrategy &&\n hasValidReserve &&\n hasValidIncomeLayer &&\n hasValidOptionOverlay &&\n hasValidDca;\n const hasPendingChange = hasPrivateAccounts && hasValidStrategy && hasPendingChanges(buildInputs());\n dispatch.disabled = !state.auth.allowed || loadingConfig || !hasPrivateAccounts || !hasValidStrategy || !hasPendingChange;\n dispatch.textContent = state.auth.allowed\n ? (loadingConfig\n ? t(\"loadingConfig\")\n : (hasPrivateAccounts ? (hasValidStrategy ? (hasPendingChange ? t(\"runSwitch\") : t(\"noChanges\")) : t(\"configureAccounts\")) : t(\"configureAccounts\")))\n : t(\"loginToRun\");\n const note = el(\"action-note\");\n note.textContent = state.auth.allowed\n ? (loadingConfig\n ? t(\"loadingConfigNote\")\n : (hasPrivateAccounts\n ? (hasRunnableStrategy\n ? (hasValidReserve\n ? (hasValidIncomeLayer\n ? (hasValidOptionOverlay\n ? (hasValidDca ? (hasPendingChange ? t(\"readyNote\") : \"\") : t(\"invalidDcaNote\"))\n : t(\"invalidOptionOverlayNote\"))\n : t(\"invalidIncomeLayerNote\"))\n : (executionCashPolicyConflict(state.forms[state.selected])\n ? t(\"invalidExecutionCashPolicyNote\")\n : t(\"invalidReservePolicyNote\")))\n : strategyActionNoteText())\n : t(\"missingConfigNote\")))\n : t(\"readonlyNote\");\n note.classList.toggle(\n \"warning\",\n state.auth.allowed && !loadingConfig && (!hasPrivateAccounts || !hasValidStrategy),\n );\n }\n\n function renderAppVisibility() {\n document.body.classList.toggle(\"app-loading\", !state.appReady);\n el(\"boot-message\").textContent = t(state.bootMessageKey);\n }\n\n function normalizeControlPlanePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid control plane payload\");\n const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n attention: payload.attention && typeof payload.attention === \"object\" && !Array.isArray(payload.attention)\n ? {\n status: [\"research_only\", \"attention_required\", \"unavailable\"].includes(payload.attention.status)\n ? payload.attention.status\n : \"unavailable\",\n reason_codes: Array.isArray(payload.attention.reason_codes) ? payload.attention.reason_codes : [],\n }\n : { status: \"unavailable\", reason_codes: [\"control_plane_attention_missing\"] },\n candidates: candidates.filter((item) => item && typeof item === \"object\" && item.lifecycle && typeof item.lifecycle === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function normalizeOwnerDecisionQueue(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid owner decision queue\");\n const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n candidates: candidates.filter((entry) => entry && typeof entry === \"object\"\n && entry.candidate && typeof entry.candidate === \"object\"\n && typeof entry.candidate_evidence_sha256 === \"string\"),\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function ownerDecisionEntry(candidateId) {\n return state.ownerDecisions.candidates.find((entry) => entry.candidate?.candidate_id === candidateId) || null;\n }\n\n function ownerDecisionLabel(decision) {\n return {\n approve_limited_live_canary: t(\"ownerDecisionApprove\"),\n keep_parked: t(\"ownerDecisionPark\"),\n retire_candidate: t(\"ownerDecisionRetire\"),\n }[decision] || String(decision || \"—\");\n }\n\n function controlPlaneDataStatusText(status) {\n return status === \"ready\"\n ? t(\"controlDataReady\")\n : (status === \"stale\" ? t(\"controlDataStale\") : t(\"controlDataUnavailable\"));\n }\n\n function controlPlaneAttentionText(attention) {\n const status = attention?.status || \"unavailable\";\n if (status === \"research_only\") return t(\"controlAttentionResearchOnly\");\n if (status === \"attention_required\") return t(\"controlAttentionRequired\");\n return t(\"controlAttentionUnavailable\");\n }\n\n function renderControlPlane() {\n const payload = state.controlPlane.payload;\n const summary = payload.summary || {};\n el(\"control-plane-status\").textContent = `${controlPlaneDataStatusText(payload.data_status)} · ${controlPlaneAttentionText(payload.attention)}`;\n el(\"control-plane-computed-at\").textContent = payload.computed_at\n ? t(\"controlComputedAt\").replace(\"{time}\", new Date(payload.computed_at).toLocaleString())\n : t(\"controlComputedAt\").replace(\"{time}\", \"—\");\n el(\"control-count-candidates\").textContent = String(Number(summary.candidate_count) || 0);\n el(\"control-count-deferred\").textContent = String(Number(summary.deferred) || 0);\n el(\"control-count-parked\").textContent = String(Number(summary.parked) || 0);\n el(\"control-count-owner-decision\").textContent = String(Number(summary.owner_decision_required) || 0);\n\n const notice = el(\"control-plane-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"controlLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"controlStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"controlUnavailableNotice\");\n } else if (payload.attention?.status === \"attention_required\") {\n notice.textContent = t(\"controlAttentionNotice\")\n .replace(\"{deferred}\", String(Number(summary.deferred) || 0))\n .replace(\"{parked}\", String(Number(summary.parked) || 0))\n .replace(\"{signals}\", String(payload.attention.reason_codes?.length || 0));\n } else if (payload.errors?.length) {\n notice.textContent = t(\"controlUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || \"live 仍需所有者明确决定。\";\n }\n\n const list = el(\"control-plane-list\");\n list.replaceChildren();\n if (!payload.candidates.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"controlEmptyCandidates\");\n list.appendChild(empty);\n return;\n }\n for (const item of payload.candidates) {\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = `${item.candidate_kind || \"candidate\"} · ${domainLabel(item.domain || \"\")}`;\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(item.candidate_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = item.recommendation?.reason || t(\"controlNoRecommendation\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"controlStageMeta\")\n .replace(\"{stage}\", item.lifecycle?.stage || \"—\")\n .replace(\"{status}\", item.lifecycle?.status || \"unknown\")\n .replace(\"{freshness}\", item.freshness?.status || \"unknown\");\n main.append(meta, title, reason, detail);\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"controlNext\");\n const stage = document.createElement(\"strong\");\n stage.textContent = item.lifecycle?.stage || \"—\";\n const recommendation = document.createElement(\"small\");\n recommendation.textContent = item.recommendation?.code || \"none\";\n stateBlock.append(label, stage, recommendation);\n const ownerEntry = ownerDecisionEntry(item.candidate_id);\n if (ownerEntry) {\n const ownerDecision = document.createElement(\"div\");\n ownerDecision.className = \"owner-decision\";\n const ownerTitle = document.createElement(\"strong\");\n ownerTitle.textContent = t(\"ownerDecisionTitle\");\n const ownerDetail = document.createElement(\"small\");\n if (ownerEntry.intent) {\n ownerDetail.textContent = t(\"ownerDecisionRecorded\")\n .replace(\"{decision}\", ownerDecisionLabel(ownerEntry.intent.decision));\n } else if (!state.auth.admin) {\n ownerDetail.textContent = t(\"ownerDecisionAdminOnly\");\n } else {\n ownerDetail.textContent = t(\"ownerDecisionReady\");\n }\n ownerDecision.append(ownerTitle, ownerDetail);\n if (!ownerEntry.intent && state.auth.admin) {\n const actions = document.createElement(\"div\");\n actions.className = \"owner-decision__actions\";\n const submitting = state.ownerDecisions.submittingCandidateId === item.candidate_id;\n for (const [decision, labelKey] of [\n [\"approve_limited_live_canary\", \"ownerDecisionApprove\"],\n [\"keep_parked\", \"ownerDecisionPark\"],\n [\"retire_candidate\", \"ownerDecisionRetire\"],\n ]) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"owner-decision__button\";\n button.dataset.ownerDecision = decision;\n button.dataset.candidateId = item.candidate_id;\n button.dataset.candidateEvidenceSha256 = ownerEntry.candidate_evidence_sha256;\n button.disabled = submitting;\n button.textContent = submitting ? t(\"ownerDecisionSubmitting\") : t(labelKey);\n actions.appendChild(button);\n }\n ownerDecision.appendChild(actions);\n }\n main.appendChild(ownerDecision);\n }\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeReconciliationRecoveryPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\"invalid reconciliation recovery payload\");\n }\n const recoveries = Array.isArray(payload.recoveries) ? payload.recoveries : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n recoveries: recoveries.filter((entry) => entry && typeof entry === \"object\"\n && entry.recovery && typeof entry.recovery === \"object\"\n && entry.recovery.dual_review && typeof entry.recovery.dual_review === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderReconciliationRecovery() {\n const payload = state.reconciliationRecovery.payload;\n const notice = el(\"reconciliation-recovery-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"reconciliationRecoveryLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"reconciliationRecoveryStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"reconciliationRecoveryUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"reconciliationRecoveryUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"reconciliationRecoveryNoOrder\");\n }\n const list = el(\"reconciliation-recovery-list\");\n list.replaceChildren();\n if (!payload.recoveries.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"reconciliationRecoveryEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.recoveries) {\n const recovery = entry.recovery || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"reconciliationRecoveryMeta\")\n .replace(\"{platform}\", recovery.platform || \"unknown\")\n .replace(\"{strategy}\", recovery.strategy_profile || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(recovery.recovery_id || \"unknown\");\n const detail = document.createElement(\"p\");\n detail.className = \"health-card__reason\";\n detail.textContent = t(\"reconciliationRecoveryDetail\")\n .replace(\"{state}\", recovery.reconciliation_state || \"unknown\")\n .replace(\"{samples}\", String(recovery.evidence_sample_count || 0))\n .replace(\"{review}\", recovery.dual_review?.outcome || \"unknown\")\n .replace(\"{lastObserved}\", recovery.last_observed_at || \"—\");\n const status = document.createElement(\"div\");\n status.className = \"health-card__meta\";\n const ready = payload.data_status === \"ready\"\n && entry.freshness?.data_status === \"ready\"\n && recovery.readiness === \"awaiting_human_confirmation\"\n && recovery.blocker_codes?.length === 0\n && recovery.dual_review?.outcome === \"approved\"\n && recovery.dual_review?.evidence_binding_sha256 === recovery.candidate_sha256;\n if (entry.confirmation) {\n status.textContent = t(\"reconciliationRecoveryConfirmed\");\n } else if (!ready) {\n status.textContent = t(\"reconciliationRecoveryBlocked\")\n .replace(\"{blockers}\", (recovery.blocker_codes || []).join(\", \") || recovery.readiness || \"unknown\");\n } else if (!state.auth.admin) {\n status.textContent = t(\"reconciliationRecoveryAdminOnly\");\n } else {\n status.textContent = t(\"reconciliationRecoveryReady\");\n }\n main.append(meta, title, detail, status);\n if (ready && !entry.confirmation && state.auth.admin) {\n const actions = document.createElement(\"div\");\n actions.className = \"owner-decision__actions\";\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"owner-decision__button\";\n button.dataset.reconciliationRecoveryConfirm = \"true\";\n button.dataset.recoveryId = recovery.recovery_id || \"\";\n button.dataset.candidateSha256 = recovery.candidate_sha256 || \"\";\n button.dataset.dualReviewBindingSha256 = recovery.dual_review?.evidence_binding_sha256 || \"\";\n const submitting = state.reconciliationRecovery.submittingRecoveryId === recovery.recovery_id;\n button.disabled = submitting;\n button.textContent = submitting ? t(\"reconciliationRecoverySubmitting\") : t(\"reconciliationRecoveryConfirm\");\n actions.appendChild(button);\n main.appendChild(actions);\n }\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"controlNext\");\n const stateText = document.createElement(\"strong\");\n stateText.textContent = entry.confirmation ? \"confirmed\" : (ready ? \"confirm\" : \"blocked\");\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, stateText, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n const M0_RESEARCH_DISPLAY_LIMIT = 100;\n\n const m0ResearchLabels = {\n subject: {\n asset_idea: { zh: \"资产观察\", en: \"Asset idea\" },\n theme_context: { zh: \"主题背景\", en: \"Theme context\" },\n strategy_hypothesis: { zh: \"研究假设\", en: \"Research hypothesis\" },\n risk_context: { zh: \"风险背景\", en: \"Risk context\" },\n },\n state: {\n candidate: { zh: \"待验证\", en: \"Candidate\" },\n source_verification_required: { zh: \"等待来源核验\", en: \"Source verification required\" },\n deferred: { zh: \"暂缓\", en: \"Deferred\" },\n context_only: { zh: \"仅作背景\", en: \"Context only\" },\n },\n freshness: {\n fresh: { zh: \"有效\", en: \"Fresh\" },\n stale: { zh: \"已过期\", en: \"Stale\" },\n unknown: { zh: \"未知\", en: \"Unknown\" },\n },\n horizon: {\n short: { zh: \"短期\", en: \"Short\" },\n medium: { zh: \"中期\", en: \"Medium\" },\n long: { zh: \"长期\", en: \"Long\" },\n not_applicable: { zh: \"不适用\", en: \"Not applicable\" },\n },\n confidence: {\n high: { zh: \"高\", en: \"High\" },\n medium: { zh: \"中\", en: \"Medium\" },\n low: { zh: \"低\", en: \"Low\" },\n mixed: { zh: \"混合\", en: \"Mixed\" },\n no_event: { zh: \"无事件\", en: \"No event\" },\n unknown: { zh: \"未知\", en: \"Unknown\" },\n },\n style: {\n event_driven: { zh: \"事件驱动\", en: \"Event driven\" },\n long_horizon_growth: { zh: \"长期成长\", en: \"Long-horizon growth\" },\n value_quality: { zh: \"价值质量\", en: \"Value quality\" },\n macro_context: { zh: \"宏观背景\", en: \"Macro context\" },\n mixed_research: { zh: \"综合研究\", en: \"Mixed research\" },\n },\n conflict: {\n none: { zh: \"无\", en: \"None\" },\n conflict: { zh: \"存在\", en: \"Present\" },\n unavailable: { zh: \"不可用\", en: \"Unavailable\" },\n },\n drift: {\n none: { zh: \"无\", en: \"None\" },\n drift: { zh: \"存在\", en: \"Present\" },\n unavailable: { zh: \"不可用\", en: \"Unavailable\" },\n },\n };\n\n function m0ResearchLabel(group, value) {\n const entry = m0ResearchLabels[group]?.[value];\n return entry ? (state.lang === \"zh\" ? entry.zh : entry.en) : \"—\";\n }\n\n function m0ResearchTimestamp(value) {\n if (typeof value !== \"string\" || !value) return \"—\";\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? \"—\" : parsed.toLocaleString();\n }\n\n function m0ResearchHorizons(values) {\n if (!Array.isArray(values) || !values.length) return \"—\";\n const labels = values.map((value) => m0ResearchLabel(\"horizon\", value)).filter((value) => value !== \"—\");\n return labels.length ? labels.join(\" / \") : \"—\";\n }\n\n function m0ResearchEntries(subjects) {\n if (!Array.isArray(subjects)) return [];\n const entries = [];\n for (const item of subjects) {\n if (!item || typeof item !== \"object\" || !item.subject || typeof item.subject !== \"object\") continue;\n if (!Array.isArray(item.observations)) continue;\n for (const observation of item.observations) {\n if (!observation || typeof observation !== \"object\" || !observation.research_context) continue;\n entries.push({\n subject: item.subject,\n observation,\n horizonConflict: item.horizon_conflict,\n historicalStaleHorizonDrift: item.historical_stale_horizon_drift,\n });\n }\n }\n return entries;\n }\n\n function normalizeM0ResearchPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid M0 research payload\");\n const subjects = Array.isArray(payload.subjects) ? payload.subjects : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n viewed_at: typeof payload.viewed_at === \"string\" ? payload.viewed_at : null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n subjects: subjects.filter((item) => item && typeof item === \"object\"\n && item.subject && typeof item.subject === \"object\" && Array.isArray(item.observations)),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderM0Research() {\n const payload = state.m0Research.payload;\n const entries = m0ResearchEntries(payload.subjects);\n const shownEntries = entries.slice(0, M0_RESEARCH_DISPLAY_LIMIT);\n const notice = el(\"m0-research-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"m0ResearchLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"m0ResearchStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"m0ResearchUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"m0ResearchUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n const more = entries.length > M0_RESEARCH_DISPLAY_LIMIT\n ? ` ${t(\"m0ResearchMore\").replace(\"{count}\", String(M0_RESEARCH_DISPLAY_LIMIT))}`\n : \"\";\n notice.textContent = `${t(\"m0ResearchNoOrder\")}${more}`;\n }\n\n const list = el(\"m0-research-list\");\n list.replaceChildren();\n if (!shownEntries.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"m0ResearchEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of shownEntries) {\n const subject = entry.subject || {};\n const observation = entry.observation || {};\n const context = observation.research_context || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"m0ResearchMeta\")\n .replace(\"{kind}\", m0ResearchLabel(\"subject\", subject.kind))\n .replace(\"{viewed}\", m0ResearchTimestamp(payload.viewed_at));\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = typeof subject.identifier === \"string\" ? subject.identifier : \"—\";\n const stateFreshness = document.createElement(\"p\");\n stateFreshness.className = \"health-card__reason\";\n stateFreshness.textContent = t(\"m0ResearchStateFreshness\")\n .replace(\"{state}\", m0ResearchLabel(\"state\", context.state))\n .replace(\"{freshness}\", m0ResearchLabel(\"freshness\", observation.freshness?.status));\n const horizons = document.createElement(\"div\");\n horizons.className = \"health-card__meta\";\n horizons.textContent = t(\"m0ResearchHorizons\")\n .replace(\"{primary}\", m0ResearchLabel(\"horizon\", context.primary_horizon))\n .replace(\"{suitable}\", m0ResearchHorizons(context.suitable_horizons));\n const evidence = document.createElement(\"div\");\n evidence.className = \"health-card__meta\";\n evidence.textContent = t(\"m0ResearchEvidence\")\n .replace(\"{confidence}\", m0ResearchLabel(\"confidence\", context.source_confidence))\n .replace(\"{style}\", m0ResearchLabel(\"style\", context.source_style))\n .replace(\"{digest}\", `${shortResearchDigest(observation.source_report_digest)} / ${shortResearchDigest(observation.source_entry_digest)}`);\n const consistency = document.createElement(\"div\");\n consistency.className = \"health-card__meta\";\n consistency.textContent = t(\"m0ResearchConsistency\")\n .replace(\"{conflict}\", m0ResearchLabel(\"conflict\", entry.horizonConflict?.status))\n .replace(\"{drift}\", m0ResearchLabel(\"drift\", entry.historicalStaleHorizonDrift?.status));\n main.append(meta, title, stateFreshness, horizons, evidence, consistency);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"M0\";\n const freshness = document.createElement(\"strong\");\n freshness.textContent = m0ResearchLabel(\"freshness\", observation.freshness?.status);\n const stateLabel = document.createElement(\"small\");\n stateLabel.textContent = m0ResearchLabel(\"state\", context.state);\n status.append(label, freshness, stateLabel);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function normalizeAdaptiveSelectionPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid adaptive selection payload\");\n const selections = Array.isArray(payload.selections) ? payload.selections : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n selections: selections.filter((item) => item && typeof item === \"object\"\n && typeof item.source_id === \"string\" && item.decision && typeof item.decision === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderAdaptiveSelection() {\n const payload = state.adaptiveSelection.payload;\n const notice = el(\"adaptive-selection-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"adaptiveSelectionLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"adaptiveSelectionStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"adaptiveSelectionUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"adaptiveSelectionUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n notice.textContent = payload.policy?.notice || t(\"adaptiveSelectionNoOrder\");\n }\n\n const list = el(\"adaptive-selection-list\");\n list.replaceChildren();\n if (!payload.selections.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"adaptiveSelectionEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.selections) {\n const decision = entry.decision || {};\n const context = decision.market_context || {};\n const recommended = decision.candidates?.find((item) => item.accepted\n && item.strategy_profile === decision.recommended_strategy_profile) || null;\n const rejected = (decision.candidates || []).filter((item) => !item.accepted);\n const reasons = (recommended?.reasons?.length ? recommended.reasons : rejected.flatMap((item) => item.reasons || []))\n .slice(0, 3)\n .join(\", \");\n\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"adaptiveSelectionMeta\")\n .replace(\"{source}\", entry.source_id || \"unknown\")\n .replace(\"{domain}\", domainLabel(context.domain || \"\"))\n .replace(\"{asOf}\", context.as_of || \"—\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = recommended\n ? `${t(\"adaptiveSelectionRecommended\")} · ${recommended.strategy_profile}`\n : t(\"adaptiveSelectionNoCandidate\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = reasons\n ? t(\"adaptiveSelectionReason\").replace(\"{reasons}\", reasons)\n : t(\"adaptiveSelectionNoOrder\");\n main.append(meta, title, reason);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"NO ORDER\";\n const score = document.createElement(\"strong\");\n score.textContent = typeof recommended?.score === \"number\" ? recommended.score.toFixed(3) : \"—\";\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n status.append(label, score, freshness);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function normalizeExecutionEvidencePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid execution evidence payload\");\n const deployments = Array.isArray(payload.deployments) ? payload.deployments : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n deployments: deployments.filter((item) => item && typeof item === \"object\" && item.deployment && typeof item.deployment === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function executionEvidenceReceiptOutcomeLabel(value) {\n return {\n not_due: t(\"executionEvidenceReceiptNotDue\"),\n no_action: t(\"executionEvidenceReceiptNoAction\"),\n risk_blocked: t(\"executionEvidenceReceiptRiskBlocked\"),\n submitted: t(\"executionEvidenceReceiptSubmitted\"),\n broker_acknowledged: t(\"executionEvidenceReceiptAcknowledged\"),\n partially_filled: t(\"executionEvidenceReceiptPartiallyFilled\"),\n filled: t(\"executionEvidenceReceiptFilled\"),\n reconciliation_required: t(\"executionEvidenceReceiptReconciliation\"),\n failed: t(\"executionEvidenceReceiptFailed\"),\n }[value] || t(\"executionEvidenceReceiptMissing\");\n }\n\n function executionEvidenceReceiptConfirmationLabel(value) {\n return {\n not_applicable: t(\"executionEvidenceConfirmationNotApplicable\"),\n not_observed: t(\"executionEvidenceConfirmationNotObserved\"),\n acknowledged: t(\"executionEvidenceConfirmationAcknowledged\"),\n partially_filled: t(\"executionEvidenceConfirmationPartiallyFilled\"),\n filled: t(\"executionEvidenceConfirmationFilled\"),\n reconciliation_required: t(\"executionEvidenceConfirmationReconciliation\"),\n }[value] || t(\"executionEvidenceConfirmationNotObserved\");\n }\n\n function renderExecutionEvidence() {\n const payload = state.executionEvidence.payload;\n const notice = el(\"execution-evidence-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"executionEvidenceLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"executionEvidenceStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"executionEvidenceUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"executionEvidenceUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"executionEvidenceNoOrder\");\n }\n\n const list = el(\"execution-evidence-list\");\n list.replaceChildren();\n if (!payload.deployments.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"executionEvidenceEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.deployments) {\n const deployment = entry.deployment || {};\n const strategy = deployment.strategy || {};\n const target = deployment.target || {};\n const capabilities = deployment.capabilities || {};\n const evidence = deployment.evidence || {};\n const executionReceipt = deployment.execution_receipt || null;\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"executionEvidenceMeta\")\n .replace(\"{platform}\", target.platform || \"unknown\")\n .replace(\"{environment}\", target.environment || \"unknown\")\n .replace(\"{source}\", entry.source_id || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(strategy.candidate_id || deployment.deployment_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = deployment.recommendation?.reason_code || t(\"executionEvidenceNoOrder\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"executionEvidenceDetail\")\n .replace(\"{strategy}\", evidence.strategy || \"unknown\")\n .replace(\"{data}\", evidence.target_data || \"unknown\")\n .replace(\"{execution}\", evidence.target_execution || \"unknown\")\n .replace(\"{shadow}\", capabilities.shadow || \"unknown\")\n .replace(\"{paper}\", capabilities.paper || \"unknown\");\n const receiptDetail = document.createElement(\"div\");\n receiptDetail.className = \"health-card__meta\";\n receiptDetail.textContent = executionReceipt\n ? t(\"executionEvidenceReceipt\")\n .replace(\"{outcome}\", executionEvidenceReceiptOutcomeLabel(executionReceipt.outcome))\n .replace(\"{confirmation}\", executionEvidenceReceiptConfirmationLabel(executionReceipt.broker_confirmation))\n : t(\"executionEvidenceReceiptMissing\");\n main.append(meta, title, reason, detail, receiptDetail);\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"executionEvidenceNext\");\n const recommendation = document.createElement(\"strong\");\n recommendation.textContent = deployment.recommendation?.code || \"parked\";\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, recommendation, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeRuntimeTargetLifecyclePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\"invalid runtime target lifecycle payload\");\n }\n const targets = Array.isArray(payload.targets) ? payload.targets : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n targets: targets.filter((entry) => entry && typeof entry === \"object\"\n && entry.target && typeof entry.target === \"object\"\n && entry.target.target && typeof entry.target.target === \"object\"\n && entry.target.monitoring && typeof entry.target.monitoring === \"object\"\n && entry.target.disposition && typeof entry.target.disposition === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function runtimeTargetLifecycleCheckLabel(value) {\n return {\n pass: t(\"runtimeTargetLifecycleCheckPass\"),\n attention: t(\"runtimeTargetLifecycleCheckAttention\"),\n not_due: t(\"runtimeTargetLifecycleCheckNotDue\"),\n not_applicable: t(\"runtimeTargetLifecycleCheckNotApplicable\"),\n unavailable: t(\"runtimeTargetLifecycleCheckUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleCheckUnavailable\");\n }\n\n function runtimeTargetLifecycleStateLabel(value) {\n return value === \"enabled\"\n ? t(\"runtimeTargetLifecycleStateEnabled\")\n : t(\"runtimeTargetLifecycleStateDisabled\");\n }\n\n function runtimeTargetLifecycleObservationLabel(value) {\n return {\n not_due: t(\"runtimeTargetLifecycleObservationNotDue\"),\n monitoring_only: t(\"runtimeTargetLifecycleObservationMonitoringOnly\"),\n not_applicable: t(\"runtimeTargetLifecycleObservationNotApplicable\"),\n attention: t(\"runtimeTargetLifecycleObservationAttention\"),\n unavailable: t(\"runtimeTargetLifecycleObservationUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleObservationUnavailable\");\n }\n\n function runtimeTargetLifecycleOrderEvidenceLabel(value) {\n return {\n not_collected: t(\"runtimeTargetLifecycleOrderEvidenceNotCollected\"),\n }[value] || t(\"runtimeTargetLifecycleOrderEvidenceNotCollected\");\n }\n\n function runtimeTargetLifecycleDispositionLabel(value) {\n return {\n continue_enabled_monitoring: t(\"runtimeTargetLifecycleDispositionEnabled\"),\n continue_disabled_validation: t(\"runtimeTargetLifecycleDispositionDisabled\"),\n parked: t(\"runtimeTargetLifecycleDispositionParked\"),\n }[value] || t(\"runtimeTargetLifecycleDispositionParked\");\n }\n\n function runtimeTargetLifecycleReasonLabel(value) {\n return {\n none: t(\"runtimeTargetLifecycleReasonNone\"),\n target_intentionally_disabled: t(\"runtimeTargetLifecycleReasonDisabled\"),\n runtime_guard_attention: t(\"runtimeTargetLifecycleReasonRuntimeGuard\"),\n execution_heartbeat_attention: t(\"runtimeTargetLifecycleReasonHeartbeat\"),\n monitoring_unavailable: t(\"runtimeTargetLifecycleReasonUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleReasonUnavailable\");\n }\n\n function renderRuntimeTargetLifecycle() {\n const payload = state.runtimeTargetLifecycle.payload;\n const notice = el(\"runtime-target-lifecycle-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"runtimeTargetLifecycleLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"runtimeTargetLifecycleStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"runtimeTargetLifecycleUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"runtimeTargetLifecycleUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n notice.textContent = payload.policy?.notice || t(\"runtimeTargetLifecycleNoOrder\");\n }\n\n const list = el(\"runtime-target-lifecycle-list\");\n list.replaceChildren();\n if (!payload.targets.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"runtimeTargetLifecycleEmpty\");\n list.appendChild(empty);\n return;\n }\n\n const targets = [...payload.targets].sort((left, right) => {\n const leftParked = left.target?.disposition?.code === \"parked\" ? 0 : 1;\n const rightParked = right.target?.disposition?.code === \"parked\" ? 0 : 1;\n if (leftParked !== rightParked) return leftParked - rightParked;\n return String(left.target?.target_id || \"\").localeCompare(String(right.target?.target_id || \"\"));\n });\n for (const entry of targets) {\n const target = entry.target || {};\n const configuration = target.target || {};\n const monitoring = target.monitoring || {};\n const disposition = target.disposition || {};\n const executionObservation = entry.execution_observation || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"runtimeTargetLifecycleMeta\")\n .replace(\"{platform}\", configuration.platform || \"unknown\")\n .replace(\"{state}\", runtimeTargetLifecycleStateLabel(configuration.configured_state))\n .replace(\"{mode}\", configuration.execution_mode || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(target.target_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = runtimeTargetLifecycleReasonLabel(disposition.reason_code);\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"runtimeTargetLifecycleDetail\")\n .replace(\"{guard}\", runtimeTargetLifecycleCheckLabel(monitoring.runtime_guard))\n .replace(\"{heartbeat}\", runtimeTargetLifecycleCheckLabel(monitoring.execution_heartbeat));\n const observation = document.createElement(\"div\");\n observation.className = \"health-card__meta\";\n observation.textContent = t(\"runtimeTargetLifecycleObservation\")\n .replace(\"{observation}\", runtimeTargetLifecycleObservationLabel(executionObservation.code))\n .replace(\"{evidence}\", runtimeTargetLifecycleOrderEvidenceLabel(executionObservation.order_or_fill_evidence));\n main.append(meta, title, reason, detail, observation);\n\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"runtimeTargetLifecycleNext\");\n const current = document.createElement(\"strong\");\n current.textContent = runtimeTargetLifecycleDispositionLabel(disposition.code);\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, current, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeResearchTaskPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid research task payload\");\n const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n tasks: tasks.filter((item) => item && typeof item === \"object\" && item.task && typeof item.task === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function shortResearchDigest(value) {\n const text = String(value || \"\");\n return /^[0-9a-f]{64}$/.test(text) ? `${text.slice(0, 10)}…` : \"—\";\n }\n\n function renderResearchTasks() {\n const payload = state.researchTasks.payload;\n const notice = el(\"research-task-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"researchTaskLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"researchTaskStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"researchTaskUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"researchTaskUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"researchTaskNoOrder\");\n }\n\n const list = el(\"research-task-list\");\n list.replaceChildren();\n if (!payload.tasks.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"researchTaskEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.tasks) {\n const task = entry.task || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"researchTaskMeta\")\n .replace(\"{type}\", task.task_type || \"research\")\n .replace(\"{domain}\", domainLabel(task.target?.domain || \"\"))\n .replace(\"{created}\", task.created_at ? new Date(task.created_at).toLocaleString() : \"—\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = `${task.target?.candidate_id || \"unknown\"} · ${task.task_id || \"unknown\"}`;\n const hypothesis = document.createElement(\"p\");\n hypothesis.className = \"health-card__reason\";\n hypothesis.textContent = task.experiment?.hypothesis || t(\"researchTaskNoOrder\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"researchTaskLimits\")\n .replace(\"{runs}\", String(task.experiment?.max_runs || \"—\"))\n .replace(\"{seconds}\", String(task.experiment?.max_wall_seconds || \"—\"))\n .replace(\"{p1}\", shortResearchDigest(task.evidence?.p1_input_digest))\n .replace(\"{p2}\", shortResearchDigest(task.evidence?.p2_config_digest))\n .replace(\"{p3}\", shortResearchDigest(task.evidence?.p3_evidence_id));\n main.append(meta, title, hypothesis, detail);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"NO ORDER\";\n const count = document.createElement(\"strong\");\n count.textContent = String(task.experiment?.max_runs || \"—\");\n const source = document.createElement(\"small\");\n source.textContent = entry.freshness?.data_status || \"unknown\";\n status.append(label, count, source);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function healthStatusLabel(status) {\n return { healthy: \"健康\", watch: \"观察\", review: \"复核\", critical: \"严重\" }[status] || \"未知\";\n }\n\n function normalizeHealthPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid health payload\");\n const strategies = Array.isArray(payload.strategies) ? payload.strategies : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n strategies: strategies.filter((item) => item && typeof item === \"object\" && [\"healthy\", \"watch\", \"review\", \"critical\"].includes(item.status)),\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderHealth() {\n const payload = state.health.payload;\n const summary = payload.summary || {};\n const statusText = payload.data_status === \"ready\"\n ? \"快照已加载\"\n : (payload.data_status === \"stale\" ? \"快照已过期\" : \"等待可用快照\");\n el(\"health-status\").textContent = statusText;\n el(\"health-computed-at\").textContent = payload.computed_at\n ? `最近计算:${new Date(payload.computed_at).toLocaleString()}`\n : \"最近计算:—\";\n el(\"health-count-total\").textContent = String(Number(summary.strategy_count) || 0);\n el(\"health-count-healthy\").textContent = String(Number(summary.healthy) || 0);\n el(\"health-count-watch\").textContent = String(Number(summary.watch) || 0);\n el(\"health-count-review\").textContent = String(Number(summary.review) || 0);\n el(\"health-count-critical\").textContent = String(Number(summary.critical) || 0);\n\n const notice = el(\"health-notice\");\n if (!state.auth.allowed) {\n notice.textContent = \"登录后读取私有策略健康快照;没有快照时不会展示虚构指标。\";\n } else if (payload.data_status === \"stale\") {\n notice.textContent = \"健康快照已超过允许的新鲜度窗口;页面保留原始状态,但不会把它当作当前健康结论。\";\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = \"还没有可用的策略健康快照;当前页面保持 fail-closed 空状态。\";\n } else if (payload.errors?.length) {\n notice.textContent = `快照已加载,但有 ${payload.errors.length} 个上游提示;缺失数据不会被替换成虚构指标。`;\n } else {\n notice.textContent = \"健康不等于已批准 live;正常实盘、资金和杠杆变更仍需人工确认。\";\n }\n\n const list = el(\"health-list\");\n list.replaceChildren();\n const strategies = payload.strategies.filter((item) => state.health.filter === \"all\" || item.status === state.health.filter);\n if (!strategies.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = \"暂无可展示的策略健康快照。\";\n list.appendChild(empty);\n return;\n }\n for (const item of strategies) {\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = `${healthStatusLabel(item.status)} · ${item.domain || \"unknown\"}`;\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(item.profile || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = `${item.decision?.label || \"证据不足,保持研究态\"}。${item.decision?.reason || \"没有可用的机器检查结果。\"}`;\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = `阶段:${item.review?.requested_stage || \"未标记\"} · 截至:${item.as_of || \"—\"}`;\n main.append(meta, title, reason, detail);\n const scoreBlock = document.createElement(\"div\");\n scoreBlock.className = \"health-card__score\";\n const scoreLabel = document.createElement(\"small\");\n scoreLabel.textContent = \"HEALTH\";\n const score = document.createElement(\"strong\");\n score.textContent = typeof item.score === \"number\" ? item.score.toFixed(1) : \"—\";\n const decision = document.createElement(\"small\");\n decision.textContent = item.decision?.code || \"evidence_missing\";\n scoreBlock.append(scoreLabel, score, decision);\n card.append(main, scoreBlock);\n list.appendChild(card);\n }\n }\n\n function renderConsoleView() {\n const controlButton = el(\"control-plane-view-button\");\n const healthButton = el(\"health-view-button\");\n const switchButton = el(\"switch-view-button\");\n const controlVisible = state.view === \"control\";\n const healthVisible = state.view === \"health\";\n el(\"control-plane-view\").hidden = !controlVisible;\n el(\"health-view\").hidden = !healthVisible;\n el(\"switch-view\").hidden = controlVisible || healthVisible;\n controlButton.classList.toggle(\"active\", controlVisible);\n healthButton.classList.toggle(\"active\", healthVisible);\n switchButton.classList.toggle(\"active\", !controlVisible && !healthVisible);\n }\n\n function render() {\n applyLanguage();\n renderRuntimeAuthorityStatus();\n renderConsoleView();\n renderControlPlane();\n renderM0Research();\n renderAdaptiveSelection();\n renderExecutionEvidence();\n renderRuntimeTargetLifecycle();\n renderResearchTasks();\n renderHealth();\n renderPlatforms();\n renderControls();\n renderSummary();\n renderPlanReadiness();\n renderAuth();\n renderAppVisibility();\n }\n\n async function refreshSession() {\n state.bootMessageKey = \"bootSession\";\n render();\n try {\n const session = await requestJson(\"/api/session\");\n state.auth = {\n available: true,\n allowed: Boolean(session.allowed),\n admin: Boolean(session.admin),\n login: session.login || null,\n };\n } catch {\n state.auth = { available: false, allowed: false, admin: false, login: null };\n }\n if (state.auth.allowed) {\n await refreshControlPlane();\n await refreshOwnerDecisions();\n await refreshReconciliationRecovery();\n await refreshM0Research();\n await refreshAdaptiveSelection();\n await refreshExecutionEvidence();\n await refreshRuntimeTargetLifecycle();\n await refreshResearchTasks();\n await refreshHealth();\n await refreshConfig();\n } else {\n state.bootMessageKey = \"bootPublic\";\n state.appReady = true;\n render();\n }\n }\n\n async function refreshHealth() {\n if (!state.auth.allowed) {\n renderHealth();\n return;\n }\n try {\n state.health.payload = normalizeHealthPayload(await requestJson(\"/api/strategy-health\"));\n } catch {\n state.health.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 },\n strategies: [],\n errors: [\"health_request_failed\"],\n };\n }\n renderHealth();\n }\n\n async function refreshControlPlane() {\n if (!state.auth.allowed) {\n renderControlPlane();\n return;\n }\n try {\n state.controlPlane.payload = normalizeControlPlanePayload(await requestJson(\"/api/control-plane\"));\n } catch {\n state.controlPlane.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 },\n attention: { status: \"unavailable\", reason_codes: [\"control_plane_request_failed\"] },\n candidates: [],\n policy: { p4_p5_automation: \"not_configured\", p6_owner_decision_required: true },\n errors: [\"control_plane_request_failed\"],\n };\n }\n renderControlPlane();\n }\n\n async function refreshOwnerDecisions() {\n if (!state.auth.allowed) {\n state.ownerDecisions = { data_status: \"unavailable\", candidates: [], errors: [] };\n renderControlPlane();\n return;\n }\n try {\n state.ownerDecisions = normalizeOwnerDecisionQueue(await requestJson(\"/api/owner-decisions\"));\n } catch {\n state.ownerDecisions = {\n data_status: \"unavailable\",\n candidates: [],\n errors: [\"owner_decision_queue_request_failed\"],\n };\n }\n renderControlPlane();\n }\n\n async function refreshReconciliationRecovery() {\n if (!state.auth.allowed) {\n renderReconciliationRecovery();\n return;\n }\n try {\n state.reconciliationRecovery.payload = normalizeReconciliationRecoveryPayload(\n await requestJson(\"/api/reconciliation-recovery\"),\n );\n } catch {\n state.reconciliationRecovery.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 },\n recoveries: [],\n policy: { human_confirmation_required: true, current_evidence_required: true, no_order: true, execution_authority_granted: false },\n errors: [\"reconciliation_recovery_request_failed\"],\n };\n }\n renderReconciliationRecovery();\n }\n\n async function refreshM0Research() {\n if (!state.auth.allowed) {\n renderM0Research();\n return;\n }\n try {\n state.m0Research.payload = normalizeM0ResearchPayload(await requestJson(\"/api/m0-research\"));\n } catch {\n state.m0Research.payload = {\n data_status: \"unavailable\",\n viewed_at: null,\n summary: {\n subject_count: 0,\n observation_count: 0,\n fresh_observation_count: 0,\n stale_observation_count: 0,\n unknown_observation_count: 0,\n horizon_conflict_count: 0,\n historical_stale_horizon_drift_count: 0,\n },\n subjects: [],\n policy: { authority: \"research_only\", no_order: true, permitted_next_step: \"research_validation_only\" },\n errors: [\"m0_research_request_failed\"],\n };\n }\n renderM0Research();\n }\n\n async function refreshAdaptiveSelection() {\n if (!state.auth.allowed) {\n renderAdaptiveSelection();\n return;\n }\n try {\n state.adaptiveSelection.payload = normalizeAdaptiveSelectionPayload(await requestJson(\"/api/adaptive-selection\"));\n } catch {\n state.adaptiveSelection.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 },\n selections: [],\n policy: { authority: \"shadow_only\", no_order: true, execution_authority_granted: false },\n errors: [\"adaptive_selection_request_failed\"],\n };\n }\n renderAdaptiveSelection();\n }\n\n async function recordOwnerDecision(button) {\n if (!state.auth.admin) return;\n const candidateId = String(button.dataset.candidateId || \"\");\n const candidateEvidenceSha256 = String(button.dataset.candidateEvidenceSha256 || \"\");\n const decision = String(button.dataset.ownerDecision || \"\");\n if (!candidateId || !candidateEvidenceSha256 || !decision) return;\n if (!window.confirm(t(\"ownerDecisionConfirm\"))) return;\n state.ownerDecisions.submittingCandidateId = candidateId;\n renderControlPlane();\n try {\n const response = await fetch(\"/api/owner-decisions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n candidate_id: candidateId,\n decision,\n candidate_evidence_sha256: candidateEvidenceSha256,\n }),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"ownerDecisionFailed\"));\n showToast(t(\"ownerDecisionSuccess\"), { duration: 7000 });\n await refreshControlPlane();\n await refreshOwnerDecisions();\n } catch (error) {\n showToast(`${t(\"ownerDecisionFailed\")}: ${error.message}`, { duration: 12000 });\n } finally {\n delete state.ownerDecisions.submittingCandidateId;\n renderControlPlane();\n }\n }\n\n async function recordReconciliationRecoveryConfirmation(button) {\n if (!state.auth.admin) return;\n const recoveryId = String(button.dataset.recoveryId || \"\");\n const candidateSha256 = String(button.dataset.candidateSha256 || \"\");\n const dualReviewBindingSha256 = String(button.dataset.dualReviewBindingSha256 || \"\");\n if (!recoveryId || !candidateSha256 || !dualReviewBindingSha256) return;\n if (!window.confirm(t(\"reconciliationRecoveryConfirmPrompt\"))) return;\n state.reconciliationRecovery.submittingRecoveryId = recoveryId;\n renderReconciliationRecovery();\n try {\n const response = await fetch(\"/api/reconciliation-recovery-confirmations\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n recovery_id: recoveryId,\n candidate_sha256: candidateSha256,\n dual_review_binding_sha256: dualReviewBindingSha256,\n }),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"reconciliationRecoveryFailed\"));\n showToast(t(\"reconciliationRecoverySuccess\"), { duration: 9000 });\n await refreshReconciliationRecovery();\n } catch (error) {\n showToast(`${t(\"reconciliationRecoveryFailed\")}: ${error.message}`, { duration: 12000 });\n } finally {\n state.reconciliationRecovery.submittingRecoveryId = null;\n renderReconciliationRecovery();\n }\n }\n\n async function refreshExecutionEvidence() {\n if (!state.auth.allowed) {\n renderExecutionEvidence();\n return;\n }\n try {\n state.executionEvidence.payload = normalizeExecutionEvidencePayload(await requestJson(\"/api/execution-evidence\"));\n } catch {\n state.executionEvidence.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 },\n deployments: [],\n policy: { execution_evidence_read_only: true, p6_owner_decision_required: true, limited_live_canary_active: false },\n errors: [\"execution_evidence_request_failed\"],\n };\n }\n renderExecutionEvidence();\n }\n\n async function refreshRuntimeTargetLifecycle() {\n if (!state.auth.allowed) {\n renderRuntimeTargetLifecycle();\n return;\n }\n try {\n state.runtimeTargetLifecycle.payload = normalizeRuntimeTargetLifecyclePayload(\n await requestJson(\"/api/runtime-target-lifecycle\"),\n );\n } catch {\n state.runtimeTargetLifecycle.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 },\n targets: [],\n policy: { lifecycle_status_read_only: true, no_order: true },\n errors: [\"runtime_target_lifecycle_request_failed\"],\n };\n }\n renderRuntimeTargetLifecycle();\n }\n\n async function refreshResearchTasks() {\n if (!state.auth.allowed) {\n renderResearchTasks();\n return;\n }\n try {\n state.researchTasks.payload = normalizeResearchTaskPayload(await requestJson(\"/api/research-tasks\"));\n } catch {\n state.researchTasks.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { task_count: 0 },\n tasks: [],\n policy: { research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false },\n errors: [\"research_task_request_failed\"],\n };\n }\n renderResearchTasks();\n }\n\n async function refreshStrategyProfiles() {\n state.bootMessageKey = \"bootStrategy\";\n render();\n try {\n const payload = await requestJson(\"/api/strategy-profiles\");\n applyStrategyProfiles(payload.strategyProfiles || []);\n if (payload.platformMeta) platformMeta = payload.platformMeta;\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n render();\n } catch {\n applyStrategyProfiles(defaultStrategyProfiles);\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n }\n }\n\n async function refreshConfig() {\n if (!state.auth.available || !state.auth.allowed) return;\n state.configSource = \"loading\";\n state.bootMessageKey = \"bootConfig\";\n render();\n try {\n const payload = await requestJson(\"/api/config\");\n if (payload.accountOptions) {\n applyStrategyProfiles(payload.strategyProfiles || defaultStrategyProfiles);\n state.accountOptions = normalizeAccountOptions(payload.accountOptions);\n if (payload.platformMeta) platformMeta = payload.platformMeta;\n state.repositories = normalizePlatformRepositories(payload.platformRepositories || {});\n state.currentStrategies = normalizeCurrentStrategies(payload.currentStrategies || {});\n state.configSource = \"private\";\n for (const platform of Object.keys(platformMeta)) {\n ensureAccountSelection(platform);\n syncStrategyForAccount(platform);\n }\n } else {\n state.configSource = \"default\";\n state.currentStrategies = {};\n }\n } catch (error) {\n state.configSource = \"default\";\n state.currentStrategies = {};\n if (isRequestTimeoutError(error)) {\n state.bootMessageKey = \"bootTimeout\";\n } else {\n state.bootMessageKey = \"bootPublic\";\n }\n } finally {\n state.appReady = true;\n render();\n }\n }\n\n function normalizeAccountOptions(raw) {\n const normalized = clone(defaultAccountOptions);\n for (const platform of Object.keys(platformMeta)) {\n if (!Array.isArray(raw[platform]) || !raw[platform].length) continue;\n normalized[platform] = raw[platform].map((item, index) => ({\n key: String(item.key || item.target_name || index),\n label: String(item.label || item.target_name || item.key || platform),\n target_name: String(item.target_name || item.key || \"\"),\n account_selector: item.account_selector ? String(item.account_selector) : \"\",\n deployment_selector: item.deployment_selector ? String(item.deployment_selector) : \"\",\n account_scope: item.account_scope ? String(item.account_scope) : \"\",\n service_name: item.service_name ? String(item.service_name) : \"\",\n cash_currency: item.cash_currency || item.market_currency || item.trading_currency\n ? String(item.cash_currency || item.market_currency || item.trading_currency).trim().toUpperCase()\n : \"\",\n supported_domains: normalizeSupportedDomains(platform, item),\n github_environment: item.github_environment ? String(item.github_environment) : \"\",\n variable_scope: item.variable_scope ? String(item.variable_scope) : \"\",\n plugin_mode: item.plugin_mode ? String(item.plugin_mode) : \"\",\n option_overlay_mode: item.option_overlay_mode ? normalizeOptionOverlayMode(item.option_overlay_mode) : \"\",\n cash_only_execution_mode: item.cash_only_execution_mode\n ? normalizeCashOnlyExecutionMode(item.cash_only_execution_mode)\n : \"\",\n dca_mode: item.dca_mode ? normalizeDcaMode(item.dca_mode) : \"\",\n dca_base_investment_usd: cleanDisplayPositiveNumber(item.dca_base_investment_usd),\n }));\n }\n return normalized;\n }\n\n function normalizeSupportedDomains(platform, item) {\n const raw = Array.isArray(item?.supported_domains)\n ? item.supported_domains\n : String(item?.supported_domains || \"\").split(/[\\s,;]+/);\n const cleaned = raw.map(cleanStrategyDomain).filter(Boolean);\n if (cleaned.length) return [...new Set(cleaned)];\n return inferSupportedDomains(platform, item || {});\n }\n\n function normalizeCurrentStrategies(raw) {\n const normalized = {};\n for (const platform of Object.keys(platformMeta)) {\n if (!raw[platform] || typeof raw[platform] !== \"object\" || Array.isArray(raw[platform])) continue;\n normalized[platform] = {};\n for (const [key, entry] of Object.entries(raw[platform])) {\n const profile = cleanStrategyProfile(entry?.strategy_profile);\n const minReservedCashUsd = cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd);\n const reservedCashRatio = cleanDisplayRatio(entry?.reserved_cash_ratio);\n const incomeLayerEnabled = cleanOptionalBoolean(entry?.income_layer_enabled);\n const incomeLayerStartUsd = cleanDisplayNumber(entry?.income_layer_start_usd);\n const incomeLayerMaxRatio = cleanDisplayRatio(entry?.income_layer_max_ratio);\n const optionOverlayEnabled = cleanOptionalBoolean(entry?.option_overlay_enabled);\n const cashOnlyExecution = cleanOptionalBoolean(entry?.cash_only_execution);\n const runtimeTargetEnabled = cleanOptionalBoolean(entry?.runtime_target_enabled);\n const dcaMode = entry?.dca_mode ? normalizeDcaMode(entry.dca_mode) : \"\";\n const dcaBaseInvestmentUsd = cleanDisplayPositiveNumber(entry?.dca_base_investment_usd);\n const executionMode = normalizeExecutionMode(entry?.execution_mode, entry?.dry_run_only);\n if (\n !profile &&\n !minReservedCashUsd &&\n !reservedCashRatio &&\n incomeLayerEnabled === null &&\n !incomeLayerStartUsd &&\n !incomeLayerMaxRatio &&\n optionOverlayEnabled === null &&\n cashOnlyExecution === null &&\n runtimeTargetEnabled === null &&\n !dcaMode &&\n !dcaBaseInvestmentUsd &&\n !executionMode\n ) continue;\n normalized[platform][String(key)] = {\n strategy_profile: profile,\n execution_mode: executionMode,\n dry_run_only: entry?.dry_run_only === true || entry?.dry_run_only === \"true\" || entry?.dry_run_only === \"1\",\n min_reserved_cash_usd: minReservedCashUsd,\n reserved_cash_ratio: reservedCashRatio,\n income_layer_enabled: incomeLayerEnabled,\n income_layer_start_usd: incomeLayerStartUsd,\n income_layer_max_ratio: incomeLayerMaxRatio,\n option_overlay_enabled: optionOverlayEnabled,\n cash_only_execution: cashOnlyExecution,\n runtime_target_enabled: runtimeTargetEnabled,\n dca_mode: dcaMode,\n dca_base_investment_usd: dcaBaseInvestmentUsd,\n source: entry?.source ? String(entry.source) : \"\",\n };\n }\n if (!Object.keys(normalized[platform]).length) delete normalized[platform];\n }\n return normalized;\n }\n\n function normalizePlatformRepositories(raw) {\n const normalized = clone(defaultRepositories);\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return normalized;\n for (const platform of Object.keys(platformMeta)) {\n const repository = String(raw[platform] || \"\").trim();\n if (/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/.test(repository)) {\n normalized[platform] = repository;\n }\n }\n return normalized;\n }\n\n async function dispatchSwitch() {\n if (!state.auth.allowed) return;\n showToast(t(\"dispatching\"), { duration: 0 });\n try {\n const response = await fetch(\"/api/switch\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildInputs()),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"dispatchFailed\"));\n showToast(t(\"dispatched\"), { duration: 4000 });\n if (payload.actions_url) window.open(payload.actions_url, \"_blank\", \"noopener,noreferrer\");\n await refreshConfig();\n } catch (error) {\n showToast(`${t(\"dispatchFailed\")}: ${error.message}`, { duration: 12000 });\n }\n }\n\n async function handleLogout() {\n await fetch(\"/api/logout\", { method: \"POST\" });\n window.location.reload();\n }\n\n function summaryText() {\n const inputs = buildInputs();\n return summaryRows(inputs).map(([label, value]) => `${label}: ${value}`).join(\"\\\n\");\n }\n\n document.querySelectorAll(\"[data-view]\").forEach((button) => button.addEventListener(\"click\", () => {\n state.view = [\"control\", \"health\", \"switch\"].includes(button.dataset.view) ? button.dataset.view : \"control\";\n renderConsoleView();\n if (state.view === \"control\") {\n refreshControlPlane();\n refreshOwnerDecisions();\n refreshReconciliationRecovery();\n refreshM0Research();\n refreshAdaptiveSelection();\n refreshExecutionEvidence();\n refreshRuntimeTargetLifecycle();\n refreshResearchTasks();\n }\n if (state.view === \"health\") refreshHealth();\n }));\n\n document.querySelectorAll(\"[data-health-filter]\").forEach((button) => button.addEventListener(\"click\", () => {\n document.querySelectorAll(\"[data-health-filter]\").forEach((node) => node.classList.remove(\"active\"));\n button.classList.add(\"active\");\n state.health.filter = button.dataset.healthFilter;\n renderHealth();\n }));\n\n el(\"control-plane-list\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-owner-decision]\");\n if (!button || button.disabled) return;\n recordOwnerDecision(button);\n });\n\n el(\"reconciliation-recovery-list\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-reconciliation-recovery-confirm]\");\n if (!button || button.disabled) return;\n recordReconciliationRecoveryConfirmation(button);\n });\n\n el(\"platform-strip\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-platform]\");\n if (!button) return;\n state.selected = button.dataset.platform;\n state.forms[state.selected].strategyTouched = false;\n render();\n });\n\n el(\"account-select\").addEventListener(\"change\", () => {\n state.forms[state.selected].accountKey = el(\"account-select\").value;\n state.forms[state.selected].runtimeTargetTouched = false;\n state.forms[state.selected].reservedCashTouched = false;\n state.forms[state.selected].incomeLayerTouched = false;\n state.forms[state.selected].optionOverlayTouched = false;\n state.forms[state.selected].cashOnlyExecutionTouched = false;\n state.forms[state.selected].dcaTouched = false;\n state.forms[state.selected].strategyTouched = false;\n syncStrategyForAccount(state.selected);\n render();\n });\n\n el(\"strategy-select\").addEventListener(\"change\", () => {\n state.forms[state.selected].strategy = el(\"strategy-select\").value;\n state.forms[state.selected].strategyTouched = true;\n state.forms[state.selected].incomeLayerTouched = false;\n state.forms[state.selected].optionOverlayTouched = false;\n state.forms[state.selected].dcaTouched = false;\n syncIncomeLayerForAccount(state.selected);\n syncOptionOverlayForAccount(state.selected);\n syncDcaForAccount(state.selected);\n render();\n });\n\n el(\"mode-control\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-mode]\");\n if (!button || button.disabled) return;\n if (!supportedExecutionModesForPlatform(state.selected).includes(button.dataset.mode)) return;\n state.forms[state.selected].executionMode = button.dataset.mode;\n render();\n });\n\n el(\"plugin-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.pluginMode = normalizePluginMode(el(\"plugin-mode-select\").value);\n render();\n });\n\n el(\"runtime-target-enabled-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.runtimeTargetMode = normalizeRuntimeTargetMode(el(\"runtime-target-enabled-select\").value);\n form.runtimeTargetTouched = form.runtimeTargetMode !== \"current\";\n render();\n });\n\n el(\"income-layer-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerMode = normalizeIncomeLayerMode(el(\"income-layer-mode-select\").value);\n form.incomeLayerTouched = form.incomeLayerMode !== \"current\";\n if (form.incomeLayerMode === \"current\") {\n form.incomeLayerTouched = false;\n syncIncomeLayerForAccount(state.selected);\n }\n render();\n });\n\n el(\"income-layer-start-usd-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerTouched = true;\n form.incomeLayerMode = \"enabled\";\n form.incomeLayerStartUsd = el(\"income-layer-start-usd-input\").value.trim();\n render();\n });\n\n el(\"income-layer-max-ratio-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerTouched = true;\n form.incomeLayerMode = \"enabled\";\n form.incomeLayerMaxRatio = el(\"income-layer-max-ratio-input\").value.trim();\n render();\n });\n\n el(\"option-overlay-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.optionOverlayMode = normalizeOptionOverlayMode(el(\"option-overlay-mode-select\").value);\n form.optionOverlayTouched = form.optionOverlayMode !== \"current\";\n render();\n });\n\n el(\"cash-only-execution-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.cashOnlyExecutionMode = normalizeCashOnlyExecutionMode(el(\"cash-only-execution-mode-select\").value);\n form.cashOnlyExecutionTouched = form.cashOnlyExecutionMode !== \"current\";\n if (allowMarginExplicitlySelected(form)) reconcileExecutionCashPolicy(form, \"margin\");\n else restoreReserveAfterMarginDisabled(form);\n render();\n });\n\n el(\"dca-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.dcaTouched = true;\n form.dcaMode = normalizeDcaMode(el(\"dca-mode-select\").value);\n render();\n });\n\n el(\"dca-base-investment-usd-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.dcaTouched = true;\n form.dcaBaseInvestmentUsd = el(\"dca-base-investment-usd-input\").value.trim();\n render();\n });\n\n el(\"reserve-policy-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.reservePolicyMode = normalizeReservePolicyMode(el(\"reserve-policy-mode-select\").value);\n form.reservedCashTouched = form.reservePolicyMode !== \"current\";\n if (form.reservePolicyMode === \"current\") syncReservePolicyForAccount(state.selected);\n reconcileExecutionCashPolicy(form, \"reserve\");\n render();\n });\n\n el(\"min-reserved-cash-input\").addEventListener(\"input\", () => {\n state.forms[state.selected].reservedCashTouched = true;\n state.forms[state.selected].minReservedCashUsd = el(\"min-reserved-cash-input\").value.trim();\n render();\n });\n\n el(\"reserved-cash-ratio-input\").addEventListener(\"input\", () => {\n state.forms[state.selected].reservedCashTouched = true;\n state.forms[state.selected].reservedCashRatio = el(\"reserved-cash-ratio-input\").value.trim();\n render();\n });\n\n el(\"copy-button\").addEventListener(\"click\", async () => {\n try {\n await navigator.clipboard.writeText(summaryText());\n showToast(t(\"copied\"), { duration: 3000 });\n } catch {\n showToast(summaryText(), { duration: 0 });\n }\n });\n\n el(\"dispatch-button\").addEventListener(\"click\", dispatchSwitch);\n el(\"logout-button\").addEventListener(\"click\", handleLogout);\n el(\"lang-button\").addEventListener(\"click\", () => {\n state.lang = state.lang === \"zh\" ? \"en\" : \"zh\";\n localStorage.setItem(\"qsl-switch-lang\", state.lang);\n render();\n });\n\n applyStrategyProfiles(defaultStrategyProfiles);\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n render();\n boot();\n\n async function boot() {\n try {\n await refreshStrategyProfiles();\n await refreshSession();\n } catch {\n state.auth = { available: false, allowed: false, admin: false, login: null };\n state.configSource = \"default\";\n state.currentStrategies = {};\n state.bootMessageKey = \"bootTimeout\";\n state.appReady = true;\n render();\n }\n }\n"; +export const APP_JS = "\n\n let platformMeta = {\n binance: { label: \"Binance\", code: \"BN\", accent: \"var(--bn)\" },\n firstrade: { label: \"Firstrade\", code: \"FT\", accent: \"var(--ft)\" },\n ibkr: { label: \"IBKR\", code: \"IB\", accent: \"var(--ib)\" },\n longbridge: { label: \"LongBridge\", code: \"LB\", accent: \"var(--lb)\" },\n qmt: { label: \"QMT\", code: \"QM\", accent: \"var(--qmt)\" },\n schwab: { label: \"Schwab\", code: \"SW\", accent: \"var(--sw)\" },\n };\n\n const platformRepositories = {\n binance: \"QuantStrategyLab/BinancePlatform\",\n firstrade: \"QuantStrategyLab/FirstradePlatform\",\n ibkr: \"QuantStrategyLab/InteractiveBrokersPlatform\",\n longbridge: \"QuantStrategyLab/LongBridgePlatform\",\n qmt: \"QuantStrategyLab/QmtPlatform\",\n schwab: \"QuantStrategyLab/CharlesSchwabPlatform\",\n };\n // Alias for backward compatibility\n const defaultRepositories = platformRepositories;\n\n const defaultAccountOptions = window.__DEFAULT_ACCOUNT_OPTIONS__ || {\n binance: [{\"key\": \"default\", \"label\": \"Binance\", \"target_name\": \"default\", \"cash_currency\": \"USD\", \"supported_domains\": [\"crypto\"]}],\n firstrade: [{\"key\": \"preview\", \"label\": \"Firstrade\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\", \"service_name\": \"firstrade-quant-service\"}],\n ibkr: [{\"key\": \"preview\", \"label\": \"IBKR\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\", \"hk_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\"}],\n longbridge: [{\"key\": \"preview\", \"label\": \"LongBridge\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\", \"hk_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\"}],\n qmt: [{\"key\": \"default\", \"label\": \"QMT\", \"target_name\": \"default\", \"cash_currency\": \"CNY\", \"supported_domains\": [\"cn_equity\"], \"service_name\": \"qmt-quant-service\"}],\n schwab: [{\"key\": \"preview\", \"label\": \"Schwab\", \"target_name\": \"preview\", \"supported_domains\": [\"us_equity\"], \"cash_currency\": \"USD\", \"default_execution_mode\": \"live\", \"service_name\": \"charles-schwab-quant-service\"}],\n };\n\n const domainLabels = window.__DOMAIN_LABELS__ || {\n cn_equity: { zh: \"A股\", en: \"CN A-share\" },\n crypto: { zh: \"加密\", en: \"Crypto\" },\n hk_equity: { zh: \"港股\", en: \"HK Equity\" },\n us_equity: { zh: \"美股\", en: \"US Equity\" },\n };\n\n const platformConfig = window.__PLATFORM_CONFIG__ || {\n binance: {\n dry_run_only: false,\n margin_policy: false,\n reserved_cash: false,\n income_layer: false,\n option_overlay: false,\n dca: false,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n firstrade: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"firstrade-quant-service\",\n default_execution_mode: \"live\"\n },\n ibkr: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n longbridge: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"\",\n default_execution_mode: \"live\"\n },\n qmt: {\n dry_run_only: true,\n margin_policy: false,\n reserved_cash: false,\n income_layer: false,\n option_overlay: false,\n dca: false,\n execution_mode: \"paper\",\n service_name: \"qmt-quant-service\",\n default_execution_mode: \"paper\"\n },\n schwab: {\n dry_run_only: false,\n margin_policy: true,\n reserved_cash: true,\n income_layer: true,\n option_overlay: true,\n dca: true,\n execution_mode: \"live\",\n service_name: \"charles-schwab-quant-service\",\n default_execution_mode: \"live\"\n },\n };\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n const reservePolicyModes = [\"none\", \"ratio\", \"floor\", \"max\"];\n const incomeLayerModes = [\"enabled\", \"disabled\"];\n const optionOverlayModes = [\"enabled\", \"disabled\"];\n const cashOnlyExecutionModes = [\"enabled\", \"disabled\"];\n const runtimeTargetModes = [\"enabled\", \"disabled\"];\n const pluginModes = [\"none\"];\n const dcaModes = [\"fixed\", \"smart\"];\n const runtimeTargetEnabledVariable = \"RUNTIME_TARGET_ENABLED\";\n const incomeLayerEnabledVariable = \"INCOME_LAYER_ENABLED\";\n const incomeLayerStartUsdVariable = \"INCOME_LAYER_START_USD\";\n const incomeLayerMaxRatioVariable = \"INCOME_LAYER_MAX_RATIO\";\n const dcaProfileDefaults = window.__DCA_PROFILE_DEFAULTS__ || {\n nasdaq_sp500_smart_dca: { defaultMode: \"fixed\", defaultBaseInvestmentUsd: \"1000\" },\n ibit_smart_dca: { defaultMode: \"fixed\", defaultBaseInvestmentUsd: \"1000\" },\n };\n const APP_BOOT_TIMEOUT_MS = 15000;\n const platformMinReservedCashVariables = {\n longbridge: \"LONGBRIDGE_MIN_RESERVED_CASH_USD\",\n ibkr: \"IBKR_MIN_RESERVED_CASH_USD\",\n schwab: \"SCHWAB_MIN_RESERVED_CASH_USD\",\n firstrade: \"FIRSTRADE_MIN_RESERVED_CASH_USD\",\n };\n const platformReservedCashRatioVariables = {\n longbridge: \"LONGBRIDGE_RESERVED_CASH_RATIO\",\n ibkr: \"IBKR_RESERVED_CASH_RATIO\",\n schwab: \"SCHWAB_RESERVED_CASH_RATIO\",\n firstrade: \"FIRSTRADE_RESERVED_CASH_RATIO\",\n };\n\n const defaultStrategyProfiles = window.__DEFAULT_STRATEGY_PROFILES__ || [\n {\n \"profile\": \"tqqq_growth_income\",\n \"label\": \"纳斯达克增长收益\",\n \"label_en\": \"NASDAQ Growth Income\",\n \"label_zh\": \"纳斯达克增长收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"250000\",\n \"income_layer_max_ratio\": \"0.55\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.3,\n \"DGRO\": 0.2,\n \"SGOV\": 0.4,\n \"SPYI\": 0.08,\n \"QQQI\": 0.02\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"tqqq_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"250000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.03\"\n },\n {\n \"profile\": \"soxl_soxx_trend_income\",\n \"label\": \"半导体趋势收益\",\n \"label_en\": \"Semiconductor Trend Income\",\n \"label_zh\": \"半导体趋势收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"150000\",\n \"income_layer_max_ratio\": \"0.95\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.15,\n \"DGRO\": 0.1,\n \"SGOV\": 0.7,\n \"SPYI\": 0.04,\n \"QQQI\": 0.01\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_income_overlay_enabled\": true,\n \"option_income_overlay_recipe\": \"soxx_put_credit_spread_income_v1\",\n \"option_income_overlay_start_usd\": \"150000\",\n \"option_income_overlay_nav_risk_ratio\": \"0.01\"\n },\n {\n \"profile\": \"nasdaq_sp500_smart_dca\",\n \"label\": \"纳指标普定投\",\n \"label_en\": \"NASDAQ/S&P 500 DCA\",\n \"label_zh\": \"纳指标普定投\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"dca_enabled\": true,\n \"dca_default_mode\": \"fixed\",\n \"dca_default_base_investment_usd\": \"1000\"\n },\n {\n \"profile\": \"ibit_smart_dca\",\n \"label\": \"IBIT比特币定投\",\n \"label_en\": \"IBIT Bitcoin DCA\",\n \"label_zh\": \"IBIT比特币定投\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"dca_enabled\": true,\n \"dca_default_mode\": \"fixed\",\n \"dca_default_base_investment_usd\": \"1000\"\n },\n {\n \"profile\": \"global_etf_rotation\",\n \"label\": \"全球ETF轮动\",\n \"label_en\": \"Global ETF Rotation\",\n \"label_zh\": \"全球ETF轮动\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"income_layer_start_usd\": \"500000\",\n \"income_layer_max_ratio\": \"0.15\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.4,\n \"DGRO\": 0.25,\n \"SGOV\": 0.3,\n \"SPYI\": 0.05\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"500000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"russell_top50_leader_rotation\",\n \"label\": \"罗素Top50领涨\",\n \"label_en\": \"Russell Top50 Leaders\",\n \"label_zh\": \"罗素Top50领涨\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\",\n \"income_layer_start_usd\": \"300000\",\n \"income_layer_max_ratio\": \"0.25\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.45,\n \"DGRO\": 0.3,\n \"SGOV\": 0.25\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"300000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"tecl_xlk_trend_income\",\n \"label\": \"TECL/XLK趋势收益\",\n \"label_en\": \"TECL/XLK Trend Income\",\n \"label_zh\": \"TECL/XLK趋势收益\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"failed_promotion_vs_live_profiles\"\n },\n {\n \"profile\": \"us_equity_combo\",\n \"label\": \"美股核心组合\",\n \"label_en\": \"US Core Combo\",\n \"label_zh\": \"美股核心组合\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": true,\n \"option_overlay_enabled\": true,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\",\n \"income_layer_start_usd\": \"300000\",\n \"income_layer_max_ratio\": \"0.25\",\n \"income_layer_allocations\": {\n \"SCHD\": 0.25,\n \"DGRO\": 0.25,\n \"SGOV\": 0.2,\n \"SPYI\": 0.15,\n \"QQQI\": 0.15\n },\n \"option_overlay_live_gate\": \"promotion_required\",\n \"option_overlay_live_status\": \"research_only\",\n \"option_growth_overlay_enabled\": true,\n \"option_growth_overlay_recipe\": \"spy_leaps_growth_v1\",\n \"option_growth_overlay_start_usd\": \"300000\",\n \"option_growth_overlay_nav_budget_ratio\": \"0.015\"\n },\n {\n \"profile\": \"us_equity_combo_core\",\n \"label\": \"美股核心组合影子\",\n \"label_en\": \"US Core Combo Shadow\",\n \"label_zh\": \"美股核心组合影子\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"us_equity_combo_leveraged\",\n \"label\": \"美股加速组合\",\n \"label_en\": \"US Alpha Combo\",\n \"label_zh\": \"美股加速组合\",\n \"domain\": \"us_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"hk_global_etf_tactical_rotation\",\n \"label\": \"港股ETF战术轮动\",\n \"label_en\": \"HK ETF Tactical Rotation\",\n \"label_zh\": \"港股ETF战术轮动\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"hk_low_vol_dividend_quality_snapshot\",\n \"label\": \"港股红利质量\",\n \"label_en\": \"HK Dividend Quality\",\n \"label_zh\": \"港股红利质量\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"hk_equity_combo\",\n \"label\": \"港股恒生组合\",\n \"label_en\": \"HK Core Combo\",\n \"label_zh\": \"港股恒生组合\",\n \"domain\": \"hk_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"cn_industry_etf_rotation\",\n \"label\": \"A股行业ETF轮动\",\n \"label_en\": \"CN Industry ETF Rotation\",\n \"label_zh\": \"A股行业ETF轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"cn_industry_etf_rotation_aggressive\",\n \"label\": \"A股ETF轮动\",\n \"label_en\": \"CN ETF Rotation\",\n \"label_zh\": \"A股ETF轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"live_candidate\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"live_candidate_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_index_etf_tactical_rotation\",\n \"label\": \"A股宽基ETF战术轮动\",\n \"label_en\": \"CN Index ETF Tactical Rotation\",\n \"label_zh\": \"A股宽基ETF战术轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_tactical_rotation\",\n \"label\": \"创业板战术轮动\",\n \"label_en\": \"CN ChiNext Tactical Rotation\",\n \"label_zh\": \"创业板战术轮动\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_growth_momentum_quality\",\n \"label\": \"创业板成长动量质量\",\n \"label_en\": \"CN ChiNext Growth Momentum Quality\",\n \"label_zh\": \"创业板成长动量质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_dividend_quality_snapshot\",\n \"label\": \"A股红利质量\",\n \"label_en\": \"CN Dividend Quality\",\n \"label_zh\": \"A股红利质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_chinext_growth_momentum_quality_snapshot\",\n \"label\": \"创业板成长质量快照\",\n \"label_en\": \"CN ChiNext Growth Quality Snapshot\",\n \"label_zh\": \"创业板成长质量快照\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_star_growth_momentum_quality\",\n \"label\": \"科创板成长动量质量\",\n \"label_en\": \"CN STAR Growth Momentum Quality\",\n \"label_zh\": \"科创板成长动量质量\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"cn_equity_combo\",\n \"label\": \"A股进取组合\",\n \"label_en\": \"CN Alpha Combo\",\n \"label_zh\": \"A股进取组合\",\n \"domain\": \"cn_equity\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n },\n {\n \"profile\": \"crypto_live_pool_rotation\",\n \"label\": \"加密实时池轮动\",\n \"label_en\": \"Crypto Live Pool Rotation\",\n \"label_zh\": \"加密实时池轮动\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"missing_current_promotion_evidence_and_preauthorized_autonomy_policy\"\n },\n {\n \"profile\": \"crypto_btc_dca\",\n \"label\": \"BTC定投\",\n \"label_en\": \"BTC DCA\",\n \"label_zh\": \"BTC定投\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"shadow_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"paper\",\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"shadow_candidate_requires_evidence_package\"\n },\n {\n \"profile\": \"crypto_trend_rotation\",\n \"label\": \"山寨趋势轮动\",\n \"label_en\": \"Altcoin Trend\",\n \"label_zh\": \"山寨趋势轮动\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": false,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\"\n },\n {\n \"profile\": \"crypto_equity_combo\",\n \"label\": \"加密动量组合\",\n \"label_en\": \"Crypto Core Combo\",\n \"label_zh\": \"加密动量组合\",\n \"domain\": \"crypto\",\n \"runtime_enabled\": false,\n \"income_layer_enabled\": false,\n \"option_overlay_enabled\": false,\n \"combo_enabled\": true,\n \"lifecycle_stage\": \"research_active\",\n \"can_switch_live\": false,\n \"allowed_execution_modes\": [\n \"dry_run\"\n ],\n \"blocked_live_reason\": \"research_backtest_only_requires_evidence_package\",\n \"combo_mode\": \"dynamic\"\n }\n ];\n\n const localStrategyLabels = {\n tqqq_growth_income: { zh: \"纳斯达克增长收益\", en: \"NASDAQ Growth Income\" },\n soxl_soxx_trend_income: { zh: \"半导体趋势收益\", en: \"Semiconductor Trend Income\" },\n nasdaq_sp500_smart_dca: { zh: \"纳指标普定投\", en: \"NASDAQ/S&P 500 DCA\" },\n ibit_smart_dca: { zh: \"IBIT比特币定投\", en: \"IBIT Bitcoin DCA\" },\n global_etf_rotation: { zh: \"全球ETF轮动\", en: \"Global ETF Rotation\" },\n russell_top50_leader_rotation: { zh: \"罗素Top50领涨\", en: \"Russell Top50 Leaders\" },\n hk_global_etf_tactical_rotation: { zh: \"港股ETF战术轮动\", en: \"HK ETF Tactical Rotation\" },\n hk_low_vol_dividend_quality_snapshot: { zh: \"港股红利质量\", en: \"HK Dividend Quality\" },\n cn_industry_etf_rotation: { zh: \"A股行业ETF轮动\", en: \"CN Industry ETF Rotation\" },\n cn_dividend_quality_snapshot: { zh: \"A股红利质量\", en: \"CN Dividend Quality\" },\n us_equity_combo: { zh: \"美股核心组合\", en: \"US Core Combo\" },\n us_equity_combo_leveraged: { zh: \"美股加速组合\", en: \"US Alpha Combo\" },\n hk_equity_combo: { zh: \"港股恒生组合\", en: \"HK Core Combo\" },\n cn_industry_etf_rotation_aggressive: { zh: \"A股ETF轮动\", en: \"CN ETF Rotation\" },\n cn_stock_momentum_rotation: { zh: \"A股个股动量\", en: \"CN Stock Momentum\" },\n cn_equity_combo: { zh: \"A股进取组合\", en: \"CN Alpha Combo\" },\n crypto_btc_dca: { zh: \"BTC定投\", en: \"BTC DCA\" },\n crypto_trend_rotation: { zh: \"山寨趋势轮动\", en: \"Altcoin Trend\" },\n crypto_equity_combo: { zh: \"加密动量组合\", en: \"Crypto Core Combo\" },\n };\n\n const fallbackIncomeLayerDefaults = window.__INCOME_LAYER_DEFAULTS__ || {\n tqqq_growth_income: {\n startUsd: 250000,\n maxRatio: \"0.55\",\n allocations: { SCHD: 0.30, DGRO: 0.20, SGOV: 0.40, SPYI: 0.08, QQQI: 0.02 },\n },\n soxl_soxx_trend_income: {\n startUsd: 150000,\n maxRatio: \"0.95\",\n allocations: { SCHD: 0.15, DGRO: 0.10, SGOV: 0.70, SPYI: 0.04, QQQI: 0.01 },\n },\n global_etf_rotation: {\n startUsd: 500000,\n maxRatio: \"0.15\",\n allocations: { SCHD: 0.40, DGRO: 0.25, SGOV: 0.30, SPYI: 0.05 },\n },\n russell_top50_leader_rotation: {\n startUsd: 300000,\n maxRatio: \"0.25\",\n allocations: { SCHD: 0.45, DGRO: 0.30, SGOV: 0.25 },\n },\n us_equity_combo: {\n startUsd: 300000,\n maxRatio: \"0.25\",\n allocations: { SCHD: 0.25, DGRO: 0.25, SGOV: 0.20, SPYI: 0.15, QQQI: 0.15 },\n }};\n let incomeLayerDefaults = {};\n const fallbackOptionOverlayDefaults = window.__OPTION_OVERLAY_DEFAULTS__ || {\n tqqq_growth_income: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"tqqq_leaps_growth_v1\", startUsd: \"250000\", ratio: \"0.03\", ratioKind: \"budget\" },\n ],\n },\n soxl_soxx_trend_income: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"income\", recipe: \"soxx_put_credit_spread_income_v1\", startUsd: \"150000\", ratio: \"0.01\", ratioKind: \"risk\" },\n ],\n },\n global_etf_rotation: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"500000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n russell_top50_leader_rotation: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"300000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n us_equity_combo: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [\n { family: \"growth\", recipe: \"spy_leaps_growth_v1\", startUsd: \"300000\", ratio: \"0.015\", ratioKind: \"budget\" },\n ],\n },\n us_equity_combo_leveraged: {\n liveGate: \"promotion_required\",\n liveStatus: \"research_only\",\n families: [],\n }};\n let optionOverlayDefaults = {};\n\n const strategyDomains = [\"us_equity\", \"hk_equity\", \"cn_equity\", \"crypto\"];\n let strategyOptions = [];\n let strategyLabels = {};\n let strategyCatalog = {};\n\n\n const copy = {\n zh: {\n appTitle: \"QuantStrategyLab\",\n appSubtitle: \"策略与平台的日常管理\",\n controlPlaneView: \"决策\",\n healthView: \"系统状态\",\n switchView: \"策略设置\",\n controlPlaneEyebrow: \"待处理事项\",\n controlPlaneTitle: \"今天需要你处理什么?\",\n controlPlaneSubtitle: \"系统正常时这里保持为空;出现风险、异常或恢复事项时,按优先级显示。\",\n controlCandidateTotal: \"监控对象\",\n controlDeferred: \"待复核\",\n controlParked: \"暂停中\",\n controlOwnerDecision: \"待处理\",\n controlQueueEyebrow: \"优先处理\",\n controlQueueHint: \"有风险或需要确认时才出现\",\n controlCandidateBoard: \"需要你处理\",\n controlDataReady: \"已更新\",\n controlDataStale: \"更新延迟\",\n controlDataUnavailable: \"暂时无法读取\",\n controlAttentionResearchOnly: \"暂无待处理事项\",\n controlAttentionRequired: \"需要关注\",\n controlAttentionUnavailable: \"等待数据\",\n controlComputedAt: \"更新于:{time}\",\n controlLoginNotice: \"登录后查看待处理事项和运行状态。\",\n controlStaleNotice: \"数据更新延迟,暂不建议据此做新决定。\",\n controlUnavailableNotice: \"暂时无法读取最新状态,请稍后刷新。\",\n controlUpstreamNotice: \"部分数据暂不可用,请稍后重试。\",\n controlAttentionNotice: \"有 {deferred} 项待复核,{parked} 项已暂停。\",\n controlNormalNotice: \"当前没有需要你处理的事项。\",\n controlEmptyCandidates: \"当前没有待处理事项。\",\n controlNoRecommendation: \"暂未给出处理建议。\",\n controlItemMeta: \"{kind} · {domain} · 最近更新:{freshness}\",\n controlNext: \"处理建议\",\n controlStatus: \"当前状态\",\n ownerDecisionTitle: \"请选择下一步\",\n ownerDecisionAdminOnly: \"请由管理员确认。\",\n ownerDecisionReady: \"相关信息已就绪,请选择下一步。\",\n ownerDecisionRecorded: \"已保存:{decision}\",\n ownerDecisionApprove: \"确认试运行\",\n ownerDecisionPark: \"保持暂停\",\n ownerDecisionRetire: \"停止跟踪\",\n ownerDecisionConfirm: \"确认保存此项决定?\",\n ownerDecisionSubmitting: \"正在保存…\",\n ownerDecisionSuccess: \"决定已保存。\",\n ownerDecisionFailed: \"无法保存决定\",\n reconciliationRecoveryBoard: \"恢复事项\",\n reconciliationRecoveryLoginNotice: \"登录后查看需要恢复或复核的事项。\",\n reconciliationRecoveryStaleNotice: \"恢复信息更新延迟,请等待下一次检查。\",\n reconciliationRecoveryUnavailableNotice: \"暂时没有可用的恢复信息。\",\n reconciliationRecoveryUpstreamNotice: \"部分恢复信息暂不可用,请先查看阻断原因。\",\n reconciliationRecoveryEmpty: \"当前没有需要恢复的事项。\",\n reconciliationRecoveryMeta: \"{platform} · {strategy}\",\n reconciliationRecoveryDetail: \"状态:{state} · 样本:{samples} · 复核:{review} · 最近更新:{lastObserved}\",\n reconciliationRecoveryBlocked: \"仍需处理:{blockers}\",\n reconciliationRecoveryReady: \"相关核对已完成,等待确认。\",\n reconciliationRecoveryAdminOnly: \"请由管理员确认。\",\n reconciliationRecoveryConfirmed: \"恢复确认已保存。\",\n reconciliationRecoveryConfirm: \"确认恢复\",\n reconciliationRecoveryConfirmPrompt: \"确认保存恢复决定?\",\n reconciliationRecoverySubmitting: \"正在保存…\",\n reconciliationRecoverySuccess: \"恢复决定已保存。\",\n reconciliationRecoveryFailed: \"无法保存恢复决定\",\n reconciliationRecoveryNoOrder: \"查看当前恢复状态。\",\n executionEvidenceBoard: \"执行与成交记录\",\n executionEvidenceLoginNotice: \"登录后查看执行和成交记录。\",\n executionEvidenceStaleNotice: \"执行记录更新延迟,请结合最新平台状态判断。\",\n executionEvidenceUnavailableNotice: \"暂时没有可用的执行记录。\",\n executionEvidenceUpstreamNotice: \"部分执行记录暂不可用,请稍后重试。\",\n executionEvidenceEmpty: \"当前没有可展示的执行记录。\",\n executionEvidenceMeta: \"{platform} · 当前通道:{environment} · 来源:{source}\",\n executionEvidenceDetail: \"策略:{strategy} · 数据:{data} · 执行:{execution} · 观察:{shadow} · 模拟:{paper}\",\n executionEvidenceReceipt: \"执行回执:{outcome} · 券商确认:{confirmation}\",\n executionEvidenceReceiptMissing: \"执行回执:未采集\",\n executionEvidenceReceiptNotDue: \"未到应交易窗口\",\n executionEvidenceReceiptNoAction: \"策略未产生订单\",\n executionEvidenceReceiptRiskBlocked: \"已被风控拦截\",\n executionEvidenceReceiptSubmitted: \"已提交,尚未确认\",\n executionEvidenceReceiptAcknowledged: \"券商已确认\",\n executionEvidenceReceiptPartiallyFilled: \"部分成交\",\n executionEvidenceReceiptFilled: \"已成交\",\n executionEvidenceReceiptReconciliation: \"需要对账\",\n executionEvidenceReceiptFailed: \"执行失败,需复核\",\n executionEvidenceConfirmationNotApplicable: \"不适用\",\n executionEvidenceConfirmationNotObserved: \"未观察到确认\",\n executionEvidenceConfirmationAcknowledged: \"已确认\",\n executionEvidenceConfirmationPartiallyFilled: \"部分成交\",\n executionEvidenceConfirmationFilled: \"已成交\",\n executionEvidenceConfirmationReconciliation: \"需对账\",\n executionEvidenceNoOrder: \"查看当前执行和成交结果。\",\n executionEvidenceNext: \"处理建议\",\n runtimeTargetLifecycleBoard: \"平台运行状态\",\n runtimeTargetLifecycleLoginNotice: \"登录后查看平台运行状态。\",\n runtimeTargetLifecycleStaleNotice: \"平台状态更新延迟,请先检查运行情况。\",\n runtimeTargetLifecycleUnavailableNotice: \"暂时没有可用的平台状态。\",\n runtimeTargetLifecycleUpstreamNotice: \"部分平台状态暂不可用,请先核对。\",\n runtimeTargetLifecycleEmpty: \"当前没有可展示的平台状态。\",\n runtimeTargetLifecycleMeta: \"{platform} · {state} · 通道:{mode}\",\n runtimeTargetLifecycleDetail: \"运行监测:{guard} · 执行心跳:{heartbeat}\",\n runtimeTargetLifecycleObservation: \"本次执行:{observation} · 成交证据:{evidence}\",\n runtimeTargetLifecycleNoOrder: \"查看当前平台运行情况。\",\n runtimeTargetLifecycleNext: \"当前建议\",\n runtimeTargetLifecycleStateEnabled: \"已启用\",\n runtimeTargetLifecycleStateDisabled: \"已停用\",\n runtimeTargetLifecycleCheckPass: \"通过\",\n runtimeTargetLifecycleCheckAttention: \"需处理\",\n runtimeTargetLifecycleCheckNotDue: \"未到检查时间\",\n runtimeTargetLifecycleCheckNotApplicable: \"不适用\",\n runtimeTargetLifecycleCheckUnavailable: \"不可用\",\n runtimeTargetLifecycleObservationNotDue: \"未到应交易窗口\",\n runtimeTargetLifecycleObservationMonitoringOnly: \"仅监测通过\",\n runtimeTargetLifecycleObservationNotApplicable: \"目标停用,不适用\",\n runtimeTargetLifecycleObservationAttention: \"需要复核\",\n runtimeTargetLifecycleObservationUnavailable: \"不可用\",\n runtimeTargetLifecycleOrderEvidenceNotCollected: \"未采集订单/成交回执\",\n runtimeTargetLifecycleDispositionEnabled: \"持续监控\",\n runtimeTargetLifecycleDispositionDisabled: \"无执行验证\",\n runtimeTargetLifecycleDispositionParked: \"已暂停\",\n runtimeTargetLifecycleReasonNone: \"监测正常\",\n runtimeTargetLifecycleReasonDisabled: \"按配置停用,仍持续验证\",\n runtimeTargetLifecycleReasonRuntimeGuard: \"运行监测需要复核\",\n runtimeTargetLifecycleReasonHeartbeat: \"执行心跳需要复核\",\n runtimeTargetLifecycleReasonUnavailable: \"监测数据不可用\",\n m0ResearchBoard: \"市场研究\",\n m0ResearchLoginNotice: \"登录后查看市场研究和观察记录。\",\n m0ResearchStaleNotice: \"研究内容更新延迟,仅供历史参考。\",\n m0ResearchUnavailableNotice: \"暂时没有可用的市场研究。\",\n m0ResearchUpstreamNotice: \"部分研究内容暂不可用,请稍后重试。\",\n m0ResearchEmpty: \"当前没有可展示的市场研究。\",\n m0ResearchMore: \"显示前 {count} 条研究观察。\",\n m0ResearchMeta: \"{kind} · 查看于:{viewed}\",\n m0ResearchStateFreshness: \"状态:{state} · 新鲜度:{freshness}\",\n m0ResearchHorizons: \"主要观察期:{primary} · 适用观察期:{suitable}\",\n m0ResearchEvidence: \"置信:{confidence} · 风格:{style} · 资料摘要:{digest}\",\n m0ResearchConsistency: \"当前周期分歧:{conflict} · 历史失效漂移:{drift}\",\n m0ResearchNoOrder: \"查看当前研究摘要。\",\n adaptiveSelectionBoard: \"系统建议\",\n adaptiveSelectionLoginNotice: \"登录后查看系统建议。\",\n adaptiveSelectionStaleNotice: \"建议更新延迟,仅供历史参考。\",\n adaptiveSelectionUnavailableNotice: \"暂时没有可用的系统建议。\",\n adaptiveSelectionUpstreamNotice: \"部分建议暂不可用,请稍后重试。\",\n adaptiveSelectionEmpty: \"当前没有可展示的系统建议。\",\n adaptiveSelectionMeta: \"{source} · {domain} · 市场截至 {asOf}\",\n adaptiveSelectionRecommended: \"建议观察\",\n adaptiveSelectionNoCandidate: \"暂无合适建议\",\n adaptiveSelectionReason: \"原因:{reasons}\",\n adaptiveSelectionNoOrder: \"查看系统的当前建议。\",\n researchTaskBoard: \"自动化任务\",\n researchTaskLoginNotice: \"登录后查看自动化任务。\",\n researchTaskStaleNotice: \"任务信息更新延迟,仅供历史参考。\",\n researchTaskUnavailableNotice: \"暂时没有可用的自动化任务。\",\n researchTaskUpstreamNotice: \"部分任务信息暂不可用,请稍后重试。\",\n researchTaskEmpty: \"当前没有可展示的自动化任务。\",\n researchTaskMeta: \"{type} · {domain} · 创建于:{created}\",\n researchTaskLimits: \"研究预算:最多 {runs} 次 / {seconds} 秒\",\n researchTaskNoOrder: \"查看当前自动化任务。\",\n healthEyebrow: \"系统状态\",\n healthTitle: \"运行情况\",\n healthSubtitle: \"只突出需要关注的风险、延迟和恢复状态。\",\n healthTotal: \"策略总数\",\n healthHealthy: \"健康\",\n healthWatch: \"观察\",\n healthReview: \"需要复核\",\n healthCritical: \"严重\",\n healthBoard: \"策略状态\",\n bootKicker: \"初始化控制台\",\n bootTitle: \"读取策略配置\",\n bootMessage: \"正在读取登录状态、账号配置和当前状态。\",\n bootStrategy: \"正在读取策略目录。\",\n bootSession: \"正在验证登录状态。\",\n bootConfig: \"正在读取账号配置和当前状态。\",\n bootTimeout: \"加载超时,已切换到公开预览(登录后可重试)。\",\n bootPublic: \"公开预览已就绪。\",\n login: \"登录\",\n logout: \"退出\",\n signedInAs: \"已登录 {login}\",\n planEyebrow: \"策略设置\",\n planTitle: \"修改策略设置\",\n planSubtitle: \"选择平台、账户、策略和运行环境;保存前核对本次改动。\",\n planAdvancedSummary: \"高级设置\",\n planAdvancedHint: \"运行状态、插件、现金、收入层和定投通常沿用当前配置;需要改动时再展开。\",\n planScopeTitle: \"选择范围\",\n planScopeSubtitle: \"平台、账号、策略与目标环境\",\n planRuntimeTitle: \"运行保护\",\n planRuntimeSubtitle: \"停用、插件和附加层都受同一计划约束\",\n planOverlayTitle: \"策略附加层\",\n planOverlaySubtitle: \"仅使用策略已定义的默认边界\",\n planCashSubtitle: \"现金预留优先于融资;两者不能同时覆盖\",\n activePlatform: \"目标平台\",\n account: \"目标账号\",\n strategy: \"策略\",\n mode: \"运行环境\",\n live: \"实盘\",\n paper: \"旧版非实盘\",\n dryRun: \"模拟运行\",\n liveModeUnavailable: \"该策略暂不支持实盘,请选择非实盘。\",\n runtimeTargetMode: \"账号运行状态\",\n runtimeSectionTitle: \"运行与插件\",\n runtimeTargetCurrent: \"沿用当前状态\",\n runtimeTargetEnabled: \"启用\",\n runtimeTargetDisabled: \"禁用\",\n runtimeTargetModeMeta: \"停用后正式运行会跳过,模拟运行和健康检查仍可用。\",\n pluginMode: \"插件状态\",\n pluginModeNone: \"不挂载旧插件\",\n pluginModeMeta: \"当前候选未绑定插件;旧插件不会自动挂载。\",\n incomeLayerMode: \"收入层状态\",\n incomeLayerSectionTitle: \"收入层\",\n incomeLayerCurrent: \"沿用当前配置\",\n incomeLayerEnabled: \"开启收入层\",\n incomeLayerDisabled: \"关闭收入层\",\n incomeLayerNotSupported: \"该策略未定义收入层\",\n incomeLayerStartUsd: \"收入层起始金额\",\n incomeLayerMaxRatio: \"收入层最高比例\",\n incomeLayerModeMeta: \"仅对已定义收入层的美股策略生效。\",\n incomeLayerDefaultMeta: \"策略默认:起始 {start},最高 {ratio}。\",\n incomeLayerAllocationMeta: \"默认分配:{allocations}。\",\n incomeLayerStartMeta: \"总资产达到该金额后启用收入层。\",\n incomeLayerRatioMeta: \"例如 0.55 表示最高 55%。\",\n optionOverlayMode: \"期权层状态\",\n optionOverlaySectionTitle: \"期权层\",\n optionOverlayCurrent: \"沿用当前配置\",\n optionOverlayEnabled: \"启用期权层\",\n optionOverlayDisabled: \"关闭期权层\",\n optionOverlayNotSupported: \"该策略未定义期权层\",\n optionOverlayModeMeta: \"启用时使用策略默认的最佳 recipe 和预算,不在这里手动调比例。\",\n optionOverlayDefaultMeta: \"{defaults}\",\n optionOverlayFamilyGrowth: \"增长\",\n optionOverlayFamilyIncome: \"收入\",\n optionOverlayBudgetRatio: \"预算 {ratio}\",\n optionOverlayRiskRatio: \"风险 {ratio}\",\n cashOnlyExecutionMode: \"允许融资\",\n cashOnlyExecutionCurrent: \"沿用当前配置\",\n cashOnlyExecutionYes: \"是\",\n cashOnlyExecutionNo: \"否\",\n cashOnlyExecutionModeMeta: \"选「否」时只按真实现金下单,不会动用 margin 购买力。\",\n cashOnlyExecutionValueYes: \"是\",\n cashOnlyExecutionValueNo: \"否\",\n currentCashOnlyExecution: \"当前允许融资\",\n pendingCashOnlyExecution: \"待提交允许融资\",\n executionCashPolicyTitle: \"资本边界\",\n executionCashPolicyNote: \"允许融资与预留现金覆盖不能同时生效;选「是」会清空预留覆盖,设预留覆盖会强制「否」。\",\n executionCashMarginBlocksReserve: \"已选允许融资;提交时会清空预留现金覆盖。\",\n executionCashReserveBlocksMargin: \"已设预留现金覆盖;提交时会强制不允许融资。\",\n qmtPlatformCashNote: \"A 股 QMT 不使用 margin / 平台预留现金;现金约束在策略参数 execution_cash_reserve_ratio 内配置。\",\n qmtDryRunOnlyNote: \"当前平台仅支持模拟运行。\",\n binancePlatformNote: \"Binance 平台不使用券商级收入层与期权层;相关功能由策略内部实现。\",\n invalidExecutionCashPolicyNote: \"允许融资与预留现金覆盖冲突,请只保留一种约束。\",\n dcaMode: \"定投模式\",\n dcaSectionTitle: \"定投\",\n dcaModeFixed: \"定额定投\",\n dcaModeSmart: \"智能定投\",\n dcaBaseInvestmentUsd: \"定投基准金额\",\n dcaModeMeta: \"仅定投策略可配置。\",\n dcaDefaultMeta: \"默认:{mode},基准金额 {amount}。\",\n dcaNotSupported: \"该策略不是定投策略\",\n dcaPlatformNotSupported: \"当前平台不支持定投策略\",\n currentDca: \"当前定投设置\",\n pendingDca: \"待提交定投设置\",\n dcaText: \"{mode},基准金额 {amount}\",\n minReservedCash: \"最小预留现金 ({currency})\",\n reservedCashRatio: \"预留现金比例\",\n reservedCashMode: \"预留现金策略\",\n reservePolicyCurrent: \"沿用当前配置\",\n reservePolicyNone: \"不设置平台预留现金\",\n reservePolicyRatio: \"仅按比例\",\n reservePolicyFloor: \"仅按固定金额\",\n reservePolicyMax: \"固定金额和比例取较大值\",\n reservedCashModeMeta: \"选择是否沿用、清空或覆盖平台预留现金。\",\n reservedCashNone: \"不设置\",\n reservedCashDefault: \"未配置(平台默认:0 {currency} / 0%)\",\n reservedCashMeta: \"固定金额下限,可单独设置或与比例取较大值。\",\n reservedCashRatioMeta: \"例如 0.03 表示 3%。\",\n summary: \"风险与变更摘要\",\n summaryCurrent: \"当前边界\",\n summaryPending: \"待提交变更\",\n planReadinessTitle: \"提交前核对\",\n planCheckAccount: \"账号与作用范围\",\n planCheckStrategy: \"策略与目标环境\",\n planCheckRisk: \"现金与风险设置\",\n planCheckAuthority: \"实盘状态\",\n planCheckWaiting: \"等待配置\",\n planCheckSelected: \"已读取\",\n planCheckValid: \"已校验\",\n planCheckFix: \"需修正\",\n planCheckNonLive: \"非实盘\",\n planCheckNoAuthority: \"未就绪\",\n planAuditNote: \"本次修改会保存到变更记录。\",\n copySummary: \"复制状态\",\n loginToRun: \"登录后提交计划\",\n loadingConfig: \"读取配置中\",\n configureAccounts: \"配置账号后切换\",\n runSwitch: \"保存设置\",\n noChanges: \"无变更\",\n readonlyNote: \"登录后可保存设置。\",\n publicReadonly: \"登录后查看账号配置。\",\n loadingConfigNote: \"正在读取账号配置和当前状态。\",\n missingConfigNote: \"账号配置未加载,暂时不能执行。\",\n readyNote: \"请核对上方改动后保存。\",\n invalidStrategyNote: \"当前账号没有可执行策略,暂时不能切换。\",\n invalidReservePolicyNote: \"请为当前预留现金策略填写有效金额或比例。\",\n invalidIncomeLayerNote: \"请填写有效的收入层起始金额和最高比例。\",\n invalidOptionOverlayNote: \"当前策略未定义可启用的期权层。\",\n invalidDcaNote: \"请填写有效的定投模式和基准金额。\",\n noAccount: \"没有账号选项\",\n noStrategy: \"没有支持的策略\",\n repository: \"平台仓库\",\n selectedAccount: \"账号\",\n selectedMarket: \"市场\",\n currentRuntimeTarget: \"当前账号状态\",\n pendingRuntimeTarget: \"待提交账号状态\",\n reservedCashPolicy: \"当前预留现金\",\n currentIncomeLayer: \"当前收入层\",\n pendingIncomeLayer: \"待提交收入层\",\n currentOptionOverlay: \"当前期权层\",\n pendingOptionOverlay: \"待提交期权层\",\n pendingReservedCashPolicy: \"待提交预留现金\",\n pendingMode: \"待提交模式\",\n currentPluginMode: \"当前插件范围\",\n pendingPluginMode: \"待提交插件范围\",\n unchanged: \"不变\",\n copied: \"已复制状态\",\n dispatching: \"正在触发工作流...\",\n dispatched: \"已触发工作流\",\n dispatchFailed: \"触发失败\",\n targetMeta: \"目标 {target} · 服务 {service} · 市场 {domains}\",\n strategyMeta: \"支持市场:{domains}\",\n strategyLifecycleMeta: \"当前门槛 {stage}\",\n strategyBlockedCountMeta: \"{count} 个策略未达 live 门槛\",\n strategyDefaultBlockedMeta: \"默认策略 {profile} 已阻断:{reason}\",\n usEquity: \"美股\",\n hkEquity: \"港股\",\n cnEquity: \"A股\",\n cryptoEquity: \"加密\",\n currentStrategy: \"当前策略\",\n nextStrategy: \"切换策略\",\n notRead: \"读取失败\",\n runtimeTargetOn: \"启用\",\n runtimeTargetOff: \"禁用\",\n incomeLayerDefault: \"开启,{start}起 {ratio}\",\n incomeLayerOff: \"关闭\",\n incomeLayerOn: \"开启,起始 {start},最高 {ratio}\",\n optionOverlayOff: \"关闭\",\n optionOverlayOn: \"开启\",\n optionOverlayDefaultSimple: \"开启\",\n optionOverlayDefault: \"开启,{detail}\",\n cashOnlyExecutionDefault: \"仅用现金\",\n },\n en: {\n appTitle: \"QuantStrategyLab\",\n appSubtitle: \"Daily management for strategies and platforms\",\n controlPlaneView: \"Decisions\",\n healthView: \"System status\",\n switchView: \"Strategy settings\",\n controlPlaneEyebrow: \"To do\",\n controlPlaneTitle: \"What needs your attention today?\",\n controlPlaneSubtitle: \"This stays empty when everything is normal. Risks, exceptions, and recovery items appear in priority order.\",\n controlCandidateTotal: \"Monitored items\",\n controlDeferred: \"To review\",\n controlParked: \"Paused\",\n controlOwnerDecision: \"To do\",\n controlQueueEyebrow: \"Priority\",\n controlQueueHint: \"Only appears when action is needed\",\n controlCandidateBoard: \"Needs your attention\",\n controlDataReady: \"Up to date\",\n controlDataStale: \"Update delayed\",\n controlDataUnavailable: \"Unavailable\",\n controlAttentionResearchOnly: \"Nothing to do\",\n controlAttentionRequired: \"Needs attention\",\n controlAttentionUnavailable: \"Waiting for data\",\n controlComputedAt: \"Updated: {time}\",\n controlLoginNotice: \"Sign in to see your to-do items and runtime status.\",\n controlStaleNotice: \"Data is delayed. Avoid making a new decision from it for now.\",\n controlUnavailableNotice: \"The latest status is temporarily unavailable. Please refresh later.\",\n controlUpstreamNotice: \"Some data is temporarily unavailable. Please retry later.\",\n controlAttentionNotice: \"{deferred} item(s) need review and {parked} are paused.\",\n controlNormalNotice: \"There is nothing you need to handle right now.\",\n controlEmptyCandidates: \"There is nothing to handle right now.\",\n controlNoRecommendation: \"No action is recommended yet.\",\n controlItemMeta: \"{kind} · {domain} · updated {freshness}\",\n controlNext: \"Recommended action\",\n controlStatus: \"Current status\",\n ownerDecisionTitle: \"Choose the next step\",\n ownerDecisionAdminOnly: \"An administrator needs to confirm this.\",\n ownerDecisionReady: \"The relevant information is ready. Choose the next step.\",\n ownerDecisionRecorded: \"Saved: {decision}\",\n ownerDecisionApprove: \"Confirm trial run\",\n ownerDecisionPark: \"Keep parked\",\n ownerDecisionRetire: \"Stop tracking\",\n ownerDecisionConfirm: \"Save this decision?\",\n ownerDecisionSubmitting: \"Saving…\",\n ownerDecisionSuccess: \"Decision saved.\",\n ownerDecisionFailed: \"Could not save the decision\",\n reconciliationRecoveryBoard: \"Recovery items\",\n reconciliationRecoveryLoginNotice: \"Sign in to see recovery and review items.\",\n reconciliationRecoveryStaleNotice: \"Recovery information is delayed. Wait for the next check.\",\n reconciliationRecoveryUnavailableNotice: \"Recovery information is temporarily unavailable.\",\n reconciliationRecoveryUpstreamNotice: \"Some recovery information is unavailable. Review the blockers first.\",\n reconciliationRecoveryEmpty: \"There are no recovery items right now.\",\n reconciliationRecoveryMeta: \"{platform} · {strategy}\",\n reconciliationRecoveryDetail: \"status: {state} · samples: {samples} · review: {review} · updated: {lastObserved}\",\n reconciliationRecoveryBlocked: \"Still needed: {blockers}\",\n reconciliationRecoveryReady: \"The relevant checks are complete and await confirmation.\",\n reconciliationRecoveryAdminOnly: \"An administrator needs to confirm this.\",\n reconciliationRecoveryConfirmed: \"Recovery confirmation saved.\",\n reconciliationRecoveryConfirm: \"Confirm recovery\",\n reconciliationRecoveryConfirmPrompt: \"Save this recovery decision?\",\n reconciliationRecoverySubmitting: \"Saving…\",\n reconciliationRecoverySuccess: \"Recovery decision saved.\",\n reconciliationRecoveryFailed: \"Could not save the recovery decision\",\n reconciliationRecoveryNoOrder: \"View the current recovery status.\",\n executionEvidenceBoard: \"Execution and fills\",\n executionEvidenceLoginNotice: \"Sign in to see execution and fill records.\",\n executionEvidenceStaleNotice: \"Execution records are delayed. Check the latest platform status as well.\",\n executionEvidenceUnavailableNotice: \"Execution records are temporarily unavailable.\",\n executionEvidenceUpstreamNotice: \"Some execution records are unavailable. Please retry later.\",\n executionEvidenceEmpty: \"There are no execution records to display.\",\n executionEvidenceMeta: \"{platform} · current lane: {environment} · source: {source}\",\n executionEvidenceDetail: \"strategy: {strategy} · data: {data} · execution: {execution} · observe: {shadow} · simulated: {paper}\",\n executionEvidenceReceipt: \"execution receipt: {outcome} · broker confirmation: {confirmation}\",\n executionEvidenceReceiptMissing: \"execution receipt: not collected\",\n executionEvidenceReceiptNotDue: \"not in a due window\",\n executionEvidenceReceiptNoAction: \"strategy produced no order\",\n executionEvidenceReceiptRiskBlocked: \"blocked by risk controls\",\n executionEvidenceReceiptSubmitted: \"submitted; not yet confirmed\",\n executionEvidenceReceiptAcknowledged: \"broker acknowledged\",\n executionEvidenceReceiptPartiallyFilled: \"partially filled\",\n executionEvidenceReceiptFilled: \"filled\",\n executionEvidenceReceiptReconciliation: \"reconciliation required\",\n executionEvidenceReceiptFailed: \"execution failed; review required\",\n executionEvidenceConfirmationNotApplicable: \"not applicable\",\n executionEvidenceConfirmationNotObserved: \"not observed\",\n executionEvidenceConfirmationAcknowledged: \"acknowledged\",\n executionEvidenceConfirmationPartiallyFilled: \"partially filled\",\n executionEvidenceConfirmationFilled: \"filled\",\n executionEvidenceConfirmationReconciliation: \"reconciliation required\",\n executionEvidenceNoOrder: \"View the current execution and fill results.\",\n executionEvidenceNext: \"Recommended action\",\n runtimeTargetLifecycleBoard: \"Platform status\",\n runtimeTargetLifecycleLoginNotice: \"Sign in to see platform status.\",\n runtimeTargetLifecycleStaleNotice: \"Platform status is delayed. Check runtime first.\",\n runtimeTargetLifecycleUnavailableNotice: \"Platform status is temporarily unavailable.\",\n runtimeTargetLifecycleUpstreamNotice: \"Some platform status is unavailable. Please review it first.\",\n runtimeTargetLifecycleEmpty: \"There is no platform status to display.\",\n runtimeTargetLifecycleMeta: \"{platform} · {state} · lane: {mode}\",\n runtimeTargetLifecycleDetail: \"runtime guard: {guard} · execution heartbeat: {heartbeat}\",\n runtimeTargetLifecycleObservation: \"execution: {observation} · order/fill evidence: {evidence}\",\n runtimeTargetLifecycleNoOrder: \"View the current platform runtime status.\",\n runtimeTargetLifecycleNext: \"Current recommendation\",\n runtimeTargetLifecycleStateEnabled: \"enabled\",\n runtimeTargetLifecycleStateDisabled: \"disabled\",\n runtimeTargetLifecycleCheckPass: \"pass\",\n runtimeTargetLifecycleCheckAttention: \"attention\",\n runtimeTargetLifecycleCheckNotDue: \"not due\",\n runtimeTargetLifecycleCheckNotApplicable: \"not applicable\",\n runtimeTargetLifecycleCheckUnavailable: \"unavailable\",\n runtimeTargetLifecycleObservationNotDue: \"not in a due window\",\n runtimeTargetLifecycleObservationMonitoringOnly: \"monitoring only\",\n runtimeTargetLifecycleObservationNotApplicable: \"target disabled; not applicable\",\n runtimeTargetLifecycleObservationAttention: \"needs review\",\n runtimeTargetLifecycleObservationUnavailable: \"unavailable\",\n runtimeTargetLifecycleOrderEvidenceNotCollected: \"not collected\",\n runtimeTargetLifecycleDispositionEnabled: \"continue monitoring\",\n runtimeTargetLifecycleDispositionDisabled: \"disabled validation\",\n runtimeTargetLifecycleDispositionParked: \"parked\",\n runtimeTargetLifecycleReasonNone: \"monitoring normal\",\n runtimeTargetLifecycleReasonDisabled: \"intentionally disabled; validation continues\",\n runtimeTargetLifecycleReasonRuntimeGuard: \"runtime guard needs review\",\n runtimeTargetLifecycleReasonHeartbeat: \"execution heartbeat needs review\",\n runtimeTargetLifecycleReasonUnavailable: \"monitoring data unavailable\",\n m0ResearchBoard: \"Market research\",\n m0ResearchLoginNotice: \"Sign in to see market research and observations.\",\n m0ResearchStaleNotice: \"Research is delayed and is provided for historical context only.\",\n m0ResearchUnavailableNotice: \"Market research is temporarily unavailable.\",\n m0ResearchUpstreamNotice: \"Some research content is unavailable. Please retry later.\",\n m0ResearchEmpty: \"There is no market research to display.\",\n m0ResearchMore: \"Showing the first {count} research observations.\",\n m0ResearchMeta: \"{kind} · viewed {viewed}\",\n m0ResearchStateFreshness: \"State: {state} · freshness: {freshness}\",\n m0ResearchHorizons: \"Primary horizon: {primary} · suitable horizons: {suitable}\",\n m0ResearchEvidence: \"Confidence: {confidence} · style: {style} · source digest: {digest}\",\n m0ResearchConsistency: \"Current horizon conflict: {conflict} · historical stale drift: {drift}\",\n m0ResearchNoOrder: \"View the current research summary.\",\n adaptiveSelectionBoard: \"System suggestions\",\n adaptiveSelectionLoginNotice: \"Sign in to see system suggestions.\",\n adaptiveSelectionStaleNotice: \"Suggestions are delayed and are provided for historical context only.\",\n adaptiveSelectionUnavailableNotice: \"System suggestions are temporarily unavailable.\",\n adaptiveSelectionUpstreamNotice: \"Some suggestions are unavailable. Please retry later.\",\n adaptiveSelectionEmpty: \"There are no system suggestions to display.\",\n adaptiveSelectionMeta: \"{source} · {domain} · market as of {asOf}\",\n adaptiveSelectionRecommended: \"Observe suggestion\",\n adaptiveSelectionNoCandidate: \"No suitable suggestion yet\",\n adaptiveSelectionReason: \"Reasons: {reasons}\",\n adaptiveSelectionNoOrder: \"View the system's current suggestions.\",\n researchTaskBoard: \"Automation tasks\",\n researchTaskLoginNotice: \"Sign in to see automation tasks.\",\n researchTaskStaleNotice: \"Task information is delayed and is provided for historical context only.\",\n researchTaskUnavailableNotice: \"Automation tasks are temporarily unavailable.\",\n researchTaskUpstreamNotice: \"Some task information is unavailable. Please retry later.\",\n researchTaskEmpty: \"There are no automation tasks to display.\",\n researchTaskMeta: \"{type} · {domain} · created {created}\",\n researchTaskLimits: \"Research budget: up to {runs} run(s) / {seconds}s\",\n researchTaskNoOrder: \"View the current automation tasks.\",\n healthEyebrow: \"System status\",\n healthTitle: \"Runtime status\",\n healthSubtitle: \"Highlights only the risks, delays, and recovery states that need attention.\",\n healthTotal: \"Strategies\",\n healthHealthy: \"Healthy\",\n healthWatch: \"Watch\",\n healthReview: \"Review\",\n healthCritical: \"Critical\",\n healthBoard: \"Strategy status\",\n bootKicker: \"Starting console\",\n bootTitle: \"Loading strategy config\",\n bootMessage: \"Reading session, account config, and current state.\",\n bootStrategy: \"Reading strategy catalog.\",\n bootSession: \"Checking sign-in status.\",\n bootConfig: \"Reading account config and current state.\",\n bootTimeout: \"Loading timed out; switched to public preview. Retry after signing in.\",\n bootPublic: \"Public preview is ready.\",\n login: \"Sign in\",\n logout: \"Sign out\",\n signedInAs: \"Signed in as {login}\",\n planEyebrow: \"Strategy settings\",\n planTitle: \"Change strategy settings\",\n planSubtitle: \"Choose a platform, account, strategy, and environment; review this change before saving.\",\n planAdvancedSummary: \"Advanced settings\",\n planAdvancedHint: \"Runtime, plugins, cash, overlays, and DCA normally retain their current setup. Expand only when changing one.\",\n planScopeTitle: \"Choose scope\",\n planScopeSubtitle: \"Platform, account, strategy, and target environment\",\n planRuntimeTitle: \"Runtime protection\",\n planRuntimeSubtitle: \"Disable state, plugins, and overlays share one plan boundary\",\n planOverlayTitle: \"Strategy overlays\",\n planOverlaySubtitle: \"Only strategy-defined default guardrails are available\",\n planCashSubtitle: \"Cash reserve takes precedence over margin; they cannot override together\",\n activePlatform: \"Target platform\",\n account: \"Target account\",\n strategy: \"Strategy\",\n mode: \"Target environment\",\n live: \"Live\",\n paper: \"Legacy non-live\",\n dryRun: \"Simulated run\",\n liveModeUnavailable: \"This strategy is not ready for Live. Choose a non-live environment.\",\n runtimeTargetMode: \"Account status\",\n runtimeSectionTitle: \"Runtime and plugins\",\n runtimeTargetCurrent: \"Keep current status\",\n runtimeTargetEnabled: \"Enabled\",\n runtimeTargetDisabled: \"Disabled\",\n runtimeTargetModeMeta: \"Disabled accounts skip live runs; dry runs and health checks still work.\",\n pluginMode: \"Plugin status\",\n pluginModeNone: \"Do not mount legacy plugins\",\n pluginModeMeta: \"The current candidate has no bound plugin; legacy plugins are not auto-mounted.\",\n incomeLayerMode: \"Income layer\",\n incomeLayerSectionTitle: \"Income layer\",\n incomeLayerCurrent: \"Keep current config\",\n incomeLayerEnabled: \"Enable income layer\",\n incomeLayerDisabled: \"Disable income layer\",\n incomeLayerNotSupported: \"No income layer for this strategy\",\n incomeLayerStartUsd: \"Income layer start amount\",\n incomeLayerMaxRatio: \"Income layer max ratio\",\n incomeLayerModeMeta: \"Only applies to US equity strategies with an income layer.\",\n incomeLayerDefaultMeta: \"Strategy default: starts at {start}, max {ratio}.\",\n incomeLayerAllocationMeta: \"Default allocation: {allocations}.\",\n incomeLayerStartMeta: \"Income layer activates after total assets reach this amount.\",\n incomeLayerRatioMeta: \"Use 0.55 for a 55% cap.\",\n optionOverlayMode: \"Option layer\",\n optionOverlaySectionTitle: \"Option layer\",\n optionOverlayCurrent: \"Keep current config\",\n optionOverlayEnabled: \"Enable option layer\",\n optionOverlayDisabled: \"Disable option layer\",\n optionOverlayNotSupported: \"No option layer for this strategy\",\n optionOverlayModeMeta: \"Enabled mode uses the strategy's default recipe and budget; ratios are not edited here.\",\n optionOverlayDefaultMeta: \"{defaults}\",\n optionOverlayFamilyGrowth: \"Growth\",\n optionOverlayFamilyIncome: \"Income\",\n optionOverlayBudgetRatio: \"budget {ratio}\",\n optionOverlayRiskRatio: \"risk {ratio}\",\n cashOnlyExecutionMode: \"Allow margin\",\n cashOnlyExecutionCurrent: \"Keep current config\",\n cashOnlyExecutionYes: \"Yes\",\n cashOnlyExecutionNo: \"No\",\n cashOnlyExecutionModeMeta: \"Choose No to use available cash only and avoid margin buying power.\",\n cashOnlyExecutionValueYes: \"Yes\",\n cashOnlyExecutionValueNo: \"No\",\n currentCashOnlyExecution: \"Current allow margin\",\n pendingCashOnlyExecution: \"Pending allow margin\",\n executionCashPolicyTitle: \"Capital boundary\",\n executionCashPolicyNote: \"Allow margin and reserve-cash overrides cannot both apply. Yes clears reserve overrides; reserve overrides force No.\",\n executionCashMarginBlocksReserve: \"Allow margin is selected; submitting will clear reserve-cash overrides.\",\n executionCashReserveBlocksMargin: \"Reserve-cash override is active; submitting will force allow margin to No.\",\n qmtPlatformCashNote: \"QMT A-share does not use margin or platform reserve cash; cash constraints live in strategy execution_cash_reserve_ratio.\",\n qmtDryRunOnlyNote: \"This platform supports no-order dry runs only; no live execution route is configured.\",\n binancePlatformNote: \"Binance does not use broker-level income/option layers; features are implemented inside strategies.\",\n invalidExecutionCashPolicyNote: \"Allow margin and reserve-cash overrides conflict. Keep only one constraint.\",\n dcaMode: \"DCA mode\",\n dcaSectionTitle: \"DCA\",\n dcaModeFixed: \"Fixed DCA\",\n dcaModeSmart: \"Smart DCA\",\n dcaBaseInvestmentUsd: \"Base DCA amount\",\n dcaModeMeta: \"Only DCA strategies can use this.\",\n dcaDefaultMeta: \"Default: {mode}, base amount {amount}.\",\n dcaNotSupported: \"This is not a DCA strategy\",\n dcaPlatformNotSupported: \"DCA not supported on this platform\",\n currentDca: \"Current DCA settings\",\n pendingDca: \"Pending DCA settings\",\n dcaText: \"{mode}, base amount {amount}\",\n minReservedCash: \"Minimum reserved cash ({currency})\",\n reservedCashRatio: \"Reserved cash ratio\",\n reservedCashMode: \"Reserved cash policy\",\n reservePolicyCurrent: \"Keep current config\",\n reservePolicyNone: \"No platform reserve\",\n reservePolicyRatio: \"Ratio only\",\n reservePolicyFloor: \"Fixed amount only\",\n reservePolicyMax: \"Max of amount and ratio\",\n reservedCashModeMeta: \"Choose whether to keep, clear, or override platform reserved cash.\",\n reservedCashNone: \"None\",\n reservedCashDefault: \"Not configured (platform default: 0 {currency} / 0%)\",\n reservedCashMeta: \"Fixed cash floor. Use alone or with a ratio.\",\n reservedCashRatioMeta: \"Use 0.03 for 3%.\",\n summary: \"Risk and change summary\",\n summaryCurrent: \"Current boundary\",\n summaryPending: \"Pending change\",\n planReadinessTitle: \"Before submission\",\n planCheckAccount: \"Account and scope\",\n planCheckStrategy: \"Strategy and environment\",\n planCheckRisk: \"Cash and risk settings\",\n planCheckAuthority: \"Live status\",\n planCheckWaiting: \"Waiting\",\n planCheckSelected: \"Read\",\n planCheckValid: \"Checked\",\n planCheckFix: \"Needs correction\",\n planCheckNonLive: \"Non-live\",\n planCheckNoAuthority: \"Not ready\",\n planAuditNote: \"This change is saved in the change history.\",\n copySummary: \"Copy state\",\n loginToRun: \"Sign in to submit a plan\",\n loadingConfig: \"Loading config\",\n configureAccounts: \"Configure accounts\",\n runSwitch: \"Save settings\",\n noChanges: \"No changes\",\n readonlyNote: \"Sign in to save settings.\",\n publicReadonly: \"Sign in to view account config.\",\n loadingConfigNote: \"Reading account config and current state.\",\n missingConfigNote: \"Account config is not loaded, so switching is disabled.\",\n readyNote: \"Review the changes above, then save.\",\n invalidStrategyNote: \"This account has no runnable strategy, so switching is disabled.\",\n invalidReservePolicyNote: \"Enter a valid amount or ratio for the selected reserved-cash policy.\",\n invalidIncomeLayerNote: \"Enter a valid income layer start amount and max ratio.\",\n invalidOptionOverlayNote: \"This strategy does not define an option layer to enable.\",\n invalidDcaNote: \"Enter a valid DCA mode and base amount.\",\n noAccount: \"No accounts\",\n noStrategy: \"No supported strategies\",\n repository: \"Repository\",\n selectedAccount: \"Account\",\n selectedMarket: \"Market\",\n currentRuntimeTarget: \"Current account status\",\n pendingRuntimeTarget: \"Pending account status\",\n reservedCashPolicy: \"Current reserved cash\",\n currentIncomeLayer: \"Current income layer\",\n pendingIncomeLayer: \"Pending income layer\",\n currentOptionOverlay: \"Current option layer\",\n pendingOptionOverlay: \"Pending option layer\",\n pendingReservedCashPolicy: \"Pending reserved cash\",\n pendingMode: \"Pending mode\",\n currentPluginMode: \"Current plugin scope\",\n pendingPluginMode: \"Pending plugin scope\",\n unchanged: \"Unchanged\",\n copied: \"State copied\",\n dispatching: \"Dispatching workflow...\",\n dispatched: \"Workflow dispatched\",\n dispatchFailed: \"Dispatch failed\",\n targetMeta: \"target {target} · service {service} · market {domains}\",\n strategyMeta: \"Markets: {domains}\",\n strategyLifecycleMeta: \"current gate {stage}\",\n strategyBlockedCountMeta: \"{count} strategies are blocked from live\",\n strategyDefaultBlockedMeta: \"Default strategy {profile} is blocked: {reason}\",\n usEquity: \"US equity\",\n hkEquity: \"HK equity\",\n cnEquity: \"CN A-share\",\n cryptoEquity: \"Crypto\",\n currentStrategy: \"Current strategy\",\n nextStrategy: \"Switch strategy\",\n notRead: \"Not read\",\n runtimeTargetOn: \"Enabled\",\n runtimeTargetOff: \"Disabled\",\n incomeLayerDefault: \"Enabled, {start} start, {ratio} max\",\n incomeLayerOff: \"Disabled\",\n incomeLayerOn: \"Enabled, starts at {start}, max {ratio}\",\n optionOverlayOff: \"Disabled\",\n optionOverlayOn: \"Enabled\",\n optionOverlayDefaultSimple: \"Strategy default: enabled\",\n optionOverlayDefault: \"Enabled, {detail}\",\n cashOnlyExecutionDefault: \"Cash only\",\n },\n };\n\n const storedLang = localStorage.getItem(\"qsl-switch-lang\");\n const initialLang = storedLang === \"zh\" || storedLang === \"en\"\n ? storedLang\n : ((navigator.language || \"\").toLowerCase().startsWith(\"zh\") ? \"zh\" : \"en\");\n const clone = (value) => JSON.parse(JSON.stringify(value));\n const defaultReserveForm = () => ({\n reservePolicyMode: \"current\",\n minReservedCashUsd: \"\",\n reservedCashRatio: \"\",\n reservedCashTouched: false,\n incomeLayerMode: \"current\",\n incomeLayerStartUsd: \"\",\n incomeLayerMaxRatio: \"\",\n incomeLayerTouched: false,\n optionOverlayMode: \"current\",\n optionOverlayTouched: false,\n cashOnlyExecutionMode: \"current\",\n cashOnlyExecutionTouched: false,\n runtimeTargetMode: \"current\",\n runtimeTargetTouched: false,\n dcaMode: \"fixed\",\n dcaBaseInvestmentUsd: \"\",\n dcaTouched: false,\n strategyTouched: false,\n });\n\n const state = {\n selected: \"longbridge\",\n lang: initialLang,\n view: \"control\",\n appReady: false,\n bootMessageKey: \"bootMessage\",\n auth: { available: false, allowed: false, admin: false, login: null },\n accountOptions: clone(defaultAccountOptions),\n currentStrategies: {},\n health: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 },\n strategies: [],\n },\n filter: \"all\",\n },\n controlPlane: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 },\n attention: { status: \"unavailable\", reason_codes: [\"control_plane_source_unavailable\"] },\n candidates: [],\n policy: { p4_p5_automation: \"not_configured\", p6_owner_decision_required: true },\n errors: [],\n },\n },\n ownerDecisions: {\n data_status: \"unavailable\",\n candidates: [],\n errors: [],\n },\n reconciliationRecovery: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 },\n recoveries: [],\n policy: { human_confirmation_required: true, current_evidence_required: true, no_order: true, execution_authority_granted: false },\n errors: [],\n },\n submittingRecoveryId: null,\n },\n m0Research: {\n payload: {\n data_status: \"unavailable\",\n viewed_at: null,\n summary: {\n subject_count: 0,\n observation_count: 0,\n fresh_observation_count: 0,\n stale_observation_count: 0,\n unknown_observation_count: 0,\n horizon_conflict_count: 0,\n historical_stale_horizon_drift_count: 0,\n },\n subjects: [],\n policy: { authority: \"research_only\", no_order: true, permitted_next_step: \"research_validation_only\" },\n errors: [],\n },\n },\n adaptiveSelection: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 },\n selections: [],\n policy: { authority: \"shadow_only\", no_order: true, execution_authority_granted: false },\n errors: [],\n },\n },\n executionEvidence: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 },\n deployments: [],\n policy: { execution_evidence_read_only: true, p6_owner_decision_required: true, limited_live_canary_active: false },\n errors: [],\n },\n },\n runtimeTargetLifecycle: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 },\n targets: [],\n policy: { lifecycle_status_read_only: true, no_order: true },\n errors: [],\n },\n },\n researchTasks: {\n payload: {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { task_count: 0 },\n tasks: [],\n policy: { research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false },\n errors: [],\n },\n },\n configSource: \"default\",\n repositories: clone(defaultRepositories),\n forms: {\n longbridge: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n ibkr: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n schwab: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n firstrade: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\", ...defaultReserveForm() },\n qmt: { accountKey: \"preview\", strategy: \"\", executionMode: \"paper\", pluginMode: \"none\", ...defaultReserveForm() },\n binance: { accountKey: \"preview\", strategy: \"\", executionMode: \"live\", pluginMode: \"none\" },\n },\n };\n\n const el = (id) => document.getElementById(id);\n const t = (key) => copy[state.lang][key] || copy.en[key] || key;\n let toastTimer = null;\n\n function showToast(message, { duration = 4000 } = {}) {\n const node = el(\"toast\");\n if (toastTimer) {\n window.clearTimeout(toastTimer);\n toastTimer = null;\n }\n node.textContent = message || \"\";\n if (message && duration > 0) {\n toastTimer = window.setTimeout(() => {\n node.textContent = \"\";\n toastTimer = null;\n }, duration);\n }\n }\n\n async function fetchWithTimeout(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {\n const controller = new AbortController();\n const timeoutId = window.setTimeout(() => controller.abort(), timeoutMs);\n try {\n return await fetch(url, { ...init, signal: controller.signal });\n } catch (error) {\n if (error?.name === \"AbortError\") {\n throw new Error(\"request timeout\");\n }\n throw error;\n } finally {\n window.clearTimeout(timeoutId);\n }\n }\n\n async function requestJson(url, init = {}, timeoutMs = APP_BOOT_TIMEOUT_MS) {\n const response = await fetchWithTimeout(url, { ...init, cache: \"no-store\" }, timeoutMs);\n if (!response.ok) throw new Error(\"request failed\");\n return response.json();\n }\n\n function isRequestTimeoutError(error) {\n return String(error?.message || \"\").toLowerCase() === \"request timeout\";\n }\n\n function optionsFor(platform) {\n return state.accountOptions[platform] && state.accountOptions[platform].length\n ? state.accountOptions[platform]\n : defaultAccountOptions[platform];\n }\n\n function selectedAccount(platform = state.selected) {\n const options = optionsFor(platform);\n const form = state.forms[platform];\n return options.find((option) => option.key === form.accountKey) || options[0];\n }\n\n function hasPrivateConfig() {\n return Boolean(state.auth.allowed && state.configSource === \"private\");\n }\n\n function cleanStrategyProfile(value) {\n const profile = String(value || \"\").trim();\n return /^[a-z0-9._=-]{1,120}$/.test(profile) ? profile : \"\";\n }\n\n function cleanStrategyDomain(value) {\n const domain = String(value || \"\").trim();\n return strategyDomains.includes(domain) ? domain : \"\";\n }\n\n function domainLabel(domain) {\n const entry = domainLabels[domain];\n if (entry) return state.lang === \"zh\" ? entry.zh : entry.en;\n return domain;\n }\n\n function platformSupportsMarginPolicy(platform = state.selected) {\n return platformConfig[platform]?.margin_policy ?? true;\n }\n\n function platformSupportsReservedCashPolicy(platform = state.selected) {\n return platformConfig[platform]?.reserved_cash ?? true;\n }\n\n function platformDryRunOnly(platform = state.selected) {\n return platformConfig[platform]?.dry_run_only ?? false;\n }\n\n function supportedExecutionModesForPlatform(platform = state.selected) {\n const modes = platformConfig[platform]?.supported_execution_modes;\n return Array.isArray(modes) ? modes.filter((mode) => mode === \"live\" || mode === \"dry_run\") : [];\n }\n\n function allowMarginExplicitlySelected(form) {\n return normalizeCashOnlyExecutionMode(form?.cashOnlyExecutionMode) === \"disabled\";\n }\n\n function reserveCashOverrideActive(form) {\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n return mode === \"ratio\" || mode === \"floor\" || mode === \"max\";\n }\n\n function executionCashPolicyConflict(form) {\n return allowMarginExplicitlySelected(form) && reserveCashOverrideActive(form);\n }\n\n function reconcileExecutionCashPolicy(form, changed) {\n if (!form) return;\n if (changed === \"margin\" && allowMarginExplicitlySelected(form)) {\n if (form.reservePolicyMode !== \"none\" && (form.minReservedCashUsd || form.reservedCashRatio)) {\n form._prevReserve = {\n mode: form.reservePolicyMode,\n floor: form.minReservedCashUsd,\n ratio: form.reservedCashRatio,\n };\n }\n form.reservePolicyMode = \"none\";\n form.reservedCashTouched = true;\n } else if (changed === \"reserve\" && reserveCashOverrideActive(form)) {\n form.cashOnlyExecutionMode = \"enabled\";\n form.cashOnlyExecutionTouched = true;\n }\n }\n\n function restoreReserveAfterMarginDisabled(form) {\n if (!form || allowMarginExplicitlySelected(form) || !form._prevReserve) return;\n form.reservePolicyMode = form._prevReserve.mode;\n form.minReservedCashUsd = form._prevReserve.floor;\n form.reservedCashRatio = form._prevReserve.ratio;\n form.reservedCashTouched = true;\n delete form._prevReserve;\n }\n\n function strategyDomain(profile) {\n return strategyCatalog[profile]?.domain || \"\";\n }\n\n function selectedCashCurrency(platform = state.selected, account = selectedAccount(platform)) {\n const configured = String(account?.cash_currency || \"\").trim().toUpperCase();\n if (configured === \"USD\" || configured === \"HKD\" || configured === \"CNY\") return configured;\n const domain = strategyDomain(state.forms[platform]?.strategy);\n if (domain === \"hk_equity\") return \"HKD\";\n if (domain === \"cn_equity\") return \"CNY\";\n return \"USD\";\n }\n\n function applyStrategyProfiles(rawProfiles) {\n const profiles = Array.isArray(rawProfiles) && rawProfiles.length\n ? rawProfiles\n : defaultStrategyProfiles;\n const nextOptions = [];\n const nextLabels = {};\n const nextCatalog = {};\n const nextIncomeLayerDefaults = {};\n const nextOptionOverlayDefaults = {};\n for (const item of profiles) {\n const profile = cleanStrategyProfile(item?.profile || item?.strategy_profile);\n if (!profile || nextOptions.includes(profile)) continue;\n const domain = cleanStrategyDomain(item?.domain || \"us_equity\");\n if (!domain) continue;\n nextOptions.push(profile);\n nextLabels[profile] = strategyLabelSet(profile, item);\n nextCatalog[profile] = {\n profile,\n label: nextLabels[profile].en || nextLabels[profile].zh || profile,\n label_en: nextLabels[profile].en || \"\",\n label_zh: nextLabels[profile].zh || \"\",\n domain,\n runtime_enabled: cleanOptionalBoolean(item?.runtime_enabled ?? false) === true,\n };\n const lifecycleStage = normalizeLifecycleStage(item?.lifecycle_stage ?? item?.lifecycleStage);\n if (lifecycleStage) nextCatalog[profile].lifecycle_stage = lifecycleStage;\n const allowedExecutionModes = normalizeAllowedExecutionModes(item?.allowed_execution_modes);\n if (allowedExecutionModes.length) nextCatalog[profile].allowed_execution_modes = allowedExecutionModes;\n const canSwitchLive = cleanOptionalBoolean(item?.can_switch_live);\n if (canSwitchLive !== null) nextCatalog[profile].can_switch_live = canSwitchLive;\n const blockedLiveReason = cleanDisplayText(item?.blocked_live_reason);\n if (blockedLiveReason) nextCatalog[profile].blocked_live_reason = blockedLiveReason;\n const latestEvidenceStatus = cleanDisplayText(item?.latest_evidence_status);\n if (latestEvidenceStatus) nextCatalog[profile].latest_evidence_status = latestEvidenceStatus;\n const pluginGateStatus = cleanDisplayText(item?.plugin_gate_status);\n if (pluginGateStatus) nextCatalog[profile].plugin_gate_status = pluginGateStatus;\n const dcaDefaults = dcaProfileDefaults[profile] || null;\n if (dcaDefaults || item?.dca_enabled === true) {\n nextCatalog[profile].dca_enabled = true;\n nextCatalog[profile].dca_default_mode = normalizeDcaMode(\n item?.dca_default_mode || item?.default_dca_mode || dcaDefaults?.defaultMode || \"fixed\",\n );\n nextCatalog[profile].dca_default_base_investment_usd = cleanDisplayPositiveNumber(\n item?.dca_default_base_investment_usd ||\n item?.default_dca_base_investment_usd ||\n dcaDefaults?.defaultBaseInvestmentUsd ||\n \"1000\",\n ) || \"1000\";\n }\n const profileIncomeDefaults = incomeLayerDefaultsFromProfileItem(item);\n const incomeDefaults = profileIncomeDefaults === false\n ? null\n : (profileIncomeDefaults || fallbackIncomeLayerDefaults[profile] || null);\n if (incomeDefaults) {\n nextIncomeLayerDefaults[profile] = incomeDefaults;\n nextCatalog[profile].income_layer_enabled = true;\n nextCatalog[profile].income_layer_start_usd = String(incomeDefaults.startUsd);\n nextCatalog[profile].income_layer_max_ratio = incomeDefaults.maxRatio;\n nextCatalog[profile].income_layer_allocations = incomeDefaults.allocations;\n }\n const profileOptionDefaults = optionOverlayDefaultsFromProfileItem(item);\n const optionDefaults = profileOptionDefaults === false\n ? null\n : (profileOptionDefaults || fallbackOptionOverlayDefaults[profile] || null);\n if (optionDefaults) {\n nextOptionOverlayDefaults[profile] = optionDefaults;\n nextCatalog[profile].option_overlay_enabled = true;\n nextCatalog[profile].option_overlay_live_gate = optionDefaults.liveGate || \"\";\n nextCatalog[profile].option_overlay_live_status = optionDefaults.liveStatus || \"\";\n }\n }\n if (!nextOptions.length && profiles !== defaultStrategyProfiles) return applyStrategyProfiles(defaultStrategyProfiles);\n strategyOptions = nextOptions;\n strategyLabels = nextLabels;\n strategyCatalog = nextCatalog;\n incomeLayerDefaults = nextIncomeLayerDefaults;\n optionOverlayDefaults = nextOptionOverlayDefaults;\n }\n\n function incomeLayerDefaultsFromProfileItem(item) {\n const enabled = cleanOptionalBoolean(item?.income_layer_enabled);\n const hasConfig = enabled !== null ||\n item?.income_layer_start_usd !== undefined ||\n item?.income_layer_max_ratio !== undefined ||\n item?.income_layer_allocations !== undefined;\n if (!hasConfig) return null;\n if (enabled === false) return false;\n const startUsd = cleanDisplayNumber(item?.income_layer_start_usd);\n const maxRatio = cleanDisplayRatio(item?.income_layer_max_ratio);\n const allocations = cleanIncomeLayerAllocations(item?.income_layer_allocations);\n if (!startUsd || !maxRatio || !allocations) return null;\n return { startUsd, maxRatio, allocations };\n }\n\n function cleanIncomeLayerAllocations(value) {\n if (!value || Array.isArray(value) || typeof value !== \"object\") return null;\n const allocations = {};\n let total = 0;\n for (const [rawSymbol, rawRatio] of Object.entries(value)) {\n const symbol = String(rawSymbol || \"\").trim().toUpperCase();\n const ratio = cleanDisplayPositiveNumber(rawRatio);\n if (!/^[A-Z0-9.-]{1,12}$/.test(symbol) || !ratio) continue;\n allocations[symbol] = Number(ratio);\n total += Number(ratio);\n }\n return total > 0 && Object.keys(allocations).length ? allocations : null;\n }\n\n function optionOverlayDefaultsFromProfileItem(item) {\n const enabled = cleanOptionalBoolean(item?.option_overlay_enabled);\n const hasConfig = enabled !== null ||\n item?.option_growth_overlay_enabled !== undefined ||\n item?.option_income_overlay_enabled !== undefined ||\n item?.option_overlay_live_gate !== undefined ||\n item?.option_overlay_live_status !== undefined;\n if (!hasConfig) return null;\n if (enabled === false) return false;\n const families = [\n optionOverlayFamilyDefaultsFromProfileItem(item, \"growth\"),\n optionOverlayFamilyDefaultsFromProfileItem(item, \"income\"),\n ].filter(Boolean);\n if (!families.length) return null;\n return {\n liveGate: String(item?.option_overlay_live_gate || \"promotion_required\"),\n liveStatus: String(item?.option_overlay_live_status || \"research_only\"),\n families,\n };\n }\n\n function optionOverlayFamilyDefaultsFromProfileItem(item, family) {\n const prefix = `option_${family}_overlay`;\n const enabled = cleanOptionalBoolean(item?.[`${prefix}_enabled`]);\n if (enabled !== true) return null;\n const recipe = cleanStrategyProfile(item?.[`${prefix}_recipe`]);\n const startUsd = cleanDisplayNumber(item?.[`${prefix}_start_usd`]);\n const ratioField = family === \"growth\"\n ? \"option_growth_overlay_nav_budget_ratio\"\n : \"option_income_overlay_nav_risk_ratio\";\n const ratio = cleanDisplayRatio(item?.[ratioField]);\n if (!recipe || !startUsd || !ratio) return null;\n return {\n family,\n recipe,\n startUsd,\n ratio,\n ratioKind: family === \"growth\" ? \"budget\" : \"risk\",\n };\n }\n\n function supportedDomainsForAccount(platform, account) {\n if (Array.isArray(account?.supported_domains) && account.supported_domains.length) {\n const cleaned = account.supported_domains.map(cleanStrategyDomain).filter(Boolean);\n if (cleaned.length) return [...new Set(cleaned)];\n }\n return inferSupportedDomains(platform, account);\n }\n\n function inferSupportedDomains(platform, account) {\n void account;\n if (platform === \"qmt\") return [\"cn_equity\"];\n if (platform === \"longbridge\" || platform === \"ibkr\") return [\"us_equity\", \"hk_equity\"];\n return [\"us_equity\"];\n }\n\n function supportedDomainLabel(platform, account) {\n return supportedDomainsForAccount(platform, account).map(domainLabel).join(\" / \");\n }\n\n function platformSupportsDca(platform = state.selected) {\n return platformConfig[platform]?.dca ?? false;\n }\n\n function strategyCatalogEntry(profile) {\n return strategyCatalog[cleanStrategyProfile(profile)] || {};\n }\n\n function normalizeLifecycleStage(value) {\n const text = String(value || \"\").trim().toLowerCase();\n if (!text || text.length > 40 || !/^[a-z0-9._-]+$/.test(text)) return \"\";\n if ([\"research_backtest_only\", \"ai_monitored_candidate\"].includes(text)) return \"research_active\";\n if (text === \"shadow_candidate\") return \"shadow_active\";\n if (text === \"runtime_enabled\") return \"live_candidate\";\n return text;\n }\n\n function normalizeAllowedExecutionModes(value) {\n const items = Array.isArray(value)\n ? value\n : String(value || \"\").split(/[,\\s/|]+/);\n const modes = [];\n for (const item of items) {\n const mode = String(item || \"\").trim().toLowerCase();\n if (!mode || mode.length > 32 || !/^[a-z0-9._-]+$/.test(mode)) continue;\n if (!modes.includes(mode)) modes.push(mode);\n }\n return modes;\n }\n\n function cleanDisplayText(value) {\n const text = String(value ?? \"\").trim();\n if (!text || text.length > 120 || /[<>]/.test(text)) return \"\";\n return text;\n }\n\n function strategyCanSwitchLive(entry) {\n if (!entry || typeof entry !== \"object\") return false;\n if (entry.runtime_enabled !== true) return false;\n const allowedModes = normalizeAllowedExecutionModes(entry.allowed_execution_modes);\n if (!allowedModes.includes(\"live\")) return false;\n if (cleanOptionalBoolean(entry.can_switch_live) !== true) return false;\n const lifecycleStage = normalizeLifecycleStage(entry.lifecycle_stage);\n if (![\"live_enabled\", \"runtime_enabled\"].includes(lifecycleStage)) return false;\n const blockedReason = cleanDisplayText(entry.blocked_live_reason);\n if (blockedReason) return false;\n const evidenceStatus = cleanDisplayText(entry.latest_evidence_status);\n if (evidenceStatus && [\"research_only\", \"blocked\", \"pending\"].includes(evidenceStatus.toLowerCase())) return false;\n const pluginGateStatus = cleanDisplayText(entry.plugin_gate_status);\n if (pluginGateStatus && [\"blocked\", \"locked\", \"disabled\"].includes(pluginGateStatus.toLowerCase())) return false;\n return true;\n }\n\n function strategyDisplayMetaText(platform, account, profile) {\n void profile;\n return t(\"strategyMeta\").replace(\"{domains}\", supportedDomainLabel(platform, account));\n }\n\n function strategyActionNoteText(platform = state.selected, account = selectedAccount(platform)) {\n const profile = state.forms[platform]?.strategy || \"\";\n const meta = strategyDisplayMetaText(platform, account, profile);\n return meta ? `${t(\"invalidStrategyNote\")}\\n${meta}` : t(\"invalidStrategyNote\");\n }\n\n function strategyChoiceLabel(profile, platform = state.selected, account = selectedAccount(platform), executionMode = state.forms[platform]?.executionMode) {\n const label = strategyLabel(profile);\n const entry = strategyCatalogEntry(profile);\n const domain = entry.domain ? domainLabel(entry.domain) : \"\";\n if (!entry.profile) return label;\n if (normalizeExecutionMode(executionMode, false) === \"live\" && !strategyCanSwitchLive(entry)) {\n return domain ? `${label}(${domain})` : label;\n }\n if (entry.runtime_enabled === false) {\n return domain ? `${label}(${domain})` : label;\n }\n return label;\n }\n\n function strategyAllowedForAccount(platform, account, profile, executionMode = state.forms[platform]?.executionMode) {\n const cleanProfile = cleanStrategyProfile(profile);\n const catalogEntry = strategyCatalogEntry(cleanProfile);\n if (!catalogEntry.profile) return false;\n if (dcaConfigForStrategy(cleanProfile) && !platformSupportsDca(platform)) return false;\n if (!supportedDomainsForAccount(platform, account).includes(catalogEntry.domain)) return false;\n const mode = normalizeExecutionMode(executionMode, false);\n if (!supportedExecutionModesForPlatform(platform).includes(mode)) return false;\n if (mode === \"live\") return strategyCanSwitchLive(catalogEntry);\n const allowedModes = normalizeAllowedExecutionModes(catalogEntry.allowed_execution_modes);\n if (allowedModes.length && !allowedModes.includes(mode)) return false;\n return true;\n }\n\n function strategyChoicesForAccount(platform = state.selected, account = selectedAccount(platform), executionMode = state.forms[platform]?.executionMode) {\n const choices = strategyOptions.filter((profile) => strategyAllowedForAccount(platform, account, profile, executionMode));\n const addChoice = (value) => {\n const profile = cleanStrategyProfile(value);\n if (profile && !choices.includes(profile) && strategyAllowedForAccount(platform, account, profile, executionMode)) {\n choices.push(profile);\n }\n };\n return choices;\n }\n\n function hasLiveStrategyOption(platform = state.selected, account = selectedAccount(platform)) {\n return strategyOptions.some((profile) => strategyAllowedForAccount(platform, account, profile, \"live\"));\n }\n\n function strategyLabel(profile) {\n const labels = strategyLabels[profile] || localStrategyLabels[profile];\n if (!labels) return profile;\n return state.lang === \"zh\"\n ? (labels.zh || labels.en || profile)\n : (labels.en || labels.zh || profile);\n }\n\n function strategyLabelSet(profile, item) {\n const local = localStrategyLabels[profile] || {};\n const label = String(item?.label || item?.display_name || \"\").trim();\n const labelEn = String(item?.label_en || item?.display_name_en || \"\").trim();\n const labelZh = String(item?.label_zh || item?.display_name_zh || \"\").trim();\n return {\n zh: labelZh || local.zh || label || local.en || profile,\n en: labelEn || label || local.en || labelZh || local.zh || profile,\n };\n }\n\n function modeLabel(mode) {\n return mode === \"dry_run\" ? t(\"dryRun\") : mode === \"paper\" ? t(\"paper\") : t(\"live\");\n }\n\n function normalizePluginMode(value) {\n return pluginModes.includes(value) ? value : \"none\";\n }\n\n function pluginModeLabel(mode) {\n void mode;\n return t(\"pluginModeNone\");\n }\n\n function dcaConfigForStrategy(profile) {\n const cleanProfile = cleanStrategyProfile(profile);\n const catalog = strategyCatalog[cleanProfile] || {};\n if (catalog.dca_enabled === true) {\n return {\n defaultMode: normalizeDcaMode(catalog.dca_default_mode || \"fixed\"),\n defaultBaseInvestmentUsd: cleanDisplayPositiveNumber(catalog.dca_default_base_investment_usd) || \"1000\",\n };\n }\n return dcaProfileDefaults[cleanProfile] || null;\n }\n\n function dcaSupported(profile) {\n return Boolean(dcaConfigForStrategy(profile));\n }\n\n function normalizeDcaMode(value) {\n const mode = String(value || \"\").trim().toLowerCase();\n if (mode === \"smart_dca\") return \"smart\";\n if (mode === \"fixed_dca\" || mode === \"ordinary\" || mode === \"ordinary_dca\") return \"fixed\";\n return dcaModes.includes(mode) ? mode : \"fixed\";\n }\n\n function dcaModeLabel(mode) {\n return normalizeDcaMode(mode) === \"smart\" ? t(\"dcaModeSmart\") : t(\"dcaModeFixed\");\n }\n\n function normalizeRuntimeTargetMode(value) {\n return runtimeTargetModes.includes(value) ? value : \"current\";\n }\n\n function runtimeTargetModeLabel(mode) {\n if (mode === \"enabled\") return t(\"runtimeTargetEnabled\");\n if (mode === \"disabled\") return t(\"runtimeTargetDisabled\");\n return t(\"runtimeTargetCurrent\");\n }\n\n function runtimeTargetEnabledForAccount(platform, account) {\n return cleanOptionalBoolean(currentEntryForAccount(platform, account)?.runtime_target_enabled);\n }\n\n function runtimeTargetStateForAccount(platform = state.selected, account = selectedAccount(platform)) {\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return { known: false, enabled: null };\n const configured = cleanOptionalBoolean(entry.runtime_target_enabled);\n return { known: true, enabled: configured ?? true };\n }\n\n function runtimeTargetText(enabled) {\n return enabled ? t(\"runtimeTargetOn\") : t(\"runtimeTargetOff\");\n }\n\n function runtimeTargetTone(enabled) {\n return enabled ? \"enabled\" : \"disabled\";\n }\n\n function currentRuntimeTargetText(platform = state.selected, account = selectedAccount(platform)) {\n const target = runtimeTargetStateForAccount(platform, account);\n if (!target.known) return t(\"notRead\");\n return runtimeTargetText(target.enabled);\n }\n\n function currentRuntimeTargetTone(platform = state.selected, account = selectedAccount(platform)) {\n const target = runtimeTargetStateForAccount(platform, account);\n if (!target.known) return \"neutral\";\n return runtimeTargetTone(target.enabled);\n }\n\n function incomeLayerDefaultForStrategy(profile) {\n return incomeLayerDefaults[cleanStrategyProfile(profile)] || null;\n }\n\n function incomeLayerSupported(profile) {\n return Boolean(incomeLayerDefaultForStrategy(profile));\n }\n\n function normalizeIncomeLayerMode(value) {\n return incomeLayerModes.includes(value) ? value : \"current\";\n }\n\n function incomeLayerModeLabel(mode) {\n if (mode === \"enabled\") return t(\"incomeLayerEnabled\");\n if (mode === \"disabled\") return t(\"incomeLayerDisabled\");\n return t(\"incomeLayerCurrent\");\n }\n\n function optionOverlayDefaultForStrategy(profile) {\n return optionOverlayDefaults[cleanStrategyProfile(profile)] || null;\n }\n\n function optionOverlaySupported(profile) {\n return Boolean(optionOverlayDefaultForStrategy(profile));\n }\n\n function normalizeOptionOverlayMode(value) {\n return optionOverlayModes.includes(value) ? value : \"current\";\n }\n\n function optionOverlayModeLabel(mode) {\n if (mode === \"enabled\") return t(\"optionOverlayEnabled\");\n if (mode === \"disabled\") return t(\"optionOverlayDisabled\");\n return t(\"optionOverlayCurrent\");\n }\n\n function optionOverlayText(enabled) {\n return enabled ? t(\"optionOverlayOn\") : t(\"optionOverlayOff\");\n }\n\n function normalizeCashOnlyExecutionMode(value) {\n return cashOnlyExecutionModes.includes(value) ? value : \"current\";\n }\n\n function cashOnlyExecutionModeLabel(mode) {\n if (mode === \"enabled\") return t(\"cashOnlyExecutionNo\");\n if (mode === \"disabled\") return t(\"cashOnlyExecutionYes\");\n return t(\"cashOnlyExecutionCurrent\");\n }\n\n function cashOnlyExecutionText(enabled) {\n if (enabled === null) return t(\"notRead\");\n return enabled ? t(\"cashOnlyExecutionValueNo\") : t(\"cashOnlyExecutionValueYes\");\n }\n\n function platformCashOnlyExecutionDefault() {\n return true;\n }\n\n function effectiveCashOnlyExecutionForAccount(platform, account) {\n const configured = currentCashOnlyExecutionForAccount(platform, account);\n if (configured !== null) return configured;\n if (!platformSupportsMarginPolicy(platform)) return null;\n return platformCashOnlyExecutionDefault();\n }\n\n function currentCashOnlyExecutionForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n if (entry) {\n const val = cleanOptionalBoolean(entry.cash_only_execution);\n if (val !== null) return val;\n }\n return platformCashOnlyExecutionDefault();\n }\n\n function currentCashOnlyExecutionText(platform = state.selected, account = selectedAccount(platform)) {\n if (!platformSupportsMarginPolicy(platform)) return t(\"notRead\");\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const configured = cleanOptionalBoolean(entry.cash_only_execution);\n if (configured === null) return t(\"cashOnlyExecutionDefault\");\n return cashOnlyExecutionText(configured);\n }\n\n function currentOptionOverlayForAccount(platform, account) {\n return cleanOptionalBoolean(currentEntryForAccount(platform, account)?.option_overlay_enabled);\n }\n\n function effectiveOptionOverlayForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const configured = currentOptionOverlayForAccount(platform, account);\n if (configured !== null) return configured;\n if (!optionOverlaySupported(profile)) return null;\n return true;\n }\n\n function optionOverlayDefaultSummaryDetail(defaults) {\n if (!defaults?.families?.length) return \"\";\n return defaults.families.map((item) => {\n const family = item.family === \"income\" ? t(\"optionOverlayFamilyIncome\") : t(\"optionOverlayFamilyGrowth\");\n const ratioText = item.ratioKind === \"risk\"\n ? t(\"optionOverlayRiskRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio))\n : t(\"optionOverlayBudgetRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio));\n return `${family} ${ratioText}`;\n }).join(\" / \");\n }\n\n function optionOverlayDefaultText(profile) {\n const defaults = optionOverlayDefaultForStrategy(profile);\n if (!defaults) return t(\"optionOverlayNotSupported\");\n const detail = optionOverlayDefaultSummaryDetail(defaults);\n return detail ? t(\"optionOverlayDefault\").replace(\"{detail}\", detail) : t(\"optionOverlayDefaultSimple\");\n }\n\n function currentOptionOverlayText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const configured = cleanOptionalBoolean(entry.option_overlay_enabled);\n if (!optionOverlaySupported(profile)) {\n return configured === null ? t(\"optionOverlayNotSupported\") : optionOverlayText(configured);\n }\n if (configured === null) return optionOverlayDefaultText(profile);\n return optionOverlayText(configured);\n }\n\n function currentIncomeLayerForAccount(platform, account) {\n return incomeLayerFromEntry(currentEntryForAccount(platform, account));\n }\n\n function incomeLayerFromEntry(entry) {\n return {\n enabled: cleanOptionalBoolean(entry?.income_layer_enabled),\n startUsd: cleanDisplayNumber(entry?.income_layer_start_usd),\n maxRatio: cleanDisplayRatio(entry?.income_layer_max_ratio),\n };\n }\n\n function incomeLayerFieldsConfigured(entry) {\n const current = incomeLayerFromEntry(entry);\n return current.enabled !== null || Boolean(current.startUsd || current.maxRatio);\n }\n\n function effectiveIncomeLayerForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return null;\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return null;\n const current = incomeLayerFromEntry(entry);\n if (!incomeLayerFieldsConfigured(entry)) {\n return {\n enabled: true,\n startUsd: String(defaults.startUsd),\n maxRatio: defaults.maxRatio,\n };\n }\n return {\n enabled: current.enabled ?? true,\n startUsd: current.startUsd || String(defaults.startUsd),\n maxRatio: current.maxRatio || defaults.maxRatio,\n };\n }\n\n function currentDcaForAccount(platform, account, profile = state.forms[platform]?.strategy) {\n const defaults = dcaConfigForStrategy(profile);\n if (!defaults) return { supported: false, mode: \"\", baseInvestmentUsd: \"\" };\n const entry = currentEntryForAccount(platform, account);\n return {\n supported: true,\n mode: normalizeDcaMode(entry?.dca_mode || account?.dca_mode || defaults.defaultMode),\n baseInvestmentUsd: cleanDisplayPositiveNumber(entry?.dca_base_investment_usd) ||\n cleanDisplayPositiveNumber(account?.dca_base_investment_usd) ||\n defaults.defaultBaseInvestmentUsd,\n };\n }\n\n function normalizeAccountLookupKey(value) {\n return String(value || \"\").trim().toLowerCase();\n }\n\n function collectAccountLookupCandidates(keys) {\n const candidates = new Set();\n for (const rawKey of keys) {\n const key = normalizeAccountLookupKey(rawKey);\n if (!key) continue;\n\n candidates.add(key);\n\n const compact = key.replace(/[^a-z0-9]+/g, \"\");\n if (compact) candidates.add(compact);\n\n const parts = key.split(/[^a-z0-9]+/).filter(Boolean);\n if (parts.length > 1) candidates.add(parts[parts.length - 1]);\n }\n return [...candidates];\n }\n\n function resolveCurrentEntryByKey(byPlatform, keys) {\n const candidates = new Set(collectAccountLookupCandidates(keys));\n if (!candidates.size) return null;\n\n for (const key of keys) {\n const entry = byPlatform[key];\n if (currentEntryHasState(entry)) return entry;\n }\n\n for (const [rawKey, entry] of Object.entries(byPlatform)) {\n if (!currentEntryHasState(entry)) continue;\n const rawCandidates = collectAccountLookupCandidates([rawKey]);\n const hasMatch = rawCandidates.some((candidate) => candidates.has(candidate));\n if (hasMatch) return entry;\n }\n\n return null;\n }\n\n function currentEntryForAccount(platform, account) {\n const byPlatform = state.currentStrategies[platform] || {};\n const keys = [account?.key, account?.target_name, account?.label]\n .filter(Boolean)\n .map((value) => String(value));\n const entry = resolveCurrentEntryByKey(byPlatform, keys);\n if (entry) return entry;\n const globalDefaults = window.__DEFAULT_ACCOUNT_OPTIONS__?.[platform]?.[0] || {};\n const merged = { ...globalDefaults, ...(account || {}) };\n const synth = {\n strategy_profile: \"\",\n source: \"account_defaults\",\n };\n const cashMode = merged.cash_only_execution_mode;\n if (cashMode === \"enabled\") synth.cash_only_execution = true;\n else if (cashMode === \"disabled\") synth.cash_only_execution = false;\n else if (platformSupportsMarginPolicy(platform)) synth.cash_only_execution = true;\n if (merged.min_reserved_cash_usd) synth.min_reserved_cash_usd = merged.min_reserved_cash_usd;\n if (merged.reserved_cash_ratio) synth.reserved_cash_ratio = merged.reserved_cash_ratio;\n synth.runtime_target_enabled = merged.runtime_target_enabled !== false;\n const execMode = merged.default_execution_mode || platformConfig[platform]?.default_execution_mode || \"live\";\n synth.execution_mode = execMode;\n synth.dry_run_only = execMode === \"paper\";\n return synth;\n }\n\n function currentEntryHasState(entry) {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return false;\n return Boolean(\n cleanStrategyProfile(entry.strategy_profile) ||\n cleanDisplayNumber(entry.min_reserved_cash_usd ?? entry.reserved_cash_floor_usd) ||\n cleanDisplayRatio(entry.reserved_cash_ratio) ||\n cleanOptionalBoolean(entry.income_layer_enabled) !== null ||\n cleanDisplayNumber(entry.income_layer_start_usd) ||\n cleanDisplayRatio(entry.income_layer_max_ratio) ||\n cleanOptionalBoolean(entry.option_overlay_enabled) !== null ||\n cleanOptionalBoolean(entry.cash_only_execution) !== null ||\n cleanOptionalBoolean(entry.runtime_target_enabled) !== null ||\n normalizeDcaMode(entry.dca_mode || \"\") !== \"fixed\" ||\n cleanDisplayPositiveNumber(entry.dca_base_investment_usd) ||\n normalizeExecutionMode(entry.execution_mode, entry.dry_run_only),\n );\n }\n\n function currentStrategyForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n return cleanStrategyProfile(entry?.strategy_profile) || \"\";\n }\n\n function currentReservePolicyForAccount(platform, account) {\n const entry = currentEntryForAccount(platform, account);\n return reservePolicyFromEntry(entry);\n }\n\n function currentPluginModeForAccount(platform, account) {\n void platform;\n return normalizePluginMode(account?.plugin_mode);\n }\n\n function reservePolicyFromEntry(entry) {\n return {\n minReservedCashUsd: cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd),\n reservedCashRatio: cleanDisplayRatio(entry?.reserved_cash_ratio),\n };\n }\n\n function cleanDisplayNumber(value) {\n const text = String(value ?? \"\").trim();\n if (!text || text.length > 32 || !/^(?:\\d+|\\d*\\.\\d+)$/.test(text)) return \"\";\n const numeric = Number(text);\n if (!Number.isFinite(numeric) || numeric < 0) return \"\";\n return text;\n }\n\n function cleanDisplayRatio(value) {\n const text = cleanDisplayNumber(value);\n if (!text) return \"\";\n const numeric = Number(text);\n return numeric >= 0 && numeric <= 1 ? text : \"\";\n }\n\n function cleanDisplayPositiveNumber(value) {\n const text = cleanDisplayNumber(value);\n return text && Number(text) > 0 ? text : \"\";\n }\n\n function normalizeExecutionMode(value, dryRunOnly) {\n const mode = String(value || \"\").trim().toLowerCase();\n if (mode === \"live\") return \"live\";\n if (mode === \"paper\" || mode === \"dry_run\" || mode === \"dry-run\") return \"dry_run\";\n if (dryRunOnly === true || dryRunOnly === \"true\" || dryRunOnly === \"1\" || dryRunOnly === 1) return \"dry_run\";\n if (dryRunOnly === false || dryRunOnly === \"false\" || dryRunOnly === \"0\" || dryRunOnly === 0) return \"live\";\n return \"\";\n }\n\n function cleanOptionalBoolean(value) {\n if (value === true || value === 1) return true;\n if (value === false || value === 0) return false;\n if (typeof value === \"string\") {\n const text = value.trim().toLowerCase();\n if (text === \"true\" || text === \"1\") return true;\n if (text === \"false\" || text === \"0\") return false;\n }\n return null;\n }\n\n function defaultExecutionModeForAccount(platform, account, fallback = \"live\") {\n if (platformDryRunOnly(platform)) return \"dry_run\";\n const currentMode = normalizeExecutionMode(\n currentEntryForAccount(platform, account)?.execution_mode,\n currentEntryForAccount(platform, account)?.dry_run_only,\n );\n if (currentMode) return currentMode;\n const hint = [\n account?.key,\n account?.label,\n account?.target_name,\n account?.deployment_selector,\n account?.account_scope,\n account?.service_name,\n ].join(\" \").toLowerCase();\n if (hint.split(/\\s+/).includes(\"paper\") || hint.includes(\"-paper\") || hint.includes(\"_paper\") || hint.includes(\"dry_run\") || hint.includes(\"dry-run\")) {\n return \"dry_run\";\n }\n return fallback;\n }\n\n function defaultStrategyForAccount(platform, account) {\n const currentProfile = currentStrategyForAccount(platform, account);\n if (currentProfile) return currentProfile;\n return \"\";\n }\n\n function syncStrategyForAccount(platform) {\n const account = selectedAccount(platform);\n if (!account) return;\n state.forms[platform].strategy = defaultStrategyForAccount(platform, account);\n state.forms[platform].executionMode = defaultExecutionModeForAccount(\n platform,\n account,\n );\n state.forms[platform].pluginMode = currentPluginModeForAccount(platform, account);\n syncRuntimeTargetForAccount(platform);\n syncReservePolicyForAccount(platform);\n syncIncomeLayerForAccount(platform);\n syncOptionOverlayForAccount(platform);\n syncCashOnlyExecutionForAccount(platform);\n reconcileExecutionCashPolicy(state.forms[platform], \"margin\");\n syncDcaForAccount(platform);\n }\n\n function syncRuntimeTargetForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.runtimeTargetTouched) return;\n const current = runtimeTargetEnabledForAccount(platform, selectedAccount(platform));\n form.runtimeTargetMode = current === false ? \"disabled\" : \"enabled\";\n }\n\n function syncReservePolicyForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.reservedCashTouched) return;\n const policy = currentReservePolicyForAccount(platform, selectedAccount(platform));\n const hasFloor = Boolean(policy.minReservedCashUsd);\n const hasRatio = Boolean(policy.reservedCashRatio);\n if (hasFloor && hasRatio) form.reservePolicyMode = \"max\";\n else if (hasFloor) form.reservePolicyMode = \"floor\";\n else if (hasRatio) form.reservePolicyMode = \"ratio\";\n else form.reservePolicyMode = \"none\";\n form.minReservedCashUsd = policy.minReservedCashUsd;\n form.reservedCashRatio = policy.reservedCashRatio;\n }\n\n function syncIncomeLayerForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.incomeLayerTouched) return;\n const defaults = incomeLayerDefaultForStrategy(form.strategy);\n const current = currentIncomeLayerForAccount(platform, selectedAccount(platform));\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n if (entry && incomeLayerFieldsConfigured(entry)) {\n form.incomeLayerMode = current.enabled === false ? \"disabled\" : \"enabled\";\n } else {\n form.incomeLayerMode = incomeLayerSupported(form.strategy) ? \"enabled\" : \"disabled\";\n }\n form.incomeLayerStartUsd = current.startUsd || String(defaults?.startUsd || \"\");\n form.incomeLayerMaxRatio = current.maxRatio || defaults?.maxRatio || \"\";\n }\n\n function syncOptionOverlayForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.optionOverlayTouched) return;\n const configured = normalizeOptionOverlayMode(selectedAccount(platform)?.option_overlay_mode);\n if (configured !== \"current\") {\n form.optionOverlayMode = configured;\n return;\n }\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n const rawValue = cleanOptionalBoolean(entry?.option_overlay_enabled);\n if (rawValue !== null) {\n form.optionOverlayMode = rawValue ? \"enabled\" : \"disabled\";\n return;\n }\n form.optionOverlayMode = optionOverlaySupported(form.strategy) ? \"enabled\" : \"disabled\";\n }\n\n function syncCashOnlyExecutionForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.cashOnlyExecutionTouched) return;\n const configured = normalizeCashOnlyExecutionMode(selectedAccount(platform)?.cash_only_execution_mode);\n if (configured !== \"current\") {\n form.cashOnlyExecutionMode = configured;\n return;\n }\n const entry = currentEntryForAccount(platform, selectedAccount(platform));\n const rawValue = cleanOptionalBoolean(entry?.cash_only_execution);\n if (rawValue !== null) {\n form.cashOnlyExecutionMode = rawValue ? \"enabled\" : \"disabled\";\n return;\n }\n // No explicit config — use platform default (cash-only for margin-capable platforms)\n form.cashOnlyExecutionMode = platformSupportsMarginPolicy(platform) ? \"enabled\" : \"disabled\";\n }\n\n function syncDcaForAccount(platform) {\n const form = state.forms[platform];\n if (!form || form.dcaTouched) return;\n const current = currentDcaForAccount(platform, selectedAccount(platform), form.strategy);\n form.dcaMode = current.supported ? current.mode : \"fixed\";\n form.dcaBaseInvestmentUsd = current.supported ? current.baseInvestmentUsd : \"\";\n }\n\n function ensureAccountSelection(platform) {\n const options = optionsFor(platform);\n if (!options.length) return;\n if (!options.some((option) => option.key === state.forms[platform].accountKey)) {\n state.forms[platform].accountKey = options[0].key;\n state.forms[platform].runtimeTargetTouched = false;\n state.forms[platform].reservedCashTouched = false;\n state.forms[platform].incomeLayerTouched = false;\n state.forms[platform].optionOverlayTouched = false;\n state.forms[platform].cashOnlyExecutionTouched = false;\n state.forms[platform].dcaTouched = false;\n state.forms[platform].strategy = defaultStrategyForAccount(platform, options[0]);\n state.forms[platform].pluginMode = currentPluginModeForAccount(platform, options[0]);\n syncRuntimeTargetForAccount(platform);\n syncReservePolicyForAccount(platform);\n syncIncomeLayerForAccount(platform);\n syncOptionOverlayForAccount(platform);\n syncCashOnlyExecutionForAccount(platform);\n syncDcaForAccount(platform);\n }\n }\n\n function derivedService(platform, targetName) {\n if (platform === \"longbridge\") return `longbridge-quant-${targetName.toLowerCase()}-service`;\n if (platform === \"ibkr\") return `interactive-brokers-${targetName.toLowerCase()}-service`;\n if (platform === \"schwab\") return \"charles-schwab-quant-service\";\n if (platform === \"firstrade\") return \"firstrade-quant-service\";\n if (platform === \"qmt\") return \"qmt-quant-service\";\n return \"\";\n }\n\n function accountMetaText(platform = state.selected) {\n const account = selectedAccount(platform);\n const targetName = account.target_name || account.key;\n const raw = account.service_name || derivedService(platform, targetName);\n const service = raw || (state.lang === \"zh\" ? \"无\" : \"-\");\n return t(\"targetMeta\")\n .replace(\"{target}\", targetName)\n .replace(\"{service}\", service)\n .replace(\"{domains}\", supportedDomainLabel(platform, account));\n }\n\n function hasRunnableStrategySelection(platform = state.selected) {\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n return Boolean(form?.strategy && account && strategyAllowedForAccount(platform, account, form.strategy, form.executionMode));\n }\n\n function hasValidReservePolicy(platform = state.selected) {\n if (!platformSupportsReservedCashPolicy(platform)) return true;\n const form = state.forms[platform];\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n if (mode === \"current\" || mode === \"none\") return true;\n return Boolean(reservePolicyOverrideForForm(form, platform));\n }\n\n function hasValidExecutionCashPolicy(platform = state.selected) {\n if (!platformSupportsMarginPolicy(platform) && !platformSupportsReservedCashPolicy(platform)) return true;\n const form = state.forms[platform];\n return !executionCashPolicyConflict(form) && hasValidReservePolicy(platform);\n }\n\n function hasValidIncomeLayerPolicy(platform = state.selected) {\n const form = state.forms[platform];\n if (!incomeLayerSupported(form?.strategy)) return true;\n const mode = normalizeIncomeLayerMode(form?.incomeLayerMode);\n if (mode === \"current\" || mode === \"disabled\") return true;\n const defaults = incomeLayerDefaultForStrategy(form?.strategy);\n const startUsd = cleanDisplayNumber(form?.incomeLayerStartUsd || defaults?.startUsd);\n const maxRatio = cleanDisplayRatio(form?.incomeLayerMaxRatio || defaults?.maxRatio);\n return Boolean(startUsd && maxRatio);\n }\n\n function hasValidOptionOverlayPolicy(platform = state.selected) {\n const form = state.forms[platform];\n const mode = normalizeOptionOverlayMode(form?.optionOverlayMode);\n return mode !== \"enabled\" || optionOverlaySupported(form?.strategy);\n }\n\n function hasValidDcaPolicy(platform = state.selected) {\n const form = state.forms[platform];\n if (!dcaSupported(form?.strategy) || !platformSupportsDca(platform)) return true;\n return Boolean(dcaModes.includes(normalizeDcaMode(form?.dcaMode)) && cleanDisplayPositiveNumber(form?.dcaBaseInvestmentUsd));\n }\n\n function hasValidStrategySelection(platform = state.selected) {\n return hasRunnableStrategySelection(platform) &&\n hasValidExecutionCashPolicy(platform) &&\n hasValidIncomeLayerPolicy(platform) &&\n hasValidOptionOverlayPolicy(platform) &&\n hasValidDcaPolicy(platform);\n }\n\n function normalizeReservePolicyMode(value) {\n return reservePolicyModes.includes(value) ? value : \"current\";\n }\n\n function reservePolicyOverrideForForm(form, platform = state.selected) {\n if (!platformSupportsReservedCashPolicy(platform)) return null;\n const mode = normalizeReservePolicyMode(form?.reservePolicyMode);\n const floor = cleanDisplayNumber(form?.minReservedCashUsd);\n const ratio = cleanDisplayRatio(form?.reservedCashRatio);\n const extraVariables = {};\n if (mode === \"current\") return null;\n if (mode === \"none\") {\n extraVariables[platformMinReservedCashVariables[platform]] = \"\";\n extraVariables[platformReservedCashRatioVariables[platform]] = \"\";\n return { inputs: {}, extraVariables };\n }\n if (mode === \"ratio\") {\n if (!ratio) return null;\n extraVariables[platformMinReservedCashVariables[platform]] = \"\";\n return { inputs: { reserved_cash_ratio: ratio }, extraVariables };\n }\n if (mode === \"floor\") {\n if (!floor) return null;\n extraVariables[platformReservedCashRatioVariables[platform]] = \"\";\n return { inputs: { min_reserved_cash_usd: floor }, extraVariables };\n }\n if (mode === \"max\") {\n if (!floor || !ratio) return null;\n return { inputs: { min_reserved_cash_usd: floor, reserved_cash_ratio: ratio }, extraVariables };\n }\n return null;\n }\n\n function runtimeTargetOverrideForForm(form) {\n const mode = normalizeRuntimeTargetMode(form?.runtimeTargetMode);\n if (mode === \"current\") return null;\n return {\n inputs: { runtime_target_enabled_mode: mode },\n extraVariables: { [runtimeTargetEnabledVariable]: mode === \"enabled\" ? \"true\" : \"false\" },\n };\n }\n\n function incomeLayerOverrideForForm(form) {\n const defaults = incomeLayerDefaultForStrategy(form?.strategy);\n if (!defaults) return null;\n const mode = normalizeIncomeLayerMode(form?.incomeLayerMode);\n if (mode === \"current\") return null;\n const extraVariables = {};\n if (mode === \"disabled\") {\n extraVariables[incomeLayerEnabledVariable] = \"false\";\n extraVariables[incomeLayerStartUsdVariable] = \"\";\n extraVariables[incomeLayerMaxRatioVariable] = \"\";\n return { inputs: { income_layer_mode: mode }, extraVariables };\n }\n const startUsd = cleanDisplayNumber(form?.incomeLayerStartUsd || defaults.startUsd);\n const maxRatio = cleanDisplayRatio(form?.incomeLayerMaxRatio || defaults.maxRatio);\n if (!startUsd || !maxRatio) return null;\n extraVariables[incomeLayerEnabledVariable] = \"true\";\n extraVariables[incomeLayerStartUsdVariable] = startUsd;\n extraVariables[incomeLayerMaxRatioVariable] = maxRatio;\n return { inputs: { income_layer_mode: mode, income_layer_start_usd: startUsd, income_layer_max_ratio: maxRatio }, extraVariables };\n }\n\n function optionOverlayOverrideForForm(form) {\n const mode = normalizeOptionOverlayMode(form?.optionOverlayMode);\n if (mode === \"current\") return null;\n if (mode === \"enabled\" && !optionOverlaySupported(form?.strategy)) return null;\n return { inputs: { option_overlay_mode: mode } };\n }\n\n function cashOnlyExecutionOverrideForForm(form, platform = state.selected) {\n if (!platformSupportsMarginPolicy(platform)) return null;\n const mode = normalizeCashOnlyExecutionMode(form?.cashOnlyExecutionMode);\n if (mode === \"current\") return null;\n return { inputs: { cash_only_execution_mode: mode } };\n }\n\n function dcaOverrideForForm(form) {\n if (!dcaSupported(form?.strategy) || !platformSupportsDca(state.selected)) return null;\n const mode = normalizeDcaMode(form?.dcaMode);\n const baseInvestmentUsd = cleanDisplayPositiveNumber(form?.dcaBaseInvestmentUsd);\n if (!baseInvestmentUsd) return null;\n return { inputs: { dca_mode: mode, dca_base_investment_usd: baseInvestmentUsd } };\n }\n\n function mergeExtraVariables(inputs, extraVariables) {\n if (!extraVariables || !Object.keys(extraVariables).length) return;\n const merged = inputs.extra_variables_json ? JSON.parse(inputs.extra_variables_json) : {};\n Object.assign(merged, extraVariables);\n inputs.extra_variables_json = JSON.stringify(merged);\n }\n\n function buildInputs(platform = state.selected) {\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n const targetName = account.target_name || account.key;\n const inputs = {\n platform,\n target_name: targetName,\n strategy_profile: form.strategy,\n execution_mode: form.executionMode,\n variable_scope: account.variable_scope || \"default\",\n plugin_mode: normalizePluginMode(form.pluginMode),\n service_targets_mode: \"auto\",\n apply: \"true\",\n trigger_platform_sync: \"true\",\n confirm_apply: \"APPLY_AND_SYNC\",\n platform_sync_workflow: \"sync-cloud-run-env.yml\",\n };\n for (const field of [\n \"github_environment\",\n \"deployment_selector\",\n \"account_selector\",\n \"account_scope\",\n \"service_name\",\n ]) {\n if (account[field]) inputs[field] = account[field];\n }\n const reserveOverride = platformSupportsReservedCashPolicy(platform)\n ? reservePolicyOverrideForForm(form, platform)\n : null;\n if (platformSupportsReservedCashPolicy(platform)) {\n inputs.reserved_cash_policy_mode = normalizeReservePolicyMode(form.reservePolicyMode);\n if (reserveOverride) {\n Object.assign(inputs, reserveOverride.inputs);\n mergeExtraVariables(inputs, reserveOverride.extraVariables);\n }\n }\n const runtimeTargetOverride = runtimeTargetOverrideForForm(form);\n inputs.runtime_target_enabled_mode = normalizeRuntimeTargetMode(form.runtimeTargetMode);\n if (runtimeTargetOverride) {\n Object.assign(inputs, runtimeTargetOverride.inputs);\n mergeExtraVariables(inputs, runtimeTargetOverride.extraVariables);\n }\n const incomeOverride = incomeLayerOverrideForForm(form);\n inputs.income_layer_mode = normalizeIncomeLayerMode(form.incomeLayerMode);\n if (incomeOverride) {\n Object.assign(inputs, incomeOverride.inputs);\n mergeExtraVariables(inputs, incomeOverride.extraVariables);\n }\n const optionOverlayOverride = optionOverlayOverrideForForm(form);\n inputs.option_overlay_mode = normalizeOptionOverlayMode(form.optionOverlayMode);\n if (optionOverlayOverride) {\n Object.assign(inputs, optionOverlayOverride.inputs);\n }\n const cashOnlyOverride = cashOnlyExecutionOverrideForForm(form, platform);\n if (platformSupportsMarginPolicy(platform)) {\n inputs.cash_only_execution_mode = normalizeCashOnlyExecutionMode(form.cashOnlyExecutionMode);\n if (cashOnlyOverride) {\n Object.assign(inputs, cashOnlyOverride.inputs);\n }\n }\n const dcaOverride = dcaOverrideForForm(form);\n if (dcaOverride) {\n Object.assign(inputs, dcaOverride.inputs);\n }\n return inputs;\n }\n\n function reservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform), fallback = t(\"unchanged\")) {\n if (inputs?.reserved_cash_policy_mode === \"none\") return t(\"reservedCashNone\");\n const floor = cleanDisplayNumber(inputs?.min_reserved_cash_usd);\n const ratio = cleanDisplayRatio(inputs?.reserved_cash_ratio);\n const currency = selectedCashCurrency(platform, account);\n const hasEffectiveFloor = Boolean(floor && !(ratio && Number(floor) === 0));\n if (!hasEffectiveFloor && !ratio) return fallback;\n if (hasEffectiveFloor && ratio) return `max(${floor} ${currency}, ${formatRatioPercent(ratio)})`;\n if (hasEffectiveFloor) return `${floor} ${currency}`;\n return formatRatioPercent(ratio);\n }\n\n function platformReservedCashDefaultText(platform = state.selected, account = selectedAccount(platform)) {\n return t(\"reservedCashDefault\").replace(\"{currency}\", selectedCashCurrency(platform, account));\n }\n\n function currentReservedCashPolicyText(platform = state.selected, account = selectedAccount(platform)) {\n const entry = currentEntryForAccount(platform, account);\n const policy = currentReservePolicyForAccount(platform, account);\n return reservedCashPolicyText(\n {\n min_reserved_cash_usd: policy.minReservedCashUsd,\n reserved_cash_ratio: policy.reservedCashRatio,\n },\n platform,\n account,\n entry ? platformReservedCashDefaultText(platform, account) : t(\"notRead\"),\n );\n }\n\n function pendingReservedCashPolicyText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n return reservedCashPolicyText(pendingReservePolicy(inputs, platform, account).inputs, platform, account, t(\"unchanged\"));\n }\n\n function pendingReservePolicy(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const current = currentReservePolicyForAccount(platform, account);\n const currentFloor = cleanDisplayNumber(current.minReservedCashUsd);\n const currentRatio = cleanDisplayRatio(current.reservedCashRatio);\n const mode = normalizeReservePolicyMode(inputs.reserved_cash_policy_mode);\n const next = {\n min_reserved_cash_usd: cleanDisplayNumber(inputs.min_reserved_cash_usd),\n reserved_cash_ratio: cleanDisplayRatio(inputs.reserved_cash_ratio),\n };\n if (mode === \"none\") {\n next.reserved_cash_policy_mode = \"none\";\n }\n const entry = currentEntryForAccount(platform, account);\n const changed = Boolean(entry && (\n next.min_reserved_cash_usd !== currentFloor ||\n next.reserved_cash_ratio !== currentRatio ||\n (mode === \"none\" && (currentFloor || currentRatio))\n ));\n return { changed, inputs: next };\n }\n\n function currentIncomeLayerText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return t(\"incomeLayerNotSupported\");\n const entry = currentEntryForAccount(platform, account);\n if (!entry) return t(\"notRead\");\n const current = incomeLayerFromEntry(entry);\n if (!incomeLayerFieldsConfigured(entry)) {\n return t(\"incomeLayerDefault\")\n .replace(\"{start}\", formatUsd(defaults.startUsd))\n .replace(\"{ratio}\", formatRatioPercent(defaults.maxRatio));\n }\n const enabled = current.enabled ?? true;\n const startUsd = current.startUsd || String(defaults.startUsd);\n const ratio = current.maxRatio || defaults.maxRatio;\n return enabled\n ? t(\"incomeLayerOn\")\n .replace(\"{start}\", formatUsd(startUsd))\n .replace(\"{ratio}\", formatRatioPercent(ratio))\n : t(\"incomeLayerOff\");\n }\n\n function pendingIncomeLayerText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingIncomeLayer(inputs, platform, account);\n if (!pending.supported) return t(\"incomeLayerNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n if (pending.inputs.income_layer_enabled === false) return t(\"incomeLayerOff\");\n return t(\"incomeLayerOn\")\n .replace(\"{start}\", formatUsd(pending.inputs.income_layer_start_usd))\n .replace(\"{ratio}\", formatRatioPercent(pending.inputs.income_layer_max_ratio));\n }\n\n function pendingOptionOverlayText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingOptionOverlay(inputs, platform, account);\n if (!pending.supported && pending.inputs.option_overlay_enabled !== false) return t(\"optionOverlayNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n return optionOverlayText(pending.inputs.option_overlay_enabled);\n }\n\n function pendingCashOnlyExecutionText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingCashOnlyExecution(inputs, platform, account);\n if (!pending.changed) return t(\"unchanged\");\n return cashOnlyExecutionText(pending.inputs.cash_only_execution);\n }\n\n function pendingRuntimeTargetText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingRuntimeTarget(inputs, platform, account);\n if (!pending.changed) return t(\"unchanged\");\n return runtimeTargetText(pending.inputs.runtime_target_enabled);\n }\n\n function pendingRuntimeTargetTone(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingRuntimeTarget(inputs, platform, account);\n if (!pending.changed) return \"neutral\";\n return runtimeTargetTone(pending.inputs.runtime_target_enabled);\n }\n\n function currentDcaText(platform = state.selected, account = selectedAccount(platform), profile = state.forms[platform]?.strategy) {\n const current = currentDcaForAccount(platform, account, profile);\n if (!current.supported) return t(\"dcaNotSupported\");\n return t(\"dcaText\")\n .replace(\"{mode}\", dcaModeLabel(current.mode))\n .replace(\"{amount}\", formatUsd(current.baseInvestmentUsd));\n }\n\n function pendingDcaText(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const pending = pendingDca(inputs, platform, account);\n if (!pending.supported) return t(\"dcaNotSupported\");\n if (!pending.changed) return t(\"unchanged\");\n return t(\"dcaText\")\n .replace(\"{mode}\", dcaModeLabel(pending.inputs.dca_mode))\n .replace(\"{amount}\", formatUsd(pending.inputs.dca_base_investment_usd));\n }\n\n function pendingRuntimeTarget(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const mode = normalizeRuntimeTargetMode(inputs.runtime_target_enabled_mode);\n if (mode === \"current\") {\n return {\n changed: false,\n inputs: { runtime_target_enabled: runtimeTargetEnabledForAccount(platform, account) ?? true },\n };\n }\n const current = runtimeTargetEnabledForAccount(platform, account);\n const currentEnabled = current ?? true;\n const nextEnabled = mode === \"enabled\";\n const entry = currentEntryForAccount(platform, account);\n return {\n changed: Boolean(entry && current !== null && currentEnabled !== nextEnabled),\n inputs: { runtime_target_enabled: nextEnabled },\n };\n }\n\n function pendingIncomeLayer(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const defaults = incomeLayerDefaultForStrategy(profile);\n if (!defaults) return { supported: false, changed: false, inputs: {} };\n const mode = normalizeIncomeLayerMode(inputs.income_layer_mode);\n const entry = currentEntryForAccount(platform, account);\n const rawCurrent = currentIncomeLayerForAccount(platform, account);\n const effective = effectiveIncomeLayerForAccount(platform, account, profile);\n const currentEnabled = effective?.enabled ?? true;\n const currentStartUsd = effective?.startUsd ?? String(defaults.startUsd);\n const currentRatio = effective?.maxRatio ?? defaults.maxRatio;\n if (mode === \"current\") {\n return {\n supported: true,\n changed: false,\n inputs: {\n income_layer_enabled: rawCurrent.enabled,\n income_layer_start_usd: rawCurrent.startUsd,\n income_layer_max_ratio: rawCurrent.maxRatio,\n },\n };\n }\n if (mode === \"disabled\") {\n if (!entry) {\n return {\n supported: true,\n changed: false,\n inputs: {\n income_layer_enabled: false,\n income_layer_start_usd: \"\",\n income_layer_max_ratio: \"\",\n },\n };\n }\n return {\n supported: true,\n changed: currentEnabled !== false || Boolean(rawCurrent.startUsd || rawCurrent.maxRatio),\n inputs: {\n income_layer_enabled: false,\n income_layer_start_usd: \"\",\n income_layer_max_ratio: \"\",\n },\n };\n }\n const nextStartUsd = cleanDisplayNumber(inputs.income_layer_start_usd || defaults.startUsd);\n const nextRatio = cleanDisplayRatio(inputs.income_layer_max_ratio || defaults.maxRatio);\n return {\n supported: true,\n changed: Boolean(entry && (currentEnabled !== true || nextStartUsd !== currentStartUsd || nextRatio !== currentRatio)),\n inputs: {\n income_layer_enabled: true,\n income_layer_start_usd: nextStartUsd,\n income_layer_max_ratio: nextRatio,\n },\n };\n }\n\n function pendingOptionOverlay(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const supported = optionOverlaySupported(profile);\n const mode = normalizeOptionOverlayMode(inputs.option_overlay_mode);\n const current = effectiveOptionOverlayForAccount(platform, account, profile);\n if (mode === \"current\") {\n return {\n supported,\n changed: false,\n inputs: { option_overlay_enabled: currentOptionOverlayForAccount(platform, account) },\n };\n }\n if (mode === \"enabled\") {\n return {\n supported,\n changed: supported && current !== null && current !== true,\n inputs: { option_overlay_enabled: true },\n };\n }\n return {\n supported,\n changed: current === true,\n inputs: { option_overlay_enabled: false },\n };\n }\n\n function pendingCashOnlyExecution(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const mode = normalizeCashOnlyExecutionMode(inputs.cash_only_execution_mode);\n if (!platformSupportsMarginPolicy(platform) || mode === \"current\") {\n return { changed: false, inputs: {} };\n }\n const current = effectiveCashOnlyExecutionForAccount(platform, account);\n const nextEnabled = mode === \"enabled\";\n const entry = currentEntryForAccount(platform, account);\n return {\n changed: Boolean(entry && current !== null && current !== nextEnabled),\n inputs: { cash_only_execution: nextEnabled },\n };\n }\n\n function pendingDca(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const profile = cleanStrategyProfile(inputs.strategy_profile || state.forms[platform]?.strategy);\n const defaults = dcaConfigForStrategy(profile);\n if (!defaults) return { supported: false, changed: false, inputs: {} };\n const current = currentDcaForAccount(platform, account, profile);\n const nextMode = normalizeDcaMode(inputs.dca_mode || defaults.defaultMode);\n const nextBase = cleanDisplayPositiveNumber(inputs.dca_base_investment_usd || defaults.defaultBaseInvestmentUsd);\n return {\n supported: true,\n changed: Boolean(current.mode !== nextMode || current.baseInvestmentUsd !== nextBase),\n inputs: {\n dca_mode: nextMode,\n dca_base_investment_usd: nextBase,\n },\n };\n }\n\n function pendingChangeState(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const currentProfile = currentStrategyForAccount(platform, account);\n const nextProfile = cleanStrategyProfile(inputs.strategy_profile);\n const currentEntry = currentEntryForAccount(platform, account);\n const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);\n const currentPluginMode = currentPluginModeForAccount(platform, account);\n const nextPluginMode = normalizePluginMode(inputs.plugin_mode);\n const runtimeTarget = pendingRuntimeTarget(inputs, platform, account);\n const reserve = pendingReservePolicy(inputs, platform, account);\n const income = pendingIncomeLayer(inputs, platform, account);\n const optionOverlay = pendingOptionOverlay(inputs, platform, account);\n const cashOnly = pendingCashOnlyExecution(inputs, platform, account);\n const dca = pendingDca(inputs, platform, account);\n return {\n currentProfile,\n nextProfile,\n currentMode,\n currentPluginMode,\n nextPluginMode,\n strategyChanged: Boolean(nextProfile && ((state.forms[platform]?.strategyTouched) || (currentProfile && currentProfile !== nextProfile))),\n modeChanged: Boolean(inputs.execution_mode && currentMode && currentMode !== inputs.execution_mode),\n pluginModeChanged: Boolean(nextPluginMode && currentPluginMode && currentPluginMode !== nextPluginMode),\n runtimeTargetChanged: runtimeTarget.changed,\n reserveCashChanged: reserve.changed,\n incomeLayerChanged: income.changed,\n optionOverlayChanged: optionOverlay.changed,\n cashOnlyChanged: cashOnly.changed,\n dcaChanged: dca.changed,\n runtimeTarget,\n reserve,\n income,\n optionOverlay,\n cashOnly,\n dca,\n };\n }\n\n function hasPendingChanges(inputs, platform = state.selected, account = selectedAccount(platform)) {\n const changes = pendingChangeState(inputs, platform, account);\n return Boolean(\n changes.strategyChanged ||\n changes.modeChanged ||\n changes.pluginModeChanged ||\n changes.runtimeTargetChanged ||\n changes.reserveCashChanged ||\n changes.incomeLayerChanged ||\n changes.optionOverlayChanged ||\n changes.cashOnlyChanged ||\n changes.dcaChanged\n );\n }\n\n function formatRatioPercent(value) {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n return `${(numeric * 100).toFixed(2).replace(/\\.?0+$/, \"\")}%`;\n }\n\n function formatUsd(value) {\n const numeric = Number(value);\n if (!Number.isFinite(numeric)) return String(value);\n return `$${numeric.toLocaleString(\"en-US\", { maximumFractionDigits: 0 })}`;\n }\n\n function incomeLayerAllocationText(defaults) {\n if (!defaults?.allocations) return \"\";\n return Object.entries(defaults.allocations)\n .map(([symbol, ratio]) => `${symbol} ${formatRatioPercent(ratio)}`)\n .join(\" / \");\n }\n\n function incomeLayerDefaultMetaText(defaults) {\n if (!defaults) return t(\"incomeLayerModeMeta\");\n return t(\"incomeLayerDefaultMeta\")\n .replace(\"{start}\", formatUsd(defaults.startUsd))\n .replace(\"{ratio}\", formatRatioPercent(defaults.maxRatio));\n }\n\n function optionOverlayDefaultMetaText(defaults) {\n if (!defaults?.families?.length) return t(\"optionOverlayModeMeta\");\n const familyText = defaults.families.map((item) => {\n const family = item.family === \"income\" ? t(\"optionOverlayFamilyIncome\") : t(\"optionOverlayFamilyGrowth\");\n const ratioText = item.ratioKind === \"risk\"\n ? t(\"optionOverlayRiskRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio))\n : t(\"optionOverlayBudgetRatio\").replace(\"{ratio}\", formatRatioPercent(item.ratio));\n return `${family}: ${item.recipe}, ${formatUsd(item.startUsd)}, ${ratioText}`;\n }).join(\" / \");\n return t(\"optionOverlayDefaultMeta\").replace(\"{defaults}\", familyText);\n }\n\n function summaryRows(inputs) {\n const account = selectedAccount();\n const changes = pendingChangeState(inputs, state.selected, account);\n const currentStrategyText = changes.currentProfile ? strategyLabel(changes.currentProfile) : t(\"notRead\");\n const rows = [\n [t(\"repository\"), state.repositories[state.selected] || defaultRepositories[state.selected]],\n [t(\"selectedAccount\"), account.label],\n [t(\"currentStrategy\"), currentStrategyText],\n [t(\"selectedMarket\"), supportedDomainLabel(state.selected, account)],\n [\n t(\"currentRuntimeTarget\"),\n currentRuntimeTargetText(state.selected, account),\n \"\",\n currentRuntimeTargetTone(state.selected, account),\n ],\n [t(\"currentPluginMode\"), pluginModeLabel(changes.currentPluginMode)],\n [t(\"reservedCashPolicy\"), currentReservedCashPolicyText(state.selected, account)],\n ];\n if (platformSupportsMarginPolicy(state.selected)) {\n rows.push([t(\"currentCashOnlyExecution\"), currentCashOnlyExecutionText(state.selected, account)]);\n }\n if (incomeLayerSupported(inputs.strategy_profile)) {\n rows.push([t(\"currentIncomeLayer\"), currentIncomeLayerText(state.selected, account, inputs.strategy_profile)]);\n }\n if (optionOverlaySupported(inputs.strategy_profile) || changes.optionOverlayChanged) {\n rows.push([t(\"currentOptionOverlay\"), currentOptionOverlayText(state.selected, account, inputs.strategy_profile)]);\n }\n if (dcaSupported(inputs.strategy_profile)) {\n rows.push([t(\"currentDca\"), currentDcaText(state.selected, account, inputs.strategy_profile)]);\n }\n if (changes.reserveCashChanged) {\n rows.push([t(\"pendingReservedCashPolicy\"), pendingReservedCashPolicyText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.incomeLayerChanged) {\n rows.push([t(\"pendingIncomeLayer\"), pendingIncomeLayerText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.optionOverlayChanged) {\n rows.push([t(\"pendingOptionOverlay\"), pendingOptionOverlayText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.cashOnlyChanged) {\n rows.push([t(\"pendingCashOnlyExecution\"), pendingCashOnlyExecutionText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.dcaChanged) {\n rows.push([t(\"pendingDca\"), pendingDcaText(inputs, state.selected, account), \"pending\"]);\n }\n if (changes.modeChanged) {\n rows.push([t(\"pendingMode\"), modeLabel(inputs.execution_mode), \"pending\"]);\n }\n if (changes.pluginModeChanged) {\n rows.push([t(\"pendingPluginMode\"), pluginModeLabel(changes.nextPluginMode), \"pending\"]);\n }\n if (changes.runtimeTargetChanged) {\n rows.push([\n t(\"pendingRuntimeTarget\"),\n pendingRuntimeTargetText(inputs, state.selected, account),\n \"pending\",\n pendingRuntimeTargetTone(inputs, state.selected, account),\n ]);\n }\n if (changes.strategyChanged && changes.nextProfile) {\n rows.push([t(\"nextStrategy\"), strategyLabel(changes.nextProfile), \"pending\"]);\n }\n return rows;\n }\n\n function applyLanguage() {\n document.documentElement.lang = state.lang === \"zh\" ? \"zh-CN\" : \"en\";\n document.querySelectorAll(\"[data-i18n]\").forEach((node) => {\n node.textContent = t(node.dataset.i18n);\n });\n el(\"lang-button\").textContent = state.lang === \"zh\" ? \"EN\" : \"中\";\n }\n\n function renderPlatforms() {\n const strip = el(\"platform-strip\");\n strip.replaceChildren();\n const showPrivateConfig = hasPrivateConfig();\n for (const platform of Object.keys(platformMeta)) {\n ensureAccountSelection(platform);\n const meta = platformMeta[platform];\n const form = state.forms[platform];\n const account = selectedAccount(platform);\n const button = document.createElement(\"button\");\n button.className = \"platform-button\";\n button.type = \"button\";\n button.dataset.platform = platform;\n button.classList.toggle(\"active\", platform === state.selected);\n const mark = document.createElement(\"span\");\n mark.className = \"mark\";\n mark.textContent = meta.code;\n const copyNode = document.createElement(\"span\");\n copyNode.className = \"platform-copy\";\n const labelNode = document.createElement(\"strong\");\n labelNode.textContent = meta.label;\n copyNode.append(labelNode);\n if (showPrivateConfig) {\n const accountNode = document.createElement(\"span\");\n accountNode.textContent = account.label;\n const strategyNode = document.createElement(\"small\");\n strategyNode.textContent = strategyLabel(form.strategy);\n copyNode.append(accountNode, strategyNode);\n }\n button.append(mark, copyNode);\n strip.appendChild(button);\n }\n }\n\n function renderControls() {\n const platform = state.selected;\n const meta = platformMeta[platform];\n const form = state.forms[platform];\n const accounts = optionsFor(platform);\n const account = selectedAccount(platform);\n const choices = strategyChoicesForAccount(platform, account, form.executionMode);\n const currentStrategy = currentStrategyForAccount(platform, account);\n const currentStrategyBlocked = Boolean(\n currentStrategy &&\n !strategyAllowedForAccount(platform, account, currentStrategy, form.executionMode),\n );\n const accountSelect = el(\"account-select\");\n const strategySelect = el(\"strategy-select\");\n const runtimeTargetEnabledSelect = el(\"runtime-target-enabled-select\");\n const pluginModeSelect = el(\"plugin-mode-select\");\n const incomeLayerModeSelect = el(\"income-layer-mode-select\");\n const incomeLayerStartUsdInput = el(\"income-layer-start-usd-input\");\n const incomeLayerMaxRatioInput = el(\"income-layer-max-ratio-input\");\n const optionOverlayModeSelect = el(\"option-overlay-mode-select\");\n const cashOnlyExecutionModeSelect = el(\"cash-only-execution-mode-select\");\n const dcaModeSelect = el(\"dca-mode-select\");\n const dcaBaseInvestmentUsdInput = el(\"dca-base-investment-usd-input\");\n const reservePolicyModeSelect = el(\"reserve-policy-mode-select\");\n const minReservedCashInput = el(\"min-reserved-cash-input\");\n const reservedCashRatioInput = el(\"reserved-cash-ratio-input\");\n const showPrivateControls = hasPrivateConfig();\n\n el(\"switch-panel\").style.setProperty(\"--platform-color\", meta.accent);\n el(\"platform-title\").textContent = meta.label;\n el(\"quick-form\").hidden = !showPrivateControls;\n el(\"run-area\").hidden = !showPrivateControls;\n el(\"public-note\").hidden = showPrivateControls;\n el(\"public-note\").textContent = state.auth.allowed ? t(\"missingConfigNote\") : t(\"publicReadonly\");\n\n if (!showPrivateControls) {\n accountSelect.replaceChildren();\n strategySelect.replaceChildren();\n runtimeTargetEnabledSelect.replaceChildren();\n pluginModeSelect.replaceChildren();\n incomeLayerModeSelect.replaceChildren();\n optionOverlayModeSelect.replaceChildren();\n cashOnlyExecutionModeSelect.replaceChildren();\n dcaModeSelect.replaceChildren();\n reservePolicyModeSelect.replaceChildren();\n incomeLayerStartUsdInput.value = \"\";\n incomeLayerMaxRatioInput.value = \"\";\n dcaBaseInvestmentUsdInput.value = \"\";\n minReservedCashInput.value = \"\";\n reservedCashRatioInput.value = \"\";\n el(\"account-meta\").textContent = \"\";\n el(\"strategy-meta\").textContent = \"\";\n el(\"income-layer-mode-meta\").textContent = \"\";\n el(\"income-layer-start-meta\").textContent = \"\";\n el(\"income-layer-ratio-meta\").textContent = \"\";\n el(\"option-overlay-mode-meta\").textContent = \"\";\n el(\"cash-only-execution-mode-meta\").textContent = \"\";\n el(\"dca-mode-meta\").textContent = \"\";\n el(\"dca-base-meta\").textContent = \"\";\n return;\n }\n\n accountSelect.replaceChildren();\n if (accounts.length) {\n for (const account of accounts) {\n accountSelect.append(new Option(account.label, account.key, false, account.key === form.accountKey));\n }\n } else {\n accountSelect.append(new Option(t(\"noAccount\"), \"\"));\n }\n el(\"account-meta\").textContent = accounts.length ? accountMetaText(platform) : \"\";\n\n if (choices.length && !choices.includes(form.strategy) && !currentStrategyBlocked) {\n form.strategy = choices[0];\n }\n strategySelect.disabled = !choices.length;\n strategySelect.replaceChildren();\n if (currentStrategyBlocked) {\n const blockedOption = new Option(\n strategyChoiceLabel(currentStrategy, platform, account, form.executionMode),\n currentStrategy,\n true,\n currentStrategy === form.strategy,\n );\n blockedOption.disabled = true;\n strategySelect.append(blockedOption);\n }\n if (choices.length) {\n for (const strategy of choices) {\n strategySelect.append(\n new Option(strategyChoiceLabel(strategy, platform, account, form.executionMode), strategy, false, strategy === form.strategy),\n );\n }\n } else {\n strategySelect.append(new Option(t(\"noStrategy\"), \"\"));\n }\n el(\"strategy-meta\").textContent = account\n ? strategyDisplayMetaText(platform, account, form.strategy)\n : \"\";\n runtimeTargetEnabledSelect.replaceChildren();\n for (const mode of runtimeTargetModes) {\n runtimeTargetEnabledSelect.append(\n new Option(runtimeTargetModeLabel(mode), mode, false, mode === normalizeRuntimeTargetMode(form.runtimeTargetMode)),\n );\n }\n pluginModeSelect.replaceChildren();\n for (const mode of pluginModes) {\n pluginModeSelect.append(new Option(pluginModeLabel(mode), mode, false, mode === normalizePluginMode(form.pluginMode)));\n }\n const incomeDefaults = incomeLayerDefaultForStrategy(form.strategy);\n el(\"income-layer-section\").hidden = false;\n el(\"option-overlay-section\").hidden = false;\n incomeLayerModeSelect.replaceChildren();\n if (incomeDefaults) {\n incomeLayerModeSelect.disabled = false;\n for (const mode of incomeLayerModes) {\n incomeLayerModeSelect.append(new Option(incomeLayerModeLabel(mode), mode, false, mode === normalizeIncomeLayerMode(form.incomeLayerMode)));\n }\n el(\"income-layer-mode-meta\").textContent = incomeLayerDefaultMetaText(incomeDefaults);\n el(\"income-layer-start-meta\").textContent = t(\"incomeLayerStartMeta\");\n el(\"income-layer-ratio-meta\").textContent = t(\"incomeLayerAllocationMeta\").replace(\n \"{allocations}\",\n incomeLayerAllocationText(incomeDefaults),\n );\n } else {\n incomeLayerModeSelect.disabled = true;\n incomeLayerModeSelect.append(new Option(t(\"incomeLayerNotSupported\"), \"current\"));\n el(\"income-layer-mode-meta\").textContent = t(\"incomeLayerModeMeta\");\n el(\"income-layer-start-meta\").textContent = t(\"incomeLayerStartMeta\");\n el(\"income-layer-ratio-meta\").textContent = t(\"incomeLayerRatioMeta\");\n }\n const supportsMargin = platformSupportsMarginPolicy(platform);\n const supportsReserve = platformSupportsReservedCashPolicy(platform);\n if (supportsMargin) syncCashOnlyExecutionForAccount(platform);\n reconcileExecutionCashPolicy(form, \"margin\");\n const executionCashPolicyGrid = el(\"execution-cash-policy-grid\");\n const qmtPlatformCashNote = el(\"qmt-platform-cash-note\");\n const executionCashPolicyNote = el(\"execution-cash-policy-note\");\n executionCashPolicyGrid.hidden = !supportsMargin && !supportsReserve;\n qmtPlatformCashNote.hidden = supportsMargin || supportsReserve || platform !== \"qmt\";\n executionCashPolicyNote.hidden = !supportsMargin || !supportsReserve;\n\n const marginBlocksReserve = supportsMargin && supportsReserve && allowMarginExplicitlySelected(form);\n const reserveBlocksMargin = supportsMargin && supportsReserve && reserveCashOverrideActive(form);\n\n if (supportsReserve) {\n reservePolicyModeSelect.replaceChildren();\n for (const mode of reservePolicyModes) {\n reservePolicyModeSelect.append(new Option(t(`reservePolicy${mode[0].toUpperCase()}${mode.slice(1)}`), mode, false, mode === normalizeReservePolicyMode(form.reservePolicyMode)));\n }\n const reserveMode = normalizeReservePolicyMode(form.reservePolicyMode);\n el(\"min-reserved-cash-label\").textContent = t(\"minReservedCash\").replace(\n \"{currency}\",\n selectedCashCurrency(platform, account),\n );\n reservePolicyModeSelect.disabled = false;\n minReservedCashInput.disabled = reserveMode === \"current\" || reserveMode === \"none\" || reserveMode === \"ratio\";\n reservedCashRatioInput.disabled = reserveMode === \"current\" || reserveMode === \"none\" || reserveMode === \"floor\";\n minReservedCashInput.value = reserveMode === \"ratio\" || reserveMode === \"none\" ? \"\" : form.minReservedCashUsd;\n reservedCashRatioInput.value = reserveMode === \"floor\" || reserveMode === \"none\" ? \"\" : form.reservedCashRatio;\n el(\"reserve-policy-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"min-reserve-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"reserve-ratio-block\").classList.toggle(\"policy-block-muted\", marginBlocksReserve);\n el(\"reserve-policy-mode-meta\").textContent = marginBlocksReserve\n ? t(\"executionCashMarginBlocksReserve\")\n : t(\"reservedCashModeMeta\");\n } else {\n reservePolicyModeSelect.replaceChildren();\n minReservedCashInput.value = \"\";\n reservedCashRatioInput.value = \"\";\n }\n const incomeMode = normalizeIncomeLayerMode(form.incomeLayerMode);\n const incomeLayerInputsDisabled = !incomeDefaults || incomeMode === \"disabled\";\n incomeLayerStartUsdInput.disabled = incomeLayerInputsDisabled;\n incomeLayerMaxRatioInput.disabled = incomeLayerInputsDisabled;\n if (incomeDefaults && incomeMode !== \"disabled\" && !cleanDisplayNumber(form.incomeLayerStartUsd)) {\n form.incomeLayerStartUsd = String(incomeDefaults.startUsd);\n }\n if (incomeDefaults && incomeMode !== \"disabled\" && !cleanDisplayRatio(form.incomeLayerMaxRatio)) {\n form.incomeLayerMaxRatio = incomeDefaults.maxRatio;\n }\n incomeLayerStartUsdInput.value = incomeDefaults && incomeMode !== \"disabled\" ? form.incomeLayerStartUsd : \"\";\n incomeLayerMaxRatioInput.value = incomeDefaults && incomeMode !== \"disabled\" ? form.incomeLayerMaxRatio : \"\";\n\n const optionDefaults = optionOverlayDefaultForStrategy(form.strategy);\n optionOverlayModeSelect.replaceChildren();\n if (optionDefaults) {\n optionOverlayModeSelect.disabled = false;\n for (const mode of optionOverlayModes) {\n optionOverlayModeSelect.append(\n new Option(optionOverlayModeLabel(mode), mode, false, mode === normalizeOptionOverlayMode(form.optionOverlayMode)),\n );\n }\n el(\"option-overlay-mode-meta\").textContent = optionOverlayDefaultMetaText(optionDefaults);\n } else {\n optionOverlayModeSelect.disabled = true;\n optionOverlayModeSelect.append(new Option(t(\"optionOverlayNotSupported\"), \"current\"));\n el(\"option-overlay-mode-meta\").textContent = t(\"optionOverlayModeMeta\");\n }\n\n if (supportsMargin) {\n cashOnlyExecutionModeSelect.replaceChildren();\n for (const mode of cashOnlyExecutionModes) {\n const option = new Option(\n mode === \"enabled\" ? t(\"cashOnlyExecutionNo\") : t(\"cashOnlyExecutionYes\"),\n mode,\n false,\n mode === normalizeCashOnlyExecutionMode(form.cashOnlyExecutionMode),\n );\n cashOnlyExecutionModeSelect.append(option);\n }\n el(\"cash-only-policy-block\").classList.toggle(\"policy-block-muted\", reserveBlocksMargin);\n el(\"cash-only-execution-mode-meta\").textContent = reserveBlocksMargin\n ? t(\"executionCashReserveBlocksMargin\")\n : t(\"cashOnlyExecutionModeMeta\");\n } else {\n cashOnlyExecutionModeSelect.replaceChildren();\n el(\"cash-only-execution-mode-meta\").textContent = \"\";\n }\n\n const dcaDefaults = dcaConfigForStrategy(form.strategy);\n dcaModeSelect.replaceChildren();\n const dcaAllowed = Boolean(dcaDefaults) && platformSupportsDca(platform);\n if (dcaAllowed) {\n dcaModeSelect.disabled = false;\n for (const mode of dcaModes) {\n dcaModeSelect.append(new Option(dcaModeLabel(mode), mode, false, mode === normalizeDcaMode(form.dcaMode)));\n }\n if (!cleanDisplayPositiveNumber(form.dcaBaseInvestmentUsd)) {\n form.dcaBaseInvestmentUsd = dcaDefaults.defaultBaseInvestmentUsd;\n }\n dcaBaseInvestmentUsdInput.disabled = false;\n dcaBaseInvestmentUsdInput.value = form.dcaBaseInvestmentUsd;\n el(\"dca-mode-meta\").textContent = t(\"dcaDefaultMeta\")\n .replace(\"{mode}\", dcaModeLabel(dcaDefaults.defaultMode))\n .replace(\"{amount}\", formatUsd(dcaDefaults.defaultBaseInvestmentUsd));\n el(\"dca-base-meta\").textContent = t(\"dcaModeMeta\");\n } else {\n dcaModeSelect.disabled = true;\n dcaModeSelect.append(new Option(\n dcaDefaults && !platformSupportsDca(platform) ? t(\"dcaPlatformNotSupported\") : t(\"dcaNotSupported\"),\n \"fixed\",\n ));\n dcaBaseInvestmentUsdInput.disabled = true;\n dcaBaseInvestmentUsdInput.value = \"\";\n el(\"dca-mode-meta\").textContent = t(\"dcaModeMeta\");\n el(\"dca-base-meta\").textContent = t(\"dcaModeMeta\");\n }\n\n const supportedModes = supportedExecutionModesForPlatform(platform);\n const liveModeAvailable = supportedModes.includes(\"live\") && hasLiveStrategyOption(platform, account);\n if (!supportedModes.includes(form.executionMode)) form.executionMode = \"dry_run\";\n document.querySelectorAll(\"#mode-control [data-mode]\").forEach((button) => {\n button.disabled = !supportedModes.includes(button.dataset.mode) || (\n button.dataset.mode === \"live\" && !liveModeAvailable\n );\n button.classList.toggle(\"active\", button.dataset.mode === form.executionMode);\n });\n el(\"mode-meta\").textContent = !supportedModes.includes(\"live\")\n ? t(\"qmtDryRunOnlyNote\")\n : (!liveModeAvailable ? t(\"liveModeUnavailable\") : \"\");\n }\n\n function renderSummary() {\n const showSummary = hasPrivateConfig();\n const summaryPanel = document.querySelector(\".summary-panel\");\n const switchSurface = document.querySelector(\".switch-surface\");\n summaryPanel.hidden = !showSummary;\n switchSurface.classList.toggle(\"summary-hidden\", !showSummary);\n if (!showSummary) return;\n\n const inputs = buildInputs();\n const list = el(\"summary-list\");\n list.replaceChildren();\n document.querySelector(\".summary-head h2\").textContent = t(\"summary\");\n const currentHeading = document.createElement(\"p\");\n currentHeading.className = \"summary-section-title\";\n currentHeading.textContent = t(\"summaryCurrent\");\n list.appendChild(currentHeading);\n let pendingSectionInserted = false;\n for (const [label, value, rowClass, valueTone] of summaryRows(inputs)) {\n if (!pendingSectionInserted && rowClass === \"pending\") {\n const pendingHeading = document.createElement(\"p\");\n pendingHeading.className = \"summary-section-title\";\n pendingHeading.textContent = t(\"summaryPending\");\n list.appendChild(pendingHeading);\n pendingSectionInserted = true;\n }\n const row = document.createElement(\"div\");\n row.className = \"summary-row\";\n row.setAttribute(\"role\", \"listitem\");\n if (rowClass) row.classList.add(rowClass);\n const labelNode = document.createElement(\"div\");\n labelNode.className = \"summary-label\";\n labelNode.textContent = label;\n const valueNode = document.createElement(\"div\");\n valueNode.className = \"summary-value\";\n if (valueTone) {\n const badge = document.createElement(\"span\");\n badge.className = `summary-status ${valueTone}`;\n badge.textContent = value;\n valueNode.appendChild(badge);\n } else {\n valueNode.textContent = value;\n }\n row.append(labelNode, valueNode);\n list.appendChild(row);\n }\n\n const account = selectedAccount();\n const currentEntry = currentEntryForAccount(state.selected, account);\n const currentMode = normalizeExecutionMode(currentEntry?.execution_mode, currentEntry?.dry_run_only);\n el(\"mode-pill\").textContent = currentMode ? modeLabel(currentMode) : t(\"notRead\");\n }\n\n function setPlanCheck(id, value, tone) {\n const node = el(id);\n if (!node) return;\n node.textContent = value;\n node.dataset.tone = tone || \"neutral\";\n }\n\n function renderPlanReadiness() {\n const platform = state.selected;\n const account = selectedAccount(platform);\n const configured = hasPrivateConfig() && Boolean(account?.key);\n const runnable = configured && hasRunnableStrategySelection(platform);\n const riskValid = configured && hasValidExecutionCashPolicy(platform) &&\n hasValidIncomeLayerPolicy(platform) && hasValidOptionOverlayPolicy(platform) && hasValidDcaPolicy(platform);\n const form = state.forms[platform];\n const isNonLive = normalizeExecutionMode(form?.executionMode, false) !== \"live\";\n setPlanCheck(\n \"plan-check-account\",\n configured ? t(\"planCheckSelected\") : t(\"planCheckWaiting\"),\n configured ? \"ready\" : \"neutral\",\n );\n setPlanCheck(\n \"plan-check-strategy\",\n runnable ? t(\"planCheckValid\") : (configured ? t(\"planCheckFix\") : t(\"planCheckWaiting\")),\n runnable ? \"ready\" : (configured ? \"warning\" : \"neutral\"),\n );\n setPlanCheck(\n \"plan-check-risk\",\n riskValid ? t(\"planCheckValid\") : (configured ? t(\"planCheckFix\") : t(\"planCheckWaiting\")),\n riskValid ? \"ready\" : (configured ? \"warning\" : \"neutral\"),\n );\n setPlanCheck(\n \"plan-check-authority\",\n isNonLive ? t(\"planCheckNonLive\") : t(\"planCheckNoAuthority\"),\n isNonLive ? \"ready\" : \"warning\",\n );\n }\n\n function renderAuth() {\n const status = el(\"auth-status\");\n const loginLink = el(\"login-link\");\n const logoutButton = el(\"logout-button\");\n const signedIn = Boolean(state.auth.allowed && state.auth.login);\n\n status.hidden = !signedIn;\n status.textContent = signedIn ? t(\"signedInAs\").replace(\"{login}\", state.auth.login) : \"\";\n loginLink.hidden = signedIn;\n loginLink.href = \"/login\";\n loginLink.textContent = t(\"login\");\n logoutButton.hidden = !signedIn;\n logoutButton.textContent = t(\"logout\");\n\n const dispatch = el(\"dispatch-button\");\n const hasPrivateAccounts = state.configSource === \"private\";\n const loadingConfig = state.configSource === \"loading\";\n const hasRunnableStrategy = hasRunnableStrategySelection();\n const hasValidReserve = hasValidExecutionCashPolicy();\n const hasValidIncomeLayer = hasValidIncomeLayerPolicy();\n const hasValidOptionOverlay = hasValidOptionOverlayPolicy();\n const hasValidDca = hasValidDcaPolicy();\n const hasValidStrategy = hasRunnableStrategy &&\n hasValidReserve &&\n hasValidIncomeLayer &&\n hasValidOptionOverlay &&\n hasValidDca;\n const hasPendingChange = hasPrivateAccounts && hasValidStrategy && hasPendingChanges(buildInputs());\n dispatch.disabled = !state.auth.allowed || loadingConfig || !hasPrivateAccounts || !hasValidStrategy || !hasPendingChange;\n dispatch.textContent = state.auth.allowed\n ? (loadingConfig\n ? t(\"loadingConfig\")\n : (hasPrivateAccounts ? (hasValidStrategy ? (hasPendingChange ? t(\"runSwitch\") : t(\"noChanges\")) : t(\"configureAccounts\")) : t(\"configureAccounts\")))\n : t(\"loginToRun\");\n const note = el(\"action-note\");\n note.textContent = state.auth.allowed\n ? (loadingConfig\n ? t(\"loadingConfigNote\")\n : (hasPrivateAccounts\n ? (hasRunnableStrategy\n ? (hasValidReserve\n ? (hasValidIncomeLayer\n ? (hasValidOptionOverlay\n ? (hasValidDca ? (hasPendingChange ? t(\"readyNote\") : \"\") : t(\"invalidDcaNote\"))\n : t(\"invalidOptionOverlayNote\"))\n : t(\"invalidIncomeLayerNote\"))\n : (executionCashPolicyConflict(state.forms[state.selected])\n ? t(\"invalidExecutionCashPolicyNote\")\n : t(\"invalidReservePolicyNote\")))\n : strategyActionNoteText())\n : t(\"missingConfigNote\")))\n : t(\"readonlyNote\");\n note.classList.toggle(\n \"warning\",\n state.auth.allowed && !loadingConfig && (!hasPrivateAccounts || !hasValidStrategy),\n );\n }\n\n function renderAppVisibility() {\n document.body.classList.toggle(\"app-loading\", !state.appReady);\n el(\"boot-message\").textContent = t(state.bootMessageKey);\n }\n\n function normalizeControlPlanePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid control plane payload\");\n const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n attention: payload.attention && typeof payload.attention === \"object\" && !Array.isArray(payload.attention)\n ? {\n status: [\"research_only\", \"attention_required\", \"unavailable\"].includes(payload.attention.status)\n ? payload.attention.status\n : \"unavailable\",\n reason_codes: Array.isArray(payload.attention.reason_codes) ? payload.attention.reason_codes : [],\n }\n : { status: \"unavailable\", reason_codes: [\"control_plane_attention_missing\"] },\n candidates: candidates.filter((item) => item && typeof item === \"object\" && item.lifecycle && typeof item.lifecycle === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function normalizeOwnerDecisionQueue(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid owner decision queue\");\n const candidates = Array.isArray(payload.candidates) ? payload.candidates : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n candidates: candidates.filter((entry) => entry && typeof entry === \"object\"\n && entry.candidate && typeof entry.candidate === \"object\"\n && typeof entry.candidate_evidence_sha256 === \"string\"),\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function ownerDecisionEntry(candidateId) {\n return state.ownerDecisions.candidates.find((entry) => entry.candidate?.candidate_id === candidateId) || null;\n }\n\n function ownerDecisionLabel(decision) {\n return {\n approve_limited_live_canary: t(\"ownerDecisionApprove\"),\n keep_parked: t(\"ownerDecisionPark\"),\n retire_candidate: t(\"ownerDecisionRetire\"),\n }[decision] || String(decision || \"—\");\n }\n\n const operatorLabels = {\n kind: {\n individual: { zh: \"策略\", en: \"Strategy\" },\n portfolio: { zh: \"组合\", en: \"Portfolio\" },\n plugin: { zh: \"插件\", en: \"Plugin\" },\n },\n status: {\n research: { zh: \"观察中\", en: \"Monitoring\" },\n evidence_pending: { zh: \"待复核\", en: \"To review\" },\n verified: { zh: \"正常\", en: \"Normal\" },\n deferred: { zh: \"待复核\", en: \"To review\" },\n parked: { zh: \"已暂停\", en: \"Paused\" },\n paper: { zh: \"测试中\", en: \"Testing\" },\n shadow: { zh: \"观察中\", en: \"Monitoring\" },\n owner_decision_required: { zh: \"等待处理\", en: \"Needs attention\" },\n },\n action: {\n keep_research: { zh: \"继续观察\", en: \"Keep monitoring\" },\n defer: { zh: \"稍后复核\", en: \"Review later\" },\n park: { zh: \"保持暂停\", en: \"Keep paused\" },\n auto_paper_evaluation: { zh: \"继续测试\", en: \"Continue testing\" },\n auto_shadow_evaluation: { zh: \"继续观察\", en: \"Keep monitoring\" },\n owner_live_decision: { zh: \"需要确认\", en: \"Confirmation needed\" },\n none: { zh: \"暂无操作\", en: \"No action\" },\n },\n freshness: {\n fresh: { zh: \"最新\", en: \"Current\" },\n stale: { zh: \"更新延迟\", en: \"Delayed\" },\n unavailable: { zh: \"暂不可用\", en: \"Unavailable\" },\n unknown: { zh: \"暂不可用\", en: \"Unavailable\" },\n },\n };\n\n function operatorLabel(group, value) {\n const label = operatorLabels[group]?.[value];\n return label ? (state.lang === \"zh\" ? label.zh : label.en) : \"—\";\n }\n\n function controlPlaneDataStatusText(status) {\n return status === \"ready\"\n ? t(\"controlDataReady\")\n : (status === \"stale\" ? t(\"controlDataStale\") : t(\"controlDataUnavailable\"));\n }\n\n function controlPlaneAttentionText(attention) {\n const status = attention?.status || \"unavailable\";\n if (status === \"research_only\") return t(\"controlAttentionResearchOnly\");\n if (status === \"attention_required\") return t(\"controlAttentionRequired\");\n return t(\"controlAttentionUnavailable\");\n }\n\n function renderControlPlane() {\n const payload = state.controlPlane.payload;\n const summary = payload.summary || {};\n el(\"control-plane-status\").textContent = `${controlPlaneDataStatusText(payload.data_status)} · ${controlPlaneAttentionText(payload.attention)}`;\n el(\"control-plane-computed-at\").textContent = payload.computed_at\n ? t(\"controlComputedAt\").replace(\"{time}\", new Date(payload.computed_at).toLocaleString())\n : t(\"controlComputedAt\").replace(\"{time}\", \"—\");\n el(\"control-count-candidates\").textContent = String(Number(summary.candidate_count) || 0);\n el(\"control-count-deferred\").textContent = String(Number(summary.deferred) || 0);\n el(\"control-count-parked\").textContent = String(Number(summary.parked) || 0);\n el(\"control-count-owner-decision\").textContent = String(Number(summary.owner_decision_required) || 0);\n\n const notice = el(\"control-plane-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"controlLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"controlStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"controlUnavailableNotice\");\n } else if (payload.attention?.status === \"attention_required\") {\n notice.textContent = t(\"controlAttentionNotice\")\n .replace(\"{deferred}\", String(Number(summary.deferred) || 0))\n .replace(\"{parked}\", String(Number(summary.parked) || 0))\n .replace(\"{signals}\", String(payload.attention.reason_codes?.length || 0));\n } else if (payload.errors?.length) {\n notice.textContent = t(\"controlUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = t(\"controlNormalNotice\");\n }\n\n const list = el(\"control-plane-list\");\n list.replaceChildren();\n if (!payload.candidates.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"controlEmptyCandidates\");\n list.appendChild(empty);\n return;\n }\n for (const item of payload.candidates) {\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = `${operatorLabel(\"kind\", item.candidate_kind)} · ${domainLabel(item.domain || \"\")}`;\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(item.candidate_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = item.recommendation?.reason || t(\"controlNoRecommendation\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"controlItemMeta\")\n .replace(\"{kind}\", operatorLabel(\"status\", item.lifecycle?.status))\n .replace(\"{domain}\", domainLabel(item.domain || \"\"))\n .replace(\"{freshness}\", operatorLabel(\"freshness\", item.freshness?.status || \"unknown\"));\n main.append(meta, title, reason, detail);\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"controlNext\");\n const stage = document.createElement(\"strong\");\n stage.textContent = operatorLabel(\"action\", item.recommendation?.code || \"none\");\n const recommendation = document.createElement(\"small\");\n recommendation.textContent = `${t(\"controlStatus\")}:${operatorLabel(\"status\", item.lifecycle?.status)}`;\n stateBlock.append(label, stage, recommendation);\n const ownerEntry = ownerDecisionEntry(item.candidate_id);\n if (ownerEntry) {\n const ownerDecision = document.createElement(\"div\");\n ownerDecision.className = \"owner-decision\";\n const ownerTitle = document.createElement(\"strong\");\n ownerTitle.textContent = t(\"ownerDecisionTitle\");\n const ownerDetail = document.createElement(\"small\");\n if (ownerEntry.intent) {\n ownerDetail.textContent = t(\"ownerDecisionRecorded\")\n .replace(\"{decision}\", ownerDecisionLabel(ownerEntry.intent.decision));\n } else if (!state.auth.admin) {\n ownerDetail.textContent = t(\"ownerDecisionAdminOnly\");\n } else {\n ownerDetail.textContent = t(\"ownerDecisionReady\");\n }\n ownerDecision.append(ownerTitle, ownerDetail);\n if (!ownerEntry.intent && state.auth.admin) {\n const actions = document.createElement(\"div\");\n actions.className = \"owner-decision__actions\";\n const submitting = state.ownerDecisions.submittingCandidateId === item.candidate_id;\n for (const [decision, labelKey] of [\n [\"approve_limited_live_canary\", \"ownerDecisionApprove\"],\n [\"keep_parked\", \"ownerDecisionPark\"],\n [\"retire_candidate\", \"ownerDecisionRetire\"],\n ]) {\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"owner-decision__button\";\n button.dataset.ownerDecision = decision;\n button.dataset.candidateId = item.candidate_id;\n button.dataset.candidateEvidenceSha256 = ownerEntry.candidate_evidence_sha256;\n button.disabled = submitting;\n button.textContent = submitting ? t(\"ownerDecisionSubmitting\") : t(labelKey);\n actions.appendChild(button);\n }\n ownerDecision.appendChild(actions);\n }\n main.appendChild(ownerDecision);\n }\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeReconciliationRecoveryPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\"invalid reconciliation recovery payload\");\n }\n const recoveries = Array.isArray(payload.recoveries) ? payload.recoveries : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n recoveries: recoveries.filter((entry) => entry && typeof entry === \"object\"\n && entry.recovery && typeof entry.recovery === \"object\"\n && entry.recovery.dual_review && typeof entry.recovery.dual_review === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderReconciliationRecovery() {\n const payload = state.reconciliationRecovery.payload;\n const notice = el(\"reconciliation-recovery-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"reconciliationRecoveryLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"reconciliationRecoveryStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"reconciliationRecoveryUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"reconciliationRecoveryUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"reconciliationRecoveryNoOrder\");\n }\n const list = el(\"reconciliation-recovery-list\");\n list.replaceChildren();\n if (!payload.recoveries.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"reconciliationRecoveryEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.recoveries) {\n const recovery = entry.recovery || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"reconciliationRecoveryMeta\")\n .replace(\"{platform}\", recovery.platform || \"unknown\")\n .replace(\"{strategy}\", recovery.strategy_profile || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(recovery.recovery_id || \"unknown\");\n const detail = document.createElement(\"p\");\n detail.className = \"health-card__reason\";\n detail.textContent = t(\"reconciliationRecoveryDetail\")\n .replace(\"{state}\", recovery.reconciliation_state || \"unknown\")\n .replace(\"{samples}\", String(recovery.evidence_sample_count || 0))\n .replace(\"{review}\", recovery.dual_review?.outcome || \"unknown\")\n .replace(\"{lastObserved}\", recovery.last_observed_at || \"—\");\n const status = document.createElement(\"div\");\n status.className = \"health-card__meta\";\n const ready = payload.data_status === \"ready\"\n && entry.freshness?.data_status === \"ready\"\n && recovery.readiness === \"awaiting_human_confirmation\"\n && recovery.blocker_codes?.length === 0\n && recovery.dual_review?.outcome === \"approved\"\n && recovery.dual_review?.evidence_binding_sha256 === recovery.candidate_sha256;\n if (entry.confirmation) {\n status.textContent = t(\"reconciliationRecoveryConfirmed\");\n } else if (!ready) {\n status.textContent = t(\"reconciliationRecoveryBlocked\")\n .replace(\"{blockers}\", (recovery.blocker_codes || []).join(\", \") || recovery.readiness || \"unknown\");\n } else if (!state.auth.admin) {\n status.textContent = t(\"reconciliationRecoveryAdminOnly\");\n } else {\n status.textContent = t(\"reconciliationRecoveryReady\");\n }\n main.append(meta, title, detail, status);\n if (ready && !entry.confirmation && state.auth.admin) {\n const actions = document.createElement(\"div\");\n actions.className = \"owner-decision__actions\";\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.className = \"owner-decision__button\";\n button.dataset.reconciliationRecoveryConfirm = \"true\";\n button.dataset.recoveryId = recovery.recovery_id || \"\";\n button.dataset.candidateSha256 = recovery.candidate_sha256 || \"\";\n button.dataset.dualReviewBindingSha256 = recovery.dual_review?.evidence_binding_sha256 || \"\";\n const submitting = state.reconciliationRecovery.submittingRecoveryId === recovery.recovery_id;\n button.disabled = submitting;\n button.textContent = submitting ? t(\"reconciliationRecoverySubmitting\") : t(\"reconciliationRecoveryConfirm\");\n actions.appendChild(button);\n main.appendChild(actions);\n }\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"controlNext\");\n const stateText = document.createElement(\"strong\");\n stateText.textContent = entry.confirmation ? \"confirmed\" : (ready ? \"confirm\" : \"blocked\");\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, stateText, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n const M0_RESEARCH_DISPLAY_LIMIT = 100;\n\n const m0ResearchLabels = {\n subject: {\n asset_idea: { zh: \"资产观察\", en: \"Asset idea\" },\n theme_context: { zh: \"主题背景\", en: \"Theme context\" },\n strategy_hypothesis: { zh: \"研究假设\", en: \"Research hypothesis\" },\n risk_context: { zh: \"风险背景\", en: \"Risk context\" },\n },\n state: {\n candidate: { zh: \"待验证\", en: \"Candidate\" },\n source_verification_required: { zh: \"等待来源核验\", en: \"Source verification required\" },\n deferred: { zh: \"暂缓\", en: \"Deferred\" },\n context_only: { zh: \"仅作背景\", en: \"Context only\" },\n },\n freshness: {\n fresh: { zh: \"有效\", en: \"Fresh\" },\n stale: { zh: \"已过期\", en: \"Stale\" },\n unknown: { zh: \"未知\", en: \"Unknown\" },\n },\n horizon: {\n short: { zh: \"短期\", en: \"Short\" },\n medium: { zh: \"中期\", en: \"Medium\" },\n long: { zh: \"长期\", en: \"Long\" },\n not_applicable: { zh: \"不适用\", en: \"Not applicable\" },\n },\n confidence: {\n high: { zh: \"高\", en: \"High\" },\n medium: { zh: \"中\", en: \"Medium\" },\n low: { zh: \"低\", en: \"Low\" },\n mixed: { zh: \"混合\", en: \"Mixed\" },\n no_event: { zh: \"无事件\", en: \"No event\" },\n unknown: { zh: \"未知\", en: \"Unknown\" },\n },\n style: {\n event_driven: { zh: \"事件驱动\", en: \"Event driven\" },\n long_horizon_growth: { zh: \"长期成长\", en: \"Long-horizon growth\" },\n value_quality: { zh: \"价值质量\", en: \"Value quality\" },\n macro_context: { zh: \"宏观背景\", en: \"Macro context\" },\n mixed_research: { zh: \"综合研究\", en: \"Mixed research\" },\n },\n conflict: {\n none: { zh: \"无\", en: \"None\" },\n conflict: { zh: \"存在\", en: \"Present\" },\n unavailable: { zh: \"不可用\", en: \"Unavailable\" },\n },\n drift: {\n none: { zh: \"无\", en: \"None\" },\n drift: { zh: \"存在\", en: \"Present\" },\n unavailable: { zh: \"不可用\", en: \"Unavailable\" },\n },\n };\n\n function m0ResearchLabel(group, value) {\n const entry = m0ResearchLabels[group]?.[value];\n return entry ? (state.lang === \"zh\" ? entry.zh : entry.en) : \"—\";\n }\n\n function m0ResearchTimestamp(value) {\n if (typeof value !== \"string\" || !value) return \"—\";\n const parsed = new Date(value);\n return Number.isNaN(parsed.getTime()) ? \"—\" : parsed.toLocaleString();\n }\n\n function m0ResearchHorizons(values) {\n if (!Array.isArray(values) || !values.length) return \"—\";\n const labels = values.map((value) => m0ResearchLabel(\"horizon\", value)).filter((value) => value !== \"—\");\n return labels.length ? labels.join(\" / \") : \"—\";\n }\n\n function m0ResearchEntries(subjects) {\n if (!Array.isArray(subjects)) return [];\n const entries = [];\n for (const item of subjects) {\n if (!item || typeof item !== \"object\" || !item.subject || typeof item.subject !== \"object\") continue;\n if (!Array.isArray(item.observations)) continue;\n for (const observation of item.observations) {\n if (!observation || typeof observation !== \"object\" || !observation.research_context) continue;\n entries.push({\n subject: item.subject,\n observation,\n horizonConflict: item.horizon_conflict,\n historicalStaleHorizonDrift: item.historical_stale_horizon_drift,\n });\n }\n }\n return entries;\n }\n\n function normalizeM0ResearchPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid M0 research payload\");\n const subjects = Array.isArray(payload.subjects) ? payload.subjects : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n viewed_at: typeof payload.viewed_at === \"string\" ? payload.viewed_at : null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n subjects: subjects.filter((item) => item && typeof item === \"object\"\n && item.subject && typeof item.subject === \"object\" && Array.isArray(item.observations)),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderM0Research() {\n const payload = state.m0Research.payload;\n const entries = m0ResearchEntries(payload.subjects);\n const shownEntries = entries.slice(0, M0_RESEARCH_DISPLAY_LIMIT);\n const notice = el(\"m0-research-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"m0ResearchLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"m0ResearchStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"m0ResearchUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"m0ResearchUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n const more = entries.length > M0_RESEARCH_DISPLAY_LIMIT\n ? ` ${t(\"m0ResearchMore\").replace(\"{count}\", String(M0_RESEARCH_DISPLAY_LIMIT))}`\n : \"\";\n notice.textContent = `${t(\"m0ResearchNoOrder\")}${more}`;\n }\n\n const list = el(\"m0-research-list\");\n list.replaceChildren();\n if (!shownEntries.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"m0ResearchEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of shownEntries) {\n const subject = entry.subject || {};\n const observation = entry.observation || {};\n const context = observation.research_context || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"m0ResearchMeta\")\n .replace(\"{kind}\", m0ResearchLabel(\"subject\", subject.kind))\n .replace(\"{viewed}\", m0ResearchTimestamp(payload.viewed_at));\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = typeof subject.identifier === \"string\" ? subject.identifier : \"—\";\n const stateFreshness = document.createElement(\"p\");\n stateFreshness.className = \"health-card__reason\";\n stateFreshness.textContent = t(\"m0ResearchStateFreshness\")\n .replace(\"{state}\", m0ResearchLabel(\"state\", context.state))\n .replace(\"{freshness}\", m0ResearchLabel(\"freshness\", observation.freshness?.status));\n const horizons = document.createElement(\"div\");\n horizons.className = \"health-card__meta\";\n horizons.textContent = t(\"m0ResearchHorizons\")\n .replace(\"{primary}\", m0ResearchLabel(\"horizon\", context.primary_horizon))\n .replace(\"{suitable}\", m0ResearchHorizons(context.suitable_horizons));\n const evidence = document.createElement(\"div\");\n evidence.className = \"health-card__meta\";\n evidence.textContent = t(\"m0ResearchEvidence\")\n .replace(\"{confidence}\", m0ResearchLabel(\"confidence\", context.source_confidence))\n .replace(\"{style}\", m0ResearchLabel(\"style\", context.source_style))\n .replace(\"{digest}\", `${shortResearchDigest(observation.source_report_digest)} / ${shortResearchDigest(observation.source_entry_digest)}`);\n const consistency = document.createElement(\"div\");\n consistency.className = \"health-card__meta\";\n consistency.textContent = t(\"m0ResearchConsistency\")\n .replace(\"{conflict}\", m0ResearchLabel(\"conflict\", entry.horizonConflict?.status))\n .replace(\"{drift}\", m0ResearchLabel(\"drift\", entry.historicalStaleHorizonDrift?.status));\n main.append(meta, title, stateFreshness, horizons, evidence, consistency);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = state.lang === \"zh\" ? \"研究\" : \"Research\";\n const freshness = document.createElement(\"strong\");\n freshness.textContent = m0ResearchLabel(\"freshness\", observation.freshness?.status);\n const stateLabel = document.createElement(\"small\");\n stateLabel.textContent = m0ResearchLabel(\"state\", context.state);\n status.append(label, freshness, stateLabel);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function normalizeAdaptiveSelectionPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid adaptive selection payload\");\n const selections = Array.isArray(payload.selections) ? payload.selections : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n selections: selections.filter((item) => item && typeof item === \"object\"\n && typeof item.source_id === \"string\" && item.decision && typeof item.decision === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderAdaptiveSelection() {\n const payload = state.adaptiveSelection.payload;\n const notice = el(\"adaptive-selection-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"adaptiveSelectionLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"adaptiveSelectionStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"adaptiveSelectionUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"adaptiveSelectionUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n notice.textContent = payload.policy?.notice || t(\"adaptiveSelectionNoOrder\");\n }\n\n const list = el(\"adaptive-selection-list\");\n list.replaceChildren();\n if (!payload.selections.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"adaptiveSelectionEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.selections) {\n const decision = entry.decision || {};\n const context = decision.market_context || {};\n const recommended = decision.candidates?.find((item) => item.accepted\n && item.strategy_profile === decision.recommended_strategy_profile) || null;\n const rejected = (decision.candidates || []).filter((item) => !item.accepted);\n const reasons = (recommended?.reasons?.length ? recommended.reasons : rejected.flatMap((item) => item.reasons || []))\n .slice(0, 3)\n .join(\", \");\n\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"adaptiveSelectionMeta\")\n .replace(\"{source}\", entry.source_id || \"unknown\")\n .replace(\"{domain}\", domainLabel(context.domain || \"\"))\n .replace(\"{asOf}\", context.as_of || \"—\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = recommended\n ? `${t(\"adaptiveSelectionRecommended\")} · ${recommended.strategy_profile}`\n : t(\"adaptiveSelectionNoCandidate\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = reasons\n ? t(\"adaptiveSelectionReason\").replace(\"{reasons}\", reasons)\n : t(\"adaptiveSelectionNoOrder\");\n main.append(meta, title, reason);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = state.lang === \"zh\" ? \"研究次数\" : \"Research runs\";\n const score = document.createElement(\"strong\");\n score.textContent = typeof recommended?.score === \"number\" ? recommended.score.toFixed(3) : \"—\";\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n status.append(label, score, freshness);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function normalizeExecutionEvidencePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid execution evidence payload\");\n const deployments = Array.isArray(payload.deployments) ? payload.deployments : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n deployments: deployments.filter((item) => item && typeof item === \"object\" && item.deployment && typeof item.deployment === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function executionEvidenceReceiptOutcomeLabel(value) {\n return {\n not_due: t(\"executionEvidenceReceiptNotDue\"),\n no_action: t(\"executionEvidenceReceiptNoAction\"),\n risk_blocked: t(\"executionEvidenceReceiptRiskBlocked\"),\n submitted: t(\"executionEvidenceReceiptSubmitted\"),\n broker_acknowledged: t(\"executionEvidenceReceiptAcknowledged\"),\n partially_filled: t(\"executionEvidenceReceiptPartiallyFilled\"),\n filled: t(\"executionEvidenceReceiptFilled\"),\n reconciliation_required: t(\"executionEvidenceReceiptReconciliation\"),\n failed: t(\"executionEvidenceReceiptFailed\"),\n }[value] || t(\"executionEvidenceReceiptMissing\");\n }\n\n function executionEvidenceReceiptConfirmationLabel(value) {\n return {\n not_applicable: t(\"executionEvidenceConfirmationNotApplicable\"),\n not_observed: t(\"executionEvidenceConfirmationNotObserved\"),\n acknowledged: t(\"executionEvidenceConfirmationAcknowledged\"),\n partially_filled: t(\"executionEvidenceConfirmationPartiallyFilled\"),\n filled: t(\"executionEvidenceConfirmationFilled\"),\n reconciliation_required: t(\"executionEvidenceConfirmationReconciliation\"),\n }[value] || t(\"executionEvidenceConfirmationNotObserved\");\n }\n\n function renderExecutionEvidence() {\n const payload = state.executionEvidence.payload;\n const notice = el(\"execution-evidence-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"executionEvidenceLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"executionEvidenceStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"executionEvidenceUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"executionEvidenceUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"executionEvidenceNoOrder\");\n }\n\n const list = el(\"execution-evidence-list\");\n list.replaceChildren();\n if (!payload.deployments.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"executionEvidenceEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.deployments) {\n const deployment = entry.deployment || {};\n const strategy = deployment.strategy || {};\n const target = deployment.target || {};\n const capabilities = deployment.capabilities || {};\n const evidence = deployment.evidence || {};\n const executionReceipt = deployment.execution_receipt || null;\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"executionEvidenceMeta\")\n .replace(\"{platform}\", target.platform || \"unknown\")\n .replace(\"{environment}\", target.environment || \"unknown\")\n .replace(\"{source}\", entry.source_id || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(strategy.candidate_id || deployment.deployment_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = deployment.recommendation?.reason_code || t(\"executionEvidenceNoOrder\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"executionEvidenceDetail\")\n .replace(\"{strategy}\", evidence.strategy || \"unknown\")\n .replace(\"{data}\", evidence.target_data || \"unknown\")\n .replace(\"{execution}\", evidence.target_execution || \"unknown\")\n .replace(\"{shadow}\", capabilities.shadow || \"unknown\")\n .replace(\"{paper}\", capabilities.paper || \"unknown\");\n const receiptDetail = document.createElement(\"div\");\n receiptDetail.className = \"health-card__meta\";\n receiptDetail.textContent = executionReceipt\n ? t(\"executionEvidenceReceipt\")\n .replace(\"{outcome}\", executionEvidenceReceiptOutcomeLabel(executionReceipt.outcome))\n .replace(\"{confirmation}\", executionEvidenceReceiptConfirmationLabel(executionReceipt.broker_confirmation))\n : t(\"executionEvidenceReceiptMissing\");\n main.append(meta, title, reason, detail, receiptDetail);\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"executionEvidenceNext\");\n const recommendation = document.createElement(\"strong\");\n recommendation.textContent = deployment.recommendation?.code || \"parked\";\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, recommendation, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeRuntimeTargetLifecyclePayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) {\n throw new Error(\"invalid runtime target lifecycle payload\");\n }\n const targets = Array.isArray(payload.targets) ? payload.targets : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n targets: targets.filter((entry) => entry && typeof entry === \"object\"\n && entry.target && typeof entry.target === \"object\"\n && entry.target.target && typeof entry.target.target === \"object\"\n && entry.target.monitoring && typeof entry.target.monitoring === \"object\"\n && entry.target.disposition && typeof entry.target.disposition === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function runtimeTargetLifecycleCheckLabel(value) {\n return {\n pass: t(\"runtimeTargetLifecycleCheckPass\"),\n attention: t(\"runtimeTargetLifecycleCheckAttention\"),\n not_due: t(\"runtimeTargetLifecycleCheckNotDue\"),\n not_applicable: t(\"runtimeTargetLifecycleCheckNotApplicable\"),\n unavailable: t(\"runtimeTargetLifecycleCheckUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleCheckUnavailable\");\n }\n\n function runtimeTargetLifecycleStateLabel(value) {\n return value === \"enabled\"\n ? t(\"runtimeTargetLifecycleStateEnabled\")\n : t(\"runtimeTargetLifecycleStateDisabled\");\n }\n\n function runtimeTargetLifecycleObservationLabel(value) {\n return {\n not_due: t(\"runtimeTargetLifecycleObservationNotDue\"),\n monitoring_only: t(\"runtimeTargetLifecycleObservationMonitoringOnly\"),\n not_applicable: t(\"runtimeTargetLifecycleObservationNotApplicable\"),\n attention: t(\"runtimeTargetLifecycleObservationAttention\"),\n unavailable: t(\"runtimeTargetLifecycleObservationUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleObservationUnavailable\");\n }\n\n function runtimeTargetLifecycleOrderEvidenceLabel(value) {\n return {\n not_collected: t(\"runtimeTargetLifecycleOrderEvidenceNotCollected\"),\n }[value] || t(\"runtimeTargetLifecycleOrderEvidenceNotCollected\");\n }\n\n function runtimeTargetLifecycleDispositionLabel(value) {\n return {\n continue_enabled_monitoring: t(\"runtimeTargetLifecycleDispositionEnabled\"),\n continue_disabled_validation: t(\"runtimeTargetLifecycleDispositionDisabled\"),\n parked: t(\"runtimeTargetLifecycleDispositionParked\"),\n }[value] || t(\"runtimeTargetLifecycleDispositionParked\");\n }\n\n function runtimeTargetLifecycleReasonLabel(value) {\n return {\n none: t(\"runtimeTargetLifecycleReasonNone\"),\n target_intentionally_disabled: t(\"runtimeTargetLifecycleReasonDisabled\"),\n runtime_guard_attention: t(\"runtimeTargetLifecycleReasonRuntimeGuard\"),\n execution_heartbeat_attention: t(\"runtimeTargetLifecycleReasonHeartbeat\"),\n monitoring_unavailable: t(\"runtimeTargetLifecycleReasonUnavailable\"),\n }[value] || t(\"runtimeTargetLifecycleReasonUnavailable\");\n }\n\n function renderRuntimeTargetLifecycle() {\n const payload = state.runtimeTargetLifecycle.payload;\n const notice = el(\"runtime-target-lifecycle-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"runtimeTargetLifecycleLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"runtimeTargetLifecycleStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"runtimeTargetLifecycleUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"runtimeTargetLifecycleUpstreamNotice\").replace(\"{count}\", String(payload.errors.length));\n } else {\n notice.textContent = payload.policy?.notice || t(\"runtimeTargetLifecycleNoOrder\");\n }\n\n const list = el(\"runtime-target-lifecycle-list\");\n list.replaceChildren();\n if (!payload.targets.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"runtimeTargetLifecycleEmpty\");\n list.appendChild(empty);\n return;\n }\n\n const targets = [...payload.targets].sort((left, right) => {\n const leftParked = left.target?.disposition?.code === \"parked\" ? 0 : 1;\n const rightParked = right.target?.disposition?.code === \"parked\" ? 0 : 1;\n if (leftParked !== rightParked) return leftParked - rightParked;\n return String(left.target?.target_id || \"\").localeCompare(String(right.target?.target_id || \"\"));\n });\n for (const entry of targets) {\n const target = entry.target || {};\n const configuration = target.target || {};\n const monitoring = target.monitoring || {};\n const disposition = target.disposition || {};\n const executionObservation = entry.execution_observation || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"runtimeTargetLifecycleMeta\")\n .replace(\"{platform}\", configuration.platform || \"unknown\")\n .replace(\"{state}\", runtimeTargetLifecycleStateLabel(configuration.configured_state))\n .replace(\"{mode}\", configuration.execution_mode || \"unknown\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(target.target_id || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = runtimeTargetLifecycleReasonLabel(disposition.reason_code);\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"runtimeTargetLifecycleDetail\")\n .replace(\"{guard}\", runtimeTargetLifecycleCheckLabel(monitoring.runtime_guard))\n .replace(\"{heartbeat}\", runtimeTargetLifecycleCheckLabel(monitoring.execution_heartbeat));\n const observation = document.createElement(\"div\");\n observation.className = \"health-card__meta\";\n observation.textContent = t(\"runtimeTargetLifecycleObservation\")\n .replace(\"{observation}\", runtimeTargetLifecycleObservationLabel(executionObservation.code))\n .replace(\"{evidence}\", runtimeTargetLifecycleOrderEvidenceLabel(executionObservation.order_or_fill_evidence));\n main.append(meta, title, reason, detail, observation);\n\n const stateBlock = document.createElement(\"div\");\n stateBlock.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = t(\"runtimeTargetLifecycleNext\");\n const current = document.createElement(\"strong\");\n current.textContent = runtimeTargetLifecycleDispositionLabel(disposition.code);\n const freshness = document.createElement(\"small\");\n freshness.textContent = entry.freshness?.data_status || \"unknown\";\n stateBlock.append(label, current, freshness);\n card.append(main, stateBlock);\n list.appendChild(card);\n }\n }\n\n function normalizeResearchTaskPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid research task payload\");\n const tasks = Array.isArray(payload.tasks) ? payload.tasks : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n tasks: tasks.filter((item) => item && typeof item === \"object\" && item.task && typeof item.task === \"object\"),\n policy: payload.policy && typeof payload.policy === \"object\" ? payload.policy : {},\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function shortResearchDigest(value) {\n const text = String(value || \"\");\n return /^[0-9a-f]{64}$/.test(text) ? `${text.slice(0, 10)}…` : \"—\";\n }\n\n function renderResearchTasks() {\n const payload = state.researchTasks.payload;\n const notice = el(\"research-task-notice\");\n if (!state.auth.allowed) {\n notice.textContent = t(\"researchTaskLoginNotice\");\n } else if (payload.data_status === \"stale\") {\n notice.textContent = t(\"researchTaskStaleNotice\");\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = t(\"researchTaskUnavailableNotice\");\n } else if (payload.errors?.length) {\n notice.textContent = t(\"researchTaskUpstreamNotice\").replace(\"{count}\", payload.errors.length);\n } else {\n notice.textContent = payload.policy?.notice || t(\"researchTaskNoOrder\");\n }\n\n const list = el(\"research-task-list\");\n list.replaceChildren();\n if (!payload.tasks.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = t(\"researchTaskEmpty\");\n list.appendChild(empty);\n return;\n }\n for (const entry of payload.tasks) {\n const task = entry.task || {};\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = t(\"researchTaskMeta\")\n .replace(\"{type}\", task.task_type || \"research\")\n .replace(\"{domain}\", domainLabel(task.target?.domain || \"\"))\n .replace(\"{created}\", task.created_at ? new Date(task.created_at).toLocaleString() : \"—\");\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = `${task.target?.candidate_id || \"unknown\"} · ${task.task_id || \"unknown\"}`;\n const hypothesis = document.createElement(\"p\");\n hypothesis.className = \"health-card__reason\";\n hypothesis.textContent = task.experiment?.hypothesis || t(\"researchTaskNoOrder\");\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = t(\"researchTaskLimits\")\n .replace(\"{runs}\", String(task.experiment?.max_runs || \"—\"))\n .replace(\"{seconds}\", String(task.experiment?.max_wall_seconds || \"—\"))\n .replace(\"{p1}\", shortResearchDigest(task.evidence?.p1_input_digest))\n .replace(\"{p2}\", shortResearchDigest(task.evidence?.p2_config_digest))\n .replace(\"{p3}\", shortResearchDigest(task.evidence?.p3_evidence_id));\n main.append(meta, title, hypothesis, detail);\n const status = document.createElement(\"div\");\n status.className = \"health-card__score\";\n const label = document.createElement(\"small\");\n label.textContent = \"NO ORDER\";\n const count = document.createElement(\"strong\");\n count.textContent = String(task.experiment?.max_runs || \"—\");\n const source = document.createElement(\"small\");\n source.textContent = entry.freshness?.data_status || \"unknown\";\n status.append(label, count, source);\n card.append(main, status);\n list.appendChild(card);\n }\n }\n\n function healthStatusLabel(status) {\n return { healthy: \"健康\", watch: \"观察\", review: \"复核\", critical: \"严重\" }[status] || \"未知\";\n }\n\n function normalizeHealthPayload(payload) {\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) throw new Error(\"invalid health payload\");\n const strategies = Array.isArray(payload.strategies) ? payload.strategies : [];\n return {\n data_status: [\"ready\", \"stale\", \"unavailable\"].includes(payload.data_status) ? payload.data_status : \"unavailable\",\n computed_at: payload.computed_at || null,\n summary: payload.summary && typeof payload.summary === \"object\" ? payload.summary : {},\n strategies: strategies.filter((item) => item && typeof item === \"object\" && [\"healthy\", \"watch\", \"review\", \"critical\"].includes(item.status)),\n errors: Array.isArray(payload.errors) ? payload.errors : [],\n };\n }\n\n function renderHealth() {\n const payload = state.health.payload;\n const summary = payload.summary || {};\n const statusText = payload.data_status === \"ready\"\n ? \"已更新\"\n : (payload.data_status === \"stale\" ? \"更新延迟\" : \"暂时无法读取\");\n el(\"health-status\").textContent = statusText;\n el(\"health-computed-at\").textContent = payload.computed_at\n ? `更新于:${new Date(payload.computed_at).toLocaleString()}`\n : \"更新于:—\";\n el(\"health-count-total\").textContent = String(Number(summary.strategy_count) || 0);\n el(\"health-count-healthy\").textContent = String(Number(summary.healthy) || 0);\n el(\"health-count-watch\").textContent = String(Number(summary.watch) || 0);\n el(\"health-count-review\").textContent = String(Number(summary.review) || 0);\n el(\"health-count-critical\").textContent = String(Number(summary.critical) || 0);\n\n const notice = el(\"health-notice\");\n if (!state.auth.allowed) {\n notice.textContent = \"登录后查看策略状态。\";\n } else if (payload.data_status === \"stale\") {\n notice.textContent = \"状态更新延迟,请稍后刷新。\";\n } else if (payload.data_status !== \"ready\") {\n notice.textContent = \"暂时无法读取策略状态,请稍后刷新。\";\n } else if (payload.errors?.length) {\n notice.textContent = \"部分策略状态暂不可用,请稍后重试。\";\n } else {\n notice.textContent = \"当前策略状态已更新。\";\n }\n\n const list = el(\"health-list\");\n list.replaceChildren();\n const strategies = payload.strategies.filter((item) => state.health.filter === \"all\" || item.status === state.health.filter);\n if (!strategies.length) {\n const empty = document.createElement(\"div\");\n empty.className = \"health-card__empty\";\n empty.textContent = \"当前没有可展示的策略状态。\";\n list.appendChild(empty);\n return;\n }\n for (const item of strategies) {\n const card = document.createElement(\"article\");\n card.className = \"health-card\";\n const main = document.createElement(\"div\");\n main.className = \"health-card__main\";\n const meta = document.createElement(\"div\");\n meta.className = \"health-card__meta\";\n meta.textContent = `${healthStatusLabel(item.status)} · ${item.domain || \"unknown\"}`;\n const title = document.createElement(\"h4\");\n title.className = \"health-card__title\";\n title.textContent = String(item.profile || \"unknown\");\n const reason = document.createElement(\"p\");\n reason.className = \"health-card__reason\";\n reason.textContent = `${item.decision?.label || \"证据不足,保持研究态\"}。${item.decision?.reason || \"没有可用的机器检查结果。\"}`;\n const detail = document.createElement(\"div\");\n detail.className = \"health-card__meta\";\n detail.textContent = `阶段:${item.review?.requested_stage || \"未标记\"} · 截至:${item.as_of || \"—\"}`;\n main.append(meta, title, reason, detail);\n const scoreBlock = document.createElement(\"div\");\n scoreBlock.className = \"health-card__score\";\n const scoreLabel = document.createElement(\"small\");\n scoreLabel.textContent = \"HEALTH\";\n const score = document.createElement(\"strong\");\n score.textContent = typeof item.score === \"number\" ? item.score.toFixed(1) : \"—\";\n const decision = document.createElement(\"small\");\n decision.textContent = item.decision?.code || \"evidence_missing\";\n scoreBlock.append(scoreLabel, score, decision);\n card.append(main, scoreBlock);\n list.appendChild(card);\n }\n }\n\n function renderConsoleView() {\n const controlButton = el(\"control-plane-view-button\");\n const healthButton = el(\"health-view-button\");\n const switchButton = el(\"switch-view-button\");\n const controlVisible = state.view === \"control\";\n const healthVisible = state.view === \"health\";\n el(\"control-plane-view\").hidden = !controlVisible;\n el(\"health-view\").hidden = !healthVisible;\n el(\"switch-view\").hidden = controlVisible || healthVisible;\n controlButton.classList.toggle(\"active\", controlVisible);\n healthButton.classList.toggle(\"active\", healthVisible);\n switchButton.classList.toggle(\"active\", !controlVisible && !healthVisible);\n }\n\n function render() {\n applyLanguage();\n renderConsoleView();\n renderControlPlane();\n renderM0Research();\n renderAdaptiveSelection();\n renderExecutionEvidence();\n renderRuntimeTargetLifecycle();\n renderResearchTasks();\n renderHealth();\n renderPlatforms();\n renderControls();\n renderSummary();\n renderPlanReadiness();\n renderAuth();\n renderAppVisibility();\n }\n\n async function refreshSession() {\n state.bootMessageKey = \"bootSession\";\n render();\n try {\n const session = await requestJson(\"/api/session\");\n state.auth = {\n available: true,\n allowed: Boolean(session.allowed),\n admin: Boolean(session.admin),\n login: session.login || null,\n };\n } catch {\n state.auth = { available: false, allowed: false, admin: false, login: null };\n }\n if (state.auth.allowed) {\n await refreshControlPlane();\n await refreshOwnerDecisions();\n await refreshReconciliationRecovery();\n await refreshM0Research();\n await refreshAdaptiveSelection();\n await refreshExecutionEvidence();\n await refreshRuntimeTargetLifecycle();\n await refreshResearchTasks();\n await refreshHealth();\n await refreshConfig();\n } else {\n state.bootMessageKey = \"bootPublic\";\n state.appReady = true;\n render();\n }\n }\n\n async function refreshHealth() {\n if (!state.auth.allowed) {\n renderHealth();\n return;\n }\n try {\n state.health.payload = normalizeHealthPayload(await requestJson(\"/api/strategy-health\"));\n } catch {\n state.health.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { strategy_count: 0, healthy: 0, watch: 0, review: 0, critical: 0 },\n strategies: [],\n errors: [\"health_request_failed\"],\n };\n }\n renderHealth();\n }\n\n async function refreshControlPlane() {\n if (!state.auth.allowed) {\n renderControlPlane();\n return;\n }\n try {\n state.controlPlane.payload = normalizeControlPlanePayload(await requestJson(\"/api/control-plane\"));\n } catch {\n state.controlPlane.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { candidate_count: 0, deferred: 0, parked: 0, owner_decision_required: 0 },\n attention: { status: \"unavailable\", reason_codes: [\"control_plane_request_failed\"] },\n candidates: [],\n policy: { p4_p5_automation: \"not_configured\", p6_owner_decision_required: true },\n errors: [\"control_plane_request_failed\"],\n };\n }\n renderControlPlane();\n }\n\n async function refreshOwnerDecisions() {\n if (!state.auth.allowed) {\n state.ownerDecisions = { data_status: \"unavailable\", candidates: [], errors: [] };\n renderControlPlane();\n return;\n }\n try {\n state.ownerDecisions = normalizeOwnerDecisionQueue(await requestJson(\"/api/owner-decisions\"));\n } catch {\n state.ownerDecisions = {\n data_status: \"unavailable\",\n candidates: [],\n errors: [\"owner_decision_queue_request_failed\"],\n };\n }\n renderControlPlane();\n }\n\n async function refreshReconciliationRecovery() {\n if (!state.auth.allowed) {\n renderReconciliationRecovery();\n return;\n }\n try {\n state.reconciliationRecovery.payload = normalizeReconciliationRecoveryPayload(\n await requestJson(\"/api/reconciliation-recovery\"),\n );\n } catch {\n state.reconciliationRecovery.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { recovery_count: 0, awaiting_human_confirmation: 0, blocked: 0, confirmed: 0 },\n recoveries: [],\n policy: { human_confirmation_required: true, current_evidence_required: true, no_order: true, execution_authority_granted: false },\n errors: [\"reconciliation_recovery_request_failed\"],\n };\n }\n renderReconciliationRecovery();\n }\n\n async function refreshM0Research() {\n if (!state.auth.allowed) {\n renderM0Research();\n return;\n }\n try {\n state.m0Research.payload = normalizeM0ResearchPayload(await requestJson(\"/api/m0-research\"));\n } catch {\n state.m0Research.payload = {\n data_status: \"unavailable\",\n viewed_at: null,\n summary: {\n subject_count: 0,\n observation_count: 0,\n fresh_observation_count: 0,\n stale_observation_count: 0,\n unknown_observation_count: 0,\n horizon_conflict_count: 0,\n historical_stale_horizon_drift_count: 0,\n },\n subjects: [],\n policy: { authority: \"research_only\", no_order: true, permitted_next_step: \"research_validation_only\" },\n errors: [\"m0_research_request_failed\"],\n };\n }\n renderM0Research();\n }\n\n async function refreshAdaptiveSelection() {\n if (!state.auth.allowed) {\n renderAdaptiveSelection();\n return;\n }\n try {\n state.adaptiveSelection.payload = normalizeAdaptiveSelectionPayload(await requestJson(\"/api/adaptive-selection\"));\n } catch {\n state.adaptiveSelection.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { source_count: 0, decision_count: 0, candidate_count: 0, recommended_count: 0, rejected_candidate_count: 0 },\n selections: [],\n policy: { authority: \"shadow_only\", no_order: true, execution_authority_granted: false },\n errors: [\"adaptive_selection_request_failed\"],\n };\n }\n renderAdaptiveSelection();\n }\n\n async function recordOwnerDecision(button) {\n if (!state.auth.admin) return;\n const candidateId = String(button.dataset.candidateId || \"\");\n const candidateEvidenceSha256 = String(button.dataset.candidateEvidenceSha256 || \"\");\n const decision = String(button.dataset.ownerDecision || \"\");\n if (!candidateId || !candidateEvidenceSha256 || !decision) return;\n if (!window.confirm(t(\"ownerDecisionConfirm\"))) return;\n state.ownerDecisions.submittingCandidateId = candidateId;\n renderControlPlane();\n try {\n const response = await fetch(\"/api/owner-decisions\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n candidate_id: candidateId,\n decision,\n candidate_evidence_sha256: candidateEvidenceSha256,\n }),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"ownerDecisionFailed\"));\n showToast(t(\"ownerDecisionSuccess\"), { duration: 7000 });\n await refreshControlPlane();\n await refreshOwnerDecisions();\n } catch (error) {\n showToast(`${t(\"ownerDecisionFailed\")}: ${error.message}`, { duration: 12000 });\n } finally {\n delete state.ownerDecisions.submittingCandidateId;\n renderControlPlane();\n }\n }\n\n async function recordReconciliationRecoveryConfirmation(button) {\n if (!state.auth.admin) return;\n const recoveryId = String(button.dataset.recoveryId || \"\");\n const candidateSha256 = String(button.dataset.candidateSha256 || \"\");\n const dualReviewBindingSha256 = String(button.dataset.dualReviewBindingSha256 || \"\");\n if (!recoveryId || !candidateSha256 || !dualReviewBindingSha256) return;\n if (!window.confirm(t(\"reconciliationRecoveryConfirmPrompt\"))) return;\n state.reconciliationRecovery.submittingRecoveryId = recoveryId;\n renderReconciliationRecovery();\n try {\n const response = await fetch(\"/api/reconciliation-recovery-confirmations\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n recovery_id: recoveryId,\n candidate_sha256: candidateSha256,\n dual_review_binding_sha256: dualReviewBindingSha256,\n }),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"reconciliationRecoveryFailed\"));\n showToast(t(\"reconciliationRecoverySuccess\"), { duration: 9000 });\n await refreshReconciliationRecovery();\n } catch (error) {\n showToast(`${t(\"reconciliationRecoveryFailed\")}: ${error.message}`, { duration: 12000 });\n } finally {\n state.reconciliationRecovery.submittingRecoveryId = null;\n renderReconciliationRecovery();\n }\n }\n\n async function refreshExecutionEvidence() {\n if (!state.auth.allowed) {\n renderExecutionEvidence();\n return;\n }\n try {\n state.executionEvidence.payload = normalizeExecutionEvidencePayload(await requestJson(\"/api/execution-evidence\"));\n } catch {\n state.executionEvidence.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { deployment_count: 0, autonomous_shadow: 0, autonomous_paper: 0, owner_canary_decision: 0, parked: 0 },\n deployments: [],\n policy: { execution_evidence_read_only: true, p6_owner_decision_required: true, limited_live_canary_active: false },\n errors: [\"execution_evidence_request_failed\"],\n };\n }\n renderExecutionEvidence();\n }\n\n async function refreshRuntimeTargetLifecycle() {\n if (!state.auth.allowed) {\n renderRuntimeTargetLifecycle();\n return;\n }\n try {\n state.runtimeTargetLifecycle.payload = normalizeRuntimeTargetLifecyclePayload(\n await requestJson(\"/api/runtime-target-lifecycle\"),\n );\n } catch {\n state.runtimeTargetLifecycle.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { target_count: 0, enabled: 0, disabled: 0, attention: 0 },\n targets: [],\n policy: { lifecycle_status_read_only: true, no_order: true },\n errors: [\"runtime_target_lifecycle_request_failed\"],\n };\n }\n renderRuntimeTargetLifecycle();\n }\n\n async function refreshResearchTasks() {\n if (!state.auth.allowed) {\n renderResearchTasks();\n return;\n }\n try {\n state.researchTasks.payload = normalizeResearchTaskPayload(await requestJson(\"/api/research-tasks\"));\n } catch {\n state.researchTasks.payload = {\n data_status: \"unavailable\",\n computed_at: null,\n summary: { task_count: 0 },\n tasks: [],\n policy: { research_only: true, no_order: true, size_zero_required: true, p4_p5_p6_authorized: false },\n errors: [\"research_task_request_failed\"],\n };\n }\n renderResearchTasks();\n }\n\n async function refreshStrategyProfiles() {\n state.bootMessageKey = \"bootStrategy\";\n render();\n try {\n const payload = await requestJson(\"/api/strategy-profiles\");\n applyStrategyProfiles(payload.strategyProfiles || []);\n if (payload.platformMeta) platformMeta = payload.platformMeta;\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n render();\n } catch {\n applyStrategyProfiles(defaultStrategyProfiles);\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n }\n }\n\n async function refreshConfig() {\n if (!state.auth.available || !state.auth.allowed) return;\n state.configSource = \"loading\";\n state.bootMessageKey = \"bootConfig\";\n render();\n try {\n const payload = await requestJson(\"/api/config\");\n if (payload.accountOptions) {\n applyStrategyProfiles(payload.strategyProfiles || defaultStrategyProfiles);\n state.accountOptions = normalizeAccountOptions(payload.accountOptions);\n if (payload.platformMeta) platformMeta = payload.platformMeta;\n state.repositories = normalizePlatformRepositories(payload.platformRepositories || {});\n state.currentStrategies = normalizeCurrentStrategies(payload.currentStrategies || {});\n state.configSource = \"private\";\n for (const platform of Object.keys(platformMeta)) {\n ensureAccountSelection(platform);\n syncStrategyForAccount(platform);\n }\n } else {\n state.configSource = \"default\";\n state.currentStrategies = {};\n }\n } catch (error) {\n state.configSource = \"default\";\n state.currentStrategies = {};\n if (isRequestTimeoutError(error)) {\n state.bootMessageKey = \"bootTimeout\";\n } else {\n state.bootMessageKey = \"bootPublic\";\n }\n } finally {\n state.appReady = true;\n render();\n }\n }\n\n function normalizeAccountOptions(raw) {\n const normalized = clone(defaultAccountOptions);\n for (const platform of Object.keys(platformMeta)) {\n if (!Array.isArray(raw[platform]) || !raw[platform].length) continue;\n normalized[platform] = raw[platform].map((item, index) => ({\n key: String(item.key || item.target_name || index),\n label: String(item.label || item.target_name || item.key || platform),\n target_name: String(item.target_name || item.key || \"\"),\n account_selector: item.account_selector ? String(item.account_selector) : \"\",\n deployment_selector: item.deployment_selector ? String(item.deployment_selector) : \"\",\n account_scope: item.account_scope ? String(item.account_scope) : \"\",\n service_name: item.service_name ? String(item.service_name) : \"\",\n cash_currency: item.cash_currency || item.market_currency || item.trading_currency\n ? String(item.cash_currency || item.market_currency || item.trading_currency).trim().toUpperCase()\n : \"\",\n supported_domains: normalizeSupportedDomains(platform, item),\n github_environment: item.github_environment ? String(item.github_environment) : \"\",\n variable_scope: item.variable_scope ? String(item.variable_scope) : \"\",\n plugin_mode: item.plugin_mode ? String(item.plugin_mode) : \"\",\n option_overlay_mode: item.option_overlay_mode ? normalizeOptionOverlayMode(item.option_overlay_mode) : \"\",\n cash_only_execution_mode: item.cash_only_execution_mode\n ? normalizeCashOnlyExecutionMode(item.cash_only_execution_mode)\n : \"\",\n dca_mode: item.dca_mode ? normalizeDcaMode(item.dca_mode) : \"\",\n dca_base_investment_usd: cleanDisplayPositiveNumber(item.dca_base_investment_usd),\n }));\n }\n return normalized;\n }\n\n function normalizeSupportedDomains(platform, item) {\n const raw = Array.isArray(item?.supported_domains)\n ? item.supported_domains\n : String(item?.supported_domains || \"\").split(/[\\s,;]+/);\n const cleaned = raw.map(cleanStrategyDomain).filter(Boolean);\n if (cleaned.length) return [...new Set(cleaned)];\n return inferSupportedDomains(platform, item || {});\n }\n\n function normalizeCurrentStrategies(raw) {\n const normalized = {};\n for (const platform of Object.keys(platformMeta)) {\n if (!raw[platform] || typeof raw[platform] !== \"object\" || Array.isArray(raw[platform])) continue;\n normalized[platform] = {};\n for (const [key, entry] of Object.entries(raw[platform])) {\n const profile = cleanStrategyProfile(entry?.strategy_profile);\n const minReservedCashUsd = cleanDisplayNumber(entry?.min_reserved_cash_usd ?? entry?.reserved_cash_floor_usd);\n const reservedCashRatio = cleanDisplayRatio(entry?.reserved_cash_ratio);\n const incomeLayerEnabled = cleanOptionalBoolean(entry?.income_layer_enabled);\n const incomeLayerStartUsd = cleanDisplayNumber(entry?.income_layer_start_usd);\n const incomeLayerMaxRatio = cleanDisplayRatio(entry?.income_layer_max_ratio);\n const optionOverlayEnabled = cleanOptionalBoolean(entry?.option_overlay_enabled);\n const cashOnlyExecution = cleanOptionalBoolean(entry?.cash_only_execution);\n const runtimeTargetEnabled = cleanOptionalBoolean(entry?.runtime_target_enabled);\n const dcaMode = entry?.dca_mode ? normalizeDcaMode(entry.dca_mode) : \"\";\n const dcaBaseInvestmentUsd = cleanDisplayPositiveNumber(entry?.dca_base_investment_usd);\n const executionMode = normalizeExecutionMode(entry?.execution_mode, entry?.dry_run_only);\n if (\n !profile &&\n !minReservedCashUsd &&\n !reservedCashRatio &&\n incomeLayerEnabled === null &&\n !incomeLayerStartUsd &&\n !incomeLayerMaxRatio &&\n optionOverlayEnabled === null &&\n cashOnlyExecution === null &&\n runtimeTargetEnabled === null &&\n !dcaMode &&\n !dcaBaseInvestmentUsd &&\n !executionMode\n ) continue;\n normalized[platform][String(key)] = {\n strategy_profile: profile,\n execution_mode: executionMode,\n dry_run_only: entry?.dry_run_only === true || entry?.dry_run_only === \"true\" || entry?.dry_run_only === \"1\",\n min_reserved_cash_usd: minReservedCashUsd,\n reserved_cash_ratio: reservedCashRatio,\n income_layer_enabled: incomeLayerEnabled,\n income_layer_start_usd: incomeLayerStartUsd,\n income_layer_max_ratio: incomeLayerMaxRatio,\n option_overlay_enabled: optionOverlayEnabled,\n cash_only_execution: cashOnlyExecution,\n runtime_target_enabled: runtimeTargetEnabled,\n dca_mode: dcaMode,\n dca_base_investment_usd: dcaBaseInvestmentUsd,\n source: entry?.source ? String(entry.source) : \"\",\n };\n }\n if (!Object.keys(normalized[platform]).length) delete normalized[platform];\n }\n return normalized;\n }\n\n function normalizePlatformRepositories(raw) {\n const normalized = clone(defaultRepositories);\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return normalized;\n for (const platform of Object.keys(platformMeta)) {\n const repository = String(raw[platform] || \"\").trim();\n if (/^[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/.test(repository)) {\n normalized[platform] = repository;\n }\n }\n return normalized;\n }\n\n async function dispatchSwitch() {\n if (!state.auth.allowed) return;\n showToast(t(\"dispatching\"), { duration: 0 });\n try {\n const response = await fetch(\"/api/switch\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(buildInputs()),\n });\n const payload = await response.json();\n if (!response.ok || !payload.ok) throw new Error(payload.error || t(\"dispatchFailed\"));\n showToast(t(\"dispatched\"), { duration: 4000 });\n if (payload.actions_url) window.open(payload.actions_url, \"_blank\", \"noopener,noreferrer\");\n await refreshConfig();\n } catch (error) {\n showToast(`${t(\"dispatchFailed\")}: ${error.message}`, { duration: 12000 });\n }\n }\n\n async function handleLogout() {\n await fetch(\"/api/logout\", { method: \"POST\" });\n window.location.reload();\n }\n\n function summaryText() {\n const inputs = buildInputs();\n return summaryRows(inputs).map(([label, value]) => `${label}: ${value}`).join(\"\\\n\");\n }\n\n document.querySelectorAll(\"[data-view]\").forEach((button) => button.addEventListener(\"click\", () => {\n state.view = [\"control\", \"health\", \"switch\"].includes(button.dataset.view) ? button.dataset.view : \"control\";\n renderConsoleView();\n if (state.view === \"control\") {\n refreshControlPlane();\n refreshOwnerDecisions();\n refreshReconciliationRecovery();\n refreshM0Research();\n refreshAdaptiveSelection();\n refreshExecutionEvidence();\n refreshRuntimeTargetLifecycle();\n refreshResearchTasks();\n }\n if (state.view === \"health\") refreshHealth();\n }));\n\n document.querySelectorAll(\"[data-health-filter]\").forEach((button) => button.addEventListener(\"click\", () => {\n document.querySelectorAll(\"[data-health-filter]\").forEach((node) => node.classList.remove(\"active\"));\n button.classList.add(\"active\");\n state.health.filter = button.dataset.healthFilter;\n renderHealth();\n }));\n\n el(\"control-plane-list\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-owner-decision]\");\n if (!button || button.disabled) return;\n recordOwnerDecision(button);\n });\n\n el(\"reconciliation-recovery-list\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-reconciliation-recovery-confirm]\");\n if (!button || button.disabled) return;\n recordReconciliationRecoveryConfirmation(button);\n });\n\n el(\"platform-strip\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-platform]\");\n if (!button) return;\n state.selected = button.dataset.platform;\n state.forms[state.selected].strategyTouched = false;\n render();\n });\n\n el(\"account-select\").addEventListener(\"change\", () => {\n state.forms[state.selected].accountKey = el(\"account-select\").value;\n state.forms[state.selected].runtimeTargetTouched = false;\n state.forms[state.selected].reservedCashTouched = false;\n state.forms[state.selected].incomeLayerTouched = false;\n state.forms[state.selected].optionOverlayTouched = false;\n state.forms[state.selected].cashOnlyExecutionTouched = false;\n state.forms[state.selected].dcaTouched = false;\n state.forms[state.selected].strategyTouched = false;\n syncStrategyForAccount(state.selected);\n render();\n });\n\n el(\"strategy-select\").addEventListener(\"change\", () => {\n state.forms[state.selected].strategy = el(\"strategy-select\").value;\n state.forms[state.selected].strategyTouched = true;\n state.forms[state.selected].incomeLayerTouched = false;\n state.forms[state.selected].optionOverlayTouched = false;\n state.forms[state.selected].dcaTouched = false;\n syncIncomeLayerForAccount(state.selected);\n syncOptionOverlayForAccount(state.selected);\n syncDcaForAccount(state.selected);\n render();\n });\n\n el(\"mode-control\").addEventListener(\"click\", (event) => {\n const button = event.target.closest(\"[data-mode]\");\n if (!button || button.disabled) return;\n if (!supportedExecutionModesForPlatform(state.selected).includes(button.dataset.mode)) return;\n state.forms[state.selected].executionMode = button.dataset.mode;\n render();\n });\n\n el(\"plugin-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.pluginMode = normalizePluginMode(el(\"plugin-mode-select\").value);\n render();\n });\n\n el(\"runtime-target-enabled-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.runtimeTargetMode = normalizeRuntimeTargetMode(el(\"runtime-target-enabled-select\").value);\n form.runtimeTargetTouched = form.runtimeTargetMode !== \"current\";\n render();\n });\n\n el(\"income-layer-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerMode = normalizeIncomeLayerMode(el(\"income-layer-mode-select\").value);\n form.incomeLayerTouched = form.incomeLayerMode !== \"current\";\n if (form.incomeLayerMode === \"current\") {\n form.incomeLayerTouched = false;\n syncIncomeLayerForAccount(state.selected);\n }\n render();\n });\n\n el(\"income-layer-start-usd-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerTouched = true;\n form.incomeLayerMode = \"enabled\";\n form.incomeLayerStartUsd = el(\"income-layer-start-usd-input\").value.trim();\n render();\n });\n\n el(\"income-layer-max-ratio-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.incomeLayerTouched = true;\n form.incomeLayerMode = \"enabled\";\n form.incomeLayerMaxRatio = el(\"income-layer-max-ratio-input\").value.trim();\n render();\n });\n\n el(\"option-overlay-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.optionOverlayMode = normalizeOptionOverlayMode(el(\"option-overlay-mode-select\").value);\n form.optionOverlayTouched = form.optionOverlayMode !== \"current\";\n render();\n });\n\n el(\"cash-only-execution-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.cashOnlyExecutionMode = normalizeCashOnlyExecutionMode(el(\"cash-only-execution-mode-select\").value);\n form.cashOnlyExecutionTouched = form.cashOnlyExecutionMode !== \"current\";\n if (allowMarginExplicitlySelected(form)) reconcileExecutionCashPolicy(form, \"margin\");\n else restoreReserveAfterMarginDisabled(form);\n render();\n });\n\n el(\"dca-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.dcaTouched = true;\n form.dcaMode = normalizeDcaMode(el(\"dca-mode-select\").value);\n render();\n });\n\n el(\"dca-base-investment-usd-input\").addEventListener(\"input\", () => {\n const form = state.forms[state.selected];\n form.dcaTouched = true;\n form.dcaBaseInvestmentUsd = el(\"dca-base-investment-usd-input\").value.trim();\n render();\n });\n\n el(\"reserve-policy-mode-select\").addEventListener(\"change\", () => {\n const form = state.forms[state.selected];\n form.reservePolicyMode = normalizeReservePolicyMode(el(\"reserve-policy-mode-select\").value);\n form.reservedCashTouched = form.reservePolicyMode !== \"current\";\n if (form.reservePolicyMode === \"current\") syncReservePolicyForAccount(state.selected);\n reconcileExecutionCashPolicy(form, \"reserve\");\n render();\n });\n\n el(\"min-reserved-cash-input\").addEventListener(\"input\", () => {\n state.forms[state.selected].reservedCashTouched = true;\n state.forms[state.selected].minReservedCashUsd = el(\"min-reserved-cash-input\").value.trim();\n render();\n });\n\n el(\"reserved-cash-ratio-input\").addEventListener(\"input\", () => {\n state.forms[state.selected].reservedCashTouched = true;\n state.forms[state.selected].reservedCashRatio = el(\"reserved-cash-ratio-input\").value.trim();\n render();\n });\n\n el(\"copy-button\").addEventListener(\"click\", async () => {\n try {\n await navigator.clipboard.writeText(summaryText());\n showToast(t(\"copied\"), { duration: 3000 });\n } catch {\n showToast(summaryText(), { duration: 0 });\n }\n });\n\n el(\"dispatch-button\").addEventListener(\"click\", dispatchSwitch);\n el(\"logout-button\").addEventListener(\"click\", handleLogout);\n el(\"lang-button\").addEventListener(\"click\", () => {\n state.lang = state.lang === \"zh\" ? \"en\" : \"zh\";\n localStorage.setItem(\"qsl-switch-lang\", state.lang);\n render();\n });\n\n applyStrategyProfiles(defaultStrategyProfiles);\n for (const platform of Object.keys(platformMeta)) syncStrategyForAccount(platform);\n render();\n boot();\n\n async function boot() {\n try {\n await refreshStrategyProfiles();\n await refreshSession();\n } catch {\n state.auth = { available: false, allowed: false, admin: false, login: null };\n state.configSource = \"default\";\n state.currentStrategies = {};\n state.bootMessageKey = \"bootTimeout\";\n state.appReady = true;\n render();\n }\n }\n"; diff --git a/web/strategy-switch-console/index.html b/web/strategy-switch-console/index.html index d20009c..0bb62dc 100644 --- a/web/strategy-switch-console/index.html +++ b/web/strategy-switch-console/index.html @@ -15,7 +15,7 @@已验证证据、运行许可和所有者决策。
+策略与平台的日常管理