A scene the image holds by handle and changes; the host draws it.
Each drawable carries its own vertex and fragment stages, its own
attribute layout, named uniforms, buffers, a transform and a
visibility flag -- described as objects rather than staged globals.
A value says what it should do, ramp or oscillate, rather than what
it is each frame: nothing crosses per frame, since the image has no
business in the frame path any more than in the audio callback.
Both renderers work it out on the clock that draws it.
Eight channels, each with its own patch and pattern on its own step
clock, mixed on the audio device's thread: storage allocated once,
reached through unsafe pointers, and a lock-free ring for control,
so nothing under the callback allocates, retains or locks.
The image performs a tune, names it, and sounds chords.
Write each effect body-only against a preamble the host supplies,
and let the build systems generate what each renderer needs -- a
Swift catalog for OpenGL, a Metal fragment function through glslang
and spirv-cross. Effects react to arriving events.
Add an .engine RuntimeEvent source so host-tier issues
(devices, packages, attach, spawn-gating, instruments,
hookpacks, repl, itrace, editor) flow into the existing
event stream instead of stdout. HookPackLibrary takes a
callback for decode errors, and ITraceDecoder returns
collected panic strings so ProcessNode can yield them as
engine events.
Carry the runtime event's JSInspectValue into the notebook
entry so it renders as an inspectable value instead of a
stringified payload. Resolve the descriptor's display name
for the title — the source-level name is the def's UUID for
custom instruments.
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.
Length-prefixed JSON log at events.log next to db.sqlite,
configurable byte cap with rolling compaction. Replays on
Engine.start.
toLogJSON/fromLogJSON carry the .raw payload and sessionID
that wire JSON drops. EventLog flushes a pendingPersist
queue so the visible-window trim can't drop events.
Target picker grows a Wait for Launch mode with a popover-anchored
suggestions list. Sidebar context menu adds Arm/Disarm with a regex
prompt; armed sessions get a scope badge.
Detail banner picks among armed, idle, and detached states. The
armed banner offers Resume when gating is paused or failed and
Disarm otherwise.
EventStreamView renders spawn-gating events; raw payloads unwrap to
the inner message string instead of String(describing:) on the case
wrapper. REPL inactive message reflects armed/paused/detached.
Replace the GTK "⋯ bt" trigger with an icon button. Each
backtrace entry gets a circular "Open Disassembly" jump
button that routes through AddressActionMenu's navigator.
Symbolication failures surface an error caption with a
Symbolicate retry button.
Pause the event stream when the popover opens so live
updates don't shift the anchor; SwiftUI gets a matching
pauseEventStream environment value that EventStreamView
injects and TracerEventRowView reads.
"No events yet" and its subtitle sat with only 6pt between them
on iPhone, which read as one chunk rather than title+explanation.
Bump the stack spacing to 12pt on compact widths; macOS keeps the
tighter 6pt since the wider layout doesn't need the extra gap.
The search field sat between the Spacer and the Live/Paused label,
so every time Pause toggled the field jumped by the width
difference between "Live" and "Paused". Move the field to a
fixed position right after the source/process filters, and push
the Spacer to the right of it — the field now stays put.
Two issues on the event stream header:
- On macOS/iPad the overflow menu rendered with the default
bordered-ish chrome, so a gray button sat in a white toolbar.
Apply `.buttonStyle(.borderless)` so it matches the pause
button beside it.
- On compact iPhone the header used 8pt horizontal padding while
the surrounding drawer header uses 16pt, so the overflow button
sat almost on the screen edge and its outer edge didn't align
with the drawer's expand/close icons. Bump compact header to
16pt so the edges line up.
Trash sitting immediately next to Pause in the event stream header
was too easy to hit by accident. Replace the dedicated trash /
Clear button with an ellipsis / view-more menu that exposes Clear
Events as a destructive menu item on both SwiftUI and GTK.
Gate min-width frames behind isCompactWidth so the event stream,
target picker, codeshare browser, and collaboration panel collapse
cleanly on iPhone instead of forcing a scroll at their regular
widths. Give the event stream a dedicated compact header that
promotes the source / process filters to icon menus and keeps the
search, pause, and clear controls inline.
Drop the auto-select-first behavior in the target picker and
codeshare browser on compact, so tapping a sidebar row navigates
to the detail pane on NavigationSplitView's built-in stack instead
of stranding the user looking at a preselected item.
Push the instrument event-row monospaced font up into each
InstrumentUI factory and out of JSInspectValueView, so tracer,
hook-pack, and codeshare rows match without recomputing it for
every sub-value. Migrate the JS inspector disclosure label to
AttributedString to kill the deprecated Text + Text concatenation
warning. Tighten REPL result formatting and relabel the detach
banner hint.
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.
InstrumentDescriptor (LumaCore) holds the data: kind,
identifier, display name, icon, initial config, and
event summary. Engine maintains a descriptor registry.
InstrumentUI protocol (Luma) defines the SwiftUI view
factories. TracerUI, HookPackUI, and CodeShareUI
implement it. InstrumentUIRegistry maps descriptor
IDs to UI instances.
Workspace no longer constructs templates. Views look
up descriptors via engine and UI via the registry.
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.
Rename ProcessNode → ProcessNodeViewModel across all views
and update RuntimeEvent to wrap the LumaCore event type.
Views access core business logic through .core (evalInREPL,
symbolicate, resolve, anchor, readRemoteMemory, modules).
The ViewModel exposes Frida types directly (device, session,
script) and handles SwiftData persistence by subscribing to
the core's async event streams.
Move pure data types out of Luma/ into Sources/LumaCore/
so they can be shared with the GTK frontend:
- AddressAnchor, CFGGraph, ITraceDecoder (+ all related
types: TraceEntry, ITraceMetadata, RegisterState, etc.)
- JSInspectValue, SpawnConfig, ProcessModule
- HookPackConfig, HookPackManifest, InstrumentConfigCodec
- InstrumentIcon, InstrumentKind, ConsoleLevel
CFGGraph uses its own Point2D instead of CoreGraphics
types, keeping the core free of Apple frameworks.
Remove premature stubs (Engine, ProcessNode,
InstrumentRuntime, EventStream) that will be properly
built in a later phase once the real implementations
are ready to move.