Skip to content

docs(analytics): correct the campaign inventory the cube argument rests on - #1852

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-1847-analytics-tracker-docs
Sep 10, 2026
Merged

os-steve merged 1 commit into
mainfrom
claude/issue-1847-analytics-tracker-docs

Conversation

@claude

@claude claude Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Fixes #1847

Six files — analytics/cubes and analytics/reports, three locales each — still described the campaign member #597 removed, in the passage that argues why campaign analytics has no cube. Rewritten against what the metadata declares today: the member's response block is status · response_date · has_responded (campaign_member.object.ts:215 · :232 · :242), grouped under Response Tracking (:95), and the app records no open, click or bounce at all — the object's own docblock (:12-28) carries the platform measurement.

⭐ One of the dispatch's rulings did not survive measurement

The dispatch ruled: "The campaign half of the same sentence is correct and stays." The fields are correct and they stay. Their section is not, and it is inside the same sentence, not beside it:

…two formula fields — Response Rate % and ROI % — shown in the Performance section of the campaign record. (cubes.mdx:72)
…counters and two formula fields … all of which the campaign record shows in its Performance section. (reports.mdx:95)

Measured on main @ c423673: five of the fields those sentences place in Performance are grouped under Budget & ROI — budgeted_cost (campaign.object.ts:145), actual_cost (:152), expected_revenue (:159), actual_revenue (:182) and the roi formula (:244), against { key: 'budget', label: 'Budget & ROI' } at :40. The form agrees and says why: campaign.view.ts:157-160 puts roi beside the two manual-entry cost fields it divides by, "rather than at the bottom of Performance, where its dependency on two fields three sections away was invisible". Performance (campaign.object.ts:41, form at campaign.view.ts:163-175) holds the six counters and response_rate.

This is the same defect class PR #1850 corrected one page over (Costs → Budget & ROI on marketing/campaigns), and it is the same defect as the member half: a name that does not resolve. It is fixed here rather than filed, because it is one clause of the sentence this card exists to rewrite — not a widening into what else the page gets wrong.

The rest of the PM's re-measurement held exactly:

  • cubes.mdx:72 and reports.mdx:95 carry the four-stamp sentence verbatim, both Chinese siblings carry it with the English field names inside Chinese prose — confirmed on all six files.
  • ⭐ reports.mdx:98's paraphrase is not English-only, as the filing had it. All three locales carry it, at :98 in each, exactly as the dispatch re-measured.
  • cubes.* carries no equivalent paraphrase — re-checked independently: grep -niE "open|click" on cubes.mdx returns three hits, all of them about cases (the day a case was opened, reopened cases by agent, open/new/resolved counts), and the Simplified and Traditional siblings return zero hits for 打开 / 点击 / 開啟 / 點擊.

Per file — what was rewritten, and where each new claim comes from

analytics/cubes.mdx (+ .zh-Hans / .zh-Hant) — the Marketing — no dataset, no cube section, one paragraph

Was Now, and its source
"crm_campaign_member stamps First Opened, First Clicked, Response Date and Responded per member" "records one thing per member — the response: Status, Response Date and Has Responded, under Response Tracking" — campaign_member.object.ts:215 / :232 / :242, group response labelled Response Tracking at :95. Responded was not a field either: has_responded is the boolean, status the picklist
the money block "shown in the Performance section" two sections, named: counters + Response Rate % in Performance (campaign.object.ts:41, :190-237); costs, revenues and ROI % in Budget & ROI (:40, :145-182, :244)
"the Num Sent / Responses / Leads / Converted Leads / Opportunities / Won Opportunities counters" the labels as declared — Number Sent (:201), Number of Responses (:207), Number of Leads (:213), Converted Leads (:219), Opportunities Created (:225), Won Opportunities (:231)
— (added) "An open or a click is a different case: not an unaggregated figure but an unrecorded one — this app tracks no opens, clicks or bounces, and no First Opened or First Clicked stamp exists on a member for a cube to roll up." The page's whole argument is what exists but is not aggregated; without this line, removing the two names quietly leaves the reader believing the data exists somewhere. The phantom names stay named, in italics, per the typography convention #927 / PR #932 uses for a name a reader arrives with

analytics/reports.mdx (+ .zh-Hans / .zh-Hant) — 📣 Marketing reports, two sites

Site Was Now, and its source
:95 the same four-stamp sentence, and "all of which the campaign record shows in its Performance section (src/views/campaign.view.ts)" the member's response block as above; the two sections split, with the same campaign.view.ts citation now pointing at a true statement (:157-160 and :163-175)
:98 "Campaign Engagement — same reason, one layer down: the opens, clicks and responses are stamped on each member record, and nothing rolls them up" "whether and when a member responded is stamped on each member record, and nothing rolls it up. Opens and clicks are not one layer down but absent — the app records no open, click or bounce anywhere — so even with a dataset behind it, an engagement report would have only the response half to report." The bullet's job is to explain why a report cannot be built; "one layer down" was the wrong reason for two thirds of what it named

Why no callout here, unlike PR #1846 and PR #1850: neither page carries a single blockquote today (0 in all six files), and the negative claim belongs inside the inventory it corrects rather than beside it. Callout parity is therefore still 0/0/0 per page — see the guard table.

Chinese wording

zh-Hans takes its response vocabulary from the zh-CN pack — 响应 · 已响应 · 响应时间 (src/translations/zh-CN/objects.marketing.ts:110-123) — and its negative claim from the wording PR #1846 shipped on campaign-members.zh-Hans:59: 「本应用不跟踪打开、点击或退信」. zh-Hant mirrors it in each page's own Traditional conventions — 回應, not 響應 (the ruling on PR #1846), 開啟/點擊/退信 as campaign-members.zh-Hant:59 writes them, and 彙總 rather than 汇总/聚合 because that is the word both Traditional analytics pages already use for aggregation. Field and section names stay English on both Chinese pages — every other campaign field in these paragraphs is already written that way, and 時間戳 for stamp is the spelling cubes.zh-Hant:52 already uses.

Docs guards — every one that reads these six files, and why it stayed green

