Skip to content

fix(signals): make inbox example cards and tool-off badge answer clicks - #89589

Draft
posthog[bot] wants to merge 4 commits into
masterfrom
posthog-self-driving/fixsignals-make-inbox-dead-click-991409
Draft

fix(signals): make inbox example cards and tool-off badge answer clicks#89589
posthog[bot] wants to merge 4 commits into
masterfrom
posthog-self-driving/fixsignals-make-inbox-dead-click-991409

Conversation

@posthog

@posthog posthog Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Problem

  • A person on the self-driving onboarding takeover clicks an example card and nothing visible happens. The card is the one screen whose job is to get them to run the setup command, and the click still dead-ends weeks after an earlier fix.
  • The earlier fix changed how the card looks (an "Example" tag, an inert card, an overlay button), but the overlay's click only played a sound. A sound changes no DOM, so PostHog still counts it as a dead click. The one explanation lived in a tooltip, which never shows on touch or a fast click.
  • The "Tool off" badge in the signal sources roster has the same shape: a bare warning tag with no click handler, while the "Turn it on" button that fixes the state sits one disclosure level below.

Changes

  • Clicking an example card now scrolls to the setup command and pulses a ring around it, so the click has a visible answer. The meep stays as flair on top.
  • The "Tool off" badge is now a clickable warning tag that turns the tool on, the same action the row expansion already offers. It shows a spinner while enabling and carries a tooltip explaining why the tool matters.
  • Mechanical: the setup command gets a wrapper element so the click target has a ref to scroll to and pulse; example previews take an onExampleClick callback.

How did you test this code?

  • Added InboxOnboarding.test.tsx: it asserts that a click on an example card scrolls to the setup command and adds the pulse class. This catches the exact regression — a click that leaves the DOM unchanged (a dead click).
  • Ran the new test and the existing ReportCard preview tests; both pass.
  • Not done by the agent: no manual browser check of the pulse animation or the roster badge. The pulse is CSS with a prefers-reduced-motion fallback, and the scroll still fires under reduced motion, so the click is never silent.

Automatic notifications

  • Publish to changelog?

Docs update

None.

🤖 Agent context

Autonomy: Fully autonomous

  • Investigated both surfaces, confirmed the earlier onboarding fix only changed appearance, and fixed the click response on both.
  • No customer material is in this PR; the sample card copy is the existing invented Silicon Valley fixture.

Created with PostHog Desktop from this inbox report.

The self-driving onboarding example cards and the "Tool off" badge in the signal
sources roster both took a click and changed nothing visible, so both read as dead clicks.

Example cards now scroll to the setup command and pulse it on click, instead of only
playing a sound. The "Tool off" badge is now a clickable warning tag that turns the tool
on, the same action the row expansion offers one disclosure level below.

Generated-By: PostHog Desktop
Task-Id: d840838a-874a-4643-b2ef-0a9e06346966
@trunk-io

trunk-io Bot commented Aug 26, 2026

Copy link
Copy Markdown

Merging to master in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@posthog

posthog Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

🦔 ReviewHog reviewed this pull request

Found 0 must fix, 3 should fix, 1 consider.

Published 4 findings (view the review).

Resolved comments: 2 fixed, 1 declined, 1 left for you

@github-actions

github-actions Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

🤖 CI report

Trunk lane — non-backend lane

This PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes.

⚠️ Bundle size — 🔺 +1.1 KiB (+0.0%)

Uncompressed size of every built .js bundle, compared against the base branch.

Total: 68.40 MiB · 🔺 +1.1 KiB (+0.0%)

No file changed by more than 1000 B.

Posted automatically by build-bundle-size-report · uncompressed bytes from dist-report

Eager graph — within budget

How much code each root ships on the eager path — downloaded and parsed before the surface is interactive. Measured from the esbuild output chunks (post-tree-shake, static imports only); lazy import() / React.lazy chunks are not counted.

