Skip to content

fix(errors): report SuspenseWrapper errors to Bugsnag + SecretsSettings adopts SuspenseWrapper - #2623

Merged
camielvs merged 2 commits into
masterfrom
automated-gardening/consistency/2026-W33
Aug 19, 2026
Merged

fix(errors): report SuspenseWrapper errors to Bugsnag + SecretsSettings adopts SuspenseWrapper#2623
camielvs merged 2 commits into
masterfrom
automated-gardening/consistency/2026-W33

Conversation

@camielvs

@camielvs camielvs commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Two changes. The second is the one to review.

1. SecretsSettings adopts SuspenseWrapper

It held the last bare inline <Suspense> in non-test source; 46 files already use the canonical
wrapper. Same skeleton, so the loading state is unchanged. It gets a custom error fallback — an
InfoBox with the message and a Try Again button — because an icon-only retry isn't enough for a
whole settings panel.

2. SuspenseWrapper now reports caught errors to Bugsnag

New reportError() helper in bugsnag.ts, wired into SuspenseWrapper via onError. ErrorPage is
refactored onto the same helper with no behaviour change.

This is app-wide: 54 boundary sites across 47 files start reporting errors that previously went
nowhere.
That's intended, but it's a real increase in Bugsnag volume and the main thing to sign off on.

Sizing: one event per boundary failure (react-query retries 3× before throwing, so it's not one per
attempt), and existing Bugsnag grouping and suppression already apply since they run for manual
notifies too. Events are tagged boundary: "SuspenseWrapper" with a component stack, so the new
volume is filterable.

What to check

  • The Bugsnag rollout is wanted — this is the item that should block merge if not
  • Errors in the Secrets panel now render a local retry instead of taking out the page (intended,
    but it is a visible change)
  • Loading state untouched
  • Known gap: the new reporting has no test. Ask and I'll add one.
  • Gardening self-review was skipped (review skill unavailable) — review manually

pnpm run validate:test ✅ 192 files / 1,972 passed

