## Implementation Plan: Add Devlog Timeline of Related Posts to Project Page ### Changes Required 1. **`database/schema.py`** - Add index on `posts.project_uid` in `init_db()` after the table creation block: ```python cursor.execute("CREATE INDEX IF NOT EXISTS idx_posts_project_uid ON posts(project_uid)") ``` - No backfill needed — `project_uid` column already exists with `NULL` default for old posts. 2. **`schemas/listings.py`** - In `ProjectDetailOut`, add a field: ```python project_posts: list[PostSummaryOut] = [] ``` - Import `PostSummaryOut` from `schemas/content.py`. 3. **`routers/projects/index.py`** - In `project_detail()` function, after fetching the project and existing fields, add a query for posts attached to this project: ```python from routers.posts import enrich_items rows = cursor.execute( "SELECT * FROM posts WHERE project_uid = ? AND deleted_at IS NULL ORDER BY created_at DESC LIMIT 50", (project.uid,) ).fetchall() posts = [PostInDB(**row) for row in rows] enriched = enrich_items(cursor, posts) project_dict["project_posts"] = enriched # already serialized ``` - Ensure `ProjectInDB` is imported (likely already present). 4. **`templates/project_detail.html`** - Add a tab button in the tab bar (inside `
`): ```html ``` - Add the tab content section after the other tab contents: ```html

Devlog

{% if project_posts %} {% else %}

No posts yet for this project.

{% endif %}
``` - Make sure the template receives `project_posts` (already added in router step). 5. **`static/css/projects.css`** - Add styling for the devlog tab and post list: ```css .tab-content#devlog .post-list { list-style: none; padding: 0; } .tab-content#devlog .post-list li { padding: 0.5rem 0; border-bottom: 1px solid #eee; } .tab-content#devlog .post-list li:last-child { border-bottom: none; } ``` 6. **`tests/api/projects/index.py`** (create if not exists) - Add test: ```python def test_project_detail_returns_related_posts(client, db): # Create project proj = create_project() # Create post attached to project post = create_post(project_uid=proj.uid) response = client.get(f"/projects/{proj.slug}") assert response.status_code == 200 data = response.json() assert "project_posts" in data assert len(data["project_posts"]) == 1 assert data["project_posts"][0]["uid"] == post.uid ``` - Use existing helpers like `create_project`, `create_post` from conftest. --- ### Definition of Done - [ ] All code changes described above are applied and committed. - [ ] New index on `posts.project_uid` exists in database schema. - [ ] `ProjectDetailOut` schema includes `project_posts` field as a list of `PostSummaryOut`. - [ ] Project detail API endpoint returns `project_posts` array when posts are attached. - [ ] Project page HTML shows a "Devlog" tab with the post list (or "No posts yet" message). - [ ] Tab styling is consistent with existing tabs. - [ ] The following test command passes: ``` pip install -e '.[dev]' -q && python -m pytest tests/api/projects/index.py ``` - [ ] Manual verification: on a project with attached posts, the Devlog tab appears and displays posts in reverse chronological order; other tabs (About, Followers) remain functional. - [ ] No regression in unrelated tests (dialog/gist failures are pre-existing and not caused by these changes).