templates and availability spawned `emulator -list-avds` on every
render, so opening the boot sheet -- and clicking a template, which
re-queries them all -- stuttered for seconds. Cache the listing and
warm it off the main thread when the picker or sheet appears.
Collapse the two per-architecture Android entries into one; the
chosen AVD now decides the architecture, retiring the message that
wrongly claimed Frida could only instrument the other one.
Give the detail side the same placeholder rows as the device list:
while a device enumerates, with a status footer that keeps the
connection-stage readout; and while devices are still being
discovered, so the detail never flashes "Select a Device" first.
Render redacted placeholder rows in the same list the loaded state
uses, so the sidebar keeps its shape and the swap to real devices
is a fill-in rather than a jump from empty space.
The mode picker's maxWidth cap compressed and clipped its leading
segment on the first layout pass, before the detail column's width
resolved. Pin it to its natural size instead. Drop the redundant
discovery hint so the loading sidebar is just the header.
The sidebar dropped its header and centred a lone spinner while
discovering, then snapped to the header and list once devices
arrived. Pin the header for every state so rows just fill in.
Read the shared connection activity in the target picker and put the
current stage, with a progress bar that eases toward the next stage
over its expected duration, under the spinner in place of "Querying".
Show the stage on an attaching session's row too, since a session
re-establishing connects the same way.
Add a machine abstraction to LumaCore, with QEMU as its first
backend: templates mirroring frida-core's test guests, a QMP client,
and a display that maps QEMU's surface over D-Bus rather than
copying frames.
Leave readiness to the user: drive the guest, mark it ready, and come
back to that snapshot later. Keep the machine in the project, so
booting it again resumes where it was marked. Hand the stub to
addBareboneDevice, so it joins the picker beside the other devices.
Show the display while booting, and afterwards in a Machines panel,
which the side panel now models as an enum to share its slot.
Surface each process's argv as a dim subtitle next to its PID, so
same-named processes can be told apart. Match it when filtering too.
Falls back to just the PID when the backend reports no argv.
Move the app filter alongside the Application/Program toggle, collapse
launch options behind a single Options disclosure, and bump min height
so the picker fits its content without the bottom form eating slack.
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.
The previous sheet rolled everything — title, fields, advanced
options, action buttons — into one ScrollView. On compact iPhone
with `.medium` / `.large` detents, that left big empty space
below the buttons in the collapsed form, and once the user opened
Advanced Options the content pushed Cancel/Add below the detent
line so they were only reachable by dragging the sheet all the
way open.
Split the sheet by platform: iOS now uses NavigationStack + Form
with Cancel/Add pinned in the toolbar, so they stay visible
regardless of how much the form scrolls. Add is disabled until
the address field has content. macOS keeps its existing layout
since it never had the detent problem.
The large-title area already provides generous bottom padding on
iPhone, so the extra 12pt above the "Pick a device…" hint pushed
it too far from "New Session". Drop the top padding to 0 on
compact; wide layouts keep the original 12pt since they don't have
a large title above the header.
On iPhone the list card sat flush against the subtitle at the top
because its insetGrouped backdrop only covers the scroll content,
not the parent. Paint the systemGroupedBackground on the whole
device pane and hide the list's own scroll backdrop so the card
floats on one continuous grey page — horizontal gutters and the
new top gap now match.
On iPhone the uppercase DEVICES label was redundant with the
navigation bar title ("New Session") and the "Pick a device…"
subtitle. Drop the section heading in compact mode — the subtitle
becomes the only leading label, right-aligned + button stays. Bump
the bottom padding to 16 so the list card doesn't butt right up
against the subtitle. Wide layouts keep the DEVICES heading since
there's no navigation-bar fallback context there.
When the detail pane is visible alongside the device list, the
mode selector at the top of the detail pane already spells out
what the picker does ("Spawn a new app…" / "Attach to an
already-running process…"). Repeating the same guidance under
"Devices" is noise. Show the subtitle only on compact widths
where the detail pane isn't visible yet.
The monogram placeholder wasn't really session-specific — the same
hue-from-seed + initials logic fits every target that has an
optional bundle icon. Rename `SessionPlaceholder` → `IconPlaceholder`
(data in LumaCore) and the two view wrappers to `IconPlaceholderView`
so the scope reads correctly, then wire the target picker's app and
process rows to fall back on it when no Frida-provided icon is
available. Seeds are app.identifier for applications and proc.name
for processes so repeat targets keep consistent colours.
On iPhone the sheet centred its content, leaving big empty strips
at top and bottom. Wrap the form in a scroll view anchored at the
top-leading corner and opt into medium + large detents (iOS only)
so the sheet behaves like a native form. macOS keeps its
fit-to-content behaviour.
The Devices header sat directly on top of the list with a vague
green status dot. Rework into a titled section with an explanatory
subtitle, drop the dot (the ProgressView already covers the only
transient state), and align the + button with a platform-aware
horizontal inset so it lines up with the list's trailing edge.
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.
The "Mode" label next to the Spawn|Attach segmented picker was
redundant — the caption text beside the picker already tells the
user what each mode does. Hiding the label also lets the picker
sit flush-left, matching the luma-gtk layout.
Six small refinements to the spawn UI, applied to both frontends
where relevant:
- Right-align the Application|Program picker so the spawn header
reads lighter when no device is picked yet.
- Rename the Pipe stdio option to "Pipe to Luma" and make it the
default — most users want Luma to capture stdout/stderr, and
the old name wasn't specific enough about where the pipe goes.
- Move the "When turned off…" caption off the Execution section
footer and directly under the auto-resume toggle, so it
doesn't read like it applies to the stdio picker above.
- Collapse Launch Arguments, Environment, Working Directory and
Execution by default when spawning an Application (rare to
need any of them) — GtkExpander in luma-gtk, Section with an
isExpanded binding in SwiftUI. Program mode keeps them
expanded.
- luma-gtk only: indent the Program section's card the same as
the other sections, and fix a small left-padding asymmetry.
The app-mode hint said "Environment variables will be added…"
while the program-mode hint said "…are added…" — no meaningful
difference, just accidental drift between two copies of the same
sentence. Use the tighter present-tense version in both
frontends.
The "Spawn on <name>" title + device id subtitle + "Luma will
spawn the target and attach a new session automatically." blurb
did not pull their weight — the device selection is already
visible on the left, and the mode is obvious from the picker.
Keep the Application|Program picker, drop the rest, in both
frontends.
Each .luma document is a package (directory bundle)
containing project.sqlite. LumaProject implements
ReferenceFileDocument to integrate with SwiftUI's
DocumentGroup:
- New documents create a fresh SQLite database
- Existing documents read the database from the package
- Snapshots serialize the database for save operations
Workspace takes a ProjectStore from the document rather
than creating its own. Each document window gets an
independent store/workspace pair.
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.
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.