Root Eager (shipped) Δ vs base Budget
entry (logged-out pages, app bootstrap)
src/index.tsx
1.36 MiB · 22 files 🔺 +336 B (+0.0%) ███░░░░░░░ 30.2% of 4.51 MiB
authenticated shell (every logged-in page)
src/scenes/AuthenticatedShell.tsx
8.77 MiB · 3,236 files 🔺 +104 B (+0.0%) █████████░ 90.3% of 9.71 MiB

🟢 node_modules/monaco-editor/ stays out of src/index.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 [object Object] stays out of src/index.tsx
🟢 node_modules/monaco-editor/ stays out of src/scenes/AuthenticatedShell.tsx
🟢 src/lib/components/ActivityLog/describers stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx
🟢 [object Object] stays out of src/scenes/AuthenticatedShell.tsx

Largest files eagerly shipped from src/index.tsx
Size File
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
24.6 KiB ../node_modules/.pnpm/buffer@6.0.3/node_modules/buffer/index.js
6.3 KiB ../node_modules/.pnpm/react@18.3.1/node_modules/react/cjs/react.production.min.js
4.5 KiB ../node_modules/.pnpm/@jspm+core@2.1.0/node_modules/@jspm/core/nodelibs/browser/process.js
3.9 KiB ../node_modules/.pnpm/scheduler@0.23.2/node_modules/scheduler/cjs/scheduler.production.min.js
1.4 KiB ../node_modules/.pnpm/base64-js@1.5.1/node_modules/base64-js/index.js
1.3 KiB src/RootErrorBoundary.tsx
912 B ../node_modules/.pnpm/ieee754@1.2.1/node_modules/ieee754/index.js
789 B src/scenes/ChunkLoadErrorBoundary.tsx
762 B src/index.tsx
Largest files eagerly shipped from src/scenes/AuthenticatedShell.tsx
Size File
306.5 KiB ../node_modules/.pnpm/posthog-js@1.418.17/node_modules/posthog-js/dist/rrweb.js
267.7 KiB ../node_modules/.pnpm/@posthog+icons@0.38.0_react-dom@18.3.1_react@18.3.1__react@18.3.1/node_modules/@posthog/icons/dist/posthog-icons.es.js
258.5 KiB ../node_modules/.pnpm/posthog-js@1.418.17/node_modules/posthog-js/dist/module.js
246.0 KiB src/taxonomy/core-filter-definitions-by-group.json
154.2 KiB ../node_modules/.pnpm/re2js@0.4.1/node_modules/re2js/build/index.esm.js
126.8 KiB ../node_modules/.pnpm/react-dom@18.3.1_react@18.3.1/node_modules/react-dom/cjs/react-dom.production.min.js
104.6 KiB src/lib/api.ts
95.1 KiB ../packages/quill/packages/quill/dist/index.js
93.3 KiB ../node_modules/.pnpm/prosemirror-view@1.40.1/node_modules/prosemirror-view/dist/index.js
90.6 KiB ../node_modules/.pnpm/@tiptap+core@3.20.6_@tiptap+pm@3.20.6/node_modules/@tiptap/core/dist/index.js

Posted automatically by check-eager-graph · sizes are eager output bytes (shipped, post-tree-shake) from the esbuild metafile · part of #32479

Toolbar bundle — eager 2.25 MiB within budget

What the toolbar ships to customer pages, measured from the esbuild output (minified, post-tree-shake). The eager set is the entry plus everything statically imported from it — fetched before any feature runs; deferred chunks load lazily. The eager guardrail is 5.72 MiB. Each output file must also stay below 10 MB, where CloudFront stops compressing it. The module boundary is enforced separately by check-toolbar-graph.

