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.
The playground, the notebook cells, the source editor with its
marks, the inspector columns and the canvas are written once for
both Apple platforms, with a file naming what the two frameworks
call differently. The editor is where they really part company.
The engine owns the image and its start-up, so a mission reaches
the whole of Pharo.
A mark's body opens as a view attachment on its own line under it,
with the rest of the send carried below, so the code reflows around
it rather than stacking under the editor. Snippets reorder and copy;
a graph or chart draws offline from its snapshot.
A compile error reads in words with a dot at the failing spot.
Evaluate splits from evaluate-and-inspect, the snippet spins while
it runs, and print it, format and browse sit on GT's keys.
Snippets share their variables across runs and evaluate from the
keyboard, a page can be maximized, an empty one says what it is for,
and the columns beside it head and size their own lists.
A drill reveals its new column whole, scrolling through the reader
rather than at a position measured before the column exists, and
the strip's thumb both follows the columns and drives them.
Each value opens in a pane on the gutter, beside the page rather
than under it. Panes drag, close and walk, a crowded tab strip
scrolls, and the split and its divider stay where they were put.
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.
Port the mai-ios block parser into LumaCore; render headings,
lists, tables, task lists, blockquotes, code blocks and
footnotes across the SwiftUI and GTK frontends.
Parse inline spans and rewrite LaTeX to Unicode in LumaCore so
both frontends share one parser. Highlight code through
StyledText. Drop the inline-only AttributedString path.
Co-authored-by: trufae <[email protected]>
Extracts the avatar stack used by notebook entries into a shared
view; reuses it on the mission list/detail and in the address-
note popover header and message rows.
EngineRegistry caches one Engine per working-project URL.
MainWindowView.init does a pure lookup, so SwiftUI's
view-tree re-evaluations cannot open extra ProjectStores
that race the live one's BEGIN IMMEDIATE and crash via
try!.
UI-state persistence (collapse flag, sidebar selection,
session detail section, etc.) lives on Engine for GTK to
adopt. SQLite has a 5s busy timeout. Engine open failures
surface as an error view.
TargetPicker is host @State injected via environment.
Global instrument-UI defaults register at app startup;
per-engine instrument and local-notifier hookups live in
Engine extensions.
Reorder the entry header to place editor avatars on the left,
process chip next, the title filling the flex middle, and the
timestamp pinned to the right. The time's x position now stays
stable across entries regardless of title length or editor count,
so a column of notebook entries reads as a neat column of times.
Avatars now use the GitHub-style overlap: 20pt size, -8pt trailing
spacing (≈40% overlap), 2pt ring in the card background so
adjacent circles stay visually separated, and explicit z-order
with the creator on top. On GTK the cluster is built with
Gtk.Fixed for the overlap and a `luma-editor-avatar` CSS class
applies the ring via box-shadow.
Matches the lab-page layout after 2e5c7c9 in luma-server.
Relative "5 min ago" labels gave the notebook a chat-like feel.
Replace with absolute notebook-style timestamps — today shows
"HH:mm", yesterday "Yesterday", last week's entries the weekday
name, older entries an abbreviated date. Share the formatter from
LumaCore so GTK renders the same strings. Demote the SwiftUI
label to tertiary to reinforce "metadata, not content".
The floating New Note button looked squashed — wide without any
matching height. Apply a capsule button shape, bump the iOS control
size, and pad the label vertically so the pill sits closer to a
golden-ratio aspect. macOS keeps its small control size.
Give the empty state a prominent New Note button and have the
sidebar's no-selection view route Add Note through it too. Hide
the floating New Note button while any entry is being edited so
it doesn't stack on top of the row controls. Default freshly
inserted notes to an empty title, show a placeholder in the body
editor, and discard on cancel when both fields are still empty
instead of leaving a stray "Note" row behind.
Scroll to a dedicated bottom anchor on insert so the new row
lands above the keyboard / safe area instead of at the exact row
origin. Trim the horizontal inset on compact width so notebook
content isn't clipped by the narrow iPhone margin. Omit the
instruction walkthrough on compact; its width assumptions don't
hold on the phone.
Restore iOS compilation by gating AppKit-only code in
StyledTextSwiftUI, ITraceTimeline, CollaborationPanel, and
MainWindowView. Stub ITraceCFGView behind #if canImport(AppKit)
since the Metal/NSView renderer is not ported yet. Add a
UIApplicationDelegateAdaptor mirroring the macOS delegate for
luma:// URL handling and APNs. Fix the rpath for iOS's flat bundle
via SDK-conditional LD_RUNPATH_SEARCH_PATHS overrides.
Build a new phone-first shell (PhoneMainView /
PhoneSessionsListView / PhoneSessionView) that replaces
MainWindowView on iOS. Right-edge drawers for events and
collaboration with glow indicators, a session-switcher popover, and
a segmented REPL / Instruments / Insights / Captures layout. The
Sessions list and Session screen each render a custom compact
header instead of the system nav bar so they sit flush under the
DocumentGroup chrome. Notebook pushes from the Sessions root.
Extract SessionContent so the detach banner lives at the parent of
any session-bound surface instead of each detail view rendering its
own. Make HexView responsive: hide the offset column and use
smaller hex and ASCII fonts in compact width, with selection hit
testing aware of the hidden column. Move the REPL cell timestamp
to share the request line so the result row gets the full cell
width, and trim the REPL horizontal padding in compact width.
Replace Color.cyan with a light/dark adaptive Color.jsTypeLabel so
Object{N} labels stay readable on a light background, and
ellipsize the JS inspector disclosure header to one line.
Three small polish fixes:
- Mac notebook: drop the inline pencil "Edit Note" button from
the row header. It was pushing the editors and timestamp
leftward only for notes, so notes and capture rows visually
drifted apart. Double-click and the right-click menu already
cover entering edit mode.
- Mac notebook: slim the top drop strip from 16 pt to 6 pt and
stop applying the LazyVStack's 12 pt top padding to it, which
together had left the first row floating far below the top of
the pane.
- GTK notebook row: Adwaita's `.card` style already provides
vertical padding, so the explicit 10 pt inner margins were
stacking and making the top of each card look visibly taller
than the bottom. Let the stylesheet own the vertical space.
Follow-ups to the last round:
- Mac notebook: the floating "New Note" button used to overlap
the last row's Save/Cancel controls even after scrolling. Add
80 pt of bottom breathing room to the LazyVStack, and scroll
the freshly-inserted note into view via ScrollViewReader so
the editor shows up below the fold without the user hunting.
- Mac notebook drag: per-row `.dropDestination` only fires
inside the row's frame, so drops into the empty space above
the first row landed nowhere. Add an explicit 16 pt top drop
strip (`TopDropZone`) that reorders the source to position 0.
- Mac CollaborationPanel: center-align the lab picture and
title+edit button (was top-aligned, which looked lopsided).
- Both CollaborationPanels: when a project has already been
linked to a lab and we're just disconnected, say "You're
currently offline from the shared lab … Reconnect to rejoin
and resume syncing." and label the button "Reconnect" —
instead of the previous "Enable collaboration" copy that
implied a fresh activation.
- Engine: drop the `[notebook] reorder …` diagnostic print.
A batch of small UI fixes across both frontends:
- GTK header bar: drop the row of text buttons ("New Session…",
"Add Instrument…", "Install Package…", "Collaboration") and
replace with symbolic icon buttons pinned to the right, GNOME
HIG-style. Icons are list-add, applications-engineering,
media-playback-start (unchanged), folder-download, and
system-users. Labels survive as tooltips.
- Both CollaborationPanels: shorten "Share this link so others
can open a new project and join this notebook." to "Share this
link to invite others to this notebook."
- GTK CollaborationPanel: single-line ellipsized lab title
(was wrapping to three lines to match picture height);
consistent "Members"/"Chat" section labels instead of all-caps
"PARTICIPANTS"/"CHAT".
- Mac CollaborationPanel: lab title wraps over multiple lines
instead of ellipsizing; panel width is now draggable via
HSplitView (min 260, ideal 300, max 520); lab picture uses
fixed-size frame+clip so large-dimension uploads render
crisply at 48 pt; uploads downscale to ≤512 px JPEG before
sending.
- GTK CollaborationPanel: matching lab-picture downscale via a
new CLuma shim (`luma_image_normalize`) that uses GdkPixbuf
to decode→scale→JPEG-encode.
- Mac notebook editor: explicit Save/Cancel buttons on the
entry editor (mirrors the GTK row). A freshly-inserted blank
note cancels itself away instead of leaving a stub row.
- Mac notebook drag: capture row height via GeometryReader so
the above/below midpoint is actually computed, which means
drops above the first row finally work.
- GTK notebook row: drop the redundant "Edit" button from the
header — double-click and the right-click menu both already
enter edit mode.
Lab pictures are now uploadable by the owner — pick an image in
the CollaborationPanel (NSOpenPanel on Mac, GtkFileDialog shim
on GTK) and it becomes the lab's avatar on every client and on
the browser /l/<id> page. PNG, JPEG, WebP, and GIF are accepted;
the server enforces a 512 KiB cap. When no picture is set we
fall through to the owner's GitHub avatar, so a lab that nobody
bothers customizing still shows a real face. Clients get
pictures inline in the .join snapshot (bytes appended after the
notebook blob) and live updates via +update on
/labs/<id>/picture; CollaborationSession gains setLabPicture /
removeLabPicture RPCs.
Two other fixes in the same pass:
- Relative timestamps now refresh. Mac wraps each label in a
TimelineView(.periodic(..., by: 60)); GTK runs a single 60 s
Task that rewrites the string on every registered Label. So
"1 min ago" actually advances to "2 min ago" without a
rebuild.
- Closing a document window now stops collaboration. Previously
the session stayed joined until the whole app quit — Mac via
.onDisappear on MainWindowView, GTK via the existing
closeHandler calling engine.collaboration.stop() before
persisting state.
Every lab now has a title. On .create the client mints one from
the owner's local timezone — "<Weekday> <morning|afternoon|
evening|night> <verb>" with verb ∈ {reversing, tracing, hacking,
spelunking, poking, sleuthing, dissecting} — so the first thing
everyone sees is something like "Friday evening spelunking"
instead of an opaque id. Owners rename at any time via a pencil
button in the CollaborationPanel (SwiftUI inline field on Mac,
inline Entry on GTK); the server broadcasts +update on
/labs/<id> so every connected client gets the new title live.
Separately, all visible timestamps switch to a coarse "time ago"
form ("just now", "5 min ago", "2 hr ago", "yesterday", "3 days
ago", "last week"). A shared `LumaCore.RelativeTime.string(from:)`
helper keeps Mac and GTK consistent; the absolute timestamp lives
on as a tooltip for anyone who needs it. Event stream / REPL
cells keep their absolute high-precision times since those are
high-volume and the exact value matters.
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.
The server stamps NotebookEntry.author from the authenticated
connection on every +add and echoes it back, but Engine's handler
was ignoring the echo for entries that already existed locally,
so the local row never picked up the stamped identity.
Merge author from the echo into the locally-cached entry and fire
.updated. Mac NotebookView and GTK NotebookPane now render an
avatar + display name chip in each entry's header, mirroring the
CollaborationPanel look.
The numbered walkthrough was geometrically centered but visually
lopsided: the title was narrow, the bullet block was wide, and
the whole block sat left-of-title. Add a leading offset so the
block's content sits visually centered beneath "Capture
interesting findings here." — 72pt in SwiftUI, 64pt in luma-gtk.
Also on the SwiftUI side:
- Restore the inline SF Symbol references to the toolbar buttons
(target / waveform.path.ecg) that step 1 and 2 point at. The
macOS toolbar buttons are icon-only and don't have visible
labels, so the prose wouldn't otherwise tell users what to
click.
- Bump instructionsMaxWidth 350 → 440 so step 3 no longer wraps
after "save it here."
- Monospaced-digit the leading number so "1." "2." "3." render
at identical widths regardless of proportional metrics.
- SwiftUI: split each numbered step into an HStack with a fixed
18pt right-aligned number column and a leading text column,
matching the luma-gtk presentation so the step text aligns
cleanly regardless of digit width. Drop the inline SF Symbols
since the equivalent is awkward on GTK and the icons weren't
pulling much weight.
- luma-gtk: match the SwiftUI copy for step 3 ("Pin any event
from the stream to save it here." instead of "Pin any output
to save it here.").
Workspace is now a thin host adapter. Engine owns sessions and
notebook entries (via store observation), GitHub auth + OAuth
device flow, hook-pack discovery, address annotations and address
actions, the disassembler cache, and all instrument lifecycle.
EventLog is @Observable so SwiftUI tracks it directly; the host
no longer mirrors event arrays or version counters.
R2 (live disassembly + trace inspection) lives entirely in
LumaCore as concrete Disassembler / TraceDisassembler classes
returning portable StyledText spans. ProcessNodeViewModel and
InstrumentRuntime are gone; views read engine.processNodes /
node.instruments / engine.descriptor(for:) directly.
Reorganize Luma/Models/ — instrument UI dispatch lives under
Luma/Instruments/, Monaco editor glue under Luma/Editor/, and the
remaining UI types sit at the top of Luma/.
CollaborationSession is now @Observable. Views read its
state directly via workspace.engine.collaboration. The
notebook entry methods live on Engine. The portal cert
is a LumaCore SwiftPM resource via Bundle.module.
Workspace keeps only the auth-flow glue (showing the
sign-in sheet, signOut clearing UI state) and the deep
link join handler.
Update every view and Workspace extension to use LumaCore
types and ProjectStore instead of SwiftData:
- SidebarView: fetch instruments/insights/captures from store
- DetailView: resolve sessions via store
- REPLView: load cells from store by sessionID
- NotebookView: use LumaCore.NotebookEntry value types
- Workspace+Packages: delegate to CompilerWorkspace, use
ESMBundleParser.parse(), persist via store
- Workspace+Collaboration: fetch/save collaboration state
and notebook entries through store
- All remaining views qualified with LumaCore types
The Luma app should now compile without SwiftData.
Delete all 10 @Model classes (replaced by LumaCore structs).
Remove SwiftData imports from 27 files. Drop DocumentGroup,
schema versions, and migration plan from LumaApp.
ProcessNodeViewModel now persists REPL cells, ITrace
captures, and session updates through ProjectStore instead
of ModelContext. InstrumentRuntime uses the LumaCore
InstrumentInstance struct directly.
WIP: Workspace needs ProjectStore injection and views need
wiring to load data from the store. The Xcode build has
errors until the remaining view plumbing is done.