mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-06 02:48:25 +00:00
## Summary Improves the web UI's React Doctor audit score by separating reusable helpers from React component modules, tightening effect/state ownership, and extracting real component boundaries in the install wizard, stage activity view, run-files diff browser, RunDetail route, and Runs workspace. The branch removes the previously deferred RunDetail and Runs giant-component diagnostics without changing RunDetail UX, route contracts, action ordering, or Runs workspace behavior. | Metric | Main baseline | Initial PR | Current PR | |--------|---------------|------------|------------| | React Doctor score | 63 | 71 | 99 | | React Doctor errors | 123 | 0 | 0 | | React Doctor warnings | 241 | 163 | 3 | | React Doctor diagnostics | 364 | 163 | 3 | ## Changes - Moves exported helper logic out of component files so Fast Refresh/component-export rules no longer dominate the audit. - Adds a targeted React Doctor config exception for React Router route modules, where non-component exports like route metadata are intentional. - Refactors low-risk state/effect patterns: keyed interview question state, reducer-backed editable run title state, event-owned preview opening, route-keyed insights editor initialization, refresh timer ownership, and selection/derived list cleanup. - Reworks `InstallApp` around an install reducer, a controller hook for install lifecycle state, and focused wizard step components for LLM, server, object-store, sandbox, and GitHub setup. - Moves `RunStages` selected-stage activity into a keyed boundary for panel/debug detail state while preserving stage activity filters across navigation. - Extracts the `RunFiles` loaded diff-browser view from route/query coordination so the route owns data/URL state and the loaded view owns rendering. - Splits `RunDetail` into route-local header, actions, tab shell, docked controls, model, and lifecycle-toast modules; the actions menu now uses grouped descriptors instead of a large boolean/callback prop matrix. - Extracts Runs workspace preference ownership into `useRunsWorkspacePreferences` and moves toolbar rendering into `RunsToolbar`, leaving the route focused on data, DnD state, filtering, and view selection. - Guards `InsightsEditor` query execution with a latest-run id and timeout cleanup so stale or unmounted mock query runs cannot overwrite newer results. - Adds regression coverage for archived-run deletion from RunDetail and stale-result handling in InsightsEditor. - Improves semantic/accessibility coverage with labeled controls, native meter/section semantics, decorative status dots, and clearer unavailable copy. - Removes dead UI code and applies local suppressions only where the rule is a documented false positive or an intentional imperative integration boundary. ## Remaining React Doctor warnings Current score is 99 with 0 errors and 3 warnings. The remaining warnings are intentionally left for separate judgment rather than mechanical churn: - `prefer-useReducer` (3): `AutomationsNew`, `InsightsEditor`, and `CreateSecretForm` need reducers only if they encode real coupled transitions, not simple field setters. ## Verification - `cd apps/fabro-web && bun test app/routes/run-detail.test.ts` -> `22 pass`, `0 fail` - `cd apps/fabro-web && bun test app/routes/insights-editor.test.tsx app/routes/runs.preferences.test.tsx` -> `7 pass`, `0 fail` - `cd apps/fabro-web && bun run typecheck` - `cd apps/fabro-web && bun test --isolate` -> `490 pass`, `0 fail` - `cd apps/fabro-web && bunx react-doctor@latest --full --json > /tmp/fabro-react-doctor-runs-insights.json` -> score `99`, `0` errors, `3` warnings - Earlier branch verification also included `cd apps/fabro-web && bun run build` - `git diff --check` --- [](https://github.com/EveryInc/compound-engineering-plugin) 🤖 Generated with GPT-5 (context not reported, default reasoning) via [Codex](https://openai.com/codex)
77 lines
2.2 KiB
TypeScript
77 lines
2.2 KiB
TypeScript
import { ComputerDesktopIcon, UserCircleIcon } from "@heroicons/react/24/outline";
|
|
import { Link, Outlet, useLocation } from "react-router";
|
|
|
|
export function meta({}: any) {
|
|
return [{ title: "Profile — Fabro" }];
|
|
}
|
|
|
|
export const handle = { hideHeader: true };
|
|
|
|
type NavItem = {
|
|
name: string;
|
|
href: string;
|
|
icon: typeof UserCircleIcon;
|
|
match: (pathname: string) => boolean;
|
|
};
|
|
|
|
const navItems: NavItem[] = [
|
|
{
|
|
name: "Profile",
|
|
href: "/profile",
|
|
icon: UserCircleIcon,
|
|
match: (p) => p === "/profile",
|
|
},
|
|
{
|
|
name: "Sessions",
|
|
href: "/profile/sessions",
|
|
icon: ComputerDesktopIcon,
|
|
match: (p) => p.startsWith("/profile/sessions"),
|
|
},
|
|
];
|
|
|
|
function classNames(...classes: Array<string | false | null | undefined>) {
|
|
return classes.filter(Boolean).join(" ");
|
|
}
|
|
|
|
export default function ProfileLayout() {
|
|
const { pathname } = useLocation();
|
|
const currentName = navItems.find((item) => item.match(pathname))?.name ?? "Profile";
|
|
|
|
return (
|
|
<div className="flex flex-col gap-6 lg:flex-row">
|
|
<aside className="lg:w-56 lg:shrink-0">
|
|
<nav className="sticky top-6">
|
|
<ul className="flex gap-1 overflow-x-auto lg:flex-col lg:gap-0.5">
|
|
{navItems.map((item) => {
|
|
const current = item.match(pathname);
|
|
return (
|
|
<li key={item.name}>
|
|
<Link
|
|
to={item.href}
|
|
aria-current={current ? "page" : undefined}
|
|
className={classNames(
|
|
"flex items-center gap-2 rounded-md px-2.5 py-2 text-sm whitespace-nowrap transition-colors",
|
|
current
|
|
? "bg-overlay text-fg"
|
|
: "text-fg-3 hover:bg-overlay hover:text-fg",
|
|
)}
|
|
>
|
|
<item.icon className="size-4 shrink-0" aria-hidden="true" />
|
|
{item.name}
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</nav>
|
|
</aside>
|
|
|
|
<div className="min-w-0 flex-1">
|
|
<h1 className="mb-2 text-xl font-semibold tracking-tight text-fg">
|
|
{currentName}
|
|
</h1>
|
|
<Outlet />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|