BUG-1252: answer prose drops text-wrap: pretty. WebKit (every iPhone browser)
applies it to the whole paragraph, so lines came out evenly short with a blank
strip on the right and every streamed character re-broke the lines above.
BUG-1253: the condensed checklists and shared reading open with an
analysis-only note; PLAIN_SPEECH_RULE (one definition) tells the writer not to
quote layer names, signal counts, rules or house-counting, caps evidence in
brackets at two, and gives everyday words for card fields with no Chinese name
(no coined names like 合力星). Marriage layers become everyday wording; career,
wealth and health say not to announce layers.
Full suite 4966 / fail 24, identical to baseline d2c22ca2 (4962 / 24); build
keeps / Static; first-load gzip unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
72 lines
3.7 KiB
TypeScript
72 lines
3.7 KiB
TypeScript
// BUG-1252 / BUG-1253 (2026-10-06 real-device review). 1252: answer prose on
|
|
// iPhone left a blank strip on the right and jittered while streaming — the
|
|
// prose carried `text-wrap: pretty`, which WebKit applies to the whole
|
|
// paragraph. 1253: a marriage answer quoted the analysis checklist to the
|
|
// reader (layer names, house-counting, a strength rule) and coined 「合力星」.
|
|
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
CONSULTATION_CONDENSED_CHECKLISTS,
|
|
condensedConsultationChecklist,
|
|
sharedConsultationReading,
|
|
} from "../src/lib/consultation-condensed-checklist.ts";
|
|
import {
|
|
AFFLICTION_RANGE_RULE,
|
|
CHECKLIST_ANALYSIS_ONLY_NOTE,
|
|
NATAL_ANSWER_SHAPE_SUMMARY,
|
|
PLAIN_SPEECH_RULE,
|
|
followUpAnswerShapeInstruction,
|
|
natalAnswerShapeBody,
|
|
} from "../src/lib/consultation-thinking-plan.ts";
|
|
|
|
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
|
const voice = readFileSync(new URL("../src/mastra/product-voice.ts", import.meta.url), "utf8");
|
|
|
|
test("answer prose wraps plainly: no text-wrap: pretty on .message p / .message-markdown (BUG-1252)", () => {
|
|
const rule = /\.message p, \.message-markdown \{([^}]*)\}/.exec(css);
|
|
assert.ok(rule, "the shared answer prose rule exists");
|
|
assert.doesNotMatch(rule[1]!, /text-wrap/);
|
|
assert.doesNotMatch(rule[1]!, /auto-phrase/);
|
|
// No other rule puts it back on consultation answer prose. The first-run
|
|
// onboarding bubbles keep their own phrase-protected short copy (out of scope).
|
|
const offenders = [...css.matchAll(/([^{}\n]*\.(?:message-markdown|message-answer|consultation-report-analysis)[^{}]*)\{[^}]*text-wrap:\s*pretty/g)]
|
|
.map((match) => match[1]!.trim())
|
|
.filter((selector) => !selector.startsWith(".onboarding-message"));
|
|
assert.deepEqual(offenders, []);
|
|
});
|
|
|
|
test("the checklists say they are for analysis, and the shape says how to speak (BUG-1253)", () => {
|
|
assert.ok(sharedConsultationReading().startsWith(CHECKLIST_ANALYSIS_ONLY_NOTE));
|
|
for (const domain of Object.keys(CONSULTATION_CONDENSED_CHECKLISTS) as (keyof typeof CONSULTATION_CONDENSED_CHECKLISTS)[]) {
|
|
assert.ok(condensedConsultationChecklist(domain)!.startsWith(CHECKLIST_ANALYSIS_ONLY_NOTE), domain);
|
|
}
|
|
for (const text of [natalAnswerShapeBody(), followUpAnswerShapeInstruction()]) {
|
|
assert.ok(text.includes(PLAIN_SPEECH_RULE));
|
|
}
|
|
assert.match(voice, /\$\{PLAIN_SPEECH_RULE\}/);
|
|
assert.match(NATAL_ANSWER_SHAPE_SUMMARY, /checklists are for reasoning only/);
|
|
});
|
|
|
|
test("layer names are everyday words and never announced (BUG-1253)", () => {
|
|
const all = Object.values(CONSULTATION_CONDENSED_CHECKLISTS).flat().join("\n");
|
|
for (const label of ["心动接触", "关系成对", "社会法律落地", "分三层说", "压力窗", "恢复窗"]) {
|
|
assert.equal(all.includes(label), false, label);
|
|
}
|
|
const marriage = CONSULTATION_CONDENSED_CHECKLISTS.marriage!.join("\n");
|
|
for (const plain of ["遇到喜欢的人、谈恋爱", "定下来、长期在一起", "结婚、领证", "不报层名和编号"]) {
|
|
assert.ok(marriage.includes(plain), plain);
|
|
}
|
|
});
|
|
|
|
test("card fields without an everyday Chinese name have one, and the brackets stay short (BUG-1253)", () => {
|
|
for (const pair of ["功能凶星 → 对你这个上升来说偏添麻烦的星", "yogakaraka → 不提", "不自造译名", "DK → 代表伴侣的星", "落陷 → 力气弱"]) {
|
|
assert.ok(PLAIN_SPEECH_RULE.includes(pair), pair);
|
|
}
|
|
assert.match(PLAIN_SPEECH_RULE, /括号里的依据最多两条/);
|
|
assert.match(PLAIN_SPEECH_RULE, /「从某宫数起第几宫」/);
|
|
assert.equal(AFFLICTION_RANGE_RULE.includes("括号里写是哪几条"), false);
|
|
assert.match(AFFLICTION_RANGE_RULE, /括号里只写最能说明问题的一两条/);
|
|
});
|