Skip to content

feat(code): add Discuss button to inbox reports#2241

Merged
andrewm4894 merged 15 commits into
mainfrom
inbox-discuss-button
May 22, 2026
Merged

feat(code): add Discuss button to inbox reports#2241
andrewm4894 merged 15 commits into
mainfrom
inbox-discuss-button

Conversation

@andrewm4894
Copy link
Copy Markdown
Member

@andrewm4894 andrewm4894 commented May 20, 2026

Problem

Inbox reports have Dismiss and Create PR actions, but no lightweight way to talk through a report with the agent before deciding what to do. Manually opening a new task, referencing the report, choosing the right mode, and submitting is too much friction for “let me think about this with you.”

Changes

Kick off discussion and optionally add a question or prompt:

image

Then a chat will open in auto mode and use the mcp to try answer/respond to your initial prompt:

image
  • Adds a Discuss split button to the inbox report detail header.
  • Primary click starts a chat for the current report immediately.
  • Chevron opens a compact popover where you can optionally add the first question before starting the discussion.
  • Discuss tasks auto-submit the initial prompt and start in Auto Mode, so the agent can use the inbox MCP tools without stopping on the initial permission prompt.
  • The initial prompt includes the report ID plus a clickable app deeplink back to the inbox item.
  • Markdown rendering now explicitly supports posthog-code:// and posthog-code-dev:// deeplinks.
  • Auto-submit waits for task config/model readiness before creating the cloud run, and clears the transient Discuss draft after successful submission so New Task opens cleanly afterward.
  • The report association banner remains attached to the created task, and repository selection reuses the same report/repo fallback path as Create PR.

How did you test this?

  • pnpm --filter code typecheck
  • Focused Biome checks on touched files.
  • Pre-commit hook ran Biome and full pnpm typecheck.
  • Manual local smoke test in the Electron dev app:
    • default Discuss flow starts a chat automatically
    • optional first-question popover submits the custom question
    • Discuss starts in Auto Mode and uses inbox MCP tools
    • inbox deeplink renders as clickable in chat
    • returning to New Task no longer preserves the previous Discuss prompt

Publish to changelog?

no


Small, frontend-focused workflow improvement — candidate for stamphog AI-assisted approval.

@andrewm4894 andrewm4894 self-assigned this May 20, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 33678e7055

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread apps/code/src/renderer/features/task-detail/components/TaskInput.tsx Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 20, 2026

Comments Outside Diff (1)

  1. apps/code/src/renderer/stores/navigationStore.test.ts, line 111-139 (link)

    P2 Single test covers two independent assertions — prefer separate parameterised cases

    The test validates both (a) that autoSubmit is stored in the view state and (b) that a second navigation mints a fresh taskInputRequestId. These are orthogonal concerns; combining them means a failure in either half gives a confusing error. Given the project's preference for parameterised tests, splitting these — or at least into two it blocks — would make each assertion's intent clearer and failures easier to diagnose.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/code/src/renderer/stores/navigationStore.test.ts
    Line: 111-139
    
    Comment:
    **Single test covers two independent assertions — prefer separate parameterised cases**
    
    The test validates both (a) that `autoSubmit` is stored in the view state and (b) that a second navigation mints a fresh `taskInputRequestId`. These are orthogonal concerns; combining them means a failure in either half gives a confusing error. Given the project's preference for parameterised tests, splitting these — or at least into two `it` blocks — would make each assertion's intent clearer and failures easier to diagnose.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/code/src/renderer/features/task-detail/components/TaskInput.tsx:501-509
**Auto-submit never fires — `canSubmit` is always `false` while spinning**

The `PromptInput` (which calls `ref={editorRef}`) is only rendered in the non-`isAutoSubmitting` branch. When the spinner is showing, `editorRef.current` is `null`, so `canSubmit = !!editorRef.current && canSubmitBase && !editorIsEmpty` evaluates to `false` unconditionally. The guard `if (!canSubmit || !initialPrompt) return` therefore always short-circuits — `hasAutoSubmittedRef.current` never becomes `true` — and every Discuss click falls through to the 2.5 s timeout, leaving the user to submit manually.

