From ab5dfc7dea8cb2857a865e28d1885425526df9a3 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 1 Aug 2026 06:37:09 +0000 Subject: [PATCH] docs: ban Radix in agent instructions, add CLAUDE.md symlinks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agents keep reaching for `@radix-ui/themes` because the surrounding code does and nothing told them not to. Make the ban explicit everywhere an agent reads instructions. Root AGENTS.md gains a "UI Components" section: no `@radix-ui/*` import, ever; layout primitives (Box/Flex/Grid/Section/Container) become `div`s with Tailwind; everything else maps to its `@posthog/quill` equivalent. Replace Radix in the code you touch rather than matching it. Two carve-outs are spelled out so the rule isn't over-applied — the Radix CSS variables (`--gray-*`, `--radius-*`) stay, and the app/test `` root stays pending a separate migration. Also reinforced in the feature guides (canvas, browser-tabs, inbox), in docs/conventions.md — whose canonical component example was itself teaching `` — and in the storybook-stories skill. Feature guides now follow the root convention: content in AGENTS.md with a CLAUDE.md symlink beside it, so Claude picks them up. Adds the missing symlinks for canvas and browser-tabs, and flips inbox (CLAUDE.md became AGENTS.md + symlink). Docs only — no source or lint-config changes. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DEM8aVRU7GGbWjMssYYoTf --- .claude/skills/storybook-stories/SKILL.md | 5 +- AGENTS.md | 70 +++++++- docs/conventions.md | 8 +- .../ui/src/features/browser-tabs/AGENTS.md | 9 + .../ui/src/features/browser-tabs/CLAUDE.md | 1 + packages/ui/src/features/canvas/AGENTS.md | 13 +- packages/ui/src/features/canvas/CLAUDE.md | 1 + packages/ui/src/features/inbox/AGENTS.md | 155 ++++++++++++++++++ packages/ui/src/features/inbox/CLAUDE.md | 153 +---------------- 9 files changed, 252 insertions(+), 163 deletions(-) create mode 120000 packages/ui/src/features/browser-tabs/CLAUDE.md create mode 120000 packages/ui/src/features/canvas/CLAUDE.md create mode 100644 packages/ui/src/features/inbox/AGENTS.md mode change 100644 => 120000 packages/ui/src/features/inbox/CLAUDE.md diff --git a/.claude/skills/storybook-stories/SKILL.md b/.claude/skills/storybook-stories/SKILL.md index 9214c9db94..7cd6d2bd18 100644 --- a/.claude/skills/storybook-stories/SKILL.md +++ b/.claude/skills/storybook-stories/SKILL.md @@ -23,7 +23,10 @@ should **not** add its own providers or ``: `ServiceProvider`, and a minimal TanStack Router. So `useHostTRPC()`, `useService()`, `useRouterState()`, etc. render instead of throwing "must be used within a Provider". -- A `` (Radix) bound to the dark/light toolbar global. +- A `` (Radix) bound to the dark/light toolbar global. This root provider is + the one sanctioned Radix usage — it supplies the CSS tokens. Radix *components* + are banned: build stories and components from `@posthog/quill` plus `div` + + Tailwind (see [UI Components](../../../AGENTS.md#ui-components)). Add a per-story decorator only to constrain layout (e.g. wrap in a `maxWidth` div so a full-width component sizes realistically). diff --git a/AGENTS.md b/AGENTS.md index 9ec5ba9418..950c6be100 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,8 @@ `AGENTS.md` is the source of truth for architecture and development rules. `CLAUDE.md` is a symlink to this file. Edit this file only. +Feature-level guides follow the same convention: the content lives in `/AGENTS.md` with a `/CLAUDE.md` symlink beside it. When you add a new guide, add the symlink too (`ln -s AGENTS.md CLAUDE.md`). + ## Architecture PostHog uses a layered architecture. Business logic and UI live in shared `packages/*`. Each `apps/*` host boots those packages and binds host-specific implementations. `@posthog/core` and `@posthog/ui` must run unchanged on desktop, web, and mobile. @@ -53,10 +55,12 @@ Hosts: 10. Boot side effects are `Contribution`s bound in feature modules and started by `boot()`. 11. tRPC routers are one-line forwards over services. No inline business logic. 12. Use Inversify with `@inversifyjs/strongly-typed`. Define each token as a standalone `export const TOKEN = Symbol.for("posthog..")` beside its `interface`/service — never an object-literal token bag (`TOKENS = { X: Symbol.for(...) }`), because object properties are not `unique symbol` and cannot key a binding map. Every composition root declares a `BindingMap` interface (token → bound type) and constructs `new TypedContainer()`, so a mistyped bind or a resolve of an unbound token fails at compile time. Bind in the feature module. Do not use `@provide` or `*Port` naming. -13. Use `@posthog/quill` for rendering-layer primitives when available. Routing is TanStack Router contributed per feature. +13. Use `@posthog/quill` for rendering-layer primitives. Never import Radix — see [UI Components](#ui-components). Routing is TanStack Router contributed per feature. Hard boundary: `@posthog/core` and `@posthog/ui` never import host transports. No `trpcClient`, `electron`, or `node:*`. +Hard boundary: no new `@radix-ui/*` imports anywhere in the repo. + ## Import Direction Enforced by Biome `noRestrictedImports`. @@ -99,6 +103,8 @@ For each new file or meaningful change: ## Forbidden Patterns +- Any new `@radix-ui/*` import (`@radix-ui/themes`, `@radix-ui/react-*`). Use `@posthog/quill` plus `div` + Tailwind. +- Radix layout primitives (`Box`, `Flex`, `Grid`, `Section`, `Container`) in code you touch; replace them with `div`s. - Business logic in store actions. - Domain stores in `@posthog/ui`. - `trpcClient` imports in `@posthog/core` or `@posthog/ui`. @@ -222,6 +228,66 @@ All merges into `main` go through the Trunk merge queue. Never run `gh pr merge` - Queue progress is the `Trunk Merge Queue (main)` check run on the PR's head commit. On failure the Trunk bot comments with links to the failing workflows; fix, push, and re-enqueue. - Never force-push a branch while it is in the queue -- it removes the PR from the queue. +## UI Components + +**Radix is banned. Do not add a `@radix-ui/*` import to any file, ever.** That covers +`@radix-ui/themes` and every `@radix-ui/react-*` package. There is no "just this once" +exception for matching surrounding code: the ~500 files that still import Radix are +legacy awaiting migration, not a pattern to copy. If a file you are editing already +imports Radix, that is a reason to remove those imports, not to add more. + +Rendering primitives come from `@posthog/quill`. Layout comes from plain HTML elements +plus Tailwind. + +### Replace Radix as you go + +When you touch a component that imports Radix, migrate the parts you are working in. +Swap the whole file when the file is small or the change is broad; otherwise migrate at +least the elements your diff touches, and leave the file no more Radix-dependent than +you found it. Don't open a drive-by migration of an untouched file in an unrelated PR. + +Layout primitives become `div`s (or the right semantic element) with Tailwind classes: + +| Radix Themes | Replace with | +| --- | --- | +| `` | `
` | +| `` | `
` | +| `` | `
` | +| `` | `
` | +| `
`, `` | `
` / `
` + Tailwind | +| `` | `
` | +| `` | quill `Heading`, or `

