Repository navigation
feat: show users what their action did, and say refusals in business words (#2371) - #2376
Merged
Merged
Conversation
…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
#2371 row 6) 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
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
Author
席位核对Verdict: ACCEPT ·
Generated by Claude Code |
This was referenced Oct 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Irene · project thread
Fixes #2371
Clause-②: no
Metadata-protocol: none
维护者速读
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.src/sales/actions/lead.actions.ts:224,src/sales/actions/contact.actions.ts:117read the flow's outputscampaignName,addedCount,alreadyCountfromcampaign_member_enroll(mark_already/mark_contact_already, edgese12). 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-summaryupdatedsrc/sales/pages/opportunity_detail.page.ts:120(opp_approval_pending_manager, amount ≤ 500,000) and:153(opp_approval_pending_director, above),record:alertseverity info, four localestest/opportunity-approval-pending-alert.test.ts(new)src/sales/objects/opportunity.object.ts:715-717: Prospecting, Qualification and Needs Analysis may move toclosed_wontest/opportunity-stage-moves.test.ts, block "an early win saves without a warning (#2371 row 3)"src/sales/actions/opportunity.actions.ts:72returns{ id, name }; toast:81Created "{{ result.name }}". You are now on the copy., four packstest/clone-opportunity-opens-clone.test.ts@objectstack/plugin-approvals18.2.0dist/index.mjs:7199,notifyDecisionOutcome, inbox to the submitter) stays. The flow'snotify_approved/notify_rejected(src/sales/flows/opportunity-approval.flow.ts:231,:253) are email only; its inbox copiesnotify_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-noticeupdatedsrc/revenue/flows/quote-generation.flow.ts:240-242,record_linkto the quote (#2297 shape). Contract: new flowsrc/sales/flows/contract-drafted-notice.flow.ts:55(inbox, topiccontract_drafted), started by the descriptionquote_accepted_contract_draftwrites (src/revenue/objects/quote.hook.ts:598, unedited)test/quote-generation-result-screen.test.ts,test/contract-drafted-notice.test.ts(new)src/revenue/objects/quote.object.ts:444, thequote_status_progressionstate machine; transitions unchangedtest/refusal-labels.test.ts, block "the quote status warning names the statuses by their labels"src/sales/objects/forecast.object.ts: the month-start rule skips quarterly forecasts, so the quarter rule's sentence (:373) is the only refusaltest/forecast-period-boundary.test.ts,test/forecast-period-end-boundary.test.tssrc/sales/profiles/sales-manager.profile.ts:149andsrc/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 dealsrc/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/specsrc/data/object.zod.ts:1426:enabled,visibleWhen,disabledWhenonly)Also in this PR:
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).crm.contract_draftedinsrc/sales/email-templates, four locales; it is the template the inbox notice renders.Tests
New files:
approval-one-notice(3),campaign-enrollment-result-copy(4 cases, 21 tests witheach),contract-drafted-notice(3),opportunity-approval-pending-alert(3 cases, 15 tests),quote-generation-result-screen(5).contract-drafted-noticeis listed intest/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 HEADwas clean after the run. Readings are under Verification.Acceptance notes
userMessageon 18.2.0 (@objectstack/specsrc/data/validation.zod.tshas no such key), andrefusal-envelopepins hook throws, so the Browser pass #2320 on 18.2.0 — HotCRM-side defects (13 items: dashboards shown to personas with no access, a dead-end case close, campaign editors who cannot edit, a toolbar action that always fails, …) — list card, split per item at dispatch #2338 two-reader shape does not apply. The sentence is themessage, each pack restates it, andrefusal-labelsholds it to the option labels. Codes are unchanged.quote.hook.tshas no status-transition block; the transitions live in the state machine and were not changed. The comment aboverejected: ['draft'](quote.object.ts) still says "A rejected quote is not frozen", which Offer only the actions a record can take, and add the missing ones: converted leads, settled and new quotes, hook-maintained counters, agent-visible sales actions, Generate Quote while locked, Reopen case, quote lifecycle buttons, terminal path states #2366 made false; left for its owner.successMessagecannot read run variables on 18.2.0 (holes render raw), so the counts are on a result screen, which, per the lead conversion: end on a result screen that links the created account, contact and opportunity (#2285 ruling B, second half) #2297 shape, always pauses. Every screen advance also shows a "Saved — next step" toast. Platform, reported.crm_contract, is defined there; registering it in revenue logs "targets object 'crm_contract', which this stack does not define". It runs assystem(rationale in the file) and reads only the new contract.@objectstack/objectql18.2.0dist/index.mjs:7084). Platform; this row removes the overlap on our side.GETanswered 404) and the campaign'snum_sentandnum_leadswent back to their earlier values within seconds.Verification
pnpm verify at 3fd6d09 (after merging #2366's 21664f7)
The first run, at 6612101, was red on one guard:
test/runtime-coverage.test.tslistedcontract_drafted_noticeas 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
Console readings on the dev boot (18.2.0, Playwright)
🤖 Generated with Claude Code
https://claude.ai/code/session_01J8VVAkrQzE8i7K8MQR3hR2