Skip to content

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

Description

@zhuangjianguo

The defect

DESIGN.md §09 specifies the Legal Workbench widget as 各阶段合同数漏斗 — a funnel. Two merged cards have since moved the app away from that, in two independent ways:

Change Merged What §09 now misdescribes
#48 / PR #57 (2b74c4f) the mark stopped being a funnel — type: 'horizontal-bar', ordered by the measure the word 漏斗
#59 / PR #62 (b667fda) the filter dropped active; it plots the six in-flight stages, not seven what the widget covers

So the design authority and the tree disagree twice over, and §09 is the section a reader consults to learn what the analytics layer is supposed to be.

The rulings behind both changes are sound and are not in question here. A funnel encodes monotonic decline over a distribution that does not decline (draft 10 · submitted 6 · in_review 12 · in_approval 8 · approved 4 · signing 6 · active 60), and one stage of 60 was setting the axis for six stages of 4–12. This card only brings the document up to what shipped.

What to change

DESIGN.md §09's entry for this widget:

  1. Stop calling it a funnel. It is a ranked horizontal bar ordered by the measure.
  2. Say what it plots — the six in-flight stages, not every lifecycle stage. The criterion the widget's own comment now states is whether legal still has work to do on the stage, which is why active and the four terminal outcomes are all out.
  3. Do not re-promise an authored stage order. options.stageOrder was removed deliberately: it is honoured only by the funnel branch of console 17.4.0 and is silently dropped in every non-en locale (Dashboard widgets: options.stageOrder is declared for every chart type, documented for a chart type that does not exist, and silently dropped in every non-en locale objectstack#17344). §09 must not describe a lifecycle ordering this platform version cannot deliver — that is the same defect class as the 「各段时长」 promise PR Record four maintainer rulings in DESIGN.md §03, §06 and §09 #40 is removing from the same section.

Read the merged widget before you write: src/dashboards/legal.dashboard.ts, the stage_funnel entry. Its comment block carries the reasoning and the measurements, and the document should agree with it rather than paraphrase this card.

Constraints

Acceptance

  • pnpm validate && pnpm lint && pnpm typecheck && pnpm lint:i18n-gate all 0, exit codes captured before any pipe. None of them reads DESIGN.md — say that plainly rather than implying the gates verified the change.
  • Every claim in the new text traceable to src/dashboards/legal.dashboard.ts as merged, in a claim → reading table.
  • No browser run owed: the diff changes no runnable surface.
  • Grep §09 and §02 for any other description of this widget before declaring done. PR Record four maintainer rulings in DESIGN.md §03, §06 and §09 #40 already found that §02's Insights row was pointing at a §09 promise that had changed; the same pointer pattern could exist for this one.

Provenance

Found by the dev on #59 while implementing PR #62, reported as an out-of-scope finding and correctly neither edited nor filed there — §09 is the maintainer's wording, DESIGN.md was held, and no PR trips over it. Confirmed by the PM seat against both merge commits.

Related

#48 / PR #57 (the mark) · #59 / PR #62 (the filter) · PR #40 (holds DESIGN.md; rewrites §09's neighbouring paragraph) · #44 · #58 (the other two queued DESIGN.md items) · objectstack-ai/objectstack#17344 (why no stage order is promised)

Activity

  1. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    CollaboratorAuthor

    Claim: session session_01R3n3GGzobdegM4HUzah1iR · branch claude/issue-63-design-09-not-a-funnel

    PM dispatch. The assignee and this claim are set by the PM seat on behalf of the dev that will work this card; the dev inherits both, checks that this is the newest Claim: on the thread and that it names its branch, and ⛔ posts no second claim and never writes the assignee field.

    Blocker cleared. PR #40 merged as 296fab8 (the maintainer's, on the governed path), then #44 as 0701eb1 and #58 as 9b7538a. DESIGN.md is unheld and you are the last of the three queued cards on it. Branch off current origin/main (9b7538a).

    ⚠️ Re-read §09 on 9b7538a before editing. Three commits have touched DESIGN.md since this card was written:

    commit what moved
    296fab8 (PR #40) §02, §03, §06 and §09 — this is the one that matters to you. It rewrote what §09 says the analytics layer delivers, removed the 「各段时长」 and per-type 「超 SLA」 promises, and rewrote the 管理层 bullet.
    0701eb1 (#44) §06 F14, one word
    9b7538a (#58) §10's seeding sentence and §11's M1 row

    Your edit has to sit alongside #40's §09 rewrite, not undo it. #58's dev measured §09 as spanning lines 293–309 on 0701eb1; re-derive that yourself rather than trusting the number. Confirm 各阶段合同数漏斗 is still there verbatim, and quote what you actually find — #44's dev caught that its card had dropped backticks from the target string, and an exact-match edit that silently matches nothing reports success.

    Dispatch notes on top of the card body:

    Acceptance

    • All four gates 0, exit codes captured before any pipe. None of them reads DESIGN.md — demonstrate the negative rather than asserting it. PR docs: the position census disagrees with itself in three places — README says seven over a table of six, DESIGN.md §11 says both 7 and 8 #69 set the current bar with three independent demonstrations (grep over the gate logs, tsc --listFiles showing zero .md files, and diff/walk disjointness); one solid demonstration is enough, but it has to be a measurement.
    • Every claim in the new §09 text traced to src/dashboards/legal.dashboard.ts as merged, in a claim → reading table.
    • Verify the edit landed on disk by before/after counts of the old and new spellings — ⛔ never by an editor's exit code.
    • No changeset (this repo has no changeset gate). No browser run owed: the diff changes no runnable surface.

    Generated by Claude Code

  2. claude commented on Sep 10, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 63,
      "status": "done",
      "branch": "claude/issue-63-design-09-not-a-funnel",
      "pr": "https://github.com/objectstack-ai/hotclm/pull/70",
      "premise_still_valid": true,
      "summary": "Re-derived §09 on 9b7538a myself (lines 293-309; '## 09 分析' at 293, '## 10 种子数据' at 310) and confirmed 各阶段合同数漏斗 present verbatim at the end of line 302's 法务工作台 bullet — quoted in full in the PR body. PR #40 (296fab8) had already rewritten the rest of that bullet (超 SLA → 审查超 30 天(固定阈值…), 平均周转 → 审批吞吐(…用平台 compareTo)) and appended the 各段时长 ruling paragraph; 漏斗 was the one stale item left, and this edit sits alongside #40's rewrite — that paragraph and every other §09 row are byte-identical. Two hunks, both inside §09: line 302's bullet now reads 各阶段合同数(横向条形图,按合同数从多到少排;只画在办的六个阶段), and a new paragraph at line 310 carries the reasoning (why a funnel asserted something the numbers do not say) plus the explicit ⛔ non-promise about stage order, citing objectstack-ai/objectstack#17344. Hunk 2 is flagged in the PR body as an addition rather than a correction so the seat can split it; the case for keeping it is that card items 2 and 3 cannot be discharged by the bullet alone — a recorded negative is what stops the next reader re-adding stageOrder, which is how PR #40 handled the same defect class. Every claim is traced to src/dashboards/legal.dashboard.ts as merged in a 15-row claim → reading table in the PR body. No app file touched. §01-§04 byte-identical (sha256 9d1de298628d97616a705d6e4be904768a0c06b14591354e3b5939f8ed8abc67 before and after). Assignee arrived already set by the PM dispatch and was not written; the newest Claim: names this session and this branch, so no second claim was posted.",
      "tests": "All four gates on 8ecfb1c, each redirected to its own log with $? read on the next statement, no pipe between command and exit code: validate=0 lint=0 typecheck=0 lint:i18n-gate=0. i18n gate verdict line: '✓ i18n gate / COVERAGE: 0 missing keys across 2 locale(s)' over 12 surface classes (widget=38, dashboard=6). lint printed '21 warning(s), 5 suggestion(s)' and validate 27 ⚠ — all pre-existing, none reachable from a diff that changes no metadata. NONE OF THE GATES READS DESIGN.md — measured, not asserted, three independent ways plus a disjointness check. (1) grep -ni for DESIGN.md or any .md over all four gate logs: exit 1, 0 matching lines. (2) tsc --noEmit --listFiles: exit 0, 305 files compiled, 0 of them .md, 0 lines mentioning DESIGN. (3) ABLATION, the decisive one — fix committed FIRST so the restore leg points at HEAD; DESIGN.md overwritten with two lines of non-markdown garbage; mutation proven on disk BEFORE running anything (303 non-empty lines → 2; marker 'DELIBERATELY CORRUPTED' count 1; on-disk git hash-object c17cd06b… ≠ HEAD blob 4bf1529b…, asserted non-equal); all four gates re-run against the corrupted file: validate=0 lint=0 typecheck=0 i18n-gate=0. A DESIGN.md reduced to two lines of garbage passes every gate unchanged, so the gates verify nothing about this diff. Restore ran under trap '... EXIT INT TERM' with an absolute path from git rev-parse --show-toplevel and git checkout HEAD -- (not bare checkout --), and was verified BY STATE not by an exit code: git 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) diff/walk disjointness: git diff --name-only 9b7538a..HEAD = DESIGN.md only; its intersection with the gates' walk root (objectstack.config.ts + src/** per tsconfig include) is 0 lines, so every byte the gates read is identical to 9b7538a. EDIT VERIFIED ON DISK by before/after spelling counts, never by an editor's exit code: 各阶段合同数漏斗 1→0, 横向条形图 0→2, 在办 0→2, sortBy 0→1, stageOrder 0→1, 17344 0→1. 漏斗 still matches 1 line deliberately — 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 a silent zero-match edit could not have reported success. Control-character scan clean: grep -naP over DESIGN.md exit 1. No browser run owed — the diff changes no runnable surface, and the ablation shows the running app cannot observe this file at all. No changeset (this repo has no changeset gate).",
      "mcp_calls": "0 — every GitHub read and write went through repo-scoped REST (probe returned HTTP 200); no MCP GitHub tool was called",
      "open_questions": [],
      "out_of_scope_findings": [
        "noted, not filed: src/dashboards/legal.dashboard.ts:4-5 — the file header quotes §09's 法务工作台 list to say which dashboard it implements, and that quote is stale twice over: 平均周转(本月 vs 上月) (PR #40 changed §09 to 审批吞吐) and 各阶段合同数漏斗 (this PR). Left untouched because the card and dispatch both say ⛔ do not touch the app — not because the card did not name it; flagged in the PR body so the seat can decide. Successor: the seat reviewing PR #70, or the next card that opens legal.dashboard.ts.",
        "noted, not filed: 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 #57, so the sentence's contrast no longer holds. Same reason for leaving it, same successor; suggested in the PR body as one follow-up card covering both, not filed so as not to pre-empt the seat's split.",
        "noted, not filed: §02's Insights row (DESIGN.md:80) was CHECKED for the stale-pointer pattern the card warned about and is clean for this widget — it points at the contract_cycle_time dataset and the approval_throughput tile, says nothing about a funnel or about coverage, and PR #40 already corrected it. It is also inside the governed range §01-§04. No change needed and none made. Both translation bundles (en/zh-CN stage_funnel) were already correct; the new §09 wording was aligned to those shipped strings rather than to the card's prose."
      ]
    }

    Generated by Claude Code

  3. zhuangjianguo commented on Sep 10, 2026

    @zhuangjianguo
    CollaboratorAuthor

    ACCEPT — PR #70

    CI green on head 8ecfb1cd (two Validate runs, both success), base is current main (9b7538a), one file, +3/−1, §09 only, no governed surface. First round. Squash-merging.

    The ablation is the best verification any card has produced this session

    I asked you to demonstrate rather than assert that no gate reads DESIGN.md. The two cards before you did that by showing the gates never open the file — grep over the logs, tsc --listFiles with zero .md among 305. You did something categorically stronger:

    DESIGN.md was overwritten with two lines of garbage, the mutation proven on disk (303 lines → 2, marker present, on-disk hash ≠ HEAD blob), and all four gates re-run: validate=0 lint=0 typecheck=0 i18n-gate=0.

    "No evidence the gates read it" and "proof the gates cannot detect its destruction" are different claims, and only the second actually licenses the sentence every one of these PRs has to write. Then you restored by state rather than by an exit code — git hash-object equal to git rev-parse HEAD:DESIGN.md, git diff HEAD empty, marker gone, 303 lines back — which is the same discipline #58 and #44 used for their edits, applied to an ablation.

    And you drew the right conclusion out loud: the gates verify that the metadata parses; they say nothing whatsoever about this diff. The only checks on this change are the reading table and human review. That sentence should outlive this PR.

    The edit itself

    Both hunks sit alongside PR #40's §09 rewrite rather than over it — you confirmed the 各段时长 paragraph and every other row byte-identical, and matched the new paragraph to the register #40 established (bold lead, the reason, the ⛔ prohibition, the upstream tracker). That was the specific risk in this card and you closed it first.

    Flagging hunk 2 as an addition rather than a correction, with the case for keeping it, is the right handling — and the case is correct: item 3 needs the negative recorded, or the next reader re-adds stageOrder, which is exactly the defect class #40 removed from this section by recording rather than by deleting silently.

    The 漏斗 count going 1 → 1 with an explanation — the surviving line uses the word only to deny it, four times, and it is no longer the widget's name anywhere — is the kind of thing a careless before/after check would have flagged as a failed edit. Good that you anticipated it.

    Your two out-of-scope findings — filed as #71, with a third you did not spot

    Both confirmed on 9b7538a, and correctly left untouched: the card and the dispatch both said ⛔ do not touch the app, and naming them rather than silently dropping them is what let the seat decide.

    One sharpening. You reported legal.dashboard.ts:3–5 as stale twice. It is stale three times — the header also quotes 超 SLA, which PR #40 changed to 「审查超 30 天(固定阈值,写进砖的标题)」 in the same commit that changed 平均周转. So all three items in that quoted list are wrong, and two of them (平均周转, 超 SLA) name metrics §09 now explicitly declines to deliver. A reader trusting that header concludes the dashboard should ship two things the maintainer ruled out.

    #71 carries both files, with the recommendation that the header stop quoting the list and point at §09 instead: a copy that has drifted three times in two days is a copy that will drift again; a pointer cannot.

    §02

    Checked and clean for this widget, with the reason — its Insights row points at contract_cycle_time and the throughput tile, not at the stage bar, and PR #40 already corrected it. Reporting the negative with its evidence is worth as much here as a hit would have been.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions