Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
06c5f42
Add frontend visibility and focus reporting to daemon WebSocket
backnotprop May 19, 2026
cb74740
Move browser opening from CLI to daemon with smart presentation
backnotprop May 19, 2026
8edabb4
Add session notification toasts and keep completed sessions in sidebar
backnotprop May 19, 2026
8a73455
Collapse sidebar on direct session links, open on landing page
backnotprop May 19, 2026
42f2848
Add disk-backed session snapshots for completed session persistence
backnotprop May 19, 2026
41fbd5d
Wire legacy tab mode through server config to surface overlays
backnotprop May 19, 2026
7fea604
Document legacyTabMode config setting in AGENTS.md
backnotprop May 19, 2026
894c5de
Load session snapshots from disk on daemon startup
backnotprop May 19, 2026
8059bd2
Add worktree-aware project hierarchy to landing page
backnotprop May 19, 2026
095fcfd
Fix parent project registration dedup and add branch to all session l…
backnotprop May 19, 2026
9c510aa
Fix blank page when adding a worktree project
backnotprop May 20, 2026
4260c73
Filter temp directory worktrees from project listing
backnotprop May 20, 2026
9e3da6d
Sort worktrees by last activity (index mtime > commit time > dir mtime)
backnotprop May 20, 2026
3422418
Fix toast: skip for frontend-initiated sessions, clean label, fix colors
backnotprop May 20, 2026
f402ecd
Replace manual project input with searchable directory picker
backnotprop May 20, 2026
942ddd0
Only show worktree chevron when worktrees exist, add Worktrees label
backnotprop May 20, 2026
ec2f257
Fix: add missing useEffect import in LandingPage
backnotprop May 20, 2026
07da486
Fix project row layout: chevron to right, remove branch icons, align …
backnotprop May 20, 2026
17b508c
Add ASCII art Plannotator banner to landing page
backnotprop May 20, 2026
0da3d43
Increase ASCII banner opacity to 70%
backnotprop May 20, 2026
bcd9210
Remove redundant Plannotator label from landing page nav
backnotprop May 20, 2026
4efb809
Make Add Project buttons more visible
backnotprop May 20, 2026
efeeafa
Design audit: fix color contrast, remove opacity abuse, fix a11y
backnotprop May 20, 2026
885b668
Design audit: fix directory picker dialog contrast and accessibility
backnotprop May 20, 2026
207a9ab
Add goal files for session lifecycle and worktree projects
backnotprop May 20, 2026
b16af05
Unified settings, performance optimizations, and Zustand review store…
backnotprop May 22, 2026
8778208
Add legacy tab mode toggle to settings dialog
backnotprop May 22, 2026
5d201b2
Add remaining backlog items: GitLab detection, stack splitting, confi…
backnotprop May 22, 2026
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
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ claude --plugin-dir ./apps/hook
**Config-only settings (`~/.plannotator/config.json`)**: Some settings have no env-var equivalent and are toggled by editing the config file directly:

- `pfmReminder` (`true` / `false`, default `false`) — when enabled, a Plannotator Flavored Markdown reminder is injected at plan-time describing the renderer's extensions (code-file links, callouts, tables, diagrams, task lists, hex swatches, wiki-links). Lets the planning agent enrich plans with PFM features without having to discover them. Composes cleanly with the compound-skill improvement hook. Supported across all three runtimes: Claude Code (`improve-context` PreToolUse hook in `apps/hook/server/index.ts`), OpenCode (`experimental.chat.system.transform` in `apps/opencode-plugin/index.ts`), and Pi (`before_agent_start` in `apps/pi-extension/index.ts`).
- `legacyTabMode` (`true` / `false`, default `false`) — when enabled, the daemon opens a new browser tab for every session regardless of whether a frontend is already connected. Sessions use the full-screen `CompletionOverlay` with auto-close instead of the inline `CompletionBanner`. Preserves the pre-frontend tab-per-session behavior for users who prefer it.

**Legacy:** `SSH_TTY` and `SSH_CONNECTION` are still detected when `PLANNOTATOR_REMOTE` is unset. Set `PLANNOTATOR_REMOTE=1` / `true` to force remote mode or `0` / `false` to force local mode.

Expand Down Expand Up @@ -234,6 +235,16 @@ The daemon is the single long-running Bun server used by normal plan/review/anno
| `/daemon/sessions/:id/cancel` | POST | Cancel a session and dispose its resources |
| `/daemon/sessions/:id` | DELETE | Delete a session record |
| `/daemon/shutdown` | POST | Ask the daemon to stop |
| `/daemon/config` | GET | Read global config (`~/.plannotator/config.json`) |
| `/daemon/config` | POST | Write global config keys (allowlisted: `displayName`, `pfmReminder`, `legacyTabMode`, `diffOptions`, `conventionalComments`, `conventionalLabels`) |
| `/daemon/git/user` | GET | Return git user name from `git config user.name` |
| `/daemon/vaults` | GET | Detect available Obsidian vaults |
| `/daemon/obsidian/vaults` | GET | Alias for `/daemon/vaults` |
| `/daemon/hooks/status` | GET | Return PFM reminder and improvement hook status |
| `/daemon/projects` | DELETE | Remove a project by `?cwd=` (optional `?clean=1` to cancel active sessions) |
| `/daemon/projects/prs` | GET | List open PRs for a project (`?cwd=`) |
| `/daemon/projects/prs/detailed` | GET | List PRs with review metadata for dashboard (`?cwd=`) |
| `/daemon/fs/list` | GET | List directory contents (`?path=`) |
| `/daemon/ws` | WebSocket | Multiplex daemon lifecycle events, session-scoped external annotation events, agent job events, and correlated session actions |
| `/s/:id` | GET | Serve the browser HTML for a session |
| `/s/:id/api/...` | Any | Route browser API requests to that session's plan/review/annotate handler |
Expand Down
3 changes: 3 additions & 0 deletions apps/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,20 @@
},
"dependencies": {
"@fontsource-variable/geist-mono": "^5.2.7",
"@fontsource-variable/instrument-sans": "^5.2.8",
"@fontsource-variable/inter": "^5.2.8",
"@plannotator/code-review": "workspace:*",
"@plannotator/plan-review": "workspace:*",
"@plannotator/shared": "workspace:*",
"@plannotator/ui": "workspace:*",
"@radix-ui/react-collapsible": "^1.1.12",
"@radix-ui/react-context-menu": "^2.2.16",
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-separator": "^1.1.8",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@radix-ui/react-tooltip": "^1.2.8",
"@tanstack/react-router": "^1.141.0",
"class-variance-authority": "^0.7.1",
Expand Down
64 changes: 48 additions & 16 deletions apps/frontend/src/app/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ import { Toaster } from "sonner";
import { SidebarProvider, useSidebar } from "@/components/ui/sidebar";
import { TooltipProvider } from "@/components/ui/tooltip";
import { AppSidebar } from "../components/sidebar/AppSidebar";
import { SidebarPeek } from "../components/sidebar/SidebarPeek";
import { AddProjectDialog } from "../components/landing/AddProjectDialog";
import { AppSettingsDialog } from "../components/settings/AppSettingsDialog";
import { SessionSurface } from "../components/sessions/SessionSurface";
import { appStore } from "../stores/app-store";
import { setGlobalFetchBase } from "@plannotator/ui/utils/api";
import { useDaemonEvents } from "../daemon/events/use-daemon-events";

setGlobalFetchBase("/daemon");
import { projectStore } from "../stores/project-store";
import { useAppStore } from "../stores/app-store";

Expand All @@ -18,20 +24,35 @@ function LayoutContent() {
const matchRoute = useMatchRoute();
const { open: sidebarOpen } = useSidebar();

useDaemonEvents();
const { reportActiveSession } = useDaemonEvents();

useEffect(() => {
void projectStore.getState().fetchProjects();
}, []);

const isOnSession = !!matchRoute({ to: "/s/$sessionId", fuzzy: true });

useEffect(() => {
reportActiveSession(isOnSession ? activeSessionId : null);
}, [reportActiveSession, isOnSession, activeSessionId]);
const showLanding = !isOnSession;

const openAddProject = useCallback(() => setAddProjectOpen(true), [setAddProjectOpen]);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key === ",") {
e.preventDefault();
const current = appStore.getState().settingsOpen;
appStore.getState().setSettingsOpen(!current);
}
};
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, []);

return (
<>
<AppSidebar onAddProject={openAddProject} />
<AppSidebar />
<SidebarPeek />
<main className="relative flex-1 overflow-hidden">
<div
className="absolute inset-0"
Expand All @@ -43,27 +64,35 @@ function LayoutContent() {
<Outlet />
</div>

{Object.values(visitedSessions).map(({ sessionId, bootstrap }) => (
<div
key={sessionId}
className={`absolute inset-0 overflow-hidden ${sidebarOpen ? "rounded-tl-xl border-l border-border/50" : ""}`}
style={{
visibility: sessionId === activeSessionId && isOnSession ? "visible" : "hidden",
zIndex: sessionId === activeSessionId && isOnSession ? 1 : 0,
}}
>
<SessionSurface bootstrap={bootstrap} />
</div>
))}
{Object.values(visitedSessions).map(({ sessionId, bootstrap }) => {
const isActive = sessionId === activeSessionId && isOnSession;
return (
<div
key={sessionId}
className={`absolute inset-0 overflow-hidden ${sidebarOpen ? "rounded-tl-xl border-l border-border/50" : ""}`}
style={{
visibility: isActive ? "visible" : "hidden",
contentVisibility: isActive ? "visible" : "hidden",
containIntrinsicSize: isActive ? undefined : "auto 100vh",
zIndex: isActive ? 1 : 0,
}}
>
<SessionSurface bootstrap={bootstrap} />
</div>
);
})}
</main>
<AddProjectDialog open={addProjectOpen} onOpenChange={setAddProjectOpen} />
<AppSettingsDialog />
<Toaster
position="bottom-right"
toastOptions={{
style: {
"--normal-bg": "var(--card)",
"--normal-border": "var(--border)",
"--normal-text": "var(--foreground)",
"--normal-action-bg": "var(--primary)",
"--normal-action-text": "var(--primary-foreground)",
} as React.CSSProperties,
}}
/>
Expand All @@ -72,10 +101,13 @@ function LayoutContent() {
}

export function Layout() {
const matchRoute = useMatchRoute();
const initiallyOnSession = !!matchRoute({ to: "/s/$sessionId", fuzzy: true });

return (
<TooltipProvider delayDuration={200} skipDelayDuration={100}>
<SidebarProvider
defaultOpen={false}
defaultOpen={!initiallyOnSession}
style={{ "--sidebar-width": "16rem" } as React.CSSProperties}
>
<LayoutContent />
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading