diff --git a/PROJECT.md b/PROJECT.md
new file mode 100644
index 0000000..9a97193
--- /dev/null
+++ b/PROJECT.md
@@ -0,0 +1,45 @@
+# Project Overview
+
+## What is this project?
+
+Hikari Desktop is a Tauri-based desktop application that wraps Claude Code with a visual anime character companion (Hikari) who appears on screen. It provides a rich UI for interacting with Claude Code, including conversation management, agent monitoring, cost tracking, and more.
+
+The app was inspired by a Hatsune Miku mod for the ship AI in _The Outer Worlds_ — the idea of an AI assistant with an anime girl avatar that you can actually _see_.
+
+## Goals
+
+- Provide a beautiful, personalised interface for Claude Code
+- Surface real-time status (thinking, typing, searching, etc.) through animated character sprites
+- Track costs, context usage, and agent activity across sessions
+- Support power-user workflows: multi-tab conversations, todo lists, git integration, MCP server management, session compaction, and more
+- Build a foundation for autonomous task execution (agent orchestration, PRD-driven workflows)
+
+## Tech Stack
+
+- **Frontend**: Svelte 5 + TypeScript + Tailwind CSS
+- **Backend**: Rust (Tauri v2)
+- **Build**: Vite + pnpm
+- **Testing**: Vitest (frontend) + cargo test (backend)
+- **Linting**: ESLint + Prettier (frontend) + Clippy (backend)
+- **IPC**: Tauri commands + events between Rust and Svelte
+
+## Architecture
+
+```
+hikari-desktop/
+├── src/ # Svelte frontend
+│ └── lib/
+│ ├── components/ # UI components (panels, modals, status bar)
+│ ├── stores/ # Svelte stores (state management)
+│ ├── types/ # TypeScript type definitions
+│ └── utils/ # Utility functions
+├── src-tauri/ # Rust backend
+│ └── src/
+│ ├── commands.rs # Tauri command handlers
+│ ├── wsl_bridge.rs # Claude Code process management
+│ ├── types.rs # Shared types & CharacterState enum
+│ └── stats.rs # Cost tracking
+└── public/ # Static assets (sprites, sounds)
+```
+
+Claude Code is launched as a child process via `WslBridge`, communicating via `--output-format stream-json` (NDJSON). Messages flow from the Rust backend to the Svelte frontend via Tauri events.
diff --git a/src/lib/components/InputBar.svelte b/src/lib/components/InputBar.svelte
index aa63b6a..024aedc 100644
--- a/src/lib/components/InputBar.svelte
+++ b/src/lib/components/InputBar.svelte
@@ -37,6 +37,7 @@
import DraftPanel from "$lib/components/DraftPanel.svelte";
import TextInputContextMenu from "$lib/components/TextInputContextMenu.svelte";
import { draftsStore } from "$lib/stores/drafts";
+ import { injectTextStore } from "$lib/stores/projectContext";
import type { Attachment } from "$lib/types/messages";
const INPUT_HISTORY_KEY = "hikari-input-history";
@@ -178,6 +179,14 @@
}
});
+ // Project context injection — set by StatusBar via injectTextStore signal.
+ injectTextStore.subscribe((text) => {
+ if (text === null) return;
+ inputValue = inputValue.trim() ? text + "\n\n" + inputValue : text;
+ userHasTyped = true;
+ injectTextStore.set(null);
+ });
+
function clearInput() {
inputValue = "";
const activeId = get(claudeStore.activeConversationId);
diff --git a/src/lib/components/ProjectContextPanel.svelte b/src/lib/components/ProjectContextPanel.svelte
new file mode 100644
index 0000000..1a807ee
--- /dev/null
+++ b/src/lib/components/ProjectContextPanel.svelte
@@ -0,0 +1,225 @@
+
+
+
e.key === "Escape" && onClose()}
+>
+
e.stopPropagation()}
+ onkeydown={(e) => e.stopPropagation()}
+ role="dialog"
+ aria-labelledby="project-context-title"
+ tabindex="-1"
+ >
+
+
+
+
+ Project Context
+
+ {#if $isLoading[$activeFile]}
+ Loading...
+ {:else if fileExists($activeFile)}
+
+ ✓ File exists
+
+ {:else}
+
+ ✗ Not created
+
+ {/if}
+ {#if hasUnsavedChanges}
+ Unsaved changes
+ {/if}
+
+
+
+
+
+
+ {#each PROJECT_FILES as file (file)}
+
+ {/each}
+
+
+
+
+
+
+
+
+
+
+ {workingDirectory}/{PROJECT_FILE_NAMES[$activeFile]}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/components/StatusBar.svelte b/src/lib/components/StatusBar.svelte
index 0c59b7f..6e55b24 100644
--- a/src/lib/components/StatusBar.svelte
+++ b/src/lib/components/StatusBar.svelte
@@ -30,6 +30,8 @@
import CastPanel from "./CastPanel.svelte";
import PluginManagementPanel from "./PluginManagementPanel.svelte";
import McpManagementPanel from "./McpManagementPanel.svelte";
+ import ProjectContextPanel from "./ProjectContextPanel.svelte";
+ import { injectTextStore, PROJECT_CONTEXT_SYSTEM_ADDENDUM } from "$lib/stores/projectContext";
import { conversationsStore } from "$lib/stores/conversations";
import {
generateContextInjection,
@@ -62,6 +64,7 @@
let showCastPanel = $state(false);
let showPluginPanel = $state(false);
let showMcpPanel = $state(false);
+ let showProjectContext = $state(false);
let isSummarising = $state(false);
let showWorkspaceTrust = $state(false);
let pendingHookInfo: WorkspaceHookInfo | null = $state(null);
@@ -185,7 +188,8 @@
working_dir: targetDir,
model: currentConfig.model || null,
api_key: currentConfig.api_key || null,
- custom_instructions: currentConfig.custom_instructions || null,
+ custom_instructions:
+ (currentConfig.custom_instructions ?? "") + PROJECT_CONTEXT_SYSTEM_ADDENDUM,
mcp_servers_json: currentConfig.mcp_servers_json || null,
allowed_tools: allAllowedTools,
use_worktree: currentConfig.use_worktree ?? false,
@@ -300,6 +304,10 @@
onToggleAchievements();
}
+ function handleInjectContext(content: string): void {
+ injectTextStore.set(content);
+ }
+
async function handleCompactConversation() {
const activeId = get(conversationsStore.activeConversationId);
if (!activeId) return;
@@ -345,7 +353,8 @@
working_dir: workingDirectory || selectedDirectory,
model: currentConfig.model || null,
api_key: currentConfig.api_key || null,
- custom_instructions: currentConfig.custom_instructions || null,
+ custom_instructions:
+ (currentConfig.custom_instructions ?? "") + PROJECT_CONTEXT_SYSTEM_ADDENDUM,
mcp_servers_json: currentConfig.mcp_servers_json || null,
allowed_tools: allAllowedTools,
use_worktree: currentConfig.use_worktree ?? false,
@@ -564,6 +573,20 @@
/>
+