7e-1: Source-of-truth cutover -- note writes parse to blocks first #110
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set.
Reference
ldraney/pal-e-api!110
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "109-7e-1-source-of-truth-cutover-note-writes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Make blocks the canonical representation for note content.
create_note()andupdate_note()now parse HTML to blocks viaparse_html_to_blocks(), store them, then recompilehtml_contentfrom blocks. This ensures block reads (get_section, get_toc, list_blocks) are never stale after a note write via the original API.Changes
src/pal_e_docs/blocks/sync.py(new) -- Sharedrecompile()andparse_and_store_blocks()helpers extracted from the duplicated_recompile()logicsrc/pal_e_docs/blocks/__init__.py-- Export newrecompileandparse_and_store_blockssrc/pal_e_docs/routes/blocks.py-- Replace local_recompile()with sharedrecompile()import; removedhashlibandCompiledPageimports no longer neededsrc/pal_e_docs/routes/notes.py-- Wireparse_and_store_blocks()intocreate_note()andupdate_note()whenhtml_contentis provided; revision stores compiled outputtests/test_note_block_sync.py(new) -- 14 tests covering create, update, metadata-only update, empty content, round-trip consistency, TOC/section integrationTest Plan
test_note_block_sync.pyall passpytest tests/ -vto verifyReview Checklist
_recompilelogic, extracted to shared module)Related
plan-2026-02-26-tf-modularize-postgresPR #110 Review
BLOCKERS
None.
NITS
create_notesetshtml_contenttwice on the Note object. Innotes.pyline 273, the Note is constructed withhtml_content=body.html_content(the raw input). Then at line 287,parse_and_store_blocks()overwritesnote.html_contentwith the compiled output. This works correctly -- the compiled version wins before commit -- but the initial assignment is technically wasted work. A minor clarity improvement would be to sethtml_content=""(orNone) at construction and letparse_and_store_blocksbe the sole writer. Not blocking since the end result is correct.Test helper sessions are opened outside the request lifecycle. The
_count_blocks(),_get_blocks(), and_get_compiled_page()helpers intest_note_block_sync.pyopen their ownTestingSessionLocal()sessions. This works because the test DB is shared, but it relies on the commit having completed before the helper runs. This is fine for these tests (the client calls do commit), but worth noting for future test authors. Not blocking._get_compiled_pagereturns a detached ORM object. The helper closes the session after returning theCompiledPageinstance. Accessing lazy-loaded relationships on the returned object would fail. This is fine for current usage (onlycontent_hash,toc_json,htmlare accessed, all column attributes), but could surprise future test authors. Not blocking.SOP COMPLIANCE
109-7e-1-source-of-truth-cutover-note-writesreferences issue #109)plan-2026-02-26-tf-modularize-postgres)test_note_block_sync.py, reported 503 total passing)Code Quality Notes
_recompile()duplication inroutes/blocks.pyis cleanly extracted toblocks/sync.pyasrecompile(). The newparse_and_store_blocks()composes parse, delete, insert, and recompile in the right order.create_noteandupdate_notenow storenote.html_content(compiled) in revisions instead ofbody.html_content(raw). This is the correct behavior for source-of-truth cutover -- revisions should reflect what blocks actually produce.if body.html_content:(falsy check) increate_noteskips block processing for empty strings, which is the right behavior. Inupdate_note,if body.html_content is not None:correctly triggers block processing even for empty string (to clear blocks), which is also correct and tested.id) beforeparse_and_store_blocksruns, which needsnote.idfor block insertion. Therecompileat the end flushes again. Commit happens in the route after all operations complete.VERDICT: APPROVED