Skip to content

docs(flows): name what the flow nodes do, not what they were once meant to do - #877

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-869-flow-node-labels
Aug 6, 2026
Merged

yinlianghui merged 1 commit into
mainfrom
claude/issue-869-flow-node-labels

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #869

#851 / PR #870 corrected the Automation page's flow table and the opportunity_won_alert description. The same claims survived one layer further in — on the nodes themselves, which ship as authored metadata inside dist/objectstack.json, and in a file-header comment that had begun contradicting the description eleven lines below it. Same "write the current behaviour" standard, same wording, no third phrasing.

Premise re-check against the current baseline

Baseline origin/main = 6cd53d2, which contains PR #870 (5868728) and #874. All three sites re-located after #870's edits and all three are still present:

issue says after #870 still there?
opportunity-won-alert.flow.ts:71 label Notify Management line 71 yes
case-escalation.flow.ts:87 label Assign to Senior Agent line 87 yes
opportunity-won-alert.flow.ts:10-12 JSDoc "notify the owner and their manager" lines 10-12 yes

Node id reference chains, checked before editing (the issue flagged these as load-bearing):

notify_management    -> declared 1x + referenced by edges e1.target, e2.source
assign_senior_agent  -> declared 1x + referenced by edges e2.target, e3.source + one comment cross-ref
notify_team          -> declared 1x + referenced by edges e3.target, e4.source
CaseEscalationOnCreateFlow rewrites the node list via `n.id === 'start'`

No id is touched. Diffing the id: / source: / target: lines against origin/main shows every id string byte-identical; only label: values and comments differ.

The three sites

file before after why
src/flows/opportunity-won-alert.flow.ts label Notify Management Notify Owner recipients: ['{record.owner_id}'] — the owner and nobody else. The comment directly above the node already explained why there is no manager recipient: {record.owner_id.manager} cannot traverse a lookup on the raw trigger snapshot, so it interpolates to the literal undefined and the message goes to a phantom user. The label was contradicted by its own header.
src/flows/case-escalation.flow.ts label Assign to Senior Agent Flag as Escalated the update_record node writes is_escalated, escalation_reason, escalated_date, status. It never touches owner_id; the comment inside it opens with No owner reassignment:.
src/flows/opportunity-won-alert.flow.ts:10-12 JSDoc "notify the owner and their manager" "notify the owner — the owner alone, not their manager" verbatim the correction #870 landed in the description eleven lines below, so the two no longer disagree.

Notify Owner is not a new coinage: task-urgent-alert, task-due-reminder, contract-renewal and contract-expiration already label this node exactly that.

One further label in the same file

The dispatch allows same-file labels that are equally untrue to be corrected in the same pass, listed here:

  • src/flows/case-escalation.flow.ts — label Notify Support Team -> Notify Case Owner. recipients is the single entry {caseRecord.owner_id}, not a team. The decisive evidence: the identical node in src/flows/case-sla-monitor.flow.ts carries the same notify_team id and the same owner-only recipient, and its label already reads Alert Owner — this one had simply been left behind. A prior pass had already corrected this node's message body ("reassigned" was also false: this flow never changes the owner) without correcting its label.

Why the ids keep their old spellings

notify_management, assign_senior_agent and notify_team now each carry a short comment recording that the id is deliberately stale-looking. After this PR every one of them reads like a drift a future agent might "tidy" — and renaming any would be a behaviour change wearing a wording fix's clothes.

Artifact landing

pnpm build then scanning dist/objectstack.json:

NEW   "Notify Owner"       -> 5 hits  (4 pre-existing flows + this node)
      "Flag as Escalated"  -> 2 hits  (case_escalation + case_escalation_on_create)
      "Notify Case Owner"  -> 2 hits  (same two flows)

OLD   "Notify Management"      -> 0 hits
      "Assign to Senior Agent" -> 0 hits
      "Notify Support Team"    -> 0 hits

IDS   notify_management -> 3   assign_senior_agent -> 6   notify_team -> 8

The 2-hit counts are correct rather than duplicated: CaseEscalationOnCreateFlow builds its node list from CaseEscalationFlow.nodes.map(...), so both flows carry the corrected nodes.

Do any tests pin these labels?

No — confirmed, as the dispatch predicted. test/automation-docs-coverage.test.ts derives its expectations from each flow's own top-level label (labelFor = (flow) => flow.label), plus row sets, trigger cells and counts. Node labels never enter it. test/docs-drift.test.ts extracts only numeric thresholds and cron schedules. A repo-wide grep for the three literals returns the three source lines and nothing else — no test, fixture or doc quotes them.