Metric Size Δ vs base Budget
Eager (shipped)
entry + static imports
2.25 MiB · 17 files 🔺 +109 B (+0.0%) ████░░░░░░ 39.3% of 5.72 MiB
Deferred (lazy) 2.09 MiB · 33 files no change n/a — loads on demand
Loader dist/toolbar.js 1.1 KiB no change █░░░░░░░░░ 5.8% of 19.5 KiB
Largest eagerly-shipped chunks
Size File
746.2 KiB dist/toolbar/toolbar-app-JDJU4DRU.css
582.9 KiB dist/toolbar/chunk-chunk-WSQWAVBM.js
484.6 KiB dist/toolbar/chunk-chunk-6S5AAQW2.js
133.8 KiB dist/toolbar/chunk-chunk-FUPUKVGN.js
131.8 KiB dist/toolbar/chunk-chunk-T5KY5WYR.js
71.3 KiB dist/toolbar/toolbar-app-OJAF4RZ6.js
69.0 KiB dist/toolbar/chunk-chunk-27JL52RE.js
35.6 KiB dist/toolbar/chunk-chunk-6QYURFVS.js
20.9 KiB dist/toolbar/chunk-chunk-RLNGEW4O.js
12.2 KiB dist/toolbar/chunk-chunk-PIK3PADE.js

Posted automatically by check-toolbar-size · sizes are toolbar output bytes (shipped, post-tree-shake) from the esbuild metafile

Dist folder size — 🔺 +71.5 KiB (+0.0%)

Total size of the built frontend/dist folder (all assets), compared against the base branch.

Total: 1442.02 MiB · 🔺 +71.5 KiB (+0.0%)

@posthog

posthog Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

ReviewHog Alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ReviewHog Report

Found 3 should fix, 1 consider.

Comment on lines +503 to +516
type="warning"
size="small"
forceClickable
icon={enablingTool ? <Spinner /> : undefined}
onClick={(e) => {
e.stopPropagation()
if (!enablingTool) {
onEnableTool(tool)
}
}}
>
Turn it on
</LemonTag>
</Tooltip>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The new tool action cannot be used with a keyboard

should_fix

Why we think it's a valid issue
  • Checked: The shared component at frontend/src/lib/lemon-ui/LemonTag/LemonTag.tsx, the new control at products/signals/frontend/inbox/components/config/AgentsRoster.tsx:498-516, the row container and the expansion body in the same file, and every forceClickable call site in the repository.
  • Found: The premise is exact. LemonTag sets role={isClickable ? 'button' : undefined} at LemonTag.tsx:85 on a plain <div>. A grep for tabIndex, onKeyDown, onKeyUp, and onKeyPress across that file returns nothing. The element therefore takes a button role, but it accepts no keyboard focus and it handles no key press.
  • Found: The new control does reach that branch. forceClickable at AgentsRoster.tsx:505 overrides the tooltip-trigger guard in LemonTag.tsx:71, so isClickable resolves true and the button role is applied.
  • Found: The same file already holds a keyboard-operable equivalent. AgentsRoster.tsx:312-319 renders a real LemonButton with the same "Turn it on" label, the same onEnableTool(tool) call, and loading={enablingTool}. LemonButton is already imported and is used again for the "Connect" action in the same row, so the swap the reviewer proposes is local and cheap.
  • Found: The keyboard path to that expansion button does not exist either. The row container at AgentsRoster.tsx:469-473 is a plain div with onClick={onExpand} and no role, no tabIndex, and no key handler. This code sits outside the diff, so the roster was already closed to keyboard users before this change.
  • Found: The shape is a house-wide pattern, not a local slip. The repository holds about 55 clickable LemonTag usages, and 15 call sites across frontend/src and products opt in through the dedicated forceClickable prop.
  • Impact: There is a small new harm. The slot previously held an inert tag with no role. It now announces a button that a screen-reader user can land on through button navigation and then cannot activate. A control that names itself a button and does nothing is worse for assistive technology than a plain label.
  • Impact: No keyboard user loses a capability. The badge duplicates an action that was already out of keyboard reach, because the row that discloses it never accepted focus. So this change does not remove a working path; it fails to add one.
  • Priority: Lowered to consider. The diagnosis is verified and the harm is real, so the finding stays on record. The root cause sits in the shared LemonTag, where a proper fix serves all 55 clickable usages, and the surrounding row is already not operable by keyboard. Repairing this one badge would still leave keyboard users unable to work the roster, which puts the item below the should_fix bar for this PR.
Issue description