Notes

  • Not migrated: PreviewTaskNodeCard.tsx:32 (renders nothing on error by design — migrating
    wouldn't preserve behaviour), the app-shell boundary at index.tsx:32, and router-owned
    pendingComponent / errorComponent.
  • Not fixed here: the error messages in secretsStorage.ts interpolate a ReadableStream instead
    of the response text, so they read [object ReadableStream]. Harmless but useless — worth a separate
    one-line fix.
  • For the registry (not applied): 222 files import the canonical Icon, 39 still import from
    lucide-react — 85% supermajority, but no registry entry blesses it yet, so it's a proposal only.
    This PR touches none of them.

@camielvs camielvs added the automated-gardening Automated codebase gardening label Aug 13, 2026
@camielvs
camielvs requested a review from a team August 13, 2026 19:17
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown

🎩 Preview

A preview build has been created at: automated-gardening/consistency/2026-W33/0ea5cfa

Copy link
Copy Markdown
Collaborator Author

I have updated the registry skill to encourage Icon migration in #2627. Once merged, future Gardening sweeps will migrate Lucide Icon calls to icon primitive.

@camielvs
camielvs marked this pull request as ready for review August 13, 2026 21:21
Comment thread src/routes/Settings/sections/SecretsSettings.tsx Outdated

@morgan-wowk morgan-wowk 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.

🤖 Agent review (perf / behaviour / breaking / UX / security). One app-wide behaviour change worth confirming before merge (inline). Also: the PR description says "1 file, +3/-3", but the diff is 4 files / +65/-15 — the SuspenseWrapper change below isn't mentioned in the summary.

Comment thread src/components/shared/SuspenseWrapper.tsx
camielvs added a commit that referenced this pull request Aug 17, 2026
Actions **B17** in the gardening queue (#2626): blesses `Icon` as the canonical way to render an icon, so
the `consistency` pillar is allowed to migrate the stragglers instead of only proposing.

Two files, docs only. **No `src` changes** — the migration itself is a later, reviewable PR.

## Why it needed a human

The empirical bar was already met — **222** files import `Icon`, **30** still import `lucide-react`
directly (88.1%, over `supermajorityRatio: 0.75`), and **10 of the 30 do both**, which reads as drift
rather than intent. But `pillars/consistency.md` forbids migrating on measurement alone: without a
registry entry that would be the bot inventing a convention and enforcing it repo-wide. Hence the entry.

## Non-Lucide icons are exceptions, not stragglers

`Icon`'s `name` is `keyof typeof icons` from `lucide-react`, so it **structurally cannot** render anything
else. Recorded as KEEP so no future run "fixes" them:

- **`react-icons` brand/language logos** — `FaPython` ×2, `SiGnubash`, `SiRuby`, `TbBrandJavascript`,
  `FaGoogleDrive` across 3 files. Lucide dropped brand logos; there is no equivalent to migrate to.
- **`src/components/ui/**`** — 9 files. shadcn primitives (`dialog`, `select`, `sheet`, `command`,
  `calendar`, `checkbox`, `breadcrumb`, `date-picker`) import their own glyphs and are CLI-regenerable, so
  a hand edit is overwritten by the next `shadcn add`. `icon.tsx` *is* the primitive.
- **Raw `<svg>` that isn't an icon** — `<defs>`/`<marker>` arrowheads in `FlowCanvas/Edges/*`, the
  `Spinner` primitive's own markup, and the bespoke resize grip in `FloatingWindow.tsx`.

## The swap is not size-neutral, so the entry is not blanket `apply: yes`

Worth reading before approving, because it contradicts the "mechanical swap" framing in #2623:

`iconVariants` emits `!w-4 !h-4`. Leading-`!` **still compiles to `!important`** in Tailwind v4.3.3
(verified by compiling `!w-4` with the project's own Tailwind: `.\!w-4 { width: …; !important }`), and
`twMerge@3.6.0` does **not** dedupe `!w-4` against a later `size-5` — it emits both, so `!important` wins.
A blind `<ChevronRight className="size-5" />` → `<Icon name="ChevronRight" className="size-5" />` renders
**16px instead of 20px**.

Across the 30 files there are **59** Lucide JSX usages:

| Bucket | Count | Disposition |
| --- | --- | --- |
| Size class on the scale (`size-4`, `w-5 h-5`, …) | 37 | `apply: yes` — size moves to the `size` prop |
| **No** size class | 9 | `apply: yes` **as `size="xl"`** — Lucide's own default is `width: 24` (`lucide-react.js:39`), so `size="md"` would shrink them |
| Off-scale (`w-2 h-2` ×6, `h-8 w-8`, `w-12 h-12`) | 8 | `apply: flag` — no variant matches and `className` can't beat `!important` |
| Computed `className={cn(…)}` | 5 | `apply: flag` — size not statically readable |

So ~46 of 59 usages are provably size-identical; the other 13 need a human. The off-scale ones are mostly
`StatusIndicator.tsx` at `w-2 h-2` (8px, below `xs`) — if you'd rather they migrate too, the `Icon` scale
needs a step, which is a primitive change and deliberately not in this PR.

## Also fixes a claim I got wrong

#2623 said there was "no `canon-registry.md` entry **and** no convention-skill rule naming `Icon` as
canonical." The registry entry was indeed missing; the convention rule was not — `ui-primitives#icons`
already said "Use `Icon` from `@/components/ui/icon`". That line was one sentence with no `size` guidance,
which is how the `!important` interaction went unnoticed, so this PR fills it in: the size scale, the
warning that a size `className` loses to the variant, and the `react-icons` escape hatch.

## Checks

`prettier --check` passes on both files. No `src`, test, or config changes, so `lint`/`typecheck`/`test`
are unaffected — I did not run the full `validate:test` gate because its `fix` step rewrites the working
tree, and there is no code here for it to validate.
@camielvs camielvs changed the title garden(consistency): weekly groundskeeping — 2026-W33 fix(errors): report SuspenseWrapper errors to Bugsnag + SecretsSettings adopts SuspenseWrapper Aug 18, 2026
@camielvs

Copy link
Copy Markdown
Collaborator Author

Both good catches — thanks.

The Bugsnag rollout is intended, and you're right that it deserved its own billing rather than
riding along with a consistency migration. I've rewritten the PR title and description to lead with
it, including the scope (54 boundary sites across 47 files, previously reporting nothing) and the
factors that bound the volume.

The secret-echo concern doesn't apply here. The error messages on that path interpolate a
ReadableStream rather than the response text, so no backend content ever reaches the message —
nothing rendered, nothing transmitted. It does mean those messages are useless for debugging, which
is worth a separate fix; I've left it out of this PR and noted why in the description.

One gap I found and flagged rather than hid: the new reporting has no test coverage. Happy to add one
if you'd like it before merge.

@camielvs
camielvs requested a review from morgan-wowk August 18, 2026 20:58

@morgan-wowk morgan-wowk 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.

🤖 Agent-assisted re-review (posted by Morgan). Approving. My HIGH was 'confirm intended + volume-sized' on turning onError → Bugsnag reporting for ~48 SuspenseWrapper consumers app-wide — you've confirmed it's a deliberate net positive, and the PR title/body now describe the real scope (Bugsnag reporting + SecretsSettings adoption, 4 files) rather than the old 1-file summary, so the surprise is gone. Deferring to your call on volume. One residual, non-blocking: on the secrets fallback path error.message is now both rendered and shipped to Bugsnag — worth a glance someday that backend errors there can't echo secret-adjacent content, but nothing to hold this PR on.

camielvs commented Aug 19, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Aug 19, 12:04 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Aug 19, 12:05 AM UTC: Graphite rebased this pull request as part of a merge.
  • Aug 19, 12:08 AM UTC: Graphite rebased this pull request as part of a merge.
  • Aug 19, 12:15 AM UTC: Graphite rebased this pull request as part of a merge.
  • Aug 19, 12:18 AM UTC: @camielvs merged this pull request with Graphite.

@camielvs
camielvs force-pushed the automated-gardening/consistency/2026-W33 branch 2 times, most recently from 7790d72 to 6b6a174 Compare August 19, 2026 00:07
camielvs and others added 2 commits August 19, 2026 00:14
…atterns#suspense

The only bare inline <Suspense> left in non-test source. 46 files use the
canonical SuspenseWrapper. Fallback is preserved exactly; the wrapper adds the
QueryErrorResetBoundary + ErrorBoundary pair the bare form dropped.

canon-registry: async-boundary (apply: yes)
Swapping Suspense for SuspenseWrapper put a boundary in front of the
router's errorComponent, and SuspenseWrapper had no onError — so render
errors in this subtree stopped reaching Bugsnag and degraded to a 16px
MonitorX icon button where the settings panel used to be.

- add onError to SuspenseWrapper, closing the reporting gap for all of
  its call sites rather than just this one
- extract reportError() into the bugsnag service and reuse it from
  ErrorPage, which had the same notify-with-pathname logic inline
- pass a page-scale errorFallback from SecretsSettings: the error text
  in an InfoBox plus a Try Again that resets the boundary

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@camielvs
camielvs force-pushed the automated-gardening/consistency/2026-W33 branch from 6b6a174 to 0ea5cfa Compare August 19, 2026 00:14
@camielvs
camielvs merged commit 6980829 into master Aug 19, 2026
16 checks passed
@camielvs
camielvs deleted the automated-gardening/consistency/2026-W33 branch August 19, 2026 00:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-gardening Automated codebase gardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants