Skip to content

Commit 7ecc129

Browse files
committed
initial commit
0 parents  commit 7ecc129

27 files changed

+5075
-0
lines changed

CLAUDE.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
This is a Python TUI (Terminal User Interface) application for editing blog content stored in PostgreSQL. Built with [Textual](https://textual.textualize.io/), it allows users to browse, search, create, edit, and delete posts across multiple collections (blog, notes, microblog).
8+
9+
## Commands
10+
11+
### Development Setup
12+
```bash
13+
# Install dependencies with uv
14+
uv sync
15+
16+
# Create environment file from template
17+
cp .env.example .env
18+
# Edit .env with your PostgreSQL CONNECTION_STRING
19+
```
20+
21+
### Running the Application
22+
```bash
23+
# Using uv
24+
uv run content-editor
25+
26+
# Or with Python directly (if virtual environment is activated)
27+
.venv/bin/content-editor
28+
```
29+
30+
### Environment Configuration
31+
The application requires the `CONNECTION_STRING` environment variable:
32+
```bash
33+
# Option 1: Use .env file
34+
echo 'CONNECTION_STRING=postgresql://user:password@localhost:5432/database' > .env
35+
36+
# Option 2: Set environment variable
37+
export CONNECTION_STRING="postgresql://user:password@localhost:5432/database"
38+
uv run content-editor
39+
40+
# Option 3: Inline (one command)
41+
CONNECTION_STRING="postgresql://..." uv run content-editor
42+
```
43+
44+
## Architecture
45+
46+
### Core Components
47+
48+
**Database Layer** (`content_editor/db.py`)
49+
- `DatabaseManager` class handles all PostgreSQL operations
50+
- Supports three collections: `blog`, `notes`, `microblog` (switchable at runtime)
51+
- Uses dynamic SQL with proper parameterization to prevent SQL injection
52+
- Junction tables: `blog_tags`, `notes_tags`, `microblog_tags` (unified `tags` table)
53+
- Key methods: `get_posts()`, `get_post()`, `create_post()`, `update_post()`, `delete_post()`
54+
55+
**UI Layer** (`content_editor/main.py`)
56+
- `ContentEditorApp` - main Textual application managing the TUI
57+
- Organizes layout into three sections: preview (markdown), table (posts), sidebar (tags)
58+
- Implements pagination (50 posts per page) for large collections
59+
- Uses async workers (`@work` decorator) to fetch full post content without blocking UI
60+
- Tag sidebar shows post counts per tag
61+
62+
**UI Screens** (`content_editor/ui.py`)
63+
- Modal screens for search, create/edit posts, delete confirmation
64+
- Collection selection screen for switching between blog/notes/microblog
65+
- Dynamic field visibility based on collection (microblog has no title/description)
66+
67+
### Multi-Collection Architecture
68+
69+
The application dynamically switches between three collections stored in the same PostgreSQL database:
70+
- **Blog**: Full-featured posts with title, description, content
71+
- **Notes**: Same schema as blog, separate data
72+
- **Microblog**: Content-only posts (no title/description fields)
73+
74+
Each collection has:
75+
- Its own table (`blog`, `notes`, `microblog`)
76+
- Its own junction table for tags (`blog_tags`, `notes_tags`, `microblog_tags`)
77+
- Shared unified `tags` table across all collections
78+
79+
**Key Collection-Specific Logic:**
80+
- Database layer handles schema differences (microblog queries don't select title/description)
81+
- UI table columns adjust dynamically (Blog/Notes show title; Microblog shows content preview)
82+
- Create/Edit screens hide title/description fields for microblog
83+
- Field validation accounts for collection-specific requirements
84+
85+
### Async Architecture
86+
87+
- Full post content loads asynchronously using Textual's `@work` decorator
88+
- UI shows post preview while fetching full content in background worker thread
89+
- Exclusive workers prevent race conditions during concurrent operations
90+
- Post refreshes maintain scroll position and selection
91+
92+
## Project Dependencies
93+
94+
- **textual>=1.0.0** - TUI framework
95+
- **psycopg[binary]>=3.0.0** - PostgreSQL adapter
96+
- **pyyaml>=6.0** - YAML parsing (for potential config)
97+
- **python-dotenv>=1.0.0** - Environment variable management
98+
- **Python 3.10+** - Required
99+
100+
## Code Organization
101+
102+
```
103+
content_editor/
104+
├── __init__.py # Package initialization
105+
├── main.py # ContentEditorApp (main TUI logic, styling, keybindings)
106+
├── db.py # DatabaseManager (all database operations)
107+
└── ui.py # Screen classes (modals, forms, dialogs)
108+
109+
Project Root/
110+
├── pyproject.toml # Project metadata and dependencies
111+
├── README.md # User-facing documentation
112+
├── QUICKSTART.md # Quick setup guide
113+
└── tui-collection.md # Collection switcher implementation details
114+
```
115+
116+
## Keyboard Shortcuts
117+
118+
| Key | Action |
119+
|-----|--------|
120+
| `q` | Quit application |
121+
| `e` | Edit selected post |
122+
| `n` | Create new post |
123+
| `d` | Delete selected post |
124+
| `/` | Search posts |
125+
| `c` | Change collection |
126+
| `r` | Reset view |
127+
| `PageDown` | Next page |
128+
| `PageUp` | Previous page |
129+
| `Ctrl+S` | Save changes (in edit/create screens) |
130+
| `Escape` | Cancel/Go back |
131+
132+
## Key Implementation Details
133+
134+
### Database Queries
135+
- All queries use parameterized statements with `psycopg.sql` module for safety
136+
- Junction tables use dynamic SQL identifiers based on current collection
137+
- Tag operations use aggregate queries to avoid N+1 problems (see `get_all_tags_with_counts()`)
138+
139+
### UI Rendering
140+
- `DataTable` shows posts with collection-aware columns
141+
- `ListView` shows tags with post count in sidebar
142+
- `MarkdownViewer` renders post content in preview panel
143+
- Async fetch prevents UI blocking during database queries
144+
145+
### Error Handling
146+
- Database connection errors display in subtitle
147+
- Operation failures show toast notifications
148+
- All database operations wrapped in try/finally to clean up cursor resources
149+
150+
### Pagination
151+
- Default page size: 50 posts per page
152+
- Search term preserved across page navigation
153+
- Current page tracked in app state
154+
155+
## Common Development Tasks
156+
157+
### Adding a New Feature
158+
1. Identify if it involves database changes (modify `db.py`) or UI changes (modify `main.py`/`ui.py`)
159+
2. For database: Add method to `DatabaseManager` and test with all collections
160+
3. For UI: Update keybindings in `BINDINGS` list and implement action method
161+
4. Test with at least two collections to ensure collection-agnostic code
162+
163+
### Debugging Database Issues
164+
- Check `CONNECTION_STRING` is set and PostgreSQL is running
165+
- Use `psql` to verify table structure and data
166+
- Review `tui-collection.md` for schema documentation
167+
- Check cursor cleanup in finally blocks (all queries should close cursors)
168+
169+
### Testing Collection Switching
170+
- Manually switch between collections (`c` key)
171+
- Verify posts load for each collection
172+
- Verify tag sidebar updates appropriately
173+
- Create/edit/delete in each collection to ensure proper table/junction table usage
174+
175+
### Performance Optimization
176+
- Current async architecture prevents UI blocking
177+
- Tag sidebar query uses aggregate (`COUNT`) to avoid N+1
178+
- Full post content fetched only when selected (lazy loading)
179+
- Pagination loads 50 posts at a time, not all posts
180+
181+
## Important Notes
182+
183+
- **MCP Postgres Server**: Disabled in settings (`content_editor/.claude/settings.local.json`) - Claude Code skips the postgres MCP server integration
184+
- **No Tests**: This project currently has no automated test suite; testing is manual
185+
- **Microblog Edge Case**: Microblog collection has different schema (no title/description) - ensure all new code handles this
186+
- **SQL Injection Prevention**: Always use `psycopg.sql.SQL()` with identifiers for table/column names, parameterized queries for values

0 commit comments

Comments
 (0)