Pages form a tree via a nullable parent_id self-relation (SetNull
backstop; the real trash/purge semantics follow with #107). Slugs and
URLs stay flat and pond-unique, so moving a page never breaks links.
- Shared: generic parent-id tree helpers in tree.ts (labels re-export
them; buildLabelTree keeps its name-sorted behavior), MAX_PAGE_DEPTH=6,
parentId on PageView, createPageInputSchema.parentId (nullish),
repositionPageInputSchema.parentId (optional; absent = keep parent).
- API: create validates the parent (same pond, live, depth);
PATCH /pages/:id/position reparents atomically with the placement,
rejecting cycles (page_cycle) and depth violations
(page_depth_exceeded); GET /ponds/:id/pages nulls parentId when the
caller may not read the parent, so hidden page ids never leak.
- New error codes translated de+en; hierarchy.db.test.ts covers create,
404s, depth, cycle, atomic reparent, and the permission nulling.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>