LemonTag renders a div with role="button". It does not add tabIndex or keyboard activation. Keyboard users cannot focus or activate this new action.

Suggested fix

Use a LemonButton styled for this compact warning action. If LemonTag must remain, add focus support and handle Enter and Space activation.

Prompt to fix with AI (copy-paste)
## Context
@products/signals/frontend/inbox/components/config/AgentsRoster.tsx#L503-516

<issue_description>
`LemonTag` renders a `div` with `role="button"`. It does not add `tabIndex` or keyboard activation. Keyboard users cannot focus or activate this new action.
</issue_description>

<issue_validation>
- **Checked:** The shared component at `frontend/src/lib/lemon-ui/LemonTag/LemonTag.tsx`, the new control at `products/signals/frontend/inbox/components/config/AgentsRoster.tsx:498-516`, the row container and the expansion body in the same file, and every `forceClickable` call site in the repository.
- **Found:** The premise is exact. `LemonTag` sets `role={isClickable ? 'button' : undefined}` at `LemonTag.tsx:85` on a plain `<div>`. A grep for `tabIndex`, `onKeyDown`, `onKeyUp`, and `onKeyPress` across that file returns nothing. The element therefore takes a button role, but it accepts no keyboard focus and it handles no key press.
- **Found:** The new control does reach that branch. `forceClickable` at `AgentsRoster.tsx:505` overrides the tooltip-trigger guard in `LemonTag.tsx:71`, so `isClickable` resolves true and the button role is applied.
- **Found:** The same file already holds a keyboard-operable equivalent. `AgentsRoster.tsx:312-319` renders a real `LemonButton` with the same "Turn it on" label, the same `onEnableTool(tool)` call, and `loading={enablingTool}`. `LemonButton` is already imported and is used again for the "Connect" action in the same row, so the swap the reviewer proposes is local and cheap.
- **Found:** The keyboard path to that expansion button does not exist either. The row container at `AgentsRoster.tsx:469-473` is a plain `div` with `onClick={onExpand}` and no role, no `tabIndex`, and no key handler. This code sits outside the diff, so the roster was already closed to keyboard users before this change.
- **Found:** The shape is a house-wide pattern, not a local slip. The repository holds about 55 clickable `LemonTag` usages, and 15 call sites across `frontend/src` and `products` opt in through the dedicated `forceClickable` prop.
- **Impact:** There is a small new harm. The slot previously held an inert tag with no role. It now announces a button that a screen-reader user can land on through button navigation and then cannot activate. A control that names itself a button and does nothing is worse for assistive technology than a plain label.
- **Impact:** No keyboard user loses a capability. The badge duplicates an action that was already out of keyboard reach, because the row that discloses it never accepted focus. So this change does not remove a working path; it fails to add one.
- **Priority:** Lowered to `consider`. The diagnosis is verified and the harm is real, so the finding stays on record. The root cause sits in the shared `LemonTag`, where a proper fix serves all 55 clickable usages, and the surrounding row is already not operable by keyboard. Repairing this one badge would still leave keyboard users unable to work the roster, which puts the item below the `should_fix` bar for this PR.
</issue_validation>

## Task
Investigate the issue and solve it

<potential_solution>
Use a `LemonButton` styled for this compact warning action. If `LemonTag` must remain, add focus support and handle Enter and Space activation.
</potential_solution>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a real gap and worth fixing — I've left it for a human to choose the approach rather than pick one unattended. Confirmed: the badge renders with a button role (via forceClickable) but LemonTag adds no keyboard focus or Enter/Space handling, so keyboard and screen-reader users can land on it and not activate it. It matters more than the badge alone suggests: turning the tool on is currently mouse-only, because the equivalent 'Turn it on' button lives inside the row expansion, and the row only expands on a mouse click (a plain clickable div with no keyboard support — pre-existing, outside this PR).

