chore: remove landing app (#154)

This commit is contained in:
Carlos Florêncio
2026-02-02 17:31:11 +00:00
committed by GitHub
parent 6bba1cab9f
commit d5085cfd75
46 changed files with 6 additions and 1775 deletions
+6 -24
View File
@@ -4,7 +4,6 @@
# Directories
BACKEND_DIR := apps/backend
WEB_DIR := apps/web
LANDING_DIR := apps/landing
APPS_DIR := apps
# Tools
@@ -46,20 +45,18 @@ help:
@echo " dev Run backend + web via CLI (auto ports)"
@echo " dev-backend Run backend in development mode (port 8080)"
@echo " dev-web Run web app in development mode (port 3000)"
@echo " dev-landing Run landing page in development mode (port 3001)"
@echo " dev Note: Uses apps/cli launcher (auto ports)"
@echo ""
@echo "Production Commands:"
@echo " start Install deps, build, and start backend + web in production mode"
@echo ""
@echo "Build Commands:"
@echo " build Build backend, web app, and landing page"
@echo " build Build backend and web app"
@echo " build-backend Build backend binary"
@echo " build-web Build web app for production"
@echo " build-landing Build landing page for production"
@echo ""
@echo "Installation:"
@echo " install Install all dependencies (backend + web + landing)"
@echo " install Install all dependencies (backend + web)"
@echo " install-backend Install backend dependencies"
@echo " install-web Install web dependencies (uses pnpm workspace)"
@echo ""
@@ -103,17 +100,12 @@ dev-web:
@echo "Starting web app on http://localhost:3000"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web dev
.PHONY: dev-landing
dev-landing:
@echo "Starting landing page on http://localhost:3001"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/landing dev
#
# Build
#
.PHONY: build
build: build-backend build-web build-landing
build: build-backend build-web
@printf "\n$(GREEN)$(BOLD)✓ Build complete!$(RESET)\n"
#
@@ -153,16 +145,6 @@ build-web-quiet:
@printf " $(DIM)Web app$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/web build 2>&1 | grep -v "Warning:" | grep -v "parseLineType" | grep -v "^$$" || true
.PHONY: build-landing
build-landing:
@printf "$(CYAN)Building landing page...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/landing build
.PHONY: build-landing-quiet
build-landing-quiet:
@printf " $(DIM)Landing page$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) --filter @kandev/landing build 2>&1 | grep -v "Warning:" | grep -v "parseLineType" | grep -v "^$$" || true
#
# Installation
#
@@ -178,7 +160,7 @@ install-backend:
.PHONY: install-web
install-web:
@printf "$(CYAN)Installing web and landing dependencies...$(RESET)\n"
@printf "$(CYAN)Installing web dependencies...$(RESET)\n"
@cd $(APPS_DIR) && $(PNPM) install --silent 2>/dev/null || cd $(APPS_DIR) && $(PNPM) install
#
@@ -256,8 +238,8 @@ clean-backend:
.PHONY: clean-web
clean-web:
@printf "$(CYAN)Cleaning web and landing artifacts...$(RESET)\n"
@rm -rf $(WEB_DIR)/.next $(LANDING_DIR)/.next $(APPS_DIR)/node_modules
@printf "$(CYAN)Cleaning web artifacts...$(RESET)\n"
@rm -rf $(WEB_DIR)/.next $(APPS_DIR)/node_modules
@rm -rf $(APPS_DIR)/packages/*/node_modules
.PHONY: clean-db
-105
View File
@@ -1,105 +0,0 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
import { IconTarget, IconRocket, IconHeartHandshake } from '@tabler/icons-react';
const values = [
{
icon: IconTarget,
title: 'Developer First',
description: 'Built by developers, for developers. We understand your workflow because we live it every day.',
},
{
icon: IconRocket,
title: 'Ship Fast, Ship Safe',
description: 'Speed without sacrificing security. AI-powered automation with human oversight and approval.',
},
{
icon: IconHeartHandshake,
title: 'Open & Transparent',
description: 'Open source at heart. We believe in building in public and learning from the community.',
},
];
const timeline = [
{
year: '2026',
title: 'The Beginning',
description: 'kandev.ai is born from the need to combine visual task management with AI-assisted development.',
},
];
export default function AboutPage() {
return (
<div className="container mx-auto py-24 max-w-6xl px-4">
<div className="flex flex-col gap-4 mb-12">
<h1 className="text-4xl font-bold">About kandev.ai</h1>
<p className="text-lg text-muted-foreground">
We're building the future of development workflows.
</p>
</div>
<div className="prose prose-slate dark:prose-invert max-w-none mb-12">
<h2>Our Mission</h2>
<p>
kandev.ai exists to make software development more visual, more collaborative, and more efficient.
We believe that combining Kanban methodology with AI-assisted coding creates a workflow that's
greater than the sum of its parts.
</p>
<p>
Traditional development tools force you to context switch between project management, coding,
and deployment. We're building a unified experience where your Kanban board is the control center
for AI agents working in isolated, secure environments.
</p>
</div>
<div className="mb-12">
<h2 className="text-2xl font-bold mb-6">Our Values</h2>
<div className="grid gap-6 md:grid-cols-3">
{values.map((value) => {
const Icon = value.icon;
return (
<Card key={value.title}>
<CardHeader>
<Icon className="h-8 w-8 text-primary mb-2" />
<CardTitle>{value.title}</CardTitle>
</CardHeader>
<CardContent>
<CardDescription>{value.description}</CardDescription>
</CardContent>
</Card>
);
})}
</div>
</div>
<div className="mb-12">
<h2 className="text-2xl font-bold mb-6">Our Journey</h2>
<div className="space-y-6">
{timeline.map((item) => (
<Card key={item.year}>
<CardHeader>
<div className="text-sm text-muted-foreground mb-1">{item.year}</div>
<CardTitle>{item.title}</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">{item.description}</p>
</CardContent>
</Card>
))}
</div>
</div>
<div className="prose prose-slate dark:prose-invert max-w-none">
<h2>Why Kanban + Development + AI?</h2>
<p>
Kanban boards provide visual clarity. Development tools provide execution. AI provides automation.
Together, they create a workflow where you can see all your work, execute it in parallel, and
automate the routine parts while maintaining full control.
</p>
<p>
We're just getting started. As we grow, we'll continue to focus on making development more
intuitive, more powerful, and more enjoyable.
</p>
</div>
</div>
);
}
-5
View File
@@ -1,5 +0,0 @@
import { redirect } from 'next/navigation';
export default function BlogPage() {
redirect('/changelog');
}
@@ -1,61 +0,0 @@
import { notFound } from 'next/navigation';
import { MDXRemote } from 'next-mdx-remote/rsc';
import { getChangelogEntry, getChangelogSlugs } from '@/lib/changelog';
import { mdxComponents } from '@/components/mdx-components';
function formatDate(date: string) {
if (!date) return '';
return new Intl.DateTimeFormat('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
}).format(new Date(date));
}
export async function generateStaticParams() {
const slugs = await getChangelogSlugs();
return slugs.map((slug) => ({ slug }));
}
export default async function ChangelogEntryPage({
params,
}: {
params: { slug?: string } | Promise<{ slug?: string }>;
}) {
const resolvedParams = await Promise.resolve(params);
const slug = resolvedParams.slug;
if (!slug) {
notFound();
}
const entry = await getChangelogEntry(slug);
if (!entry) {
notFound();
}
return (
<div className="relative overflow-hidden">
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_top,_rgba(109,40,217,0.12),_transparent_55%)]" />
<div className="container mx-auto max-w-4xl px-4 pb-24 pt-24">
<div className="mb-10">
<p className="text-sm uppercase tracking-[0.3em] text-muted-foreground">{entry.surface}</p>
<h1 className="mt-4 text-4xl font-semibold text-foreground md:text-5xl">{entry.title}</h1>
<p className="mt-4 text-sm text-muted-foreground">{formatDate(entry.date)}</p>
</div>
<div className="space-y-6">
{entry.summary.map((line) => (
<p key={line} className="text-lg text-muted-foreground">
{line}
</p>
))}
</div>
<div className="mt-10 border-t border-border/60 pt-8">
<MDXRemote source={entry.content} components={mdxComponents} />
</div>
</div>
</div>
);
}
-70
View File
@@ -1,70 +0,0 @@
import Link from 'next/link';
import { getChangelogEntries } from '@/lib/changelog';
function formatDate(date: string) {
if (!date) return '';
return new Intl.DateTimeFormat('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
}).format(new Date(date));
}
export default async function ChangelogPage() {
const entries = await getChangelogEntries();
return (
<div className="relative overflow-hidden">
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_top,_rgba(109,40,217,0.12),_transparent_55%)]" />
<div className="container mx-auto max-w-6xl px-4 pb-24 pt-24">
<div className="mb-12">
<p className="text-sm uppercase tracking-[0.3em] text-muted-foreground">Changelog</p>
<h1 className="mt-4 text-4xl font-semibold text-foreground md:text-5xl">Product updates</h1>
<p className="mt-4 max-w-2xl text-lg text-muted-foreground">
A running log of kandev.ai releases, shipped features, and operational improvements.
</p>
</div>
<div className="space-y-12">
{entries.map((entry, index) => (
<article
key={entry.slug}
className="grid gap-6 border-b border-border/60 pb-12 md:grid-cols-[180px_1fr]"
>
<div className="text-sm text-muted-foreground">
<p className="font-medium text-foreground">{entry.surface}</p>
<p>{formatDate(entry.date)}</p>
{index === 0 ? (
<span className="mt-3 inline-flex items-center rounded-full border border-border bg-background px-2 py-1 text-[11px] font-semibold uppercase tracking-[0.2em] text-foreground">
Latest
</span>
) : null}
</div>
<div>
<h2 className="text-2xl font-semibold text-foreground">
<Link href={`/changelog/${entry.slug}`} className="hover:text-primary">
{entry.title}
</Link>
</h2>
<div className="mt-4 space-y-3 text-muted-foreground">
{entry.summary.map((line) => (
<p key={line}>{line}</p>
))}
</div>
<div className="mt-6">
<Link
href={`/changelog/${entry.slug}`}
className="text-sm font-semibold text-primary hover:text-primary/80"
>
Read full update →
</Link>
</div>
</div>
</article>
))}
</div>
</div>
</div>
);
}
-35
View File
@@ -1,35 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
export default function AgentsDocsPage() {
return (
<DocsShell
eyebrow="Features"
title="AI Agents"
description="Agents are containerized workers that execute tasks. They communicate with the backend using ACP, a JSON-RPC protocol over stdin/stdout."
>
<section className="grid gap-4 md:grid-cols-2">
<DocsCard title="Agent lifecycle">
<ol className="space-y-2 list-decimal list-inside">
<li>Container starts with agentctl on port 9999.</li>
<li>Backend calls <code>initialize</code> and <code>session/new</code>.</li>
<li>Tasks are sent via <code>session/prompt</code>.</li>
<li>Agent streams <code>session/update</code> notifications.</li>
</ol>
</DocsCard>
<DocsCard title="What agents can do" variant="accent">
<ul className="space-y-2">
<li>Read and edit files inside the worktree</li>
<li>Run shell commands with approval gates</li>
<li>Stream progress updates to the UI</li>
</ul>
</DocsCard>
</section>
<DocsCard title="ACP example" variant="muted">
<DocsCode>{`{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"1.0"}}`}</DocsCode>
</DocsCard>
</DocsShell>
);
}
-51
View File
@@ -1,51 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
const stages = [
{ title: 'Backlog', desc: 'Collect ideas and future work.' },
{ title: 'In Progress', desc: 'Active tasks owned by agents.' },
{ title: 'Review', desc: 'Approvals and QA before merge.' },
{ title: 'Done', desc: 'Completed and shipped items.' },
];
export default function BoardsDocsPage() {
return (
<DocsShell
eyebrow="Core Concepts"
title="Boards"
description="Boards are the Kanban surface for your workspace. Each board defines columns that represent the lifecycle of a task, from backlog to review to done."
>
<section className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
{stages.map((stage) => (
<DocsCard key={stage.title} title={stage.title}>
<p>{stage.desc}</p>
</DocsCard>
))}
</section>
<DocsCard title="Example board config" variant="muted">
<p>Keep columns minimal and apply WIP limits where review capacity is tight.</p>
<DocsCode>{`board:
name: "Product Roadmap"
columns:
- name: "Backlog"
wipLimit: null
- name: "In Progress"
wipLimit: 4
- name: "Review"
wipLimit: 2
- name: "Done"
wipLimit: null`}</DocsCode>
</DocsCard>
<DocsCard title="Tips" variant="accent">
<ul className="space-y-2">
<li>Use a single “Review” column so approvals are centralized.</li>
<li>Keep WIP limits low to prevent agent overload.</li>
<li>Archive finished tasks weekly to keep focus on active work.</li>
</ul>
</DocsCard>
</DocsShell>
);
}
-36
View File
@@ -1,36 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
export default function DockerDocsPage() {
return (
<DocsShell
eyebrow="Features"
title="Docker Execution"
description="Every agent runs inside a Docker container to keep execution isolated from your host machine. Containers are created per task and destroyed after completion."
>
<section className="grid gap-4 md:grid-cols-2">
<DocsCard title="What is mounted">
<ul className="space-y-2">
<li><code>/workspace</code> — your repository worktree</li>
<li><code>/root/.augment/sessions</code> — agent session storage (when enabled)</li>
</ul>
</DocsCard>
<DocsCard title="Why containers" variant="accent">
<ol className="space-y-2 list-decimal list-inside">
<li>Consistent, reproducible environments</li>
<li>Security boundaries for file and network access</li>
<li>Easy cleanup after task completion</li>
</ol>
</DocsCard>
</section>
<DocsCard title="Sample run (conceptual)" variant="muted">
<DocsCode>{`docker run -it --rm \
-v /path/to/repo:/workspace \
-e TASK_DESCRIPTION="Fix the login bug" \
kandev/augment-agent:latest`}</DocsCode>
</DocsCard>
</DocsShell>
);
}
@@ -1,42 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
export default function InstallationDocsPage() {
return (
<DocsShell
eyebrow="Getting Started"
title="Installation"
description="These steps outline a typical local installation. Exact commands may change as we finalize the installer, but the flow remains the same."
>
<section className="grid gap-4 md:grid-cols-2">
<DocsCard title="Prerequisites" variant="accent">
<ul className="space-y-2">
<li>Docker Desktop (or Docker Engine)</li>
<li>Git 2.30+</li>
<li>Node.js 20+</li>
</ul>
</DocsCard>
<DocsCard title="Install the repo">
<DocsCode>{`git clone https://github.com/kdlbs/kandev.git
cd kandev
pnpm install`}</DocsCode>
</DocsCard>
</section>
<DocsCard title="Run the services" variant="muted">
<p>Start the backend, app, and landing site in separate terminals.</p>
<DocsCode>{`pnpm --filter @kandev/backend dev
pnpm --filter @kandev/web dev
pnpm --filter @kandev/landing dev`}</DocsCode>
</DocsCard>
<DocsCard title="Verify">
<p>
Open the UI and create a test workspace. The system should start an agent container on demand and stream
progress back over WebSocket.
</p>
</DocsCard>
</DocsShell>
);
}
-12
View File
@@ -1,12 +0,0 @@
import { DocsSidebar } from '@/components/docs-sidebar';
export default function DocsLayout({ children }: { children: React.ReactNode }) {
return (
<div className="min-h-screen bg-[radial-gradient(circle_at_top,_rgba(109,40,217,0.12),_transparent_55%)]">
<div className="mx-auto flex w-full max-w-6xl gap-8 px-6">
<DocsSidebar />
<main className="flex-1 py-16">{children}</main>
</div>
</div>
);
}
-51
View File
@@ -1,51 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
export default function DocsPage() {
return (
<DocsShell
eyebrow="Getting Started"
title="Documentation"
description="Learn how kandev.ai orchestrates AI agents across isolated worktrees, Docker containers, and a WebSocket-first control plane."
>
<section className="grid gap-4 md:grid-cols-2">
<DocsCard title="What is kandev.ai?" variant="accent">
<p>
kandev.ai is a WebSocket-first platform that pairs Kanban task management with AI-assisted execution.
Every task runs inside a dedicated git worktree and a Docker container, with approvals for sensitive
operations.
</p>
</DocsCard>
<DocsCard title="Why teams use it">
<ul className="space-y-2">
<li>Ship in parallel without branch conflicts.</li>
<li>Keep execution auditable and reviewable.</li>
<li>Stream progress updates in real time.</li>
</ul>
</DocsCard>
</section>
<DocsCard title="Core workflow" variant="muted">
<p>From task creation to approval, kandev.ai follows a consistent loop.</p>
<DocsCode>{`1. Create task on the board
2. Start agent container via agentctl
3. Initialize ACP session
4. Stream progress over WebSocket
5. Review and approve output`}</DocsCode>
</DocsCard>
<section className="grid gap-4 md:grid-cols-3">
<DocsCard title="Workspaces">
<p>Group repositories, boards, and team settings.</p>
</DocsCard>
<DocsCard title="Boards" variant="accent">
<p>Model your workflow with Kanban columns.</p>
</DocsCard>
<DocsCard title="Tasks">
<p>Define the scope, constraints, and agent requirements.</p>
</DocsCard>
</section>
</DocsShell>
);
}
@@ -1,46 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
const steps = [
{
step: '1. Create a workspace',
body: 'Create a workspace for the repository you want to manage.',
},
{
step: '2. Add a board and task',
body: 'Add a board with columns, then create a task that describes the desired change.',
},
{
step: '3. Run an agent',
body: 'Start an agent from the task. The UI should stream progress updates as the agent works.',
},
{
step: '4. Review and merge',
body: 'Review the diff, approve any gated actions, and merge the worktree.',
},
];
export default function QuickStartDocsPage() {
return (
<DocsShell
eyebrow="Getting Started"
title="Quick Start"
description="This guide walks through a minimal workflow so you can see the agent loop end-to-end."
>
<section className="grid gap-4 md:grid-cols-2">
{steps.map((item) => (
<DocsCard key={item.step} title={item.step}>
<p>{item.body}</p>
</DocsCard>
))}
</section>
<DocsCard title="Example task prompt" variant="muted">
<DocsCode>{`Task: Update onboarding copy
Constraints: Touch only apps/landing
Acceptance: Copy updated and passes lint`}</DocsCode>
</DocsCard>
</DocsShell>
);
}
-42
View File
@@ -1,42 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
export default function TasksDocsPage() {
return (
<DocsShell
eyebrow="Core Concepts"
title="Tasks"
description="Tasks capture the work that agents will execute. A good task includes a goal, constraints, and the desired output so the agent can plan accurately."
>
<section className="grid gap-4 md:grid-cols-2">
<DocsCard title="Recommended fields">
<ul className="space-y-2">
<li>Title and description</li>
<li>Priority and due date</li>
<li>Repository and base branch</li>
<li>Agent type and environment</li>
<li>Approval requirements</li>
</ul>
</DocsCard>
<DocsCard title="Writing effective tasks" variant="accent">
<ol className="space-y-2 list-decimal list-inside">
<li>State the outcome, not the steps.</li>
<li>Call out sensitive files or commands.</li>
<li>Include acceptance criteria.</li>
</ol>
</DocsCard>
</section>
<DocsCard title="Example task" variant="muted">
<DocsCode>{`Title: Add changelog page
Repo: github.com/kdlbs/kandev
Branch: main
Agent: augment-agent
Goal: Replace blog with changelog
Constraints: Keep existing navbar/footer
Output: New route + MDX entries`}</DocsCode>
</DocsCard>
</DocsShell>
);
}
-42
View File
@@ -1,42 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
export default function WorkspacesDocsPage() {
return (
<DocsShell
eyebrow="Core Concepts"
title="Workspaces"
description="Workspaces are the top-level container in kandev.ai. They group repositories, boards, agent settings, and members so teams can ship without stepping on each other."
>
<section className="grid gap-4 md:grid-cols-2">
<DocsCard title="What a workspace includes">
<ul className="space-y-2">
<li>Boards that define workflow stages</li>
<li>Repositories connected to those boards</li>
<li>Default agent settings and approvals</li>
<li>Member roles and permissions</li>
</ul>
</DocsCard>
<DocsCard title="Best practices" variant="accent">
<ol className="space-y-2 list-decimal list-inside">
<li>Use one workspace per product or client.</li>
<li>Keep board naming consistent across teams.</li>
<li>Set approvals at the workspace level.</li>
</ol>
</DocsCard>
</section>
<DocsCard title="Example structure" variant="muted">
<p>A workspace keeps everything organized in one place.</p>
<DocsCode>{`Workspace: "Core Platform"
Boards:
- Product Roadmap
- Incident Response
Repositories:
- github.com/kdlbs/kandev
- github.com/kdlbs/kandev-docs`}</DocsCode>
</DocsCard>
</DocsShell>
);
}
-40
View File
@@ -1,40 +0,0 @@
import { DocsShell } from '@/components/docs-shell';
import { DocsCard } from '@/components/docs-card';
import { DocsCode } from '@/components/docs-code';
const benefits = [
{ title: 'Parallel tasks', desc: 'Multiple agents can work without branch collisions.' },
{ title: 'Clean diffs', desc: 'Each task produces an isolated patch.' },
{ title: 'Fast setup', desc: 'No extra clones required.' },
];
export default function WorktreesDocsPage() {
return (
<DocsShell
eyebrow="Features"
title="Git Worktrees"
description="kandev.ai uses git worktrees to isolate each agent run. Every task gets its own directory that shares the same repository history while keeping file changes separate."
>
<section className="grid gap-4 md:grid-cols-3">
{benefits.map((item) => (
<DocsCard key={item.title} title={item.title}>
<p>{item.desc}</p>
</DocsCard>
))}
</section>
<DocsCard title="How it looks on disk" variant="muted">
<DocsCode>{`/repos/kandev
/main (primary checkout)
/.worktrees/task-123
/.worktrees/task-124`}</DocsCode>
</DocsCard>
<DocsCard title="Cleanup" variant="accent">
<p>
When a task is completed and merged, the worktree can be safely removed without affecting the main checkout.
</p>
</DocsCard>
</DocsShell>
);
}
-62
View File
@@ -1,62 +0,0 @@
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="en">
<head>
<title>Error - kandev.ai</title>
</head>
<body style={{
margin: 0,
padding: 0,
fontFamily: 'system-ui, sans-serif',
display: 'flex',
minHeight: '100vh',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#ffffff'
}}>
<div style={{
textAlign: 'center',
padding: '2rem'
}}>
<h1 style={{
fontSize: '2rem',
fontWeight: 'bold',
marginBottom: '1rem',
color: '#000000'
}}>
Something went wrong!
</h1>
<p style={{
marginBottom: '2rem',
color: '#666666'
}}>
We're sorry, but something unexpected happened.
</p>
<button
onClick={() => reset()}
style={{
padding: '0.75rem 1.5rem',
backgroundColor: '#6B46C1',
color: '#ffffff',
border: 'none',
borderRadius: '0.5rem',
fontSize: '1rem',
fontWeight: '500',
cursor: 'pointer'
}}
>
Try again
</button>
</div>
</body>
</html>
);
}
-155
View File
@@ -1,155 +0,0 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "@kandev/theme/fonts.css";
@source "../components/**/*.{js,ts,jsx,tsx}";
@source "../../packages/ui/src/**/*.{js,ts,jsx,tsx}";
@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-sans);
--font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: var(--radius);
--radius-md: var(--radius);
--radius-lg: var(--radius);
--radius-xl: var(--radius);
--radius-2xl: var(--radius);
--radius-3xl: var(--radius);
--radius-4xl: var(--radius);
}
: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);
--card-foreground: oklch(0.145 0 0);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.145 0 0);
--primary: oklch(0.51 0.23 277);
--primary-foreground: oklch(0.96 0.02 272);
--secondary: oklch(0.967 0.001 286.375);
--secondary-foreground: oklch(0.21 0.006 285.885);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.68 0.18 276);
--accent-foreground: oklch(0.98 0 0);
--destructive: oklch(0.58 0.22 27);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.79 0.10 275);
--chart-2: oklch(0.68 0.16 277);
--chart-3: oklch(0.59 0.20 277);
--chart-4: oklch(0.51 0.23 277);
--chart-5: oklch(0.46 0.21 277);
--radius: 0.375rem;
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: oklch(0.51 0.23 277);
--sidebar-primary-foreground: oklch(0.96 0.02 272);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--card: oklch(0.205 0 0);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.205 0 0);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.59 0.20 277);
--primary-foreground: oklch(0.96 0.02 272);
--secondary: oklch(0.274 0.006 286.033);
--secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.62 0.18 276);
--accent-foreground: oklch(0.98 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.79 0.10 275);
--chart-2: oklch(0.68 0.16 277);
--chart-3: oklch(0.59 0.20 277);
--chart-4: oklch(0.51 0.23 277);
--chart-5: oklch(0.46 0.21 277);
--sidebar: oklch(0.205 0 0);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.68 0.16 277);
--sidebar-primary-foreground: oklch(0.96 0.02 272);
--sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.556 0 0);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
html {
scrollbar-gutter: stable;
}
body {
@apply bg-background text-foreground;
}
}
@layer utilities {
.animate-pulse-slow {
animation: pulse-slow 8s ease-in-out infinite;
}
}
@keyframes pulse-slow {
0%, 100% {
transform: scale(1);
opacity: 0.6;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
}
-32
View File
@@ -1,32 +0,0 @@
import type { Metadata } from 'next';
import './globals.css';
import { ThemeProvider } from '@/components/theme-provider';
import { Navbar } from '@/components/navbar';
import { Footer } from '@/components/footer';
export const dynamic = 'force-dynamic';
export const metadata: Metadata = {
title: 'kandev.ai - AI-powered Kanban for Developers',
description: 'Kanban meets AI development. Manage tasks with isolated worktrees, Docker containers, and AI agents.',
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className="font-sans antialiased">
<ThemeProvider>
<Navbar />
<main className="pt-14">
{children}
</main>
<Footer />
</ThemeProvider>
</body>
</html>
);
}
-15
View File
@@ -1,15 +0,0 @@
import { HeroSection } from '@/components/hero-section';
import { FeaturesSection } from '@/components/features-section';
import { AgentsSection } from '@/components/agents-section';
import { FutureFeaturesSection } from '@/components/future-features-section';
export default function HomePage() {
return (
<main>
<HeroSection />
<FeaturesSection />
<AgentsSection />
<FutureFeaturesSection />
</main>
);
}
@@ -1,54 +0,0 @@
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
const agents = [
{
name: 'Claude Code',
description: "Anthropic's official CLI for Claude. Powerful agent with excellent reasoning and code generation.",
icon: '🤖',
},
{
name: 'OpenAI Codex',
description: 'GPT-4 powered coding assistant with strong performance across multiple languages.',
icon: '🔮',
},
{
name: 'Augment',
description: 'AI coding assistant focused on code completion and refactoring workflows.',
icon: '⚡',
},
{
name: 'Opencode',
description: 'Open-source AI coding agent with customizable models and integrations.',
icon: '🌐',
},
];
export function AgentsSection() {
return (
<section className="w-full bg-muted/50 py-16 md:py-24">
<div className="container mx-auto max-w-6xl px-4">
<div className="flex flex-col items-center gap-4 text-center">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
Supported AI Agents
</h2>
<p className="max-w-2xl text-lg text-muted-foreground">
Work with your favorite AI coding assistants. All agents run in isolated, secure environments.
</p>
</div>
<div className="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-4">
{agents.map((agent) => (
<Card key={agent.name} className="transition-transform hover:scale-105 cursor-pointer">
<CardHeader>
<div className="text-4xl mb-2">{agent.icon}</div>
<CardTitle>{agent.name}</CardTitle>
</CardHeader>
<CardContent>
<CardDescription>{agent.description}</CardDescription>
</CardContent>
</Card>
))}
</div>
</div>
</section>
);
}
@@ -1,28 +0,0 @@
import type { HTMLAttributes } from 'react';
type ChangelogVideoProps = {
src: string;
poster?: string;
caption?: string;
} & HTMLAttributes<HTMLVideoElement>;
export function ChangelogVideo({ src, poster, caption, className, ...props }: ChangelogVideoProps) {
return (
<figure className="mt-6">
<video
className={`w-full rounded-2xl border border-border bg-muted/40 ${className ?? ''}`}
src={src}
poster={poster}
autoPlay
loop
muted
playsInline
preload="metadata"
{...props}
/>
{caption ? (
<figcaption className="mt-2 text-sm text-muted-foreground">{caption}</figcaption>
) : null}
</figure>
);
}
-26
View File
@@ -1,26 +0,0 @@
import type { ReactNode } from 'react';
export function DocsCard({
title,
children,
variant = 'default',
}: {
title: string;
children: ReactNode;
variant?: 'default' | 'accent' | 'muted';
}) {
const base =
'rounded-2xl border border-border/60 bg-card/50 p-6 shadow-[0_12px_40px_-28px_rgba(37,99,235,0.5)] backdrop-blur';
const variants: Record<typeof variant, string> = {
default: 'bg-card/50',
accent: 'border-primary/30 bg-primary/10',
muted: 'bg-muted/30',
};
return (
<div className={`${base} ${variants[variant]}`}>
<h2 className="text-lg font-semibold text-foreground">{title}</h2>
<div className="mt-3 text-sm text-muted-foreground">{children}</div>
</div>
);
}
-9
View File
@@ -1,9 +0,0 @@
import type { ReactNode } from 'react';
export function DocsCode({ children }: { children: ReactNode }) {
return (
<pre className="mt-4 rounded-xl border border-primary/30 bg-background/80 p-4 text-sm text-foreground shadow-[0_16px_30px_-24px_rgba(59,130,246,0.6)]">
<code>{children}</code>
</pre>
);
}
-25
View File
@@ -1,25 +0,0 @@
export function DocsShell({
eyebrow,
title,
description,
children,
}: {
eyebrow: string;
title: string;
description: string;
children: React.ReactNode;
}) {
return (
<div className="relative overflow-hidden">
<div className="absolute inset-0 -z-10 " />
<div className="space-y-10">
<header className="space-y-3">
<p className="text-sm uppercase tracking-[0.3em] text-muted-foreground">{eyebrow}</p>
<h1 className="text-4xl font-semibold text-foreground">{title}</h1>
<p className="text-lg text-muted-foreground">{description}</p>
</header>
{children}
</div>
</div>
);
}
-65
View File
@@ -1,65 +0,0 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { cn } from '@/lib/utils';
const sections = [
{
title: 'Getting Started',
items: [
{ title: 'Introduction', href: '/docs' },
{ title: 'Installation', href: '/docs/installation' },
{ title: 'Quick Start', href: '/docs/quick-start' },
],
},
{
title: 'Core Concepts',
items: [
{ title: 'Workspaces', href: '/docs/workspaces' },
{ title: 'Boards', href: '/docs/boards' },
{ title: 'Tasks', href: '/docs/tasks' },
],
},
{
title: 'Features',
items: [
{ title: 'Git Worktrees', href: '/docs/worktrees' },
{ title: 'AI Agents', href: '/docs/agents' },
{ title: 'Docker Execution', href: '/docs/docker' },
],
},
];
export function DocsSidebar() {
const pathname = usePathname();
return (
<aside className="sticky top-22 h-[calc(100vh-3.5rem)] w-64 shrink-0 overflow-y-auto border-r border-border py-8 pr-6">
<nav className="space-y-6">
{sections.map((section) => (
<div key={section.title}>
<h4 className="mb-2 font-semibold text-sm">{section.title}</h4>
<ul className="space-y-2">
{section.items.map((item) => (
<li key={item.href}>
<Link
href={item.href}
className={cn(
'block text-sm transition-colors hover:text-foreground',
pathname === item.href
? 'font-medium text-foreground'
: 'text-muted-foreground'
)}
>
{item.title}
</Link>
</li>
))}
</ul>
</div>
))}
</nav>
</aside>
);
}
@@ -1,72 +0,0 @@
import { IconGitBranch, IconCode, IconShieldCheck, IconPlugConnected, IconFolders, IconBrandDocker } from '@tabler/icons-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
const features = [
{
icon: IconCode,
title: 'Parallel Agent Execution',
description:
'Run multiple AI coding agents simultaneously on different tasks. Each agent works in isolated environments, accelerating development without conflicts.',
},
{
icon: IconGitBranch,
title: 'Isolated Worktrees',
description:
'Each task gets its own git worktree. Work on multiple features, bugs, and experiments in parallel without branch switching or stashing.',
},
{
icon: IconShieldCheck,
title: 'Built-in Code Review',
description:
'Automated code review and approval workflows. Review agent changes before merging, with full audit logs and rollback capabilities.',
},
{
icon: IconPlugConnected,
title: 'MCP Integration',
description:
'Connect with Model Context Protocol servers. Extend agent capabilities with custom tools, APIs, and integrations.',
},
{
icon: IconFolders,
title: 'Multiple Workspaces',
description:
'Organize projects by client, team, or product. Switch between workspaces seamlessly and keep your work isolated.',
},
{
icon: IconBrandDocker,
title: 'Local Docker',
description:
'AI agents run in local Docker containers for complete isolation. Secure execution with approval workflows and audit logs for every operation.',
},
];
export function FeaturesSection() {
return (
<section id="features" className="container mx-auto max-w-6xl py-16 md:py-24 px-4">
<div className="flex flex-col items-center gap-4 text-center">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
Build for development velocity
</h2>
<p className="max-w-2xl text-lg text-muted-foreground">
Combine the visual clarity of Kanban boards with the power of AI-assisted development.
</p>
</div>
<div className="mt-12 grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => {
const Icon = feature.icon;
return (
<Card key={feature.title}>
<CardHeader>
<Icon className="h-8 w-8 text-primary mb-2" />
<CardTitle>{feature.title}</CardTitle>
</CardHeader>
<CardContent>
<CardDescription>{feature.description}</CardDescription>
</CardContent>
</Card>
);
})}
</div>
</section>
);
}
-80
View File
@@ -1,80 +0,0 @@
import Link from 'next/link';
import { IconBrandGithub, IconBrandTwitter, IconBrandDiscord } from '@tabler/icons-react';
export function Footer() {
return (
<footer className="border-t border-border bg-background">
<div className="container mx-auto max-w-6xl py-12 md:py-16 px-4">
<div className="grid gap-8 md:grid-cols-3">
<div>
<h3 className="font-bold mb-4">kandev.ai</h3>
<p className="text-sm text-muted-foreground">
AI-powered Kanban for developers. Ship faster with isolated worktrees and secure Docker execution.
</p>
</div>
<div>
<h4 className="font-semibold mb-4">Product</h4>
<ul className="space-y-2 text-sm">
<li>
<Link href="/#features" className="text-muted-foreground hover:text-foreground">
Features
</Link>
</li>
<li>
<Link href="/docs" className="text-muted-foreground hover:text-foreground">
Documentation
</Link>
</li>
</ul>
</div>
<div>
<h4 className="font-semibold mb-4">Company</h4>
<ul className="space-y-2 text-sm">
<li>
<Link href="/about" className="text-muted-foreground hover:text-foreground">
About
</Link>
</li>
<li>
<Link href="/changelog" className="text-muted-foreground hover:text-foreground">
Changelog
</Link>
</li>
</ul>
</div>
</div>
<div className="mt-8 border-t border-border pt-8 flex flex-col md:flex-row justify-between items-center gap-4">
<p className="text-sm text-muted-foreground">
© 2026 kandev.ai. All rights reserved. Built with respect for your privacy and GDPR compliance.
</p>
<div className="flex gap-4">
<a
href="https://github.com/kdlbs/kandev"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground"
>
<IconBrandGithub className="h-5 w-5" />
</a>
<a
href="https://twitter.com/kandev_ai"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground"
>
<IconBrandTwitter className="h-5 w-5" />
</a>
<a
href="https://discord.gg/kandev"
target="_blank"
rel="noopener noreferrer"
className="text-muted-foreground hover:text-foreground"
>
<IconBrandDiscord className="h-5 w-5" />
</a>
</div>
</div>
</div>
</footer>
);
}
@@ -1,60 +0,0 @@
import { IconCloud, IconDeviceMobile, IconUsers } from '@tabler/icons-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@kandev/ui/card';
import { Badge } from '@kandev/ui/badge';
const futureFeatures = [
{
icon: IconCloud,
title: 'Remote Executors',
description: 'Run AI agents on cloud or remote machines. Scale your development with powerful remote compute.',
status: 'Coming Soon',
},
{
icon: IconDeviceMobile,
title: 'Mobile App',
description: 'Launch and monitor tasks from your mobile device. Stay connected to your development workflow.',
status: 'In Development',
},
{
icon: IconUsers,
title: 'Team Collaboration',
description: 'Share workspaces, assign tasks, and collaborate with your team in real-time.',
status: 'Planned',
},
];
export function FutureFeaturesSection() {
return (
<section className="container mx-auto max-w-6xl py-16 md:py-24 px-4">
<div className="flex flex-col items-center gap-4 text-center">
<h2 className="text-3xl font-bold tracking-tight sm:text-4xl md:text-5xl">
Coming Soon
</h2>
<p className="max-w-2xl text-lg text-muted-foreground">
We're constantly improving kandev.ai. Here's what's on our roadmap.
</p>
</div>
<div className="mt-12 grid gap-6 md:grid-cols-3">
{futureFeatures.map((feature) => {
const Icon = feature.icon;
return (
<Card key={feature.title} className="relative">
<CardHeader>
<Icon className="h-8 w-8 text-primary mb-2" />
<div className="flex items-center justify-between">
<CardTitle>{feature.title}</CardTitle>
</div>
<Badge variant="secondary" className="w-fit">
{feature.status}
</Badge>
</CardHeader>
<CardContent>
<CardDescription>{feature.description}</CardDescription>
</CardContent>
</Card>
);
})}
</div>
</section>
);
}
-64
View File
@@ -1,64 +0,0 @@
'use client';
import { useState } from 'react';
import { Button } from '@kandev/ui/button';
import { Badge } from '@kandev/ui/badge';
import { IconCheck, IconCopy } from '@tabler/icons-react';
export function HeroSection() {
const [copied, setCopied] = useState(false);
const copyCommand = () => {
navigator.clipboard.writeText('npx kandev');
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};
return (
<section className="relative overflow-hidden">
<div className="absolute inset-0 -z-10 bg-[radial-gradient(circle_at_top,_rgba(109,40,217,0.12),_transparent_55%)]" />
<div className="container mx-auto max-w-6xl flex flex-col items-center gap-8 px-4 pt-16 pb-16 md:pb-24">
<Badge variant="secondary" className="cursor-pointer border border-border px-6 py-3">
<span className="text-sm">kandev.ai – AI-powered Kanban for developers</span>
</Badge>
<h1 className="max-w-4xl text-center text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl">
Kanban meets <span className="text-primary">AI development</span>
</h1>
<p className="max-w-2xl text-center text-lg text-muted-foreground md:text-xl">
Manage development tasks with isolated git worktrees, secure Docker execution, and AI agents.
Ship faster without breaking your workflow.
</p>
<div className="flex flex-col items-center gap-4 sm:flex-row">
<Button size="lg" asChild className="cursor-pointer">
<a href="#features">Get Started</a>
</Button>
<Button size="lg" variant="outline" asChild className="cursor-pointer">
<a href="#demo">Watch Demo</a>
</Button>
</div>
<div className="mt-8 w-full max-w-2xl">
<div className="flex items-center justify-center gap-2 rounded-lg border border-border bg-card p-4">
<code className="flex-1 text-sm font-mono text-foreground">npx kandev</code>
<Button
size="icon"
variant="ghost"
onClick={copyCommand}
className="cursor-pointer"
>
{copied ? (
<IconCheck className="h-4 w-4 text-green-500" />
) : (
<IconCopy className="h-4 w-4" />
)}
</Button>
</div>
</div>
<div className="mt-8 w-full max-w-5xl">
<div className="aspect-video rounded-lg border border-border bg-muted flex items-center justify-center">
<p className="text-muted-foreground">Video placeholder</p>
</div>
</div>
</div>
</section>
);
}
@@ -1,44 +0,0 @@
import type { ComponentProps, ReactElement } from 'react';
import { ChangelogVideo } from '@/components/changelog-video';
function MdxHeading({ className, ...props }: ComponentProps<'h2'>) {
return <h2 className={`mt-10 text-2xl font-semibold text-foreground ${className ?? ''}`} {...props} />;
}
function MdxSubheading({ className, ...props }: ComponentProps<'h3'>) {
return <h3 className={`mt-8 text-xl font-semibold text-foreground ${className ?? ''}`} {...props} />;
}
function MdxParagraph({ className, ...props }: ComponentProps<'p'>) {
return <p className={`mt-4 text-base leading-relaxed text-muted-foreground ${className ?? ''}`} {...props} />;
}
function MdxList({ className, ...props }: ComponentProps<'ul'>) {
return <ul className={`mt-4 space-y-2 pl-6 text-muted-foreground ${className ?? ''}`} {...props} />;
}
function MdxListItem({ className, ...props }: ComponentProps<'li'>) {
return <li className={`list-disc text-base ${className ?? ''}`} {...props} />;
}
function MdxImage({ className, alt, ...props }: ComponentProps<'img'>): ReactElement {
return (
<img
className={`mt-6 w-full rounded-2xl border border-border bg-muted/40 ${className ?? ''}`}
alt={alt ?? ''}
loading="lazy"
decoding="async"
{...props}
/>
);
}
export const mdxComponents = {
h2: MdxHeading,
h3: MdxSubheading,
p: MdxParagraph,
ul: MdxList,
li: MdxListItem,
img: MdxImage,
ChangelogVideo,
};
-69
View File
@@ -1,69 +0,0 @@
'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { IconBrandGithub } from '@tabler/icons-react';
import { Button } from '@kandev/ui/button';
import { cn } from '@/lib/utils';
export function Navbar() {
const pathname = usePathname();
const stars = '2k'; // Dummy star count display
return (
<header className="fixed top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<div className="container mx-auto flex h-14 max-w-6xl items-center justify-between px-4">
<Link href="/" className="flex items-center space-x-2">
<span className="font-bold">kandev.ai</span>
</Link>
<nav className="flex items-center gap-6 text-sm">
<Link
href="/#product"
className={cn(
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
pathname === '/' ? 'text-foreground' : 'text-muted-foreground'
)}
>
Product
</Link>
<Link
href="/docs"
className={cn(
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
pathname?.startsWith('/docs') ? 'text-foreground' : 'text-muted-foreground'
)}
>
Documentation
</Link>
<Link
href="/changelog"
className={cn(
'text-sm font-medium transition-colors hover:text-foreground cursor-pointer',
pathname?.startsWith('/changelog') ? 'text-foreground' : 'text-muted-foreground'
)}
>
Changelog
</Link>
</nav>
<div className="flex items-center space-x-2">
<Button variant="outline" size="sm" asChild>
<a
href="https://github.com/kdlbs/kandev"
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-2"
>
<IconBrandGithub className="h-4 w-4" />
<span>Star</span>
<span className="ml-1 rounded-full bg-muted px-2 py-0.5 text-xs font-medium">
{stars}
</span>
</a>
</Button>
</div>
</div>
</header>
);
}
@@ -1,12 +0,0 @@
'use client';
import { ThemeProvider as NextThemesProvider } from 'next-themes';
import type { ReactNode } from 'react';
export function ThemeProvider({ children }: { children: ReactNode }) {
return (
<NextThemesProvider attribute="class" defaultTheme="dark" enableSystem={false}>
{children}
</NextThemesProvider>
);
}
@@ -1,37 +0,0 @@
---
title: "kandev.ai Launch"
date: "2026-01-17"
surface: "Platform"
summary:
- "kandev.ai ships a WebSocket-first Kanban + AI agent workflow for developer teams."
- "Tasks run inside isolated git worktrees and Docker containers with approval gates."
---
## What is kandev.ai?
kandev.ai is a Kanban-native development platform that pairs task management with AI agents. Every task can be executed by an agent in a clean, isolated environment, while you keep control over approvals and merge decisions.
![kandev launch overview](/changelog/2026-01-17-launch/launch-hero.svg)
## Initial launch features
- **Kanban boards for engineering work** with task metadata, priorities, and agent selection.
- **Isolated git worktrees** so parallel tasks never collide.
- **Docker-based execution** for secure, repeatable runs.
- **Agent control plane** via `agentctl` with start/stop/status and ACP relays.
- **WebSocket-only client API** for real-time orchestration and updates.
- **Approval gates** for sensitive paths and commands.
## How it works (high level)
1. The client opens a single WebSocket connection to the backend.
2. The backend spins up an agent container with `agentctl` as a sidecar.
3. The backend initializes ACP over stdin/stdout and creates a session.
4. The task prompt is streamed to the agent and progress events flow back in real time.
5. Results are reviewed, approved, and merged back into the main branch.
![High-level flow](/changelog/2026-01-17-launch/high-level-flow.svg)
### What’s next
Expect expanded agent types, richer approvals, and deeper task analytics in upcoming releases.
-71
View File
@@ -1,71 +0,0 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import matter from 'gray-matter';
export type ChangelogEntry = {
slug: string;
title: string;
date: string;
surface: string;
summary: string[];
content: string;
};
const changelogRoot = path.join(process.cwd(), 'content', 'changelog');
function normalizeSummary(summary: unknown): string[] {
if (Array.isArray(summary)) {
return summary.filter((item): item is string => typeof item === 'string');
}
if (typeof summary === 'string') {
return [summary];
}
return [];
}
function parseEntry(slug: string, fileContent: string): ChangelogEntry {
const { data, content } = matter(fileContent);
return {
slug,
title: String(data.title ?? slug),
date: String(data.date ?? ''),
surface: String(data.surface ?? 'Product'),
summary: normalizeSummary(data.summary),
content,
};
}
export async function getChangelogEntries(): Promise<ChangelogEntry[]> {
const entries = await fs.readdir(changelogRoot, { withFileTypes: true });
const slugs = entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
const items = await Promise.all(
slugs.map(async (slug) => {
const entryPath = path.join(changelogRoot, slug, 'index.mdx');
const fileContent = await fs.readFile(entryPath, 'utf8');
return parseEntry(slug, fileContent);
})
);
return items.sort((a, b) => {
const aTime = new Date(a.date).getTime();
const bTime = new Date(b.date).getTime();
return bTime - aTime;
});
}
export async function getChangelogEntry(slug: string): Promise<ChangelogEntry | null> {
const entryPath = path.join(changelogRoot, slug, 'index.mdx');
try {
const fileContent = await fs.readFile(entryPath, 'utf8');
return parseEntry(slug, fileContent);
} catch {
return null;
}
}
export async function getChangelogSlugs(): Promise<string[]> {
const entries = await fs.readdir(changelogRoot, { withFileTypes: true });
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name);
}
-6
View File
@@ -1,6 +0,0 @@
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
-7
View File
@@ -1,7 +0,0 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
transpilePackages: ["@kandev/ui", "@kandev/theme", "@kandev/types"],
};
export default nextConfig;
-36
View File
@@ -1,36 +0,0 @@
{
"name": "@kandev/landing",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev -p 3001",
"build": "NODE_ENV=production next build",
"start": "next start -p 3001",
"lint": "next lint"
},
"dependencies": {
"@kandev/theme": "workspace:*",
"@kandev/ui": "workspace:*",
"@tabler/icons-react": "^3.24.0",
"clsx": "^2.1.1",
"next": "16.1.1",
"next-themes": "^0.4.6",
"next-mdx-remote": "^5.0.0",
"react": "19.2.3",
"react-dom": "19.2.3",
"gray-matter": "^4.0.3",
"shadcn": "^3.6.3",
"tailwind-merge": "^2.6.0",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.0",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.1",
"tailwindcss": "^4.0.0",
"typescript": "^5"
}
}
-7
View File
@@ -1,7 +0,0 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;
@@ -1,18 +0,0 @@
<svg width="1200" height="640" viewBox="0 0 1200 640" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1200" height="640" rx="32" fill="#0B1220"/>
<rect x="80" y="120" width="240" height="120" rx="20" fill="#111827" stroke="#1F2937" stroke-width="2"/>
<rect x="360" y="120" width="240" height="120" rx="20" fill="#111827" stroke="#1F2937" stroke-width="2"/>
<rect x="640" y="120" width="240" height="120" rx="20" fill="#111827" stroke="#1F2937" stroke-width="2"/>
<rect x="920" y="120" width="200" height="120" rx="20" fill="#111827" stroke="#1F2937" stroke-width="2"/>
<path d="M320 180H360" stroke="#38BDF8" stroke-width="6" stroke-linecap="round"/>
<path d="M600 180H640" stroke="#38BDF8" stroke-width="6" stroke-linecap="round"/>
<path d="M880 180H920" stroke="#38BDF8" stroke-width="6" stroke-linecap="round"/>
<text x="110" y="190" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="16">Client WS</text>
<text x="390" y="190" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="16">Backend</text>
<text x="670" y="190" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="16">agentctl</text>
<text x="950" y="190" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="16">Agent</text>
<rect x="80" y="300" width="1040" height="200" rx="24" fill="#0F172A" stroke="#1E293B" stroke-width="2"/>
<text x="120" y="360" fill="#94A3B8" font-family="Figtree, sans-serif" font-size="16">orchestrator.start → /api/v1/start → ACP initialize/session/prompt → session/update</text>
<text x="120" y="400" fill="#94A3B8" font-family="Figtree, sans-serif" font-size="16">Real-time progress streamed back over WebSocket</text>
<text x="96" y="96" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="24" font-weight="600">High-level execution flow</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

