Clarify birth time terminal next step
This commit is contained in:
@@ -412,6 +412,11 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.birth-time-confirmation-panel { display: grid; gap: var(--space-3); padding: var(--space-4); border-left: 2px solid var(--color-action); background: var(--color-action-soft); }
|
||||
.birth-time-confirmation-panel > b { color: var(--color-ink); font-size: var(--type-body-sm); }
|
||||
.birth-time-confirmation-panel > p { margin: 0; color: var(--color-ink-secondary); font-size: var(--type-caption); line-height: 1.6; word-break: keep-all; overflow-wrap: break-word; }
|
||||
.birth-time-next-step { box-sizing: border-box; width: 100%; display: grid; gap: var(--space-3); padding: var(--space-4); border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-action-soft); }
|
||||
.birth-time-next-step > b { color: var(--color-ink); }
|
||||
.birth-time-next-step > p { margin: 0; color: var(--color-ink-secondary); }
|
||||
.birth-time-next-step > .birth-time-guided-action { width: 100%; min-height: 48px; }
|
||||
.birth-time-next-step > .form-error { width: 100%; color: var(--color-danger); }
|
||||
.birth-time-retry-card { display: grid; justify-items: start; gap: var(--space-3); }
|
||||
.birth-time-retry-card p { margin: 0; color: var(--color-ink-secondary); font-size: var(--type-body-sm); line-height: 1.6; }
|
||||
.birth-time-guide-turn, .birth-time-evidence-draft-card, .birth-time-scoring-status, .birth-time-candidate-terminal { min-width: 0; max-width: 100%; display: grid; gap: var(--space-4); }
|
||||
|
||||
@@ -7,6 +7,7 @@ import { guidedTerminalPath } from "@/lib/birth-time-guided-terminal";
|
||||
type CandidateResultProps = {
|
||||
readonly journey: JourneyClientResponse;
|
||||
readonly controller: BirthTimeGuidedController;
|
||||
readonly error: string;
|
||||
};
|
||||
|
||||
const confidenceLabels = {
|
||||
@@ -15,7 +16,7 @@ const confidenceLabels = {
|
||||
high: "较高置信",
|
||||
} as const;
|
||||
|
||||
export function BirthTimeCandidateResult({ journey, controller }: CandidateResultProps) {
|
||||
export function BirthTimeCandidateResult({ journey, controller, error }: CandidateResultProps) {
|
||||
const result = journey.candidateResult;
|
||||
const action = journey.nextAction;
|
||||
const dynamic = journey.journeyProtocol === "dynamic-choice-v2";
|
||||
@@ -24,7 +25,8 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
|
||||
return (
|
||||
<div className="birth-time-candidate-result" aria-live="polite">
|
||||
<p className="birth-time-evidence-boundary">系统不会选择或应用未经证据支持的具体分钟,<span className="phrase-nowrap">当前排盘使用时间</span>保持不变。</p>
|
||||
{terminalPath && <TerminalAction controller={controller} path={terminalPath} />}
|
||||
{terminalPath && <TerminalAction controller={controller} error={error} path={terminalPath} />}
|
||||
{!terminalPath && error ? <p className="form-error" role="alert">{error}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -52,13 +54,13 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
|
||||
{action.kind === "present_low_result" && (
|
||||
<div className="birth-time-candidate-terminal" role="status">
|
||||
<p>{dynamic ? "目前没有足够的新信息继续稳定缩小范围,本次评估已结束并保存当前候选范围。" : "本轮校正已安全结束,只保留候选范围,当前排盘使用时间保持不变。"}</p>
|
||||
{terminalPath && <TerminalAction controller={controller} path={terminalPath} />}
|
||||
{terminalPath && <TerminalAction controller={controller} error={error} path={terminalPath} />}
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "present_medium_result" && winner && dynamic && (
|
||||
<div className="birth-time-candidate-terminal" role="status">
|
||||
<p>已形成较窄的候选范围,本次评估已结束;它不会自动改动<span className="phrase-nowrap">当前排盘使用时间</span>。</p>
|
||||
{terminalPath && <TerminalAction controller={controller} path={terminalPath} />}
|
||||
{terminalPath && <TerminalAction controller={controller} error={error} path={terminalPath} />}
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "present_medium_result" && winner && !dynamic && (
|
||||
@@ -72,7 +74,7 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
|
||||
{action.kind === "candidate_saved" && (
|
||||
<div className="birth-time-candidate-terminal" role="status">
|
||||
<p className="birth-time-success-note">候选时间范围已保存。<span className="phrase-nowrap">当前排盘使用时间</span>没有改变。</p>
|
||||
{terminalPath && <TerminalAction controller={controller} path={terminalPath} />}
|
||||
{terminalPath && <TerminalAction controller={controller} error={error} path={terminalPath} />}
|
||||
</div>
|
||||
)}
|
||||
{action.kind === "request_candidate_confirmation" && winner && (
|
||||
@@ -90,27 +92,32 @@ export function BirthTimeCandidateResult({ journey, controller }: CandidateResul
|
||||
<button className="button-primary birth-time-guided-action" disabled={controller.pending} type="button" onClick={controller.acknowledgeReady}>继续使用此排盘</button>
|
||||
</div>
|
||||
)}
|
||||
{!terminalPath && error ? <p className="form-error" role="alert">{error}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TerminalAction({ controller, path }: {
|
||||
function TerminalAction({ controller, error, path }: {
|
||||
readonly controller: BirthTimeGuidedController;
|
||||
readonly error: string;
|
||||
readonly path: NonNullable<ReturnType<typeof guidedTerminalPath>>;
|
||||
}) {
|
||||
if (path.kind === "complete_with_candidate") {
|
||||
return (
|
||||
<div className="birth-time-new-assessment">
|
||||
<div className="birth-time-next-step">
|
||||
<b>评估已完成,下一步</b>
|
||||
<p>点击后将使用 {path.time} 作为当前排盘时间并进入对话;原始填报和本次候选结果仍会保留。</p>
|
||||
<button className="button-primary birth-time-guided-action" disabled={controller.pending} onClick={() => controller.completeCandidate(path.time)} type="button">
|
||||
{controller.pending ? "保存中…" : "完成并继续对话"}
|
||||
{controller.pending ? `正在采用 ${path.time}…` : `采用 ${path.time} 并进入对话`}
|
||||
</button>
|
||||
<small>将以 {path.time} 作为当前工作排盘时间;候选状态和原始资料都会保留。</small>
|
||||
{error ? <p className="form-error" role="alert">{error}</p> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="birth-time-new-assessment">
|
||||
<button className="button-secondary birth-time-guided-action" disabled={controller.pending} onClick={controller.editBirthTimeDetails} type="button">开始新的评估</button>
|
||||
{error ? <p className="form-error" role="alert">{error}</p> : null}
|
||||
<small>会建立新的记录,当前结果仍会保留。</small>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -76,8 +76,14 @@ export function BirthTimeLegacyRectification(props: LegacyRectificationProps) {
|
||||
{action.kind === "score_pending" && <p className="birth-time-scoring-status" role="status">正在使用已确认的关键经历评分…</p>}
|
||||
{action.kind === "retry_scoring" && <button className="button-primary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.retryScoring} type="button">重新评分</button>}
|
||||
{action.kind === "paused" && <button className="button-primary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.resume} type="button">继续校正</button>}
|
||||
{showsCandidate && <BirthTimeCandidateResult controller={props.controller} journey={props.journey} />}
|
||||
{error && <p className="form-error" role="alert">{error}</p>}
|
||||
{showsCandidate ? (
|
||||
<BirthTimeCandidateResult
|
||||
controller={props.controller}
|
||||
error={error}
|
||||
journey={props.journey}
|
||||
/>
|
||||
) : null}
|
||||
{error && !showsCandidate ? <p className="form-error" role="alert">{error}</p> : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,8 +115,14 @@ export function BirthTimeRectification(props: BirthTimeRectificationProps) {
|
||||
)}
|
||||
{action.kind === "retry_scoring" && <button className="button-primary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.retryScoring} type="button">重新评分</button>}
|
||||
{action.kind === "paused" && <button className="button-primary birth-time-guided-action" disabled={props.controller.pending} onClick={props.controller.resume} type="button">继续本次评估</button>}
|
||||
{showsCandidate && <BirthTimeCandidateResult controller={props.controller} journey={props.journey} />}
|
||||
{error && action.kind !== "ask_dynamic_choice" && action.kind !== "clarify_unmatched_answer" && <p className="form-error" role="alert">{error}</p>}
|
||||
{showsCandidate ? (
|
||||
<BirthTimeCandidateResult
|
||||
controller={props.controller}
|
||||
error={error}
|
||||
journey={props.journey}
|
||||
/>
|
||||
) : null}
|
||||
{error && !showsCandidate ? <p className="form-error" role="alert">{error}</p> : null}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -119,6 +119,23 @@ test("ready completion is explicit and terminal low has no finish mutation", ()
|
||||
assert.doesNotMatch(candidateSource, /controller\.finish/);
|
||||
});
|
||||
|
||||
test("terminal candidate owns one explicit next step and its completion error", () => {
|
||||
const candidateResultSource = readFileSync(new URL("../src/components/birth-time-candidate-result.tsx", import.meta.url), "utf8");
|
||||
const rectificationSource = readFileSync(new URL("../src/components/birth-time-rectification.tsx", import.meta.url), "utf8");
|
||||
const legacyRectificationSource = readFileSync(new URL("../src/components/birth-time-legacy-rectification.tsx", import.meta.url), "utf8");
|
||||
const globalCssSource = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||||
|
||||
assert.match(candidateResultSource, /评估已完成,下一步/);
|
||||
assert.match(candidateResultSource, /采用 \$\{path\.time\} 并进入对话/);
|
||||
assert.match(candidateResultSource, /正在采用 \$\{path\.time\}…/);
|
||||
assert.match(candidateResultSource, /birth-time-next-step/);
|
||||
assert.match(rectificationSource, /error=\{error\}/);
|
||||
assert.match(rectificationSource, /error && !showsCandidate/);
|
||||
assert.match(legacyRectificationSource, /error=\{error\}/);
|
||||
assert.match(legacyRectificationSource, /error && !showsCandidate/);
|
||||
assert.match(globalCssSource, /\.birth-time-next-step/);
|
||||
});
|
||||
|
||||
test("completed rectification transcript does not repeat the birth place turn", () => {
|
||||
const pageSource = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user