The reason it needs a human call is that the three sensible fixes each carry a trade-off:

  • Swap the badge to a LemonButton styled as a compact warning action (the reviewer's preference, and it matches our 'interactive elements are real buttons' convention). This changes the deliberate warning-tag look and should get a quick visual check in the row.
  • Keep the tag and add tabIndex + Enter/Space handling at this call site. Smallest change, but it doubles down on a clickable-div pattern our frontend guide explicitly says to avoid.
  • Fix it once in the shared LemonTag so all ~55 clickable tags become keyboard-operable. Best long-term, but it's a shared-component change well beyond this PR and needs its own review.

My recommendation: go with the LemonButton swap here (it's local, matches the existing expansion button, and is convention-compliant), and file the shared LemonTag keyboard-support fix separately. The row-expansion keyboard gap is a separate pre-existing issue worth its own ticket.

Comment thread products/signals/frontend/inbox/components/config/AgentsRoster.tsx
The `jest-no-byrole-name-queries` semgrep rule blocks role+name queries because they are slow in jsdom. The overlay button carries an aria-label, so `getAllByLabelText` is the preferred query.

Generated-By: PostHog Desktop
Task-Id: d840838a-874a-4643-b2ef-0a9e06346966
@stamphog

stamphog Bot commented Aug 26, 2026

Copy link
Copy Markdown

The new "Tool off" badge is clickable and takes a button role via forceClickable, but ReviewHog flagged it as unusable via keyboard/screen reader, and the bot's own reply confirms this is a real gap and deliberately left unaddressed pending a human's choice among three fix options — a substantive, unresolved accessibility regression introduced by this PR.

  • Unresolved ReviewHog comment on AgentsRoster.tsx: the new clickable 'Tool off' warning tag gets role=button via forceClickable but LemonTag adds no tabIndex/keyboard handling, so keyboard and screen-reader users can't activate it — explicitly confirmed as a real, unaddressed gap by the reviewing bot's own reply, which leaves the choice of fix (swap to LemonButton, add key handlers locally, or fix LemonTag globally) to a human.
  • The second should-fix comment (concurrent tool enables losing loading state) does appear addressed in this diff via the enablingTool -> enablingTools Set change and the added concurrent-enable test, but the keyboard-access issue has no corresponding code change.
Gate mechanics and policy version
Gate Result
prerequisites all clear
deny-list no deny categories matched
size 212L, 5F substantive, 272L/7F incl. docs/generated/snapshots — within ceiling
tier T1-agent / T1c-medium (272L, 7F, single-area, fix)
stamphog 2.0.0b4 .stamphog/policy.yml @ 8dbb8ad · reviewed head 8dbb8ad

@stamphog stamphog Bot added the reviewhog ($$$) Reviews pull requests before humans do label Aug 26, 2026
posthog Bot added 2 commits August 26, 2026 17:36
The example-card click relied on a pulse animation and a smooth scroll to
answer the click. Under prefers-reduced-motion the pulse was disabled with no
substitute, and the scroll can be a no-op when the command is already in view,
so the click had no visible answer for those users.

Keep the accent ring as a static box-shadow in the reduced-motion block, and
jump the scroll instead of animating it when reduced motion is set.

Generated-By: PostHog Desktop
Task-Id: 9dce5554-e7e0-477d-b2ca-8db9bde7771b
The roster now shows a clickable "Turn it on" badge on every collapsed row
whose tool is off, so several enable controls can be on screen at once. The
in-flight state was a single slot, so a second click overwrote the first: the
first badge lost its spinner and reopened its guard while its request was still
running, which then accepted a duplicate enable request.

Hold the in-flight enablements in a set keyed by enablement, add one on start
and remove it on completion, and derive each row's loading state from set
membership. Each badge now keeps its own spinner and stays guarded against
double-submission until its own request finishes.

Adds a logic test for the concurrent case and updates the existing enablement
test to the new set-shaped state.

Generated-By: PostHog Desktop
Task-Id: 9dce5554-e7e0-477d-b2ca-8db9bde7771b
@trunk-io

trunk-io Bot commented Aug 26, 2026

Copy link
Copy Markdown

Static BadgeStatic BadgeStatic Badge

View Full Report ↗︎Docs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

reviewhog ($$$) Reviews pull requests before humans do

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants