Skip to content

feat: show users what their action did, and say refusals in business words (#2371) - #2376

Merged
objectstack-fleet[bot] merged 13 commits into
mainfrom
claude/project-thread-32qgtv
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 13 commits into
mainfrom
claude/project-thread-32qgtv

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Irene · project thread
Fixes #2371
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么: Show users the result of what they did, and say refusals in business words: enrolment counts, approval submission, stacked toasts, clone, double approval notices, quote and contract results, quote transitions, forecast validation, lead delete #2371 的十行逐行实测后处理:营销活动加入成员时提示"新加入 N 人、已是成员 N 人",批量 Enroll 结束页列出新加入、已是成员、被跳过的人数;大额商机审批中时详情页顶部显示蓝色横幅;早期阶段直接赢单不再报警告;克隆商机的提示写出副本名称;审批结果只给提交人一条站内通知;生成报价单后显示带链接的结果页,接受报价单起草合同后给接受人一条站内通知(新流程 Contract Drafted Notice,共 34 个流程);报价单状态警告和季度预测的拒绝说明改为写出正确路径;手册补充私密商机谁能看见、删除线索/联系人会连带删除其活动成员记录。
  • 为什么改: Irene 同意了 UX 汇总的建议:每个操作之后,用户都应在屏幕上看到发生了什么、下一步是什么。
  • 风险与回滚: 低到中。行为变化集中在三个流程(活动成员加入、批量 Enroll、商机审批)和一个新流程;商机阶段允许 Prospecting/Qualification/Needs Analysis 直接到 Closed Won(原来是警告而非拒绝)。不改任何权限、共享规则或岗位。回滚即 revert 本 PR。
  • 先看: src/marketing/flows/campaign-enrollment.flow.ts(循环内新增的"已是成员"计数与汇合节点、结果页)、src/sales/flows/opportunity-approval.flow.ts(check_owner_submitted_* 决策与 notify_*_inbox)、src/sales/flows/contract-drafted-notice.flow.ts(新流程,runAs system)。
  • 两分钟验证: pnpm exec vitest run test/campaign-enrollment-result-copy.test.ts test/approval-one-notice.test.ts test/contract-drafted-notice.test.ts,预期全部通过;或在控制台以 admin 打开一条线索点 Add to Campaign,应弹出 "Q3 Enterprise Email Nurture: 1 added, 0 already a member."。
  • 需要你做的: 无,席位落地。

What changes

Every row was measured on origin/main (7f3a431, then 44d2d90 after #2350, then 21664f7 after #2366) before it was edited. Each toast, notice and refusal is cited where it is emitted.

Row Verdict Where it is emitted now Test
1 · Add to Campaign and Enroll Members report success whoever was added fixed Row action toast: src/sales/actions/lead.actions.ts:224, src/sales/actions/contact.actions.ts:117 read the flow's outputs campaignName, addedCount, alreadyCount from campaign_member_enroll (mark_already / mark_contact_already, edges e12). Enroll result screen: src/marketing/flows/campaign-enrollment.flow.ts:410-412; refused ending :400; clean toast :63. test/campaign-enrollment-result-copy.test.ts (new); flow-campaign-enrollment, campaign-enrollment-doors, campaign-enrollment-gates, flow-run-summary updated
2 · a save that sends a deal for approval says nothing fixed on the page; the save toast is platform src/sales/pages/opportunity_detail.page.ts:120 (opp_approval_pending_manager, amount ≤ 500,000) and :153 (opp_approval_pending_director, above), record:alert severity info, four locales test/opportunity-approval-pending-alert.test.ts (new)
3 · closing from an early stage stacks three toasts fixed. Measured on 18.2.0: two toasts, "Saved — with warnings Invalid opportunity stage transition" (the stage state machine) and the console's "Opportunity … updated"; no third. An early win no longer warns, so only the second remains src/sales/objects/opportunity.object.ts:715-717: Prospecting, Qualification and Needs Analysis may move to closed_won test/opportunity-stage-moves.test.ts, block "an early win saves without a warning (#2371 row 3)"
4 · cloning gives no notice fixed src/sales/actions/opportunity.actions.ts:72 returns { id, name }; toast :81 Created "{{ result.name }}". You are now on the copy., four packs test/clone-opportunity-opens-clone.test.ts
5 · an approval sends two notices fixed The platform's notice (@objectstack/plugin-approvals 18.2.0 dist/index.mjs:7199, notifyDecisionOutcome, inbox to the submitter) stays. The flow's notify_approved / notify_rejected (src/sales/flows/opportunity-approval.flow.ts:231, :253) are email only; its inbox copies notify_approved_inbox / notify_rejected_inbox (:278, :292) send only when someone other than the owner submitted (check_owner_submitted_*, :276, :290) test/approval-one-notice.test.ts (new); approval-recall-no-rejected-notice updated
6 · Generate Quote and the drafted contract go unannounced fixed Quote: result screen src/revenue/flows/quote-generation.flow.ts:240-242, record_link to the quote (#2297 shape). Contract: new flow src/sales/flows/contract-drafted-notice.flow.ts:55 (inbox, topic contract_drafted), started by the description quote_accepted_contract_draft writes (src/revenue/objects/quote.hook.ts:598, unedited) test/quote-generation-result-screen.test.ts, test/contract-drafted-notice.test.ts (new)
7 · "Invalid quote status transition" fixed in wording; naming this move's two statuses is platform (a state-machine message takes no holes) src/revenue/objects/quote.object.ts:444, the quote_status_progression state machine; transitions unchanged test/refusal-labels.test.ts, block "the quote status warning names the statuses by their labels"
8 · forecast messages arrive joined by semicolons fixed on our side (no rule pair overlaps now); the "; " join is platform src/sales/objects/forecast.object.ts: the month-start rule skips quarterly forecasts, so the quarter rule's sentence (:373) is the only refusal test/forecast-period-boundary.test.ts, test/forecast-period-end-boundary.test.ts
9 · a manager opening a private deal reads "does not exist" by design; the wording is objectstack#27355 row 7 RLS src/sales/profiles/sales-manager.profile.ts:149 and src/sales/profiles/marketing-user.profile.ts:77 (is_private == false || owner_id == current_user.id); the console's "does not exist or may have been deleted" page is the platform's. Manual now says who sees a private deal docs only
10 · deleting a lead does not warn about its members platform-only; manual added Cascade at src/marketing/objects/campaign_member.object.ts:171-183; the console's delete confirmation has no slot for extra text on 18.2.0 (RowCrudActionOverrideSchema, @objectstack/spec src/data/object.zod.ts:1426: enabled, visibleWhen, disabledWhen only) docs only; cascade + recount measured live

Also in this PR:

  • Docs. In-app manual, en and zh: src/docs/crm_sales_opportunities (early win, approval notices, the pending banner, private deals), src/marketing/docs/crm_marketing_automation (enrollment counts and the result screen), src/marketing/docs/crm_marketing_campaigns (deleting a person), src/revenue/docs/crm_revenue_quotes (the result screen, the status warning, the drafted-contract notice). Docs site, three locales: state-machines, pipeline-management, forecasting, approvals, campaign-members, automation (34 flows).
  • Email templates. crm.contract_drafted in src/sales/email-templates, four locales; it is the template the inbox notice renders.
  • README. The flow count is 34. The sales-package token banner is restated to the measured figures (~68k business semantics, ~31k interaction layer) per the 2026-09-05 ruling that pins the banner to the reading; ceilings unchanged.
  • Changeset (minor).

Tests

New files: approval-one-notice (3), campaign-enrollment-result-copy (4 cases, 21 tests with each), contract-drafted-notice (3), opportunity-approval-pending-alert (3 cases, 15 tests), quote-generation-result-screen (5). contract-drafted-notice is listed in test/runtime-coverage.test.ts.

Ablations: each behaviour change was reverted on disk by an exact-anchor replace, its test run, and the file restored with git checkout HEAD --; git diff --quiet HEAD was clean after the run. Readings are under Verification.

Acceptance notes

Verification

pnpm verify at 3fd6d09 (after merging #2366's 21664f7)
pnpm verify = validate && typecheck && lint && hygiene && hygiene:tokens && build && test
 Test Files  251 passed (251)
      Tests  4817 passed | 1 skipped (4818)
EXIT 0

The first run, at 6612101, was red on one guard: test/runtime-coverage.test.ts listed contract_drafted_notice as a flow with no runtime test. Its boot test is now in the guard's file list (3fd6d09). CI runs coverage and e2e.

Ablations at 3fd6d09: each change reverted on disk, its test run, the file restored
R1a lead row toast back to 'Leads added to campaign!'        campaign-enrollment-result-copy  1 failed | 20 passed
R1b per-row flow: already-a-member edge back to end          campaign-enrollment-doors        2 failed | 36 passed
R1c Enroll: clean ending skips the result screen             flow-campaign-enrollment         4 failed | 26 passed
R2  pending banner shown without approval_status pending     opportunity-approval-pending-alert 3 failed | 12 passed
R3  prospecting may not move to closed_won                   opportunity-stage-moves          2 failed | 21 passed
R4  clone body returns no name                               clone-opportunity-opens-clone    1 failed | 8 passed
R5  flow inbox sent whoever submitted (the old double notice) approval-one-notice             2 failed | 2 passed
R6a Generate Quote skips the result screen                   quote-generation-result-screen   2 failed | 6 passed
R6b contract_drafted_notice unregistered                     contract-drafted-notice          1 failed | 2 passed
R7  quote warning back to 'Invalid quote status transition'  refusal-labels                   1 failed | 11 passed
R8  month-start rule judges quarterly forecasts again        forecast-period-boundary         1 failed | 28 passed
git diff --quiet HEAD after the run: clean
Console readings on the dev boot (18.2.0, Playwright)
row 1  lead row action:   "Q3 Enterprise Email Nurture: 1 added, 0 already a member."
       run again:         "Q3 Enterprise Email Nurture: 0 added, 1 already a member."
       Enroll result:     "0 added to the campaign, 5 already members, 1 skipped because they opted out of email or have no email address."
       bulk, 2 leads:     dialog "Succeeded 2 / 2" (platform fan-out, unchanged)
row 2  pending deal:      blue banner "Sent for approval to a Sales Manager" with the Recall sentence
row 4  clone:             "Created "Copy of Globex Manufacturing Suite". You are now on the copy."
row 6  Generate Quote:    result screen with a link to QTE-0007
       accept a quote:    toast "Contract drafted: CTR-0006 …" with View
row 10 delete a lead:     member row GET 404; campaign num_sent 5 -> 4, num_leads 3 -> 2 within seconds

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2

claude added 13 commits October 29, 2026 03:03
…atus warning names the path by label (#2371 rows 7, 8)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
Closing a Prospecting, Qualification or Needs Analysis deal as won came
back as "Saved — with warnings: Invalid opportunity stage transition"
on top of the save toast. Closed Won is now on the path from every open
stage, as Closed Lost already was; a forward skip within the open stages
still warns. The docs' example of an off-path move becomes Prospecting →
Negotiation, and the in-app stage diagram draws the new arrows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
Clone Opportunity said "Opportunity cloned successfully!" and then
opened the copy, which looks like the source but for the name prefix and
the stage. The body now returns the copy's name with its id, and the
success message renders it in every locale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
…-32qgtv

# Conflicts:
#	test/refusal-labels.test.ts
The platform tells the request's submitter the outcome in the inbox
(approval.approved / approval.rejected, with the approver's comment), and
opportunity_approval told the owner again, in the inbox and by email. The
flow now emails the owner every time, since the platform sends no email,
and adds its inbox notice only when the owner did not submit the request,
so the platform's notice went to someone else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
…w 2)

The save that sends a deal for approval showed only the save toast, and
the page gained Recall / Reply buttons with no word on why. No hook or
flow on 18.2.0 can add text to that toast, so the deal page now carries
an info banner while the request waits: which position decides (Sales
Manager, or Sales Manager then Sales Director over $500,000), that the
deal is locked, and what Recall does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
…2371 row 6)

The flow closed on a toast and left the rep on the opportunity, with the
new quote reachable only through the related list. It now pauses, after
every write, on a result screen naming the quote with a record_link to
it, the shape lead conversion took in #2297.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
#2371 row 6)

quote_accepted_contract_draft is an async hook, so the acceptance showed
only the quote's save toast and the drafted contract was found only by
looking. A new record-change flow, contract_drafted_notice, fires on that
contract's insert (the hook's provenance sentence marks it) and sends the
person who accepted the quote an inbox notice linking the contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
…kipped (#2371 row 1)

Enroll Members said the members were enrolled whether or not anyone was,
and a single-row Add to Campaign said "Leads added to campaign!" on a run
that found the person already a member.

- campaign_enrollment counts who was already a member (the subflow came
  back without enrolling) and who the eligibility filters skipped (the
  segment's size less the eligible list), and ends on a result screen with
  the three counts and a link back to the campaign. The refusal names all
  four counts. The flow's success sentence is neutral, since a screen
  flow's successMessage cannot read the run's variables on 18.2.0.
- The two enroll flows output the campaign name and an added / already
  count, and Add to Campaign's toast renders them in every locale.

The bulk toolbar keeps the platform's "Succeeded N / N", which counts the
rows the action ran on; that is reported as a platform finding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
…r memberships (#2371 rows 9, 10)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
…e measured sales figures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
Keeps main's 38 actions with this branch's 34 flows, places #2366's quote
lifecycle sections ahead of the accepted-quote notice in the quote manual,
and names contract_drafted_notice's boot test in the runtime-coverage guard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2
@vercel

vercel Bot commented Oct 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Oct 29, 2026 4:17am UTC

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces configuration Build and app configuration files backend Server-side behaviour — hooks, flows, actions labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Verdict: ACCEPT · repo:hotcrm seat 1 · huangyiirene · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-10-29T04:20Z · head 3fd6d09
Implemented-by: card thread session session_01J8VVAkrQzE8i7K8MQR3hR2 (report 6487801082 on #2371). Reviewed-by: the seat, against the 82-file diff and hotcrm origin/main 21664f7.


Generated by Claude Code

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

Labels

backend Server-side behaviour — hooks, flows, actions ci/cd CI plumbing and the verification pipeline configuration Build and app configuration files documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

2 participants