Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .agents/skills/react-doctor/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: react-doctor
description: Use when finishing a feature, fixing a bug, before committing React code, or when the user wants to improve code quality or clean up a codebase. Checks for score regression. Covers lint, dead code, accessibility, bundle size, architecture diagnostics.
version: '1.0.0'
---

# React Doctor

Scans React codebases for security, performance, correctness, and architecture issues. Outputs a 0–100 health score.

## After making React code changes:

Run `npx -y react-doctor@latest . --verbose --diff` and check the score did not regress.

If the score dropped, fix the regressions before committing.

## For general cleanup or code improvement:

Run `npx -y react-doctor@latest . --verbose` (without `--diff`) to scan the full codebase. Fix issues by severity — errors first, then warnings.

## Command

```bash
npx -y react-doctor@latest . --verbose --diff
```

| Flag | Purpose |
| ----------- | --------------------------------------------- |
| `.` | Scan current directory |
| `--verbose` | Show affected files and line numbers per rule |
| `--diff` | Only scan changed files vs base branch |
| `--score` | Output only the numeric score |
27 changes: 19 additions & 8 deletions apps/web/src/app/(app)/claw/components/ChannelPairingStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,27 @@ export function ChannelPairingStep({

useEffect(() => {
let cancelled = false;
let timeoutId: ReturnType<typeof setTimeout> | undefined;

function waitForNextPoll() {
return new Promise<void>(resolve => {
timeoutId = setTimeout(resolve, 1_000);
});
}

async function poll() {
while (!cancelled) {
await refreshRef.current().catch(() => {});
if (cancelled) break;
await new Promise(r => setTimeout(r, 1_000));
await waitForNextPoll();
}
}
void poll();
return () => {
cancelled = true;
if (timeoutId) {
clearTimeout(timeoutId);
}
};
}, []);

Expand Down Expand Up @@ -143,7 +154,7 @@ export function ChannelPairingStepView({
>
<div className="border-border bg-muted/30 flex flex-col rounded-lg border">
<div className="flex items-center gap-2 px-5 pt-5 pb-4">
<Send className="text-muted-foreground h-4 w-4" />
<Send className="text-muted-foreground size-4" />
<span className="text-muted-foreground text-xs font-semibold uppercase tracking-wider">
Pairing request received
</span>
Expand Down Expand Up @@ -175,9 +186,9 @@ export function ChannelPairingStepView({
disabled={isApproving}
>
{isApproving ? (
<Loader2 className="h-4 w-4 animate-spin" />
<Loader2 className="size-4 animate-spin" />
) : (
<CheckCircle2 className="h-4 w-4" />
<CheckCircle2 className="size-4" />
)}
Authorize this request
</Button>
Expand All @@ -187,7 +198,7 @@ export function ChannelPairingStepView({
className="text-muted-foreground/50 hover:text-muted-foreground mx-auto flex cursor-pointer items-center gap-1.5 text-sm transition-colors"
onClick={onSkip}
>
<XCircle className="h-3.5 w-3.5" />
<XCircle className="size-3.5" />
Decline
</button>
</OnboardingStepView>
Expand All @@ -203,7 +214,7 @@ export function ChannelPairingStepView({
contentClassName="gap-8"
>
<div className="flex flex-col items-center gap-8">
<div className="pairing-spinner relative h-24 w-24 my-6">
<div className="pairing-spinner relative size-24 my-6">
<svg className="h-full w-full" viewBox="0 0 96 96">
<circle
cx="48"
Expand Down Expand Up @@ -238,7 +249,7 @@ export function ChannelPairingStepView({

<div className="flex flex-col items-center gap-2 text-center">
<h2 className="text-foreground text-lg font-semibold">
Waiting for you to message the bot...
Waiting for you to message the bot
</h2>
<p className="text-muted-foreground text-sm">This page will update automatically.</p>
</div>
Expand All @@ -247,7 +258,7 @@ export function ChannelPairingStepView({
className="text-muted-foreground/50 cursor-pointer hover:text-muted-foreground text-sm transition-colors my-6"
onClick={onSkip}
>
Skip I&apos;ll pair later from Settings
Skip. I&apos;ll pair later from Settings
</button>
</div>
</OnboardingStepView>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import { CalendarClock } from 'lucide-react';
import { Alert, AlertDescription } from '@/components/ui/alert';
import type { KiloClawScheduledActionStatusBlock } from '@/lib/kiloclaw/types';

const timezoneNameFormatter = new Intl.DateTimeFormat(undefined, { timeZoneName: 'short' });

type Props = {
scheduledAction: KiloClawScheduledActionStatusBlock | null;
/**
Expand Down Expand Up @@ -47,7 +49,7 @@ function formatScheduledAt(iso: string): string {
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return iso;
const dateStr = d.toLocaleString();
const tzPart = new Intl.DateTimeFormat(undefined, { timeZoneName: 'short' })
const tzPart = timezoneNameFormatter
.formatToParts(d)
.find(p => p.type === 'timeZoneName')?.value;
return tzPart ? `${dateStr} ${tzPart}` : dateStr;
Expand Down Expand Up @@ -80,7 +82,7 @@ export function KiloClawScheduledActionBanner({ scheduledAction, instanceName }:

return (
<Alert className="border-yellow-500/30 bg-yellow-500/5">
<CalendarClock className="h-4 w-4 text-yellow-400" />
<CalendarClock className="size-4 text-yellow-400" />
<AlertDescription>
{isVersionChange ? (
<>
Expand Down
16 changes: 8 additions & 8 deletions apps/web/src/app/(app)/install/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@ export default function InstallPage() {
<PageLayout title="Install">
<div className="grid gap-6 md:grid-cols-2">
<Card className="group border-brand-primary/20 hover:border-brand-primary/40 hover:shadow-brand-primary/5 relative flex flex-col justify-between overflow-hidden transition-all hover:shadow-lg">
<div className="bg-brand-primary/10 group-hover:bg-brand-primary/20 absolute top-0 right-0 h-32 w-32 translate-x-8 -translate-y-8 rounded-full blur-2xl transition-all" />
<div className="bg-brand-primary/10 group-hover:bg-brand-primary/20 absolute top-0 right-0 size-32 translate-x-8 -translate-y-8 rounded-full blur-2xl transition-all" />
<CardHeader className="relative flex-1">
<div className="bg-brand-primary/10 text-brand-primary mb-4 flex h-12 w-12 items-center justify-center rounded-lg">
<CodeIcon className="h-6 w-6" />
<div className="bg-brand-primary/10 text-brand-primary mb-4 flex size-12 items-center justify-center rounded-lg">
<CodeIcon className="size-6" />
</div>
<CardTitle className="text-xl">IDE Extension</CardTitle>
<CardDescription className="text-muted-foreground mt-2">
Expand All @@ -57,7 +57,7 @@ export default function InstallPage() {
</CardHeader>
<CardFooter>
<Button
className="bg-brand-primary hover:text-brand-primary hover:ring-brand-primary w-full text-black hover:bg-black hover:ring-2"
className="bg-brand-primary hover:text-brand-primary hover:ring-brand-primary w-full text-primary-foreground hover:bg-background hover:ring-2"
asChild
>
<Link href="https://kilo.ai/install" target="_blank" rel="noopener noreferrer">
Expand All @@ -68,10 +68,10 @@ export default function InstallPage() {
</Card>

<Card className="group border-brand-primary/20 hover:border-brand-primary/40 hover:shadow-brand-primary/5 relative flex flex-col justify-between overflow-hidden transition-all hover:shadow-lg">
<div className="bg-brand-primary/10 group-hover:bg-brand-primary/20 absolute top-0 right-0 h-32 w-32 translate-x-8 -translate-y-8 rounded-full blur-2xl transition-all" />
<div className="bg-brand-primary/10 group-hover:bg-brand-primary/20 absolute top-0 right-0 size-32 translate-x-8 -translate-y-8 rounded-full blur-2xl transition-all" />
<CardHeader className="relative flex-1">
<div className="bg-brand-primary/10 text-brand-primary mb-4 flex h-12 w-12 items-center justify-center rounded-lg">
<TerminalIcon className="h-6 w-6" />
<div className="bg-brand-primary/10 text-brand-primary mb-4 flex size-12 items-center justify-center rounded-lg">
<TerminalIcon className="size-6" />
</div>
<CardTitle className="text-xl">Command Line Tool</CardTitle>
<CardDescription className="text-muted-foreground mt-2">
Expand All @@ -80,7 +80,7 @@ export default function InstallPage() {
</CardHeader>
<CardFooter>
<Button
className="bg-brand-primary hover:text-brand-primary hover:ring-brand-primary w-full text-black hover:bg-black hover:ring-2"
className="bg-brand-primary hover:text-brand-primary hover:ring-brand-primary w-full text-primary-foreground hover:bg-background hover:ring-2"
asChild
>
<Link href="https://kilo.ai/install#cli" target="_blank" rel="noopener noreferrer">
Expand Down
16 changes: 8 additions & 8 deletions apps/web/src/app/(app)/learn/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ export default function LearnPage() {
<PageLayout title="Learn">
<div className="grid gap-6 md:grid-cols-2">
<Card className="group border-brand-primary/20 hover:border-brand-primary/40 hover:shadow-brand-primary/5 relative flex flex-col justify-between overflow-hidden transition-all hover:shadow-lg">
<div className="bg-brand-primary/10 group-hover:bg-brand-primary/20 absolute top-0 right-0 h-32 w-32 translate-x-8 -translate-y-8 rounded-full blur-2xl transition-all" />
<div className="bg-brand-primary/10 group-hover:bg-brand-primary/20 absolute top-0 right-0 size-32 translate-x-8 -translate-y-8 rounded-full blur-2xl transition-all" />
<CardHeader className="relative flex-1">
<div className="bg-brand-primary/10 text-brand-primary mb-4 flex h-12 w-12 items-center justify-center rounded-lg">
<BookIcon className="h-6 w-6" />
<div className="bg-brand-primary/10 text-brand-primary mb-4 flex size-12 items-center justify-center rounded-lg">
<BookIcon className="size-6" />
</div>
<CardTitle className="text-xl">Documentation</CardTitle>
<CardDescription className="text-muted-foreground mt-2">
Expand All @@ -55,7 +55,7 @@ export default function LearnPage() {
</CardHeader>
<CardFooter>
<Button
className="bg-brand-primary hover:text-brand-primary hover:ring-brand-primary w-full text-black hover:bg-black hover:ring-2"
className="bg-brand-primary hover:text-brand-primary hover:ring-brand-primary w-full text-primary-foreground hover:bg-background hover:ring-2"
asChild
>
<Link href="https://kilo.ai/docs" target="_blank" rel="noopener noreferrer">
Expand All @@ -66,10 +66,10 @@ export default function LearnPage() {
</Card>

<Card className="group border-brand-primary/20 hover:border-brand-primary/40 hover:shadow-brand-primary/5 relative flex flex-col justify-between overflow-hidden transition-all hover:shadow-lg">
<div className="bg-brand-primary/10 group-hover:bg-brand-primary/20 absolute top-0 right-0 h-32 w-32 translate-x-8 -translate-y-8 rounded-full blur-2xl transition-all" />
<div className="bg-brand-primary/10 group-hover:bg-brand-primary/20 absolute top-0 right-0 size-32 translate-x-8 -translate-y-8 rounded-full blur-2xl transition-all" />
<CardHeader className="relative flex-1">
<div className="bg-brand-primary/10 text-brand-primary mb-4 flex h-12 w-12 items-center justify-center rounded-lg">
<VideoIcon className="h-6 w-6" />
<div className="bg-brand-primary/10 text-brand-primary mb-4 flex size-12 items-center justify-center rounded-lg">
<VideoIcon className="size-6" />
</div>
<CardTitle className="text-xl">Live Q&A Sessions</CardTitle>
<CardDescription className="text-muted-foreground mt-2">
Expand All @@ -79,7 +79,7 @@ export default function LearnPage() {
</CardHeader>
<CardFooter>
<Button
className="bg-brand-primary hover:text-brand-primary hover:ring-brand-primary w-full text-black hover:bg-black hover:ring-2"
className="bg-brand-primary hover:text-brand-primary hover:ring-brand-primary w-full text-primary-foreground hover:bg-background hover:ring-2"
asChild
>
<Link
Expand Down
24 changes: 12 additions & 12 deletions apps/web/src/app/admin/components/BlacklistedDomains.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ function EditTab() {
);

function handleSave() {
const domains = inputValue
.split(/[\n|]/)
.map(part => part.trim().toLowerCase())
.filter(Boolean);
const domains = inputValue.split(/[\n|]/).flatMap(part => {
const domain = part.trim().toLowerCase();
return domain ? [domain] : [];
});

mutation.mutate({ domains });
}

if (isLoading) {
return <div className="text-muted-foreground py-8 text-sm">Loading...</div>;
return <div className="text-muted-foreground py-8 text-sm">Loading</div>;
}

const domainCount = data?.domains.length ?? 0;
Expand All @@ -73,7 +73,7 @@ function EditTab() {
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" />
<Shield className="size-5" />
Blacklisted Domains
</CardTitle>
<CardDescription>
Expand Down Expand Up @@ -101,7 +101,7 @@ function EditTab() {

<div className="flex items-center gap-3">
<Button onClick={handleSave} disabled={mutation.isPending || !hasChanges} size="sm">
{mutation.isPending ? 'Saving...' : 'Save'}
{mutation.isPending ? 'Saving' : 'Save'}
</Button>
{data?.updated_by_email && (
<span className="text-muted-foreground text-sm">
Expand Down Expand Up @@ -143,7 +143,7 @@ function StatsTab() {
{stats.totalDomains} {stats.totalDomains === 1 ? 'domain' : 'domains'}
</Badge>
<Badge variant="destructive" className="px-3 py-1">
<Users className="mr-1 h-3 w-3" />
<Users className="mr-1 size-3" />
{stats.totalBlockedUsers.toLocaleString()} blocked users
</Badge>
</div>
Expand All @@ -152,7 +152,7 @@ function StatsTab() {
</CardHeader>
<CardContent>
{isLoading ? (
<div className="text-muted-foreground py-8 text-center text-sm">Loading stats...</div>
<div className="text-muted-foreground py-8 text-center text-sm">Loading stats</div>
) : !stats || stats.domains.length === 0 ? (
<div className="text-muted-foreground py-8 text-center">
No blacklisted domains configured
Expand Down Expand Up @@ -219,7 +219,7 @@ function SuspiciousTab() {
{domains.length} {domains.length === 1 ? 'domain' : 'domains'}
</Badge>
<Badge variant="outline" className="px-3 py-1">
<Shield className="mr-1 h-3 w-3" />
<Shield className="mr-1 size-3" />
{blacklistedCount} already blacklisted
</Badge>
</div>
Expand Down Expand Up @@ -254,12 +254,12 @@ function SuspiciousTab() {
<TableCell>
{domain.isBlacklisted ? (
<Badge variant="secondary" className="gap-1">
<CheckCircle2 className="h-3 w-3" />
<CheckCircle2 className="size-3" />
Blacklisted
</Badge>
) : (
<Badge variant="destructive" className="gap-1">
<AlertTriangle className="h-3 w-3" />
<AlertTriangle className="size-3" />
Not blacklisted
</Badge>
)}
Expand Down
Loading