Files
clide/legacy/docs/code-organization.md
jpmschweitzerandClaude Opus 4.7 a355751437 move python clide to legacy/
Clide is being rebuilt as a Flutter desktop app. The Python Textual
implementation moves wholesale into legacy/ rather than being deleted:
its pane model, panel set, git skills, and panel communication design
are real thought that should remain readable next to the new code
while the rebuild finds its shape. Git's rename tracking preserves
history, so `git log -- legacy/` still works.

The Flutter rebuild lives at the repo root alongside a Go sidecar
(the architecture claudian was heading toward, which folds into
clide as a core component rather than a separate plugin project).
Bootstrap of the new shape lands in subsequent commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 20:30:51 +02:00

343 lines
10 KiB
Markdown

# Code Organization
Clide follows a layered architecture separating UI components from business logic, enabling testability and maintainability.
## Directory Structure
```
clide/
├── app.py # Main application, layout, keybindings
├── cli.py # Typer entry point
├── models/ # Pydantic data models
├── services/ # Background services and utilities
├── controllers/ # Business logic (no UI)
├── widgets/
│ ├── panels/ # Main layout containers
│ └── components/ # Reusable UI pieces
├── themes/ # Theme definitions and registry
├── extensions/ # Plugin system (hookspecs, manager)
├── templates/ # Bundled templates (skills, etc.)
└── vendor/ # Vendored dependencies (pyte)
```
## Layers
### Models (`clide/models/`)
Pure data structures using Pydantic with strict mode. Models are immutable and contain no business logic.
```python
from pydantic import BaseModel, ConfigDict
class GitChange(BaseModel):
model_config = ConfigDict(strict=True, frozen=True)
path: str
status: Literal["added", "modified", "deleted", "untracked", "renamed"]
staged: bool
```
**Key models:**
- `git.py` — Git-related types (GitBranch, GitCommit, GitChange, GitStatus)
- `config.py` — Application settings (ClideSettings, PanelConfig)
- `problems.py` — Linter output (Problem, Severity)
- `todos.py` — TODO items (TodoItem, ProjectTodoItem, TodoType)
### Services (`clide/services/`)
Stateless utilities that perform work without UI interaction. Services may be async or use background threads.
```python
class GitService:
"""Git operations via subprocess."""
def __init__(self, workdir: Path):
self._workdir = workdir
async def get_status(self) -> GitStatus:
"""Get current repository status."""
...
async def get_branches(self) -> list[GitBranch]:
"""List all branches."""
...
```
**Key services:**
- `git_service.py` — Git CLI operations
- `file_service.py` — File read/write operations
- `todo_scanner.py` — Scans codebase for TODO/FIXME comments
- `settings_service.py` — User settings persistence
- `skill_installer.py` — Claude Code skill management
- `file_watcher.py` — File system change monitoring
- `syntax_service.py` — Tree-sitter syntax highlighting
### Controllers (`clide/controllers/`)
Bridge between services and UI. Controllers contain business logic, manage state, and emit Textual messages. Controllers have no direct UI rendering.
```python
from clide.controllers.base import controller
@controller
class GitController:
"""Manages git state and operations."""
def __init__(self, workdir: Path):
self._service = GitService(workdir)
self._status: GitStatus | None = None
async def refresh_status(self) -> GitStatus:
"""Refresh and cache git status."""
self._status = await self._service.get_status()
return self._status
def stage_file(self, path: str) -> None:
"""Stage a file for commit."""
...
```
**Key controllers:**
- `git.py` — Git operations, skill integration
- `editor.py` — File editing state
- `diff.py` — Diff viewing and management
- `problems.py` — Linter integration
- `todos.py` — TODO tracking
- `jira.py` — Jira CLI integration
### Widgets (`clide/widgets/`)
UI components split into panels (layout containers) and components (reusable pieces).
#### Panels (`clide/widgets/panels/`)
Top-level layout containers that compose the application UI.
```python
class SidebarPanel(Vertical):
"""Left sidebar with Files, Git, and Tree tabs."""
class FileSelected(Message):
"""Emitted when a file is selected."""
def __init__(self, path: Path):
self.path = path
super().__init__()
def compose(self) -> ComposeResult:
with TabbedContent():
with TabPane("Files"):
yield FilesView(path=self._workdir)
with TabPane("Git"):
yield GitChangesView()
with TabPane("Tree"):
yield GitGraphView()
yield BranchStatus()
```
**Panels:**
- `sidebar.py` — Left sidebar (files, git, graph)
- `context.py` — Right sidebar (problems, todos, jira)
- `workspace.py` — Center workspace (editor, diff, terminal)
- `claude.py` — Claude Code terminal integration
#### Components (`clide/widgets/components/`)
Reusable UI pieces composed into panels.
**File browsing:**
- `files_view.py` — Project file tree
- `file_entry.py` — Single file/directory entry
**Git:**
- `git_changes.py` — Staged/unstaged file lists
- `git_graph.py` — Visual branch graph
- `branch_status.py` — Branch indicator with popout selector
**Context:**
- `problems_view.py` — Linter problems list
- `todos_view.py` — TODO/FIXME list with sub-tabs
- `jira_view.py` — Jira issue display
**Editor:**
- `editor_pane.py` — Code editor with syntax highlighting
- `diff_pane.py` — Side-by-side diff viewer
- `terminal_pane.py` — Command execution terminal
## Communication Patterns
### Message Flow
Components communicate via Textual's message system. Messages bubble up through the widget tree.
```
Component emits message
Parent panel receives and may re-emit
App handles and coordinates response
App calls controller methods
Controller updates state, may emit messages
UI updates reactively
```
**Example: File selection**
```python
# In FilesView (component)
class FileSelected(Message):
def __init__(self, path: Path):
self.path = path
super().__init__()
def on_tree_node_selected(self, event):
if event.node.data.is_file:
self.post_message(self.FileSelected(event.node.data.path))
# In SidebarPanel (panel)
def on_files_view_file_selected(self, event: FilesView.FileSelected):
# Re-emit for app to handle
self.post_message(self.FileSelected(event.path))
# In ClideApp (app)
def on_sidebar_panel_file_selected(self, event: SidebarPanel.FileSelected):
self.editor_controller.open_file(event.path)
self.show_workspace("editor")
```
### Reactive Properties
State that affects UI uses Textual's reactive system:
```python
class ClideApp(App):
# Reactive state
workspace_visible: reactive[bool] = reactive(False)
problem_count: reactive[int] = reactive(0)
current_branch: reactive[str] = reactive("main")
def watch_workspace_visible(self, visible: bool) -> None:
"""React to workspace visibility changes."""
workspace = self.query_one("#panel-workspace")
workspace.display = visible
claude = self.query_one("#panel-claude")
claude.styles.height = "40%" if visible else "100%"
```
### Background Tasks
Long-running operations use the `@work` decorator to avoid blocking the UI:
```python
from textual import work
class ClideApp(App):
@work(thread=True)
def refresh_git_status(self) -> None:
"""Refresh git status in background."""
status = self.git_controller.get_status_sync()
self.call_from_thread(self._update_git_ui, status)
def _update_git_ui(self, status: GitStatus) -> None:
"""Update UI with git status (runs on main thread)."""
sidebar = self.query_one(SidebarPanel)
sidebar.update_git_status(status.staged, status.unstaged)
```
## Extension System
Clide uses Pluggy for extensibility. Extensions implement hooks defined in `hookspecs.py`.
```python
# clide/extensions/hookspecs.py
class ClideHookSpec:
@hookspec
def clide_startup(self, app: App) -> None:
"""Called when app starts."""
@hookspec
def clide_on_file_changed(self, event: FileEvent) -> None:
"""Called when a file changes."""
# User extension
class MyExtension:
@hookimpl
def clide_on_file_changed(self, event: FileEvent) -> None:
if event.path.suffix == ".py":
# Custom logic for Python files
...
```
**Available hooks:**
- `clide_startup` — App initialization
- `clide_shutdown` — App cleanup
- `clide_on_file_changed` — File system changes
- `clide_on_file_saved` — File saved in editor
## Skills System
Clide integrates with Claude Code skills for git operations. Skills are installed to the project's `.claude/skills/` directory.
```python
# clide/services/skill_installer.py
class SkillInstaller:
def install(self, skill_name: str, scope: Literal["user", "project"] = "project"):
"""Install a skill from bundled templates."""
template_dir = TEMPLATES_DIR / skill_name
target_dir = self.project_skills_dir / skill_name
shutil.copytree(template_dir, target_dir)
```
**Bundled skills** (`clide/templates/skills/`):
- `commit` — Git commit workflow
- `stash` — Git stash operations
- `pull` — Git pull with rebase
- `push` — Git push to remote
- `branch` — Branch management
When a git action button is clicked, Clide ensures the skill is installed before sending the command to Claude.
## Testing
Tests mirror the source structure:
```
tests/
├── unit/
│ ├── test_models.py
│ ├── test_services.py
│ ├── test_controllers.py
│ ├── test_widgets.py
│ └── test_app.py
├── integration/
│ └── test_files_view.py
└── snapshots/
└── test_app_snapshots.py
```
**Unit tests** verify individual components in isolation.
**Integration tests** verify component interactions.
**Snapshot tests** catch visual regressions using pytest-textual-snapshot.
## Configuration
### Application Settings
`ClideSettings` in `clide/models/config.py` defines app configuration loaded from environment or `.config/settings.toml`.
### User Settings
`UserSettings` persisted to `~/.clide/settings.json` stores user preferences:
- Theme selection
- Panel visibility defaults
- Compact mode preference
- Jira integration settings