fix(editor): repair block identity for template pages (duplicate/reordered lines)
Template-created pages (weekly report, project doc, meeting notes, to-do list, ...) were persisted without block ids. The editor assigned ids client-side, but the realtime room loaded the raw id-less content and sent it back on 'sync'; applySync then merged id-less server blocks with local blocks, producing data-bid='undefined' collisions and duplicated/shuffled lines as soon as the user edited. Editing an empty page was unaffected because the server state was empty. Fixes: - board.use_page_template: materialize unique block ids (recursively) when instantiating built-in or user templates. - realtime_server: unique block_id() (uuid) + ensure_block_ids() on room load and on insert ops. - editor: recursive ensureBlockIds() in init; gtTok() now restores [[fddate:...]] tokens (date chips survived as labels before). - realtime client: applySync() normalizes ids, no longer appends unknown local blocks (duplication), and keeps local content when server is empty. Tests: pytest (templates + realtime) and Playwright e2e covering to-do list, weekly report date chip, Enter ordering and legacy id-less repair.
This commit is contained in:
+22
-1
@@ -42,6 +42,25 @@ def _ensure_page_editable(conn, page_id: int, user: dict | None) -> None:
|
||||
raise HTTPException(423, "Page is locked — only the owner of the lock or an admin can edit")
|
||||
|
||||
|
||||
def _ensure_block_ids(blocks) -> None:
|
||||
"""Assign unique ids to blocks missing one, recursively.
|
||||
|
||||
Built-in page templates ship without ids (the editor used to assign them
|
||||
client-side only). Without persisted ids, the realtime layer and the editor
|
||||
disagree on block identity, which duplicated lines / shuffled blocks when
|
||||
editing a template-created page. We now materialize ids at creation time.
|
||||
"""
|
||||
import uuid
|
||||
if not isinstance(blocks, list):
|
||||
return
|
||||
for b in blocks:
|
||||
if isinstance(b, dict):
|
||||
if not b.get("id"):
|
||||
b["id"] = "b" + uuid.uuid4().hex[:12]
|
||||
if isinstance(b.get("children"), list):
|
||||
_ensure_block_ids(b["children"])
|
||||
|
||||
|
||||
@router.get("/api/wiki/pages")
|
||||
async def wiki_page_search(request: Request, q: str = Query(default="")):
|
||||
"""v5.11.0: page picker for [[ wiki-links. Fuzzy-ish title search across
|
||||
@@ -274,9 +293,11 @@ async def use_page_template(request: Request, template_id: int):
|
||||
if body_ws:
|
||||
ws_key = body_ws
|
||||
try:
|
||||
json.loads(blocks_json)
|
||||
parsed_blocks = json.loads(blocks_json)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
raise HTTPException(500, "Template content corrupted") from None
|
||||
_ensure_block_ids(parsed_blocks)
|
||||
blocks_json = json.dumps(parsed_blocks)
|
||||
with get_conn() as conn:
|
||||
if ws_id is not None:
|
||||
next_order = conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user