<project title="Codexia" summary='&lt;div align="center"&gt; &lt;img src="src-tauri/icons/128x128@2x.png" alt="Codexia Logo" width="120" height="120" /&gt;'>**Remember:**
- Start with Docs for install & onboarding
- Check Tutorials for end-to-end workflows
- Review API references before integrating<docs><doc title="README" desc="install &amp; quickstart."># Codexia Docs

- [USAGE](./USAGE.md) — Installation, development, and how to use the app
- [ARCHITECTURE](./ARCHITECTURE.md) — High-level design and components</doc><doc title="Architecture" desc="core concept."># Codexia Architecture

This document provides a high-level overview of Codexia’s architecture, including the frontend, backend, and how sessions are managed. For app usage, see [USAGE](./USAGE.md).

## Overview

Codexia is a cross‑platform desktop app built with Tauri v2 (Rust backend) and React + TypeScript (frontend). It provides a GUI around the Codex CLI with multi-session handling, streaming responses, and project‑aware workflows.

## Frontend (React + TypeScript)

- Zustand for state management with persistence
- shadcn/ui for UI components (Radix + Tailwind CSS)
- Real-time event handling for streaming responses
- Theme and accent selection persisted via Zustand

## Backend (Rust + Tauri)

- Multi-process management for concurrent Codex sessions
- JSON-RPC protocol over stdin/stdout to communicate with the Codex CLI
- Async event streaming from backend to frontend
- Resource cleanup and process lifecycle management per session

## Session Management

- Independent processes per chat session
- Configurable startup parameters (model, sandboxing, approval policy, working directory)
- Event isolation between sessions for security and clarity

important files connect Codexia and Codex CLI via `app-server` json RPC
 
```
src/hooks/useCodex/useConversationEvents.ts
src-tauri/src/codex/client
```

## Project Structure

```
codexia/
├── src/                    # React frontend source
│   ├── components/         # UI components
│   ├── hooks/              # Custom React hooks
│   ├── store/              # Zustand state management
│   ├── services/           # Business logic services
│   └── types/              # TypeScript type definitions
├── src-tauri/              # Rust backend source
│   ├── src/
│   │   ├── lib.rs          # Main Tauri application
│   │   └── codex # Codex process management
│   └── Cargo.toml          # Rust dependencies
├── public/                 # Static assets
└── package.json            # Node.js dependencies
```

## Key Technologies

- Frontend: React 19, TypeScript, Zustand, shadcn/ui, Vite
- Backend: Rust, Tauri v2, Tokio async runtime
- Process Communication: JSON-RPC over stdin/stdout
- State Management: Zustand with persistence middleware
- UI Framework: shadcn/ui built on Radix UI and Tailwind CSS
</doc><doc title="Auth Codexia" desc="docs page."># auth for codexia

get supabase for auth login

```
# .env
VITE_SUPABASE_URL=https://example.supabase.co
VITE_SUPABASE_ANON_KEY=eyb...
VITE_REDIRECT_URL=http://localhost:1420/auth-success
```

## VITE_REDIRECT_URL

### Next.js

```sh
next dev # command to run server http://localhost:1420 or other
```

page.tsx

```js
"use client";

import { useSearchParams } from "next/navigation";
import { Suspense, useEffect } from "react";

function CallbackContent() {
  const searchParams = useSearchParams();

  useEffect(() => {
    if (searchParams) {
      const query = searchParams.toString();
      const deepLinkUrl = `codexia://auth/callback?${query}`;
      window.location.href = deepLinkUrl;
    }
  }, [searchParams]);

  return (
    <div style={{ maxWidth: 600, margin: "100px auto", padding: 16 }}>
      login success, you can close this page now.
    </div>
  );
}

export default function CallbackPage() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <CallbackContent />
    </Suspense>
  );
}
```

### index.html

```sh
python3 -m http.server 1420 # command to run server http://localhost:1420 or other
```

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Auth Callback</title>
    <script>
      window.onload = function () {
        const searchParams = window.location.search;
        const deepLinkUrl = `codexia://auth/callback${searchParams}`;
        window.location.href = deepLinkUrl;
      };
    </script>
  </head>
  <body>
    <div style="max-width: 600px; margin: 100px auto; padding: 16px">
      login success, you can close this page now.
    </div>
  </body>
