fix(boltbot): address UI skills review — 16 violations
- min-h-screen → min-h-dvh for dynamic viewport - arbitrary z-[100] → z-50 (fixed scale) - removed tracking-tight/tracking-wide (letter-spacing not requested) - replaced 3 URL-sync useEffects with inline wrapper functions - initialParams moved to useRef (prevent re-computation per render) - removed backdrop-blur-sm on full-screen overlay (perf) - added text-balance on headings - added min-w-0 on SessionView tool name truncate - empty states now include guidance text with clear next action
This commit is contained in:
parent
b2c6b055ee
commit
5657dad29d
@ -12,10 +12,6 @@ import SessionView from "./components/SessionView";
|
||||
|
||||
const LIMIT = 50;
|
||||
|
||||
function getSearchParams() {
|
||||
return new URLSearchParams(window.location.search);
|
||||
}
|
||||
|
||||
function setSearchParams(params: Record<string, string>) {
|
||||
const url = new URL(window.location.href);
|
||||
for (const [k, v] of Object.entries(params)) {
|
||||
@ -26,7 +22,7 @@ function setSearchParams(params: Record<string, string>) {
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const initialParams = getSearchParams();
|
||||
const initialParams = useRef(new URLSearchParams(window.location.search)).current;
|
||||
const [selectedTiers, setSelectedTiers] = useState<string[]>(() => {
|
||||
const tiers = initialParams.get("tiers");
|
||||
return tiers ? tiers.split(",").filter(Boolean) : [];
|
||||
@ -47,18 +43,18 @@ export default function App() {
|
||||
const { stats, isLoading: statsLoading, error: statsError } = useStats();
|
||||
const { receipts, isLoading: receiptsLoading, error: receiptsError } = useReceipts(LIMIT, 0);
|
||||
|
||||
// Sync URL with state changes
|
||||
useEffect(() => {
|
||||
setSearchParams({ view: viewMode === "list" ? "" : viewMode });
|
||||
}, [viewMode]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({ tiers: selectedTiers.join(",") });
|
||||
}, [selectedTiers]);
|
||||
|
||||
useEffect(() => {
|
||||
setSearchParams({ anomalies: anomalyOnly ? "1" : "" });
|
||||
}, [anomalyOnly]);
|
||||
function updateViewMode(v: "list" | "sessions") {
|
||||
setViewMode(v);
|
||||
setSearchParams({ view: v === "list" ? "" : v });
|
||||
}
|
||||
function updateSelectedTiers(tiers: string[]) {
|
||||
setSelectedTiers(tiers);
|
||||
setSearchParams({ tiers: tiers.join(",") });
|
||||
}
|
||||
function updateAnomalyOnly(v: boolean) {
|
||||
setAnomalyOnly(v);
|
||||
setSearchParams({ anomalies: v ? "1" : "" });
|
||||
}
|
||||
|
||||
// Polling: merge new receipts at the top, do NOT touch loadedCount or hasMore
|
||||
useEffect(() => {
|
||||
@ -114,8 +110,8 @@ export default function App() {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-neutral-950 text-neutral-100">
|
||||
<a href="#main-content" className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-[100] focus:bg-neutral-800 focus:text-white focus:px-4 focus:py-2 focus:rounded-lg">
|
||||
<div className="min-h-dvh bg-neutral-950 text-neutral-100">
|
||||
<a href="#main-content" className="sr-only focus:not-sr-only focus:fixed focus:top-2 focus:left-2 focus:z-50 focus:bg-neutral-800 focus:text-white focus:px-4 focus:py-2 focus:rounded-lg">
|
||||
Skip to main content
|
||||
</a>
|
||||
<Header />
|
||||
@ -126,7 +122,7 @@ export default function App() {
|
||||
|
||||
<div className="flex items-center gap-2" role="tablist">
|
||||
<button
|
||||
onClick={() => setViewMode("list")}
|
||||
onClick={() => updateViewMode("list")}
|
||||
role="tab"
|
||||
aria-selected={viewMode === "list"}
|
||||
className={cn(
|
||||
@ -139,7 +135,7 @@ export default function App() {
|
||||
All Receipts
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setViewMode("sessions")}
|
||||
onClick={() => updateViewMode("sessions")}
|
||||
role="tab"
|
||||
aria-selected={viewMode === "sessions"}
|
||||
className={cn(
|
||||
@ -155,9 +151,9 @@ export default function App() {
|
||||
|
||||
<FilterControls
|
||||
selectedTiers={selectedTiers}
|
||||
onTiersChange={setSelectedTiers}
|
||||
onTiersChange={updateSelectedTiers}
|
||||
anomalyOnly={anomalyOnly}
|
||||
onAnomalyOnlyChange={setAnomalyOnly}
|
||||
onAnomalyOnlyChange={updateAnomalyOnly}
|
||||
/>
|
||||
|
||||
{viewMode === "list" ? (
|
||||
|
||||
@ -4,7 +4,7 @@ export default function Header() {
|
||||
return (
|
||||
<header aria-label="Boltbot" className="fixed top-0 left-0 right-0 z-50 h-14 flex items-center px-5 bg-neutral-900/80 backdrop-blur border-b border-neutral-800">
|
||||
<Zap className="w-5 h-5 text-emerald-400 mr-2" aria-hidden="true" />
|
||||
<span className="text-lg font-bold tracking-tight" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||||
<span className="text-lg font-bold text-balance" style={{ fontFamily: "'Space Grotesk', sans-serif" }}>
|
||||
Boltbot
|
||||
</span>
|
||||
</header>
|
||||
|
||||
@ -114,7 +114,7 @@ export default function ReceiptDetail({ receipt, onClose }: Props) {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm z-50"
|
||||
className="fixed inset-0 bg-black/60 z-50"
|
||||
onClick={onClose}
|
||||
role="button"
|
||||
aria-label="Close"
|
||||
@ -128,7 +128,7 @@ export default function ReceiptDetail({ receipt, onClose }: Props) {
|
||||
>
|
||||
<div key={receipt.id} className="p-5">
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<h2 id="receipt-detail-title" className="text-lg font-bold truncate pr-4">{receipt.toolName}</h2>
|
||||
<h2 id="receipt-detail-title" className="text-lg font-bold truncate pr-4 text-balance">{receipt.toolName}</h2>
|
||||
<button
|
||||
ref={closeButtonRef}
|
||||
onClick={onClose}
|
||||
|
||||
@ -50,14 +50,14 @@ export default function ReceiptList({
|
||||
if (receipts.length === 0) {
|
||||
return (
|
||||
<div className="text-neutral-400 text-sm text-center py-12">
|
||||
No actions recorded yet
|
||||
No actions recorded yet. Receipts appear here when your agent uses tools.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div role="table" aria-label="Action receipts" aria-busy={isLoading}>
|
||||
<div role="row" className="grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 px-3 pb-2 text-neutral-400 text-xs uppercase tracking-wide">
|
||||
<div role="row" className="grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 px-3 pb-2 text-neutral-400 text-xs uppercase">
|
||||
<span role="columnheader">Tool</span>
|
||||
<span role="columnheader">Tier</span>
|
||||
<span role="columnheader">Time</span>
|
||||
|
||||
@ -103,7 +103,7 @@ function SessionCard({
|
||||
{expanded && (
|
||||
<div className="border-t border-neutral-800 p-3">
|
||||
<div role="table">
|
||||
<div role="row" className="grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 px-3 pb-2 text-neutral-400 text-xs uppercase tracking-wide">
|
||||
<div role="row" className="grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 px-3 pb-2 text-neutral-400 text-xs uppercase">
|
||||
<span role="columnheader">Tool</span>
|
||||
<span role="columnheader">Tier</span>
|
||||
<span role="columnheader">Time</span>
|
||||
@ -124,7 +124,7 @@ function SessionCard({
|
||||
}}
|
||||
className="grid grid-cols-[1fr_80px_90px_40px_40px] gap-2 items-center px-3 py-2 rounded-lg cursor-pointer hover:bg-neutral-800/30 transition-colors"
|
||||
>
|
||||
<span role="cell" className="text-sm font-mono truncate">{r.toolName}</span>
|
||||
<span role="cell" className="text-sm font-mono truncate min-w-0">{r.toolName}</span>
|
||||
<span role="cell">
|
||||
<span className={cn("rounded-full px-2 py-0.5 text-xs", tierBadge[r.tier])}>
|
||||
{r.tier}
|
||||
@ -162,7 +162,7 @@ export default function SessionView({ receipts, onSelectReceipt }: Props) {
|
||||
if (groups.length === 0) {
|
||||
return (
|
||||
<div className="text-neutral-400 text-sm text-center py-12">
|
||||
No sessions recorded yet
|
||||
No sessions recorded yet. Sessions appear here when your agent processes conversations.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export default function StatsCards({ stats, isLoading, error }: Props) {
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<card.icon className={`w-4 h-4 ${card.color}`} aria-hidden="true" />
|
||||
<span className="text-xs text-neutral-400 uppercase tracking-wide">
|
||||
<span className="text-xs text-neutral-400 uppercase">
|
||||
{card.label}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user