Skip to content

feat(inbox): add Dismissed tab with restore#2704

Open
andrewm4894 wants to merge 9 commits into
mainfrom
inbox/dismissed-tab
Open

feat(inbox): add Dismissed tab with restore#2704
andrewm4894 wants to merge 9 commits into
mainfrom
inbox/dismissed-tab

Conversation

@andrewm4894

@andrewm4894 andrewm4894 commented Jun 16, 2026

Copy link
Copy Markdown
Member

Adds a Dismissed tab to the inbox: lists dismissed (suppressed) reports, lets you re-read one, restore it, or grab a shareable link.

Backend dependency (PostHog/posthog#64019) is merged + deployed, and the whole feature has been verified end-to-end against the live app (including a real restore round-trip — status confirmed flipping suppressed → potential server-side).

What's in it

Tab + list

  • New 4th tab. useInboxDismissedReports fetches status=suppressed (the main pipeline query excludes suppressed reports), no polling — relies on reportKeys.all invalidation, which dismiss/restore both trigger.
  • No count badge (an open-ended archive total adds no signal, and skipping it avoids an always-on count query in the inbox shell). Reviewer-scope selector hidden (the dismissed list isn't scoped). Membership stays canonical in reportMembership.ts (isDismissedReport), unit-tested.

Restore

  • useInboxRestoreReport reuses the state action's potential ("reopen") transition — the only reopen path the backend exposes. Invalidates reportKeys.all so the report leaves Dismissed.
  • ⚠️ Behaviour worth knowing: restore sets the report to potential, which is a queued run status, so a restored report lands in the Runs tab (Queued), not Reports — it re-enters the pipeline rather than popping back to its prior state (that prior status isn't persisted, and the state endpoint only accepts potential/suppressed). It cycles back to Reports on its own once it re-reaches ready. It stays re-dismissible throughout (via the run → report detail, or from Reports once ready).

Read-only detail

  • DismissedReportDetail reuses InboxReportDetailGate + InboxDetailFrame — summary + evidence + Restore + copy-link. No triage affordances (dismiss / discuss / create PR / reviewers): the report is out of the pipeline until restored.
  • InboxDetailFrame gains a showDismiss opt-out; the gate skips OPENED/CLOSED engagement tracking for the dismissed tab (not a tracked InboxDetailTab — its rank would be measured against the wrong list).
  • Routes: dismissed.tsx is an Outlet layout, with dismissed.index (list) and dismissed.$reportId (detail). The loader resolves the cached report from the dismissed list cache, so navigation renders instantly.

Shareable link

  • Copy-link button beside Restore (same copyInboxReportLink deep-link util the other detail screens use).
  • useOpenInboxReport now routes a suppressed report to the Dismissed detail (it previously had no suppressed branch and fell through to the Reports detail, which showed a stray Dismiss action on an already-dismissed report). Adds navigateToInboxDismissedDetail.

Verification

  • @posthog/core + @posthog/ui typecheck ✅ · core tests 1550 passed ✅ · biome lint ✅ (pre-commit runs biome + full typecheck)
  • Driven in the running app over CDP: tab renders (no count badge, scope hidden), card → detail loads with summary + evidence, copy-link present beside Restore.
  • Restore round-trip confirmed server-side via the API: report 019eced2… went suppressed → potential.

Reviewer notes

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit b870732.

@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Comments Outside Diff (1)

  1. packages/core/src/inbox/reportMembership.ts, line 109-111 (link)

    P2 isInboxDetailPath now matches phantom dismissed detail paths

    INBOX_DETAIL_PATH_RE is built from INBOX_TAB_KEYS, which now includes "dismissed". So /code/inbox/dismissed/<anyId> will be treated as a detail path by isInboxDetailPath, causing InboxView to hide InboxPageHeader even though no detail route exists for dismissed reports. A user who manually navigates to such a URL gets a router 404 with the page header stripped. Consider excluding "dismissed" from the regex, or building the pattern from a separate INBOX_DETAIL_TAB_KEYS constant that only lists tabs that actually have detail routes.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: packages/core/src/inbox/reportMembership.ts
    Line: 109-111
    
    Comment:
    **`isInboxDetailPath` now matches phantom dismissed detail paths**
    
    `INBOX_DETAIL_PATH_RE` is built from `INBOX_TAB_KEYS`, which now includes `"dismissed"`. So `/code/inbox/dismissed/<anyId>` will be treated as a detail path by `isInboxDetailPath`, causing `InboxView` to hide `InboxPageHeader` even though no detail route exists for dismissed reports. A user who manually navigates to such a URL gets a router 404 with the page header stripped. Consider excluding `"dismissed"` from the regex, or building the pattern from a separate `INBOX_DETAIL_TAB_KEYS` constant that only lists tabs that actually have detail routes.
    
    How can I resolve this? If you propose a fix, please make it concise.
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
packages/core/src/inbox/reportMembership.test.ts:41-53
**Prefer `it.each` for the negative-status loop**

The `for...of` inside a single `it()` block will report only "does not match active or deleted reports" when any status fails, making it impossible to tell which status triggered the failure without reading the stack trace. Per the team's preference for parametrised tests, converting this to `it.each` gives one named case per status and immediate failure attribution.

### Issue 2 of 2
packages/core/src/inbox/reportMembership.ts:109-111
**`isInboxDetailPath` now matches phantom dismissed detail paths**

`INBOX_DETAIL_PATH_RE` is built from `INBOX_TAB_KEYS`, which now includes `"dismissed"`. So `/code/inbox/dismissed/<anyId>` will be treated as a detail path by `isInboxDetailPath`, causing `InboxView` to hide `InboxPageHeader` even though no detail route exists for dismissed reports. A user who manually navigates to such a URL gets a router 404 with the page header stripped. Consider excluding `"dismissed"` from the regex, or building the pattern from a separate `INBOX_DETAIL_TAB_KEYS` constant that only lists tabs that actually have detail routes.

Reviews (1): Last reviewed commit: "feat(inbox): add Dismissed tab with rest..." | Re-trigger Greptile

Comment thread packages/core/src/inbox/reportMembership.test.ts Outdated
@andrewm4894 andrewm4894 marked this pull request as ready for review June 16, 2026 15:26
@andrewm4894 andrewm4894 self-assigned this Jun 16, 2026
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Reviews (2): Last reviewed commit: "feat(inbox): add read-only detail view f..." | Re-trigger Greptile

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

Copy link
Copy Markdown

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: e17a9f273d

ℹ️ 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 packages/ui/src/features/inbox/hooks/useInboxDismissedReports.ts
Comment thread packages/ui/src/features/inbox/components/DismissedTab.tsx
@andrewm4894 andrewm4894 force-pushed the inbox/dismissed-tab branch from 6c67481 to 79333c6 Compare June 17, 2026 09:08
Adds a fourth inbox tab listing dismissed (suppressed) reports, newest first,
with a per-card Restore action that reopens a report back into the pipeline
(reuses the state action's 'potential' transition).

- Dedicated useInboxDismissedReports query (status=suppressed); the main
  pipeline query excludes suppressed, so the tab fetches them separately.
- useInboxRestoreReport invalidates reportKeys.all so a restored report moves
  out of Dismissed and back into the pipeline tabs.
- Cards are read-only (no detail link): the report detail endpoint currently
  404s for suppressed reports. Clicking-through depends on PostHog/posthog#64019
  shipping; until then Restore is the only action.
- No count badge on the tab (an open-ended archive total adds no signal), so
  no extra always-on query in the inbox shell.
- Reviewer-scope selector hidden on the tab (the dismissed list isn't scoped).

Updates the feature CLAUDE.md IA map to four tabs.
Dismissed cards now link into a detail view (summary + evidence) with a single
Restore action; no triage affordances since the report is out of the pipeline.

- DismissedReportDetail reuses InboxReportDetailGate + InboxDetailFrame.
- InboxDetailFrame gains a showDismiss opt-out (a dismissed report shouldn't
  show 'Dismiss'); the gate skips OPENED/CLOSED engagement tracking for the
  dismissed tab (its rank would be measured against the wrong list).
- Routes restructured: dismissed.tsx is now an Outlet layout, with
  dismissed.index (list) and dismissed.$reportId (detail).
- The route loader resolves the cached report from the dismissed list cache, so
  navigation renders instantly; the detail's own fetch depends on the backend
  serving suppressed reports on retrieve/signals (PostHog/posthog#64019).
- DismissedReportDetail gains a copy-link button beside Restore (same deep-link
  util the other detail screens use), so a dismissed report can be shared.
- useOpenInboxReport now routes a suppressed report to the Dismissed detail
  (was falling through to the Reports detail, which showed a stray Dismiss
  action on an already-dismissed report). Adds navigateToInboxDismissedDetail.
@andrewm4894 andrewm4894 force-pushed the inbox/dismissed-tab branch from 79333c6 to 4de74ab Compare June 17, 2026 09:16
@andrewm4894 andrewm4894 requested a review from a team June 17, 2026 11:00

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

Copy link
Copy Markdown

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: 95690b4824

ℹ️ 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 packages/ui/src/features/inbox/components/DismissedReportDetail.tsx
Surface why each report was suppressed: render the dismissal reason (with
the note as a tooltip) as a chip in the DismissedReportCard meta row.

The reason and note are denormalised onto the list SignalReport by the
backend serializer, mirroring how priority/actionability/already_addressed
are lifted from their artefacts, so cards avoid an N+1 per-card fetch.
Reason codes are client-owned, so an unknown value falls back to the raw
code via dismissalReasonLabel rather than being dropped.
@andrewm4894

Copy link
Copy Markdown
Member Author

Added: the dismissed report cards now show why each report was suppressed — the dismissal reason as a chip (with the note as a tooltip).

The reason/note are denormalised onto the list SignalReport by the backend serializer in PostHog/posthog#64267 (same artefact-lift pattern as priority/actionability/already_addressed), so cards avoid an N+1 per-card artefact fetch. The field is optional — this PR ships independently; cards just show no chip until the backend lands.

A /code/inbox/dismissed/$reportId URL can go stale (history, bookmark, or
copied deep link) after the report was restored and moved on. The detail
view rendered Restore unconditionally, and READY/RESOLVED -> POTENTIAL is an
allowed server-side transition, so restoring re-queued an active report.

Redirect non-suppressed reports to their current home (pulls when a PR
exists, else reports), mirroring useOpenInboxReport's routing, instead of
offering Restore.

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

Copy link
Copy Markdown

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: 2c06b7d57e

ℹ️ 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 packages/ui/src/features/inbox/components/DismissedReportDetail.tsx Outdated
Comment thread packages/ui/src/features/inbox/components/DismissedReportDetail.tsx Outdated
Follow-ups on the dismissed-detail guard:

- Don't redirect on a stale cache snapshot. After a dismissal the suppress
  mutation invalidates but doesn't rewrite reportKeys.detail, so the gate can
  briefly surface the pre-dismissal (e.g. ready) record via initialData. The
  redirect now waits for the detail query to settle (!isFetching) — which the
  query forces fresh on mount via initialDataUpdatedAt: 0 — so a just-dismissed
  report opened from the Dismissed list no longer bounces straight out.
- Route non-suppressed reports by the same membership predicates the tabs use:
  Pulls when a PR exists, Runs for in-flight runs (potential/candidate/
  in_progress/pending_input), else Reports — instead of sending every non-PR
  report to Reports, which left run-state reports on the wrong tab.

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

Copy link
Copy Markdown

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: f391b98053

ℹ️ 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 packages/ui/src/features/inbox/components/DismissedReportDetail.tsx Outdated
Comment thread packages/ui/src/features/inbox/components/InboxReportDetailGate.tsx
Comment thread packages/ui/src/features/inbox/components/DismissedTab.tsx
…direct

isReportTabReport excludes failed (failed runs live only in the Runs tab), so
the previous isAgentRunReport check sent a restored-then-failed report to
/reports, which doesn't list it. Route by membership precedence instead:
PR -> pulls, isReportTabReport -> reports, else -> runs.
#54: a suppressed report reached via a stale /pulls, /reports, or /runs URL
rendered that tab's full triage actions (dismiss, discuss, create PR) on an
out-of-pipeline report, now that the backend serves suppressed reports on
retrieve. Centralize the status<->route guard in InboxReportDetailGate: redirect
across the dismissed<->pipeline boundary in both directions (suppressed off the
triage routes -> dismissed; non-suppressed off /dismissed -> its current home),
gated on a settled fetch. This subsumes the bespoke guard that lived in
DismissedReportDetail, so that component is back to just rendering.

#59: harden useInboxRestoreReport to re-read the report and no-op (with a toast
+ list refresh) if it's no longer suppressed, covering the card-level Restore
on a row left stale by a change in another session — not just the detail path.

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

Copy link
Copy Markdown

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: b870732fd7

ℹ️ 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".

const onDismissedRoute = backTo === "/code/inbox/dismissed";
const isSuppressed = resolvedReport?.status === "suppressed";
let redirectTo: InboxDetailRoute | null = null;
if (resolvedReport && !isFetching) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Hide stale detail actions during status refetch

When a report is already cached as a normal inbox report but has been suppressed in another session, useInboxReportById returns that cached row while the forced fresh fetch is still pending. This branch only postpones the redirect while isFetching, but the component then falls through and renders children(resolvedReport), so /reports, /pulls, or /runs can briefly show full triage controls (including Create PR/Discuss) for a now-suppressed report until the fetch settles. Consider rendering the spinner or disabling actions while the route-boundary status is being confirmed.

Useful? React with 👍 / 👎.

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.

1 participant