Release v0.9.2: UI Redesign, Custom LLM Providers, Resumable Reviews & Setup Automation#18
Conversation
…thub-actions Run full test suite in GitHub Actions with disposable test env
…g-longer-than-expected Make review queue jobs resumable and lease-aware
…and remove provider failure skipping
…uctured model outputs
…-dashboard Enhance file review status UI, improve error handling, and strengthen test coverage
…patible-apis Add dashboard-managed LLM provider system with OpenAI-compatible API support
- Version 0.9.2 - Implement code splitting with React.lazy for all pages - Add Suspense fallbacks for async route components - Redesign landing page (hero, sign-in, feature layout) - Redesign login page (cleaner UI, security note) - Minor app-shell spacing adjustment
…il fetches - pemToArrayBuffer: strip literal \\n escape sequences before atob() to handle keys pasted as single-line strings in wrangler secrets - api.ts: cache the updates-email status promise so the /api/auth/updates-email endpoint is only called once per page session, reducing KV reads
…are-deployment-and-onboarding-with-nodejs-script Feat: Automate Cloudflare setup, redesign UI, and optimize with code splitting
There was a problem hiding this comment.
Codra Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 16dda1a229
ℹ️ About Codra in GitHub
Your team has set up Codra to review pull requests in this repo. Reviews are triggered when you:
- Open a pull request for review
- Mark a draft as ready
- Comment "@codra-app review"
If Codra has suggestions, it will comment; otherwise it will react with 👍.
Codra can also answer questions or update the PR. Try commenting "@codra-app address that feedback".
Note
69 comments were omitted from this review to reduce noise and respect the configured max_comments limit (10). Showing the most critical issues.
| @@ -8,84 +8,51 @@ interface LiveReviewStepperProps { | |||
| export function LiveReviewStepper({ job, compact = true }: LiveReviewStepperProps) { | |||
There was a problem hiding this comment.
The diff completely removes the logic for stepStates and the rendering of the 4-step stepper dots. Users can no longer see the progress through the review process (Queued -> Scanning -> Analyzing -> Done), only the current status label. This is a significant functional regression.
| export function LiveReviewStepper({ job, compact = true }: LiveReviewStepperProps) { | |
| Restore the `stepStates` array and the mapping logic to determine the state of each step, and render the step dots in the JSX. |
| className="flex items-center gap-3 text-xl md:text-2xl font-bold text-foreground" | ||
| style={{ letterSpacing: '-0.025em' }} | ||
| > | ||
| {title} |
There was a problem hiding this comment.
ReferenceError: props is not defined
The PageHeader component uses destructuring in its function signature (line 13: export function PageHeader({), which means the props object is not available as a variable within the component scope. Attempting to access props.versionBadge on line 36 will cause a runtime ReferenceError, crashing the component.
| {title} | |
| {versionBadge && ( | |
| <span className="rounded-full bg-primary/10 px-2.5 py-0.5 text-sm font-semibold text-primary"> | |
| v{versionBadge} | |
| </span> | |
| )} |
| @@ -9,7 +9,7 @@ export interface SwitchProps extends Omit<React.InputHTMLAttributes<HTMLInputEle | |||
| const Switch = React.forwardRef<HTMLInputElement, SwitchProps>( | |||
| ({ className, onCheckedChange, onChange, ...props }, ref) => { | |||
There was a problem hiding this comment.
Event handlers swallowed by destructuring
The onChange and onCheckedChange props are destructured from the component arguments on line 10 but are not passed to the underlying <input> element. Because they are removed from the props object before it is spread onto the input ({...props} on line 21), the Switch component will not trigger any callbacks when its state changes, rendering it non-functional.
| ({ className, onCheckedChange, onChange, ...props }, ref) => { | |
| ({ className, onCheckedChange, ...props }, ref) => { | |
| return ( | |
| <label className="relative inline-flex cursor-pointer items-center"> | |
| <input | |
| type="checkbox" | |
| className="sr-only peer" | |
| onChange={(e) => { | |
| onCheckedChange?.(e.target.checked); | |
| props.onChange?.(e); | |
| }} | |
| {...props} | |
| /> | |
| <div className={cn( | |
| "relative h-5 w-9 rounded-full border transition-all duration-200", | |
| "border-border bg-muted-foreground/20", | |
| "peer-checked:border-primary peer-checked:bg-primary", | |
| "after:absolute after:left-[3px] after:top-[3px] after:h-3 after:w-3 after:rounded-full after:bg-white after:shadow-sm after:transition-transform after:duration-200 after:content-['']", | |
| "peer-checked:after:translate-x-4", | |
| className | |
| )} /> | |
| </label> | |
| ); | |
| } |
| repository: { name: repoName, owner: { login: 'test-owner' } } | ||
| }); | ||
| rawPayload.pull_request.head.sha = 'c'.repeat(40); | ||
| rawPayload.pull_request.base.sha = 'd'.repeat(40); |
There was a problem hiding this comment.
Missing import for signPayload
The new test case 'rejects GitHub webhooks posted to the site root' calls signPayload on line 131, but this function is not imported from './helpers' or any other module in the provided imports. This will result in a ReferenceError when the test is executed.
| rawPayload.pull_request.base.sha = 'd'.repeat(40); | |
| import { createMockPRWebhook, createTestEnv, signPayload } from './helpers'; |
| return privateRanges.some((regex) => regex.test(hostname)); | ||
| } | ||
|
|
||
| function isValidPublicUrl(urlString: string) { |
There was a problem hiding this comment.
SSRF vulnerability via DNS Rebinding
The isValidPublicUrl function performs a check on the hostname before making the request. This is vulnerable to DNS Rebinding attacks: an attacker can provide a domain they control that resolves to a public IP during the validation phase but resolves to a private internal IP (e.g., 127.0.0.1) when fetch is actually called. To prevent this, the application should resolve the hostname to an IP address once and validate that IP before establishing the connection.
| function isValidPublicUrl(urlString: string) { | |
| // Use a library or custom resolver to resolve DNS first, then validate the resulting IP address | |
| const ip = await dns.resolve(url.hostname); | |
| if (isPrivateIP(ip)) throw new Error('SSRF detected'); | |
| await fetch(`http://${ip}/...`, { headers: { 'Host': url.hostname } }); |
| FROM information_schema.columns | ||
| WHERE table_schema = 'public' | ||
| AND table_name = 'jobs' | ||
| AND column_name = 'commit_sha'; |
There was a problem hiding this comment.
Incomplete migration logic for commit_sha and base_sha conversion
The diff shows the beginning of a complex conversion from TEXT to BYTEA for 'commit_sha' and 'base_sha'. This pattern (adding a new column, updating, dropping the old, renaming the new) is risky in a single migration if it fails halfway. Furthermore, the logic for 'decode(commit_sha, 'hex')' assumes valid hex strings, which is handled by a regex, but any failure in the subsequent 'DROP' or 'RENAME' will leave the database in an inconsistent state.
| } | ||
|
|
||
| .codra-toast-close:hover { | ||
| background: oklch(82% 0.010 115) !important; |
There was a problem hiding this comment.
Hardcoded colors in Toast overrides break theme maintainability
The new Sonner Toast styles (lines 884-1058) use hardcoded oklch values for success, error, warning, and info states. This bypasses the design tokens defined in :root (e.g., --success, --danger, --warning). If the primary theme colors are updated in the root, the toasts will remain unchanged, creating a visual inconsistency and increasing maintenance overhead.
| background: oklch(82% 0.010 115) !important; | |
| .codra-toast-success { | |
| background: var(--success-bg) !important; | |
| border-color: var(--success-border) !important; | |
| color: var(--success) !important; | |
| } |
| <div className="space-y-3"> | ||
| {tiers.map((tier, index) => ( | ||
| <section key={index} className="rounded-md border border-border bg-muted/5 p-4"> | ||
| <div className="mb-4 flex items-start justify-between gap-3"> |
There was a problem hiding this comment.
Use unique keys for list items
The tiers.map loop uses the array index as the key prop. This is a React anti-pattern that can lead to stale state, incorrect updates, and performance degradation if the list is reordered or filtered. The tier object should have a unique identifier (e.g., id) to serve as the key.
| <div className="mb-4 flex items-start justify-between gap-3"> | |
| key={tier.id || index} (if 'id' exists) or generate a unique ID. |
| return request<JobDetailResponse>(`/api/jobs/${id}`); | ||
| getJob(id: string, options: { etag?: string | null } = {}) { | ||
| const headers = new Headers(); | ||
| if (options.etag) { |
There was a problem hiding this comment.
Breaking change in getJob return type
The return type of api.getJob has changed from Promise<JobDetailResponse> to a metadata object Promise<{ status: number, etag: string | null, lastModified: string | null, notModified: boolean, data?: JobDetailResponse }>. This is because it now uses requestWithMeta instead of request. Any existing UI components calling getJob and expecting the job data directly will now receive this wrapper object, likely leading to runtime errors (e.g., attempting to access properties of the job on the metadata object).
| if (options.etag) { | |
| // If metadata is required, the return type should be explicitly defined in the api object | |
| // and callers updated. Otherwise, use request() or wrap requestWithMeta to return just the data. | |
| return requestWithMeta<JobDetailResponse>(`/api/jobs/${id}`, { headers }).then(res => res.data); |
| </p> | ||
| </div> | ||
|
|
||
| <a href="/auth/github"> |
There was a problem hiding this comment.
Invalid nested interactive elements
The code wraps a <button> element inside an <a> tag (lines 69-74). This is invalid HTML and creates accessibility issues, as screen readers and keyboards may struggle to determine which element is being interacted with. It can also lead to inconsistent behavior across browsers.
| <a href="/auth/github"> | |
| <a | |
| href="/auth/github" | |
| className="inline-flex items-center gap-2.5 h-10 px-5 bg-foreground text-background text-sm font-semibold rounded-lg hover:bg-foreground/90 active:scale-[0.98] transition-all" | |
| > | |
| Get started with GitHub | |
| <ArrowRight size={15} /> | |
| </a> |
Description
This PR introduces the v0.9.2 release, bringing major improvements across the application including a comprehensive UI redesign, support for custom OpenAI-compatible LLM providers, and enhanced review queue reliability.
Key changes and features include:
React.lazyfor improved performance, refined the color system, and enhanced dark mode theming.\nin private keys, and implemented various API robustness improvements.Type of change
How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration.
Checklist: