feat(workspace): tabula-rasa outliner architecture, SQLite blocks/block_tags schema & echo-protected live room sync
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m12s

This commit is contained in:
sandrodach committed 2026-10-26 21:14:06 +01:00
1 parent f3b3f07e04
commit 86867b0d6f
17 files changed
+3409 -7178

No files matched your search

+6 -84
View File
@@ -1,7 +1,6 @@
package ws
import (
"encoding/base64"
"encoding/json"
"fmt"
"log"
@@ -47,10 +46,9 @@ type docLockItem struct {
expiresAt time.Time
}
type CRDTStore interface {
GetDocumentCRDTState(docID string) ([]byte, [][]byte, error)
AppendDocumentCRDTUpdate(docID string, update []byte, userID string) error
SaveDocumentCRDTState(docID string, stateBlob []byte, userID string) error
type DocStore interface {
LoadDocumentState(docID string) (string, []domain.DocumentBlock, error)
FlushDocumentState(docID string, title string, blocks []domain.DocumentBlock) error
}
type Hub struct {
@@ -63,7 +61,7 @@ type Hub struct {
sseRooms map[string]map[*SSEClient]bool
locks map[string]*docLockItem
docRooms map[string]*DocRoom
crdtStore CRDTStore
docStore DocStore
}
func NewHub() *Hub {
@@ -81,10 +79,10 @@ func NewHub() *Hub {
return h
}
func (h *Hub) SetCRDTStore(store CRDTStore) {
func (h *Hub) SetDocStore(store DocStore) {
h.mu.Lock()
defer h.mu.Unlock()
h.crdtStore = store
h.docStore = store
}
func (h *Hub) Register(c *Client) {
@@ -688,39 +686,6 @@ func (h *Hub) ServeSSE(w http.ResponseWriter, r *http.Request, roomID, userID st
fmt.Fprintf(w, "data: %s\n\n", connectedMsg)
flusher.Flush()
// If initial rooms had doc rooms, send CRDT_INIT
if h.crdtStore != nil {
for rID := range client.subscribedRooms {
if strings.HasPrefix(rID, "doc:") {
docID := strings.TrimPrefix(rID, "doc:")
stateBlob, updates, err := h.crdtStore.GetDocumentCRDTState(docID)
if err == nil && (len(stateBlob) > 0 || len(updates) > 0) {
var b64Updates []string
for _, u := range updates {
b64Updates = append(b64Updates, base64.StdEncoding.EncodeToString(u))
}
b64State := ""
if len(stateBlob) > 0 {
b64State = base64.StdEncoding.EncodeToString(stateBlob)
}
initMsg := domain.WSMessage{
Event: "CRDT_INIT",
Payload: map[string]interface{}{
"docId": docID,
"state": b64State,
"updates": b64Updates,
},
Timestamp: domain.NowISO(),
}
if initBytes, err := json.Marshal(initMsg); err == nil {
fmt.Fprintf(w, "data: %s\n\n", initBytes)
flusher.Flush()
}
}
}
}
}
ticker := time.NewTicker(15 * time.Second)
defer ticker.Stop()
@@ -811,49 +776,6 @@ func (c *Client) readPump() {
if c.hub.ReleaseDocLock(docID, c.userID, false) {
c.hub.BroadcastDocEvent(docID, "DOC_UNLOCKED", map[string]string{"docId": docID, "doc_id": docID})
}
case "CRDT_JOIN":
c.hub.JoinRoom(c, "doc:"+docID)
room := c.hub.GetOrCreateDocRoom(docID)
b64State, b64Updates := room.GetInitialSnapshot()
if len(b64State) > 0 || len(b64Updates) > 0 {
c.SendEvent("CRDT_INIT", map[string]interface{}{
"docId": docID,
"state": b64State,
"updates": b64Updates,
})
}
case "CRDT_UPDATE":
var p struct {
Update string `json:"update"`
}
_ = json.Unmarshal(incoming.Payload, &p)
if p.Update != "" {
if raw, err := base64.StdEncoding.DecodeString(p.Update); err == nil && len(raw) > 0 {
room := c.hub.GetOrCreateDocRoom(docID)
room.ApplyUpdate(raw, c.userID, nil, nil)
}
}
case "CRDT_COMPACT":
var p struct {
State string `json:"state"`
}
_ = json.Unmarshal(incoming.Payload, &p)
if p.State != "" {
if raw, err := base64.StdEncoding.DecodeString(p.State); err == nil && len(raw) > 0 {
room := c.hub.GetOrCreateDocRoom(docID)
room.CompactState(raw, c.userID)
}
}
case "CRDT_AWARENESS":
c.hub.BroadcastRoom("doc:"+docID, "CRDT_AWARENESS", incoming.Payload, c)
room := c.hub.GetOrCreateDocRoom(docID)
room.BroadcastAwareness(incoming.Payload, c.userID, nil, nil)
case "CRDT_LEAVE":
c.hub.LeaveRoom(c, "doc:"+docID)
c.hub.BroadcastRoom("doc:"+docID, "CRDT_AWARENESS_LEAVE", map[string]interface{}{
"docId": docID,
"userId": c.userID,
}, c)
}
}
}