update README to show idiomatic usage examples
1 files changed,  +220, -376
M README.md
+220, -376
  1@@ -2,207 +2,93 @@
  2 
  3 Go bindings for the Rust [y-crdt](https://github.com/y-crdt/y-crdt) library, providing CRDT (Conflict-free Replicated Data Types) functionality for building collaborative applications.
  4 
  5-## Overview
  6+## Features
  7 
  8-This library provides Go bindings to y-crdt's Yjs-compatible CRDT implementation via CGO. It enables real-time collaborative editing with support for:
  9+- **Yjs CRDT Bindings** - Go bindings to the Rust y-crdt library for manipulating Yjs documents (Text, Array, Map, XML types)
 10+- **y-sweet Integration** - Sync documents in real-time with [jamsocket/y-sweet](https://github.com/jamsocket/y-sweet) servers via WebSocket
 11+- **Document Hooks** - React to changes with `OnUpdate` callbacks for document modifications
 12+- **Proxy Hooks** - Intercept and act on connections when using ygo as a WebSocket proxy to y-sweet
 13 
 14-- **Documents** - The core unit of collaborative state
 15-- **Text** - Rich text with formatting and attributes
 16-- **Arrays** - Ordered collections with move operations
 17-- **Maps** - Key-value stores with nested types
 18-- **XML** - XML fragment and element types
 19-- **Transactions** - Atomic read/write operations
 20-- **Updates** - State synchronization between peers
 21-- **Undo/Redo** - Operation history management
 22-
 23-**Resource Management:** All types (`Doc`, `Text`, `Array`, `Map`, `XmlFragment`, etc.) require explicit cleanup. Always use `defer obj.Destroy()` when creating these objects to prevent memory leaks. See the Quick Start example below.
 24-
 25-## Installation
 26-
 27-```bash
 28-go get github.com/BTBurke/ygo
 29-```
 30-
 31-## Building
 32-
 33-### Prerequisites
 34+## Prerequisites
 35 
 36 - Go 1.22 or later
 37 - C compiler (gcc or clang)
 38 
 39-The library includes pre-built static libraries for Linux and FreeBSD on amd64 and arm64. No Rust toolchain is required for normal use.
 40+The library uses Cgo and includes pre-built static libraries for Linux and FreeBSD on amd64 and arm64. No Rust toolchain is required for normal use.
 41 
 42-### Linux / FreeBSD
 43+## Creating Documents
 44 
 45-**Simple usage (using pre-built libraries):**
 46-
 47-```bash
 48-go get github.com/BTBurke/ygo
 49-go build .
 50-```
 51+### New Document
 52 
 53-**For static linking (recommended for distribution):**
 54+```go
 55+doc, _ := ygo.NewDoc()
 56+// Cgo bindings require manually freeing memory (sorry!)
 57+defer doc.Destroy()
 58 
 59-```bash
 60-CGO_ENABLED=1 go build -ldflags '-linkmode external -extldflags "-static"' .
 61-```
 62+txt, _ := doc.GetText("content")
 63+defer txt.Destroy()
 64 
 65-For a completely static binary (no dynamic library dependencies):
 66-```bash
 67-CGO_ENABLED=1 go build -ldflags '-linkmode external -extldflags "-static -ldl -lm"' .
 68+_ = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
 69+    txt.Insert(txn, 0, "Hello, world!")
 70+    return nil
 71+})
 72 ```
 73 
 74-### Building from Source (optional)
 75-
 76-If you need to build the Rust library from source (e.g., for a different platform or to apply patches):
 77-
 78-1. **Install Rust toolchain:**
 79-   ```bash
 80-   # TODO: need to install devbox or have rustup available
 81-   devbox shell
 82-   ```
 83-
 84-2. **Clone with submodules and build:**
 85-   ```bash
 86-   git clone --recursive https://github.com/BTBurke/ygo
 87-   cd ygo
 88-   make libyrs-local  # Builds for current platform, overwrites prebuilt
 89-   go build .
 90-   ```
 91-
 92-3. **Cross-compile for all supported platforms:**
 93-   ```bash
 94-   make libyrs-cross  # Builds for linux-amd64, freebsd-amd64
 95-   ```
 96-
 97-## Quick Start
 98+### Create a document from a Yjs V1 Update
 99 
100 ```go
101-package main
102-
103-import (
104-    "fmt"
105-    "log"
106-
107-    "github.com/BTBurke/ygo"
108-)
109+data, _ := os.ReadFile("document.yjs")
110 
111-func main() {
112-    // Create a new document
113-    doc, err := ygo.NewDoc()
114-    if err != nil {
115-        log.Fatal(err)
116-    }
117-    defer doc.Destroy()
118+doc, _ := ygo.NewDoc()
119+defer doc.Destroy()
120 
121-    // Get or create a text field
122-    txt, err := doc.GetText("content")
123-    if err != nil {
124-        log.Fatal(err)
125-    }
126-    defer txt.Destroy()
127+_ = doc.UnmarshalBinary(data)
128+```
129 
130-    // Insert text within a transaction
131-    err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
132-        txt.Insert(txn, 0, "Hello, collaborative world!")
133-        return nil
134-    })
135-    if err != nil {
136-        log.Fatal(err)
137-    }
138+### Create a document from the y-sweet format (similar to Yjs V1 format, but tracks changes)
139 
140-    // Read the text back
141-    var content string
142-    err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
143-        var err error
144-        content, err = txt.String(txn)
145-        return err
146-    })
147-    if err != nil {
148-        log.Fatal(err)
149-    }
150+```go
151+// From file
152+doc, _ := ygo.NewDocFromYSweetFile("data.ysweet")
153+defer doc.Destroy()
154 
155-    fmt.Println(content) // "Hello, collaborative world!"
156-}
157+// From reader
158+doc, _ := ygo.NewDocFromYSweet(reader)
159+defer doc.Destroy()
160 ```
161 
162-## Convenience Features
163+## Transactions
164 
165-### Typed Map Getters
166+When you want to make changes to a Y document, we offer two APIs:
167 
168-Get typed values from maps without manual type checking:
169+### Callback Pattern (Atomic)
170 
171-```go
172-// Instead of verbose Output handling:
173-// out, _ := m.Get(txn, "name")
174-// defer out.Destroy()
175-// str, _ := out.String()
176-
177-// Use convenience getters:
178-name, _ := m.GetString(txn, "name")
179-age, _ := m.GetInt(txn, "age")
180-pi, _ := m.GetFloat(txn, "pi")
181-active, _ := m.GetBool(txn, "active")
182-```
183-
184-### Map/Array Iteration
185-
186-Iterate with callbacks instead of manual iterator management:
187+For simple changes, callback pattern automatically creates a transaction for you.  The underlying library has no concept of rollbacks, so a failed insert, push, etc. may leave you in an indeterminate state.
188 
189 ```go
190-// Map iteration
191-err = m.ForEach(txn, func(key string, value *ygo.Output) error {
192-    // Process each entry
193-    // Return error to stop iteration early
194-    return nil
195-})
196-
197-// Array iteration  
198-err = arr.ForEach(txn, func(index uint32, value *ygo.Output) error {
199-    // Process each element
200+_ = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
201+    txt.Insert(txn, 0, "Hello")
202+    arr.Push(txn, ygo.String("item"))
203+    m.Insert(txn, "key", ygo.Int(42))
204     return nil
205 })
206 ```
207 
208-### Map Input from Go Maps
209-
210-Create inputs from Go maps instead of parallel slices:
211-
212 ```go
213-// Old: parallel slices (error-prone)
214-// ygo.YMap([]string{"name", "age"}, []ygo.Input{ygo.String("Alice"), ygo.Int(30)})
215-
216-// New: Go map syntax
217-data := map[string]ygo.Input{
218-    "name": ygo.String("Alice"),
219-    "age":  ygo.Int(30),
220-}
221-input, _ := ygo.MapInput(data)
222-m.Insert(txn, "user", input)
223+_ = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
224+    content, _ := txt.String(txn)
225+    length := arr.Len()
226+    return nil
227+})
228 ```
229 
230-### Explicit Transactions
231+### Explicit Pattern (Flat)
232 
233-Use explicit Begin/Commit for flatter code with less nesting:
234+Use when you want flatter code with manual control. Rollback and Commit are basically the same thing, don't assume that rollback fixes anything. This is a limitation of the underlying Rust library.  Open an issue if this is a problem; the fix is to snapshot it before an attempted transaction and roll back to it.
235 
236 ```go
237-// Old: Callback-based API (nested)
238-err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
239-    txt.Insert(txn, 0, "Hello")
240-    arr.Push(txn, ygo.Int(42))
241-    return nil
242-})
243-
244-// New: Explicit transaction API (flatter)
245-txn, err := doc.BeginWrite()
246-if err != nil {
247-    return err
248-}
249+txn, _ := doc.BeginWrite()
250 defer func() {
251-    if r := recover(); r != nil {
252-        txn.Rollback()
253-        panic(r)
254-    }
255     if err != nil {
256         txn.Rollback()
257     } else {
258@@ -210,262 +96,239 @@ defer func() {
259     }
260 }()
261 
262-// Perform operations (no nesting!)
263 txt.Insert(txn, 0, "Hello")
264-arr.Push(txn, ygo.Int(42))
265+arr.Push(txn, ygo.String("item"))
266+```
267 
268-// Read transactions work similarly
269-txn, err = doc.BeginRead()
270-if err != nil {
271-    return err
272-}
273+```go
274+txn, _ := doc.BeginRead()
275 defer txn.Commit()
276 
277 length := arr.Len()
278 content, _ := txt.String(txn)
279 ```
280 
281-The explicit API gives you full control over transaction lifecycle:
282-- **BeginRead()** → **Commit()** for read-only transactions
283-- **BeginWrite()** → **Commit()** or **Rollback()** for write transactions
284-- **BeginWriteWithOrigin(origin)** to mark changes with a source identifier
285-
286-**Note on Rollback:** The y-crdt library applies changes immediately during the transaction. Calling `Rollback()` only closes the transaction without syncing to remote peers - the local document state is already modified. For true atomic operations that automatically rollback on error, use `WithWriteTransaction()` which only commits if the callback succeeds.
287-
288-### Breaking API Changes (v0.x → v1.0)
289-
290-**Len() Methods Simplified:**
291+### With Origin Markers
292 
293-All `Len()` methods have been updated for consistency:
294+Track change sources:
295 
296 ```go
297-// Old API - returned (uint32, error)
298-length, err := arr.Len()
299-if err != nil {
300-    return err
301-}
302-
303-// New API - returns just uint32, panics on nil (like Go's len())
304-length := arr.Len()  // Array.Len() - no txn needed
305-length := m.Len(txn) // Map.Len() - txn still required
306-length := txt.Len(txn) // Text.Len() - txn still required
307+_ = doc.WithWriteTransactionWithOrigin([]byte("user-edit"), func(txn *ygo.Transaction) error {
308+    txt.Insert(txn, 0, "Hello")
309+    return nil
310+})
311+
312+txn, _ := doc.BeginWriteWithOrigin([]byte("system"))
313+defer txn.Commit()
314 ```
315 
316-**Error Handling - Nil Checks Now Panic:**
317+## Working with Data Types
318 
319-Methods now panic on programming errors (nil pointers) instead of returning errors:
320+### Text
321 
322 ```go
323-// Old API
324-length, err := arr.Len()
325-if err != nil {
326-    // Handle nil array error
327-}
328-
329-// New API - panics like Go's built-in len()
330-var arr *ygo.Array
331-length := arr.Len() // panic: "ygo: Array.Len called on nil Array"
332+txt, _ := doc.GetText("content")
333+defer txt.Destroy()
334+
335+_ = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
336+    txt.Insert(txn, 0, "Hello, ")
337+    txt.Insert(txn, 7, "world!")
338+    txt.RemoveRange(txn, 0, 7)
339+    length := txt.Len(txn)
340+    content, _ := txt.String(txn)
341+    return nil
342+})
343 ```
344 
345-This aligns with Go best practices where programming errors (nil dereferences) should fail fast. Only runtime errors (key not found, index out of bounds) return errors.
346+### Array
347 
348-## Serialization
349+```go
350+arr, _ := doc.GetArray("items")
351+defer arr.Destroy()
352+
353+_ = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
354+    arr.Push(txn, ygo.String("first"))
355+    arr.Insert(txn, 0, ygo.Int(42))
356+    length := arr.Len()
357+    return nil
358+})
359+```
360 
361-Documents support standard Go binary marshaling:
362+### Map
363 
364 ```go
365-// Serialize document to bytes
366-data, err := doc.MarshalBinary()
367-if err != nil {
368-    log.Fatal(err)
369-}
370+m, _ := doc.GetMap("data")
371+defer m.Destroy()
372 
373-// Save to file or send over network
374-os.WriteFile("document.yjs", data, 0644)
375+_ = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
376+    m.Insert(txn, "name", ygo.String("Alice"))
377+    m.Insert(txn, "count", ygo.Int(10))
378 
379-// Later, load it back
380-data, _ := os.ReadFile("document.yjs")
381-doc2, _ := ygo.NewDoc()
382-txt2, _ := doc2.GetText("content")
383-if err := doc2.UnmarshalBinary(data); err != nil {
384-    log.Fatal(err)
385-}
386+    name, _ := m.GetString(txn, "name")
387+    count, _ := m.GetInt(txn, "count")
388+    return nil
389+})
390 ```
391 
392-## Collaborative Updates
393-
394-Synchronize documents between clients:
395+### Iteration
396 
397 ```go
398-// Get current state vector
399-var sv1 *ygo.StateVector
400-err = doc1.WithReadTransaction(func(txn *ygo.Transaction) error {
401-    sv1 = txn.GetStateVector()
402+_ = m.ForEach(txn, func(key string, value *ygo.Output) error {
403+    // Process entry
404     return nil
405 })
406 
407-// Get state diff (updates needed by remote)
408-var diff *ygo.Update
409-err = doc1.WithWriteTransaction(func(txn *ygo.Transaction) error {
410-    diff = txn.GetStateDiff(sv2) // sv2 is remote's state vector
411+_ = arr.ForEach(txn, func(index uint32, value *ygo.Output) error {
412+    // Process element
413     return nil
414 })
415-
416-// Apply diff on remote
417-doc2.WithWriteTransaction(func(txn *ygo.Transaction) error {
418-    return txn.ApplyUpdate(diff)
419-})
420 ```
421 
422-## Real-Time Sync with y-sweet Server
423+## Sync to y-sweet Server
424 
425-For production collaborative editing, connect to a y-sweet server with explicit lifecycle management:
426+### Simple Sync
427 
428 ```go
429-// Create document
430-doc, err := ygo.NewDoc()
431-if err != nil {
432-    log.Fatal(err)
433-}
434-defer doc.Destroy()
435+ctx, cancel := context.WithCancel(context.Background())
436+defer cancel()
437+
438+_ = doc.Sync(ctx,
439+    ygo.WithSyncEndpoint("wss://y-sweet.example.com/doc/my-doc"),
440+    ygo.WithSyncAuthToken("token"),
441+    ygo.WithOnUpdate(func(d *ygo.Doc) error {
442+        // React to remote changes
443+        return nil
444+    }),
445+)
446+```
447+
448+### With Additional Lifecycle Hooks
449+
450+```go
451+doc, _ = ygo.NewDoc()
452 
453-// Create sync client (does not connect yet)
454-client, err := ygo.NewSyncClient(doc,
455+client, _ := ygo.NewSyncClient(doc,
456     ygo.WithSyncEndpoint("wss://y-sweet.example.com/doc/my-doc"),
457-    ygo.WithSyncAuthToken("my-token"),
458+    ygo.WithSyncAuthToken("token"),
459 )
460-if err != nil {
461-    log.Fatal(err)
462-}
463 
464-// Set up event handlers
465 client.OnConnect(func(doc *ygo.Doc, stats ygo.SyncStats) error {
466     log.Printf("Connected with %d peers", stats.PeerCount)
467     return nil
468 })
469 
470-client.OnDisconnect(func(doc *ygo.Doc, stats ygo.SyncStats) error {
471-    log.Printf("Disconnected - %d peers remaining", stats.PeerCount)
472-    // Note: doc is NOT destroyed here - you may want to reconnect
473+client.OnDisconnect(func(doc *ygo.Doc, stats ygo.SyncStats, reason ygo.DisconnectReason) error {
474+    log.Printf("Disconnected: %v", reason)
475     return nil
476 })
477 
478-client.OnUpdateCtx(func(ctx context.Context, doc *ygo.Doc, stats ygo.SyncStats) error {
479-    // React to changes from other clients
480-    log.Printf("Update received, pending: %v", stats.PendingUpdate)
481-    return processRemoteChanges(ctx, doc)
482+client.OnUpdate(func(doc *ygo.Doc, stats ygo.SyncStats) error {
483+    // Handle remote update
484+    return nil
485 })
486 
487-// Connect in a goroutine with retry logic
488-var connectErr error
489-var wg sync.WaitGroup
490-wg.Add(1)
491-
492-go func() {
493-    defer wg.Done()
494-    
495-    for {
496-        ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
497-        err := client.Connect(ctx)
498-        cancel()
499-        
500-        if err == nil {
501-            // Connected successfully - wait for disconnect
502-            <-client.Done()
503-            
504-            // Check if we should reconnect or shut down
505-            select {
506-            case <-shutdownChan:
507-                return // Shutting down, don't reconnect
508-            default:
509-                log.Println("Disconnected, will retry...")
510-                time.Sleep(5 * time.Second)
511-                continue
512-            }
513-        }
514-        
515-        // Connection failed
516-        connectErr = err
517-        log.Printf("Connection failed: %v", err)
518-        
519-        select {
520-        case <-shutdownChan:
521-            return // Shutting down
522-        case <-time.After(5 * time.Second):
523-            // Retry connection
524-            continue
525-        }
526-    }
527-}()
528+_ = client.Connect(ctx)
529+```
530 
531-// Monitor connection stats
532-go func() {
533-    ticker := time.NewTicker(30 * time.Second)
534-    defer ticker.Stop()
535-    
536-    for {
537-        select {
538-        case <-ticker.C:
539-            stats := client.Stats()
540-            log.Printf("Status: %v, Peers: %d, Pending: %v",
541-                stats.Status, stats.PeerCount, stats.PendingUpdate)
542-        case <-shutdownChan:
543-            return
544-        }
545-    }
546-}()
547+## y-sweet API Client
548 
549-// Run for some time, then shutdown
550-select {
551-case <-time.After(1 * time.Hour):
552-case <-signalChan: // Handle SIGTERM/SIGINT
553-}
554+The ysweet package contains a client for interacting with an upstream y-sweet server.  This is needed when you need to create new documents and generate authentication credentials for the y-sweet Yjs sync provider.
555 
556-// Graceful shutdown
557-close(shutdownChan)
558+```go
559+import "github.com/BTBurke/ygo/ysweet"
560 
561-// Flush pending updates before closing
562-flushCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
563-defer cancel()
564+client, _ := ysweet.NewClient("http://localhost:8080")
565 
566-if err := client.Flush(flushCtx); err != nil {
567-    log.Printf("Flush failed: %v", err)
568-}
569+// Create new document
570+docID, _ := client.NewDoc("")
571 
572-// Close the connection
573-if err := client.Close(); err != nil {
574-    log.Printf("Close error: %v", err)
575-}
576+// Or with specific ID
577+docID, _ := client.NewDoc("my-document")
578 
579-// Wait for connection goroutine to finish
580-wg.Wait()
581+// Get authentication for sync
582+auth, _ := client.AuthDoc(docID)
583+// you can return this auth struct in JSON to configure the y-sweet Yjs provider
584 
585-// Finally destroy the document when completely done
586-// (This is your responsibility - never automatic)
587-doc.Destroy()
588+//use it to configure a Go client to connect to the y-sweet server
589+wsURL := auth.WebsocketURL()
590 ```
591 
592-### Sync Client Events
593+## Proxy Server
594 
595-- **OnConnect(doc, stats)**: Called when successfully connected to server
596-- **OnDisconnect(doc, stats)**: Called when connection closes (reconnect possible)
597-- **OnUpdate(doc, stats)**: Called when document receives changes from remote peers
598-- **OnUpdateCtx(ctx, doc, stats)**: Context-aware variant for cancellation/deadlines
599+This library provides a proxy server that you can put in front of y-sweet to proxy the websocket connections from a Yjs document.  The advantage of this is that you don't need to protect server-only routes on the y-sweet server that are meant to create documents and generate authorization credentials.  It also adds hooks so you can also react to connection/disconnection events when Yjs clients connect to your y-sweet server.
600 
601-### Connection Statistics
602+### Default URL Pattern
603 
604-The `Stats()` method returns:
605-- `PeerCount`: Number of other clients connected (via awareness protocol)
606-- `LastUpdate`: Timestamp of last received remote update
607-- `PendingUpdate`: True if local changes haven't been synced yet
608-- `Status`: Current connection state (Disconnected, Connecting, Connected, Disconnecting)
609+The default y-sweet routes returns URLs like /d/{docID}/ws/{docID}.  You can optionally change this, see the custom URL scheme below.
610 
611-### Important: Document Lifecycle
612+```go
613+handler := ysweet.ProxyHandler("ws://upstream:8080",
614+    ysweet.WithTargetAuthToken("secret"),
615+    ysweet.WithOnConnect(func(ctx context.Context, docID string, w http.ResponseWriter, r *http.Request) error {
616+        // Validate connection
617+        return nil
618+    }),
619+    ysweet.WithOnDisconnect(func(ctx context.Context, docID string, reason ysweet.DisconnectReason) error {
620+        // Cleanup on disconnect
621+        return nil
622+    }),
623+)
624+http.Handle("/d/", handler)
625+```
626 
627-The sync client **never** destroys the document automatically, even on errors or disconnect. You must explicitly call `doc.Destroy()` when completely done. This allows you to:
628-- Retry connections on network hiccups
629-- Switch servers without losing data
630-- Queue updates while offline and sync when reconnected
631+### Custom URL Scheme
632+
633+When you want a custom URL scheme, provide a function to extract the document ID. Internally, this is changed to the URL scheme that y-sweet expects.
634+
635+```go
636+handler := ysweet.ProxyHandler("ws://upstream:8080",
637+    ysweet.WithDocIDFunc(func(r *http.Request) string {
638+        // Extract docID from custom URL: /ws/{docID}
639+        return r.PathValue("docID")
640+    }),
641+)
642+http.Handle("/ws/{docID}", handler)
643+```
644+
645+## Serialization
646+
647+Implements the binary encoding interfaces using the Yjs V1 format.
648+
649+```go
650+// Serialize
651+data, _ := doc.MarshalBinary()
652+os.WriteFile("document.yjs", data, 0644)
653+
654+// Deserialize
655+data, _ := os.ReadFile("document.yjs")
656+doc2, _ := ygo.NewDoc()
657+_ = doc2.UnmarshalBinary(data)
658+```
659+
660+## State Vectors and Updates
661+
662+```go
663+// Get state vector
664+var sv *ygo.StateVector
665+_ = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
666+    sv = txn.GetStateVector()
667+    return nil
668+})
669+
670+// Get diff for remote
671+doc.WithReadTransaction(func(txn *ygo.Transaction) error {
672+    // this is the same as the Yjs function encodeStateAsUpdate V1 algorithm
673+    diff := txn.GetStateDiff(sv)
674+    // Send diff to remote
675+    return nil
676+})
677+
678+// Apply update from remote
679+_ = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
680+    update := ygo.UpdateFromBytes(data)
681+    return txn.ApplyUpdate(update)
682+})
683+```
684 
685 ## Platform Support
686 
687@@ -473,45 +336,26 @@ Pre-built static libraries are included for:
688 
689 | Platform | Architecture | Status |
690 |----------|--------------|--------|
691-| Linux    | amd64, arm64 | ✅ Pre-built libraries included |
692-| FreeBSD  | amd64, arm64 | ✅ Pre-built libraries included |
693-
694-The library automatically selects the correct pre-built library based on your platform. Cross-compilation is not supported - build on the target platform or use the provided pre-built libraries.
695+| Linux    | amd64 | ✅ Included |
696+| FreeBSD  | amd64 | ✅ Included |
697 
698 ## Updating the Native Library (for maintainers)
699 
700-To update the pre-built libraries to the latest y-crdt release:
701-
702 ```bash
703 # Update submodule and version constant
704 make update-yffi
705 
706-# Build for all supported platforms (requires Rust toolchain)
707+# Build for all supported platforms
708 make libyrs-cross
709 
710-# The lib/ directory now contains updated libraries for all platforms
711-# Commit the changes to lib/ directory
712-```
713-
714-For local development (overwrites pre-built library for current platform):
715-
716-```bash
717+# For local development (current platform only)
718 make libyrs-local
719-go build .
720 ```
721 
722 ## License
723 
724 MIT License - See [LICENSE](LICENSE) for details.
725 
726-## Contributing
727-
728-Contributions are welcome! Please ensure:
729-
730-1. Code follows existing patterns and conventions
731-2. Tests pass: `go test ./...`
732-3. Examples build: `cd examples && go build .`
733-
734 ## Acknowledgments
735 
736-This project is a Go wrapper around the excellent [y-crdt](https://github.com/y-crdt/y-crdt) Rust library by the Yjs team. All CRDT logic and algorithms are implemented in y-crdt; ygo provides the Go bindings and API layer.
737+This project is a Go wrapper around [y-crdt](https://github.com/y-crdt/y-crdt) Rust library by the Yjs team. All CRDT logic and algorithms are implemented in y-crdt; ygo provides the Go bindings and API layer.