diff --git a/.claude/skills/finalize-inbox-sync/SKILL.md b/.claude/skills/finalize-inbox-sync/SKILL.md new file mode 100644 index 0000000000..85d20d45a9 --- /dev/null +++ b/.claude/skills/finalize-inbox-sync/SKILL.md @@ -0,0 +1,59 @@ +--- +name: finalize-inbox-sync +description: Fifth step of the inbox-to-cloud sync workflow. Runs typecheck and format, runs any inbox-scene tests, and produces the structured final report the user verifies. Assumes `/simplify` has already run on the touched files (orchestrated by the parent skill). Use as part of `/sync-inbox-to-cloud`, or standalone after a hand-rolled sync followed by a manual simplify pass. +--- + +# Finalize the inbox sync + +This is sub-skill 5 of `/sync-inbox-to-cloud`. Re-read the parent skill's hard rules at `/Users/twixes/Developer/code/.claude/skills/sync-inbox-to-cloud/SKILL.md` before starting. + +## Goal + +Verify the simplified sync compiles cleanly and emit the structured final report. + +By the time this sub-skill is invoked, `/implement-inbox-sync` and `/simplify` have already run. The code is integrated and simplified; you just need to validate and report. + +## Steps + +### 1. Typecheck and format + +Run from `~/Developer/posthog/`: + +```sh +pnpm --filter=@posthog/frontend typescript:check +pnpm --filter=@posthog/frontend format +``` + +**Typecheck and format are not optional.** If typecheck takes 5+ minutes, run it anyway — kick it off in the background (`run_in_background: true` on the Bash call) and keep building the report in parallel. Do not report `not run` for typecheck or format under Verification. "Not run" is not an acceptable verification outcome — the user will run them locally and get a wall of errors that you could have surfaced. + +If typecheck fails on a generated `*LogicType.ts`, the Kea type generator should regenerate it as part of the pipeline. If it still fails, follow `/Users/twixes/Developer/posthog/.claude/skills/writing-kea-logics/SKILL.md`. + +If `/simplify` proposed deletions that broke a wire-up, fix it here before reporting. + +**Do not** run the desktop typecheck (`pnpm typecheck` from the code repo) — you didn't touch that side. + +### 2. Run any tests for the inbox scene + +```sh +hogli test frontend/src/scenes/inbox +``` + +If tests fail because of behaviour changes in the port (e.g. the default `statusFilter` changed to match desktop), update the test expectations — desktop is the source of truth. + +### 3. Produce the final report + +This is the artifact the user verifies. Keep it skimmable — bullets, not paragraphs. + +- **Synced** — features ported / polished, one bullet each, citing desktop source file → cloud destination file. +- **Stubbed (Coming soon™)** — desktop features intentionally disabled on cloud. Should contain ONLY live-chat affordances (Discuss / chat-with-inbox). If more than 1-2 items, you stubbed too aggressively — go back and revisit. +- **Reused existing cloud surface** — desktop features whose run-log viewing re-uses `products/tasks/frontend/`. The linkage itself belongs under "Synced", not here. +- **Skipped (rare)** — features with no cloud analogue (e.g. OS-only Electron API). More than a couple items means you skipped too much. +- **Open questions** — missing backend endpoints, UX ambiguities, sub-skill ambiguities for the next iteration. The user uses this to refine the skills. +- **Verification** — typecheck pass/fail, format pass/fail, simplify outcome, tests pass/fail-or-N/A. Cite commands and any error excerpts if anything failed. +- **Files modified** — final list of touched cloud files, for the user's diff review. + +## Next step + +**Do not stop here.** The parent `/sync-inbox-to-cloud` is a single uninterrupted workflow. Immediately invoke `/reflect-on-inbox-sync` using the Skill tool — it will audit the run against the hard rules and append concrete skill-refinement suggestions to your report. Do not finalize the output as "done" until reflection has appended its section. + +Make the report accurate — if you skipped something for a reason not covered in the hard rules, say so under "Open questions" so the rules can be tightened next iteration. diff --git a/.claude/skills/implement-inbox-sync/SKILL.md b/.claude/skills/implement-inbox-sync/SKILL.md new file mode 100644 index 0000000000..78a28b8c62 --- /dev/null +++ b/.claude/skills/implement-inbox-sync/SKILL.md @@ -0,0 +1,71 @@ +--- +name: implement-inbox-sync +description: Third step of the inbox-to-cloud sync workflow. Executes the manifest from `/plan-inbox-sync`, dispatching parallel sub-agents per the slicing plan and then integrating their work in the central cloud scene and logic. Owns the desktop→cloud translation (Quill/Radix → LemonUI, Zustand/TanStack Query → Kea, TanStack Router → scenes/urls). Use as part of `/sync-inbox-to-cloud`, or standalone to execute a hand-rolled manifest. +--- + +# Implement the inbox sync + +This is sub-skill 3 of `/sync-inbox-to-cloud`. Re-read the parent skill's hard rules at `/Users/twixes/Developer/code/.claude/skills/sync-inbox-to-cloud/SKILL.md` before starting. + +## Goal + +Apply the manifest from `/plan-inbox-sync` to the cloud Inbox. Parallelize where the plan says so; integrate centrally. + +## Phases + +### Phase A — Dispatch parallel sub-agents (if planned) + +**Before dispatching sub-agents**, the orchestrator verifies cloud has every non-trivial dependency the desktop side uses. Grep `~/Developer/posthog/frontend/package.json` for each library that appears in desktop imports — typical candidates: `framer-motion`, `tiptap`, `xterm`, `codemirror`, `react-virtualized`. For any missing library, decide upfront how slices should handle it: + +- **Add the dep** to cloud's `package.json` (small, well-maintained libraries). +- **Substitute** with a cloud-available alternative — and brief sub-agents on the exact substitution (e.g. `framer-motion ` → `
`). +- **Skip the visual** and brief sub-agents to surface it as Open Question. + +The wrong move is to let each sub-agent guess. Put the decision in the sub-agent prompt so all slices substitute the same way. + +Read `/Users/twixes/Developer/code/.claude/skills/sync-inbox-to-cloud/references/parallelization.md` for the full pattern. Short version: + +- Dispatch up to 4 sub-agents in a single message (parallel Agent tool calls). +- Each sub-agent owns a disjoint file scope and creates new files only in their owned subdirectory. +- Sub-agents MUST NOT touch the central scene entry-point file(s) or the central composing logic. +- Each sub-agent's prompt inlines the relevant hard rules and the translation rules from `references/translation.md`. +- Each sub-agent's prompt names the files they own AND lists the files owned by other slices as "do not touch". + +**Slice prerequisites.** If one slice produces types, utils, or API wrappers consumed by other slices, treat it as a sequential prerequisite — dispatch it alone, wait for it to return, then dispatch the dependents in parallel. The naive "everything parallel" pattern works only when slices share no contracts; in practice a Foundation slice (types + API + badges + utils) almost always exists. Default order: Foundation → wait → {feature slices in parallel} → orchestrator integration. + +If the plan said don't parallelize (small re-sync, just-shifted IA, plan has only 1-2 cohesive areas), implement sequentially. The translation rules in `references/translation.md` apply either way. + +### Phase B — Integrate + +The orchestrator (you) owns the central integration files. After sub-agents return: + +- **Write/rewrite the central cloud scene entry-point file** to compose the new subcomponents from sub-agents. If desktop's IA has shifted (e.g. introduced new tabs), this is where the new top-level shape lands. +- **Write/rewrite the central composing Kea logic** to `connect` to the sibling logics, handle routing (`urlToAction` / `actionToUrl`), and own cross-slice scene state. +- **Add new TS wrappers in `frontend/src/lib/api.ts`** if any slice surfaced an existing backend endpoint without a wrapper. Verify the endpoint exists first (grep `products/signals/backend/views.py`). +- **Update `urls.ts` and `scenes.ts`** if desktop's IA introduced new routes (new tabs, new subroutes). +- **Update `types.ts`** with any new shared types. + +### Phase C — Quick smoke-check + +Before handing off to `/finalize-inbox-sync`, do a quick visual scan of your work: + +- Did any sub-agent return saying it had to touch a file outside its scope? If so, you have an integration mess — re-plan that slice or absorb the file into the orchestrator's scope. +- Did any sub-agent return saying it stubbed Coming soon™ on something that isn't live chat? Revert that — wire it properly. +- Did any sub-agent return saying it skipped work? Re-dispatch with stronger framing — there is no skip. + +## Translation + +See `references/translation.md` for the desktop→cloud mapping covering: + +- Component library (Radix Themes + Quill → LemonUI) +- Icons (Phosphor → `@posthog/icons`) +- State management (Zustand + TanStack Query → Kea) +- Routing (TanStack Router → scenes / urls / sceneTypes) +- API (already shared; just frontend wrappers in `lib/api.ts`) +- Persistence (Zustand `persist()` → Kea `{ persist: true }` per reducer) +- Hedgehogs / empty-state assets (`lib/components/hedgehogs`) +- Analytics events (`posthog.capture(...)` from `posthog-js`; mirror desktop event names verbatim) + +## Next step + +**Do not stop here.** The parent `/sync-inbox-to-cloud` is a single uninterrupted workflow. Immediately invoke `/simplify` using the Skill tool, passing it the list of cloud files you touched as scope. Do not summarize the work and wait, do not produce a report yet (that's step 5), do not pause — chain straight to the next step. diff --git a/.claude/skills/implement-inbox-sync/references/translation.md b/.claude/skills/implement-inbox-sync/references/translation.md new file mode 100644 index 0000000000..bf274554d9 --- /dev/null +++ b/.claude/skills/implement-inbox-sync/references/translation.md @@ -0,0 +1,91 @@ +# Desktop → cloud translation + +Reference for translating PostHog Code's desktop UI/state idioms into PostHog Cloud's idioms. Used by `/implement-inbox-sync` and inlined into parallel sub-agent prompts. + +## Component library + +Desktop uses `@radix-ui/themes`, `@posthog/quill`, and `@phosphor-icons/react`. Cloud uses `@posthog/lemon-ui`, `lib/lemon-ui/*`, and `@posthog/icons`. Map each desktop component to its closest LemonUI equivalent: + +- `Button` from `@posthog/quill` or `@radix-ui/themes` → `LemonButton` from `@posthog/lemon-ui` +- `IconButton` → `LemonButton` with `icon` prop, no children +- `Text`, `Box`, `Flex` from `@radix-ui/themes` → plain `div` / `span` + Tailwind classes (`flex`, `gap-2`, etc.); cloud does not use Radix Themes +- `Dialog` from `@radix-ui/themes` → `LemonModal` from `@posthog/lemon-ui` +- `Tabs` from `@radix-ui/themes` → `LemonTabs` from `@posthog/lemon-ui` +- `DropdownMenu` from `@radix-ui/themes` → `LemonMenu` from `@posthog/lemon-ui` +- `Checkbox` → `LemonCheckbox` +- `Switch` → `LemonSwitch` +- `Select` → `LemonSelect` (single) / `LemonInputSelect` (multi) +- `TextField`, `Input` → `LemonInput` +- `TextArea` → `LemonTextArea` +- `Badge` from `@radix-ui/themes` → `LemonTag` (colored labels) or `LemonBadge` (counts) +- `Tooltip` from `@radix-ui/themes` or `@posthog/quill` → `Tooltip` from `@posthog/lemon-ui` +- `ScrollArea` from `@radix-ui/themes` → plain `div` with overflow Tailwind, or `ScrollableShadows` from `lib/components/ScrollableShadows` +- `Skeleton` → `LemonSkeleton` +- `Banner` / inline alert → `LemonBanner` +- `Spinner` from `@radix-ui/themes` → `Spinner` from `@posthog/lemon-ui` +- `Link` (any) → `Link` from `@posthog/lemon-ui` +- `Markdown` / desktop's report-summary markdown component → `LemonMarkdown` from `lib/lemon-ui/LemonMarkdown` +- Profile pictures → `ProfilePicture` from `lib/lemon-ui/ProfilePicture/ProfilePicture` +- `Kbd` from `@posthog/quill` → `KeyboardShortcut` from `lib/components/KeyboardShortcut` if cloud has it, otherwise inline `` with Tailwind +- `cn` from `@posthog/quill` → `clsx` from `clsx` + +For icons, map `@phosphor-icons/react` icons to the closest equivalent in `@posthog/icons` (e.g. `EnvelopeSimpleIcon` → `IconLetter` or `IconNotification`). Do not bring Phosphor or Lucide into cloud. + +**Some icons live in `lib/lemon-ui/icons` rather than `@posthog/icons`.** Before deciding an icon doesn't exist, also grep `frontend/src/lib/lemon-ui/icons/icons.tsx`. Common cases of icons in `lib/lemon-ui/icons` (not `@posthog/icons`): `IconOpenInNew`, `IconLink`, `IconArrowDown`, `IconTag`, `IconChevronUp`, `IconKanban`, `IconTicket`. The import path matters — `import { IconOpenInNew } from 'lib/lemon-ui/icons'`, not `from '@posthog/icons'`. + +When a desktop component has no direct LemonUI equivalent (a specific animated loader, a custom badge variant, etc.), implement it in plain JSX + Tailwind in the cloud Inbox dir. **Do not add a third component library.** + +## Animations + +Desktop uses `framer-motion` for entry/exit animations, fan stacks, spring transitions. Cloud does NOT currently have `framer-motion` installed. Substitute as follows: + +- `` → `
` if cloud has a Tailwind `fade-in` keyframe; otherwise plain `
` with `style={{ transition: 'opacity 0.2s ease' }}`. +- `` → `
`. +- `` exit animations → handle via CSS `@keyframes` + a `transition-group`-style mount/unmount delay, or accept synchronous unmount if the exit visual is minor. +- Spring-physics fan stacks (e.g. desktop's MultiSelectStack) → port via CSS `transition: transform ... cubic-bezier(...)`. The visual will land 90% of the way without `spring` physics. +- Stagger entry (per-row `delay: i * 0.035`) → CSS `animation-delay` on a `@keyframes` rule, or accept synchronous appearance for list rows (typical at >20 rows the stagger barely shows anyway). + +If a desktop animation is core UX and CSS substitution would be jarring (e.g. a primary interaction surface), surface it under Open Questions in the final report and recommend adding `framer-motion` to cloud's `package.json` rather than dropping silently. + +**Replacing `` with a bare `
` and reporting "no functional impact" is a polish drop.** The parent skill's hard rules forbid this — at minimum keep CSS transitions; at maximum surface as Open Question. + +## State management + +Desktop uses Zustand stores and TanStack Query. Cloud uses Kea. Map each desktop pattern to its Kea equivalent: + +- Zustand `create(...)` store with state + actions → Kea logic with `actions` + `reducers` +- Zustand persisted store via `persist()` middleware → Kea reducer with the `{ persist: true }` option per-reducer. Pattern: `reducers({ statusFilter: [DESKTOP_DEFAULT, { persist: true }, { setStatusFilter: (_, { value }) => value }] })`. **If desktop persists a piece of state via Zustand `persist`, the cloud Kea reducer MUST also persist via `{ persist: true }`.** Default values mirror desktop verbatim — copy the literal default from the desktop store file. +- TanStack Query `useQuery` / `useInfiniteQuery` → Kea `loaders` builder. Pagination: a `loadMoreReports` action that appends; do not re-implement infinite scroll from scratch. +- TanStack Query refetch interval / polling → Kea `afterMount` + `cache.disposables.add(...)` registering a `setInterval`. Read the cloud `using-kea-disposables` skill first. +- `useMutation` → a Kea `actions` + `listeners` block that calls `api.signalReports.*`, dispatches success/failure actions, surfaces `lemonToast` for errors. +- tRPC client call from a component → a `listeners` call that hits the appropriate `api.*` REST endpoint in `~/Developer/posthog/frontend/src/lib/api.ts`. +- `useEffect` for subscriptions / window listeners → `afterMount` + `cache.disposables.add(...)`. Never write a bare `addEventListener` in a Kea logic without disposables. +- Cross-store `useOtherStore.getState()` → Kea `connect({ values: [otherLogic, [...]], actions: [otherLogic, [...]] })`. +- `useState` for UI-local toggles → `useState` is fine on cloud too — local component state stays local. +- Custom hook orchestrating multiple queries → a selector or loader on the logic that merges them. Do not write hooks that re-run multiple queries. + +The `useDiscussReport` and any chat-with-inbox affordance are agent-chat hooks — stub them Coming soon™. The "Create PR" / `useCreatePrReport` is a **task-kickoff** hook, not a chat hook — port it fully, wiring to cloud's `api.tasks.*` and reusing desktop's prompt-building utils where the prompt content matters. + +## Routing + +Desktop uses TanStack Router. Cloud uses scenes + urls + sceneTypes. Map each desktop pattern to its cloud equivalent: + +- Route registered in `apps/code/src/renderer/router.tsx` → already-registered cloud scene (today: `Scene.Inbox` in `frontend/src/scenes/sceneTypes.ts`, mapped in `frontend/src/scenes/scenes.ts`, URL in `frontend/src/scenes/urls.ts` as `urls.inbox(reportId?)`). If desktop introduces new top-level tabs, add the corresponding cloud routes here. +- `useSearch()` / route params → `urlToAction` / `actionToUrl` in the central inbox scene logic +- Deep-link selection (desktop's `useInboxDeepLink`, `useInboxDeepLinkListSync`) → `setSelectedReportId` action + `actionToUrl` round-trip on the cloud side. Verify deep links survive the polish you port; extend if needed. + +For modals and drawers (sources dialog, dismiss dialog, configure-agents drawer), they generally stay in-scene as `LemonModal` / `LemonDrawer`, no new URL — unless desktop deep-links to a specific configuration tab, in which case mirror that with a sub-route. + +## API + +Both sides talk to the same Django backend. On cloud, REST endpoints are wrapped in `frontend/src/lib/api.ts` under `api.signalReports.*` (list, retrieve, dismiss, snooze, etc.) and adjacent groupings (`api.tasks.*`, etc.). Read those wrappers — they tell you the exact request shape. If desktop reads a field cloud's serializer doesn't expose, that's a backend gap → surface it in the report, don't add a parallel API. If the endpoint exists but cloud lacks a TS wrapper, add the wrapper. + +## Empty / loading / setup states + +Desktop has rich onboarding (warming-up panes, select-something panes, gated panes, skeleton backdrops, setup panes) plus sources dialogs. Port the **logic** of each (when it shows, what it says, what action it offers) into cloud's equivalent. Don't copy desktop's exact JSX — cloud's layout, hedgehogs, and typography differ. + +Hedgehogs come from `lib/components/hedgehogs` (e.g. `GraphsHog`, `PopUpBinocularsHog`). Use these for cloud empty states; do not import desktop assets. + +## Analytics + +Desktop fires events via `@utils/analytics` with constants from `@shared/types/analytics`. Cloud fires events via `posthog.capture(...)` from `posthog-js` (or the `eventUsageLogic` pattern). **Mirror the event name and properties verbatim** — same `inbox viewed`, same property keys — so we can dashboard both surfaces together. If desktop has a constant, copy the literal string into cloud. diff --git a/.claude/skills/inspect-inbox-surfaces/SKILL.md b/.claude/skills/inspect-inbox-surfaces/SKILL.md new file mode 100644 index 0000000000..c2654b8280 --- /dev/null +++ b/.claude/skills/inspect-inbox-surfaces/SKILL.md @@ -0,0 +1,90 @@ +--- +name: inspect-inbox-surfaces +description: First step of the inbox-to-cloud sync workflow. Lists the PostHog Code desktop Inbox feature directory and the PostHog Cloud Inbox scene directory, reads each side's entry-point files, and produces a structured inventory the planning step consumes. Use as part of `/sync-inbox-to-cloud`, or standalone when you need a fresh map of both Inboxes. +--- + +# Inspect both Inbox surfaces + +This is sub-skill 1 of `/sync-inbox-to-cloud`. Re-read the parent skill's hard rules at `/Users/twixes/Developer/code/.claude/skills/sync-inbox-to-cloud/SKILL.md` before starting — they are binding. + +## Goal + +Produce an inventory of: + +- What lives on the desktop side (source of truth) +- What lives on the cloud side (target, partially synced from prior runs) +- Which shared backend wrappers exist + +The next sub-skill (`plan-inbox-sync`) consumes this inventory to produce the sync manifest. + +## Steps + +### 1. Enumerate the desktop Inbox + +Always start with a fresh listing — do not assume the file set from a prior run. + +```sh +ls /Users/twixes/Developer/code/apps/code/src/renderer/features/inbox/ +find /Users/twixes/Developer/code/apps/code/src/renderer/features/inbox -type f \( -name "*.ts" -o -name "*.tsx" \) | sort +``` + +### 2. Read the desktop entry-point files in full + +Find the obvious shell component(s) — the file the router points at, plus the top-level layout file(s). Names will change over time; look for what's currently the entry point. Today (verify): `components/InboxView.tsx` and `components/InboxSignalsTab.tsx`, but **do not** assume these names will persist. + +### 3. Read a representative file from each sub-area + +For each of the groups you see when you list the desktop dir, read one or two representative files to understand the shape. Don't read every file — read enough to identify the contract of each area. + +Typical groupings (illustrative, derive on this run): + +- Entry / layout shells +- List / report rendering +- Detail / per-report views +- Configuration & autonomy surfaces (sources dialog, signal source toggles, team config, user autonomy config, scout / responder management as those emerge) +- Empty / loading / setup states +- Hooks (data fetching, deep-link sync, engagement tracking, etc.) +- Stores (filter state, selection, sidebar widths, etc.) +- Utils (prompt builders, filter helpers, constants) + +If desktop's IA has shifted (e.g. new top-level tabs like Pull-requests / Reports / Agents), the groupings shift with it. Mirror what's actually there. + +### 4. Enumerate the cloud Inbox + +```sh +ls /Users/twixes/Developer/posthog/frontend/src/scenes/inbox/ +find /Users/twixes/Developer/posthog/frontend/src/scenes/inbox -type f \( -name "*.ts" -o -name "*.tsx" \) | sort +``` + +Do not assume any specific cloud file or structure from this skill's text or prior runs. Read what is actually there. + +### 5. Read cloud's entry-point file(s) and central composing Kea logic in full + +Identify them by looking at the scene's exported `SceneExport.component` and `SceneExport.logic`. Read both end-to-end so you know cloud's current shape before you change it. Read the shared `types.ts` for the cloud-side type definitions. + +### 6. Skim the shared API wrappers + +```sh +grep -n "signalReports\|signal_source\|signal_team\|signal_processing" /Users/twixes/Developer/posthog/frontend/src/lib/api.ts | head -30 +``` + +Note which endpoints already have wrappers. The next sub-skill needs this to decide whether a feature can be ported (wrapper exists) or whether a wrapper needs adding (endpoint exists in backend but no TS wrapper yet) or whether it's a backend gap (endpoint doesn't exist; surface as open question). + +### 7. Browse the upcoming-direction mocks (optional but recommended) + +If the inbox is in active redesign, the mocks page at `https://posthog-self-driving.pages.dev` describes where things are heading. Open with Playwright (`browser_navigate`, `browser_snapshot`) and click through the tabs / configure-agents drawer to understand intent. This is **supplementary context for understanding the direction** — the actual desktop code is what you port. If the mocks page is unreachable, skip this step. + +## Output + +A brief inventory (kept in working memory) with: + +- **Desktop files grouped by concern** — your derived groupings from this run, with file paths +- **Cloud files** — what's currently in `scenes/inbox/`, including any partial subdirectories from prior syncs +- **Shared API wrappers** — list of `api.signalReports.*` / `api.tasks.*` / etc. wrappers already in cloud +- **Missing wrappers but existing endpoints** — backend endpoints (from grep on `products/signals/backend/`) that lack a TS wrapper +- **Obvious feature mismatches noticed in passing** — quick gut-check list, **don't plan yet**; the next sub-skill does that +- **Notes on desktop's current IA** — whether it's still list+detail, has moved to tabs, has a drawer-based config, etc. This frames the planning step. + +## Next step + +**Do not stop here.** The parent `/sync-inbox-to-cloud` is a single uninterrupted workflow. Immediately invoke `/plan-inbox-sync` using the Skill tool. Do not summarize the inventory and wait, do not ask the user anything, do not pause — chain straight to the next step. diff --git a/.claude/skills/plan-inbox-sync/SKILL.md b/.claude/skills/plan-inbox-sync/SKILL.md new file mode 100644 index 0000000000..97e8c0edab --- /dev/null +++ b/.claude/skills/plan-inbox-sync/SKILL.md @@ -0,0 +1,94 @@ +--- +name: plan-inbox-sync +description: Second step of the inbox-to-cloud sync workflow. Takes the inventory from `/inspect-inbox-surfaces` and produces a sync manifest — which desktop features get ported, which get stubbed Coming soon™, which reuse existing cloud surfaces — plus a slicing plan for parallel sub-agents. Use as part of `/sync-inbox-to-cloud`, or standalone to refresh a manifest after desktop reorganized. +--- + +# Plan the inbox sync + +This is sub-skill 2 of `/sync-inbox-to-cloud`. Re-read the parent skill's hard rules at `/Users/twixes/Developer/code/.claude/skills/sync-inbox-to-cloud/SKILL.md` before starting — they constrain every decision below. + +## Goal + +Produce two artifacts in working memory: + +1. **A per-feature manifest** — for each desktop feature, the decision: port / stub Coming soon™ / link to existing cloud surface. +2. **A parallelization plan** — 3-6 disjoint slices, each owned by a sub-agent, with one orchestrator integration phase afterwards. + +## Steps + +### 1. Diff feature-by-feature + +Walk through the inventory from `/inspect-inbox-surfaces`. For each desktop feature, ask: + +- Does cloud have it? +- If yes, is it complete? What polish details are missing? +- If no, what would porting it touch? + +Don't pre-decide based on "does cloud have a place for this" — desktop is the source of truth, and cloud may need a new place. + +### 2. Decide per feature + +For each desktop feature, pick exactly one of: + +- **Port** — implement on cloud using cloud conventions (LemonUI + Kea). This is the **default decision** for everything. If it feels structural, do the structural rewrite. + +- **Stub Coming soon™** — ONLY for features that require a live agent chat surface. Today that's the Discuss action (and any equivalent floating chat-with-inbox affordance from desktop's evolving IA). One disabled affordance with a tooltip. **Task-kickoff actions are NOT live chat** and don't stub. + +- **Port the linkage + reuse existing cloud surface for run-log viewing** — where a desktop feature links a SignalReport to its tasks, port the linkage UI on the cloud Inbox side and link out to `products/tasks/frontend/TaskDetailPage.tsx` / `TaskSessionView.tsx`. The linkage on the inbox side is the port; only the run-log viewer is reused. + +**"Skip" is not a valid decision.** Neither is "defer to follow-up PR." If a feature feels too large to port in one chunk, split it across multiple sub-agents — but ship all of them in this run. The only items that may legitimately not get ported are things that depend on a desktop-only OS API with no cloud analogue; even then, port the visible part and stub the OS bit. + +### 3. Plan the cloud directory shape + +Look at desktop's current organization (subdirectories, component splits, hook/store/util groupings). Plan an analogous shape on cloud: + +- Cloud subdirectories mirror desktop's (e.g. if desktop has `components/list/`, `components/detail/`, `components/config/`, cloud gets the same — adapted to whatever desktop's current organization actually is) +- Every named component on desktop gets a same-named component on cloud (translated to LemonUI) +- Sibling Kea logic files per cohesive area (`Logic.ts` with generated `LogicType.ts`) +- Pick names that match desktop's structure on this run — do not carry over names from prior syncs if desktop has reorganized + +### 4. Plan the slicing for parallel sub-agents + +Read `/Users/twixes/Developer/code/.claude/skills/sync-inbox-to-cloud/references/parallelization.md` for the pattern. + +Identify 3-6 cohesive UI areas with **disjoint file scopes**. The orchestrator (in the next sub-skill) will own the central scene file(s) and central composing logic; each slice gets a sub-agent that owns its own subdirectory + sibling logic. + +Slice cohesion patterns (examples — derive from desktop's actual shape on this run): + +- One slice per top-level tab (if desktop has tabs) +- One slice for a drawer-shaped configuration surface +- One slice for selection / multi-select / keyboard navigation as a cross-cutting behaviour +- One slice for analytics / engagement tracking +- One slice per cohesive area like list-rows, detail-view, sources/setup, autonomy-config + +3-6 is the sweet spot. Fewer → just run sequentially. More → coordination cost dominates. + +### 5. Validate the plan against the hard rules + +Sanity check before handing off: + +- Did you stub anything that isn't live chat? Revert that decision to "port". +- Did you decide to skip anything that should be ported? Revert. +- Are slice file scopes truly disjoint? If two slices reference the same file, one of them needs to own it (or it needs to belong to the orchestrator). +- Did you pick cloud directory names that mirror desktop's current organization (not a stale prior structure)? +- For any feature you marked "needs new wrapper in `lib/api.ts`", did you verify the backend endpoint exists? Grep `products/signals/backend/views.py`. + +## Output + +Two artifacts in working memory, suggested format: + +**Per-feature manifest** (one bullet per feature): + +- `` — port to `` +- `` — stub Coming soon™ at `` (live chat — verbatim reason) +- `` — port linkage to ``; reuse `` for run-log viewing + +**Slicing plan** (one bullet per slice + one for the orchestrator): + +- **Slice A — ``**: owns ``; reads from ``; sub-agent type: `general-purpose` +- **Slice B — ``**: ... +- **Orchestrator integration**: will touch ``, ``, `frontend/src/lib/api.ts` (for new wrappers on existing backend endpoints), `urls.ts` / `scenes.ts` (if desktop IA introduces new routes) + +## Next step + +**Do not stop here.** The parent `/sync-inbox-to-cloud` is a single uninterrupted workflow. Immediately invoke `/implement-inbox-sync` using the Skill tool. Do not summarize the manifest and wait, do not ask the user for approval, do not pause — chain straight to the next step. diff --git a/.claude/skills/reflect-on-inbox-sync/SKILL.md b/.claude/skills/reflect-on-inbox-sync/SKILL.md new file mode 100644 index 0000000000..b306420307 --- /dev/null +++ b/.claude/skills/reflect-on-inbox-sync/SKILL.md @@ -0,0 +1,106 @@ +--- +name: reflect-on-inbox-sync +description: Final step of `/sync-inbox-to-cloud`. Audits the just-completed sync against the parent skill's hard rules and anti-checklist, surfaces violations or near-misses, and proposes concrete skill-refinement suggestions for the next iteration. Use only as the closing step of `/sync-inbox-to-cloud` — not standalone. +--- + +# Reflect on the inbox sync + +This is sub-skill 6 of `/sync-inbox-to-cloud` — the last step. By the time you're invoked, the work is complete, `/simplify` has run, and `/finalize-inbox-sync` has produced the structured final report. + +## Goal + +Audit the run against the parent skill's hard rules, surface anything that drifted from the intent, and produce concrete edits the user could apply to the skill text to prevent the same drift next time. + +The user iterates on these skills by running them, inspecting the output, and refining the text. **Your job is to make that iteration loop tight** — point at specific rule gaps and propose specific text changes, not vague critique. + +## Steps + +### 1. Re-read the rules + +Re-read in full: + +- `/Users/twixes/Developer/code/.claude/skills/sync-inbox-to-cloud/SKILL.md` — the Hard rules section and the Anti-checklist section +- The translation reference at `/Users/twixes/Developer/code/.claude/skills/implement-inbox-sync/references/translation.md` +- The parallelization reference at `/Users/twixes/Developer/code/.claude/skills/sync-inbox-to-cloud/references/parallelization.md` + +You're auditing against these rules. Hold them in mind as you work through the next steps. + +### 2. Walk the final report against the rules + +Take the report `/finalize-inbox-sync` just produced. For each section: + +- **Synced** — is there evidence the polish-parity rule was honored, or did the item only port the feature and skip the polish? Cross-reference desktop's component file against the cloud destination — did the cloud component have an analogous shape? +- **Stubbed (Coming soon™)** — is every stubbed item genuinely a live-chat affordance? If anything else was stubbed, that's a violation of the live-chat-only rule. Flag it. +- **Reused existing cloud surface** — is the linkage itself ported (so it appears under Synced), or did this entry sneak in to cover for not building the linkage? Flag if the linkage is missing. +- **Skipped (rare)** — for each skipped item, is there genuinely no cloud analogue? If you can think of a way to port it, that's a "no scope escape hatch" violation. Flag it. +- **Open questions** — were any of these things that the skill should have answered? If a sub-skill produced ambiguity that the agent had to escalate, the skill text is unclear. Flag the section that needs tightening. + +### 3. Audit for things missing from the report + +Pull up the inventory `/inspect-inbox-surfaces` produced (or re-list the desktop Inbox dir if the inventory isn't in working memory). Cross-reference every desktop feature against the report. Anything in the inventory that doesn't appear under Synced / Stubbed / Reused / Skipped is **silently dropped** — that's the worst failure mode. Flag each. + +### 4. Audit the actual diff + +Don't trust the report alone — verify against the code. Run from `~/Developer/posthog/`: + +```sh +git status +git diff --stat +``` + +Then spot-check for hard-rule violations: + +```sh +# Forbidden imports in cloud Inbox files +grep -rn "@radix-ui/themes\|@posthog/quill\|@phosphor-icons/react\|lucide-react" frontend/src/scenes/inbox/ products/signals/frontend/ 2>/dev/null + +# Forbidden new files on the desktop side +git -C /Users/twixes/Developer/code status 2>/dev/null | head + +# Forbidden backend changes +git diff --name-only products/signals/backend/ + +# Forbidden new feature flags +git diff frontend/src/lib/constants.tsx | grep -i "FLAG" +``` + +Each non-empty result is a possible violation — investigate before flagging. + +### 5. Audit for Kea persistence parity + +If the manifest included filter / sort / sidebar state, check that anything desktop persists via Zustand `persist()` is mirrored on cloud with Kea `{ persist: true }`: + +```sh +# Cloud reducers that persist +grep -rn "persist: true" frontend/src/scenes/inbox/ + +# Desktop stores that persist +grep -rn "persist(" /Users/twixes/Developer/code/apps/code/src/renderer/features/inbox/stores/ +``` + +Compare. Any state desktop persists that cloud doesn't is a parity violation. + +### 6. Generate refinement suggestions + +For each issue found in steps 2-5, propose a concrete skill text change. Each suggestion specifies: + +- **Which file** — parent SKILL.md, which sub-skill SKILL.md, or which reference doc +- **Which section** — the heading or rule it lives under +- **What change** — the specific text edit or addition + +Even if no violations were found, propose at least one refinement if you noticed any ambiguity during the run — e.g. a rule that required interpretation, a hand-off that wasn't crisp, a slice boundary that produced merge friction. + +If you really cannot find anything to refine, say so honestly — but the bar for "perfect" is high. The first dozen runs of this skill should produce refinement suggestions every time. + +## Output + +Append two sections to the final report that `/finalize-inbox-sync` produced: + +- **Self-reflection** — bullets. One per finding from steps 2-5 above. Mark each as `[compliant]`, `[possible violation]`, or `[silently dropped]` with a brief explanation and citation (file path / report section). +- **Suggested skill refinements** — bullets. One per concrete edit. Format: "In `` under `
`: ." Keep each suggestion tight enough that the user can apply it without re-deriving the reasoning. + +## Do not + +- **Do not modify the skill files yourself.** Only propose changes. The user decides which to apply. +- **Do not flatter.** "Everything looks great" with no findings is suspicious — re-audit. +- **Do not invoke any other sub-skills after this.** This is the end of the workflow. After your output is appended, the run is complete. diff --git a/.claude/skills/sync-inbox-to-cloud/SKILL.md b/.claude/skills/sync-inbox-to-cloud/SKILL.md new file mode 100644 index 0000000000..70e825a8c6 --- /dev/null +++ b/.claude/skills/sync-inbox-to-cloud/SKILL.md @@ -0,0 +1,98 @@ +--- +name: sync-inbox-to-cloud +description: Port every feature and UI polish from the PostHog Code desktop Inbox (Electron app) to the PostHog Cloud Inbox (Django + React). Orchestrates four sequential sub-skills — inspect, plan, implement, finalize — to re-inspect both surfaces from scratch on every run, then implement gaps on the cloud side using cloud conventions (LemonUI, Kea, scenes/urls/sceneTypes registration). Use when the user wants to bring the cloud Inbox to feature parity with desktop, after a change shipped to the desktop Inbox, or to audit/report drift between the two surfaces. +--- + +# Sync the PostHog Code Inbox to PostHog Cloud + +## Mission + +Bring the PostHog **Cloud** Inbox to feature parity with the PostHog **Code** desktop Inbox. The desktop Inbox is the **source of truth for product behaviour and UI polish**; cloud is the **target**. + +You are syncing one direction: desktop → cloud. Do **not** touch the desktop repo. + +**Scope includes the entire Inbox feature**, not just the report list/detail. Everything in `~/Developer/code/apps/code/src/renderer/features/inbox/` is in scope: the report list + detail, the configuration / management UI (sources, signal source toggles, team config), the autonomous-agent configuration UI (per-user autonomy, scout / responder management as those emerge), every adjacent affordance (banners, deep-link sync, engagement tracking, keyboard navigation, dismiss flows). If it lives under `features/inbox/` on desktop, it gets ported. + +The two surfaces share the same backend (`SignalReport` API + signal source / config / processing endpoints, all under `products/signals/backend/` on cloud, exposed via `api.signalReports` and friends in `frontend/src/lib/api.ts`). They differ only in how the UI is built. Your job is to translate behaviour and polish, not the underlying data shapes. + +## Hard rules + +These are binding across **all four sub-skills**. Re-read them at the start of each sub-skill. + +- **Both Inbox surfaces are moving targets — this skill must NOT hard-code today's structure.** The desktop Inbox is mid-evolution (e.g. the user is moving from a single signal report list toward a tabbed Pull-requests / Reports / Agents IA, with a chat-with-inbox affordance and a drawer-based "Configure agents" surface). Cloud lags behind. Re-derive structure on each run by `ls`/`find` on both sides; do not assume any specific file, tab, component name, or layout from this skill's text. Forward-looking direction is available at the mocks page (`https://posthog-self-driving.pages.dev`, browse with Playwright if helpful) but **only the actual desktop code is the source of truth**. + +- **Inspect the desktop Inbox today, every run.** Do not rely on a prior snapshot — list every file under the desktop Inbox dir and read the ones relevant to each feature. The desktop Inbox is the moving target. + +- **Cloud uses LemonUI (`@posthog/lemon-ui` + `lib/lemon-ui/*`). Never import `@radix-ui/themes`, `@posthog/quill`, `@phosphor-icons/react`, or `lucide-react` in cloud files.** Translate every desktop component to its Lemon equivalent. Icons come from `@posthog/icons`. + +- **Cloud uses Kea for state, not Zustand or TanStack Query.** Every store/hook on the desktop side maps to a Kea logic on the cloud side. Follow `~/Developer/posthog/.claude/skills/writing-kea-logics/SKILL.md` and `using-kea-disposables/SKILL.md`. Logic file naming: `Logic.ts` with auto-generated `LogicType.ts`. + +- **Live agent chat is the ONLY thing that gets stubbed to "Coming soon™".** Cloud cannot host a real-time agent chat surface yet — a true chat-with-inbox affordance (streaming responses, message bubbles, in-app conversation thread) renders as a disabled control with "Coming soon™" on cloud. **Task-kickoff actions are NOT live chat.** If desktop's "Discuss" (or any "chat with inbox" affordance) creates a task via `taskService.createTask` and navigates away, port it — it's NOT live chat, regardless of what the button says. Anything that just creates a task and walks away — "Create PR", "Discuss", research kickoff, repo selection prompts — must be fully ported, including prompt construction. Cloud has the same task-creation capability (`api.tasks.*` + `~/Developer/posthog/products/tasks/`); use it. Only stub when the desktop surface holds an in-app conversation (streaming responses, message bubbles, etc.) — grep the desktop file for `taskService.createTask` or equivalent before deciding to stub. + +- **SignalReport ↔ Task linkage is required, not optional.** Wherever desktop links a SignalReport to its tasks, cloud must show the same affordance — render the task list / latest task status inline in the detail surface, and **link** users out to cloud's existing task UI (`products/tasks/frontend/TaskDetailPage.tsx`, `TaskSessionView.tsx`) for the actual run-log viewing. Do not rebuild that row-view run-log UI on cloud. If the API wrapper doesn't exist on cloud, add the wrapper — the backend endpoint exists (`SignalReportTaskViewSet` in `products/signals/backend/views.py`). Adding a frontend wrapper on top of an existing backend endpoint is fair game; adding a new backend endpoint is not. + +- **Port every UI aspect. No "follow-up PR" escape hatch.** Every filter, default value, empty state, keyboard shortcut, polish detail desktop has — all of it. If porting requires a structural rewrite of the cloud scene (changing the layout shape, splitting the scene file, adding new top-level tabs or drawers), do the rewrite. The cloud scene can grow, get split into subcomponents, get re-IA'd to match desktop's current IA, or be reorganized end-to-end. The ONLY legitimate reason to not fully port a thing is the live-chat rule above. "Skip" is not a routine decision; "deferred to follow-up" is not a category. + +- **Desktop is the source of truth for UI/UX behavior, defaults, copy, ordering, and persistence.** When cloud and desktop disagree on a default (e.g. cloud's `statusFilter` defaults to `[READY]` but desktop's defaults to all six in-flight statuses), change cloud to match desktop. Same for sort field / sort direction, filter persistence keys, label strings, empty-state copy, refresh intervals — match desktop exactly unless a cloud-specific constraint physically prevents it. + +- **The backend is the source of truth for data shape and enum values.** For UI/UX, desktop wins (rule above). For "does this enum value actually exist?" type questions, grep `~/Developer/posthog/products/signals/backend/{models,serializers,views}.py` and trust what the Django models / serializers say. If desktop and cloud disagree on a status enum value, follow the backend and remove whichever side is stale. + +- **Don't touch desktop code.** Read-only on the `~/Developer/code` side. One-way sync. + +- **Don't modify the Python backend in `products/signals/backend/`** — no new endpoints, no serializer field changes, no migrations. If a desktop feature genuinely needs a new backend endpoint, surface it under "Open questions" and skip just that piece. However, **adding a TS wrapper in `frontend/src/lib/api.ts` on top of an existing backend endpoint is expected and required** — that's not a backend change. Grep `products/signals/backend/views.py` and `routes.py` to confirm endpoints exist before deciding one is missing. + +- **No new feature flags unless desktop has one.** If desktop gates a feature behind a flag (e.g. `INBOX_GATED_DUE_TO_SCALE_FLAG`), mirror the gate on cloud using `useFeatureFlag` from `lib/hooks/useFeatureFlag`. Otherwise ship ungated. + +- **Mirror desktop's organization, adapted to cloud conventions — derive fresh each run.** Whatever subdirectories and component splits desktop uses today, replicate the analogous shape on cloud. Components matter most: every named component on desktop gets a same-named component on cloud (translated to LemonUI). Layer cloud's own conventions on top: PascalCase component files, generated `*LogicType.ts` next to each Kea logic. Do not dump everything into one giant scene file. If desktop has reorganized since the last sync, cloud follows. + +- **Polish parity is part of the port, not a "nice-to-have".** Every visual / interaction detail: search bar position and orientation, toolbar layout, sticky headers, hover states, badge variants, skeleton shapes, empty-state hedgehogs, responsive breakpoints, sidebar widths, scroll behaviour, keyboard focus rings. Match the look and feel within the constraint that cloud uses LemonUI. When LemonUI has no exact match (e.g. a specific animated loader, a specific badge variant), implement it in plain JSX + Tailwind in the cloud Inbox dir. Do not add a third component library. + +- **Missing dependencies don't license skipping polish.** If cloud lacks a desktop dependency (`framer-motion`, `tiptap`, etc.), pick one of: (a) add the dep to cloud's `frontend/package.json` if it's small and well-maintained; (b) port the visual using cloud-available substitutes (CSS transitions, `react-transition-group`, `Motion One`, plain `