Archived
E04 修到零被拒线,harness 补七刀:跨进程租约与广播桥、原子写、引用墙、对话区按序号排
活动侧(E04 2.1.0 → 2.1.9,I01 1.2.2): - 覆盖闭合成为机检:基座新增 distinct 读数,「计划盖住的客户数 = 候选行数」钉进 S3 收步 / S4 哨兵 / 放行 L2;人加的候选客户不再被整单无视 - 「关键人待识别」占位句式全链退役:查无联系人先发数据录入卡收人;契约新增 refersTo, 计划/回执的 contact_code 只许引用真实存在的联系人(安装期核引用目标) - 台账更新确认摆全落库面:客户档案/联系人档案两张落库预览(touched: 数据源, 正式现值 + 草稿叠加),可补改、可加行(自增主键由基座续号);姓名升必填 - 更新明细:update_key 位置键 + 「同一处只留一条」L2;依据写人话不带机器键; 标题旁不摆单号;判据与文案不夹字段名;行标识去掉单标识那一段 - 回执骨架 = 计划行数钉进 S3 收步与 S4 哨兵;重做分支不许删计划凑数 - 阶段刻度对齐文档 7 步;起单样例预填满 基座侧: - 两个后端连同一个库:task-claim 跨进程租约(迁移 0030)+ Postgres LISTEN/NOTIFY 广播桥;对话区按任务内记忆序号排,不按墙上时钟;并卡「刚说过话」按记忆序列判紧邻 - 多行 write 整批先验后写(dryRun),被拒 = 一行都没落 - 决策卡意见与本单草稿字面比对,归属以观察进轨迹(note-anchors) - 括号栈补「补闭括号」两形(少一个 } / 行对象没关直接跟下一行) - 件上加行:空着的自增主键直进续号,不从别的行借号;未落下的行点名回给前端 - 人答复落库那一刻就把 runtime 翻 running(task.wake),两块同帧变脸 - 试跑单可落真库(land_master,迁移 0031,默认开);数据域按活动分组; JSON 列不再是 [object Object];按钮键感 + 在办进度条 + 防抖 - 报价链 A1–A7 从系统卸载(只卸不删源码),种子只装 E04/I01 契约变更(api-report 已刷新):FieldDeclaration.distinct / refersTo, objectDeclaration.file,landMaster,unkeyed,assetFiles,ledgerByActivityRoute。 1700 测 / typecheck / lint / depcruise / api:check 绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EEJX8Z7CHejvwmDQyPQuBX
This commit is contained in:
@@ -25,6 +25,7 @@ import type { ViewEdits, ViewSave } from '@ui';
|
||||
import { GuideCard, WorkOrderReport } from './work-order-report.tsx';
|
||||
import { FileViewer } from './FileViewer.tsx';
|
||||
import { atFull, atMinute } from '@web-shared/at.ts';
|
||||
import { Btn } from '@web-shared/Btn.tsx';
|
||||
|
||||
|
||||
type Tab = 'open' | 'comms' | 'done' | 'wf' | 'log';
|
||||
@@ -189,16 +190,30 @@ export function ViewRegion({ detail, onReplyOrder, values, onField, locate }: Vi
|
||||
* 实测:人把三条料全改成 A 类,底下的去向还写着「去选型」。
|
||||
* 落完重取任务投影,整份件跟着一起变。**工单不办结,放行门的位置没动。**
|
||||
*/
|
||||
/**
|
||||
* **有行没落下就说出来**(2026-08-24 用户:「件上新增的那个客户,推到下一步就不见了」)。
|
||||
* 主键那几格空着就凑不出钥匙、存不住;从前基座悄悄跳过、接口照回 ok ——
|
||||
* 人看到「保存成功」,东西却不在。现在把没落下的行红字点名,他补上钥匙再存一次。
|
||||
*/
|
||||
const [saveWarn, setSaveWarn] = useState<string | undefined>(undefined);
|
||||
|
||||
const saveTable = useMutation({
|
||||
mutationFn: (save: ViewSave) =>
|
||||
call(saveTableRoute, {
|
||||
call<{ ok: boolean; rows: number; unkeyed?: Array<{ hint: string }> }>(saveTableRoute, {
|
||||
params: { taskId: detail.card.id },
|
||||
body: {
|
||||
tables: save.tables as Record<string, Array<Record<string, string>>>,
|
||||
drops: save.drops as Record<string, Array<Record<string, string>>>,
|
||||
},
|
||||
}),
|
||||
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['desk', 'task', detail.card.id] }),
|
||||
onSuccess: (out) => {
|
||||
setSaveWarn(
|
||||
out.unkeyed === undefined || out.unkeyed.length === 0
|
||||
? undefined
|
||||
: `有 ${out.unkeyed.length} 行没能存下 —— ${out.unkeyed.map((u) => u.hint).join(';')}`,
|
||||
);
|
||||
void queryClient.invalidateQueries({ queryKey: ['desk', 'task', detail.card.id] });
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
@@ -221,6 +236,14 @@ export function ViewRegion({ detail, onReplyOrder, values, onField, locate }: Vi
|
||||
onError: (error: unknown) => setFileError(error instanceof Error ? error.message : '这份资料打不开'),
|
||||
});
|
||||
|
||||
/*
|
||||
* 正在取的是哪一份 —— 先摘成两个普通值再用。
|
||||
* 直接在 JSX 里写 `openAssetFile.isPending && openAssetFile.variables.key === …` 不行:
|
||||
* 上面那一层三元已经按 `isPending` 分过岔,进到 else 分支里它被收窄成「不在办」,
|
||||
* `variables` 整个不存在(TS 判 never)—— 同一个判断在同一棵树里只能问一次。
|
||||
*/
|
||||
const fetchingFileKey = openAssetFile.isPending ? openAssetFile.variables.key : undefined;
|
||||
|
||||
const markRead = useMutation({
|
||||
mutationFn: (input: { commId: string; read: boolean }) =>
|
||||
call(markCommReadRoute, { params: { commId: input.commId }, body: { read: input.read } }),
|
||||
@@ -490,7 +513,14 @@ export function ViewRegion({ detail, onReplyOrder, values, onField, locate }: Vi
|
||||
vpClassName="wf-body"
|
||||
inset={-6}
|
||||
>
|
||||
{openFile !== undefined ? (
|
||||
{openAssetFile.isPending ? (
|
||||
/*
|
||||
* **取件的那一段要说出来** —— 资料名是件上的一个链接,点它没有按钮可以变样;
|
||||
* 那份文件又是服务端**现造**出来的字节(造 + 传,慢的有一两秒)。
|
||||
* 从前这一段屏上一动不动,人只好再点一次 —— 于是同一份资料取两遍。
|
||||
*/
|
||||
<EmptyState ico="file-text" tone="brand" title="正在取这一份资料…" desc="文件是台账那一行现造出来的,造好就摆在这儿。" />
|
||||
) : openFile !== undefined ? (
|
||||
<FileViewer doc={openFile} href={fileHref} />
|
||||
) : fileError !== undefined ? (
|
||||
/* 那一份取不回来:**如实说是哪一句**(服务端原话),别把人留在原地猜 */
|
||||
@@ -528,16 +558,15 @@ export function ViewRegion({ detail, onReplyOrder, values, onField, locate }: Vi
|
||||
不做「打开页签即已读」:那是替人宣布他看过了,而他可能只是路过。
|
||||
反向也留着:看了一半想留个记号,标回未读。
|
||||
*/}
|
||||
<button
|
||||
type="button"
|
||||
<Btn
|
||||
className={'jc-comm-mark' + (m.unread ? '' : ' done')}
|
||||
disabled={markRead.isPending}
|
||||
busy={markRead.isPending && markRead.variables.commId === m.id}
|
||||
aria-label={m.unread ? `把「${m.subject}」标为已读` : `把「${m.subject}」标回未读`}
|
||||
onClick={() => markRead.mutate({ commId: m.id, read: m.unread })}
|
||||
>
|
||||
<Ico name={m.unread ? 'check' : 'rotate-ccw'} s={11} />
|
||||
{m.unread ? '标为已读' : '标回未读'}
|
||||
</button>
|
||||
</Btn>
|
||||
</header>
|
||||
<div className="jc-comm-meta">
|
||||
<span className="jc-mono">{m.channel}</span>
|
||||
@@ -641,10 +670,10 @@ export function ViewRegion({ detail, onReplyOrder, values, onField, locate }: Vi
|
||||
{detail.assetFiles.every((f) => f.archived) ? null : <i>在办中 · 放行时存档</i>}
|
||||
</div>
|
||||
{detail.assetFiles.map((f) => (
|
||||
<button
|
||||
<Btn
|
||||
key={`${f.address}/${f.key}`}
|
||||
type="button"
|
||||
className={'jc-dv jc-dv-row s-ok m-file f-' + f.format}
|
||||
busy={fetchingFileKey === f.key}
|
||||
onClick={() => {
|
||||
setFileError(undefined);
|
||||
openAssetFile.mutate({ address: f.address, key: f.key });
|
||||
@@ -664,7 +693,7 @@ export function ViewRegion({ detail, onReplyOrder, values, onField, locate }: Vi
|
||||
<span className={'jc-dv-fmt f-' + f.format}>{f.format}</span>
|
||||
<Ico name="chevron-right" s={15} />
|
||||
</span>
|
||||
</button>
|
||||
</Btn>
|
||||
))}
|
||||
{doneList.length > 0 && <div className="jc-dvs-sub">定稿报告<em>{doneList.length} 份</em></div>}
|
||||
</>
|
||||
@@ -780,6 +809,17 @@ export function ViewRegion({ detail, onReplyOrder, values, onField, locate }: Vi
|
||||
* 判据换成直接问 `detail.viewHtml` 本身,`cur` 只在没有件、要退回卡壳
|
||||
* 呈现,或收边传值(`onField`)时才用得上。
|
||||
*/
|
||||
<>
|
||||
{/*
|
||||
**有行没落下,红字摆在件的上方** —— 而不是让人以为存好了。
|
||||
位置有意)在件之上:他刚按完保存,眼睛就在这一带。
|
||||
*/}
|
||||
{saveWarn !== undefined && (
|
||||
<div className="jc-savewarn" role="alert">
|
||||
<Ico name="info" s={14} />
|
||||
<span>{saveWarn}</span>
|
||||
</div>
|
||||
)}
|
||||
<ViewContainer
|
||||
html={detail.viewHtml}
|
||||
title={`${detail.card.activity} · 汇报报告`}
|
||||
@@ -806,6 +846,7 @@ export function ViewRegion({ detail, onReplyOrder, values, onField, locate }: Vi
|
||||
if (cur !== undefined) collectEdits(save.tables, cur, onField);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : cur !== undefined ? (
|
||||
/* 活动没推件的(还没到推件那一拍、或包里就没有模版)才退回工单报告:那时工作区总不能是白的 */
|
||||
<article className="jc-mst-doc">
|
||||
|
||||
Reference in New Issue
Block a user