A comment is a last resort, and these were not one: section banners,
doc headers naming what the declaration below already names, and
notes saying in prose what the next line says in code.
What is left is what the code cannot show -- an ordering that matters,
a platform quirk, why the obvious way fails.
Boot the VM against a bundled image, evaluate from a notebook cell
or a playground snippet, and keep what each evaluation produced.
The image is staged on every build; the framework release is pinned.
Carry the styled text on NotebookEntry alongside the plain
details, capture it when pinning an r2 cell, and render it with
color in both notebook frontends.
Replace the four per-mutation notification types (+add/+update/
+remove/+reorder) with a single +op envelope carrying {op_id,
kind, ...}. Pending mutations persist to a `notebook_outbox`
table on the client and replay on every .joined transition, so
an offline user's work propagates as soon as they're back on the
bus. Projects that never enable collaboration keep a zero-row
outbox — we only touch it once `collaboration_state.lab_id` is
set, and bulk-enqueue existing entries on createLab's success.
Model changes, in the same pass:
- NotebookEntry swaps `author: Author?` for `editors: [Author]`
(creator at index 0, each editor appended as the server applies
their update op). The boolean `isUserNote` is gone; callers now
read `entry.kind == .note` directly.
- Entries carry a `position: Double` — server-authoritative on
add (`max + 1000`), client-computed midpoint on drag. Clients
sort by `(position, id)` so concurrent reorders converge.
- Schema folds the new columns into v1 (no migration — Luma isn't
shipped).
UI:
- Mac: header renders a stack of editor avatars (tooltip = name,
tap opens the GitHub profile). Entry rows are draggable and
accept drops on top/bottom halves to compute the insertion slot.
- GTK: same avatar stack; reordering lives in the right-click
menu (Move Up / Move Down) since SwiftGtk's DragSource binding
isn't verified yet on this branch.
Rewrite CollaborationSession around the server's new envelope
shape: a single dispatcher that routes (from, type), explicit
request/response correlation via client-chosen ids, and typed
Member objects carrying role + Presence + last_seen_at.
NotebookEntry gains an Author field stamped by the server and
a Kind enum (note | capture); isUserNote is preserved as a
computed alias so the UI doesn't churn. Notebook update hooks
now fire with (UUID, changes) so Engine can merge into the
locally-cached entry without the server having to re-send the
full row.
Protocol key shake-up is snake_case throughout, and UserInfo
now reads "avatar" (matches what the server actually emits —
the previous "avatar_url" lookup was a latent bug). The Mac
and GTK collaboration panels switch from participants to
members.
All model structs now conform to FetchableRecord +
PersistableRecord with snake_case table/column names.
GRDB handles Codable encoding/decoding automatically.
ProjectStore rewritten to use the type-safe query builder:
model.save(db), Model.fetchAll(db), Model.filter(...),
Model.deleteOne(db, key:) — no raw SQL remaining.
Define all domain model structs as plain Codable types in
LumaCore, replacing the SwiftData @Model classes:
ProcessSession, InstrumentInstance, REPLCell, NotebookEntry,
ITraceCaptureRecord, AddressInsight, InstalledPackage,
ProjectPackagesState, ProjectCollaborationState,
TargetPickerState, RemoteDeviceConfig
Flesh out ProjectStore with complete GRDB/SQLite CRUD for
all 11 tables. Both the SwiftUI and GTK frontends will use
this as their persistence layer.