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>
10 KiB
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.
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.
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 operationsfile_service.py— File read/write operationstodo_scanner.py— Scans codebase for TODO/FIXME commentssettings_service.py— User settings persistenceskill_installer.py— Claude Code skill managementfile_watcher.py— File system change monitoringsyntax_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.
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 integrationeditor.py— File editing statediff.py— Diff viewing and managementproblems.py— Linter integrationtodos.py— TODO trackingjira.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.
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 treefile_entry.py— Single file/directory entry
Git:
git_changes.py— Staged/unstaged file listsgit_graph.py— Visual branch graphbranch_status.py— Branch indicator with popout selector
Context:
problems_view.py— Linter problems listtodos_view.py— TODO/FIXME list with sub-tabsjira_view.py— Jira issue display
Editor:
editor_pane.py— Code editor with syntax highlightingdiff_pane.py— Side-by-side diff viewerterminal_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
# 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:
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:
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.
# 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 initializationclide_shutdown— App cleanupclide_on_file_changed— File system changesclide_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.
# 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 workflowstash— Git stash operationspull— Git pull with rebasepush— Git push to remotebranch— 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