+ {parts.map((part, i) => {
+ const color = part.added
+ ? 'text-(--color-success)'
+ : part.removed
+ ? 'text-(--color-danger)'
+ : 'text-(--color-fg-muted)';
+ const prefix = part.added ? '+ ' : part.removed ? '- ' : ' ';
+ const lines = part.value.split('\n');
+ // diffLines returns trailing newlines as separate lines; drop the
+ // empty tail so we don't render dead rows.
+ const trimmed = lines[lines.length - 1] === '' ? lines.slice(0, -1) : lines;
+ return (
+
+ {trimmed.map((line, j) => (
+
+ {prefix}
+ {line}
+
+ ))}
+
+ );
+ })}
+
+ );
+}
diff --git a/src/web/src/components/Layout.tsx b/src/web/src/components/Layout.tsx
index b951f9e..3cb7737 100644
--- a/src/web/src/components/Layout.tsx
+++ b/src/web/src/components/Layout.tsx
@@ -1,80 +1,115 @@
import * as React from 'react';
import { NavLink, Outlet } from 'react-router-dom';
-import { clearToken } from '../api';
+import { LogOut, FolderKanban, Bot, Sparkles, Inbox, LayoutDashboard } from 'lucide-react';
+
+import { api, clearToken, type Proposal } from '../api';
+import { Badge } from './ui/badge';
+import { cn } from '../lib/utils';
/**
- * Top-of-page nav + outlet. Terminal-style dark theme so the UI feels
- * adjacent to the CLI rather than a separate product.
+ * Sidebar layout. Pending-proposals badge polls every 30 s so reviewers
+ * see a queue building up without having to refresh the page.
*/
export function Layout(): React.JSX.Element {
+ const [pendingCount, setPendingCount] = React.useState