fix(web): unblock staging next build after declared-window types
BUG-353 fallbacks used undefined window columns and un-narrowed clock/focus values, so Docker next build failed typecheck and could not publish the already-pushed staging head. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5371,4 +5371,20 @@
|
||||
- 复发自:BUG-305(thinking 与正文抢 `max_tokens`)、BUG-346(8192 预算不变就重新打开 thinking)
|
||||
- 修复版本:`0ca7da99`
|
||||
|
||||
## BUG-355 | staging web 镜像 next build 被 declared window 类型检查挡住
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-08-22
|
||||
- 最近更新:2026-08-22
|
||||
- 影响面:`deploy/railway-web.Dockerfile` 的 `RUN npm run build`、`GET/PATCH /api/account`、declared window 类型收口
|
||||
- 用户现象:xiaoxin 不可用时本机按 publish 合同构建 web 镜像,`next build` 在 TypeScript 阶段失败,staging 无法换上已推送的 `90db4098`。
|
||||
- 触发条件:当前 `staging` 头包含 BUG-353 的列缺失回退,且走 Docker `next build`。push 上的 validate 跳过 `npm run build`,因此测试绿、镜像红。
|
||||
- 根因:列缺失回退把 `declared_window_start/end` 写成 `undefined` 或省略,对不上 supabase 行类型。`isBirthClockText` 不是类型谓词,收窄后仍是 `string | null | undefined`。`Boolean(focus) && focus.intent` 不能收窄 `focus`。
|
||||
- 修复:缺失列回退写成 `null`。`isBirthClockText` 改为 `value is string`。焦点判断改为 `focus && (...)` 后再使用 `focus.intent`。
|
||||
- 验证:`npx tsc --noEmit` 通过。`tsx --test tests/declared-birth-window.test.ts tests/profile-persistence.test.ts` 14/14。
|
||||
- 防复发:账户列缺失回退必须补齐 declared window 字段为 `null`,不得用 `undefined`。staging push 的类型回归仍只在 publish Docker 暴露,本机/xiaoxin 镜像构建失败不得改 SHA 蒙混上线。
|
||||
- 相关记录:BUG-309、BUG-353、BUG-354
|
||||
- 复发自:BUG-309(validate 跳过 `next build`,publish 才暴露类型错误)
|
||||
- 修复版本:本提交
|
||||
|
||||
|
||||
|
||||
@@ -62,8 +62,8 @@ export async function GET() {
|
||||
if (!withoutDeclaredWindow.error && withoutDeclaredWindow.data) {
|
||||
profile = {
|
||||
...withoutDeclaredWindow.data,
|
||||
declared_window_start: undefined,
|
||||
declared_window_end: undefined,
|
||||
declared_window_start: null,
|
||||
declared_window_end: null,
|
||||
};
|
||||
profileError = withoutDeclaredWindow.error;
|
||||
} else if (withoutDeclaredWindow.error && isMissingProfileColumn(withoutDeclaredWindow.error)) {
|
||||
@@ -74,6 +74,8 @@ export async function GET() {
|
||||
.single();
|
||||
profile = fallback.data ? {
|
||||
...fallback.data,
|
||||
declared_window_start: null,
|
||||
declared_window_end: null,
|
||||
birth_place_label: undefined,
|
||||
birth_place_type: undefined,
|
||||
birth_place_provider: undefined,
|
||||
@@ -83,7 +85,7 @@ export async function GET() {
|
||||
} : null;
|
||||
profileError = fallback.error;
|
||||
} else {
|
||||
profile = withoutDeclaredWindow.data;
|
||||
profile = null;
|
||||
profileError = withoutDeclaredWindow.error;
|
||||
}
|
||||
}
|
||||
@@ -199,8 +201,8 @@ export async function PATCH(request: Request) {
|
||||
if (!withoutDeclaredWindow.error && withoutDeclaredWindow.data) {
|
||||
currentProfile = {
|
||||
...withoutDeclaredWindow.data,
|
||||
declared_window_start: undefined,
|
||||
declared_window_end: undefined,
|
||||
declared_window_start: null,
|
||||
declared_window_end: null,
|
||||
};
|
||||
currentProfileError = withoutDeclaredWindow.error;
|
||||
} else if (withoutDeclaredWindow.error && isMissingProfileColumn(withoutDeclaredWindow.error)) {
|
||||
@@ -211,6 +213,8 @@ export async function PATCH(request: Request) {
|
||||
.maybeSingle();
|
||||
currentProfile = fallback.data ? {
|
||||
...fallback.data,
|
||||
declared_window_start: null,
|
||||
declared_window_end: null,
|
||||
latitude: undefined,
|
||||
longitude: undefined,
|
||||
timezone_offset: undefined,
|
||||
@@ -223,7 +227,7 @@ export async function PATCH(request: Request) {
|
||||
} : null;
|
||||
currentProfileError = fallback.error;
|
||||
} else {
|
||||
currentProfile = withoutDeclaredWindow.data;
|
||||
currentProfile = null;
|
||||
currentProfileError = withoutDeclaredWindow.error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ export function isDeclaredBirthPeriod(value: string | null | undefined): value i
|
||||
|| value === "late_night";
|
||||
}
|
||||
|
||||
export function isBirthClockText(value: string | null | undefined): boolean {
|
||||
export function isBirthClockText(value: string | null | undefined): value is string {
|
||||
return typeof value === "string" && CLOCK.test(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -450,8 +450,8 @@ export function buildMethodFollowupPlan(input: {
|
||||
|
||||
const sessionOutcome = input.sessionOutcome ?? "collect_evidence";
|
||||
const focus = input.activeFocus ?? null;
|
||||
const keepAcceptedFocus = Boolean(focus) && (
|
||||
focus.intent === "reverse_verify" || focus.intent === "out_of_sample_check"
|
||||
const keepAcceptedFocus = Boolean(
|
||||
focus && (focus.intent === "reverse_verify" || focus.intent === "out_of_sample_check"),
|
||||
);
|
||||
if (focus && (!input.accepted || keepAcceptedFocus)) {
|
||||
const existingChoice = parseAgentChoiceCopy(focus.expectedAnswerSchema ?? null);
|
||||
|
||||
Reference in New Issue
Block a user