The comment even says "handleSubmit only requires `canSubmitBase` in that path", but the condition checks `canSubmit` (the editor-ref-gated variant) instead. The fix is to export `canSubmitBase` from `useTaskCreation` and gate on that here, since passing `segments` as `contentOverride` bypasses the editor ref check inside `handleSubmit`.

### Issue 2 of 2
apps/code/src/renderer/stores/navigationStore.test.ts:111-139
**Single test covers two independent assertions — prefer separate parameterised cases**

The test validates both (a) that `autoSubmit` is stored in the view state and (b) that a second navigation mints a fresh `taskInputRequestId`. These are orthogonal concerns; combining them means a failure in either half gives a confusing error. Given the project's preference for parameterised tests, splitting these — or at least into two `it` blocks — would make each assertion's intent clearer and failures easier to diagnose.

Reviews (1): Last reviewed commit: "feat(code): add Discuss button to inbox ..." | Re-trigger Greptile

Comment thread apps/code/src/renderer/features/task-detail/components/TaskInput.tsx Outdated
Opens a chat session pre-loaded with report context and auto-submits
the first message so users can talk through a report with the agent
without going through the TaskInput screen manually.

Falls back to the standard TaskInput with the prompt pre-filled if
preconditions (repo, auth, online) don't resolve within 2.5s.
@andrewm4894 andrewm4894 force-pushed the inbox-discuss-button branch from 016c739 to c075c7c Compare May 20, 2026 19:04
@andrewm4894 andrewm4894 requested a review from a team May 20, 2026 19:07
# Conflicts:
#	apps/code/src/renderer/features/inbox/components/detail/ReportDetailPane.tsx
- Add 'discuss' to InboxReportActionType with optional has_question flag
- Fire INBOX_REPORT_ACTION from the Discuss handler (popover + button)
- Defer trpcClient load in MarkdownRenderer link onClick so UserMessage.test
  doesn't fail at module init without an electronTRPC global
