fabro/apps/fabro-web/app/components
fabro-sh-0530[bot] c2da22a27c
Some checks are pending
Rust / Format (push) Waiting to run
Rust / Clippy (push) Waiting to run
Rust / Generated Docs (push) Waiting to run
Rust / Test (Linux) (push) Waiting to run
Rust / Test (macOS) (push) Waiting to run
TypeScript / Typecheck (push) Waiting to run
TypeScript / Test (push) Waiting to run
TypeScript / Build (push) Waiting to run
Replace DIY overlay primitives with Radix UI + Sonner (#424)
## Summary

Replaces ~285 lines of hand-rolled Tooltip, HoverCard, and Toast code in
`fabro-web` with battle-tested primitives — gaining real keyboard
accessibility, Radix collision detection, and Sonner's toast lifecycle —
while keeping all 13+ call sites unchanged.

### Plan Summary

- **Tooltip + HoverCard → Radix wrappers**: `@radix-ui/react-tooltip`
and `@radix-ui/react-hover-card` replace the DIY `useHoverAnchor` hook.
A `TooltipProvider` is mounted in `app-shell.tsx` (200ms delay, 300ms
skip-delay for grouped sidebar hovers). `<Tooltip>` self-wraps in a
local provider when rendered outside the shell (tests, isolated mounts).
- **Toast system → Sonner**: `toast.tsx` shrinks to a ~30-line shim
preserving the `{ push, dismiss, clear }` API. `ToastProvider` becomes a
no-op pass-through in DOM contexts; in non-DOM test environments it
renders an `aria-live` fallback backed by `useSonner` so test assertions
still work. The `action` field is dropped (was test-only).
`toast.test.tsx` is rewritten against observable rendered text.
- **CSS-only tooltips → `<Tooltip>`**: Two inline `group-hover/*` blocks
in `settings-models.tsx` are swapped for the new wrapper, gaining
keyboard focus + Esc dismiss + collision avoidance.
- **SVG-anchored hovers → `FloatingTooltip`**: A new
`app/components/floating-tooltip.tsx` helper portals to `document.body`
and computes collision-avoiding `top`/`bottom` placement from a raw
`DOMRect` (no wrappable trigger). It absorbs `hover-card-style.ts`
(deleted) and is used by `run-overview.tsx` and `event-debug.tsx`.

### What changed and why

**`FloatingTooltip`** handles the two SVG/Graphviz hover sites where
there is no React trigger element to wrap — only a `DOMRect` measured
from DOM events. It uses `useLayoutEffect` + `ResizeObserver` to measure
its own rendered size before applying final position, so it never clips
at viewport edges. This is the one place a `useLayoutEffect` is
intentional and documented.

**`Tooltip` provider fallback**: Radix throws if `<Tooltip>` renders
without an ancestor `TooltipProvider`. Rather than requiring every test
to mount the shell, the component detects provider presence via context
and injects a local one when needed.

**Toast shim backward-compat**: `ToastProvider` previously accepted
`autoDismissMs` as a prop; that prop is silently dropped. The `action`
field on `ToastInput` is removed (only one test referenced it —
`run-detail.test.ts` is updated accordingly). All other consumers
compile without changes.

**CSP fix** (bundled): `img-src` gains
`https://avatars.githubusercontent.com` to allow GitHub avatar images,
with the corresponding integration-test assertion updated.


### Fabro Details

<details>
<summary>Ran 8 stages in 54m 1s for $32.86</summary>

| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 6s | – | 0 |
| preflight_lint | 2m 23s | – | 0 |
| implement | 21m 38s | $22.48 | 0 |
| simplify_opus | 13m 51s | $7.00 | 0 |
| simplify_gpt | 3m 54s | $3.38 | 0 |
| verify | 9m 35s | – | 0 |
| **Total** | **54m 1s** | **$32.86** | **0** |

</details>

<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
edges)</summary>