Guard What it reads here Why green
docs-conversion-rate-spelling.test.ts its CARRIERS ledger names both of these pages: 转化率 / 轉化率 must be PRESENT in each Chinese locale, and no Chinese page may spell it 转换率 the one occurrence per file is at cubes.zh-Han*:83 and reports.zh-Han*:52 — outside both edited paragraphs, and re-counted after the edit: still exactly 1 in each of the four files, 0 occurrences of the rejected spelling anywhere in content/docs/analytics/. Ablated below
docs-locale-callouts.test.ts blockquote-block parity for every English/translated pair 0/0/0 on cubes and 0/0/0 on reports, before and after — no callout added or removed. Ablated below
docs-analytics-vocabulary.test.ts its analytics/index rules read the landing page by name; its two tree-wide sweeps (no page draws a cubes/ directory; the ≥100-page vacuity guard) do read all six files no src/cubes/ path added — the paths these paragraphs name are src/datasets/campaign.dataset.ts and src/views/campaign.view.ts, both real; the landing page is untouched
docs-dashboard-tiles.test.ts its prose rule reads every .mdx under content/docs: a **Name** bold run followed by a tile word must be a real widget title none of the new bold runs (Status, Response Date, Has Responded, Budget & ROI, the six counters) is followed by tile / 磁贴 / 磁磚 — this page family names sections and fields, never tiles
docs-anchor-links.test.ts parses all 231 pages as MDX and resolves every anchored link no link and no heading added or moved; the two links in the edited sections (/docs/marketing/campaigns and its locale twins) are unanchored and unchanged
docs-drift.test.ts walks all pages for the large-deal CEL threshold these paragraphs state no threshold and no money figure
docs-metadata-counts.test.ts README.md + every .mdx, for count claims in 20 declared spellings the rewrite states no count in any guarded spelling — deliberately: "the six counters" is prose, not (\d+) datasets
docs-object-term-consistency.test.ts every zh page, for retired crm_case / crm_quote spellings campaign vocabulary is not in its ledger (the caveat PR #1846's review recorded), and no retired case/quote spelling is introduced
docs-role-hierarchy.test.ts, docs-retired-personas.test.ts, persona-copy.test.ts tree-wide sweeps for a retired role hierarchy and retired Copilot persona names neither vocabulary appears in this diff
docs-zh-hant-justification.test.ts every page, for the zh-Hant navigation-convention note and its retired reason the note's declared carriers are unchanged; the new Traditional prose states no convention and names no navigation
docs-setup-navigation-names.test.ts every bold **App → …** path in content/docs/** no arrow path added or removed
docs-runnable-samples.test.ts content/docs + src/docs for documented agent names and the action-example selection key untouched
pnpm hygiene (check-source-hygiene.mjs) its control-byte scan reads content/ clean; plus an independent sweep of the six files with grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]' — no hits
docs-src-tree-paths.test.ts, heading-label.test.ts, docs-object-coverage.test.ts, docs-view-rosters.test.ts read docs by list, and these six files are in none of those lists named here so the absence is a reading rather than an omission: PRODUCT_TREE_DOCS is six getting-started/customization pages, heading-label is scoped to the three dashboards pages, and neither ledger has an analytics row

Nothing would have caught this drift — the third page family in a row where that is true. ⛔ Per epic #1579's fence no guard is added here; this is the third data point for the question already with the maintainer from PR #1846's review, not a third attempt to answer it.

⭐ Ablations — two guards this PR did not break, proven able to catch it

Both were run on the committed tree (fa0efba), each mutation proven on disk by content count and blob hash before the run, each restored with git checkout HEAD -- PATH and the restoration proven by an empty git diff HEAD and a hash equal to the HEAD blob.

1. docs-conversion-rate-spelling.test.ts — the carrier leg. Replaced 「按人物画像的转化率」 with 「按人物画像的每线索成本」 on cubes.zh-Hans.mdx — the sentence stays, the metric leaves. On disk: 转化率 count 1 → 0, injected text count 1, blob ed7f4bc → 6ae12b3. Result: exit 1, × 'analytics/cubes' still states the metric in both scripts — AssertionError: expected '---\ntitle: 多维数据集…' to contain '转化率', 1 failed | 3 passed. Restored: blob back to ed7f4bc, git diff HEAD empty.

2. docs-locale-callouts.test.ts — the parity leg. Inserted one > **ABLATION CALLOUT — English only.** block into reports.mdx only, the exact mistake this PR would have made had it added a callout the way PR #1846 and PR #1850 did. On disk: blockquote lines 0 → 1, marker count 1, blob d29a8a5 → f9d0e25. Result: exit 1, × every translated page has the same number of callouts as its English page — content/docs/analytics/reports.zh-Hans.mdx: 0 callout(s), but content/docs/analytics/reports.mdx has 1, and the same for .zh-Hant. Restored: blob back to d29a8a5, git diff HEAD empty.

Verification

pnpm verify run stage by stage on the committed tree, every exit code captured before any pipe (cmd > log 2>&1; EXIT=$?):

stage exit evidence
pnpm validate 0 4 warning(s), 12 suggestion(s) — all pre-existing, none on crm_campaign or crm_campaign_member
pnpm typecheck 0
pnpm lint 0 4 warning(s), 12 suggestion(s)
pnpm lint:i18n-gate 0 ✓ i18n lint gate: 0 i18n/missing-* issues
pnpm hygiene 0 ✓ source hygiene clean
pnpm hygiene:tokens 0 ✓ source token ratchet clean — authored total ~138,265 of ~140,000, unmoved: the ratchet measures src/**/*.ts and this PR touches none
pnpm build 0 dist/objectstack.json (1916.9 KB)
pnpm test 0 165 files, 3455 passed | 1 skipped (3456)

Test-file / test counts are identical before and after: 165 / 3455 passed / 1 skipped measured on the pre-change tree at c423673, and the same on fa0efba. No test file added, removed or edited — ⛔ nothing in test/** is touched.

CI: this PR's paths put it on the Docs App workflow (content/docs/** filter) and on Link Check (**/*.mdx filter, which then scans the .md files — the changeset).

Scope

Six docs files plus the changeset, nothing else. ⛔ No metadata change — #597 removed the member's tracker columns because nothing could write them, and this PR does not re-add them. ⛔ Nothing in test/**. ⛔ No content/docs/releases/ edit. The two live siblings (src/dashboards/executive.dashboard.ts on #1849, content/docs/marketing/campaign-members.* on #1845) meet none of these files.

Observed, not fixed here

  • Both pages bold Campaign ROI and Campaign Engagement as report names in a section whose point is that neither report exists — the same bold/italic distinction docs-analytics-vocabulary.test.ts enforces on analytics/index (bold for names the app has, italics for names a reader arrives with). It is a page-wide convention on these two pages, shared with the contract-report bullets above them, so correcting it is a page-typography pass rather than a clause of the sentence this card names. Reported rather than swept.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DuzfS5chho38Yx1jxx9DEj


Generated by Claude Code

…against source

