## Implementation Plan: `@note` Tag → Personal Note Overview ### Approach - **Option A** – Separate `/notes` page. Clean separation from manual bookmarks. New navigation entry “Notes”. No changes to existing bookmark listing. - **Source column** – Add `source` column to `bookmarks` table (`"manual"` for existing, `"note"` for `@note`-tagged items). - **Comment handling** – When `@note` is found in a comment body, bookmark the *parent post* (or gist/news/project), not the comment itself. ### Step-by-step Changes 1. **Database migration** – Add `source` column (VARCHAR, default `'manual'`) to `bookmarks` table in `devplacepy/database/schema.py`. Create index `idx_bookmarks_source` on `source`. Add a migration script or just apply via raw SQL (consistent with project’s migration practice – check `alembic` usage or direct `execute` in `schema.py`). 2. **Utility function** – Add `has_note(text: str) -> bool` to `devplacepy/utils/text.py`, using `re` pattern `@note` (case-insensitive, word boundary). Export it from `devplacepy/utils/__init__.py`. 3. **Content creation hooks** – In `devplacepy/content.py`: - In `create_content_item()` near line 194 (after `create_content_item()` and before `schedule_modification`), add: ```python if has_note(description): # or whichever text field holds the content body set_bookmark( user_id=user['id'], target_type=table_name.rstrip('s'), # e.g. 'post', 'gist' target_id=uid, source='note' ) ``` - In `create_comment_record()` near line 361 (after `create_comment_record()` and before `schedule_modification`), add: ```python if has_note(body): # Determine parent content and bookmark it parent = get_parent_by_comment_uid(comment_uid) # already exists via call at ~L356 set_bookmark( user_id=user['id'], target_type=parent['type'], target_id=parent['uid'], source='note' ) ``` - If `set_bookmark` does not already accept `source`, modify it (likely in `devplacepy/database/bookmarks.py` or similar) to accept an optional `source` kwarg. 4. **New router** – Create `devplacepy/routers/notes.py`: - `GET /notes` – Fetch bookmarks where `source='note'` for the current user. Use same pagination as `/bookmarks/saved`. - Reuse the same serialization helpers (`SavedItemOut` etc.) but filter on `source`. 5. **New template** – Create `devplacepy/templates/notes.html`. Mirror `saved.html` but with heading “Notes” and note icon. Ensure all links and filtering work. 6. **Schema update** – In `devplacepy/schemas/listings.py`, add `NotesOut` / `NotesItemOut` (or alias to `SavedItemOut` with a `source` field). Keep consistent with existing pattern. 7. **Router registration** – In `devplacepy/main.py`, mount `notes_router` at `'/notes'` (or `/bookmarks/notes` – decide consistent URL namespace; use `/notes` for clarity). 8. **Navigation** – In `devplacepy/templates/base.html`, add “Notes” link in the user dropdown (or alongside Bookmarks). Use `url_for('notes.list')`. 9. **AI agent action** – In `devplacepy/services/devii/actions/catalog/engagement.py`, add `list_notes` action similar to `list_bookmarks`, returning `@note` bookmarks. 10. **Tests** – Add directories and files: - `tests/e2e/notes/` – Playwright e2e test copying `saved.py` pattern: signup user, create a post with `@note`, verify note appears in `/notes`. - `tests/api/notes/` – API test copying `saved.py` pattern: create content, verify JSON response includes note with `source='note'`. 11. **Environment workaround** – The project’s `pip install` commands fail in this environment due to `PEP 668`. Prepend `PIP_REQUIRE_VIRTUALENV=0` to the install commands in any CI/script, or instruct the developer to do so locally. The test/lint commands in the plan should be run as: ```bash PIP_REQUIRE_VIRTUALENV=0 pip install -e '.[dev]' -q && make test-unit PIP_REQUIRE_VIRTUALENV=0 pip install -q ruff && ruff check . ``` ### Definition of Done - [ ] The `bookmarks` table has a `source` column with appropriate index; migration is applied. - [ ] `has_note()` exists in `devplacepy/utils/text.py` and is exported. - [ ] In `create_content_item()`, a post/gist/project/rant containing `@note` in its body is automatically bookmarked with `source='note'` for the author. - [ ] In `create_comment_record()`, a comment containing `@note` bookmarks its parent content (not the comment) with `source='note'`. - [ ] `GET /notes` returns a paginated list of the current user’s `@note`-tagged items, rendered from `notes.html`. - [ ] Navigation includes a “Notes” link that points to `/notes`. - [ ] AI agent can list notes via `list_notes` action. - [ ] E2E test: new user creates a post with `@note`, visits `/notes`, sees that post listed. - [ ] API test: create content with `@note`, confirm response includes the bookmark entry with `source='note'`. - [ ] Both test and lint commands pass without error: ```bash PIP_REQUIRE_VIRTUALENV=0 pip install -e '.[dev]' -q && make test-unit PIP_REQUIRE_VIRTUALENV=0 pip install -q ruff && ruff check . ```