</html>
```</doc><doc title="Performance Optimizations" desc="docs page."># Performance Optimization Summary: Resolving Text Selection Rendering Issues

## Problem Diagnosis
Users reported difficulty selecting text in chat messages, suspecting continuous rendering as the cause. Analysis identified several performance bottlenecks:

1. **Frequent component re-renders**: The entire MessageList re-renders every time the text selection changes.
2. **Repeated calculations**: The `normalizeMessage` function recalculates on every render.
3. **Redundant TextSelectionMenu instances**: A selection menu component is created for each message.
4. **Unoptimized selection detection**: Frequent DOM queries and state updates.

## Implemented Optimizations

### 1. Component Separation and Memoization
- **Created independent `Message` component**: Wrapped with `React.memo` to avoid unnecessary re-renders.
- **Optimized `MarkdownRenderer`**: Used `React.memo`, re-rendering only when content changes.
- **Cached computation results**: Used `useMemo` to cache `normalizedMessages`.

### 2. TextSelectionMenu Optimization
- **Single instance**: Changed from one menu per message to a single global menu.
- **Smart context detection**: Automatically detect the message context of selected text via DOM attributes.
- **Reduced component tree**: Removed redundant component instances.

### 3. Text Selection Detection Optimization
- **Debounce handling**: Added 100ms debounce to avoid frequent state updates.
- **Passive event listening**: Used `{ passive: true }` to improve scroll performance.
- **Intelligent state updates**: Update state only when the selected text actually changes.
- **Improved selector logic**: Included `[data-message-role]` selector.

### 4. Callback Function Optimization
- **Used `useCallback`**: Prevent function reference changes that cause re-renders.
- **Reduced dependencies**: Optimized hook dependency arrays.

## Specific Changes

### File Changes:
1. **Added**: `src/components/chat/Message.tsx` - Independent message component.
2. **Optimized**: `src/components/chat/MessageList.tsx` - Performance-optimized message list.
3. **Optimized**: `src/components/chat/TextSelectionMenu.tsx` - Single-instance selection menu.
4. **Optimized**: `src/components/chat/MarkdownRenderer.tsx` - Memoized renderer.
5. **Optimized**: `src/hooks/useTextSelection.ts` - Debounced and optimized selection detection.

### Performance Improvements:
- ✅ **Reduced re-renders**: Message components re-render only when their own content changes.
- ✅ **Optimized selection experience**: Text selection is smoother and uninterrupted by rendering.
- ✅ **Memory optimization**: Reduced number of component instances.
- ✅ **CPU optimization**: Reduced unnecessary computations and DOM operations.

## User Experience Improvements

### Before:
- Text selection was frequently interrupted.
- Multiple instances of selection menus could appear.
- UI response was delayed.

### Now:
- Text selection is smooth and stable.
- Floating menu is accurately positioned.
- UI responds quickly.
- Selection operations no longer trigger unnecessary re-renders.

## Technical Highlights

1. **React.memo**: Prevent unnecessary component re-renders.
2. **useMemo/useCallback**: Cache computed results and function references.
3. **Debounce mechanism**: Reduce high-frequency state updates.
4. **Smart selectors**: Pass context information via DOM attributes.
5. **Single-instance pattern**: Reduce the number of component instances.

These optimizations significantly improve the text selection user experience and resolve selection interruptions caused by rendering.</doc><doc title="Test Notepad Integration" desc="docs page."># Notepad & Chat Integration Test (Updated v2.0)

## Features Implemented:

### 1. Add Message to Notepad (✅ Completed)
- **Location**: In each chat message, hover to see a notepad icon
- **Function**: Click to add the entire message to a new note or existing note
- **Metadata**: Includes timestamp, message role (user/assistant), and source information

### 2. Enhanced Text Selection to Notepad (✅ Completed)  
- **Function**: Select text within a message to see a floating menu with copy and notepad options
- **UI**: Floating menu appears above selected text with copy and "Add to Note" buttons
- **Smart Selection**: Only works within message content areas, ignores UI elements
- **Metadata**: Includes "Selected text" prefix in the source information

### 3. Add Notepad Content to Chat (✅ Completed)
- **Location 1**: In NoteEditor header - button to add current note content to chat input
- **Location 2**: In NoteList - hover over any note to see chat icon to add that note's content to chat input
- **Function**: Formats content with "From note [title]:" or "From notepad:" prefix

### 4. Improved Markdown Rendering (✅ Completed)
- **Improvement**: Fixed text selection issues in markdown content
- **Features**: All text elements are now selectable with `select-text` CSS class
- **Syntax Highlighting**: Uses rehype-prism for better code block highlighting
- **Accessibility**: Better text selection UX in all markdown elements

## UI Components Created:

1. **MessageNoteActions.tsx** - Popover with options to add message/selected text to notepad
2. **TextSelectionMenu.tsx** - NEW: Floating menu for text selection actions (copy, add to note)
3. **MarkdownRenderer.tsx** - NEW: Enhanced markdown renderer with better text selection
4. **NoteToChat.tsx** - Button to add note content to chat input  
5. **useTextSelection.ts** - Enhanced hook for handling text selection across the app
6. **Updated chatInputStore** - Added global input value management with `setInputValue` and `appendToInput`

## Integration Points:

- MessageList now shows notepad actions on hover
- NoteEditor has "Add to Chat" button in header  
- NoteList items have "Add to Chat" action on hover
- ChatInterface now uses global input store for seamless integration

## Test Instructions:

1. **Test Message to Note**: 
   - Start a conversation
   - Hover over a message → Click notepad icon → Choose "Create new note" or add to existing
   - Check if note was created with proper metadata

2. **Test Enhanced Text Selection to Note**:
   - Select text within a message
   - Floating menu should appear above selection with Copy and "Add to Note" buttons
   - Click "Add to Note" → Choose destination → Note should contain only selected text with "Selected text" metadata

3. **Test Note to Chat**:
   - Open notepad, create/select a note
   - Click "Add to Chat" button in note editor OR hover over note in list and click chat icon
   - Check if content appears in chat input with proper formatting

4. **Test Cross-integration**:
   - Add message to note → edit note → add note back to chat → send message
   - Verify the full workflow maintains context and formatting</doc><doc title="Updater Usage" desc="docs page."># Updater Usage Guide

The updater system allows your Tauri application to automatically check for and install updates from GitHub releases.

## How It Works

1. **Configuration**: The updater is configured in `tauri.conf.json` to check GitHub releases
2. **Update Check**: The app checks for updates using the Tauri updater plugin
3. **Download & Install**: If an update is available, it can be downloaded and installed
4. **Restart**: The app automatically restarts after installation

## Components

### 1. UpdaterService (`src/components/updater.ts`)

A singleton service that handles all update operations:

```typescript
import { updater } from '@/components/updater';

