Repository navigation
[finding] analytics/reports and analytics/cubes bold seven report names and a metric list that do not exist #1853
Description
Observed while fixing #1847 (PR #1852), which rewrote one paragraph and one bullet on analytics/cubes and analytics/reports. ⛔ Out of that card's scope — it is a page-wide typography pass, not a clause of the sentence that card names — so it is filed rather than swept there.
The convention
#927 / PR #932 established, and test/docs-analytics-vocabulary.test.ts states in its header: bold is reserved for names the app really has, italics for a name a reader arrives with that the product does not carry. A phantom must still be named — say where the thing really lives, do not delete it silently — and must never be bolded.
The guard enforces it on content/docs/analytics/index.*, and only inside that page's ## Where to find things block (deliberately scoped: Dashboards / Reports / Cubes are phantoms as navigation items but real as page names). Nothing enforces it elsewhere.
The measurement
content/docs/analytics/reports.mdx and both its locale siblings bold report names that resolve to nothing in src/reports/. The section headings around them are explicit that the reports do not exist — "The three contract reports this section used to list are published nowhere", "src/reports/ publishes no marketing report" — so the prose and the typography disagree with each other:
:52Lead Source ROI:71Reopened Cases:87-89Contracts Expiring, Active Contracts by Product, Renewal Pipeline:97-98Campaign ROI, Campaign Engagement
None of the seven is a label in src/reports/ (the barrel's labels are Accounts by Industry and Type, At-Risk Accounts, CSM-Flagged Accounts, Cases Opened by Priority × Day, Cases by Status and Priority, Customer Churn Signals, Lead Engagement by Month × Source, Opportunities by Stage, Opportunity Funnel by Owner → Stage, Pipeline Coverage by Forecast × Quarter, Recently Lost Opportunities, SLA Performance Report, Silent High-Value Accounts, Won Opportunities by Owner).
content/docs/analytics/cubes.mdx and its siblings have the same shape one level down. The Name by name list under the marketing section (:74-80) bolds metric names that are questions a reader arrives with rather than anything the app declares: Members enrolled / responded, conversion count, Sourced and influenced revenue, Campaign spend, cost per lead, cost per opportunity, Persona (job role). One bullet mixes the two weights in a single line, bolding ROI % — a real formula field — beside three spend figures no field carries. The page is right to name all of them; the weight is what says they exist.
Why it is worth a card rather than a drive-by fix
Both pages already use italics correctly for phantoms elsewhere (cubes.mdx:56 writes "reopened cases by agent" in italics, reports.mdx:98 writes Conversion by campaign), so this is an inconsistency inside a page rather than a page with no convention. Fixing it means re-reading every bold run on six files against src/reports/ and src/datasets/, which is a measurable pass of its own — and the same question ("should a guard read this?") that PR #1846's review already put to the maintainer applies to it, since a bold/italic rule over the analytics family is exactly the mechanical relation docs-analytics-vocabulary.test.ts already implements for one block of one page.
⛔ Per epic #1579's fence, no guard is proposed here — this is the third data point for the maintainer question already open from PR #1846's review, and the card is for the content pass.
Refs: #1847 · PR #1852 (where this was observed) · #927 / PR #932 (the convention) · PR #1846's review (the guard question).
Generated by Claude Code
Activity
os-steve commentedon Sep 10, 2026
Claim:session_01DuzfS5chho38Yx1jxx9DEj→ branchclaude/issue-1853-analytics-bold-italic-passDispatched by the hotcrm PM seat (#1353). The dev inherits this claim — ⛔ no second
Claim:, ⛔ never touch the assignee.Base off
mainfetched fresh (PR #1852 has landed on these same two pages; your pass runs over its output).Model: Opus. Deliverable: one PR, ready for review,
pnpm verifygreen. ⛔ Do not arm auto-merge, ⛔ do not merge.PM verification — the measurement holds exactly
src/reports/publishes 14 reports, and their labels are:Each of the seven bolded names returns 0 hits under
src/reports/— checked one by one, not as a group:And the convention is stated by the guard itself (
test/docs-analytics-vocabulary.test.ts:47-50): "bold is reserved for names the app really has, italic for a name a reader arrives with that the product does not carry … a phantom must still be named — say where the thing really lives, do not delete it silently — and must never be bolded."⭐ Why this card is worth more than "typography"
The prose on these pages is already honest — the headings say outright that the reports are published nowhere. It is the weight that lies. A reader skimming bold runs comes away with a list of reports to go find, and the sentence that would have corrected them is the one they skipped. ⇒ this is the same false-capability class as the four false-automation claims found on the marketing pages this shift, expressed in formatting instead of verbs.
⛔ Which is exactly why the fix is re-weighting, never deleting. The convention requires the phantom still be named and its real home stated.
Scope and method
Six files:
content/docs/analytics/{reports,cubes}.{mdx,zh-Hans.mdx,zh-Hant.mdx}.src/reports/andsrc/datasets/— ⛔ do not fix only the sites this card lists. The card's own line numbers are already stale (PR docs(analytics): correct the campaign inventory the cube argument rests on #1852 landed on both pages after it was filed); ⭐ re-derive them.cubes.mdx:74-80's Name by name list needs judgement per bullet, not a sweep: one bullet mixes both weights on one line, bolding ROI % — a real formula field — beside three spend figures no field carries. Split the weights within the line rather than picking one for the whole bullet.cubes.mdx:56"reopened cases by agent",reports.mdx:98Conversion by campaign) — ⭐ match the page's own existing correct usage rather than importing a style.os lint --strictfirst, then retire the local re-implementations by family #1579's fence stands, and the "should a guard read this?" question is already with the maintainer from PR docs(marketing): rewrite campaign-members against the record #597 left behind #1846's review — this card is its third data point and the content pass, not an answer to it.Proof obligation
src/reports//src/datasets/, how many do not. That number is the deliverable's evidence — "I re-read them" is not measurable, "17 bold runs, 7 unresolved before, 0 after" is.docs-analytics-vocabulary.test.tsenforces this very convention — but only insideanalytics/index's## Where to find thingsblock. Say explicitly whether your edits reach that block (they should not) and show the command.docs-conversion-rate-spelling.test.tsrequires 转化率 / 轉化率 to be present on both Chinese siblings of both pages — exactly 1 occurrence each today. ⛔ Do not let an emphasis edit drop the sentence carrying it.node:fsto a facade recording every read of the page, attributed per test file. ⛔ Keep it a probe — that alias breaks 7 unrelated fs-tooling suites.pnpm verifystage by stage, every exit code captured before any pipe. Test-file / test counts before and after.content/docs/marketing/campaign-members*+campaigns*(#1854) andsrc/actions/**comments (#1184 phase 3). Neither meets your files.⛔ No metadata change. ⛔ Nothing in
test/**. ⛔ Nocontent/docs/releases/edit.Generated by Claude Code
os-steve commentedon Sep 10, 2026
os-dev-report
{ "issue": 1853, "status": "done", "branch": "claude/issue-1853-analytics-bold-italic-pass", "pr": "https://github.com/objectstack-ai/hotcrm/pull/1861", "premise_still_valid": true, "summary": "The premise held and was an undercount: re-reading every bold run on all six pages against a declared-label index built from src/ (2936 label/title/heading string literals) found 20 unresolved bolded name claims per reports locale and 9 per cubes locale — 87 across the six pages, against the seven the card listed. All 87 are now italic at the same site in en / zh-Hans / zh-Hant, nothing deleted, every phantom still naming where the real thing lives. Three sites took judgement rather than a sweep: the cubes marketing bullet now carries three italic spend figures beside the real bold ROI % on one line; Persona (job role) is italic while the Title field it points at stays bold; and the bullet calling `period` a real field on crm_campaign was corrected rather than re-marked, because the campaign carries Start Date and End Date and no Period field — that is the one prose change in the diff, the other 84 edits being pure weight flips. No guard proposed (epic #1579's fence), nothing under src/ or test/, no metadata change, no releases/ edit; the PR carries both the dispatch's verbatim footer and the platform-appended session footer.", "tests": "pnpm verify stage by stage, every exit code captured before any pipe, heavy stages through the container's shared verify lock: validate 0, typecheck 0, lint 0, lint:i18n-gate 0, hygiene 0, hygiene:tokens 0, build 0 (Artifact: dist/objectstack.json 1918.4 KB), test 0. Test-file / test counts identical before and after — 'Test Files 165 passed (165) / Tests 3455 passed | 1 skipped (3456)' on both runs, as expected for a content pass. GUARD READ-SET MEASURED, NOT REASONED: a scratchpad-only probe aliasing node:fs to a generated facade that re-exports all 106 of the module's names and wraps the read entry points, attributing each read of content/docs/analytics/ by call stack (which catches module-top-level reads that expect.getState().testPath misses). It found 14 guards reading the six pages where reasoning found 4 — docs-analytics-vocabulary, docs-anchor-links, docs-conversion-rate-spelling, docs-dashboard-tiles, docs-drift, docs-locale-callouts, docs-metadata-counts, docs-object-term-consistency, docs-retired-personas, docs-role-hierarchy, docs-runnable-samples, docs-setup-navigation-names, docs-view-rosters, docs-zh-hant-justification (heading-label reads the analytics dir but only dashboards/index). Because the facade re-exports the complete surface rather than a subset, it broke nothing: the probe run was byte-identical to the unprobed baseline (165 files / 3455 tests), unlike PR #1855's, which broke 7 fs-tooling suites. Not committed. VOCABULARY-GUARD SCOPE: it enforces this convention only inside analytics/index's '## Where to find things' block, and the diff does not reach it — `git diff --name-only origin/main...HEAD | grep -c 'analytics/index'` prints 0 over the seven-file change set. The three dots are load-bearing: #1854 and #1184 phase 3 landed on main after this branch was cut, so the two-dot form credits 16 of their files to this PR. CONVERSION-RATE SPELLING: 转化率/轉化率 counted per file before -> after, 1 -> 1 on all four Chinese siblings. PARITY: all six pages move identically, reports 130 -> 110 bold runs and cubes 83 -> 76, every planned site verified italic on disk with none left bold. Control-byte self-scan over the changed files (grep -naP over the C0 set plus DEL): no hits.", "mcp_calls": "6 — issue_read get, issue_read get_comments, pull_request_read get_reviews (PR #1855, returned empty so the probe was rebuilt from scratch), search_issues (dedupe for the finding below; REST /search/issues answered 403 in this container, so that one read switched channel to MCP as the fallback allows), issue_write create (#1862), add_issue_comment (this report). Card and PR bodies otherwise read via git and REST; PR #1861 was created over REST, not MCP.", "open_questions": [], "out_of_scope_findings": [ "filed as #1862: analytics/reports italicises seven names the app really does have (four published report labels at :120/:148/:149/:150, the chart titles at :21 and :29, the Stale Opportunities tab label at :30) and cubes italicises the two real position names at :40 — the same convention pointed the other way. Deliberately NOT swept into this PR: it may not be a defect at all, because the page arguably uses italics for a third purpose (quoting a title in running prose), and the card needs the maintainer to say whether the rule governs every occurrence of a name or only the one that introduces it." ] }Generated by Claude Code