Skip to content

docs(design): §09 still specifies 各阶段合同数漏斗 — the app stopped shipping a funnel in #48 and narrowed what it plots in #59 - #70

Merged
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-63-design-09-not-a-funnel
Sep 10, 2026
Merged

docs(design): §09 still specifies 各阶段合同数漏斗 — the app stopped shipping a funnel in #48 and narrowed what it plots in #59#70
zhuangjianguo merged 1 commit into
mainfrom
claude/issue-63-design-09-not-a-funnel

Conversation

@claude

@claude claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #63

DESIGN.md §09 still specified the Legal Workbench stage widget as 各阶段合同数漏斗 — a funnel over every lifecycle stage. Two merged cards moved the app off that in two independent ways, and the design authority never caught up. This is a documentation-catches-up card: no app file changes.

What §09 said on 9b7538a, verbatim

Re-derived on the branch base rather than inherited: §09 spans lines 293–309 on 9b7538a (heading ## 09 分析 at 293, ## 10 种子数据 at 310). The target string was present verbatim on line 302, at the end of the 法务工作台 bullet:

- **法务工作台** — 待受理数 · 审查中 · 审查超 30 天(固定阈值,写进砖的标题)· 谈判停滞 · 审批吞吐(本月 vs 上月,用平台 `compareTo`)· 各阶段合同数漏斗

PR #40 (296fab8) had already rewritten the rest of this bullet — 超 SLA → 审查超 30 天(固定阈值…), 平均周转 → 审批吞吐(…用平台 compareTo)— and appended the 各段时长 ruling paragraph. 漏斗 was the one stale item left in the bullet. This edit sits alongside that rewrite: the 各段时长 paragraph and every other row are byte-identical.

The change — two hunks, both inside §09

Hunk 1, line 302 — the bullet now names the mark and the coverage:

· 各阶段合同数(横向条形图,按合同数从多到少排;只画在办的六个阶段)

Hunk 2, new line 310 — a paragraph carrying the reasoning and, critically, the non-promise about stage order.

⚠️ Flagging hunk 2 explicitly so the seat can split it if it prefers. It is this widget's specification and nothing else, but it is an addition rather than a correction, so it is called out rather than slipped in. The case for keeping it: the card's items 2 and 3 cannot be discharged by hunk 1 alone. Item 2 (say what it plots, and on what criterion) does not fit in a list bullet; item 3 (⛔ do not re-promise an authored stage order) needs the negative recorded, or the next reader re-adds stageOrder — which is exactly the defect class PR #40 removed from this section by recording, not by deleting silently.

Every claim traced to src/dashboards/legal.dashboard.ts as merged

Claim in the new §09 text Reading in the merged widget
横向条形图 type: 'horizontal-bar' (L347)
「各阶段合同数」 is this widget id: 'stage_funnel' (L344); zh-CN bundle title 各阶段合同数 (src/translations/zh-CN/app.ts:202); en Pipeline by Stage
不是漏斗 type is not funnel; comment §「Why not a funnel (issue #48) — the mark was making a false claim」 (L278–302); description "not a conversion sequence" (L346)
漏斗断言逐级单调递减 "A funnel encodes MONOTONIC DECLINE: each stage narrower than the one before" (L280–281)
这本合同簿不递减,§10 分布 draft 10 · submitted 6 · in_review 12 · in_approval 8 · approved 4 · signing 6 · active 60 counted out of the running DB in the comment (L247–251); pinned by assertSpread in src/data/plan-contracts.ts; DESIGN.md §10 line 320 states the same spread
画成漏斗是在断言数据没说的事 "the mark ASSERTED something the numbers do not say — that contracts fall out at each stage" (L290–292)
收录判据 = 法务在这个阶段是否还有事要做 "The criterion that does discriminate … WHETHER LEGAL HAS WORK TO DO ON THE STAGE" (L224–225)
六个在办阶段 draft / submitted / in_review / in_approval / approved / signing 入图 filter: { status: { $in: ['draft','submitted','in_review','in_approval','approved','signing'] } } (L351)
activerejected / expired / terminated / cancelled 四种终态都不入图 absent from that $in list (L351); comment L214–233; description "Contracts already in force are not plotted." (L346)
生效合同簿的规模归管理层看板(生效合同额 · 90 天内到期) "§09 assigns the in-force sense to 管理层 and it is there — active_contract_value by currency, plus expiring_90_days" (L271–273); §09's own 管理层 bullet (line 303, unchanged)
排序按合同数从多到少 (sortBy: 'contract_count' / sortOrder: 'desc') options: { sortBy: 'contract_count', sortOrder: 'desc' } (L352–355); "it lowers to order: { contract_count: 'desc' } on the dataset query" (L327–328)
⛔ 不承诺可编排的阶段顺序 options carries no stageOrder (L352–355)
console 17.4.0 只有漏斗分支读 stageOrder "categoryOrder is consumed inside the chartType === 'funnel' branch and nowhere else" (L308–311)
条形图上静默丢弃 "this widget as a bar with stageOrder still authored came out Active · Approved · Draft · … i.e. alphabetically" (L311–313)
en 语言下连漏斗分支也因标签不匹配而丢 "Measured in a zh-CN console … the funnel lost stage order entirely … because the rows carry the API's English labels while the client's order map carries the bundle's Chinese ones and nothing matches" (L318–326)
objectstack-ai/objectstack#17344 reported upstream rather than patched (L316), per AGENTS.md 平台缺口

Gates — all four 0, on 8ecfb1c

Each redirected to its own file with $? read on the next statement; no pipe between the command and the exit code.

on 8ecfb1c — validate=0 lint=0 typecheck=0 lint:i18n-gate=0
✓ i18n gate
  LOCALES  : "en", "zh-CN" checked (required: en, zh-CN)
  REACH    : 12 surface classes walked — object=34 · field=273 · option=188 · section=35 ·
             view=37 · action=55 · app=2 · navigation=28 · dashboard=6 · widget=38 ·
             dataset=70 · page=2
  COVERAGE : 0 missing keys across 2 locale(s)

lint reports 21 warning(s), 5 suggestion(s) and validate 27 — all pre-existing and none of them reachable from this diff, which changes no metadata (see the disjointness measurement below).

⛔ None of the four gates reads DESIGN.md — measured three ways, not asserted

1. Grep the four gate logs. No gate log mentions DESIGN.md, or any .md file at all:

grep -ni "DESIGN\.md|\.md\b" gate-{validate,lint,typecheck,i18n}.log  →  exit 1, 0 matching lines

2. tsc --noEmit --listFiles — what typecheck actually opens.

tsc exit=0
total files compiled: 305
of those, .md files:  0
lines mentioning DESIGN: 0

3. Ablation — corrupt the file and watch nothing notice. The decisive one. With the fix committed first (so the restore leg points at HEAD), DESIGN.md was overwritten with two lines of garbage, the mutation proven on disk, and all four gates re-run:

proof the mutation reached disk:
  lines now: 2   (HEAD version had: 303)
  marker 'DELIBERATELY CORRUPTED' present: 1
  on-disk hash c17cd06b…  ≠  HEAD blob 4bf1529b…

ABLATION exits: validate=0 lint=0 typecheck=0 i18n-gate=0

A file that has been reduced to two lines of non-markdown passes every gate unchanged. The gates verify that the metadata parses; they say nothing whatsoever about this diff. The only checks on this change are the reading table above and human review.

Restore verified by state, not by an exit codegit hash-object DESIGN.md = 4bf1529b73a88b366b8707f9a6357216d55887b8 = git rev-parse HEAD:DESIGN.md, git diff HEAD empty, git status clean, corruption marker absent, 303 lines back.

4. (bonus) Diff/walk disjointness. git diff --name-only 9b7538a..HEAD = DESIGN.md, and its intersection with the gates' walk root (objectstack.config.ts + src/**, per tsconfig.json include) is 0 lines. Every byte the gates read is identical to 9b7538a.

Edit verified on disk by before/after counts — never by an editor's exit code

spelling before after
各阶段合同数漏斗 1 0
横向条形图 0 2
在办 0 2
sortBy 0 1
stageOrder 0 1
17344 0 1

漏斗 still matches 1 line — deliberately. That line is the new paragraph, which uses the word only to deny it (不是漏斗 / 漏斗断言逐级单调递减 / 画成漏斗是在断言数据没说的事 / 只有漏斗分支读 stageOrder). It is no longer the widget's name anywhere.

Both edit anchors were asserted to match exactly once before replacement, so an exact-match edit that silently matched nothing could not have reported success.

Scope

  • Touched: DESIGN.md §09 only. §09 now spans 293–311; the changed lines are 302 and 310–311, both inside it. §01–§04 (lines 24–205) are byte-identical — same sha256 before and after (9d1de298628d97616a705d6e4be904768a0c06b14591354e3b5939f8ed8abc67).
  • No other §09 row touched, no renumbering, no changeset (this repo has no changeset gate).
  • No browser run owed — the diff changes no runnable surface. No app file is touched, and the ablation above shows the running app cannot observe this file at all.

Acceptance notes — the grep for other descriptions of this widget

Grepped 漏斗 and funnel repo-wide and 各阶段 / stage_funnel / Pipeline by Stage across DESIGN.md.

§02 is clean for this widget. Its Insights row (line 80) reads 「阶段时间戳 → dataset → 看板:交付各阶段到达数与审批吞吐,各段时长等平台日期能力(§09)」 — that points at the contract_cycle_time dataset and the approval_throughput tile, not at the stage bar, and it says nothing about a funnel or about coverage. PR #40 already corrected that row. No stale pointer here, and §02 is inside the governed range regardless.

Both translation bundles are already correctstage_funnel.title is Pipeline by Stage / 各阶段合同数, and the zh-CN description already says 「六个阶段…按数量从多到少排列,不是转化流程。已生效的合同不在此图内。」 The new §09 wording was aligned to these shipped strings rather than to the card's prose.

Two stale occurrences remain, both in app source, both left untouched on purpose. The card and the dispatch both say ⛔ do not touch the app; these are named here rather than silently left, so the seat can decide:

  1. src/dashboards/legal.dashboard.ts:4-5 — the file header quotes §09's 法务工作台 list to identify which dashboard it implements, and that quote is stale twice over: it still says 平均周转(本月 vs 上月) (PR Record four maintainer rulings in DESIGN.md §03, §06 and §09 #40 changed §09 to 审批吞吐) and 各阶段合同数漏斗 (this PR). One line, mechanical.
  2. src/datasets/cycle-time.dataset.ts:44-45 — "The status funnel on the legal workbench is the honest funnel; these are per-stage coverage counts." The status widget has not been a funnel since PR Draw 「Pipeline by Stage」 as a ranked bar, not a funnel #57, so the sentence's contrast no longer holds.

Neither is a runtime defect: both are comments, invisible to the gates and to the product. They are the same stale-pointer pattern the card warned about, pointing the other way — code quoting a document instead of a document describing code. Suggest one follow-up card covering both; not filed, so as not to pre-empt the seat's split.

🤖 Generated with Claude Code

https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR


Generated by Claude Code

§09 still specified 各阶段合同数漏斗 — a funnel over every lifecycle stage.
Two merged cards moved the app off that in two independent ways, and the
design authority never caught up:

- #48 / PR #57 replaced the mark with `type: 'horizontal-bar'` ordered by
  the measure, because a funnel asserts monotonic decline over a book that
  does not decline (§10: draft 10 · submitted 6 · in_review 12 ·
  in_approval 8 · approved 4 · signing 6 · active 60 — a bowtie).
- #59 / PR #62 narrowed the filter to the six in-flight stages, on the
  criterion the widget's comment states: whether legal still has work to do
  on the stage. `active` and the four terminal outcomes are all out.

The bullet now names the mark and the coverage; a new paragraph carries the
reasoning and states that no authored stage order is promised —
`options.stageOrder` is read only by console 17.4.0's funnel branch and is
dropped silently outside `en` (objectstack-ai/objectstack#17344). Promising
an ordering this platform version cannot deliver is the same defect class
PR #40 just removed from this section.

Documentation only: no app file changes, and no gate reads `DESIGN.md`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 10, 2026 11:21
@zhuangjianguo
zhuangjianguo merged commit 30fd863 into main Sep 10, 2026
2 checks passed
zhuangjianguo pushed a commit that referenced this pull request Sep 10, 2026
The legal dashboard's header carried a copy of §09's six-brick list. That
copy drifted three times in two days — two items rewritten by PR #40
(1127e52), the third by PR #70 (30fd863) — and by the end all three
described bricks this file does not implement. Replace the copy with a
pointer to §09, which cannot drift.

The cycle-time dataset's stage-count paragraph contrasted its counts with
"the status funnel on the legal workbench", a funnel that has not existed
since PR #57 made that widget a bar chart. Keep the measurement and its
reason (signed_at 72 exceeds submitted_at 60; backfilled in-force book,
no intake) and drop the claim that a funnel exists to contrast with.

Comments only: no metadata, no behaviour, no DESIGN.md change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
zhuangjianguo pushed a commit that referenced this pull request Sep 10, 2026
Two comments quoted a 09 that has since moved — the mirror of the cards that
fixed documents describing code wrongly.

legal.dashboard.ts's header carried a copy of 09's six-item brick list, and
the copy lost the race three times in two days: two items were rewritten by
PR #40 (1127e52) and the third by PR #70 (30fd863), leaving all three
describing bricks this file does not implement — the board ships
review_ageing titled "In Review Over 30 Days", approval_throughput titled
"Approved This Month", and a horizontal-bar. Two of them named metrics 09 now
explicitly declines to deliver.

The quote is removed rather than updated: updating buys correctness until the
next 09 edit and leaves the same trap armed, while a pointer cannot drift.
The header now says the list is deliberately absent, so the next reader does
not paste it back, and cites commit shas — immutable — instead of prose.

cycle-time.dataset.ts contrasted its five coverage counts with "the status
funnel on the legal workbench", which has been a horizontal-bar since PR #57.
The contrast is inverted rather than deleted: there is no funnel there
either, and the same non-monotone book is the reason. The measurement stays —
signed_at (72) exceeding submitted_at (60), and the backfill that explains it.

Comments only: no metadata key, no widget, no dataset field, no dimension.
These files are inside the gates' surface and the gates pass — but comments
are invisible to tsc, which is why the drift went unnoticed for two days.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
zhuangjianguo pushed a commit that referenced this pull request Sep 10, 2026
09 changed direction on 2026-09-09 (1127e52, PR #40; final state 30fd863,
PR #70): per-stage durations and the per-type "over SLA" are outside the V1.0
delivery surface, the legal bricks are 30-day review ageing and approval
throughput, the executive position carries per-rung routing volume, and the
cycle-time dataset carries stage-reached counts with no duration measure.

Five comments under src/ still described the 09 that existed before that
ruling, reading as "09 asks for X and we cannot deliver it" when 09 now
declines X for the very reasons those comments give. The authority ran
backwards: the source file was presented as falling short of the design while
it is in fact what the design settled on.

Each is now written as what the code delivers and why that is right, citing 09
as the authority that agrees. No measurement is deleted — Field.datetime stored
as ISO text so AVG() answers an average year, the -0.85 that is the difference
of two average years, ADR-0021 leaving the semantic layer no SQL and no
expressions, sys_approval_request holding zero rows on a stock demo — because
09 now cites those same measurements as its reasons, and they were taken here.

executive.dashboard.ts's header took #71's remedy: the copied 09 brick list is
gone and a pointer replaces it, naming the one brick it advertised that this
file never built so nobody pastes the list back.

Three stale forward-promises went with the framing, all of them things 09 now
forbids or the code does not have:

  - "a persisted review_due_at stamped by the daily job belongs to card 09" —
    09 forbids filling the per-type breach from an application-side job, and
    decision #31 ruled the same way on the notification side (legal-review-sla
    .flow.ts already said so).
  - "a daily job stamping clm_contract ... Raised on the PR" for durations —
    same prohibition.
  - 'the group title says "Routing Load"' — there is no group title and no such
    string; the four tiles are titled "Routes: <rung>".

Every citation of objectstack#16737 now carries the qualifier: the in-flight
platform fix makes the wrong path ERROR rather than adding date arithmetic, so
a duration stays uncomputable after it lands. It merely stops rendering a
plausible fake number.

Comments only: no type, filter, options, dataset field, dimension or widget id,
and no line outside comment syntax changed. These three files are inside all
four gates' surface and the gates pass — but comments are invisible to tsc,
which is exactly why the drift went unnoticed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R3n3GGzobdegM4HUzah1iR
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DESIGN.md §09 still specifies 各阶段合同数**漏斗** — the app stopped shipping a funnel in #48 and narrowed what it plots in #59

2 participants