// Check for updates
const updateInfo = await updater.checkForUpdates();

// Download and install update
const success = await updater.downloadAndInstall((progress) => {
  console.log(`Progress: ${progress.percentage}%`);
});
```

### 2. UpdaterComponent (`src/components/UpdaterComponent.tsx`)

A complete React component with UI for update management:

```tsx
import UpdaterComponent from '@/components/UpdaterComponent';

function MyApp() {
  return (
    <div>
      <UpdaterComponent />
    </div>
  );
}
```

### 3. useUpdater Hook (`src/hooks/useUpdater.ts`)

A React hook for custom update logic:

```tsx
import { useUpdater } from '@/hooks/useUpdater';

function MyComponent() {
  const {
    updateInfo,
    isChecking,
    isDownloading,
    progress,
    error,
    checkForUpdates,
    downloadAndInstall
  } = useUpdater();

  return (
    <div>
      {updateInfo && (
        <button onClick={downloadAndInstall}>
          Update to {updateInfo.version}
        </button>
      )}
    </div>
  );
}
```

## Integration

The updater is already integrated into the Settings page under the "Updates" section. Users can:

1. **Check for Updates**: Click "Check for Updates" to manually check
2. **View Update Info**: See version, date, and release notes
3. **Download & Install**: Click the download button to install updates
4. **Monitor Progress**: See download progress with a progress bar

## Configuration

The updater is configured in `src-tauri/tauri.conf.json`:

```json
{
  "plugins": {
    "updater": {
      "pubkey": "your-public-key",
      "endpoints": [
        "https://github.com/milisp/codexia/releases/download/{{version}}/updater.json"
      ]
    }
  },
  "bundle": {
    "createUpdaterArtifacts": true
  }
}
```

## Release Process

To create an update:

1. **Build the app** with `bun run build`
2. **Create a GitHub release** with the new version
3. **Upload the updater artifacts** (generated in `src-tauri/target/release/bundle/`)
4. **The updater.json file** will be automatically created

## Features

- ✅ Automatic update checking
- ✅ Progress tracking during download
- ✅ Error handling
- ✅ Release notes display
- ✅ Manual check for updates
- ✅ Beautiful UI with shadcn components
- ✅ TypeScript support
- ✅ React hooks for custom integration

## Error Handling

The updater handles various error scenarios:

- Network failures during update check
- Download failures
- Installation failures
- Invalid update signatures

All errors are displayed to the user with appropriate messages.

## Security

Updates are cryptographically signed using the public key in the configuration. Only updates signed with the corresponding private key will be installed.</doc><doc title="Usage" desc="docs page."># Using Codexia

This guide covers installation, development, common workflows, and troubleshooting. See also: [Architecture](./ARCHITECTURE.md).

## Download from github release

[github release](https://github.com/milisp/codexia/releases) or [modern-github-release](https://milisp.github.io/modern-github-release/#/repo/milisp/codexia)

## Build from source Prerequisites

- Tauri prerequisites: https://v2.tauri.app/start/prerequisites/

### Installation

Clone and install dependencies:
```bash
git clone https://github.com/milisp/codexia
cd codexia
bun install
```

Run development build:
```bash
bun tauri dev
```

Build for production:
```bash
codex generate-ts --out src/bindings  # if you didn't run `bun tauri dev`
bun tauri build
```

## App Usage

### Creating Sessions
- Click the Pencil button in the session sidebar to create a new chat session.
- Each session starts its own Codex process and maintains independent configuration and context.

### Managing Conversations
- Switch between sessions via the sidebar; inactive sessions continue running in the background.

### Configuration
- Use the Settings icon to open the configuration panel.
- Changes apply to the active session and persist automatically.

### Theme & Accent Selection
- Toggle light/dark with the sun/moon button in the header.
- Pick an accent color from the palette button next to the theme toggle.
- Defaults: dark mode with a pink accent. Choices persist via Zustand.

### Chat pane

- toggle brain icon

## Troubleshooting / FAQ

### 1) App fails to start after dependency changes
Fix: Reinstall dependencies.
```bash
rm -rf node_modules bun.lock
bun install
```

### 2) Can I use ChatGPT Plus/Pro instead of the API?
Yes. Login via Codex first, then select ChatGPT:
```bash
codex  # then choose ChatGPT
```
</doc><doc title="Vision" desc="docs page.">## 🌟 Vision

Codexia aims to become the missing GUI for Codex CLI — a lightweight, cross-platform desktop app that makes working with AI coding agents more intuitive, powerful, and fun.

Our goal is to bridge the gap between raw CLI power and a modern, user-friendly experience, so developers can focus on building, not fighting the interface.

Codexia is built with Tauri, React, and Rust to deliver:
- ⚡ Speed & performance of a native app
- 🖥️ Cross-platform support (macOS, Windows, Linux)
- 🤖 Deep integration with Codex CLI workflows

⸻

## 🛣️ Roadmap

- ✅ Multi-session support with forking and editing
- ✅ Real-time AI streaming output
- ✅ Theme & accent customization
- 🔄 Better message rendering with collapsible threads
- 📂 Improved file-tree integration & notepad
- 🧩 Plugin system for extending Codexia (slash commands)
- 📦 Project-based session management (~/.codex/projects/{project}/{session})
- 📸 Screenshot ✅ & export features (for sharing insights)
- 🎨 UX improvements with shortcuts, drag-and-drop, better navigation
- 🌍 Marketplace for community plugins & themes
- ✅ 🔗 Integration with other AI tools (MCP servers)
- 🛠️ Collaboration features (shareable sessions, real-time co-chat)
- 📊 Analytics & session history search

⸻

🚀 Call to Action

Codexia is open-source and community-driven.
We welcome feedback, feature requests, and contributions.

If you’re a developer, designer, or AI tinkerer — join us in shaping the future of AI-powered coding GUIs.</doc></docs><tutorials><doc title="Claude.Example" desc="worked example."># Claude Development Notes on macOS

must be english in file

## Project Info
- The GUI for `codex cli` - coding agent
- use proto -  `codex proto --oss -m {model}` enter non-interactive model use mistral
- source code at folder `<codex-source-code-home>`
- message sessions at `~/.codex/sessions/{year}/{month}/{date}/rollout-{datetime}-{session_id}.jsonl` use jsonl
- `~/.codex/history.jsonl` - user conversation first text
  - format `{"session_id": uuid,"ts":1755040085,"text":prompt}`

### Project tech
- Package manager: bun
- Framework: React + shadcn + tailwindcss + TypeScript + Tauri v2
- UI: shadcn UI components
- code comment language: English-only
- Zustand: for state management with persistence

## Common Commands
- `bun tauri dev` - read the backend output
- `bun run build` - test frontend
- `bunx --bun shadcn@latest add <dep>` - add shadcn dep
- `cargo build` - only `cargo build at <root>/src-tauri` when rust code change
- `codex -h` - Codex CLI for help

## Project Structure
- `src/components/` - React components
- `src/pages/` - Page components
- `src/hooks/` - Custom hooks and stores
- `src/components/common/RouteTracker.tsx` - App routing configuration
- use `@/hooks` `@/types` etc.

## codex cli source code

- `codex-rs` - rust code

## ignore error

model_reasoning_summary: auto medium low - this is codex bug</doc></tutorials><src><doc title="Vite Env.D" desc="docs page.">/// <reference types="vite/client" /></doc><doc title="Backenderrorlistener" desc="docs page.">import { listen } from "@/lib/tauri-proxy";
import { useEffect } from "react";
import { useSessionStore } from "@/stores/useSessionStore";
import { toast } from "sonner";

export interface BackendErrorPayload {
  code: number;
  message: string;
  data?: unknown;
}

export function useBackendErrorListener() {
  const resetBusyState = useSessionStore((state) => state.reset);

  useEffect(() => {
    let backendErrorUnlisten: (() => void) | null = null;

    (async () => {
      try {
        backendErrorUnlisten = await listen<BackendErrorPayload>(
          "codex:backend-error",
          (event) => {
            const { code, message } = event.payload;
            toast.error(
              `Backend error (code: ${code}): ${message || "Unknown error"}`,
            );
            resetBusyState();
          },
        );
      } catch (err) {
        const message =
          err && typeof err === "object" && "message" in err
            ? (err as any).message
            : String(err);
        toast.error(
          "Failed to listen for backend errors:" +
            (message ? ` ${message}` : ""),
        );
      }
    })();

    return () => {
      backendErrorUnlisten?.();
    };
  }, [resetBusyState]);
}</doc><doc title="Beep" desc="docs page.">export function playBeep() {
  const audioCtx = new window.AudioContext();
  const oscillator = audioCtx.createOscillator();
  oscillator.type = "sine";
  oscillator.frequency.setValueAtTime(440, audioCtx.currentTime);
  oscillator.connect(audioCtx.destination);
  oscillator.start();
  oscillator.stop(audioCtx.currentTime + 0.3);
}</doc><doc title="Buildparams" desc="docs page.">import { InputItem } from "@/bindings/InputItem";
import { SendUserMessageParams } from "@/bindings/SendUserMessageParams";
import { MediaAttachment } from "@/types/chat";

export function buildMessageParams(
    conversationId: string,
    text: string,
    attachments: MediaAttachment[] = [],
  ): SendUserMessageParams {
    const textItem: InputItem = { type: "text", data: { text } };
    const imageItems: InputItem[] = attachments
      .filter((attachment) => attachment.type === "image")
      .map((attachment) => ({ type: "localImage", data: { path: attachment.path } }));
  
    if (imageItems.length < attachments.length) {
      const unsupportedTypes = attachments
        .filter((attachment) => attachment.type !== "image")
        .map((attachment) => attachment.type);
      if (unsupportedTypes.length > 0) {
        console.warn(
          "[chat] Unsupported attachment types omitted: ",
          unsupportedTypes.join(", "),
        );
      }
    }
  
    return {
      conversationId,
      items: [textItem, ...imageItems],
    };
  }</doc><doc title="Chat" desc="docs page.">import type { EventMsg } from "@/bindings/EventMsg";

export interface EventMeta {
  streamKey?: string;
  streamStartedAt?: number;
  streamDurationMs?: number;
  persisted?: boolean;
}

export interface CodexEvent {
  id: number;
  event: string; // "codex:event"
  payload: {
    method: string; // e.g. "codex/event/agent_message"
    params: {
      conversationId: string;
      id: string;
      msg: EventMsg;
    };
  };
  meta?: EventMeta;
}

export type ResumeConversationResult = {
  conversationId: string;
  model: string;
  initialMessages?: CodexEvent["payload"]["params"]["msg"][] | null;
};

export const extractInitialMessages = (
  response: ResumeConversationResult,
): CodexEvent["payload"]["params"]["msg"][] | null => {
  return response.initialMessages ?? null;
};

// dont need exec_command_output_delta
export const DELTA_EVENT_TYPES = new Set<EventMsg["type"]>([
  "agent_message_delta",
  "agent_message_content_delta",
  "agent_reasoning_delta",
  "agent_reasoning_raw_content_delta",
  "reasoning_content_delta",
  "reasoning_raw_content_delta",
]);

export interface MediaAttachment {
  id: string;
  type: "image" | "audio";
  path: string;
  name: string;
  mimeType?: string;
}</doc><doc title="Chatinputstore" desc="docs page.">import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { MediaAttachment } from '@/types/chat';

export interface FileReference {
  path: string;
  relativePath: string;
  name: string;
  is_directory: boolean;
  size?: number;
  extension?: string;
}

interface ChatInputStore {
  // File references for current input
  fileReferences: FileReference[];
  
  // Recently accessed files for quick selectionw
  recentFiles: FileReference[];

  // Media attachments (images, audio)
  mediaAttachments: MediaAttachment[];
  
  // Input value (not persisted)
  inputValue: string;

  // Prompt optimization history (local stack, not persisted)
  promptHistory: string[];
  
  // Actions for file references
  addFileReference: (path: string, relativePath: string, name: string, is_directory: boolean) => void;
  removeFileReference: (path: string) => void;
  clearFileReferences: () => void;
  replaceFileReferences: (files: FileReference[]) => void;
  addRecentFile: (file: FileReference) => void;
  
  // Actions for media attachments
  addMediaAttachment: (attachment: MediaAttachment) => void;
  removeMediaAttachment: (id: string) => void;
  clearMediaAttachments: () => void;
  
  // Input actions
  setInputValue: (value: string) => void;
  appendToInput: (value: string) => void;

  // Prompt optimization history actions
  pushPromptHistory: (value: string) => void;
  popPromptHistory: () => string | null;
  clearPromptHistory: () => void;
  
  // Focus control
  focusSignal: number;
  requestFocus: () => void;
  
  // Edit & resend target
  editingTarget: { conversationId: string; messageId: string } | null;
  setEditingTarget: (conversationId: string, messageId: string) => void;
  clearEditingTarget: () => void;

  // Clear all
  clearAll: () => void;
  
  // Utility
  hasFileReference: (path: string) => boolean;
  hasMediaAttachment: (path: string) => boolean;
}

export const useChatInputStore = create<ChatInputStore>()(
  persist(
    (set, get) => ({
      fileReferences: [],
      recentFiles: [],
      mediaAttachments: [],
      inputValue: "",
      promptHistory: [],
      
      // Focus control (increments to trigger effects)
      focusSignal: 0,
      editingTarget: null,

      // File reference actions
      addFileReference: (path: string, relativePath: string, name: string, is_directory: boolean) => {
        const { fileReferences, addRecentFile } = get();
        const exists = fileReferences.some(ref => ref.path === path);
        
        const newFileRef = { path, relativePath, name, is_directory };

        if (!exists) {
          set({
            fileReferences: [...fileReferences, newFileRef]
          });
        }
        addRecentFile(newFileRef);
      },

      addRecentFile: (file: FileReference) => {
        const { recentFiles } = get();
        // Remove if already exists to move to front
        const filtered = recentFiles.filter(ref => ref.path !== file.path);
        // Add to front, limit to 10
        set({ recentFiles: [file, ...filtered].slice(0, 10) });
      },

      removeFileReference: (path: string) => {
        set((state) => ({
          fileReferences: state.fileReferences.filter(ref => ref.path !== path)
        }));
      },

      clearFileReferences: () => {
        set({ fileReferences: [] });
      },

      replaceFileReferences: (files: FileReference[]) => {
        set({ fileReferences: files });
      },

      // Media attachment actions
      addMediaAttachment: (attachment: MediaAttachment) => {
        const { mediaAttachments } = get();
        const exists = mediaAttachments.some(media => media.path === attachment.path);
        
        if (!exists) {
          set({
            mediaAttachments: [...mediaAttachments, attachment]
          });
        }
      },

      removeMediaAttachment: (id: string) => {
        set((state) => ({
          mediaAttachments: state.mediaAttachments.filter(media => media.id !== id)
        }));
      },

      clearMediaAttachments: () => {
        set({ mediaAttachments: [] });
      },

      // Input actions – also sync with optional external setter for compatibility
      setInputValue: (value: string) => {
        set({ inputValue: value });
      },

      appendToInput: (value: string) => {
        const { inputValue } = get();
        const separator = inputValue.trim() ? '\n\n' : '';
        const newVal = inputValue + separator + value;
        set({ inputValue: newVal });
      },

      pushPromptHistory: (value: string) => {
        const { promptHistory } = get();
        const lastEntry = promptHistory[promptHistory.length - 1];
        const shouldAdd = value.trim().length > 0 && value !== lastEntry;

        if (!shouldAdd) {
          return;
        }

        set({ promptHistory: [value] });
      },

      popPromptHistory: () => {
        const { promptHistory } = get();
        if (promptHistory.length === 0) {
          return null;
        }

        const previousValue = promptHistory[promptHistory.length - 1];
        set({ promptHistory: promptHistory.slice(0, -1) });
        return previousValue;
      },

      clearPromptHistory: () => {
        set({ promptHistory: [] });
      },
      
      // Focus control: bump signal to notify listeners
      requestFocus: () => {
        set((state) => ({ focusSignal: state.focusSignal + 1 }));
      },

      // Clear all
      clearAll: () => {
        set({ 
          fileReferences: [],
          mediaAttachments: [],
          inputValue: "",
          promptHistory: [],
        });
      },

      // Edit target
      setEditingTarget: (conversationId: string, messageId: string) => {
        set({ editingTarget: { conversationId, messageId } });
      },
      clearEditingTarget: () => {
        set({ editingTarget: null });
      },

      // Utilities
      hasFileReference: (path: string) => {
        const { fileReferences } = get();
        return fileReferences.some(ref => ref.path === path);
      },

      hasMediaAttachment: (path: string) => {
        const { mediaAttachments } = get();
        return mediaAttachments.some(media => media.path === path);
      },
    }),
    {
      name: 'codexia-chat-input-store',
      // Only persist references, not input value
      partialize: (state) => ({
        fileReferences: state.fileReferences,
        mediaAttachments: state.mediaAttachments,
      }),
    }
  )
);</doc><doc title="Codex" desc="docs page.">export interface ApprovalRequest {
  id: string;
  type: "exec" | "patch" | "apply_patch";
  command?: string;
  cwd?: string;
  patch?: string;
  files?: string[];
  call_id?: string;
  changes?: any;
  reason?: string;
  grant_root?: string;
}

export interface CodexConfig {
  workingDirectory: string;
  model: string;
  provider: string; // Support any provider from config.toml
  useOss: boolean;
  customArgs?: string[];
  approvalPolicy: "untrusted" | "on-failure" | "on-request" | "never";
  sandboxMode: "read-only" | "workspace-write" | "danger-full-access";
  codexPath?: string;
  reasoningEffort?: "high" | "medium" | "low" | "minimal";
  // Optional: resume a previous session from a rollout file
  resumePath?: string;
  // Enable experimental web search tool for the agent
  webSearchEnabled?: boolean;
}

export const SANDBOX_MODES: Record<
  CodexConfig["sandboxMode"],
  {
    label: string;
    shortLabel: string;
    description: string;
    defaultApprovalPolicy: CodexConfig["approvalPolicy"];
  }
> = {
  "read-only": {
    label: "Read Only",
    shortLabel: "Chat or plan",
    description: "View files only, requires approval for edits/commands",
    defaultApprovalPolicy: "untrusted",
  },
  "workspace-write": {
    label: "Workspace Write",
    shortLabel: "Agent",
    description: "Edit project files, approval for network/external access",
    defaultApprovalPolicy: "on-request",
  },
  "danger-full-access": {
    label: "Full Access",
    shortLabel: "Agent (Full)",
    description: "System-wide access without restrictions",
    defaultApprovalPolicy: "never",
  },
};

export const APPROVAL_POLICIES: Array<{
  value: CodexConfig["approvalPolicy"];
  label: string;
  description: string;
}> = [
  {
    value: "untrusted",
    label: "Untrusted",
    description: "Always prompt before running commands, editing, or using tools.",
  },
  {
    value: "on-failure",
    label: "On Failure",
    description: "Try actions automatically and only prompt if sandbox blocks them.",
  },
  {
    value: "on-request",
    label: "On Request",
    description: "Ask before risky actions like editing, running commands, or network access.",
  },
  {
    value: "never",
    label: "Never",
    description: "Never prompt; Codex decides autonomously. Use with caution.",
  },
];

export const DEFAULT_CONFIG: CodexConfig = {
  workingDirectory: "",
  model: "gpt-5-codex",
  provider: "openai",
  useOss: false,
  approvalPolicy: "on-request",
  sandboxMode: "workspace-write",
  webSearchEnabled: false,
};</doc><doc title="Config" desc="docs page.">export interface ModelProvider {
  name: string;
  base_url: string;
  env_key?: string;
}

export interface Profile {
  model_provider: string;
  model?: string;
}

export interface ProviderConfig {
  provider: ModelProvider;
  profile?: Profile;
}</doc><doc title="Configservice" desc="docs page.">import { invoke } from "@/lib/tauri-proxy";
import { ModelProvider, Profile, ProviderConfig } from "@/types/config";

export class ConfigService {
  static async getProviderConfig(
    providerName: string,
  ): Promise<ProviderConfig | null> {
    try {
      const result = await invoke<[ModelProvider, Profile | null] | null>(
        "get_provider_config",
        {
          providerName,
        },
      );

      if (result) {
        const [provider, profile] = result;
        return {
          provider,
          profile: profile || undefined,
        };
      }

      return null;
    } catch (error) {
      console.error(
        `Failed to get provider config for ${providerName}:`,
        error,
      );
      return null;
    }
  }

  static async getProfileConfig(profileName: string): Promise<Profile | null> {
    try {
      const result = await invoke<Profile | null>("get_profile_config", {
        profileName,
      });

      return result;
    } catch (error) {
      console.error(`Failed to get profile config for ${profileName}:`, error);
      return null;
    }
  }

  static async getAllProviders(): Promise<Record<string, ModelProvider>> {
    try {
      const result = await invoke<Record<string, ModelProvider>>(
        "read_model_providers",
      );
      return result;
    } catch (error) {
      console.error("Failed to get all providers:", error);
      return {};
    }
  }

  static async getAllProfiles(): Promise<Record<string, Profile>> {
    try {
      const result = await invoke<Record<string, Profile>>("read_profiles");
      return result;
    } catch (error) {
      console.error("Failed to get all profiles:", error);
      return {};
    }
  }

  static async addOrUpdateProfile(
    profileName: string,
    profile: Profile,
  ): Promise<void> {
    try {
      await invoke("add_or_update_profile", {
        profileName,
        profile,
      });
    } catch (error) {
      console.error(`Failed to add/update profile ${profileName}:`, error);
      throw new Error(`Failed to add/update profile: ${error}`);
    }
  }

  static async addProviderWithProfile(options: {
    providerId: string;
    providerName: string;
    baseUrl?: string;
    envKey?: string;
    model?: string;
  }): Promise<void> {
    const { providerId, providerName, baseUrl, envKey, model } = options;

    const providerPayload: ModelProvider = {
      name: providerName,
      base_url: baseUrl || "",
      env_key: envKey || undefined,
    };

    await this.addOrUpdateModelProvider(providerId, providerPayload);

    if (model) {
      try {
        await this.addOrUpdateProfile(providerId, {
          model_provider: providerId,
          model,
        });
      } catch (error) {
        try {
          await this.deleteModelProvider(providerId);
        } catch (rollbackError) {
          console.error(
            `Failed to rollback provider ${providerId} after profile error:`,
            rollbackError,
          );
        }
        throw error;
      }
    }
  }

  static async deleteProfile(profileName: string): Promise<void> {
    try {
      await invoke("delete_profile", {
        profileName,
      });
    } catch (error) {
      console.error(`Failed to delete profile ${profileName}:`, error);
      throw new Error(`Failed to delete profile: ${error}`);
    }
  }

  static async addOrUpdateModelProvider(
    providerName: string,
    provider: ModelProvider,
  ): Promise<void> {
    try {
      await invoke("add_or_update_model_provider", {
        providerName,
        provider,
      });
    } catch (error) {
      console.error(
        `Failed to add/update model provider ${providerName}:`,
        error,
      );
      throw new Error(`Failed to add/update model provider: ${error}`);
    }
  }

  static async deleteModelProvider(providerName: string): Promise<void> {
    try {
      await invoke("delete_model_provider", {
        providerName,
      });
    } catch (error) {
      console.error(
        `Failed to delete model provider ${providerName}:`,
        error,
      );
      throw new Error(`Failed to delete model provider: ${error}`);
    }
  }
}</doc><doc title="Contextfilesstore" desc="docs page.">import { create } from "zustand";
import { persist } from "zustand/middleware";

interface ContextFile {
  path: string;
  name: string;
  addedAt: number;
}

interface ContextFilesState {
  contextFiles: ContextFile[];
  addFile: (path: string) => void;
  removeFile: (path: string) => void;
  clearFiles: () => void;
  getFileName: (path: string) => string;
}

export const useContextFilesStore = create<ContextFilesState>()(
  persist(
    (set, get) => ({
      contextFiles: [],

      addFile: (path: string) => {
        const fileName = get().getFileName(path);
        const newFile: ContextFile = {
          path,
          name: fileName,
          addedAt: Date.now(),
        };

        set((state) => ({
          contextFiles: state.contextFiles.some((f) => f.path === path)
            ? state.contextFiles
            : [...state.contextFiles, newFile],
        }));
      },

      removeFile: (path: string) => {
        set((state) => ({
          contextFiles: state.contextFiles.filter((f) => f.path !== path),
        }));
      },

      clearFiles: () => {
        set({ contextFiles: [] });
      },

      getFileName: (path: string) => {
        return path.split("/").pop() || path;
      },
    }),
    {
      name: "context-files-storage",
    },
  ),
);</doc></src></project>
