feat(sessions): checkpoint system with diff-based storage #165
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
Context
Sub-issue of #125 (Context-Aware Part Subscription System).
A checkpoint is a lightweight working snapshot pushed on upward context transitions (exiting sketch, leaving body editing, etc.). Checkpoints are distinct from formal revisions — they are ephemeral, TTL-limited, and serve as the bridge between edit session release and subscriber notification.
Key Design: Diff-Based Storage
For
.kcfiles containing XML parametric source, checkpoints should store diffs rather than full file copies when possible. The client computes the diff against its base revision; the server stores the patch. This dramatically reduces storage for incremental edits.Full file fallback for binary content or when diff is unavailable.
1. Database Migration
Create
migrations/024_checkpoints.sql:storage_modeindicates whetherfile_keypoints to a full file or a diff patch.base_revisionrecords which revision the diff is against (needed to reconstruct).2. Storage Layout
3. Database Layer
Create
internal/db/checkpoints.go:Methods:
Create(ctx, checkpoint)— insert, enforce max-per-item-per-userGetLatestForItem(ctx, itemID)— most recent checkpointGetLatestForItemUser(ctx, itemID, userID)— most recent by this userListForItem(ctx, itemID)— all active checkpointsDelete(ctx, id)— single deleteDeleteForItemUser(ctx, itemID, userID)— cleanup on formal revision commitDeleteExpired(ctx)— TTL sweeper, returns file_keys for filesystem cleanup4. API Endpoints
Create checkpoint handlers:
/api/items/{pn}/checkpoints/api/items/{pn}/checkpoints/api/items/{pn}/checkpoints/latest/api/items/{pn}/checkpoints/{id}Push Request
Multipart or two-step:
Option A (inline for small diffs):
With the diff/file content as a separate part or pre-uploaded.
Option B (pre-upload):
POST /api/items/{pn}/checkpoints/upload(returns file_key)Push Processing
dagpresent: callSyncFeatureTree(), mark dirty nodes, emitdag.updatedsession_idpresent: release that edit sessioncheckpoint_max_per_itemper user (delete oldest)sketch_close→ no subscription events (too granular)body_close,assembly_close,manual→ emit tolatestsubscribers (future)autosave→ store only, no eventsPush Response
5. Cleanup Sweeper
Background goroutine (can share the session sweeper tick):
DELETE FROM checkpoints WHERE expires_at < now()6. Context-to-Action Mapping
sketch_closebody_closelatestsubsassembly_closelatestsubsmanuallatestsubsautosaveAcceptance Criteria
checkpointstablePOST /checkpointsstores file or diff to filesystemstorage_mode: "diff"stores a patch file;"full"stores complete filesession_idatomically releases the edit sessiondagrunsSyncFeatureTree()and marks dirtycheckpoint_max_per_itemenforced per user (oldest evicted)GET /checkpoints/latestreturns most recent with download pathDepends On
Part Of
#125