Skip to content

Commit 1aeaab2

Browse files
refactor: color system, error handling, caching, and migration improvements
1 parent 5174d49 commit 1aeaab2

7 files changed

Lines changed: 96 additions & 56 deletions

File tree

scripts/migrate.mjs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -402,11 +402,11 @@ async function normalizeRepoConfigs() {
402402
}
403403

404404
console.log('Normalizing repo configs...');
405-
const functionName = 'codra_replace_deprecated_model_' + Date.now();
405+
const functionName = 'codra_replace_deprecated_model';
406406

407-
console.log(`Creating function: ${functionName}`);
407+
console.log(`Creating function: pg_temp.${functionName}`);
408408
await query(`
409-
CREATE FUNCTION public.${functionName}(input jsonb, old_value text, new_value text)
409+
CREATE FUNCTION pg_temp.${functionName}(input jsonb, old_value text, new_value text)
410410
RETURNS jsonb
411411
LANGUAGE sql
412412
IMMUTABLE
@@ -415,14 +415,14 @@ async function normalizeRepoConfigs() {
415415
WHEN 'string' THEN CASE WHEN input #>> '{}' = old_value THEN to_jsonb(new_value) ELSE input END
416416
WHEN 'array' THEN COALESCE(
417417
(
418-
SELECT jsonb_agg(public.${functionName}(value, old_value, new_value) ORDER BY ord)
418+
SELECT jsonb_agg(pg_temp.${functionName}(value, old_value, new_value) ORDER BY ord)
419419
FROM jsonb_array_elements(input) WITH ORDINALITY AS item(value, ord)
420420
),
421421
'[]'::jsonb
422422
)
423423
WHEN 'object' THEN COALESCE(
424424
(
425-
SELECT jsonb_object_agg(key, public.${functionName}(value, old_value, new_value))
425+
SELECT jsonb_object_agg(key, pg_temp.${functionName}(value, old_value, new_value))
426426
FROM jsonb_each(input)
427427
),
428428
'{}'::jsonb
@@ -440,15 +440,15 @@ async function normalizeRepoConfigs() {
440440
main_model = CASE WHEN main_model = $1 THEN $2 ELSE main_model END,
441441
fallback_models = CASE
442442
WHEN fallback_models IS NULL THEN NULL
443-
ELSE public.${functionName}(fallback_models, $1, $2)
443+
ELSE pg_temp.${functionName}(fallback_models, $1, $2)
444444
END,
445445
size_overrides = CASE
446446
WHEN size_overrides IS NULL THEN NULL
447-
ELSE public.${functionName}(size_overrides, $1, $2)
447+
ELSE pg_temp.${functionName}(size_overrides, $1, $2)
448448
END,
449449
parsed_json = CASE
450450
WHEN parsed_json IS NULL THEN NULL
451-
ELSE public.${functionName}(parsed_json, $1, $2)
451+
ELSE pg_temp.${functionName}(parsed_json, $1, $2)
452452
END
453453
WHERE main_model = $1
454454
OR fallback_models::text LIKE '%' || $1 || '%'
@@ -458,13 +458,16 @@ async function normalizeRepoConfigs() {
458458
[kimiK25Model, kimiK26Model],
459459
);
460460

461-
console.log(`Dropping function: ${functionName}`);
462-
await query(`DROP FUNCTION IF EXISTS public.${functionName}(jsonb, text, text)`);
461+
console.log(`Dropping function: pg_temp.${functionName}`);
462+
await query(`DROP FUNCTION IF EXISTS pg_temp.${functionName}(jsonb, text, text)`);
463463
console.log('Repo configs normalized.');
464464
}
465465

466466
async function main() {
467467
try {
468+
console.log('Acquiring advisory lock...');
469+
await query('SELECT pg_advisory_lock($1)', [migrationLockId]);
470+
468471
console.log('Starting database migrations...');
469472
await ensureMigrationTable();
470473

@@ -486,6 +489,8 @@ async function main() {
486489

487490
console.log('Database migrations are up to date.');
488491
} finally {
492+
console.log('Releasing advisory lock...');
493+
await query('SELECT pg_advisory_unlock($1)', [migrationLockId]);
489494
await sql.end();
490495
}
491496
}

src/client/app.css

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,34 +16,34 @@
1616
:root {
1717
/* Surfaces - Pure & Crisp */
1818
/* Surfaces - High-Contrast */
19-
--background: #f4f4f5;
19+
--background: oklch(96.3% 0.003 286.3); /* #f4f4f5 */
2020
--foreground: oklch(12% 0.02 115);
21-
--card: #ffffff;
21+
--card: oklch(100% 0 0); /* #ffffff */
2222
--card-foreground: oklch(12% 0.02 115);
23-
--popover: #ffffff;
23+
--popover: oklch(100% 0 0);
2424
--popover-foreground: oklch(12% 0.02 115);
2525

2626
/* Signature lime - darkened in light mode for AA accessibility on white */
2727
--primary: oklch(64% 0.24 115);
2828
--primary-foreground: oklch(100% 0 0); /* White text on the deeper green */
2929

3030
/* Secondary / muted - Zinc */
31-
--secondary: #f4f4f5;
32-
--secondary-foreground:#27272a;
33-
--muted: #f4f4f5;
34-
--muted-foreground: #71717a;
31+
--secondary: oklch(96.3% 0.003 286.3);
32+
--secondary-foreground:oklch(27.4% 0.006 286.3); /* #27272a */
33+
--muted: oklch(96.3% 0.003 286.3);
34+
--muted-foreground: oklch(55.1% 0.011 286.3); /* #71717a */
3535

3636
/* Accent - slightly darker zinc for visible hover on white popovers */
37-
--accent: #e4e4e7;
38-
--accent-foreground: #18181b;
37+
--accent: oklch(90.9% 0.004 286.3); /* #e4e4e7 */
38+
--accent-foreground: oklch(20.5% 0.005 286.3); /* #18181b */
3939

4040
/* Destructive */
4141
--destructive: oklch(55% 0.22 25);
4242
--destructive-foreground: oklch(100% 0 0);
4343

4444
/* Border / input / ring */
45-
--border: #e4e4e7;
46-
--input: #e4e4e7;
45+
--border: oklch(90.9% 0.004 286.3);
46+
--input: oklch(90.9% 0.004 286.3);
4747
--ring: oklch(72% 0.22 115);
4848

4949
/* Radius */
@@ -71,9 +71,9 @@
7171
--shadow-lg: 0 4px 16px -4px oklch(0% 0 0 / 0.04), 0 1px 6px -2px oklch(0% 0 0 / 0.03);
7272

7373
/* Code Blocks (Zinc) */
74-
--code-bg: #f4f4f5;
75-
--code-fg: #27272a;
76-
--code-border: #e4e4e7;
74+
--code-bg: oklch(96.3% 0.003 286.3);
75+
--code-fg: oklch(27.4% 0.006 286.3);
76+
--code-border: oklch(90.9% 0.004 286.3);
7777
}
7878

7979
/* ─────────────────────────────────────────────────────
@@ -125,9 +125,9 @@
125125
--shadow-lg: 0 12px 24px -4px oklch(0% 0 0 / 0.5), 0 4px 12px -2px oklch(0% 0 0 / 0.3);
126126

127127
/* Code Blocks (Zinc) */
128-
--code-bg: #18181b;
129-
--code-fg: #d4d4d8;
130-
--code-border: #27272a;
128+
--code-bg: oklch(20.5% 0.005 286.3);
129+
--code-fg: oklch(86.5% 0.005 286.3);
130+
--code-border: oklch(27.4% 0.006 286.3);
131131
}
132132

133133
/* ─────────────────────────────────────────────────────
@@ -173,11 +173,11 @@
173173
--color-info-bg: var(--info-bg);
174174
--color-info-border: var(--info-border);
175175

176-
--radius-sm: 4px;
177-
--radius-md: 8px;
178-
--radius-lg: 12px;
179-
--radius-xl: 18px;
180-
--radius-2xl: 32px;
176+
--radius-sm: 0.25rem;
177+
--radius-md: 0.5rem;
178+
--radius-lg: 0.75rem;
179+
--radius-xl: 1.125rem;
180+
--radius-2xl: 2rem;
181181

182182
/* Fluid Typography Scale (Ratio: 1.25) */
183183
--text-xs: 0.75rem;
@@ -656,21 +656,21 @@
656656
}
657657

658658
.app-shell-content {
659-
--background: #f4f4f5;
660-
--card: #ffffff;
661-
--muted: #f4f4f5;
662-
--popover: #ffffff;
663-
--secondary: #f4f4f5;
664-
--border: #e4e4e7;
665-
--input: #e4e4e7;
659+
--background: oklch(96.3% 0.003 286.3); /* #f4f4f5 */
660+
--card: oklch(100% 0 0); /* #ffffff */
661+
--muted: oklch(90.9% 0.004 286.3); /* #e4e4e7 */
662+
--popover: oklch(100% 0 0);
663+
--secondary: oklch(88.5% 0.004 286.3); /* #e2e2e6 */
664+
--border: oklch(90.9% 0.004 286.3); /* #e4e4e7 */
665+
--input: oklch(90.9% 0.004 286.3); /* #e4e4e7 */
666666
}
667667

668668
.dark .app-shell-content {
669-
--background: #09090b;
670-
--card: #09090b;
671-
--muted: #09090b;
672-
--popover: #09090b;
673-
--secondary: #09090b;
669+
--background: oklch(18% 0.018 115);
670+
--card: oklch(18% 0.018 115);
671+
--muted: oklch(22% 0.02 115);
672+
--popover: oklch(18% 0.018 115);
673+
--secondary: oklch(26% 0.02 115);
674674
--border: oklch(22% 0.02 115);
675675
--input: oklch(22% 0.02 115);
676676
}

src/client/components/shared/jobs-table.tsx

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { StatusBadge } from '@client/components/ui/badge';
99
import { Skeleton } from '@client/components/shared/skeleton';
1010
import { cn, fmtNumber } from '@client/lib/utils';
11-
import { useIsDarkMode } from '@client/hooks/use-is-dark-mode';
11+
1212
import type { JobSummary } from '@shared/schema';
1313

1414
type Column =
@@ -199,8 +199,7 @@ function JobMobileCard({ job, columns }: { job: JobSummary; columns: Column[] })
199199
export function JobsTable({ jobs, loading, columns }: JobsTableProps) {
200200
const cols: Column[] = columns ?? DEFAULT_COLUMNS;
201201
const tableMinWidth = cols.length > 7 ? 'min-w-[980px]' : 'min-w-[720px]';
202-
const isDark = useIsDarkMode();
203-
const itemBg = isDark ? '#09090b' : '#fafafa';
202+
const itemBgClass = 'bg-background';
204203

205204
return (
206205
<div className="min-w-0 max-w-full overflow-hidden">
@@ -275,7 +274,7 @@ export function JobsTable({ jobs, loading, columns }: JobsTableProps) {
275274
)}
276275
>
277276
<div className="flex min-w-0 items-center gap-2.5">
278-
<span className="flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border text-xs font-bold text-primary shadow-sm" style={{ backgroundColor: itemBg }}>
277+
<span className={cn("flex h-8 w-8 shrink-0 items-center justify-center rounded-md border border-border text-xs font-bold text-primary shadow-sm", itemBgClass)}>
279278
{job.repo.slice(0, 2).toUpperCase()}
280279
</span>
281280
<div className="min-w-0">
@@ -410,8 +409,7 @@ export function JobsTable({ jobs, loading, columns }: JobsTableProps) {
410409
>
411410
<Link
412411
to={`/jobs/${job.id}`}
413-
className="inline-flex h-8 w-8 items-center justify-center rounded-md border border-border text-muted-foreground shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:text-primary hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
414-
style={{ backgroundColor: itemBg }}
412+
className={cn("inline-flex h-8 w-8 items-center justify-center rounded-md border border-border text-muted-foreground shadow-sm transition-all hover:-translate-y-0.5 hover:border-primary/30 hover:text-primary hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", itemBgClass)}
415413
aria-label={`Open job for ${job.owner}/${job.repo} pull request ${job.prNumber}`}
416414
>
417415
<ArrowUpRight size={14} />

src/client/components/ui/select.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export function Select({
4444
triggerClassName,
4545
triggerStyle,
4646
leadingIcon,
47+
variant = 'page',
4748
}: SelectProps) {
4849
const selectedOption = options.find((opt) => opt.value === value);
4950

@@ -60,6 +61,7 @@ export function Select({
6061
variant="outline"
6162
className={cn(
6263
'h-9 w-full justify-between px-3 py-2 text-sm font-normal transition-all focus-visible:ring-0 focus-visible:ring-offset-0',
64+
variant === 'page' ? 'bg-card' : 'bg-muted/50',
6365
!selectedOption && 'text-muted-foreground',
6466
triggerClassName,
6567
)}

src/client/lib/api.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ async function requestWithMeta<T>(input: string, init?: RequestInit) {
119119
}
120120

121121
let updatesEmailPromise: Promise<UpdatesEmailResponse> | null = null;
122+
let updatesEmailFetchTime = 0;
123+
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
122124

123125
export const api = {
124126
getSession() {
@@ -130,7 +132,9 @@ export const api = {
130132
});
131133
},
132134
getUpdatesEmailStatus() {
133-
if (!updatesEmailPromise) {
135+
const now = Date.now();
136+
if (!updatesEmailPromise || (now - updatesEmailFetchTime > CACHE_TTL)) {
137+
updatesEmailFetchTime = now;
134138
updatesEmailPromise = request<UpdatesEmailResponse>('/api/auth/updates-email').catch((err) => {
135139
updatesEmailPromise = null;
136140
throw err;

src/client/main.tsx

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,35 @@ function ToasterWrapper() {
5151
);
5252
}
5353

54+
class ErrorBoundary extends React.Component<{ fallback?: React.ReactNode, children: React.ReactNode }, { error: Error | null }> {
55+
constructor(props: { fallback?: React.ReactNode, children: React.ReactNode }) {
56+
super(props);
57+
this.state = { error: null };
58+
}
59+
static getDerivedStateFromError(error: Error) { return { error }; }
60+
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
61+
console.error("ErrorBoundary caught an error:", error, errorInfo);
62+
}
63+
render() {
64+
if (this.state.error) {
65+
if (this.props.fallback) return this.props.fallback;
66+
return (
67+
<div className="flex flex-col items-center justify-center p-8 text-destructive">
68+
<p className="font-bold">An error occurred rendering this component:</p>
69+
<pre className="mt-2 rounded bg-muted p-4 text-xs font-mono">{this.state.error.toString()}</pre>
70+
</div>
71+
);
72+
}
73+
return this.props.children;
74+
}
75+
}
76+
5477
const withSuspense = (Component: React.ComponentType, isFullPage = false) => (
55-
<Suspense fallback={<div className={`flex items-center justify-center ${isFullPage ? 'h-screen' : 'h-full w-full'}`} />}>
56-
<Component />
57-
</Suspense>
78+
<ErrorBoundary>
79+
<Suspense fallback={<div role="status" aria-busy="true" className={`flex items-center justify-center ${isFullPage ? 'h-screen' : 'h-full w-full'}`} />}>
80+
<Component />
81+
</Suspense>
82+
</ErrorBoundary>
5883
);
5984

6085
const router = createBrowserRouter([

src/server/core/model-output.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,11 @@ function extractJson(raw: string) {
134134
}
135135
}
136136

137-
// Truncated - return everything from first brace
138-
return raw.slice(firstBrace).trim();
137+
// Truncated JSON: the closing brace(s) are missing. Append them so jsonrepair
138+
// has a structurally complete (though incomplete-content) object to work with.
139+
const partial = raw.slice(firstBrace).trim();
140+
const closing = '}'.repeat(Math.max(1, stack));
141+
return `${partial}${closing}`;
139142
}
140143

141144
return raw.trim();
@@ -260,8 +263,11 @@ export function parseFileReviewResponse(raw: string, file: FileDiff): {
260263
throw new Error('Model response did not contain review JSON keys.');
261264
}
262265
} catch (e) {
266+
// Log a prefix of the raw response so we can diagnose what the model returned
267+
// without bloating logs with 10k+ char dumps.
263268
logger.error('Failed to extract JSON from model response', {
264269
rawLength: raw.length,
270+
rawPrefix: raw.slice(0, 500),
265271
error: e instanceof Error ? e.message : String(e),
266272
});
267273
throw new Error('Could not find JSON root in model response.');

0 commit comments

Comments
 (0)