diff --git a/src/App.tsx b/src/App.tsx
index 2486b9d..434b8c5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,7 +1,37 @@
+import { useEffect } from "react";
+import { useAppStore } from "@/stores/app-store";
+import { AppShell } from "@/components/layout/AppShell";
+
export default function App() {
+ const initialized = useAppStore((s) => s.initialized);
+ const init = useAppStore((s) => s.init);
+ const view = useAppStore((s) => s.view);
+
+ useEffect(() => {
+ init();
+ }, [init]);
+
+ if (!initialized) {
+ return (
+
+
+ Loading...
+
+
+ );
+ }
+
return (
-
-
OpenPylon
-
+
+ {view.type === "board-list" ? (
+
+ Board List
+
+ ) : (
+
+ Board View
+
+ )}
+
);
}
diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx
new file mode 100644
index 0000000..d6a9969
--- /dev/null
+++ b/src/components/layout/AppShell.tsx
@@ -0,0 +1,18 @@
+import type { ReactNode } from "react";
+import { TooltipProvider } from "@/components/ui/tooltip";
+import { TopBar } from "@/components/layout/TopBar";
+
+interface AppShellProps {
+ children: ReactNode;
+}
+
+export function AppShell({ children }: AppShellProps) {
+ return (
+
+
+
+ {children}
+
+
+ );
+}
diff --git a/src/components/layout/TopBar.tsx b/src/components/layout/TopBar.tsx
new file mode 100644
index 0000000..dede9cf
--- /dev/null
+++ b/src/components/layout/TopBar.tsx
@@ -0,0 +1,164 @@
+import { useState, useRef, useEffect, useCallback } from "react";
+import { ArrowLeft, Settings, Search } from "lucide-react";
+import { Button } from "@/components/ui/button";
+import {
+ Tooltip,
+ TooltipTrigger,
+ TooltipContent,
+} from "@/components/ui/tooltip";
+import { useAppStore } from "@/stores/app-store";
+import { useBoardStore } from "@/stores/board-store";
+
+export function TopBar() {
+ const view = useAppStore((s) => s.view);
+ const setView = useAppStore((s) => s.setView);
+ const board = useBoardStore((s) => s.board);
+ const updateBoardTitle = useBoardStore((s) => s.updateBoardTitle);
+ const saving = useBoardStore((s) => s.saving);
+ const lastSaved = useBoardStore((s) => s.lastSaved);
+
+ const isBoardView = view.type === "board";
+
+ const [editing, setEditing] = useState(false);
+ const [editValue, setEditValue] = useState("");
+ const inputRef = useRef(null);
+
+ useEffect(() => {
+ if (editing && inputRef.current) {
+ inputRef.current.focus();
+ inputRef.current.select();
+ }
+ }, [editing]);
+
+ const startEditing = useCallback(() => {
+ if (board) {
+ setEditValue(board.title);
+ setEditing(true);
+ }
+ }, [board]);
+
+ const commitEdit = useCallback(() => {
+ const trimmed = editValue.trim();
+ if (trimmed && trimmed !== board?.title) {
+ updateBoardTitle(trimmed);
+ }
+ setEditing(false);
+ }, [editValue, board?.title, updateBoardTitle]);
+
+ const handleKeyDown = useCallback(
+ (e: React.KeyboardEvent) => {
+ if (e.key === "Enter") {
+ commitEdit();
+ } else if (e.key === "Escape") {
+ setEditing(false);
+ }
+ },
+ [commitEdit]
+ );
+
+ const savingStatus = saving
+ ? "Saving..."
+ : lastSaved
+ ? `Saved ${formatTimeAgo(lastSaved)}`
+ : null;
+
+ return (
+
+ {/* Left section */}
+
+ {isBoardView && (
+
+
+
+
+ Back to board list
+
+ )}
+
+
+ {/* Center section */}
+
+ {isBoardView && board ? (
+ editing ? (
+ setEditValue(e.target.value)}
+ onBlur={commitEdit}
+ onKeyDown={handleKeyDown}
+ className="h-7 rounded-md border border-border bg-transparent px-2 text-center font-heading text-lg text-pylon-text outline-none focus:border-pylon-accent"
+ />
+ ) : (
+
+ )
+ ) : (
+
+ OpenPylon
+
+ )}
+
+
+ {/* Right section */}
+
+ {savingStatus && (
+
+ {savingStatus}
+
+ )}
+
+
+
+
+
+
+ Command palette{" "}
+ Ctrl+K
+
+
+
+
+
+
+
+ Settings
+
+
+
+ );
+}
+
+function formatTimeAgo(timestamp: number): string {
+ const seconds = Math.floor((Date.now() - timestamp) / 1000);
+ if (seconds < 5) return "just now";
+ if (seconds < 60) return `${seconds}s ago`;
+ const minutes = Math.floor(seconds / 60);
+ return `${minutes}m ago`;
+}
diff --git a/src/lib/board-factory.ts b/src/lib/board-factory.ts
new file mode 100644
index 0000000..eb2bebc
--- /dev/null
+++ b/src/lib/board-factory.ts
@@ -0,0 +1,44 @@
+import { ulid } from "ulid";
+import type { Board, ColumnWidth } from "@/types/board";
+
+type Template = "blank" | "kanban" | "sprint";
+
+export function createBoard(
+ title: string,
+ color: string,
+ template: Template = "blank"
+): Board {
+ const ts = new Date().toISOString();
+ const board: Board = {
+ id: ulid(),
+ title,
+ color,
+ createdAt: ts,
+ updatedAt: ts,
+ columns: [],
+ cards: {},
+ labels: [],
+ settings: { attachmentMode: "link" },
+ };
+
+ const col = (t: string, w: ColumnWidth = "standard") => ({
+ id: ulid(),
+ title: t,
+ cardIds: [] as string[],
+ width: w,
+ });
+
+ if (template === "kanban") {
+ board.columns = [col("To Do"), col("In Progress"), col("Done")];
+ } else if (template === "sprint") {
+ board.columns = [
+ col("Backlog"),
+ col("To Do"),
+ col("In Progress", "wide"),
+ col("Review"),
+ col("Done", "narrow"),
+ ];
+ }
+
+ return board;
+}