diff --git a/apps/web/app/fonts.css b/apps/web/app/fonts.css
new file mode 100644
index 0000000000..58df8f3b74
--- /dev/null
+++ b/apps/web/app/fonts.css
@@ -0,0 +1,71 @@
+@font-face {
+ font-family: 'Figtree';
+ font-style: normal;
+ font-weight: 300 900;
+ font-display: swap;
+ src: url('/fonts/figtree/figtree-300-normal.woff2') format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+
+@font-face {
+ font-family: 'Figtree';
+ font-style: normal;
+ font-weight: 300 900;
+ font-display: swap;
+ src: url('/fonts/figtree/figtree-300-normal.woff2') format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+@font-face {
+ font-family: 'Geist';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+ src: url('/fonts/geist/geist-100-normal.woff2') format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+
+@font-face {
+ font-family: 'Geist';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+ src: url('/fonts/geist/geist-100-normal.woff2') format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+
+@font-face {
+ font-family: 'Geist';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+ src: url('/fonts/geist/geist-100-normal.woff2') format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
+
+@font-face {
+ font-family: 'Geist Mono';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+ src: url('/fonts/geist-mono/geist-mono-100-normal.woff2') format('woff2');
+ unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
+}
+
+@font-face {
+ font-family: 'Geist Mono';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+ src: url('/fonts/geist-mono/geist-mono-100-normal.woff2') format('woff2');
+ unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
+}
+
+@font-face {
+ font-family: 'Geist Mono';
+ font-style: normal;
+ font-weight: 100 900;
+ font-display: swap;
+ src: url('/fonts/geist-mono/geist-mono-100-normal.woff2') format('woff2');
+ unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
+}
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index 80e36586be..730bba6d9e 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -1,6 +1,7 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
+@import "./fonts.css";
@custom-variant dark (&:is(.dark *));
@@ -48,6 +49,11 @@
}
:root {
+ --font-geist-sans: "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
+ --font-sans: "Figtree", "Geist", ui-sans-serif, system-ui, -apple-system, "Segoe UI",
+ sans-serif;
+ --font-geist-mono: "Geist Mono", ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas,
+ "Liberation Mono", "Courier New", monospace;
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--card: oklch(1 0 0);
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index b187b52d30..bcc23374e1 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -1,20 +1,7 @@
import type { Metadata } from "next";
-import { Geist, Geist_Mono, Figtree } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
-const figtree = Figtree({subsets:['latin'],variable:'--font-sans'});
-
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
-
export const metadata: Metadata = {
title: "Kandev - AI Kanban",
description: "AI-powered kanban board for developers",
@@ -26,10 +13,8 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
-
+
+
{children}
diff --git a/apps/web/app/settings/environment/[id]/page.tsx b/apps/web/app/settings/environment/[id]/page.tsx
index cea4613a88..6f734581eb 100644
--- a/apps/web/app/settings/environment/[id]/page.tsx
+++ b/apps/web/app/settings/environment/[id]/page.tsx
@@ -19,7 +19,7 @@ import {
DialogTitle,
} from '@/components/ui/dialog';
import { SETTINGS_DATA } from '@/lib/settings/dummy-data';
-import type { Environment, EnvironmentType, BaseDocker } from '@/lib/settings/types';
+import type { Environment, EnvironmentType, BaseDocker, AgentType } from '@/lib/settings/types';
export default function EnvironmentEditPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = use(params);
@@ -91,7 +91,7 @@ export default function EnvironmentEditPage({ params }: { params: Promise<{ id:
});
};
- const handleToggleAgent = (agentId: string) => {
+ const handleToggleAgent = (agentId: AgentType) => {
const installed = environment.installedAgents || [];
if (installed.includes(agentId)) {
setEnvironment({
@@ -287,8 +287,8 @@ export default function EnvironmentEditPage({ params }: { params: Promise<{ id:
>
handleToggleAgent(agent.id)}
+ checked={(environment.installedAgents || []).includes(agent.agent)}
+ onChange={() => handleToggleAgent(agent.agent)}
className="h-4 w-4"
/>
diff --git a/apps/web/app/settings/integrations/page.tsx b/apps/web/app/settings/integrations/page.tsx
index 2620cd253a..a5cb3e3f40 100644
--- a/apps/web/app/settings/integrations/page.tsx
+++ b/apps/web/app/settings/integrations/page.tsx
@@ -116,9 +116,12 @@ export default function IntegrationsPage() {
- {React.cloneElement(integration.icon as React.ReactElement, {
- className: 'h-5 w-5',
- })}
+ {React.isValidElement(integration.icon)
+ ? React.cloneElement(
+ integration.icon as React.ReactElement<{ className?: string }>,
+ { className: 'h-5 w-5' }
+ )
+ : integration.icon}
diff --git a/apps/web/app/task/[id]/page.tsx b/apps/web/app/task/[id]/page.tsx
index 1a3be7773f..5c3bcea5cd 100644
--- a/apps/web/app/task/[id]/page.tsx
+++ b/apps/web/app/task/[id]/page.tsx
@@ -1,6 +1,7 @@
'use client';
-import { FormEvent, useMemo, useState } from 'react';
+import type { CSSProperties } from 'react';
+import { FormEvent, useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
import { DiffModeEnum, DiffView } from '@git-diff-view/react';
import '@git-diff-view/react/styles/diff-view.css';
@@ -324,15 +325,13 @@ function buildDiffData(filePath: string) {
}
export default function TaskPage() {
- const defaultHorizontalLayout = getLocalStorage<[number, number]>(
- 'task-layout-horizontal',
- [75, 25]
- );
- const defaultRightLayout = getLocalStorage<[number, number]>('task-layout-right', [55, 45]);
- const defaultDiffMode = getLocalStorage<'unified' | 'split'>(
- 'task-diff-view-mode',
- 'unified'
- );
+ const defaultHorizontalLayout: [number, number] = [75, 25];
+ const defaultRightLayout: [number, number] = [55, 45];
+ const defaultDiffMode: 'unified' | 'split' = 'unified';
+ const [horizontalLayout, setHorizontalLayout] = useState(defaultHorizontalLayout);
+ const [rightLayout, setRightLayout] = useState(defaultRightLayout);
+ const [horizontalSeed, setHorizontalSeed] = useState(0);
+ const [rightSeed, setRightSeed] = useState(0);
const [selectedAgent, setSelectedAgent] = useState(AGENTS[0].id);
const [chats, setChats] = useState
(INITIAL_CHATS);
const [leftTab, setLeftTab] = useState('chat');
@@ -362,6 +361,29 @@ export default function TaskPage() {
? DIFF_SAMPLES[selectedDiffPath]?.newContent ?? ''
: '';
+ useEffect(() => {
+ const storedHorizontal = getLocalStorage('task-layout-horizontal', defaultHorizontalLayout);
+ const storedRight = getLocalStorage('task-layout-right', defaultRightLayout);
+ const storedDiffMode = getLocalStorage('task-diff-view-mode', defaultDiffMode);
+
+ if (
+ storedHorizontal[0] !== defaultHorizontalLayout[0] ||
+ storedHorizontal[1] !== defaultHorizontalLayout[1]
+ ) {
+ setHorizontalLayout(storedHorizontal);
+ setHorizontalSeed((value) => value + 1);
+ }
+
+ if (storedRight[0] !== defaultRightLayout[0] || storedRight[1] !== defaultRightLayout[1]) {
+ setRightLayout(storedRight);
+ setRightSeed((value) => value + 1);
+ }
+
+ if (storedDiffMode !== defaultDiffMode) {
+ setDiffViewMode(storedDiffMode);
+ }
+ }, []);
+
const handleSubmit = (event: FormEvent) => {
event.preventDefault();
const trimmed = messageInput.trim();
@@ -480,7 +502,10 @@ export default function TaskPage() {
-
+
@@ -675,11 +700,15 @@ export default function TaskPage() {
setLocalStorage('task-layout-horizontal', sizes)}
+ onLayout={(sizes) => {
+ setHorizontalLayout(sizes as [number, number]);
+ setLocalStorage('task-layout-horizontal', sizes);
+ }}
>
-
+
-
+
{isBottomCollapsed ? (
{topFilesPanel}
@@ -997,15 +1026,19 @@ export default function TaskPage() {
) : (
setLocalStorage('task-layout-right', sizes)}
+ onLayout={(sizes) => {
+ setRightLayout(sizes as [number, number]);
+ setLocalStorage('task-layout-right', sizes);
+ }}
>
-
+
{topFilesPanel}
-
+