feat: [ENG-2621] serve analytics disclosure markdown via daemon + canonicalise privacy URL#752
Conversation
Replace the docs.byterover.dev placeholder with the canonical https://www.byterover.dev/services/privacy URL now that the public page has been published. Drops the stale "placeholder until M1.5" comment — the URL is no longer a placeholder.
…ender it in the webui Single source of truth for the disclosure copy lives in src/shared/assets/analytics-disclosure.md and was already consumed by the CLI consent prompt via loadAnalyticsDisclosureText(). The webui's Privacy panel had a separate hardcoded copy that drifted out of sync once the real PM/legal copy landed. - Add `analytics:getDisclosure` transport event + Zod response schema. - New AnalyticsDisclosureHandler wraps loadAnalyticsDisclosureText() and exposes it over the transport; wired into feature-handlers.ts alongside the other AnalyticsHandler family. - Webui `useGetAnalyticsDisclosure` query hook + render via the existing MarkdownView from the contexts feature so lists, code blocks, and links in the markdown render properly. - Delete the now-obsolete ANALYTICS_DISCLOSURE_SECTIONS / icon grid and the ANALYTICS_PRIVACY_URL duplicate; analytics-panel.tsx now consumes PRIVACY_POLICY_URL directly from src/shared/constants/privacy.ts. - Drop the constants test file along with the hardcoded data it covered.
|
Claude finished @ncnthien's task in 2m 52s —— View job Review summaryOverall score: 4 / 5 — Good Outside-In design (consumer-driven transport event, shared loader, sensible DI, solid unit tests). One blocking inconsistency between this PR's two commits, plus a handful of small follow-ups. Inline comments posted on the relevant lines. Findings🔴 blocking — privacy URLs disagree inside the same panel
The CLI 🟡 suggestion — 🟡 nitpick — PR description claim doesn't match code. Test plan says "Refocus the tab → disclosure refreshes (TanStack Query default + override on the globalConfig query)", but 🟡 nitpick — What's good
|
- Derive the visible privacy link label from PRIVACY_POLICY_URL so the URL is the single source of truth — the label can no longer drift on a future URL change. - Mark the disclosure query as staleTime: Infinity. The markdown is a bundled asset that never changes at runtime, so refetching on focus is pointless; making the cache-forever semantic explicit avoids confusion. - Guard against an empty disclosure body in AnalyticsDisclosureHandler: throw a visible error instead of returning an empty string the webui would silently render as a blank panel. Test added for the empty case.
|
Claude finished @ncnthien's task in 1m 43s —— View job Claude finished review of
Review summaryOverall score: 3 / 5 — The three non-blocking nits from my prior pass are cleanly fixed and well-tested. However, the 🔴 blocking finding from that same review was not addressed, so the PR's stated goal ("single source of truth for the privacy URL") is still not met on Findings🔴 blocking — same as last round: URL drift inside the rendered panel is still live
The same drift is visible from the CLI consent prompt ( 🟢 resolved — visible label now derives from the constant
🟢 resolved — disclosure query semantics made explicit
🟢 resolved — empty-body guard added with test
🟡 nit —
|
…on grid layout
Reviewer rejected dumping the full markdown body into the Privacy panel —
the original M1.6 icon-grid layout should be preserved. Move the markdown
-> sections conversion daemon-side so the webui still gets structured data
without owning a copy of the section labels.
- New parseAnalyticsDisclosure() helper splits the markdown on ## H2
headings into {label, body}[]. Intro paragraphs above the first H2 and
any H3+ nested headings stay inside their parent section verbatim.
- Updated AnalyticsDisclosureResponseSchema to return sections instead of
the raw markdown string. Handler throws if the markdown has zero H2s
(single-source-of-truth invariant still surfaced as an error toast).
- DisclosureDetails reverts to the 2-column icon grid (Database / Eye /
Server / Link2 / PowerOff mapped by section index), filters out the
"Privacy policy" section (already shown as the footer link), and uses
the parsed label + body. Labels render uppercased to match the design.
- Hook + handler tests updated for the new shape; new parser test covers
multi-paragraph bodies, code blocks, no-H2 input, and nested headings.
|
Claude finished @ncnthien's task in 4m 6s —— View job Review summary — synchronize on
|
…icon grid The previous commit rendered each section's body as a plain <p>, so real markdown tokens (**bold**, inline `code`, fenced code blocks, bullet lists) leaked into the UI as raw text — see the screenshot in the PR review. Swap the <p> for the existing MarkdownView (already used by the contexts feature) with a small wrapper class that keeps the cell text styling (muted, 0.8125rem, leading-relaxed). Bold / inline code / lists / fenced code now render correctly; cell heights are still variable but the content reads cleanly.
|
Claude finished @ncnthien's task in 2m 43s —— View job Review summary — synchronize on
|
…tring - The disclosure markdown is a bundled asset that never changes at runtime, but the handler re-loaded and re-parsed it on every analytics:getDisclosure request. Lazy-cache the parsed sections on first success so subsequent requests are a single field read. - Drop the multi-line WHAT docstring on the handler class per the zero-comments codebase convention. Single-source-of-truth context is already covered by the load + parse pipeline reading the markdown asset. - New test pins the caching contract: three back-to-back requests trigger exactly one load. Existing error-propagation tests still cover the load-failure path (errors don't poison the cache).
|
Claude finished @ncnthien's task in 2m 11s —— View job Review summary — synchronize on
|
- Switch icon-grid mapping from positional (SECTION_ICONS[index]) to a label-keyed Record with an Info fallback. PM/legal can now reorder or insert sections in the markdown without silently shifting every following icon to the wrong section. - Use a substring "privacy" match for the icon-grid filter instead of an exact-string compare. Survives renames to "Privacy notice", "Privacy Policy & Terms", etc. so the section never accidentally leaks into the grid next to the footer link. - Parser now drops sections with an empty body so the schema's body.min(1) matches the runtime contract — a stray "## Foo\n\n## Bar" can no longer render a blank labelled card. - Tag the analytics-disclosure.md fenced code block with `bash` so the CodeBlock header is no longer empty in the webui (and the CLI prompt gets the language label too).
|
Claude finished @ncnthien's task in 2m 33s —— View job Review summary — synchronize on
|
Summary
Two related changes that finish the wiring between `src/shared/assets/analytics-disclosure.md` and the local web UI's Privacy tab.
1. Privacy URL canonicalised (`e8de448a`)
`PRIVACY_POLICY_URL` in `src/shared/constants/privacy.ts` now points at the published `https://www.byterover.dev/services/privacy\` page. Drops the stale "M1.5 placeholder" comment.
2. Daemon-served disclosure markdown (`94b1cae7`)
The webui Privacy panel previously rendered a hardcoded 2-column icon grid of Lorem-ipsum bodies, which drifted out of sync once the real PM/legal copy landed in the markdown file (commit `125b5c44`).
Single source of truth is now the `.md` file — PM/legal edits propagate to both the CLI prompt and the webui Privacy panel with no code changes.
Test plan