`analytics/cubes` and `analytics/reports` — all three locales of each — said
`crm_campaign_member` stamps First Opened, First Clicked, Response Date and
Responded per member. Two of those four were deleted in #597; the member's
response block is `status` / `response_date` / `has_responded`, under its
Response Tracking group. `analytics/reports` carried the claim a second time as
a paraphrase ("the opens, clicks and responses are stamped on each member
record"), in all three locales.

Both pages also placed the campaign's whole money block in the Performance
section. Five of the fields they name are grouped under Budget & ROI —
budgeted_cost, actual_cost, expected_revenue, actual_revenue and the roi
formula (campaign.object.ts:144-195, :243; campaign.view.ts:152-158).
Performance holds the six counters and response_rate.

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

vercel Bot commented Sep 10, 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 Sep 10, 2026 5:50am UTC

Request Review

@os-steve
os-steve added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit f29304c Sep 10, 2026
10 checks passed

Copy link
Copy Markdown
Collaborator

PM review → accepted, auto-merge armed.

CI 9/9 green on fa0efba; seven files (six docs + changeset).

⛔ You falsified one of my rulings, and you are right

My dispatch said, as a ruling not a hint: "The campaign half of the same sentence is correct and stays — the Performance section it names is a real field group." True of the fields, false of their section, exactly as you put it. I re-derived it before arming this:

budgeted_cost    :144  group: 'budget'
actual_cost      :151  group: 'budget'
expected_revenue :158  group: 'budget'
actual_revenue   :181  group: 'budget'
roi              :243  group: 'budget'
num_sent         :199  group: 'metrics'
response_rate    :236  group: 'metrics'

and campaign.view.ts:157-160 agrees — budget / Budget & ROI holds the five money fields, metrics / Performance holds the counters and response_rate. So both sentences filed the whole money block under the wrong section header, and my ruling would have preserved that error while fixing the one next to it.

⭐ And it is the same defect PR #1850 fixed one page over (Costs → Budget & ROI). Two pages, two different wrong names for the same section, neither of which a reader could find — which says the drift is in how these pages were written, not in one sentence.

Fixing it rather than filing it was the right call and I want the boundary stated so it is not read as licence: it is one clause of the sentence this card exists to rewrite. ⛔ It is not a precedent for correcting whatever else a page gets wrong once you are in it — which is precisely why #1853 went out as a card instead.

Using the fields' declared labels (Number Sent / Number of Responses / …) instead of the compressed Num … transcription is the same discipline one level down: the page now says what the record says.

The verification is the strongest of this docs series

  • 13 guards named with the reason each stayed green, plus four named as not reaching these files — ⭐ naming the guards that don't read the page turns an absence into a reading. That is the standing lesson applied without being told.
  • Two ablations on guards you did NOT break. The carrier leg is the one that matters: swapping 「按人物画像的转化率」 for a cost-per-lead phrasing drops the 转化率 count 1 → 0 and reds docs-conversion-rate-spelling with the right message. My dispatch warned that a rewrite could silently drop that spelling; you proved the guard would have caught you rather than asserting it could not happen.
  • 转化率 / 轉化率 re-counted after the edit — still exactly 1 in each of the four Chinese files, and 0 of 转换率. Both directions, which is what that guard actually pins.
  • Callout parity untouched at 0/0/0 because you put the negative claim inside the inventory it corrects rather than adding a callout. That is the better shape here: the correction lands where the false claim was.

The rewrite says something stronger than the card asked for

"An open or a click is not an unaggregated figure but an unrecorded one." ⭐ That is the sentence the two pages needed. My dispatch framed the risk as an inventory listing deleted fields; the real damage was that the surrounding argument — why campaign analytics has no cube — implied the data exists and merely is not rolled up. Sourcing the negative from the object docblock's own platform measurement (outbound-only email service, queued | sent | failed, no pixel, no click webhook, no bounce feed) makes it a fact rather than a denial.

#1853

Correctly filed rather than swept: a page-wide typography pass over six files is not a clause of this card's sentence, and the #927 / PR #932 convention (bold = names the app has, italics = names a reader arrives with) makes it a real inconsistency inside a page rather than a style preference. ⭐ Noting that docs-analytics-vocabulary.test.ts enforces that convention on analytics/index's nav block only is what makes it gradeable. It queues; ⛔ nobody should treat it as dispatched, and ⛔ no guard is proposed with it while epic #1579's fence stands.

Auto-merge armed (squash). #1847 closes on merge — third and last family of the #597 sweep.


Generated by Claude Code

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.

The #597 tracker stamps survive on analytics/cubes and analytics/reports too — six more files, a third surface family

2 participants