`–`

` + Tailwind | +| ``, `
` | ``, `
` + Tailwind | +| `` | `className="sr-only"` | +| `` | TanStack Router `Link` (in quill, `render={}`) | + +Everything else has a quill equivalent — import it from `@posthog/quill`: + +| Radix Themes | quill | +| --- | --- | +| `Text` | `Text` | +| `Button`, `IconButton` | `Button` (`variant`/`size` props) | +| `TextField`, `TextArea` | `Input`, `Textarea`, `InputGroup*` | +| `Select`, `Checkbox`, `Switch`, `Slider`, `Progress` | same names | +| `SegmentedControl`, `RadioGroup` | `ToggleGroup` + `ToggleGroupItem`, or `Tabs` | +| `Badge`, `Avatar`, `Separator`, `Skeleton`, `Spinner`, `Kbd`, `Card`, `Table*`, `Tabs*` | same names | +| `Tooltip`, `Popover`, `Dialog`, `AlertDialog`, `DropdownMenu`, `ContextMenu` | same names, as `*Trigger` / `*Content` compounds | +| `Callout` | compose a `div` with Tailwind, or quill `Item*` | + +No quill equivalent and no plain-HTML answer? Build it from HTML + Tailwind in the +feature, or add it to quill (see [`.claude/skills/quill-code/SKILL.md`](./.claude/skills/quill-code/SKILL.md)). +Pulling in a Radix package is not the fallback. + +### The two carve-outs + +- **CSS variables are not components.** Tailwind classes like `text-(--gray-12)`, + `bg-(--gray-2)`, and `rounded-(--radius-2)` come from Radix's *CSS token* layer and + stay. Keep using them; do not "de-Radix" a stylesheet. +- **The `` root stays for now.** The app-level provider (`Providers.tsx`, + `.storybook/preview.tsx`) and the `` wrapper in existing tests supply those + tokens. Leave them alone — removing them is a separate migration. Do not add + `` to new files; new tests should not need it. + ## Code Style - Prefer local code over new dependencies for simple fixes. @@ -245,7 +311,7 @@ See [docs/conventions.md](./docs/conventions.md). ## Key Libraries -- React 19, Radix UI Themes, Tailwind CSS, `@posthog/quill` +- React 19, Tailwind CSS, `@posthog/quill` (the component library — Radix is banned in new code, see [UI Components](#ui-components); Radix Themes remains only as the CSS-token layer and legacy imports pending migration) - TanStack Query, TanStack Router - Zustand, InversifyJS (with `@inversifyjs/strongly-typed`), Zod - xterm.js, CodeMirror, Tiptap diff --git a/docs/conventions.md b/docs/conventions.md index d09774ce78..88f4ca2a1d 100644 --- a/docs/conventions.md +++ b/docs/conventions.md @@ -28,13 +28,15 @@ interface AgentMessageProps { export function AgentMessage({ content }: AgentMessageProps) { return ( - +
- +
); } ``` +Components come from `@posthog/quill`; layout is plain elements plus Tailwind. **Never import `@radix-ui/themes` or `@radix-ui/react-*`** — see [UI Components](../AGENTS.md#ui-components) for the full replacement mapping, including the layout primitives (`Box`, `Flex`, `Grid`) that become `div`s. + Keep render functions short. Extract named components for distinct UI concerns instead of long inline conditionals. Group component hooks by concern: @@ -134,7 +136,7 @@ Barrel files hide dependency edges, increase circular import risk, and make refa ## Styling -Use Tailwind first. The project uses Tailwind v4 with Radix CSS variables. +Use Tailwind first. The project uses Tailwind v4 with Radix CSS variables. Those *variables* are fine and stay; Radix *components* are banned (see [UI Components](../AGENTS.md#ui-components)). Examples: diff --git a/packages/ui/src/features/browser-tabs/AGENTS.md b/packages/ui/src/features/browser-tabs/AGENTS.md index 46e3681401..72d2aaff01 100644 --- a/packages/ui/src/features/browser-tabs/AGENTS.md +++ b/packages/ui/src/features/browser-tabs/AGENTS.md @@ -24,6 +24,15 @@ within it — because the identity differs only by `channelSection`. Dedup/identity keys on all four fields, so two channels' inboxes (or one channel's inbox vs artifacts) are distinct tabs. +## Components & styling + +Components come from `@posthog/quill`; layout is `div`s with Tailwind. **Radix is +banned** — do not add `@radix-ui/themes` or `@radix-ui/react-*` to any file here. See +[UI Components](../../../../../AGENTS.md#ui-components) in the root `AGENTS.md` for the +mapping. The tab strip's remaining `Flex`/`Box`/`Text` imports are legacy; when your +change lands in one of those files, convert what you touch (`` → `
`, `Text` → quill `Text`). + ## Where the logic lives The feature is deliberately split so the rules are portable and testable: diff --git a/packages/ui/src/features/browser-tabs/CLAUDE.md b/packages/ui/src/features/browser-tabs/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/packages/ui/src/features/browser-tabs/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/ui/src/features/canvas/AGENTS.md b/packages/ui/src/features/canvas/AGENTS.md index 897be5e307..0bc5e89dfe 100644 --- a/packages/ui/src/features/canvas/AGENTS.md +++ b/packages/ui/src/features/canvas/AGENTS.md @@ -7,13 +7,16 @@ The root `AGENTS.md` architecture rules still apply. ## Components & styling -- **Use `@posthog/quill`, not Radix.** New UI in this space pulls components from +- **Use `@posthog/quill`, never Radix.** All UI in this space pulls components from `@posthog/quill` (`Button`, `Dialog*`, `AlertDialog*`, `DropdownMenu*`, - `ContextMenu*`, `Tooltip*`, `Collapsible*`, …). Do **not** reach for - `@radix-ui/themes` or `@radix-ui/react-*`. Some older code here still imports + `ContextMenu*`, `Tooltip*`, `Collapsible*`, …). Adding a `@radix-ui/themes` or + `@radix-ui/react-*` import is banned — see [UI Components](../../../../../AGENTS.md#ui-components) + in the root `AGENTS.md` for the full mapping. Some older code here still imports `@radix-ui/themes` (`Box`, `Flex`, `Text`, `AlertDialog`) — that's legacy to be - migrated, not a pattern to copy. When you touch such code, prefer swapping to - the Quill equivalent. + migrated, not a pattern to copy. When you touch such code, replace it: layout + primitives (`Box`, `Flex`, `Grid`) become `div`s with Tailwind classes + (`` → `
`), + and everything else becomes its Quill equivalent. - **Don't restyle Quill internals.** Quill components are already themed — spacing, typography, and especially **color** are baked in. Do not add `text-gray-*` / `text-muted-foreground` / `font-*` or other color/typography diff --git a/packages/ui/src/features/canvas/CLAUDE.md b/packages/ui/src/features/canvas/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/packages/ui/src/features/canvas/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/packages/ui/src/features/inbox/AGENTS.md b/packages/ui/src/features/inbox/AGENTS.md new file mode 100644 index 0000000000..2a3a45a9ab --- /dev/null +++ b/packages/ui/src/features/inbox/AGENTS.md @@ -0,0 +1,155 @@ +# PostHog Self-driving Inbox + +The Inbox is the PostHog surface for **Self-driving**: agents that watch product signals, summarize what matters, and can ship pull requests. This document is an architecture map for agents working in this area. It explains where responsibilities live, which backend contracts are relied on, and what not to accidentally rebuild. + +## Product Model + +The renderer still talks to backend endpoints and TypeScript types with the legacy `signals` naming. User-facing copy should say **Self-driving**, **agent**, **report**, **run**, or **signal** depending on context. Do not rename backend paths or shared API fields unless the PostHog Cloud backend has changed too. + +The main objects are: + +- `SignalReport`: the unit shown in all Inbox tabs. +- Findings: the source observations that contributed to a report, fetched separately for detail screens. +- Artefacts: structured agent output attached to a report, such as priority, actionability, suggested reviewers, repo selection, and signals from research. +- Report tasks: links from a report to tasks created for research or implementation. + +## Information Architecture + +Inbox has four tabs and one reviewer-scope control: + +| Tab | Route | Membership | +| --- | --- | --- | +| Pull requests | `/code/inbox/pulls` | Reports with `implementation_pr_url` set | +| Reports | `/code/inbox/reports` | Reports without a PR and not currently running | +| Runs | `/code/inbox/runs` | Reports that are still in progress or waiting on input | +| Archive | `/code/inbox/dismissed` | Terminal reports: archived/suppressed (`status === "suppressed"`) and resolved-by-merged-PR (`status === "resolved"`) | + +Detail pages live under the same tab: `/code/inbox//$reportId`. + +The Archive tab (route `/code/inbox/dismissed`, user-facing label "Archive") is +the exception: it holds the two terminal, not-in-inbox states — `suppressed` +(user-archived) and `resolved` (implementation PR merged) — both excluded from +the main pipeline query, so the tab fetches them with a dedicated +`status=suppressed,resolved` query (`useInboxDismissedReports`). Its detail view +(`DismissedReportDetail`) is read-only — summary + evidence, no triage +affordances — and depends on the backend serving these reports on the +`retrieve`/`signals` read paths (PostHog/posthog#64019). Suppressed cards offer a +single Restore action; resolved cards are reference-only (terminal, no restore), +badged "Resolved". Restore uses `useInboxRestoreReport`, which +reuses the `state` action's `potential` ("reopen") transition — the only reopen +path the backend exposes. The reviewer scope control is hidden on this tab since +the archive list is not scoped, and the tab carries no count badge. The +Archive detail is **not** a tracked `InboxDetailTab` (no OPENED/CLOSED +engagement events), since its rank would be measured against the wrong list. + +The internal route segment, query key, and component/hook names keep the +`dismissed`/`suppressed` vocabulary (the backend status is `suppressed`); only +the user-facing copy uses "Archive"/"archived". + +Each `DismissedReportCard` shows why the report was suppressed (`dismissal_reason`, +labelled via `dismissalReasonLabel`, with `dismissal_note` as a tooltip). These +are denormalised onto the list `SignalReport` by the backend serializer — the +same artefact-lift pattern as `priority`/`actionability`/`already_addressed` — +so cards avoid an N+1 per-card artefact fetch. Unknown reason codes fall back to +the raw value; cards with no dismissal artefact simply omit the chip. + +Agent configuration is **not** an Inbox tab. It is the top-level Agents sidebar item at `/code/agents`. The legacy `/code/inbox/agents` route redirects there. + +Reviewer scope is a UI preference stored in `inboxReviewerScopeStore`. It filters the list between reports suggested for the current user and reports for someone else. It does not change tab membership; the tab predicates are independent. + +## Ownership Boundaries + +Keep the renderer thin: + +- Components render reports, route between tabs/details, and call hooks. +- Hooks wrap existing API clients and React Query. They should not orchestrate multi-step business workflows. +- Zustand stores hold UI preferences only: reviewer scope, filters, and selected report IDs used by task creation flows. +- Business decisions, report generation, task orchestration, and source configuration behavior belong in the PostHog Cloud backend or existing main-process services. + +Do not add frontend-only controls that imply a backend capability. If the UI exposes a new action, first identify the backend endpoint or task flow that makes it real. + +## Routes and Shell + +`InboxView` is the layout shell for `/code/inbox/*`. It owns the page header, tab bar, reviewer scope control, and nested route outlet. Route files live in `apps/code/src/renderer/routes/code/inbox/`. + +The tab components are intentionally simple: + +- `PullRequestsTab` partitions scoped reports with `isPullRequestReport`. +- `ReportsTab` partitions with `isReportTabReport`. +- `RunsTab` partitions with `isAgentRunReport`. +- `DismissedTab` (the "Archive" tab) lists its own `useInboxDismissedReports` query (matching `isDismissedReport`); read-only detail route, restore action per card. + +The detail components share the same shape: load the report, render a common header, then render tab-specific sections. Detail sections should explain the report in product terms, not expose backend object names. + +## Data Flow + +`useInboxAllReports` is the list source of truth. It reads UI scope/filter state, calls the paginated report list hook, returns filtered reports, and computes counts used by the tabs. Multiple tab bodies can call it because React Query dedupes the underlying request. + +Tab membership and counts live in `utils/reportMembership.ts`. Keep that file as the canonical place for report partitioning rules so the tab bodies, counts, and tests stay aligned. + +Detail screens layer additional data on top of the base report: + +- `useInboxReportById(reportId)` for the report record. +- `useInboxReportSignals(reportId)` for contributing signals. +- `useInboxReportArtefacts(reportId)` for structured outputs such as suggested reviewers and repo selection. +- `useReportTasks(reportId, status)` for linked research/implementation tasks. + +List cards should prefer fields already present in the list response. Fetching per-card secondary data is acceptable only for small, clearly bounded adornments; avoid new N+1 request patterns without a batching plan. + +## Backend Contracts + +The Inbox reads from PostHog Cloud's Self-driving backend, currently implemented in the legacy `products/signals/backend` Django app: + +- `GET /api/projects/{teamId}/signals/reports/`: paginated report list. Supports filters such as status, ordering, source product, suggested reviewers, and priority. +- `GET /api/projects/{teamId}/signals/reports/{id}/`: single report detail. +- `GET /api/projects/{teamId}/signals/reports/{id}/signals/`: contributing signals. +- `GET /api/projects/{teamId}/signals/reports/{id}/artefacts/`: structured report artefacts. +- `GET /api/projects/{teamId}/signals/reports/{id}/tasks/`: tasks linked to a report. + +The shared renderer type for the report is `SignalReport` in `apps/code/src/shared/types.ts`. If the backend serializer changes, update that type and the normalizers in `posthogClient.ts` together. + +Card headlines are derived client-side from `summary` by `utils/reportPresentation.ts`; there is no backend headline field. + +## Configuration Surface + +Agent setup lives in `features/agents/components/AgentsView.tsx`, which mounts `ConfigureAgentsSection`. This surface composes existing GitHub, Slack, source-toggle, and MCP configuration pieces. Keep setup copy outcome-focused: the user is asking Self-driving to figure out what matters, not choosing internal artefact types. + +Onboarding/setup should be task-backed when it starts work. Do not model it as a static checklist if the intended behavior is to launch an agent task. + +## UI Architecture + +The current UI is single-column, route-based, and card/list oriented. Do not reintroduce the old split-pane list/detail layout. + +Shared primitives exist to keep the surfaces consistent: + +- `InboxDetailPageHeader` for detail headers. +- `DetailSection` for content sections inside detail screens. +- `SignalsList` and the existing detail `SignalCard` for contributing signals. +- Badge and metadata helpers in `components/utils/` and `InboxMetaRow`. +- `SOURCE_PRODUCT_META` for source-product labels and icons. + +When adding or changing UI, reuse those primitives first. Avoid encoding one-off layout systems inside a tab component. + +Components come from `@posthog/quill`; layout is `div`s with Tailwind. Radix is banned — see [UI Components](../../../../../AGENTS.md#ui-components) in the root `AGENTS.md`. Inbox files that still import `@radix-ui/themes` are legacy: when you edit one, drop the Radix import rather than extending it (`Flex`/`Box` → `div` + Tailwind, `Text`/`Button`/`Badge`/`Tooltip` → the quill component of the same name). + +## Things to Avoid + +- Do not add any `@radix-ui/*` import. Use `@posthog/quill` plus `div` + Tailwind. +- Do not reuse the deleted legacy `ReportListRow`, `ReportDetailPane`, or old list/detail stores. +- Do not put page-level Inbox title or navigation into the global app header; `InboxView` owns the Inbox page chrome. +- Do not add a configure shortcut back into the Inbox header; Agents configuration is a sidebar destination. +- Scout (`signals_scout`) is a real Cloud source product. Keep it covered wherever source products surface: `INBOX_SOURCE_OPTIONS`, `SOURCE_PRODUCT_META`, and the scout-name display in `SignalCard`. +- Scout management UI (fleet configuration, run history) lives in `features/scouts/` and is backed by the PostHog Cloud scout endpoints (`/api/projects/{teamId}/signals/scout/`). Do not add scout controls that have no backing endpoint there. +- Do not put preview shims or mock report data in `apps/code/index.html`; the app shell should stay minimal. +- Do not call `electronTRPC` directly from Inbox code. Use the existing API client, React Query hooks, or tRPC client wrappers. +- Do not preserve compatibility with unshipped intermediate UI shapes on this branch. Replace them cleanly. + +## Testing + +Keep tests close to the pure logic: + +- `utils/reportMembership.test.ts` covers tab predicates, reviewer scope, routes, and counts. +- `utils/reportPresentation.test.ts` covers card headline derivation and related text shaping. +- Parser/display helpers such as conventional-commit title parsing and reviewer display should stay unit-tested. + +Use typecheck for route and hook integration. Browser screenshots are useful for design review, but preview fixtures/tooling should live outside the production `index.html` shell. diff --git a/packages/ui/src/features/inbox/CLAUDE.md b/packages/ui/src/features/inbox/CLAUDE.md deleted file mode 100644 index b8b8273d57..0000000000 --- a/packages/ui/src/features/inbox/CLAUDE.md +++ /dev/null @@ -1,152 +0,0 @@ -# PostHog Self-driving Inbox - -The Inbox is the PostHog surface for **Self-driving**: agents that watch product signals, summarize what matters, and can ship pull requests. This document is an architecture map for agents working in this area. It explains where responsibilities live, which backend contracts are relied on, and what not to accidentally rebuild. - -## Product Model - -The renderer still talks to backend endpoints and TypeScript types with the legacy `signals` naming. User-facing copy should say **Self-driving**, **agent**, **report**, **run**, or **signal** depending on context. Do not rename backend paths or shared API fields unless the PostHog Cloud backend has changed too. - -The main objects are: - -- `SignalReport`: the unit shown in all Inbox tabs. -- Findings: the source observations that contributed to a report, fetched separately for detail screens. -- Artefacts: structured agent output attached to a report, such as priority, actionability, suggested reviewers, repo selection, and signals from research. -- Report tasks: links from a report to tasks created for research or implementation. - -## Information Architecture - -Inbox has four tabs and one reviewer-scope control: - -| Tab | Route | Membership | -| --- | --- | --- | -| Pull requests | `/code/inbox/pulls` | Reports with `implementation_pr_url` set | -| Reports | `/code/inbox/reports` | Reports without a PR and not currently running | -| Runs | `/code/inbox/runs` | Reports that are still in progress or waiting on input | -| Archive | `/code/inbox/dismissed` | Terminal reports: archived/suppressed (`status === "suppressed"`) and resolved-by-merged-PR (`status === "resolved"`) | - -Detail pages live under the same tab: `/code/inbox//$reportId`. - -The Archive tab (route `/code/inbox/dismissed`, user-facing label "Archive") is -the exception: it holds the two terminal, not-in-inbox states — `suppressed` -(user-archived) and `resolved` (implementation PR merged) — both excluded from -the main pipeline query, so the tab fetches them with a dedicated -`status=suppressed,resolved` query (`useInboxDismissedReports`). Its detail view -(`DismissedReportDetail`) is read-only — summary + evidence, no triage -affordances — and depends on the backend serving these reports on the -`retrieve`/`signals` read paths (PostHog/posthog#64019). Suppressed cards offer a -single Restore action; resolved cards are reference-only (terminal, no restore), -badged "Resolved". Restore uses `useInboxRestoreReport`, which -reuses the `state` action's `potential` ("reopen") transition — the only reopen -path the backend exposes. The reviewer scope control is hidden on this tab since -the archive list is not scoped, and the tab carries no count badge. The -Archive detail is **not** a tracked `InboxDetailTab` (no OPENED/CLOSED -engagement events), since its rank would be measured against the wrong list. - -The internal route segment, query key, and component/hook names keep the -`dismissed`/`suppressed` vocabulary (the backend status is `suppressed`); only -the user-facing copy uses "Archive"/"archived". - -Each `DismissedReportCard` shows why the report was suppressed (`dismissal_reason`, -labelled via `dismissalReasonLabel`, with `dismissal_note` as a tooltip). These -are denormalised onto the list `SignalReport` by the backend serializer — the -same artefact-lift pattern as `priority`/`actionability`/`already_addressed` — -so cards avoid an N+1 per-card artefact fetch. Unknown reason codes fall back to -the raw value; cards with no dismissal artefact simply omit the chip. - -Agent configuration is **not** an Inbox tab. It is the top-level Agents sidebar item at `/code/agents`. The legacy `/code/inbox/agents` route redirects there. - -Reviewer scope is a UI preference stored in `inboxReviewerScopeStore`. It filters the list between reports suggested for the current user and reports for someone else. It does not change tab membership; the tab predicates are independent. - -## Ownership Boundaries - -Keep the renderer thin: - -- Components render reports, route between tabs/details, and call hooks. -- Hooks wrap existing API clients and React Query. They should not orchestrate multi-step business workflows. -- Zustand stores hold UI preferences only: reviewer scope, filters, and selected report IDs used by task creation flows. -- Business decisions, report generation, task orchestration, and source configuration behavior belong in the PostHog Cloud backend or existing main-process services. - -Do not add frontend-only controls that imply a backend capability. If the UI exposes a new action, first identify the backend endpoint or task flow that makes it real. - -## Routes and Shell - -`InboxView` is the layout shell for `/code/inbox/*`. It owns the page header, tab bar, reviewer scope control, and nested route outlet. Route files live in `apps/code/src/renderer/routes/code/inbox/`. - -The tab components are intentionally simple: - -- `PullRequestsTab` partitions scoped reports with `isPullRequestReport`. -- `ReportsTab` partitions with `isReportTabReport`. -- `RunsTab` partitions with `isAgentRunReport`. -- `DismissedTab` (the "Archive" tab) lists its own `useInboxDismissedReports` query (matching `isDismissedReport`); read-only detail route, restore action per card. - -The detail components share the same shape: load the report, render a common header, then render tab-specific sections. Detail sections should explain the report in product terms, not expose backend object names. - -## Data Flow - -`useInboxAllReports` is the list source of truth. It reads UI scope/filter state, calls the paginated report list hook, returns filtered reports, and computes counts used by the tabs. Multiple tab bodies can call it because React Query dedupes the underlying request. - -Tab membership and counts live in `utils/reportMembership.ts`. Keep that file as the canonical place for report partitioning rules so the tab bodies, counts, and tests stay aligned. - -Detail screens layer additional data on top of the base report: - -- `useInboxReportById(reportId)` for the report record. -- `useInboxReportSignals(reportId)` for contributing signals. -- `useInboxReportArtefacts(reportId)` for structured outputs such as suggested reviewers and repo selection. -- `useReportTasks(reportId, status)` for linked research/implementation tasks. - -List cards should prefer fields already present in the list response. Fetching per-card secondary data is acceptable only for small, clearly bounded adornments; avoid new N+1 request patterns without a batching plan. - -## Backend Contracts - -The Inbox reads from PostHog Cloud's Self-driving backend, currently implemented in the legacy `products/signals/backend` Django app: - -- `GET /api/projects/{teamId}/signals/reports/`: paginated report list. Supports filters such as status, ordering, source product, suggested reviewers, and priority. -- `GET /api/projects/{teamId}/signals/reports/{id}/`: single report detail. -- `GET /api/projects/{teamId}/signals/reports/{id}/signals/`: contributing signals. -- `GET /api/projects/{teamId}/signals/reports/{id}/artefacts/`: structured report artefacts. -- `GET /api/projects/{teamId}/signals/reports/{id}/tasks/`: tasks linked to a report. - -The shared renderer type for the report is `SignalReport` in `apps/code/src/shared/types.ts`. If the backend serializer changes, update that type and the normalizers in `posthogClient.ts` together. - -Card headlines are derived client-side from `summary` by `utils/reportPresentation.ts`; there is no backend headline field. - -## Configuration Surface - -Agent setup lives in `features/agents/components/AgentsView.tsx`, which mounts `ConfigureAgentsSection`. This surface composes existing GitHub, Slack, source-toggle, and MCP configuration pieces. Keep setup copy outcome-focused: the user is asking Self-driving to figure out what matters, not choosing internal artefact types. - -Onboarding/setup should be task-backed when it starts work. Do not model it as a static checklist if the intended behavior is to launch an agent task. - -## UI Architecture - -The current UI is single-column, route-based, and card/list oriented. Do not reintroduce the old split-pane list/detail layout. - -Shared primitives exist to keep the surfaces consistent: - -- `InboxDetailPageHeader` for detail headers. -- `DetailSection` for content sections inside detail screens. -- `SignalsList` and the existing detail `SignalCard` for contributing signals. -- Badge and metadata helpers in `components/utils/` and `InboxMetaRow`. -- `SOURCE_PRODUCT_META` for source-product labels and icons. - -When adding or changing UI, reuse those primitives first. Avoid encoding one-off layout systems inside a tab component. - -## Things to Avoid - -- Do not reuse the deleted legacy `ReportListRow`, `ReportDetailPane`, or old list/detail stores. -- Do not put page-level Inbox title or navigation into the global app header; `InboxView` owns the Inbox page chrome. -- Do not add a configure shortcut back into the Inbox header; Agents configuration is a sidebar destination. -- Scout (`signals_scout`) is a real Cloud source product. Keep it covered wherever source products surface: `INBOX_SOURCE_OPTIONS`, `SOURCE_PRODUCT_META`, and the scout-name display in `SignalCard`. -- Scout management UI (fleet configuration, run history) lives in `features/scouts/` and is backed by the PostHog Cloud scout endpoints (`/api/projects/{teamId}/signals/scout/`). Do not add scout controls that have no backing endpoint there. -- Do not put preview shims or mock report data in `apps/code/index.html`; the app shell should stay minimal. -- Do not call `electronTRPC` directly from Inbox code. Use the existing API client, React Query hooks, or tRPC client wrappers. -- Do not preserve compatibility with unshipped intermediate UI shapes on this branch. Replace them cleanly. - -## Testing - -Keep tests close to the pure logic: - -- `utils/reportMembership.test.ts` covers tab predicates, reviewer scope, routes, and counts. -- `utils/reportPresentation.test.ts` covers card headline derivation and related text shaping. -- Parser/display helpers such as conventional-commit title parsing and reviewer display should stay unit-tested. - -Use typecheck for route and hook integration. Browser screenshots are useful for design review, but preview fixtures/tooling should live outside the production `index.html` shell. diff --git a/packages/ui/src/features/inbox/CLAUDE.md b/packages/ui/src/features/inbox/CLAUDE.md new file mode 120000 index 0000000000..47dc3e3d86 --- /dev/null +++ b/packages/ui/src/features/inbox/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file