Add Clide project structure and initial implementation
Set up the TUI IDE wrapper for Claude Code CLI with: - Core app structure using Textual framework - Panel architecture (sidebar, workspace, claude, context) - Theme system with 22 built-in themes (Summer Night default) - Pydantic models for configuration and data - Makefile for development commands - Project documentation and specs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
# Clide Architecture Documentation
|
||||
|
||||
Comprehensive documentation of architecture patterns, best practices, and implementation guidelines.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Textual TUI Framework](#textual-tui-framework)
|
||||
- [Typer CLI Framework](#typer-cli-framework)
|
||||
- [Pydantic Data Validation](#pydantic-data-validation)
|
||||
- [Extension System](#extension-system)
|
||||
- [Testing Strategy](#testing-strategy)
|
||||
- [Build and Distribution](#build-and-distribution)
|
||||
|
||||
---
|
||||
|
||||
## Textual TUI Framework
|
||||
|
||||
Textual models TUIs as a reactive tree of widgets, similar to React's component tree but grid-based on character cells.
|
||||
|
||||
### Key Concepts
|
||||
|
||||
**Widgets and Containers**
|
||||
- Widgets are the building blocks of the UI
|
||||
- Containers are widgets that hold other widgets
|
||||
- Default layout stacks widgets vertically from top of screen
|
||||
|
||||
**Reactive Programming**
|
||||
- State changes trigger automatic UI updates
|
||||
- No manual refresh loops needed
|
||||
- Use reactive attributes for state management
|
||||
|
||||
**Event-Driven Model**
|
||||
- Define callbacks for key presses, mouse clicks, timer ticks
|
||||
- Actions are functions callable via keystroke or text link
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Use Immutable Objects**
|
||||
- Prefer tuples, NamedTuples, or frozen dataclasses
|
||||
- Easier to reason about, cache, and test
|
||||
- Enables side-effect-free code
|
||||
|
||||
2. **Separate Styles**
|
||||
- Keep CSS in `.tcss` files, not inline
|
||||
- Python code stays clean and focused on logic
|
||||
|
||||
3. **Async-First**
|
||||
- Textual is async under the hood
|
||||
- Use `async`/`await` for I/O operations
|
||||
- Can integrate with async libraries if needed
|
||||
|
||||
### Layout Management
|
||||
|
||||
```python
|
||||
# Grid layout example
|
||||
CSS = """
|
||||
Screen {
|
||||
layout: grid;
|
||||
grid-size: 3 1;
|
||||
grid-columns: 1fr 2fr 1fr;
|
||||
}
|
||||
"""
|
||||
```
|
||||
|
||||
### References
|
||||
|
||||
- [Textual Documentation](https://textual.textualize.io/)
|
||||
- [Textual Tutorial](https://textual.textualize.io/tutorial/)
|
||||
- [Real Python Textual Guide](https://realpython.com/python-textual/)
|
||||
- [Textual GitHub](https://github.com/Textualize/textual)
|
||||
|
||||
---
|
||||
|
||||
## Typer CLI Framework
|
||||
|
||||
Typer is built on Click with Python type hints for automatic argument parsing.
|
||||
|
||||
### Project Structure Pattern
|
||||
|
||||
```
|
||||
app/
|
||||
├── __init__.py
|
||||
├── main.py # Root Typer app
|
||||
├── commands/ # Subcommand modules
|
||||
│ ├── users.py
|
||||
│ └── tasks.py
|
||||
└── helpers/ # Shared utilities
|
||||
└── validate.py
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Organize Commands**
|
||||
- Use `add_typer()` to group commands
|
||||
- Avoid giant files with dozens of commands
|
||||
- Each command function should orchestrate, not contain all logic
|
||||
|
||||
2. **Entry Point Support**
|
||||
- Add `__main__.py` for `python -m` support
|
||||
- Define entry points in pyproject.toml for CLI scripts
|
||||
|
||||
3. **Standard Exit Codes**
|
||||
- `0` for success
|
||||
- Non-zero for errors
|
||||
- Crucial for CI/CD integration
|
||||
|
||||
4. **Type Hints for Validation**
|
||||
- Use Enum for dropdown-style restrictions
|
||||
- Type hints provide editor autocompletion
|
||||
|
||||
### Subcommand Example
|
||||
|
||||
```python
|
||||
# commands/users.py
|
||||
import typer
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
@app.command()
|
||||
def create(name: str):
|
||||
"""Create a new user."""
|
||||
...
|
||||
|
||||
# main.py
|
||||
from commands import users
|
||||
|
||||
main_app = typer.Typer()
|
||||
main_app.add_typer(users.app, name="users")
|
||||
```
|
||||
|
||||
### References
|
||||
|
||||
- [Typer Documentation](https://typer.tiangolo.com/)
|
||||
- [Typer Subcommands](https://typer.tiangolo.com/tutorial/subcommands/)
|
||||
- [Building a Package](https://typer.tiangolo.com/tutorial/package/)
|
||||
|
||||
---
|
||||
|
||||
## Pydantic Data Validation
|
||||
|
||||
Pydantic v2 with strict mode ensures type safety and validation.
|
||||
|
||||
### Strict Mode Configuration
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
class MyModel(BaseModel):
|
||||
model_config = ConfigDict(strict=True, frozen=True)
|
||||
name: str
|
||||
count: int # Will reject "123" string
|
||||
```
|
||||
|
||||
### Settings Management
|
||||
|
||||
Settings have moved to `pydantic-settings` package:
|
||||
|
||||
```python
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
class AppSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="APP_",
|
||||
env_file=".env",
|
||||
env_nested_delimiter="__",
|
||||
)
|
||||
|
||||
database_url: str
|
||||
debug: bool = False
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Use `frozen=True` for Immutability**
|
||||
- Prevents accidental mutation
|
||||
- Enables hashing for use as dict keys
|
||||
|
||||
2. **Explicit Strict Types**
|
||||
- `StrictInt`, `StrictStr` for field-level strictness
|
||||
- Or use `model_config` for model-wide strictness
|
||||
|
||||
3. **Validation vs Parsing**
|
||||
- Strict mode rejects type coercion
|
||||
- JSON parsing allows some conversion (ISO8601 → datetime)
|
||||
|
||||
### References
|
||||
|
||||
- [Pydantic v2 Documentation](https://docs.pydantic.dev/latest/)
|
||||
- [Pydantic Settings](https://docs.pydantic.dev/latest/concepts/pydantic_settings/)
|
||||
- [Pydantic Configuration](https://docs.pydantic.dev/latest/api/config/)
|
||||
- [Migration Guide](https://docs.pydantic.dev/latest/migration/)
|
||||
|
||||
---
|
||||
|
||||
## Extension System
|
||||
|
||||
The plugin system uses Pluggy for hook-based extensibility.
|
||||
|
||||
### Pluggy Concepts
|
||||
|
||||
1. **Hook Specifications** - Define the interface extensions implement
|
||||
2. **Hook Implementations** - Extension code implementing hooks
|
||||
3. **Plugin Manager** - Discovers and calls implementations
|
||||
|
||||
### Architecture
|
||||
|
||||
```python
|
||||
# hookspecs.py - Define hooks
|
||||
import pluggy
|
||||
|
||||
hookspec = pluggy.HookspecMarker("clide")
|
||||
hookimpl = pluggy.HookimplMarker("clide")
|
||||
|
||||
class ClideHookSpec:
|
||||
@hookspec
|
||||
def register_panel(self) -> dict: ...
|
||||
|
||||
# extension.py - Implement hooks
|
||||
class MyExtension:
|
||||
@hookimpl
|
||||
def register_panel(self) -> dict:
|
||||
return {"name": "custom", "widget": CustomWidget}
|
||||
```
|
||||
|
||||
### Distribution
|
||||
|
||||
Extensions can be distributed as packages using entry points:
|
||||
|
||||
```toml
|
||||
# pyproject.toml of extension package
|
||||
[project.entry-points."clide.extensions"]
|
||||
my_extension = "my_package:MyExtension"
|
||||
```
|
||||
|
||||
### Hook Execution Order
|
||||
|
||||
- Multiple implementations called in LIFO (Last In, First Out) order
|
||||
- Use `hookimpl(tryfirst=True)` or `hookimpl(trylast=True)` for ordering
|
||||
|
||||
### Alternatives
|
||||
|
||||
- **Stevedore** - Better for driver/extension patterns, uses entry points
|
||||
- Choose Pluggy for hook-based systems (like pytest uses)
|
||||
|
||||
### References
|
||||
|
||||
- [Pluggy Documentation](https://pluggy.readthedocs.io/)
|
||||
- [Stevedore Documentation](https://docs.openstack.org/stevedore/latest/)
|
||||
- [Creating Plugins with Stevedore](https://docs.openstack.org/stevedore/latest/user/tutorial/creating_plugins.html)
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### pytest-asyncio
|
||||
|
||||
Configure auto mode for automatic async test discovery:
|
||||
|
||||
```toml
|
||||
# pyproject.toml
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
```
|
||||
|
||||
### Async Test Patterns
|
||||
|
||||
```python
|
||||
import pytest
|
||||
|
||||
# Auto mode - no decorator needed
|
||||
async def test_async_operation():
|
||||
result = await some_async_function()
|
||||
assert result == expected
|
||||
|
||||
# Async fixtures
|
||||
@pytest.fixture
|
||||
async def database_connection():
|
||||
conn = await create_connection()
|
||||
yield conn
|
||||
await conn.close()
|
||||
```
|
||||
|
||||
### Async Mocking
|
||||
|
||||
```python
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
async def test_with_mock():
|
||||
mock_service = AsyncMock(return_value={"status": "ok"})
|
||||
result = await mock_service()
|
||||
assert result["status"] == "ok"
|
||||
```
|
||||
|
||||
### Snapshot Testing
|
||||
|
||||
Visual regression with pytest-textual-snapshot:
|
||||
|
||||
```python
|
||||
def test_layout(snap_compare):
|
||||
assert snap_compare("app.py", terminal_size=(120, 40))
|
||||
|
||||
def test_with_interaction(snap_compare):
|
||||
async def setup(pilot):
|
||||
await pilot.press("tab", "enter")
|
||||
|
||||
assert snap_compare("app.py", run_before=setup)
|
||||
```
|
||||
|
||||
Update snapshots after intentional changes:
|
||||
```bash
|
||||
pytest tests/snapshots/ --snapshot-update
|
||||
```
|
||||
|
||||
### Test Harness Pattern
|
||||
|
||||
Harnesses provide isolated test environments:
|
||||
|
||||
```python
|
||||
class AppHarness:
|
||||
async def start(self) -> tuple[App, Pilot]:
|
||||
"""Start app with mocked dependencies."""
|
||||
|
||||
async def stop(self) -> None:
|
||||
"""Clean shutdown."""
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always use `@pytest.mark.asyncio`** (or auto mode)
|
||||
2. **Use async fixtures** for async setup/teardown
|
||||
3. **Mock external services** - don't hit real APIs
|
||||
4. **Choose appropriate fixture scopes** for performance
|
||||
5. **Avoid blocking the event loop** in async tests
|
||||
|
||||
### References
|
||||
|
||||
- [pytest-asyncio Documentation](https://pytest-asyncio.readthedocs.io/en/latest/)
|
||||
- [pytest-textual-snapshot](https://github.com/Textualize/pytest-textual-snapshot)
|
||||
- [Textual Testing Guide](https://textual.textualize.io/guide/testing/)
|
||||
- [pytest Fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html)
|
||||
|
||||
---
|
||||
|
||||
## Build and Distribution
|
||||
|
||||
### PyInstaller Limitations
|
||||
|
||||
**Critical: PyInstaller cannot cross-compile.**
|
||||
- Build on the target OS
|
||||
- Use CI/CD for multi-platform builds
|
||||
|
||||
### CI/CD Multi-Platform Build
|
||||
|
||||
Use Gitea Actions (or compatible CI) for multi-platform builds:
|
||||
|
||||
```yaml
|
||||
# .gitea/workflows/build.yml
|
||||
name: Build
|
||||
on: [push, tag]
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- run: pip install -e ".[build]"
|
||||
- run: pyinstaller clide.spec --clean
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- run: pip install -e ".[build]"
|
||||
- run: pyinstaller clide.spec --clean
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- run: pip install -e ".[build]"
|
||||
- run: pyinstaller clide.spec --clean
|
||||
```
|
||||
|
||||
### Optimization Tips
|
||||
|
||||
1. **Use `--onefile`** for single executable
|
||||
2. **Apply `--strip`** to reduce binary size
|
||||
3. **Use UPX compression** (460 MB → ~130 MB possible)
|
||||
4. **Exclude unused modules** with `--exclude-module`
|
||||
5. **Lazy imports** for large libraries
|
||||
|
||||
### Platform-Specific Output
|
||||
|
||||
- **Windows**: `.exe` or MSIX installer
|
||||
- **macOS**: `.app` bundle in `.dmg`
|
||||
- **Linux**: AppImage or native package
|
||||
|
||||
### Linux Compatibility
|
||||
|
||||
Build on the oldest target distro version. Newer systems may produce incompatible binaries.
|
||||
|
||||
### References
|
||||
|
||||
- [PyInstaller Documentation](https://pyinstaller.org/)
|
||||
- [Building the Bootloader](https://pyinstaller.org/en/latest/bootloader-building.html)
|
||||
- [Gitea Actions](https://docs.gitea.com/usage/actions/overview)
|
||||
@@ -0,0 +1,503 @@
|
||||
# TUI IDE Specification
|
||||
|
||||
A terminal-based IDE built with Textual, designed to wrap Claude Code and integrate project management tooling (Jira/Confluence via CLI).
|
||||
|
||||
## Design Principles
|
||||
|
||||
- **Claude-centric**: Claude Code is the primary workspace, always visible
|
||||
- **Contextual panels**: Editor/Diff/Terminal appear only when needed
|
||||
- **VSCode-familiar**: Keybindings and interaction patterns follow VSCode conventions
|
||||
- **Responsive**: Works on 13" laptop and widescreen monitors
|
||||
- **No vim magic**: Standard keyboard navigation, no modal editing
|
||||
|
||||
---
|
||||
|
||||
## Panel Structure
|
||||
|
||||
### Layout Overview
|
||||
|
||||
```
|
||||
┌─────────────────┬─────────────────────────┬──────────────────┐
|
||||
│ panel-sidebar │ panel-workspace (60%) │ panel-context │
|
||||
│ │ [Editor][Diff][Terminal]│ │
|
||||
│ [Files][Git] │ (hidden when inactive) │ (content area) │
|
||||
│ [Tree] ├─────────────────────────┤ │
|
||||
│ │ │ │
|
||||
│ (content area) │ panel-claude │ │
|
||||
│ │ (40% when workspace │ │
|
||||
│ │ visible, else 100%) │ │
|
||||
├─────────────────┤ ├──────────────────┤
|
||||
│ branch-status │ │[⚠ 3][✓12][Jira] │
|
||||
│ ⎇ main ▾ │ │ context-tabs │
|
||||
└─────────────────┴─────────────────────────┴──────────────────┘
|
||||
```
|
||||
|
||||
### Panel Definitions
|
||||
|
||||
```python
|
||||
PANELS = {
|
||||
# Left sidebar
|
||||
"sidebar": "panel-sidebar",
|
||||
"sidebar-files": "panel-sidebar-files",
|
||||
"sidebar-git": "panel-sidebar-git",
|
||||
"sidebar-tree": "panel-sidebar-tree",
|
||||
"branch-status": "panel-branch-status",
|
||||
|
||||
# Center
|
||||
"claude": "panel-claude",
|
||||
"workspace": "panel-workspace",
|
||||
"editor": "panel-editor",
|
||||
"diff": "panel-diff",
|
||||
"terminal": "panel-terminal",
|
||||
|
||||
# Right context
|
||||
"context": "panel-context",
|
||||
"context-jira": "panel-context-jira",
|
||||
"context-problems": "panel-context-problems",
|
||||
"context-todos": "panel-context-todos",
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Left Sidebar (`panel-sidebar`)
|
||||
|
||||
### Tabs
|
||||
|
||||
| Tab | Content | Widget |
|
||||
|-----|---------|--------|
|
||||
| Files | Project file tree | `DirectoryTree` |
|
||||
| Git | Staged/Unstaged changes | `GitChangesView` (custom) |
|
||||
| Tree | Merge/branch graph | `GitGraphView` (custom) |
|
||||
|
||||
### Git Tab Details
|
||||
|
||||
Two collapsible sections:
|
||||
- **Staged**: Files in index, ready to commit
|
||||
- **Unstaged**: Modified/untracked files
|
||||
|
||||
Each file item shows:
|
||||
- Status icon: `+` added, `~` modified, `-` deleted, `?` untracked, `→` renamed
|
||||
- File path (relative)
|
||||
|
||||
**Interactions:**
|
||||
- Click file → opens in Editor panel
|
||||
- Double-click or keybind → stage/unstage file
|
||||
- Right-click or keybind → show context menu (discard, diff, etc.)
|
||||
|
||||
### Tree Tab Details
|
||||
|
||||
Renders `git log --graph --oneline --decorate --all` with visual styling.
|
||||
|
||||
**Polish item**: Consider custom rendering with box-drawing characters for a cleaner look:
|
||||
```
|
||||
●──┬── main: Latest commit message
|
||||
│ ●── feature: Feature work
|
||||
●──┴── Merge branch 'feature'
|
||||
◆───── Tagged release v1.0
|
||||
```
|
||||
|
||||
Use canvas or rich text with:
|
||||
```python
|
||||
GRAPH_CHARS = {
|
||||
'commit': '●',
|
||||
'merge': '◆',
|
||||
'line': '│',
|
||||
'branch': '├──',
|
||||
'join': '┴──',
|
||||
}
|
||||
```
|
||||
|
||||
### Branch Status Bar
|
||||
|
||||
Fixed at bottom of sidebar. Shows current branch with popout toggle.
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ ⎇ main ▾ │ ← Click or keybind to expand
|
||||
└─────────────────┘
|
||||
│
|
||||
▼ (popout overlay)
|
||||
┌─────────────────┐
|
||||
│ Recent branches │
|
||||
│ ○ main │
|
||||
│ ○ feature/xyz │
|
||||
│ ○ develop │
|
||||
├─────────────────┤
|
||||
│ [Checkout] [New]│
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Center Column
|
||||
|
||||
### Claude Panel (`panel-claude`)
|
||||
|
||||
The primary workspace. Displays Claude Code interaction.
|
||||
|
||||
**Default state**: 100% height of center column
|
||||
**With workspace**: 40% height (bottom)
|
||||
|
||||
**Content:**
|
||||
- Streaming markdown responses (use `Markdown` or `RichLog` widget)
|
||||
- Visual distinction between:
|
||||
- Claude's responses
|
||||
- Tool calls / file operations
|
||||
- User input
|
||||
- Input area at bottom
|
||||
|
||||
### Workspace Panel (`panel-workspace`)
|
||||
|
||||
Tabbed container for Editor, Diff, and Terminal. **Hidden by default.**
|
||||
|
||||
**Important**: Hiding is not closing. All panels retain state when hidden:
|
||||
- Editor: Open files, cursor position, scroll position, unsaved changes
|
||||
- Diff: Current diff content, scroll position
|
||||
- Terminal: Active session, command history, output buffer
|
||||
|
||||
Use `display: none` for visibility, never destroy/recreate widgets.
|
||||
|
||||
**Visibility triggers:**
|
||||
|
||||
| Trigger | Result |
|
||||
|---------|--------|
|
||||
| Click file in sidebar | Show workspace, focus Editor tab |
|
||||
| Claude proposes changes | Show workspace, focus Diff tab |
|
||||
| User presses `` Ctrl+` `` | Show workspace, focus Terminal tab |
|
||||
| User runs command | Show workspace, focus Terminal tab |
|
||||
| Close all tabs / Escape | Hide workspace, Claude reclaims space |
|
||||
|
||||
**Height**: 60% of center column when visible
|
||||
|
||||
#### Editor Tab
|
||||
|
||||
- `TextArea` widget with syntax highlighting
|
||||
- Language detection from file extension
|
||||
- Theme: Follow terminal theme or user preference
|
||||
|
||||
#### Diff Tab
|
||||
|
||||
- Side-by-side or unified diff view
|
||||
- Syntax highlighting for changed content
|
||||
- Accept/Reject buttons for Claude-proposed changes
|
||||
|
||||
#### Terminal Tab
|
||||
|
||||
- Proper PTY integration for full terminal emulation
|
||||
- Or simpler command runner with output display (decide based on complexity)
|
||||
- Working directory tied to project root
|
||||
|
||||
---
|
||||
|
||||
## Right Sidebar (`panel-context`)
|
||||
|
||||
### Content Area
|
||||
|
||||
Switches based on selected bottom tab. Shows one of:
|
||||
- Jira view (default)
|
||||
- Problems view
|
||||
- TODOs view
|
||||
|
||||
### Bottom Tab Bar (`context-tabs`)
|
||||
|
||||
```
|
||||
┌──────────────────┐
|
||||
│ [⚠ 3][✓12][Jira]│
|
||||
└──────────────────┘
|
||||
```
|
||||
|
||||
Tabs show inline counts that update reactively.
|
||||
|
||||
| Tab | Icon | Content |
|
||||
|-----|------|---------|
|
||||
| Problems | ⚠ | Linter errors, warnings (count badge) |
|
||||
| TODOs | ✓ | TODO/FIXME comments from codebase (count badge) |
|
||||
| Jira | Jira | Output from your CLI tool (default) |
|
||||
|
||||
### Jira View
|
||||
|
||||
Renders markdown output from your CLI tool. Refreshes on:
|
||||
- Panel focus
|
||||
- Manual refresh keybind
|
||||
- Configurable interval
|
||||
|
||||
### Problems View
|
||||
|
||||
Aggregates from linters (eslint, ruff, etc.). Shows:
|
||||
- File path
|
||||
- Line number
|
||||
- Severity icon
|
||||
- Message
|
||||
|
||||
Click → opens file in Editor at that line.
|
||||
|
||||
### TODOs View
|
||||
|
||||
Grep results for `TODO`, `FIXME`, `HACK`, `XXX`. Shows:
|
||||
- File path
|
||||
- Line number
|
||||
- Comment text
|
||||
|
||||
Click → opens file in Editor at that line.
|
||||
|
||||
---
|
||||
|
||||
## Responsiveness
|
||||
|
||||
### CSS Breakpoints
|
||||
|
||||
```css
|
||||
/* Widescreen (default) */
|
||||
#panel-sidebar { width: 20%; min-width: 25; }
|
||||
#panel-context { width: 25%; min-width: 30; }
|
||||
#panel-claude { width: 1fr; }
|
||||
|
||||
/* Medium terminals */
|
||||
@media (width < 120) {
|
||||
#panel-sidebar { width: 18%; }
|
||||
#panel-context { width: 22%; }
|
||||
}
|
||||
|
||||
/* Narrow terminals (laptop, split screen) */
|
||||
@media (width < 100) {
|
||||
#panel-sidebar { display: none; }
|
||||
#panel-context { width: 25%; }
|
||||
}
|
||||
|
||||
@media (width < 80) {
|
||||
#panel-context { display: none; }
|
||||
#panel-claude { width: 100%; }
|
||||
}
|
||||
```
|
||||
|
||||
### Compact Mode
|
||||
|
||||
Toggle with `Ctrl+Shift+C`. Hides both sidebars, maximizes Claude + workspace.
|
||||
|
||||
```css
|
||||
.compact #panel-sidebar { display: none; }
|
||||
.compact #panel-context { display: none; }
|
||||
```
|
||||
|
||||
### Fullscreen Mode
|
||||
|
||||
Any panel can go fullscreen with `F11` (when focused). Press `Escape` to exit.
|
||||
|
||||
```css
|
||||
.fullscreen {
|
||||
dock: top;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
layer: fullscreen;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Keybindings
|
||||
|
||||
Following VSCode conventions where possible.
|
||||
|
||||
### Global
|
||||
|
||||
| Action | Binding |
|
||||
|--------|---------|
|
||||
| Command palette | `Ctrl+Shift+P` |
|
||||
| Quick open file | `Ctrl+P` |
|
||||
| Toggle left sidebar | `Ctrl+B` |
|
||||
| Toggle right sidebar | `Ctrl+Shift+B` |
|
||||
| Toggle terminal | `` Ctrl+` `` |
|
||||
| Toggle compact mode | `Ctrl+Shift+C` |
|
||||
| Fullscreen focused panel | `F11` |
|
||||
| Exit fullscreen | `Escape` |
|
||||
|
||||
### Navigation
|
||||
|
||||
| Action | Binding |
|
||||
|--------|---------|
|
||||
| Focus Claude panel | `Ctrl+1` |
|
||||
| Focus Editor | `Ctrl+2` |
|
||||
| Focus Terminal | `Ctrl+3` |
|
||||
| Focus sidebar | `Ctrl+0` |
|
||||
| Next tab (in tabbed panels) | `Ctrl+Tab` |
|
||||
| Previous tab | `Ctrl+Shift+Tab` |
|
||||
| Close current tab/editor | `Ctrl+W` |
|
||||
|
||||
### Git
|
||||
|
||||
| Action | Binding |
|
||||
|--------|---------|
|
||||
| Open Git panel | `Ctrl+Shift+G` |
|
||||
| Stage file | `Ctrl+Enter` (in git view) |
|
||||
| Unstage file | `Ctrl+Backspace` (in git view) |
|
||||
|
||||
### Search & Problems
|
||||
|
||||
| Action | Binding |
|
||||
|--------|---------|
|
||||
| Find in file | `Ctrl+F` |
|
||||
| Find in project | `Ctrl+Shift+F` |
|
||||
| Go to problems | `Ctrl+Shift+M` |
|
||||
| Next problem | `F8` |
|
||||
| Previous problem | `Shift+F8` |
|
||||
|
||||
### Editor
|
||||
|
||||
| Action | Binding |
|
||||
|--------|---------|
|
||||
| Save | `Ctrl+S` |
|
||||
| Undo | `Ctrl+Z` |
|
||||
| Redo | `Ctrl+Shift+Z` |
|
||||
| Go to line | `Ctrl+G` |
|
||||
|
||||
---
|
||||
|
||||
## Panel Communication
|
||||
|
||||
Panels should feel connected, like a normal IDE.
|
||||
|
||||
### File Navigation
|
||||
|
||||
```
|
||||
Sidebar file click
|
||||
│
|
||||
▼
|
||||
Workspace appears (if hidden)
|
||||
│
|
||||
▼
|
||||
Editor tab focused
|
||||
│
|
||||
▼
|
||||
File loaded in Editor
|
||||
```
|
||||
|
||||
### Problems/TODOs Navigation
|
||||
|
||||
```
|
||||
Click problem/todo item
|
||||
│
|
||||
▼
|
||||
Workspace appears (if hidden)
|
||||
│
|
||||
▼
|
||||
Editor tab focused
|
||||
│
|
||||
▼
|
||||
File opened at specific line
|
||||
│
|
||||
▼
|
||||
Line highlighted/scrolled into view
|
||||
```
|
||||
|
||||
### Claude Diff Flow
|
||||
|
||||
```
|
||||
Claude proposes file changes
|
||||
│
|
||||
▼
|
||||
Workspace appears
|
||||
│
|
||||
▼
|
||||
Diff tab focused
|
||||
│
|
||||
▼
|
||||
Changes displayed with Accept/Reject
|
||||
│
|
||||
├─► Accept: Apply changes, optionally close diff
|
||||
│
|
||||
└─► Reject: Discard, close diff
|
||||
```
|
||||
|
||||
### Git File Actions
|
||||
|
||||
```
|
||||
Click file in Git tab
|
||||
│
|
||||
▼
|
||||
Workspace appears
|
||||
│
|
||||
▼
|
||||
Diff tab shows unstaged changes
|
||||
│
|
||||
▼
|
||||
Stage/unstage from diff view
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Recommended Textual Widgets
|
||||
|
||||
| Component | Widget |
|
||||
|-----------|--------|
|
||||
| File browser | `DirectoryTree` |
|
||||
| Claude output | `Markdown` or `RichLog` (for streaming) |
|
||||
| Editor | `TextArea` (syntax highlighting built-in) |
|
||||
| Tabbed panels | `TabbedContent`, `TabPane` |
|
||||
| Panel switching | `ContentSwitcher` |
|
||||
| Problems/TODOs list | `ListView` with `ListItem` |
|
||||
| Git graph | `RichLog` or custom canvas widget |
|
||||
| Command palette | `CommandPalette` (built-in) |
|
||||
|
||||
### Background Tasks
|
||||
|
||||
Use Textual's `@work` decorator for:
|
||||
- Git status refresh
|
||||
- Linter execution
|
||||
- TODO scanning
|
||||
- Jira CLI calls
|
||||
|
||||
```python
|
||||
@work(thread=True)
|
||||
def refresh_git_status(self) -> None:
|
||||
result = subprocess.run(["git", "status", "--porcelain"], ...)
|
||||
self.call_from_thread(self.update_git_view, result.stdout)
|
||||
```
|
||||
|
||||
### State Management
|
||||
|
||||
**Core principle**: Hiding is not closing. All panels persist state when hidden.
|
||||
|
||||
```python
|
||||
class IDEApp(App):
|
||||
current_file: reactive[str | None] = reactive(None)
|
||||
workspace_visible: reactive[bool] = reactive(False)
|
||||
problem_count: reactive[int] = reactive(0)
|
||||
todo_count: reactive[int] = reactive(0)
|
||||
compact_mode: reactive[bool] = reactive(False)
|
||||
```
|
||||
|
||||
**Panel visibility pattern** — toggle `display`, don't destroy:
|
||||
|
||||
```python
|
||||
def toggle_workspace(self, visible: bool) -> None:
|
||||
workspace = self.query_one("#panel-workspace")
|
||||
workspace.display = visible # Retains all child state
|
||||
|
||||
# Adjust Claude panel height
|
||||
claude = self.query_one("#panel-claude")
|
||||
claude.styles.height = "40%" if visible else "100%"
|
||||
```
|
||||
|
||||
**State to preserve per panel:**
|
||||
|
||||
| Panel | Preserved State |
|
||||
|-------|-----------------|
|
||||
| Editor | Open files, cursor positions, scroll, unsaved changes, undo history |
|
||||
| Diff | Current diff content, scroll position, accept/reject state |
|
||||
| Terminal | PTY session, command history, output buffer, working directory |
|
||||
| Sidebar tabs | Scroll position, expanded/collapsed sections, selection |
|
||||
| Context tabs | Scroll position, selected item |
|
||||
| Git views | Expanded sections, selected files |
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- **Session persistence**: Remember open files, panel sizes, last git state
|
||||
- **Multiple projects**: Workspace switcher
|
||||
- **Claude history**: Browse past conversations
|
||||
- **Custom themes**: User-selectable color schemes
|
||||
- **Plugin system**: User-defined panels/integrations
|
||||
Reference in New Issue
Block a user