@@ -1,14 +0,0 @@
<svg width="1200" height="640" viewBox="0 0 1200 640" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="1200" height="640" rx="32" fill="#0B1120"/>
<rect x="64" y="72" width="1072" height="496" rx="28" fill="#111827" stroke="#1F2937" stroke-width="2"/>
<rect x="104" y="120" width="320" height="120" rx="20" fill="#0F172A" stroke="#1F2937" stroke-width="2"/>
<rect x="448" y="120" width="320" height="120" rx="20" fill="#0F172A" stroke="#1F2937" stroke-width="2"/>
<rect x="792" y="120" width="320" height="120" rx="20" fill="#0F172A" stroke="#1F2937" stroke-width="2"/>
<rect x="104" y="272" width="640" height="44" rx="14" fill="#1E293B"/>
<rect x="104" y="332" width="560" height="44" rx="14" fill="#1E293B"/>
<rect x="104" y="392" width="480" height="44" rx="14" fill="#1E293B"/>
<circle cx="980" cy="364" r="82" fill="#0F172A" stroke="#38BDF8" stroke-width="3"/>
<path d="M940 364L968 392L1024 336" stroke="#38BDF8" stroke-width="10" stroke-linecap="round" stroke-linejoin="round"/>
<text x="104" y="112" fill="#E2E8F0" font-family="Figtree, sans-serif" font-size="24" font-weight="600">kandev.ai Launch</text>
<text x="104" y="500" fill="#94A3B8" font-family="Figtree, sans-serif" font-size="16">Kanban + AI agents with secure execution</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

-42
View File
@@ -1,42 +0,0 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./*"],
"@kandev/ui": ["../packages/ui/src"],
"@kandev/ui/*": ["../packages/ui/src/*"],
"@kandev/theme": ["../packages/theme/src"],
"@kandev/types": ["../packages/types/src"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
-2
View File
@@ -5,10 +5,8 @@
"scripts": {
"dev": "pnpm -r run dev",
"dev:web": "pnpm --filter @kandev/web dev",
"dev:landing": "pnpm --filter @kandev/landing dev",
"build": "pnpm -r run build",
"build:web": "pnpm --filter @kandev/web build",
"build:landing": "pnpm --filter @kandev/landing build",
"lint": "pnpm -r run lint",
"clean": "pnpm -r run clean && rm -rf node_modules"
},
-1
View File
@@ -1,5 +1,4 @@
packages:
- 'cli'
- 'web'
- 'landing'
- 'packages/*'