diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index 132d898f..62b48d95 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -2648,3 +2648,35 @@ - 相关记录:BUG-123、BUG-139 - 复发自:无 - 修复版本:本次后台 OTP 密码状态候选提交 + +## BUG-156 | self-hosted 报告创建被未实现的数据库过滤器直接打断 + +- 状态:resolved(local candidate,待 staging gate/deployment) +- 首次发现:2026-08-10 +- 最近更新:2026-08-10 +- 影响面:self-hosted staging 的 `POST /api/reports`;报告生成主链、用户归属、每日限额和 production 未放宽或改动。 +- 用户现象:合法的个人报告创建请求返回 `{"error":"报告生成暂时不可用","code":"report_generation_failed"}`。 +- 触发条件:已登录用户调用报告创建接口,route 在进入报告创建核心逻辑前先回收超过 15 分钟的 `generating` 记录。 +- 根因:回收查询使用 Supabase 风格的 `.lt("updated_at", staleBefore)`,但 self-hosted `LocalPostgresQueryBuilder` 只实现了现有的 `.lte()`;运行时在构建查询时抛出 `TypeError`,被 route 的稳定错误边界统一映射为 `report_generation_failed`。 +- 修复:复用本地 PostgreSQL 查询构建器已有的 `.lte()`,把边界定义为“更新时间小于或等于 staleBefore”;不新增过滤器 API。 +- 验证:个人报告 API 与入口聚焦测试 54/54 通过,静态回归锁定 route 使用 `.lte()`;聚焦 ESLint 和 `git diff --check` 通过。 +- 防复发:self-hosted route 不得调用 `LocalPostgresQueryBuilder` 未实现的 Supabase 操作符;报告回收合同继续锁定 `.lte()`,数据库集成层已有 `lte` 查询覆盖。 +- 相关记录:BUG-154 +- 复发自:BUG-154(staging 发布前遗漏 self-hosted 适配能力核对) +- 修复版本:本次报告接口修复提交 + +## BUG-157 | 侧栏“我的报告”按钮缺少 flex 布局导致图标与文字分离 + +- 状态:resolved(local candidate,待 staging gate/deployment) +- 首次发现:2026-08-10 +- 最近更新:2026-08-10 +- 影响面:展开状态的全局侧栏“我的报告”入口;导航行为、折叠态 tooltip 与 accessibility 语义不变。 +- 用户现象:报告图标贴在按钮左上方,文字单独位于中间,整体未与上方“新对话”按钮对齐。 +- 触发条件:侧栏展开并渲染 `.report-nav-button` 的图标与文字。 +- 根因:样式设置了 `justify-content` 和 `gap`,但没有启用 flex formatting context,因此这些对齐属性不生效。 +- 修复:直接复用相邻“新对话”按钮的原生 CSS 布局,补齐 `display:flex`、水平/垂直居中和一致的水平内边距。 +- 验证:个人报告入口回归锁定 `.report-nav-button` 的 flex、垂直居中和水平居中;聚焦测试、ESLint 与截图检查通过。 +- 防复发:带图标和文字的侧栏按钮必须在同一 flex formatting context 中对齐;入口合同测试持续锁定关键布局属性。 +- 相关记录:BUG-154 +- 复发自:无 +- 修复版本:本次侧栏对齐修复提交 diff --git a/frontend/src/app/api/reports/route.ts b/frontend/src/app/api/reports/route.ts index 4acdeb78..98439cb2 100644 --- a/frontend/src/app/api/reports/route.ts +++ b/frontend/src/app/api/reports/route.ts @@ -128,7 +128,7 @@ export async function POST(request: Request) { }) .eq("user_id", userId) .eq("status", "generating") - .lt("updated_at", staleBefore); + .lte("updated_at", staleBefore); if (error) throw error; } diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 6d625950..7c099f36 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -315,7 +315,7 @@ button:disabled { cursor: default; opacity: .45; } .brand-mark, .auth-brand span { width: 32px; height: 32px; border-radius: 50%; background: var(--color-canvas) url("/jyotish-logo.png") center / contain no-repeat; box-shadow: 0 0 0 1px oklch(0 0 0 / .1); } .auth-story-brand img { width: 32px; height: 32px; border-radius: 50%; object-fit: contain; box-shadow: 0 0 0 1px oklch(0 0 0 / .1); } .new-chat { width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0 var(--space-3); border: 0; background: var(--sidebar-primary); color: var(--sidebar-primary-foreground); cursor: pointer; font-size: var(--type-body-sm); transition: background-color 120ms ease-out, transform 120ms ease-out; margin: var(--space-4) 0 var(--space-2); border-radius: var(--radius-md); font-weight: 500; } -.report-nav-button { width: 100%; min-height: 44px; justify-content: flex-start; gap: var(--space-2); margin-bottom: var(--space-4); border: 1px solid var(--sidebar-border); border-radius: var(--radius-md); color: var(--sidebar-foreground); } +.report-nav-button { width: 100%; min-height: 44px; display: flex; align-items: center; justify-content: center; gap: var(--space-2); padding: 0 var(--space-3); margin-bottom: var(--space-4); border: 1px solid var(--sidebar-border); border-radius: var(--radius-md); color: var(--sidebar-foreground); } .report-nav-button:hover { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); } .session-nav-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: var(--space-2); padding: 0 var(--space-3) var(--space-2); } .sidebar-label { min-height: 44px; display: flex; align-items: center; color: var(--sidebar-muted-foreground); font-size: var(--type-overline); font-weight: 500; letter-spacing: 1.5px; } diff --git a/frontend/tests/personal-report-api.test.ts b/frontend/tests/personal-report-api.test.ts index 40f19aef..f56c6f25 100644 --- a/frontend/tests/personal-report-api.test.ts +++ b/frontend/tests/personal-report-api.test.ts @@ -815,7 +815,7 @@ test("POST defers expensive work and GET lists metadata without report bodies", ); assert.doesNotMatch(listColumns, /report_document|calculation_hash|evidence_hash/); assert.match(createRoute, /STALE_GENERATION_MS = 15 \* 60 \* 1000/); - assert.match(createRoute, /\.lt\("updated_at", staleBefore\)/); + assert.match(createRoute, /\.lte\("updated_at", staleBefore\)/); }); test("stable error codes live in the dependency-free codes module", () => { diff --git a/frontend/tests/personal-report-entry.test.ts b/frontend/tests/personal-report-entry.test.ts index c6539ca9..8503cd32 100644 --- a/frontend/tests/personal-report-entry.test.ts +++ b/frontend/tests/personal-report-entry.test.ts @@ -189,6 +189,10 @@ test("entry is global in the sidebar and absent from the active session header", assert.match(sidebarSource, /onOpenReports/); assert.match(pageSource, /onOpenReports=\{\(\) => window\.location\.assign\("\/reports"\)\}/); assert.doesNotMatch(pageSource, /GeneratePersonalReportButton|reportEntryVisible|reportEvidenceState/); + assert.match( + globalStyles, + /\.report-nav-button \{[^}]*display: flex;[^}]*align-items: center;[^}]*justify-content: center;/, + ); }); test("reported/candidate birth times remain unusable for server-side report creation", () => {