Add optional question_text field (truncated to 500 chars) to
InboxReportActionProperties so we can see what users are asking when
they submit Discuss via the popover.
@andrewm4894 andrewm4894 requested a review from a team May 21, 2026 11:13
Comment on lines +27 to +36
function isPostHogCodeDeeplink(href: string | undefined): href is string {
if (!href) return false;
try {
const protocol = new URL(href).protocol;
return protocol === "posthog-code:" || protocol === "posthog-code-dev:";
} catch {
return false;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not live here, maybe we already have something for this

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — there was already a getDeeplinkProtocol helper in apps/code/src/shared/deeplink.ts, so I moved isPostHogCodeDeeplink there alongside it and updated the protocol literals to use the existing constants. MarkdownRenderer now just imports it. Fixed in 75662a8.


navigateToInbox: () => {
navigate({ type: "inbox" });
track(ANALYTICS_EVENTS.INBOX_VIEWED);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we removing this event?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not removed, just relocated — when I merged main into this branch, the work from #2228 (feat(inbox): instrument analytics for engagement events) had already redefined INBOX_VIEWED to require a rich InboxViewedProperties payload (report counts, filters, etc.). The bare track(ANALYTICS_EVENTS.INBOX_VIEWED) call here no longer typechecks, and the new structured fire happens inside InboxSignalsTab.tsx once the inbox actually renders with data — see InboxSignalsTab.tsx:478-506. So we still get the event, just with proper props and from a more accurate spot in the lifecycle.

Comment on lines +548 to +557
// the user can submit manually once they pick a repo.
useEffect(() => {
if (!isAutoSubmitting) return;
const timer = setTimeout(() => {
if (!hasAutoSubmittedRef.current) {
setIsAutoSubmitting(false);
}
}, 2500);
return () => clearTimeout(timer);
}, [isAutoSubmitting]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can easily take longer than 2.5s. I'm sure there's a better solution than relying on a timeout :/

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gone in 9615f39 — the new useDiscussReport hook calls taskService.createTask directly so there's nothing to time out against. While creation runs the user sees a toast.loading ("Starting discussion...") and the Discuss button shows an inline spinner; on success we navigate, on failure we surface a toast and stay on the inbox.

Comment on lines 486 to 558
const { isCreatingTask, canSubmitBase, canSubmit, handleSubmit } =
useTaskCreation({
editorRef,
selectedDirectory,
selectedRepository: selectedCloudRepository,
githubUserIntegrationId: selectedGithubUserIntegrationId,
workspaceMode: effectiveWorkspaceMode,
branch: branchForTaskCreation,
editorIsEmpty,
adapter,
executionMode: currentExecutionMode,
model: currentModel,
reasoningLevel: currentReasoningLevel,
onTaskCreated,
environmentId: selectedEnvironment,
sandboxEnvironmentId:
effectiveWorkspaceMode === "cloud" && selectedCloudEnvId
? selectedCloudEnvId
: undefined,
signalReportId: activeReportAssociation?.reportId,
});

// Reset auto-submit state on each fresh navigation. We key off
// `prefillRequestKey` (a fresh UUID per navigation) so back-to-back Discuss
// clicks each get their own auto-submit attempt even when `autoSubmit`
// itself doesn't change reference.
useEffect(() => {
if (!prefillRequestKey) return;
hasAutoSubmittedRef.current = false;
setIsAutoSubmitting(!!autoSubmit);
}, [prefillRequestKey, autoSubmit]);

// Fire the first message automatically once preconditions are satisfied.
// Uses `contentOverride` so we don't race the editor hydrating from the
// draft store — `handleSubmit` only requires `canSubmitBase` in that path.
useEffect(() => {
if (!isAutoSubmitting || hasAutoSubmittedRef.current) return;
if (!canSubmitBase || !isTaskConfigReady || !initialPrompt) return;
hasAutoSubmittedRef.current = true;
void handleSubmit({
segments: [{ type: "text", text: initialPrompt }],
}).then((ok) => {
if (!ok) {
setIsAutoSubmitting(false);
return;
}
const draftActions = useDraftStore.getState().actions;
draftActions.clearPendingContent(sessionId);
draftActions.setDraft(sessionId, null);
editorRef.current?.clear();
});
}, [
isAutoSubmitting,
canSubmitBase,
isTaskConfigReady,
handleSubmit,
initialPrompt,
sessionId,
]);

// If preconditions never resolve (no repo, offline, etc.), fall back to the
// normal UI after a short grace period — the prompt stays in the editor so
// the user can submit manually once they pick a repo.
useEffect(() => {
if (!isAutoSubmitting) return;
const timer = setTimeout(() => {
if (!hasAutoSubmittedRef.current) {
setIsAutoSubmitting(false);
}
}, 2500);
return () => clearTimeout(timer);
}, [isAutoSubmitting]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This whole thing is doing way too much IMO:

  • three effects
  • canSubmitBase as a workaround..?
  • 2.5s timeout
  • lots of new state

Maybe it's better if you just directly call taskService.createTask from the Discuss component instead

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored as suggested in 9615f39. The Discuss button now goes straight through a small useDiscussReport hook that calls taskService.createTask directly — no TaskInput round-trip. That dissolves all of the things you flagged: the three effects, the canSubmitBase workaround, the 2.5s timeout, the loading placeholder, plus the matching transient state in navigationStore (autoSubmit, initialExecutionMode) and the editor-bypass changes in useTaskCreation. While the task is being created the Discuss button shows an in-place spinner and a toast.loading (same pattern as connectivityToast.ts); on success we jump to the task detail page, on failure a toast surfaces and we stay on the inbox. Net diff: ~125 lines removed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hopefully a bit simpler now - just uses a toast and flow all still feels same as a user - tested it locally

has_question?: boolean;
// The first question text the user typed before hitting Discuss. Truncated to
// 500 chars to keep event payloads bounded.
question_text?: string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should go inside the event right? And if we're truncating it, why even include it

Copy link
Copy Markdown
Member Author

@andrewm4894 andrewm4894 May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would like to keep this one - the motivation is mostly analytics ergonomics on a brand-new feature. Even truncated, having the first question text on the event makes it really easy to get a quick feel for the kinds of follow-ons people want without having to join against task/chat data each time. The full prompt is already going to the backend via the created task, so the truncated snippet on the event doesn't really add new PII exposure on top of that. Happy to revisit and drop it once Discuss is more mature and we have a clearer view of what's worth keeping long-term.

(and when we have some sort of opt out layer too for things like this for users)

Comment on lines +379 to +381
const prompt = trimmedQuestion
? `Discuss PostHog inbox report ${report.id} ([inbox item](${reportLink})). Use the inbox MCP tools to fetch the report, then answer this first: ${trimmedQuestion}`
: `Discuss PostHog inbox report ${report.id} ([inbox item](${reportLink})). Use the inbox MCP tools to fetch the report, then give me a brief readout and ask what I want to dig into.`;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's extract this to a builder

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — extracted to apps/code/src/renderer/features/inbox/utils/buildDiscussReportPrompt.ts with its own unit tests in 32fe704. ReportDetailPane now just calls buildDiscussReportPrompt({ reportId, question, isDevBuild }) so all the prompt copy lives in one place.

Move the helper out of MarkdownRenderer so other renderer surfaces can
reuse it instead of redefining the protocol check.
Pull the inline prompt template out of ReportDetailPane into a
buildDiscussReportPrompt util with its own tests, so the Discuss
prompt copy lives in one place and is easy to tweak.
Replace the auto-submit dance that routed Discuss clicks through
TaskInput (loading placeholder, three effects, 2.5s fallback timeout,
canSubmitBase workaround) with a dedicated useDiscussReport hook that
calls taskService.createTask straight from the inbox detail pane.

The Discuss button now shows an in-place spinner plus a loading toast
while the task is created; on success we jump to the task detail page,
on failure we surface a toast and stay on the inbox. TaskInput drops
the autoSubmit / initialExecutionMode props and the associated render
branch, navigationStore drops the matching transient view fields, and
useTaskCreation reverts to requiring a mounted editor.
@andrewm4894 andrewm4894 requested a review from jonathanlab May 21, 2026 13:15
Comment on lines +105 to +113
onClick={(event) => {
if (!isDeeplink || !href) return;
event.preventDefault();
// Lazy-load the tRPC client so tests that render markdown don't need
// an electronTRPC global at module init time.
void import("@renderer/trpc/client").then(({ trpcClient }) =>
trpcClient.os.openExternal.mutate({ url: href }),
);
}}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any way we can avoid this inline import pattern?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in cfe9107. Inline import is gone — back to a top-level import { trpcClient } like every other call site.

The reason it was lazy in the first place was that UserMessage.test.tsx was failing at module init because trpc/client.ts calls ipcLink() which needs window.electronTRPC, and MarkdownRenderer was the first thing in UserMessage's import graph to reach it. Fixed properly by adding vi.mock("@renderer/trpc/client", ...) to the test — same pattern 14 other test files already use.

…ge test

The lazy `import("@renderer/trpc/client")` inside the link onClick was
working around UserMessage.test.tsx failing at module init (trpc/client.ts
calls ipcLink() which needs window.electronTRPC). Top-level import matches
every other call site in the renderer; the test gets the same vi.mock
pattern other test files use.
@andrewm4894 andrewm4894 enabled auto-merge (squash) May 22, 2026 10:17
@andrewm4894 andrewm4894 merged commit f3f26a0 into main May 22, 2026
15 checks passed
@andrewm4894 andrewm4894 deleted the inbox-discuss-button branch May 22, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants