Generate anchor_ids for all block types, not just headings #120

Merged
forgejo_admin merged 2 commits from 119-block-anchor-ids into main 2026-03-09 03:27:12 +00:00

Summary

Non-heading blocks (paragraph, list, table, code, mermaid) now receive deterministic anchor_ids using the format {block_type}-{position} (e.g., paragraph-3, list-7), making them addressable via block API endpoints. Headings retain their existing slugified text anchors. Includes Alembic migration to backfill existing null anchor_ids and add a unique constraint on (note_id, anchor_id).

Changes

  • src/pal_e_docs/blocks/parser.py -- Generate {block_type}-{position} anchor_ids for non-heading blocks in parse_html_to_blocks(). Bare text nodes and fallback elements also get position-based anchors.
  • src/pal_e_docs/models.py -- Add UniqueConstraint("note_id", "anchor_id") to Block model. Import UniqueConstraint from SQLAlchemy.
  • alembic/versions/k1f2g3h4i5j6_add_block_anchor_ids_and_unique_constraint.py -- New migration: backfills null anchor_ids with block_type || '-' || position, then adds unique constraint on (note_id, anchor_id).
  • tests/test_block_parser.py -- Added TestAnchorIdGeneration class (12 tests) covering all block types, uniqueness, idempotency, and position-based anchors. Updated existing assertions from anchor_id is None to expected values.
  • tests/test_backfill.py -- Updated assertions for paragraph and list block anchor_ids.

Test Plan

  • All 564 tests pass (pytest tests/ -v)
  • Ruff lint and format checks pass
  • New tests verify: every block type gets non-null anchor_id, headings retain slugified anchors (no regression), anchor_ids are unique per note, anchor_ids are idempotent across re-parses
  • Migration is idempotent (UPDATE WHERE anchor_id IS NULL)

Review Checklist

  • Tests pass (564 passed)
  • Ruff lint clean
  • Ruff format clean
  • No unrelated changes
  • Migration is idempotent and safe for re-runs
  • Post-deploy: run migration, then backfill script to re-parse all notes
  • Forgejo issue: #119
  • Traceability: todo-block-anchor-ids
## Summary Non-heading blocks (paragraph, list, table, code, mermaid) now receive deterministic anchor_ids using the format `{block_type}-{position}` (e.g., `paragraph-3`, `list-7`), making them addressable via block API endpoints. Headings retain their existing slugified text anchors. Includes Alembic migration to backfill existing null anchor_ids and add a unique constraint on `(note_id, anchor_id)`. ## Changes - **`src/pal_e_docs/blocks/parser.py`** -- Generate `{block_type}-{position}` anchor_ids for non-heading blocks in `parse_html_to_blocks()`. Bare text nodes and fallback elements also get position-based anchors. - **`src/pal_e_docs/models.py`** -- Add `UniqueConstraint("note_id", "anchor_id")` to Block model. Import `UniqueConstraint` from SQLAlchemy. - **`alembic/versions/k1f2g3h4i5j6_add_block_anchor_ids_and_unique_constraint.py`** -- New migration: backfills null anchor_ids with `block_type || '-' || position`, then adds unique constraint on `(note_id, anchor_id)`. - **`tests/test_block_parser.py`** -- Added `TestAnchorIdGeneration` class (12 tests) covering all block types, uniqueness, idempotency, and position-based anchors. Updated existing assertions from `anchor_id is None` to expected values. - **`tests/test_backfill.py`** -- Updated assertions for paragraph and list block anchor_ids. ## Test Plan - All 564 tests pass (`pytest tests/ -v`) - Ruff lint and format checks pass - New tests verify: every block type gets non-null anchor_id, headings retain slugified anchors (no regression), anchor_ids are unique per note, anchor_ids are idempotent across re-parses - Migration is idempotent (UPDATE WHERE anchor_id IS NULL) ## Review Checklist - [x] Tests pass (564 passed) - [x] Ruff lint clean - [x] Ruff format clean - [x] No unrelated changes - [x] Migration is idempotent and safe for re-runs - [ ] Post-deploy: run migration, then backfill script to re-parse all notes ## Related - Forgejo issue: #119 - Traceability: `todo-block-anchor-ids`
Generate anchor_ids for all block types, not just headings
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
9fca477dd7
Non-heading blocks (paragraph, list, table, code, mermaid) now receive
deterministic anchor_ids using the format {block_type}-{position}, making
them addressable via the block API endpoints. Headings retain their
existing slugified text anchors.

Includes Alembic migration to backfill existing null anchor_ids and add
a unique constraint on (note_id, anchor_id).

Closes #119

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix create_block to auto-generate anchor_id for non-heading blocks
All checks were successful
ci/woodpecker/pr/woodpecker Pipeline was successful
be49726b5b
The create_block API endpoint only auto-generated anchor_ids for heading
blocks, leaving non-heading blocks with anchor_id=None. This was
inconsistent with the parser's guarantee that all blocks have anchors.

Non-heading blocks now get anchor_ids using the {block_type}-{position}
pattern with uniqueness checking, matching the parser convention.

Closes #119

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
forgejo_admin deleted branch 119-block-anchor-ids 2026-03-09 03:27:12 +00:00
Sign in to join this conversation.
No description provided.