Re-ran both guards on their own to be sure: 2 passed (2) / 53 passed (53).

A "revert it and watch the tests go red" check is therefore not available for this change, and reporting one would be fabrication: nothing pins node labels, which is precisely why these three drifted unnoticed. The load-bearing evidence here is the artifact scan above (old strings provably gone, ids provably intact) plus the guards staying green in the direction predicted before running them.

Not verified (carried over from the issue, unchanged)

The issue states it did not verify whether the process monitor / flow run detail actually renders node labels, and this PR does not close that gap either. The fix stands on the labels being authored metadata published in the artifact — a false label is worth correcting whether or not that particular surface paints it.

Verification

All six gates run under the shared lock with NODE_OPTIONS=--max-old-space-size=4096:

step exit evidence
pnpm validate 0 24 Flows; 5 author-time warnings, all pre-existing (approval staffing, campaign_member field group)
pnpm typecheck 0 clean
pnpm build 0 Build complete (2162ms), Artifact: dist/objectstack.json (1921.4 KB)
pnpm test --maxWorkers=2 0 Test Files 66 passed (66) / `Tests 1587 passed
pnpm lint 0 13 warnings, 14 suggestions — all pre-existing
pnpm hygiene 0 no raw control bytes in first-party files

Control-byte self-scan over the three changed files, beyond the gate's scan surface: 0 hits.

Scope

Labels and comments only. No recipient, condition, field set, edge or id differs, and this takes no position on whether escalation should reassign or should copy a manager — both remain open product questions under #595.

Filed while verifying, not fixed here: #875 (src/docs/crm_sales.md makes the same manager claim twice, and that file ships in the artifact via ADR-0046) and #876 (content/docs/service/sla-and-escalation describes escalation as a reassignment plus a three-way mailout, in three languages).


Generated by Claude Code

…nt to do (#869)

#851 corrected the Automation page's flow table and the `opportunity_won_alert`
`description`. The same three claims survived one layer further in — on the
nodes themselves, which ship as authored metadata inside
`dist/objectstack.json`, and in a file-header comment that had begun
contradicting the description eleven lines below it.

- `Notify Management` -> `Notify Owner` in `opportunity-won-alert.flow.ts`. The
  node addresses `{record.owner_id}` and nothing else. The comment directly
  above it already explained why there is no manager recipient:
  `{record.owner_id.manager}` cannot traverse a lookup on the raw trigger
  snapshot, so it interpolates to the literal `undefined` and the message is
  delivered to a phantom user. The label was contradicted by its own header.
  `Notify Owner` is also the label four other flows already use for this node.
- `Assign to Senior Agent` -> `Flag as Escalated` in `case-escalation.flow.ts`.
  The `update_record` node writes `is_escalated`, `escalation_reason`,
  `escalated_date` and `status`, and never touches `owner_id`; the comment
  inside it opens with "No owner reassignment". The case stays with the agent
  who had it, exactly as the escalation notice tells its recipient: "It remains
  assigned to you."
- `Notify Support Team` -> `Notify Case Owner` in `case-escalation.flow.ts`.
  Same file, same class of claim: `recipients` is the single entry
  `{caseRecord.owner_id}`, not a team. The identical node in
  `case-sla-monitor.flow.ts` — same `notify_team` id, same owner-only recipient
  — already carried the honest label `Alert Owner`; this one had been left
  behind.

The file-header comment on `opportunity-won-alert.flow.ts` still described the
flow as "notify the owner and their manager", the sentence #851 removed from
the `description` below it. It now carries that same correction.

Node ids are deliberately untouched. `notify_management`, `assign_senior_agent`
and `notify_team` keep their original spellings because `edges[]` reference
nodes by id and `CaseEscalationOnCreateFlow` rewrites the node list by id.
Renaming one would be a behaviour change wearing a wording fix's clothes, so
each node now carries a short note saying so, to keep the next reader from
tidying an id that looks stale beside its corrected label.

Labels and comments only. No recipient, condition, field set, edge or id
differs, and this takes no position on whether escalation should reassign or
should copy a manager — both remain open product questions under #595.

Co-authored-by: Claude <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hotcrm Ignored Ignored Aug 6, 2026 2:31am

Request Review

@github-actions github-actions Bot added the backend Server-side behaviour — hooks, flows, actions label Aug 6, 2026
@yinlianghui
yinlianghui marked this pull request as ready for review August 6, 2026 03:04
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 6, 2026
Merged via the queue into main with commit 482d93e Aug 6, 2026
10 checks passed
This was referenced Aug 6, 2026
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
…the real navigation (objectstack-ai#927) (objectstack-ai#932)

The section that tells a new user where to click named four items; three of
them did not survive a read of `src/apps/crm.app.ts`, and the one real item
the group does carry was missing.

Measured against `src/apps/crm.app.ts:131-140`, `group_service` has exactly
three children: `nav_case` (Cases), `nav_knowledge` (Knowledge) and
`nav_service_dashboard`, whose label is **Service Overview** — not "Service
Dashboard". `crm_task` has no entry in this group at all: its two nav items
are `nav_my_tasks` (My Tasks) and `nav_all_tasks` (All Tasks), both under
`group_work` / **My Work** (`:84` / `:93`). And no metadata anywhere carries
the name *Service Board*: the kanban is the view `case_workflow` with
`label: 'Service Workflow'` (`src/views/case.view.ts:72-75`), reached from
the **Workflow** tab in the case list's view switcher (`:59`) and never from
the sidebar.

So the list now names the three real items with their real labels, adds the
Knowledge entry it had been dropping, and keeps the two names readers will
arrive with — Tasks and Service Board — pointing at where those things
actually live, per the objectstack-ai#870 / objectstack-ai#877 / objectstack-ai#885 / objectstack-ai#894 / objectstack-ai#913 / objectstack-ai#924 convention of
naming what does not exist rather than deleting it silently.

Product questions are left open on purpose (objectstack-ai#595 / objectstack-ai#596): whether the kanban
should become its own nav item, and whether Tasks belongs under Service, are
not decided here — only the current shape is recorded.

All three locales, same lines. `src/` untouched.


Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa

Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
…to the real navigation (objectstack-ai#943) (objectstack-ai#953)

The section that tells a reader where to click named four things; three of them
do not survive a read of `src/apps/crm.app.ts`, and the one that does was
labelled with a name the app never shows.

Measured against `src/apps/crm.app.ts` on `origin/main` (b7791ca, platform
17.0.0-rc.3), the app has seven groups — Sales, My Work, Activity, Marketing,
Service, Insights, Approvals — and none of them is **Products**. The catalog's
only sidebar entry anywhere is `nav_product`, labelled **Products**, under
`group_marketing` (`:126`); `grep -rn "crm_product" src/apps/` returns that one
line. So the page now sends a reader arriving with the name "Products group" to
Marketing instead of to a place that does not exist, and links the Marketing
overview page that objectstack-ai#938 / PR objectstack-ai#942 just brought in line.

`group_approvals` (`:159-171`) has exactly one child: `nav_approval_requests`,
whose label is **Inbox** (`:165`) — not *Approval Requests*, which no metadata
in this repo carries. The other two listed items are re-judged individually
rather than deleted silently, per the objectstack-ai#870 / objectstack-ai#877 / objectstack-ai#885 / objectstack-ai#894 / objectstack-ai#913 / objectstack-ai#924 /
objectstack-ai#932 / objectstack-ai#942 convention:

- **Action History** — no nav item of that name exists. The data behind it does:
  @objectstack/plugin-approvals registers `sys_approval_action` (enumerated from
  the installed package, alongside `sys_approval`, `sys_approval_approver`,
  `sys_approval_delegation`, `sys_approval_request`, `sys_approval_token`), and
  nothing in this app's navigation opens it. The page says exactly that.
- **Processes** — deleted on purpose, and the source records why at `:166-169`:
  no `sys_approval_process` object exists in any installed plugin, so the old
  item's `requiresObject` guard hid it on every install, forever. The enumeration
  above confirms the absence, so the reason is written into the page.

The one true claim survives unchanged: **Contracts** is under **Sales**
(`nav_contract`, `:60`), and it is the app's only sidebar route to a contract.
Two further facts from source are recorded with it: `group_marketing` and
`group_approvals` declare no `expanded` key while Sales, My Work, Activity and
Service set `expanded: true`, and `GroupNavItemSchema.expanded` defaults to
`false` — so both groups are collapsed on load, which is the failure mode that
sends a reader looking for the catalog away empty-handed. And the zh-Hans page
names the label a simplified-Chinese user actually sees, 待我审批
(`src/translations/zh-CN.ts:1219`), next to the source label.

Product questions stay open on purpose (objectstack-ai#595 / objectstack-ai#596): whether Products deserves
its own group and whether Approvals should gain an audit-trail item are product
decisions, not documentation ones. Only the current shape is recorded.

All three locales, same lines; zh internal links carry no anchor. `src/`
untouched.

Fixes objectstack-ai#943


Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa

Co-authored-by: Claude <noreply@anthropic.com>
yinlianghui added a commit to yinlianghui/hotcrm that referenced this pull request Aug 10, 2026
… real app (objectstack-ai#960) (objectstack-ai#968)

The first table a new user reads, whose entire job is "here is what the sidebar
holds", had drifted in every one of its eight rows.

Measured against `src/apps/crm.app.ts` on `origin/main` (4705aed, platform
17.0.0-rc.3), the app's `navigation` is one pinned top-level entry plus seven
groups: `nav_home` (Home, :34), `group_sales` (:42), `group_work` (My Work,
:69), `group_activity` (Activity, :103), `group_marketing` (:120),
`group_service` (:131), `group_insights` (Insights, :144) and `group_approvals`
(:160). Row by row, the old table said:

- **Sales** — the group is real, but it carries nine children, not six. The
  table dropped Account Workbench (:52), Pipeline (:55) and Sales Performance
  (:61).
- **Service** — real; the entry the table called *Knowledge Base* is labelled
  **Knowledge** (:138), and **Service Overview** (:139) was missing. Both
  names were already written to source on `service/index` by objectstack-ai#927 / PR objectstack-ai#932
  and objectstack-ai#937 / PR objectstack-ai#947, so the two pages contradicted each other.
- **Marketing** — real, but its children are Campaigns (:125) and Products
  (:126). *Campaign Members* is not a navigation entry at all:
  `grep -rn "crm_campaign_member" src/apps/` returns nothing.
- **Products** — no such group. The catalog's only sidebar entry is
  `nav_product` under `group_marketing`, the same finding objectstack-ai#938 / PR objectstack-ai#942 and
  objectstack-ai#943 / PR objectstack-ai#953 already wrote to two other pages.
- **Activities** — no such group; the real one is **Activity**, and `crm_task`
  is not in it. Its two entries are My Tasks (:84) and All Tasks (:93), both
  under **My Work** — which the table never mentioned at all, though it is the
  group a rep uses every day.
- **Analytics** — no such group; the real one is **Insights**, and no entry
  anywhere is labelled *Dashboards* or *Reports*.
- **AI** — no such group anywhere in the repo, and no entry labelled *Copilot*
  or *Knowledge Bases*. `src/apps/` contains one file; neither name appears in
  it.
- **Approvals** — real, with exactly one child, labelled **Inbox** (:165).
  *Approval Requests* and *Action History* carry no metadata in this repo;
  objectstack-ai#943 / PR objectstack-ai#953 recorded the same two names on the revenue page.

So the table now lists the pinned Home entry and all seven groups with their
real children in source order, and every retired name is re-pointed rather than
deleted silently, per the objectstack-ai#870 / objectstack-ai#877 / objectstack-ai#885 / objectstack-ai#894 / objectstack-ai#913 / objectstack-ai#924 / objectstack-ai#932 / objectstack-ai#942
/ objectstack-ai#953 convention. Two further facts from source ride along: `group_marketing`,
`group_insights` and `group_approvals` declare no `expanded` key while Sales,
My Work, Activity and Service set `expanded: true`, and
`GroupNavItemSchema.expanded` defaults to `false`, so those three are collapsed
on load — the failure mode that makes a reader conclude something is absent.
And the zh pages name the labels a simplified-Chinese user actually sees
(待我审批, 知识库, 我的工作 — `src/translations/zh-CN.ts:1195-1219`), which is
also why the old English *Knowledge Base* read plausibly for so long.

Nothing checked any of it: `os validate` and `pnpm lint` walk authored metadata
and never open `content/docs`. `test/docs-quick-tour-navigation.test.ts` now
compares the table to `CrmApp.navigation` group-for-group and child-for-child in
all three locales, pins the bold-is-real / italic-is-phantom typography the
sibling pages already use, and pins the source facts the prose rests on.
Restoring the old table turns 15 of its 21 assertions red in the predicted
direction.

Product questions stay open on purpose (objectstack-ai#595 / objectstack-ai#596): whether Products or AI
deserve their own groups is a product decision, not a documentation one. Only
the current shape is recorded.

All three locales, same section; zh internal links carry no anchor. `src/`
untouched.

Fixes objectstack-ai#960

Claude-Session: https://claude.ai/code/session_01VHrPAGEgFDoHjphqYG4BMa

Co-authored-by: Claude <noreply@anthropic.com>
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

Projects

None yet

2 participants