```dot
digraph ImplementPlan {
    graph [
        goal="Implement and simplify",
        model_stylesheet="
            * { model: claude-opus-4-7; }
        "
    ]
    rankdir=LR

    start [shape=Mdiamond, label="Start"]
    exit  [shape=Msquare, label="Exit"]

    toolchain         [label="Toolchain", shape=parallelogram, script="command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1", max_retries=0]
    preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
    preflight_lint    [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
    fix_lints         [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
    implement         [label="Implement", prompt="Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.", model="gpt-55", reasoning_effort="xhigh"]
    simplify_opus     [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
    simplify_gpt      [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
    verify            [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"]
    fixup             [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]

    start -> toolchain
    toolchain -> preflight_compile [condition="outcome=succeeded"]
    toolchain -> exit
    preflight_compile -> preflight_lint [condition="outcome=succeeded"]
    preflight_compile -> exit
    preflight_lint -> implement [condition="outcome=succeeded"]
    preflight_lint -> fix_lints
    fix_lints -> preflight_lint
    implement -> simplify_opus -> simplify_gpt -> verify
    verify -> exit  [condition="outcome=succeeded"]
    verify -> fixup
    fixup -> verify
}

```

</details>

⚒️ Generated with [Fabro](https://fabro.sh)

---------

Co-authored-by: Fabro <noreply@fabro.sh>
2026-05-26 23:03:06 -04:00
..
chats refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
runs-list feat(web): add "Created by" avatar column to runs list view 2026-05-26 09:28:20 -04:00
stage-renderers refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
auth-layout.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
collapsible-file.tsx refactor(web): drop light mode, go dark-only 2026-04-20 09:27:08 -04:00
editable-run-title.test.tsx feat(web): inline-edit run titles in the run header 2026-05-09 11:54:41 -04:00
editable-run-title.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
event-debug-helpers.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
event-debug.tsx Replace DIY overlay primitives with Radix UI + Sonner (#424) 2026-05-26 23:03:06 -04:00
floating-tooltip.tsx Replace DIY overlay primitives with Radix UI + Sonner (#424) 2026-05-26 23:03:06 -04:00
graph-toolbar-constants.ts refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
graph-toolbar.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
icons.tsx feat(runs): link stored pull requests 2026-05-08 13:17:08 -07:00
inline-markdown.test.tsx feat(web): render inline Markdown in run titles 2026-05-08 18:30:51 -07:00
inline-markdown.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
interview-dock.test.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
interview-dock.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
interview-label.ts refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
pull-request-chip.test.tsx feat(pr): support GitHub pull request associations (#270) 2026-05-16 12:47:27 -04:00
pull-request-chip.tsx feat(pr): support GitHub pull request associations (#270) 2026-05-16 12:47:27 -04:00
run-summary-panel.test.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
run-summary-panel.tsx feat(web): add "Created by" avatar column to runs list view 2026-05-26 09:28:20 -04:00
run-waterfall.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
settings-panel.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
size-chip.tsx feat(web): add sortable Size column to runs list 2026-05-25 14:07:17 -04:00
stage-context.tsx feat(web): add Context tab to the stage detail view (#340) 2026-05-21 20:19:55 -04:00
stage-insights-sidebar.test.tsx refactor: rationalize server secret scopes (vault-only for optional int… (#401) 2026-05-25 17:26:01 -04:00
stage-insights-sidebar.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
stage-popover-summary.ts refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
stage-popover.test.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
stage-popover.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
stage-sidebar.test.tsx Surface reasoning_effort + speed in stage badge end-to-end (#363) 2026-05-23 13:14:55 -04:00
stage-sidebar.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
state.tsx feat(web): add "Test models" sweep button on settings/models (#410) 2026-05-25 22:42:16 -04:00
steer-bar.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
terminal-view-helpers.ts refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
terminal-view.test.ts refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
terminal-view.tsx refactor(web): improve React Doctor score (#405) 2026-05-25 22:41:37 -04:00
toast.test.tsx Replace DIY overlay primitives with Radix UI + Sonner (#424) 2026-05-26 23:03:06 -04:00
toast.tsx Replace DIY overlay primitives with Radix UI + Sonner (#424) 2026-05-26 23:03:06 -04:00
ui.tsx Replace DIY overlay primitives with Radix UI + Sonner (#424) 2026-05-26 23:03:06 -04:00