Skip to content
Merged
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
1,388 changes: 232 additions & 1,156 deletions src/main/frontend/package-lock.json

Large diffs are not rendered by default.

6 changes: 2 additions & 4 deletions src/main/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
"test:e2e:report": "playwright show-report"
},
"dependencies": {
"@ant-design/icons": "^6.2.1",
"antd": "^6.3.7",
"echarts": "^6.0.0",
"echarts-for-react": "^3.0.2",
"@ossrandom/design-system": "^0.3.0",
"d3-hierarchy": "^3.1.2",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"react-router-dom": "^7.1.5"
Expand Down
113 changes: 78 additions & 35 deletions src/main/frontend/src/components/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,88 @@
import { Outlet } from 'react-router-dom';
import { Layout, Switch, Typography, Space } from 'antd';
import { SunOutlined, MoonOutlined } from '@ant-design/icons';
import { useState } from 'react';
import { AppShell, IconButton } from '@ossrandom/design-system';
import { useTheme } from '@/context/ThemeContext';

const { Header, Content } = Layout;
function SunIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="4" />
<path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41" />
</svg>
);
}

export default function AppLayout() {
function MoonIcon() {
return (
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />
</svg>
);
}

function CopyIcon({ ok }: { ok: boolean }) {
if (ok) {
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M20 6L9 17l-5-5" />
</svg>
);
}
return (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
</svg>
);
}

function McpUrlPill() {
const [copied, setCopied] = useState(false);
const url = (typeof window !== 'undefined' ? window.location.origin : '') + '/mcp';
const onCopy = async () => {
try {
await navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 1200);
} catch {
/* clipboard blocked — silent */
}
};
return (
<div className="codeiq-mcp-url" title={url}>
<span>MCP&nbsp;·&nbsp;{url}</span>
<button onClick={onCopy} aria-label="Copy MCP URL" type="button">
<CopyIcon ok={copied} />
</button>
</div>
);
}

function Header() {
const { isDark, toggle } = useTheme();
return (
<div className="codeiq-header">
<div className="codeiq-brand">Code IQ</div>
<div className="codeiq-header-actions">
<McpUrlPill />
<IconButton
icon={isDark ? <SunIcon /> : <MoonIcon />}
aria-label={isDark ? 'Switch to light mode' : 'Switch to dark mode'}
variant="ghost"
size="sm"
onClick={toggle}
/>
</div>
</div>
);
}

export default function AppLayout() {
return (
<Layout style={{ minHeight: '100vh' }}>
<Header
style={{
padding: '0 24px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
position: 'sticky',
top: 0,
zIndex: 100,
borderBottom: isDark ? '1px solid #303030' : '1px solid #e8e8e8',
}}
>
<Typography.Title
level={4}
style={{ color: '#2563eb', margin: 0, whiteSpace: 'nowrap', lineHeight: '64px' }}
>
Code IQ
</Typography.Title>
<Space>
<Switch
checked={isDark}
onChange={toggle}
checkedChildren={<MoonOutlined />}
unCheckedChildren={<SunOutlined />}
/>
</Space>
</Header>
<Content style={{ padding: '16px 24px', overflow: 'auto' }}>
<AppShell header={<Header />}>
<div className="codeiq-content">
<Outlet />
</Content>
</Layout>
</div>
</AppShell>
);
}
27 changes: 27 additions & 0 deletions src/main/frontend/src/components/Icons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { CSSProperties } from 'react';

const base: CSSProperties = { display: 'inline-block', verticalAlign: '-2px' };

function Svg({ d, size = 14 }: { d: string; size?: number }) {
return (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor"
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" style={base}>
<path d={d} />
</svg>
);
}

export const Icon = {
Nodes: () => <Svg d="M5 12h14M12 5v14M6 6l12 12M6 18l12-12" />,
Branches: () => <Svg d="M6 3v12a3 3 0 0 0 3 3h6M6 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6zM18 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />,
File: () => <Svg d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8zM14 2v6h6" />,
Code: () => <Svg d="M16 18l6-6-6-6M8 6l-6 6 6 6" />,
Api: () => <Svg d="M2 12h6M16 12h6M12 2v6M12 16v6M5 5l4.5 4.5M14.5 14.5L19 19M5 19l4.5-4.5M14.5 9.5L19 5" />,
Safety: () => <Svg d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />,
Appstore: () => <Svg d="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z" />,
Build: () => <Svg d="M14.7 6.3a4 4 0 0 0-5.4 5.4l-7 7 2 2 7-7a4 4 0 0 0 5.4-5.4l-3 3-1.5-1.5z" />,
Play: () => <Svg d="M5 3l14 9-14 9V3z" />,
Clock: () => <Svg d="M12 6v6l4 2M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0z" />,
Search: () => <Svg d="M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16zM21 21l-4.35-4.35" />,
History: () => <Svg d="M3 12a9 9 0 1 0 3-6.7L3 8M3 3v5h5M12 7v5l3 2" />,
};
138 changes: 129 additions & 9 deletions src/main/frontend/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,133 @@ body {
margin: 0;
padding: 0;
}
/* Ant Design handles all theming */

/* MCP tool list: allow multiline descriptions */
.mcp-tool-menu .ant-menu-item {
height: auto !important;
line-height: normal !important;
padding-top: 4px !important;
padding-bottom: 4px !important;
white-space: normal !important;

/* Layout-only — visuals come from design-system tokens. */
.codeiq-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 0 16px;
height: 56px;
}

.codeiq-brand {
color: var(--accent);
font-size: var(--fs-h4);
font-weight: var(--fw-semibold);
letter-spacing: -0.01em;
white-space: nowrap;
}

.codeiq-content {
padding: 16px;
}

.codeiq-header-actions {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}

.codeiq-mcp-url {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
border: 1px solid var(--border-1);
border-radius: 6px;
background: var(--bg-2);
color: var(--fg-2);
font-family: var(--font-mono);
font-size: 12px;
max-width: 360px;
overflow: hidden;
}

.codeiq-mcp-url > span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.codeiq-mcp-url > button {
flex: none;
background: transparent;
border: 0;
padding: 2px;
color: var(--fg-3);
cursor: pointer;
border-radius: 4px;
}

.codeiq-mcp-url > button:hover {
color: var(--fg-1);
background: var(--bg-3);
}

/* Stats grid — auto-fit so cards flow on mobile. */
.codeiq-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 8px;
}

/* Hide the design-system Treemap's engine badge (renders "canvas"/"webgl"
in a corner — debug affordance, not desired in app chrome). */
.rcs-treemap-engine-badge {
display: none !important;
}

.codeiq-breadcrumb {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 4px;
padding: 6px 10px;
border: 1px solid var(--border-1);
border-radius: 6px;
background: var(--bg-1);
font-size: 12px;
color: var(--fg-2);
font-family: var(--font-mono);
}

.codeiq-breadcrumb button {
background: transparent;
border: 0;
padding: 2px 6px;
border-radius: 4px;
color: inherit;
font-family: inherit;
font-size: inherit;
cursor: pointer;
}

.codeiq-breadcrumb button:hover:not(:disabled) {
background: var(--bg-3);
color: var(--fg-1);
}

.codeiq-breadcrumb button:disabled {
cursor: default;
opacity: 0.7;
}

.codeiq-breadcrumb-sep {
opacity: 0.4;
user-select: none;
}

@media (max-width: 600px) {
.codeiq-header {
padding: 0 12px;
}
.codeiq-content {
padding: 12px;
}
.codeiq-mcp-url {
display: none;
}
}
Loading
Loading