feat: [ENG-2621] serve analytics disclosure markdown via daemon + canonicalise privacy URL#752
feat: [ENG-2621] serve analytics disclosure markdown via daemon + canonicalise privacy URL#752ncnthien wants to merge 2 commits into
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
|
| <a | ||
| className="text-foreground/80 hover:text-foreground inline-flex items-center gap-2 border-t px-5 py-3 text-sm transition-colors" | ||
| href={ANALYTICS_PRIVACY_URL} | ||
| href={PRIVACY_POLICY_URL} | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| <ExternalLink className="size-3.5 text-primary" /> | ||
| <span className="text-primary">docs.byterover.dev/privacy</span> | ||
| <span className="text-primary">byterover.dev/services/privacy</span> | ||
| </a> |
There was a problem hiding this comment.
suggestion (non-blocking): the visible label byterover.dev/services/privacy is hardcoded next to href={PRIVACY_POLICY_URL}. The whole point of e8de448 is that there is now one canonical URL constant — but the label here can still drift independently. Consider deriving the label from PRIVACY_POLICY_URL (e.g. PRIVACY_POLICY_URL.replace(/^https?:\/\/(www\.)?/, '')) so a future URL change updates both the link and the text in one place.
| export const getAnalyticsDisclosureQueryOptions = () => | ||
| queryOptions({ | ||
| queryFn: getAnalyticsDisclosure, | ||
| queryKey: ['analyticsDisclosure'], | ||
| }) |
There was a problem hiding this comment.
nitpick (non-blocking): the PR description claims the webui will refetch the disclosure on tab refocus ("TanStack Query default + override on the globalConfig query"), but the global queryConfig in src/webui/lib/react-query.ts has refetchOnWindowFocus: false, and this hook doesn't override it (unlike useGetGlobalConfig, which sets refetchOnWindowFocus: true). So in practice the disclosure won't auto-refresh on focus — which is actually fine since the markdown is bundled with the CLI binary, but the PR description's test-plan bullet is misleading. Consider either:
- Adding
staleTime: Infinityhere to make the bundled-asset semantics explicit (and avoid a re-request every time the component remounts), or - Just updating the PR description so it doesn't claim a refetch behavior the code doesn't implement.
| public setup(): void { | ||
| this.transport.onRequest<void, AnalyticsDisclosureResponse>(AnalyticsEvents.GET_DISCLOSURE, async () => { | ||
| const markdown = await this.loadDisclosure() | ||
| return {markdown} | ||
| }) | ||
| } |
There was a problem hiding this comment.
nitpick (non-blocking): AnalyticsDisclosureResponseSchema is declared in analytics-events.ts with markdown: z.string().min(1) but is only used here for type inference, never safeParse'd on the response. Sister handler AnalyticsListHandler validates inbound payloads with AnalyticsListRequestSchema.safeParse; this one has no inbound payload to validate. If loadDisclosure() ever returns "" (truncated read, bad bundle), the wire response will technically violate the .min(1) schema and the webui will silently render an empty <MarkdownView/>. Cheap defense: if (!markdown) throw new Error('disclosure missing') — keeps the failure mode visible (the existing error-state UI in DisclosureDetails) instead of "blank panel, no toast." Not blocking since the bundled asset is checked into git; flagging because the test on line 27-42 of the handler test already establishes the propagate-errors contract you'd be leaning 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