makepad-game-sim added EmitterAnchor::EntityLocal but game/render's
ParticleSystem still matched only Entity/Point (E0004). Port the
libs/render implementation: step() now resolves (pos, yaw) so a local
offset turns with its body; add clear(). Arcade caller passes yaw.
libs/sim migrated camera_boom_limit to take `ignore: u64` (the filmed
body) but the game/blocks controller, game/render scene and the
gamemaker example still called the 4-arg form, so every crate depending
on makepad-game-blocks/render failed to compile (nigig TRAFFIC-P0-01).
Pass the controller's `subject` / `world.cam_third`, mirroring the
already-migrated libs/render/src/scene.rs.
- The child's Tick drains the network queue for HTTP and script sockets (dispatch_network_runtime_events), and that queue also carries the host socket its hosted loop reads. A host batch landing during a Tick went through dispatch_studio_msg, which drops the loop's own messages: WindowGeomChange, Swapchain, Tick. The drain now parks host-socket responses in Cx::studio_backlog once a loop owns the socket (its first read), and the loop's next read takes them first, in order. Before: 1 in 2-3 split runs lost the geometry; after: 6 of 6 runs delivered every geometry sent. Applies to the macOS and Linux X11 hosted loops too; Android hosted never drained the network in its Tick
- wm's run view cleared its bootstrap (the geometry resend) on any present, so a frame already in flight when the tile changed size cancelled the geometry before it was sent; a present settles it only after the first bootstrap beat has sent the messages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Measured on .100 with apps/aichat and a draw-call log: the popup's rows use two
shaders created on its first draw (menu item background and text). D3D11
compiles them on the pool and skips their draw calls until they are installed;
the background's shader already existed, so the panel drew empty. The finished
compiles were only picked up inside a redraw (hlsl_compile_shaders ran under
need_redrawing), and a window at rest has none: the finished task raised the
internal signal, the loop woke and went back to sleep, and the rows stayed
missing until the next input. A startup shader that finished after the last
startup redraw waited 17 s for the click that opened the menu. Warm shader cache:
the same empty panel. Draw order is fine: the rows follow the background in the
popup's overlay list, above it in depth. Metal compiles inline and was correct.
- hlsl_adopt_shaders installs finished compiles; the Signal handler runs it
when the internal signal is raised (one wake per finished task, no polling),
and every paint tick runs it before it draws (and the stdin host's tick).
- Once the startup set is installed, new shaders compile on the UI thread
before the frame renders, as Metal does, with a 1 s budget per frame and the
rest on the pool. Startup keeps compiling on the pool. Measured first open
with a cold cache: 26 ms + 19 ms, the menu complete in its first frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Windows: the Store package's App Execution Alias (%LOCALAPPDATA%\Microsoft\WindowsApps\claude-desktop.exe), else the running Claude Desktop's own exe (never Claude Code's claude.exe), else the installer's AnthropicClaude\claude.exe or Programs\Claude\Claude.exe; started with no console window.
- macOS: open -a Claude <bundle>, when Claude.app is in /Applications or ~/Applications. Linux: xdg-open as before.
- No Claude Desktop found: the panel says "Claude Desktop isn't installed".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
wm.exe died at startup with "thread 'main' has overflowed its stack"
(0xc00000fd) on Windows, where the main thread gets 1 MB against 8 MB on
macOS and Linux. The startup chain is shallow (~85 frames) but five of its
frames were huge: ShellIcons (41 DrawSvg, ~110 KB) sat by value in every
ShellDraw, PhoneSurface carries one ShellDraw and WmDesk carries a
PhoneSurface plus its own ShellDraw (286 KB). Each constructor layer
(WmDesk factory, WmDesk::script_new, PhoneSurface::script_new and
script_new_with_default, ShellDraw) held its whole value in its frame:
about 970 KB in five frames. Reproduced on macOS by linking wm with a
1 MB main stack (same overflow).
script gets a transparent Box<T> (ScriptNew/ScriptApply/ScriptHook forward
to T: same type id, proto, default and apply), and ShellDraw boxes its
icons. The same chain now takes ~305 KB (WmDesk 286 -> 69 KB, PhoneSurface
140 -> 30 KB, ShellDraw 117 -> 7 KB); the 1 MB-stack build starts and runs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`smooth_scroll_to()` never cleared `tail_range`, so on a list that was following
its end (e.g., a chat timeline restored at the bottom), each draw pulled the list
back down while the scroll animation moved it up, and the target was never reached.
Now scrolling to any item but the last one stops following the end and drops any
pending tail adjustment. Scrolling to the last item still resumes tailing once it lands.
* Constrain Splash external I/O to the host service bridge
* Validate untrusted Splash source with the host I/O restriction
* Give strict Splash validation a disposable storage jail
* Close the remaining ways out of the Splash host I/O restriction
- Keep CachedWidget singletons per heap, and don't register CachedWidget
or WindowMenu in restricted isolates.
- Don't emit Html/Markdown link URLs as actions from a restricted isolate,
and ignore its menu bar updates.
- Suppress clipboard copy/cut hits for any restricted isolate, not only
within Splash.
- Give script calls made while an isolate is installed the same budget.
- On web, let package resource fetches skip the guest I/O guard.
- Name the validation jail without the wall clock, and skip it on wasm.
- Make the host I/O tests fail when their guards are removed.
- The loopback MCP server moves from Director into libs/ai/services (mcp::server); Director re-exports it, its lane tokens and tests unchanged. The dispatcher now names the server and its instructions; a TokenStore can live in memory only (ephemeral).
- mcp::host: while Claude Desktop is the provider the panel serves its registry's tools (service__tool names) on an ephemeral 127.0.0.1 port with a fresh bearer token, and writes ~/.makepad/mcp/<exe-stem>.json {pid, port, token, title} (0600 in a 0700 dir), removed when the provider changes or the panel goes. Calls queue to the UI thread and run through EngineCore::call_external: a card in the transcript, destructive calls held for the person's confirm (the pane opens for it), the result sent back when the card lands.
- mcp::mcpb: "Connect to Claude Desktop" writes <exe-stem>.mcpb (a stored zip, manifest_version 0.3, binary server = this executable with --mcp) and opens it so Claude Desktop shows its install dialog.
- platform mcp_relay: `<app> --mcp`, checked first in app_main before any Cx, window, GPU or audio, relays newline JSON-RPC on stdio to the running app's endpoint. It answers initialize/ping itself and tools/list from the app's last list while the app is down, starts the app (detached, MAKEPAD_AI_PROVIDER=claude-desktop, engine up with the pane closed) on the first call that needs it, waits up to 20 s for its file, and exits when stdin closes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
audio_route: the Core Audio process tap gains Windows (WASAPI process loopback; the source is ducked through its ISimpleAudioVolume) and Linux (the PulseAudio/PipeWire monitor of the source's sink; ducked through its stream volume) backends behind the same Route API. A route only monitors by default (copy for meters and visualisers; the app's own output is untouched). Route::set_processing turns processing on: the source is ducked to 2^-13 and boosted back by exactly 2^13 in float, so the processed signal is bit-exact. RouteConfig.state_dir keeps the duck state, so restore_after_crash puts a ducked volume back after a crash.
script/wgsl: an if/else whose branches end in a value nothing uses wrote that value as a bare statement (_phi_353;). Metal, GLSL and HLSL accept that, WGSL does not, so DrawMenuRow failed on Vulkan and menu rows lost their drawing (the theme chips on Linux). ShaderBackend::write_discarded_expr writes such values as `_ = expr;` for WGSL and keeps void calls as statements, in both places that emit leftovers. MAKEPAD_TRACE=shader.wgsl lifts the two-error log suppression.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- task: a right-click column chooser (sections as flyouts, Default Columns), every figure and graph its own sortable column, dragged order and widths saved; per-process network bytes/packets from the kernel's ntstat control socket (matches nettop, no root), disk bytes, footprint and idle wake-ups every tick; history journal v4
- widgets: data_grid_columns, one column helper (chooser, reorder, resize, fit, sort cycle, layout text) that task uses and other tables can reuse; the menu engine refreshes marks inside an open flyout; the segmented control centres its labels on the line height and no longer glides after a moved row
- svg: a stroke join never connects to the previous subpath (the diagonal through outline icons)
- platform: home::app_data_dir; script: ScriptIp body ids widened to 14 bits (16384 bodies, was 4096) with an index of 26 bits, and a clear stop instead of aliasing past the limit
- audio_route (new): tap an app's audio output through the Core Audio process tap into a host processor (equalizer, gain, limiter, analyzer) and play it; audio_picture owns the one FFT; audio_decode probes tags and length from a file's head and tail; search::fold_words; zip_file reads archives with a trailing comment
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The WM phone shell rides on it: the simulated finger in the desktop skin, time-based release velocity (80 ms window, stale after a 120 ms rest), one critically damped spring (k 900, c 60) seeded with that velocity for paging, drawer, recents and app open/close, an 8 pt / 1.2x axis lock latched through release, a drawer that tracks the finger 1:1 and draws opaque over the home tiles, hold-to-Recents precedence, launches that zoom from the icon actually drawn with an opaque launch card, no ghost card on close, and one cancel/reset path for rotation, resize, focus loss, style switch and keyboard navigation.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Apple ships system fonts whose only outline table is the proprietary hvgl
format (PingFangUI.ttc on macOS 26+ has no glyf/CFF at all). They parse
fine — cmap, metrics and shaping all work — but ttf_parser can outline
nothing, so text silently renders blank.
When ttf_parser yields no outline, Font::glyph_outline now asks CoreText
(whose in-OS decoder reads hvgl) for the glyph path and converts the CGPath
into the same GlyphOutline commands the SDF rasterizer already consumes.
Shaping, metrics and the atlas are untouched; the fallback only fires for
glyphs that render nothing today.
CTFont resolution is layered because CoreGraphics and CTFontManager both
refuse hvgl-only fonts loaded from data or URL, and CTFontCreateWithName
silently substitutes a fallback for dot-prefixed hidden names: try the
PostScript name, then scan the system UI font's cascade lists for CJK
languages (where hidden fonts like .PingFangUITextSC-Default are
reachable), then a temp-file font-manager load. Every candidate is
validated against the ttf_parser view of the face (glyph count + cmap
probes) so a substituted font can never smuggle mismatched glyph IDs into
the atlas. Variation coordinates (e.g. wght for bold) are carried onto the
CTFont via kCTFontVariationAttribute, consistent with the HVAR-adjusted
metrics rustybuzz produces.
Also: env-gated MAKEPAD_FONT_DEBUG=1 logs font family member resolution
(resource path + byte count) while families load.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 4c59a370834189b87ee6d151b9971defb860e812)
Apple system fonts carry more than 32 gvar variation tuples per glyph
(SFNS.ttf: 54). Without the gvar-alloc heap spill, VariationTuples::reserve
fails past its stack capacity and outline_glyph returns None for most
glyphs — variable-font text renders completely blank.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit f401d4d567245809f4ede9aa2d4a4b54157367fd)
splash_bench geomean against work, best of alternating runs: +12.9% with the series as submitted (the per-instruction Option<String>::take in take_allocation_error alone was +9.8%), -1.1% with this commit.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The ws1 equality/fuel port made an uncaught script error Bail the whole
evaluation (vendor semantics). Splash's incremental eval_with_append_source
sets silence_errors because incomplete source inevitably raises errors that
are meaningless until the rest arrives, and its live widget tree relies on
evaluation continuing past them; with the Bail, `field := TextInput{...}`
never produced its child and
splash::style_tests::embedded_splash_restyles_its_isolate_without_replacing_edits
regressed (bisected to vm-port/ws1-equality-fuel alone).
Gate the Bail on !silence_errors: streaming evals keep drain-and-continue,
every other eval (including Octoscript's captured-sink evals) terminates on
the first uncaught error. Regression test added in vm.rs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgSir4cQyaakzPju4h6smw
(cherry picked from commit a4347332b38d0d511287006dc19f0c079604f78a)
(cherry picked from commit 6ee2aecfcb7d9296b501be5ea481caef1a4d024a)
Workstream 3 of the Octoscript VM patch port (heap, strings, arrays, objects).
- Array opcode reads and writes validate the index (finite, non-negative,
integral, representable) before touching storage: ScriptValue::checked_index
and ScriptVm::checked_array_index, applied at every array/pod index site in
opcodes_vars.rs and opcodes_assign.rs.
- Updating an existing untracked object field keeps its insertion order
(ScriptObjectData::map_insert), matching the tracked path.
- Numeric string conversion handles inline and heap strings alike and yields
a traced NaN for text that is not a number (ScriptHeap::cast_to_f64).
- Heap accounting: Octoscript's retained-heap cap is expressed over upstream's
ScriptAllocationBudget instead of a second parallel accounting system. A
persistent budget (heap_cap) is charged by the same charge_allocation calls
as the scoped with_heap_allocation_limit budget, its headroom re-derived
from a retained-capacity estimate by reconcile_heap_bytes (setup, GC sweep,
shrink_to_fit, host boundaries). Public API preserved for octoscript-core:
set_max_heap_bytes / max_heap_bytes / accounted_heap_bytes /
reconcile_heap_bytes / take_heap_limit_exceeded. Refusals surface through
take_allocation_error, so run_core bails uncatchably as before.
- Per-string ceiling: set_max_string_bytes / max_string_bytes /
take_string_limit_exceeded, enforced on exact lengths at the store choke
points (new_string_from_str, new_string_concat, intern_or_store_string and
its preflighted variant, check_intern_string), plus ScriptStringSink,
ScriptStringBuffer, new_bounded_string_with and temp_bounded_string_with
(bounded by the string ceiling and the remaining allocation budget) for
hosts that build strings incrementally. cast_to_string is generic over the
sink. Byte-array parse_json builds its lossy text through the bounded buffer.
- Pod creation is charged; ValueMap/ScriptArrayStorage/ScriptObjectData expose
retained_bytes for the estimate.
Retired in favor of upstream: per-path capacity preflights in array_heap.rs /
object_heap.rs (charge_allocation already meters sparse growth), the sink
generalization of to_json/percent/regex builders (upstream preflights exact
lengths), array_mut_with in vec_prims.rs (host conversions are covered by
reconcile_heap_bytes at the host boundary).
Tests: invalid index reads/writes leave storage untouched, numeric conversion
variants, insertion order, capped sparse growth (array, object vec, object
map) refused before mutation, string ceiling at the store paths and in the
bounded buffer, byte-array to_string/parse_json limits, lossy UTF-8 parity,
scoped budget nesting inside the cap.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgSir4cQyaakzPju4h6smw
(cherry picked from commit ce4bbe5980f38fd0206f06fe27568528bd49a394)
(cherry picked from commit b21de9ff271d523f52b6154df3c7ef84048772d7)
Ports the parser, tokenizer and control-flow part of the Octoscript
makepad-script patch set (PATCHES.md, grammar v0.2) onto the September
`work` revision, by hand, area by area.
Decisions per PATCHES.md item:
1. Logical/comparison precedence, streaming, `!`: ADAPT. Upstream already
patches a pending ShortCircuitEnd during auto-close but forgot the
operator: ShortCircuitEnd now retains `what_op` so a tighter logical
operator keeps a looser left jump open (`a || b && c`), the checkpoint
restores the original TEST opcodes on continuation, comparisons (14)
bind tighter than equality (15), and NOT always negates truth
conversion instead of doing a bitwise NOT on f64-stored numbers.
Upstream's `last_jump_target` bookkeeping is kept at every patch site.
2. Canonical `try protected catch fallback`: PORT. `catch` is a one-shot
contextual separator carried in TryErrBlockOrExpr checkpoint state
(allow_catch/canonical_catch/protected_was_block); block branches keep
their tail value by removing the inherited pop-to-me marker before
recomputing the jump; TRY_ERR now uses its encoded relative distance
and the parser adds the extra TRY_OK skip only when legacy `ok`
follows. Legacy catch-less `try a b [ok c]` still parses (the checker
in Octoscript's own crates restricts it to the compatibility entry).
3. Cross-call unwinding: PORT. handle_errors searches all call frames
(call_stack_has_try), pops younger script calls restoring slot_base
and the return ip's body, then applies the try-frame cleanup/jump.
Hard bails stay on ScriptTrapOn::Bail.
4. Loop back-edges: ADAPT onto upstream's reset_iteration_scope fast
path: truncate_loop_iteration_bases discards iteration-local tries,
operand values and mes before the scope reset; plain loop/while keep
their iteration scope and free nested ones. Hard time-budget bails
drain their diagnostic; OK_END with no try frame bails.
5. Field-assignment reverse-pair walk: PORT (stop at a 1-opcode chunk).
6. Prototype-field `:` rewrite: PORT (chain must begin with an id,
paired insert through insert_code_with_source keeps opcode/source-map
lockstep); unavailable rust-value index is a parse error.
7. Numeric-boundary tokenizer: PORT (`_` stays in the pending number
instead of moving to Whitespace with stale text) plus the char-count
token length so a multibyte identifier cannot underflow `pos`.
Tests: inline parser/tokenizer/vm regressions and tests/try_catch.rs
(legacy syntax, block/expression branches, nested and cross-function
recovery, contextual `catch`, checkpoint restoration, loop cleanup,
streaming appends, precedence and effects, tokenizer boundaries).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgSir4cQyaakzPju4h6smw
(cherry picked from commit 8231a13906cfb339b496ff78b687e43a48e4e11b)
(cherry picked from commit a1f7afb543c8a737f9c56936bfcba605097f88df)
Port of the equality / fuel / error-bail slice of Octoscript's makepad-script
patch set onto the September `work` base.
equality (PORT, string compare ADAPTED): `deep_eq` moves from heap.rs into
the new equality.rs as an iterative worklist that visits each container pair
once (cycles and shared DAGs terminate), keeps NaN unequal to itself, and
charges one work unit per processed pair, queued edge and typed-array
element, capped by MAX_EQUALITY_WORK = 65,536 per comparison. Upstream's
69d78873e string early-out is kept instead of the patch's chunked byte
compare: every heap string is interned (string_heap.rs) and short strings
are inline, so string equality is exactly bit equality and a string pair
costs one unit. The raw host `ScriptHeap::deep_eq` is iterative and
unbounded and consumes no VM fuel. The `==`/`!=` opcodes go through
`deep_eq_bounded`: each unit charges one instruction of
`instruction_limit_remaining`, the hard deadline (now an f64 on the
platform clock) is sampled every 256 units so trivial comparisons never
touch the clock, and exhaustion drains diagnostics and raises an
uncatchable Bail, like the instruction limit.
uncaught errors (PORT): `handle_errors` without an active try frame drains
the diagnostic once and sets `ScriptTrapOn::Bail(error)`, so no later
instruction or host effect runs and `eval` returns the error value; active
`try` handlers recover exactly as before. The hard time-budget bail drains
its diagnostic before unwinding, as the instruction-limit bail already did.
allow_debug_output (PORT): new host-controlled `ScriptVmBase` flag,
default true so raw makepad debugging is unchanged. When false the `~` LOG
opcode raises a catchable not-allowed error and `ScriptVm::log` is a no-op.
Incidental VM prints are removed: run_core's `log!` traces, the undefined
opcode `eprintln!` (now a bail) and the loop "unknown state" `println!`
(now a bail). mod_std.rs needs no change: std.log already routes through
the gated `ScriptVm::log`.
Tests: platform/script/tests/equality_fuel_bail.rs covers cycles, shared
DAGs, NaN, string/number semantics, the host deep_eq on typed arrays and
beyond the ceiling, instruction fuel charging, the work ceiling being
uncatchable, the in-comparison hard-deadline check, the hard-budget drain,
uncaught-error bail with try recovery, and the debug-output flag.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgSir4cQyaakzPju4h6smw
(cherry picked from commit a44f8678ed68b20a0c413d607c07a37d55186fbe)
(cherry picked from commit 353fa369faa5672e075dd874a431dc928420bafb)
Port of Octoscript's re-entrancy and hardening items onto the new VM host contract.
- vm.rs: run_core no longer caches a raw pointer into the active body's opcode
vector across native calls. Each opcode is copied through a scoped
`bodies.borrow()` that ends before dispatch, so a native handler that
re-enters `eval` and replaces the body's parser cannot leave a dangling
pointer. Regression: reentrant_reload_of_the_active_body_does_not_keep_an_opcode_pointer.
- thread.rs: ScriptThreads::set_current validates the index before updating
the cached pointer (set_current_thread_id routes through it); update_ptr is
bounds-checked via get_mut; cur/cur_ref/trap use release-mode assert!.
Regressions: selecting_an_unknown_current_thread_panics_before_pointer_update,
empty_threads_reject_current_access_in_release_builds.
- handle.rs: ScriptHandleGc: Any; is/downcast_ref/downcast_mut compare
Any::type_id, removing the overridable ref_cast_type_id hook.
Regression: handle_downcasts_use_the_concrete_any_type.
- suggest.rs: value previews truncate at character boundaries.
Regression: preview_truncation_preserves_utf8_boundaries.
- libs/regex utf8.rs: iterator entry uses `self.range_stack.pop()?` instead of
a never-advancing `while let`, replacing upstream's #[allow(clippy::never_loop)].
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JgSir4cQyaakzPju4h6smw
(cherry picked from commit ee7729e71c1408f61ec8f9083a79bdf9117d31a7)
(cherry picked from commit cdbc33e8ac1683fcdeb6e9dcb076e26ce5d23360)
Pressing a radio grabbed key focus immediately, and the theme draws the focus
ring in the same color as hover, so a press dragged off the button left what
looks like a stuck hover highlight behind.
Move `set_key_focus` to the completed click, so a cancelled press leaves no
mark. Keyboard focus still arrives through `Hit::KeyFocus`.
* HtmlLink: don't keep the hover color after a touch release
`Hit::FingerUp` played `hover.on` whenever the release was over the link, and
that state sets `pressed` as well as `hovered`. Touch never sends a
`FingerHoverOut` afterwards, so a tapped or long-pressed link kept the red
pressed color until its list item was recycled.
Guard that branch with `has_hovers()`, the way `TextFlowLink` and `Button`
already do.
* HtmlLink: let `hover_color` show while hovering
`hover.on` snapped `pressed` to 1.0, and `draw_walk` checks `pressed` before
`hovered`, so a hovered link always drew `pressed_color` and any `hover_color`
was dead. `TextFlowLink` carries the same animator but checks `hovered` first,
which is why it never showed there.
Clear `pressed` in that state, like `Button`, `CheckBox` and `Markdown` do.
Its `from` clause already fades `pressed` over 0.01s, which only makes sense
fading to zero.
* RadioButton: don't keep the hover tint after a touch release
`Hit::FingerUp` played `hover.on` unconditionally, and touch never sends a
`FingerHoverOut` afterwards, so a tapped radio kept its hover tint until
something else redrew it. Releasing the mouse away from the button left it
tinted too, since the arm never checked `is_over`.
Guard that branch with `is_over` and `has_hovers()`, the way `Button` and
`TextFlowLink` already do.
* RadioButton: only select when the release is over the button
`Hit::FingerUp` selected and emitted `Clicked` without checking `is_over`, so
pressing a radio and releasing anywhere else still selected it. `Button` gates
its click on `is_over`; do the same here.
A Splash isolate receives makepad's own mods and nothing else, so a widget
type defined in host code is unnameable from a mounted body. Octoscript-Makepad
hit this with OctoscriptTap: unable to name it, the generated body had to
target a Button, whose handle_event captures the finger on touch-down, so
every tappable row starved the scroll it sat in.
register_splash_isolate_mod(fn(&mut ScriptVm)) records an installer; each
isolate runs the registered installers as the last step of its allocation.
Last matters: it is after the ambient-authority strip (fs, run, res,
cx.quit) and after the jailed fs and brokered host re-registrations, so a
host mod cannot be removed by that pass and sees the isolate's final
namespace. Host code is trusted and already chooses what it installs.
The registry is a thread-local because the caller has no Cx in hand, and is
collected before running so an installer may register another. Mods are
taken at allocation, so a registration only reaches later isolates.
Test covers the contract in both directions: an isolate allocated before
registration does not resolve the probe, one allocated after does, each
allocation installs again, and the earlier isolate stays unchanged.
makepad-widgets: 207 passed, 5 failed — the same 5 that fail on the
unmodified branch (desktop_style, grid x2, widget_tree x2).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GR1KyERPDtrF9FgZ9HvWqt
(cherry picked from commit 227aa3625b8e53ab1ff3f153f63933564849640d)
(cherry picked from commit 9addb746b2497abd9fe530aebb68b3c3d99b0ccf)
* Tooltip: position anchored tooltips in the same draw
`CalloutTooltip` drew itself invisibly, waited on a 5ms timer, then read the
drawn size back and re-applied margins/widths until the height stopped
changing. When the timer beat the paint it read a zero rect, gave up, and left
the tooltip opened but invisible.
* base `Tooltip` gains `show_anchored`: it draws `content`, measures it, and
shifts its align range into place (`Tooltip::place` flips to the roomier
side, clamps to the safe area, and keeps `gap` from the anchor and edges)
* `CalloutTooltip` now derefs `Tooltip`, measures its label each draw to pick
the wrap width, and writes the callout edge/offset into the shader post-draw
* Tooltip: ignore zero-delta scroll events
macOS sends a zero-delta `Scroll` whenever fingers touch or rest on the
trackpad (`ScrollPhase::Touched`, and `Began` for a two-finger rest). That hid
every open tooltip, and since the pointer never left the target, nothing showed
it again. Only a scroll that actually moves counts as an interaction now.
* Wayland: restore a maximized window maximized, not fullscreen
`configure_window`'s `is_fullscreen` is the legacy maximize-or-fullscreen
flag, and on Wayland the reporting side says so out loud: `wayland_state`
builds the geom with `is_fullscreen: is_fullscreen || is_maximized`. But
`WaylandWindow::new` took that same bool and called
`toplevel.set_fullscreen(None)`. So an app that persists `is_fullscreen()`
on exit and feeds it back through `configure_window` on the next launch --
Robrix does exactly this -- turns a window the user had merely maximized
into true compositor fullscreen, and the escalation sticks: the next save
records the monitor size rather than the work area.
Wayland was alone in reading the flag that way. x11 creates with
`self.maximize()` (`_NET_WM_STATE_MAXIMIZED_HORZ/VERT`) and reports
`get_is_maximized()` back; win32 creates with `ShowWindow(SW_MAXIMIZE)`
and reports the `WS_MAXIMIZE` style bit. Both round-trip. Only macOS takes
the flag literally, and AppKit keeps a menu bar and traffic lights there.
* Create with `set_maximized()`.
* Seed `is_maximized` from the request rather than `is_fullscreen`.
`wayland_is_fullscreen` is copied out of that field before the first
configure arrives, so the old seeding claimed fullscreen from frame one,
before the compositor had confirmed anything, while `window_geom` still
said `is_fullscreen: false`. The two signals no longer disagree.
* `should_show_csd_shadow` gets the request as `maximized`; same answer as
before, now for the right reason.
An app that wants to come up genuinely fullscreen still can:
`WindowHandle::fullscreen()` during `Event::Startup` queues
`FullscreenWindow` behind `CreateWindow` in the same FIFO drain.
Also corrects the docs this contradicted. `configure_window` claimed
`inner_size` and `position` are ignored when fullscreen and that the
window is sized to the monitor, which is true on no backend now, and
`maximize()` claimed macOS zooms when it calls `toggleFullScreen:`.
* Wayland: keep our window chrome up in fullscreen
Under client-side decorations we draw the title bar and the min/max/close
cluster ourselves, and we were hiding both the moment the compositor put
the toplevel in fullscreen. The compositor draws nothing in their place,
so the window ends up with no chrome at all -- and the max button is the
only path to `RestoreWindow`, so there is no way back out. There is no
F11, no Escape, `Window::handle_event` has no `KeyDown` arm, and the View
menu is inert outside macOS. That leaves the compositor's own keybinding,
if the desktop happens to have one bound.
Hiding chrome in fullscreen is the right call when the OS supplies its own
-- macOS has an auto-hiding menu bar and traffic lights there, which is
why that arm stays as it is. Wayland supplies nothing, so ours stays up.
* `sync_caption_bar_state` drops the `wayland_fullscreen` terms: the
caption bar and the buttons now follow `custom_chrome` alone.
* Fill `window_chrome_buttons` in the geom in fullscreen too. It is the
transitional hit-test rect `WindowDragQuery` falls back on before the
widget layout is known, so leaving it empty made the first clicks after
entering fullscreen read as a caption drag instead of a button press.
* Let the caption's own gestures through in fullscreen -- they were gated
behind `!is_fullscreen` on a bar that could not be visible then anyway.
Double-click now unsets fullscreen first: `set_maximized` under it does
nothing, so the bar would have looked dead.
`is_wayland_fullscreen()` loses its only caller but stays public: it is
the only way to tell true fullscreen from maximize, which the conflated
`is_fullscreen()` cannot. Say so on `WindowGeom::is_fullscreen` too, since
reading it as real fullscreen is what started this.
* Window: drop hide_caption_on_fullscreen, a dead trap
The `WindowGeomChange` arm hid the caption bar whenever the geom flipped
to `is_fullscreen`, on `Windows | Macos`. On Windows that flag is literally
`get_is_maximized()` (`win32_window.rs`: `is_fullscreen:
self.get_is_maximized()`), and Windows draws its own chrome, so this would
have stripped the close button on a plain maximize -- the same trap just
fixed on Wayland, one `#[live]` default away from firing.
It never fired, and could not have: `hide_caption_on_fullscreen` is set
nowhere in makepad or in any app (grep finds no other mention, DSL
included), and `sync_caption_bar_state` re-decides caption visibility on
every event through `ensure_initialized()`, so it overwrites whatever this
arm set. The macOS half it duplicated lives there already.
Deleted rather than repaired: the whole caption policy belongs in
`sync_caption_bar_state`, and a second copy that keys off a flag meaning
different things per platform is what produced the bug in the first place.
* macOS: restore() no longer enters fullscreen
`restore()` and `maximize()` were the same call, `toggleFullScreen:`, so
`CxOsOp::RestoreWindow` on a window that was not fullscreen put it *into*
fullscreen. The Window widget's max button hands `restore()` whatever
`is_fullscreen()` reports, which on macOS is the real NSWindow fullscreen
state -- so this only misfires when something else pushes `RestoreWindow`
on its own, but then it does the exact opposite of its name.
Guard on `is_fullscreen`, the field the fullscreen delegates maintain.
* x11: implement FullscreenWindow and NormalizeWindow
Both fell through to the catch-all `Not implemented on this platform`, so
`WindowRef::fullscreen()` and `disable_fullscreen()` silently did nothing
on x11. `_NET_WM_STATE_FULLSCREEN` was never even interned -- the atom
table only carried the two maximize atoms.
* Intern `_NET_WM_STATE_FULLSCREEN`, and split the `_NET_WM_STATE` client
message out of `restore_or_maximize` so the fullscreen requests can
reuse it instead of copying the send.
* `get_is_maximized` becomes a thin caller of `has_net_wm_state`, which
`get_is_fullscreen` shares.
* `get_window_geom` reports `maximized || fullscreen`, the same union
Wayland reports and the meaning the flag already had. Creation still
maps the flag to `maximize()` alone, so a persisted `true` cannot come
back as fullscreen -- the bug this branch opened with.
* `RestoreWindow` drops both states, matching the Wayland arm: a caller
restoring off `is_fullscreen()` means "make it small again", and the
union does not say which of the two is set.
* Wayland: go back to the floating size when leaving maximize
An xdg_toplevel configure of 0x0 means "pick your own size", which is what
the compositor sends on the way out of maximize or fullscreen. We fell back
to `window_geom.inner_size` -- the size we were maximized at -- so the
window came out of maximize still covering the work area, with nothing to
bring it back down. Creating a window maximized made it permanent: the
floating size was never recorded anywhere.
Track the last size the window actually floated at, and use that for the
0x0 case. `is_floating` excludes tiled as well as maximized and fullscreen,
so a half-snapped window does not get recorded as the floating size.
A configure that does carry a size is still obeyed exactly as before, which
is both what the protocol requires and what a user drag-resize produces.
Seen with a window created maximized and then normalized: 3383x1408 before,
the requested 900x600 after. Note this only covers the case where the
compositor defers to us -- GNOME sends a concrete size after unfullscreening
a window that was maximized first, and we honor it.
* Linux: tell the app when the pointer leaves the window
Neither Linux backend ever sent `Event::MouseLeave`. Windows has sent one
since forever, and `Hit` handles it -- `finger.rs` returns `FingerHoverOut`
for whatever area still holds the hover -- but on Wayland and x11 nothing
produced it, so the last hovered widget kept its hover the entire time the
pointer was outside the window.
That is what makes the window chrome buttons flicker. Hover close, move the
pointer off the top of the window, and the button stays lit; the hover only
clears on the first motion after the pointer comes back, so returning to the
window makes the button flash off. It is most obvious on the chrome buttons
because they sit against the window edge, where leaving the button and
leaving the window are the same gesture.
It also broke re-entry. `FingerHoverOut` does not clear the stored hover
area -- `cycle_hover_area` does, once per event -- so with no leave event
`hover_last` still named the button, and coming back over it returned
`FingerHoverOver` instead of `FingerHoverIn`. Widgets act on HoverIn, so a
button could fail to light up at all on the second hover.
* Wayland: emit it from `wl_pointer.leave`, guarded on `pointer_window`
being set, which it is only for the toplevel surface -- leaving a CSD
shadow gutter has no hover to drop.
* x11: finish the `LeaveNotify` TODO that has been commented out in
`xlib_app`. The condition it had, `detail == 4`, would not have fired
anyway: a real pointer-out-of-window here reports detail 3
(NotifyNonlinear). Take any detail except NotifyInferior (the pointer
only moved into a child), and only mode NotifyNormal, so a grab or
ungrab does not drop the hover mid-drag.
* Both event loops then `cycle_hover_area` + `switch_captures`, the same
pair the MouseMove arm uses and the same thing the Win32 arm does.
Verified on both backends with a probe on the hover in/out arms: before,
leaving the surface logged nothing; after, HoverOut fires on leave and a
fresh HoverIn on re-entry.
* DesktopButton: cross-fade the hover background premultiplied
The chrome buttons flash dark for an instant when the pointer leaves them.
Not a hover-state bug -- the `hover` instance really does fall 1.0 -> 0.0
monotonically over the 100ms fade. The dip is in the shader.
`bg_color` is `#00000000`: transparent BLACK. Mixing it toward an opaque
`bg_color_hover` in straight-alpha space ramps rgb up from black as well as
alpha, and `sdf.fill` then multiplies rgb by that same alpha again, so what
reaches the premultiplied blend is `rgb * h^2` against coverage `h`. Over a
caption bar of luminance C the composite is quadratic in h and sags well
below both endpoints in the middle.
Measured on a #F3F3F3 bar with the `#E9E9E9` hover face this file's callers
use (widgets/src/window.rs), sweeping hover across the button's width:
hover 0% 30% 52% 80% 100%
before 246 207 199 212 238
after 246 244 242 240 239
The endpoints are 8 levels apart, so the intended highlight is nearly
invisible -- and the 47-level excursion between them is the only thing the
eye catches. It is symmetric, but on the way in it reads as arrival feedback
and on the way out it is a dark flash left behind where the pointer just
was, which is why it gets reported as flicker on hover-out.
Premultiply each face before mixing and fill with `fill_premul`, so the
cross-fade is linear in `hover` and anti-aliasing blends in premultiplied
space too. `DesktopButton` is the only widget with a fully transparent base
colour feeding this pattern.
Remove the retired applications, asset-specific libraries and DJ pack tool
from Makepad, together with their workspace and launcher entries. All 981
removed source paths are accounted for in the private Stage repository.
Keep public AI chat generation through the AI Hub's generic job runner.
Extract shared SHA-256 and UDP binding into core_util so the public hub and
model crates no longer depend on the relocated asset libraries. Preserve
the retained public coverage in the split wasm validation script.
Validation on the exact cleanup tree in an isolated checkout:
- Release checks: core_util, model, aichat, WM and Builder.
- Release builds: aichat, WM and Builder.
- Existing tests: core_util 5, model 30, aichat 7, Builder 2 passed.
- Core/model checks: wasm32, Linux and Windows passed.
- No warnings in the successful checks, builds or tests.
Known baseline: WM library tests do not compile because the unchanged
style-transition assertion compares seven expected weights with eight.
The unrelated working-tree correction is intentionally outside this commit.
The SDK came from `env!("CARGO_MANIFEST_DIR")`, so every copy of the binary had
its own NDK at its own path. Switching copies changes `CMAKE_C_COMPILER`, and
cmake then deletes its cache and re-configures *without* the `-D` flags, losing
`CMAKE_SYSTEM_NAME=Android`. Deps like `aws-lc-sys` then build for the host and
Darwin forces `-arch arm64` into the NDK clang.
* default to `~/.makepad/<host-dir>`, independent of which binary runs
* migrate an existing per-checkout SDK with a single `rename`
* ScrollBar: add `show_handle` for a view that scrolls without a grabbable bar
The handle is both the visual and the hit target, and `show_scroll_x`
gates wheel/trackpad input too, so there was no way to keep a view
scrollable while dropping the bar a user can click.
* `show_handle: false` skips drawing the handle and its hit test.
* Wheel, trackpad, finger drag and the scroll API are untouched.
* ScrollBarTabs: stop the invisible handle from eating presses on tabs
The tab-bar handle is transparent until hovered, and it runs along the
bottom of a strip whose tabs are exactly as tall as it, so it sits over
the lower edge of every tab. `TabBar` hands presses to the scroll bars
before the tabs, so while the strip overflows, a press near a tab's
bottom grabs a bar nobody can see instead of selecting the tab.
* Default `show_handle: false`, so `TabBar`/`TabBarFlat` scroll only by
wheel, trackpad and drag.
* macOS: re-arm the display links when a window leaves the Dock
`pause_display_link` only sets `setPaused: YES` and keeps the links, so
`display_link_needs_rearm` stays false and `ensure_timer0_started` never
clears `timer0_armed`. A work beat while minimized arms the NSTimer with
that flag set, so deminiaturize early-outs before anything unpauses the
links and the restored window paces on the timer, not its own panel,
until the next idle downshift.
Clear the flag first, like the pointer-capture release does.
* Vulkan: stop printing the loader's startup narration by default
`vulkan_debug_messenger_create_info` asked for `INFO` severity on the
`GENERAL` message type, which is the channel the Vulkan loader narrates
itself on. Every run dumped around ninety lines naming each directory it
searched for layer and ICD manifests, each manifest it found, and the
layer callstack it assembled, before the app had drawn anything. None of
it comes from the validation layer -- that only loads under
`MAKEPAD_VULKAN_VALIDATION` -- so it was noise on every Linux desktop,
Android and OpenXR run, on every machine.
* Subscribe to `ERROR | WARNING`, adding `INFO | VERBOSE` only when
`MAKEPAD_TRACE=vulkan.debug` is set. The driver skips the callback for a
severity we did not ask for, so the loader no longer formats the lines
either. Validation errors and warnings still print unconditionally.
* Route the informational branch of the callback through `trace!`, so it
carries the topic that enabled it like the `gl.*` and `shader.*` ones.
* `devices` logged a line per software device it stepped over, which fires
on any machine carrying lavapipe -- that is most Mesa systems. Move it to
`MAKEPAD_TRACE=vulkan.device`, and instead say so once when software
rasterizers were the *only* devices found, since every caller then
reports no usable device, which reads as if the machine had no Vulkan at
all rather than no accelerated one. What happens next is left to the
caller that decides it: `linux_wayland` already logs its OpenGL ES
fallback.
* Two Android camera-import sites used `warning!` for a plain dump of
image size and format on the success path; they become
`MAKEPAD_TRACE=vulkan.camera`.
A desktop Linux run now prints the one line that says what it got:
Vulkan: NVIDIA GeForce GTX 1070, graphics/present queue 0
`MAKEPAD_TRACE=vulkan` brings all of it back; the topics are hierarchical,
so `vulkan.debug`, `vulkan.device` and `vulkan.camera` also work on their own.
A test is a ci.splash beside what it tests; the script decides every input and the model only ever judges one picture against one acceptance text. mod.ci: launch (hidden, --remote, user_seq preserved), key, type_text, click, get, snap, wait_log (a * is a gap inside one line), no_errors, grab, quit; step, sleep, check, run; cargo, check_targets (the cargo makepad check matrix, check only for platforms we are not on, a test fails if the two tables drift), test, build, machine (another box over the makepad tunnel), exclusive; judge, accept, ask. The watcher polls git ls-remote once a minute for work and any extra branches, syncs a checkout the CI owns, runs the root script first and alone, then the rest up to a parallel limit behind one shared model judge. The window is a wall of squares, one per script: green passed, orange warnings, red failures, with a detail panel for the selected one.
Scripts: the root ci.splash (workspace check with core warnings denied, the tests), apps/wm (desktop up, switch to macOS by Cmd+Space / type / Return, launch the terminal and the browser, each waited for by the WM's own first-frame line), and one per main app in the default shape. Proven here: apps/wm/ci.splash green in 280 s, fifteen target checks and seven vision verdicts.
Models come from Hugging Face through the hub: registry entries qwen3.5-4b-vision and qwen3.5-9b-vision with exact revisions, sizes and digests, and hub-install, a command line over LocalModels::start_install. vlm-probe reads PNG.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
iOS: VideoFileDecoder::open_audio guarded its Apple path with macOS alone, so iOS fell through to UNSUPPORTED, a constant no Apple target has. It takes the same split as every other entry point in that file.
tvOS: libs/apple_sys opened with a crate guard of macOS or iOS, so on tvOS the crate compiled to nothing and every msg_send! user lost the macro; the crate guard and its 27 inner guards of that shape now name tvOS, and MTLCopyAllDevices is macOS only, which is where it exists. The platform's Apple video playback, player and YUV modules, the 17 guards of the Metal NV12 video path and the texture-pool imports follow, and the tvOS app gains try_metal_device, the lookup the texture adopt path already calls on iOS.
wasm: the window manager's dylib host needs a process, a linker and a loader, so it is native only; the web gets a stand-in with the same surface that refuses every compile through the queue the native host answers on, and libloading is a non-wasm dependency.
Windows: three Unix-only uses in apps/wm/src/clients.rs (Cx, CancellationToken, the grace argument) are guarded to match where they are used, tests included.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`pause_display_link` only sets `setPaused: YES` and keeps the links, so
`display_link_needs_rearm` stays false and `ensure_timer0_started` never
clears `timer0_armed`. A work beat while minimized arms the NSTimer with
that flag set, so deminiaturize early-outs before anything unpauses the
links and the restored window paces on the timer, not its own panel,
until the next idle downshift.
Clear the flag first, like the pointer-capture release does.
The CEF follow-up (libs/cef, widgets/src/browser.rs, apps/browser): BrowserOptions with software frames, evaluate_javascript answered as JSON or the exception text, console messages taken by the embedder, editable_focus, with_cef_browser on the widget, the profile flushed on Event::Shutdown, and a repr(C) mismatch in the FFI fixed.
platform/video: VideoFileEncoder::new_fragmented lays the container down in movie fragments (AVAssetWriter's movieFragmentInterval), proven by tests/fragmented_growing.rs: 90 of 120 frames readable before finish, all 120 after. Windows and Linux write one movie as before. The Apple backend's plain constructor went with it: nothing called it once the fragment-aware one existed.
platform: the audio output fence is seated in the one seam (media_api.rs), so a panic in an app's output closure costs that closure and its buffer, not the device thread, and the taps are fed the silence so a recording keeps its place; its tests adapted to this tree's tap registry. /midi routes inject a message as if a device sent it, declare ports for the app to adopt, read back what the app sent, and reset (platform/src/midi.rs, remote.rs).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The child draws its frame through a texture pass, which on GL renders through an inverted projection, so its glReadPixels rows come in picture order already; the host keeps them as they are, and the two run views sample the texture as stored on every path. The old shader flip on the software path inverted it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A custom-camera pass used to keep GL's bottom-up storage while the 2D passes were inverted, so a 3D scene rendered to a texture came out upside down on Linux and Android GL and nowhere else; it now uploads an inverted copy of its projection as the web backend does. Backface culling follows with a clockwise front face on those passes, and a target allocated taller than its pass keeps the pass at row 0 instead of the far end.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The resolve flipped V and mirrored the whole window on Metal, grab-verified the wrong way round; the mask flipped on Metal and iOS alone. Both textures are ordinary passes drawn with the 2D camera, top-left on every backend.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 2D camera is GL-style: the top of a pass rect lands at clip y = +1 on every backend. Vulkan's clip space points down, so a pass drawn with that camera has to go through a negative-height viewport, window and capture alike; the window comes out upright and a capture's rows are stored top-left like Metal's. fbfec26ad made both viewports positive to cure an inverted phone desk, and every desktop Vulkan window stood on its head (the Scope report of 2026-09-20). The desk was inverted by its own OS-keyed consumer flips, not by the viewport, so those go: the gauss stack's per-OS flip and its callers, the phone shell's three Android flips and its y_flip shader term, the dock warp's capture_y_flip and its term. The direct display's letterbox blit keeps its positive viewport: its own vertex shader maps uv.y = 0 to clip -1.
Seen right side up by eye on the Arch RTX 5090 box: apps/wm as a Wayland client under sway, the hosted apps inside it, and the linux_direct WM on the panel; and on the Pixel 11 Pro XL the wm-dyn super-app (Vulkan, no GL fallback). Codex review in the session's notes endorses the restores and removals and leaves two flips for a later look: the SSAA resolve's 1.0 and the map shadow mask's Metal flip, both untouched here.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Taken from vjroger/work. A full merge of that fork fights this work branch
in widgets; this is the stems crate, vocal mel-band, span-cache lanes,
log_ring, output fence, midi inject, effect_doc, mp3 sniff, mp4 audio
edit, and audio-only file open. Stage on origin/main cargo-checks again.
Every manifest `app_main!` emitted carried three fonts no theme role uses:
`NewCMMath-Regular.otf` for `MathView`, and `Inter.ttf` / `RobotoFlex.ttf` for the opt-in
iOS and Android platform styles. That was 3.6 MB in every package, and it only existed
because a font an app forgot to declare failed silently: `FontFamily::update_font_definitions`
skipped a member whose bytes never arrived, so the text showed as boxes with no log line.
* Drop the forced extras. The manifest is now the font set's fallback chain plus whatever
the app puts in `font_assets`, which is what makepad's own apps already did for `Inter`.
* `font_assets` takes expressions, and `INTER_FONT_ASSET` / `ROBOTO_FLEX_FONT_ASSET` join
`MATH_VIEW_FONT_ASSET`, so an app declares a font by name instead of by path.
* A font that never loads now logs one `error!` naming the path and the fix. A missing
member still degrades gracefully: the family keeps its remaining members.
* The apps that use those fonts declare them: the `wm` family binds both platform faces,
`clock`, `weather` and `director` draw with Inter and can select any style, `terminal`
and the builder use Inter for symbols, `splash` and `aichat` use `MathView`.
`cargo tree` only prints a directory for path dependencies, so for a git
dependency we fell back to the `<crate>.path` file its build script drops
in the target dir. Any tool that prunes the target dir deletes that file,
and a warm cache means the build script never re-runs to recreate it, so
`add_resources` silently found no `resources` dir for `makepad-widgets`
and packaging failed with "font assets declared by makepad.font-assets.v1
are missing on disk". Ask cargo for each package's `manifest_path`
instead, keeping the `.path` file as a last resort.
`drm_sys` carries `#[link(name = "drm")]` but was compiled for every non-Android
Linux target, so an ordinary desktop build fails to link on a machine without
libdrm, even though nothing outside the DRM/KMS backend calls into it.
* Gate the module on `linux_direct`, the same cfg its only callers already
carry: `drm_native_resolution` in `vulkan_linux` and the `direct` module.
`NSView.displayLink` never fires for a window in the Dock, and the 0.2s
NSTimer fallback only starts from inside the timer 0 branch that the link
drives. So minimizing froze the UI thread, and Ctrl+C / SIGTERM / SIGHUP
sat in `REQUESTED` until the window came back.
* swap the paint clock on `windowDidMiniaturize:`/`windowDidDeminiaturize:`
* skip link pacing while every window is miniaturized
* termination worker restores `SIG_DFL` if it gives up, so the process
can't end up unkillable
A record filed as "Café del Mar" could not be found by anybody typing
"cafe". The tokenizer breaks a run at every non-ASCII character, so the
title indexed as `caf` + `e` and the query asked for `cafe` — three
terms that never meet. Which spelling a name happens to carry was
deciding whether it was reachable, and nobody decided that.
The two sides are now deliberately asymmetric, and the law is one line:
index(text) = tokenize(text) ∪ tokenize(fold(text))
query(text) = tokenize(fold(text))
so the index is a superset of what any query can ask, and folding is
idempotent, which means "café" and "cafe" ask exactly ONE question —
they have to, or the cursor fingerprint and the ranking would disagree
between two spellings of one word. The fold happens to the TEXT before
tokenization, because by the time there are terms the accent has
already split the word and there is no per-term place to hang a folded
form.
The fold is this repo's own twenty-arm Latin table, not canonical
decomposition: decomposition cannot tell you that 'ß' is "ss" or 'æ' is
"ae", and this repo already answered both, tested, inside the
importer's alias slugger. That table moves to the data crate so the
alias a record is filed under and the terms it is found by fold by
construction rather than by two tables happening to agree; the
slugger's own locked assertions pass untouched, which is the proof the
move changed nothing.
Two guards for the two ways this goes wrong quietly. An ASCII text
takes a fast path that provably tokenizes identically, so every
existing field indexes bit-for-bit as before. And a term both passes
find is counted at the HIGHER of the two frequencies, never their sum —
doubling a weight fails no assertion about which rows come back, only
about the order they come back in, so it would have shipped silently.
The index holds terms, not the rule that made them, so a catalog
written before this holds the wrong ones: schema 15 rebuilds every
posting row from the annotations that produced it. It is a
recomputation from data the store already has, not a repair, and it
takes the same builder the writer uses so the two cannot drift. It
deliberately does not enforce the index-term budget — these rows were
admitted under it already, and failing a migration on a budget would
leave a catalog nobody can open. Alias postings are untouched: an alias
is `[a-z0-9_-]` by construction, so the fold is the identity on every
one and rebuilding would be a whole-table cost for a guaranteed no-op.
Both openers carry it, the server's migration ladder and the embedded
one, and the embedded gate now admits a v14 root rather than refusing
it.
Verified against a real 3.2 MB catalog: it migrated 13 -> 15 on open,
after which "chloe" finds a track titled "…(Chloé Caillet mix)" and so
does "chloé"; "chlo", the truncation the old index held, still answers,
which is the superset law showing up as behaviour; and the ASCII
searches around it return exactly what they did before. The migration
was not perceptible on that catalog, but I could not isolate its cost
honestly — it commits inside the WAL and the store opens lazily — so no
number is claimed here.
The store's integration tests cannot link in this environment
(sqlite3.lib, pre-existing and unrelated), so the four end-to-end tests
added here are unrun locally; the tokenizer law itself is covered by
unit tests that do run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit d61b9dd4fe3c682c6e597e894f17c4c3a8b4e296)
A path that bends tighter than the stroke's half width has no inner
offset curve: the per-point inner miter points run backwards along the
path and the strip folds over itself there, spikes on the inside of the
bend and doubly blended wedges across it. The true inner edge of such a
bend is the corner where the inner edges of the two segments around it
meet, so every point of the folded run now takes that corner as its
inner vertex and the strip fans around it. Bevel and round joins are
emitted as strip pairs whose inner side collapses to the miter point,
so the two segments' quads no longer overlap; only a segment shorter
than the stroke is wide keeps its two offset points.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
apps/flow-ui is the flow editor again: the graph canvas with its camera,
cards and progress bars, the node faces and inspector, the source and
App views, the running list, the menu bar and the toolbar with the run's
total bar, the template picker behind New, the hub model lists, and the
aichat bridge (`services`) with the port, event and worker wiring.
libs/flowgraph is the reusable graph-canvas widget with a display-only
view model and orthogonal wire routing; the review that removed the
two-fillet restriction from `valid_orthogonal` stays local.
libs/flow reports a turn's progress as stages: a `Stage { stage,
permille }` event carries the named phase (admission, download, load,
prefill, serving) and that phase's own fraction when the producer knows
it, while a node's whole-operation permille is optional and reaches
1000 only with Done; the hub's Loading fraction belongs to its phase
and restarts with each one.
makepad.splash lists the app for the Studio release runner; both crates
are workspace members.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Button: activate from the keyboard when focused
`Button` matched only `KeyFocus` and `KeyFocusLost`, so a button reached with
Tab could take focus and animate, but no key press ever activated it.
* `Space`, `Enter` and numpad enter now emit `Pressed` and then `Clicked`, and
make the same `on_press`/`on_click` script calls a tap does.
* Key repeat is ignored, so holding the key down doesn't re-press.
* Both arms are gated on `enabled`, like the finger paths.
* libs/ai: exclude hub_ui and services from the AI workspace
`cargo install --git ... cargo-makepad` fails with "package
`libs/ai/services/Cargo.toml` is a member of the wrong workspace": the root
workspace lists `libs/ai/hub_ui` and `libs/ai/services` as members, but both
sit under `libs/ai`, which is its own workspace, and cargo gives a package to
the first workspace root above it that doesn't exclude it. `libs/ai/hub` and
`libs/ai/livepipe` were already fine since each declares its own `[workspace]`.
`Button` matched only `KeyFocus` and `KeyFocusLost`, so a button reached with
Tab could take focus and animate, but no key press ever activated it.
* `Space`, `Enter` and numpad enter now emit `Pressed` and then `Clicked`, and
make the same `on_press`/`on_click` script calls a tap does.
* Key repeat is ignored, so holding the key down doesn't re-press.
* Both arms are gated on `enabled`, like the finger paths.
CefAudioHandler next to the paint handlers: Browser::enable_audio_capture
names a rate and a channel count, Chromium mixes the page down to it and
mutes the page's own output while the capture runs, and poll_audio drains
Started / Packet / Stopped / Error on the thread that pumps CEF.
A browser that never enables capture hands CEF a null handler, exactly as
the stub did, so nothing that embeds a page today changes.
The capture thread takes no lock the embedder can hold: packets go out
through a bounded try_send, their buffers come back through a try_recv
and are reused, a full queue drops the packet and counts it, and a lost
Started or Stopped is made up again from the stream epoch on the drain
side.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Measured on the Pixel 11 Pro XL (PowerVR): a pane swipe presented at
~84 fps on the 120 Hz panel, a vsync dropped every three to six frames.
simpleperf showed 22% of the CPU in the driver's shader compiler and 24%
in its render-target teardown: the Vulkan backend created a VkRenderPass
and a VkFramebuffer for every offscreen pass on every frame and destroyed
them after the fence, and on this driver each render pass compiles a
load-op shader. Offscreen draw render passes now live for the device
(keyed by formats and load/store ops) and framebuffers are cached per
render pass, attachment views and storage extent, invalidated through
texture retirement so they die after the frame that used them.
The app icons were re-tessellated from SVG every frame: one DrawSvg kept
one scale and the desk draws each icon at two or three sizes. A DrawSvg
keeps up to four meshes per device scale; the geometry pool defers frees
and releases them once per frame against the geometry ids the live draw
lists still name, so a retained draw call never sees its slot reused.
A font member whose resource can never load (the WM referenced Inter and
its other faces through `self:../../widgets/...`, unmapped in a package)
kept its family incomplete, and an incomplete family is redefined every
frame: the layout cache cleared, every label laid out again, the asset
reopened. Such a member drops out of its family once, logged, keyed on
the resource registry's generation so a resource that appears later is
asked for again. The WM names its fonts through `makepad_widgets:` and
reads the clock in-process on the UI thread instead of forking `date`
twice a second.
Android gains a `frame.cpu` trace (events, next-frame, draw and repaint
milliseconds per drawn frame) and a profileable manifest so simpleperf
can sample a release build. The dyn-pack tile proof tolerates the app's
own Dirty line after an engine rebuild.
After: SurfaceFlinger presents every swipe frame at 8.3 ms, the render
thread runs at ~45% instead of 85–97%, and the frame is paced by the GPU.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The contribution widened `StyleTween` to eight weights but left its two initialisers at
seven, so the window manager did not compile. It also declared `BlackOrange` second in
`DesktopStyle`, while the window manager reads the tween's weights by discriminant (1 is
macOS, 3 Windows 2000, 4 NeXTSTEP): every style after Omarchy would have driven the chrome
of the one before it. The new style is declared last, `ALL` keeps the order the sheets are
shown in, and `next()` walks `ALL` by place rather than by discriminant. An unused import
in a storybook story goes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squashed from vjroger/makepad `storybook-pr` at 086d25452 (1,332 commits on top of
6f1e44649; his last commit restored every path outside the contribution to upstream).
- apps/storybook: every component organised, documented and previewed live.
- widgets: about eighty new widgets (accordion, alert, avatar, badge, breadcrumb, calendar,
card, carousel, chat, chip, colour, command palette, date and time pickers, dialog,
dropzone, floating action and panel, form, hamburger, line and radial menus, kanban,
masonry, menu, nav list, pagination, pill nav, popover, progress, property inspector,
range slider, rich text, select, spinner, table, tabs, tag field, timeline, toast,
toolbar, tour, transfer, tree, waveform, wheel picker and more); theme tokens and a
theme store, themes mixed by weight with a legibility check, a twelfth style sheet in
black and orange; the data grid gains row selection, drag and reorder, heading tips and
alignment; the glass button is a water lens; the portal list keeps the wheel it uses,
stands down from a press another control holds, can keep a row on screen and rule the
gap under a short list.
- platform: sweep locks and scroll blocks nest, `is_mouse_held_outside`, per-axis
scroll-handled flags, `next_frame_is_pending`, owner-scoped scroll unblocking, a
hands-off marker for the remote bridge, exploded-view projection and focus.
- draw: the interior distance of square-cornered boxes, a pointer shape, and
`turtle_ancestor_clip`.
- wm: the style tween carries an eighth weight for the new sheet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`cargo makepad android dyn-pack` stages the relocatable checkout,
cross-builds host + engine from it through the ordinary Android build,
proves every tile's on-device command against that target/, packs the
APK with the phone toolchain, the checkout and target/ as streamed LZ4
tar parts, and rehearses the phone's first tile open from the packed
APK; `dyn-rehearse` runs that last gate alone. This replaces the
Python and shell pipeline that lived outside the tree. The stage
directory is tool-owned, every cargo phase runs under one controlled
environment recorded in the target's marker, rustc runs through
cargo-makepad itself as the remapping wrapper, and the APK is renamed
into place only after signing and the rehearsal. The host package
names its engine and tiles in [package.metadata.makepad.dyn]. Only the
three /system/bin/sh templates that run on the phone stay shell.
libs/rmeta is the rustc metadata header reader apps/wm used, now shared
with cargo-makepad; libs/tar gains a streaming ustar writer with GNU
long names; the zip writer streams to any sink so a 900 MB APK never
sits in memory.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The APK carries rustc, the checkout and a packed target tree as LZ4
frames. First compile streams them out of the asset manager into files/
and shows progress on the desk; later launches skip unpack when the
stamp matches. App crates keep a desktop Cargo.toml — dynamic-module is
empty — and rustc `--extern force:` binds makepad_wm_engine already in
the process so widgets stay the host dylib.
libs/lz4 grows a streaming frame codec and a makepad-lz4 CLI; libs/tar
unpacks those frames without buffering the archive. Android Vulkan
records two in-flight repaints instead of waiting every pass, and the
capture Y-flip applies only on the OpenGL fallback.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The root menu names the two entries Makepad WM and Makepad Apps, in that
order, below Compile and run Scope. Every entry in Makepad Apps now does
what the WM entry already did: set up any missing compiler, download the
sources, compile and open the app, then return to the list with the
selection kept. The nested per-app checklist and its Back entries are
gone, so the menu has one shape. The Builder's runbook follows.
The platform reads --focus since this morning, not MAKEPAD_FOCUS; the
macOS launch of Scope passes the argument.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
tools/makepad_builder replaces tools/makepad_loader: one build target
shared across app builds, workspace package selection, checkout
progress on the public Git API, detached built apps with a completion
state, waits for Windows security scans, manual retry after compiler
locks, dedicated-folder installer checks, catalog and runtime fixes.
tools/web_server and its scripts leave for github.com/makepad/webserver.
Arch USB clone/restore scripts, the qwen38 box scripts and the G-belt
serial test join tools/. docs/agents records the agent workflow and the
remote-control handoff protocol; AGENTS.md forbids vendored sources and
bulk imports. Cargo.toml lists apps/wm-dyn, libs/code_language,
libs/search, libs/tar, libs/loader_bundle and tools/makepad_builder,
and drops the two removed crates.
Squashed from work:
- Share Builder target across Makepad app builds
- Fix Builder workspace package selection
- Align Builder checkout progress with public Git API
- Detach built apps and show completion state
- Wait for Windows security scans
- Offer manual retry after Windows compiler locks
- docs: the agent workflow of record and the remote-control handoff protocol
- builder: dedicated-folder installer checks, catalog and runtime fixes; Windows job objects hold c_void handles
- tools: Arch USB clone/restore scripts, the qwen38 box scripts, and the G-belt serial test
- tools: the web server moves to makepad/webserver
- AGENTS.md: no vendored sources or bulk imports in the tree
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
apps/mail indexes local Apple Mail through the Envelope Index: Gmail
Inbox, Sent, Starred and Important are label rows, attachments open
from the message view, and a reimport button wipes the cache. apps/wm
hosts apps as on-demand dylibs on Android (apps/wm-dyn, with the module
apps' manifests and module.rs following). apps/terminal polls the child
with MpTerm::process_exited and resolves widget fonts through
makepad_widgets. Widgets: double-click selects a word in TextFlow,
links keep their hand cursor, and three stale tests follow the tree's
root rule and the mobile font policy.
Squashed from work (the apps and widgets parts of each):
- Index local Apple Mail with Gmail labels, attachments and reimport
- terminal: MpTerm::process_exited polls the child, and widget fonts resolve through makepad_widgets
- widgets: double-click selects a word in TextFlow; links keep their hand cursor
- wm: the Android super-app hosts apps as on-demand dylibs (apps/wm-dyn)
- widgets: three stale tests follow the tree's root rule and the mobile font policy
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
libs/sqlite_query reads WITHOUT ROWID tables through the index cursor,
refuses to write them, and the pager releases its process write slot on
drop. libs/tar is an in-repo tar reader with gzip through fast_inflate.
libs/git imports packed objects with bounded checkout writers and its
HTTP file responses carry a trailer. libs/code_language gains a Haskell lexer with literate (Bird) dialect
detection. libs/loader_bundle and libs/search are new;
libs/app_module carries the super-app module surface; libs/workspace
adds Settings.infinite_zoom and RendererChoice::gpu_env_value; libs/ai
builds without warnings across the hub, llm, metal and model crates;
windows-rs job object handles are c_void.
Squashed from work (the libs parts of each):
- Restore retained renderer support for Scope (libs/search)
- Share Builder target across Makepad app builds (libs/loader_bundle)
- Index local Apple Mail with Gmail labels, attachments and reimport (libs/sqlite_query)
- libs/ai: warning cleanup across the hub, llm, metal and model crates
- code_language: a Haskell lexer with literate (Bird) dialect detection
- git: packed imports and bounded checkout writers; HTTP file responses carry a trailer
- builder: dedicated-folder installer checks, catalog and runtime fixes; Windows job objects hold c_void handles (libs/windows)
- workspace: Settings.infinite_zoom, the experimental prepared map inside the glyph
- wm: the Android super-app hosts apps as on-demand dylibs (libs/app_module)
- libs/tar: an in-repo tar reader; the super-app unpacks its archives with it
- workspace: RendererChoice::gpu_env_value follows the platform's runtime GPU choice
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review of the retained renderer on top of the runtime GPU choice: a
refused GL retained upload skips the draw and dyn-uniform writes are
bounds checked; Vulkan draws retained publications whose CPU-side data
is empty and records a fresh retained transfer command buffer after
each submission; Metal stamps consumption for every encoded retained
item, empty ranges included; a refused WebGL retained upload is final
for that content.
TaskPool::new_with_priority sets the heavy workers' thread priority.
Remote control: keys no longer hold the gate, if_user_seq is optional,
and status waits through a stall; `--focus` brings the app to the front
as its macOS window opens. The package map, the remote activity ledger
and the GPU choice are owned Cx state, not globals.
Squashed from work:
- platform: a refused GL retained upload skips the draw; dyn-uniform writes are bounds checked
- platform: TaskPool::new_with_priority sets the heavy workers' thread priority
- platform: Vulkan draws retained publications whose CPU-side data is empty
- platform: MAKEPAD_FOCUS activates the app when its window opens on macOS
- platform: `--focus` brings the app to the front as its macOS window opens
- platform: Vulkan records a fresh retained transfer command buffer after each submission
- platform: Metal stamps consumption for every encoded retained item, empty ranges included
- remote: keys no longer hold the gate, if_user_seq is optional, status waits through a stall
- platform: a refused WebGL retained upload is final for that content
- platform: the package map, the remote activity ledger and the GPU choice are owned state, not globals
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Choose the Linux GPU backend at runtime and pace Wayland frames adaptively
A Vulkan-capable desktop Linux build (the `vulkan` feature, or
`MAKEPAD=vulkan`) now carries OpenGL ES as well and picks between them
when its event loop starts: Vulkan on Wayland when a hardware device
answers, OpenGL ES when none does (no driver, only a software rasterizer,
or an X11 session). `MAKEPAD_GPU=auto|gl|vulkan` overrides the choice, and
`MAKEPAD=gl` still produces an OpenGL-only binary. The feature stays
opt-in: the hosted (`--stdin-loop`) and direct renderers of such a build
are Vulkan-only, and Vulkan has no video texture import yet.
Wayland frame pacing
- Pace presents by what the backend and the session can actually do,
rather than by a fixed number (new `wayland/frame_pacer.rs`). Vulkan
runs two presents in flight only when the compositor offers `fifo-v1`
and the driver uses it; otherwise a second present would block inside
`vkQueuePresentKHR` on a callback an occluded window never receives.
OpenGL starts the next frame early only when its measured cost says the
swap would land after the outstanding callback is due, so cheap frames
are not committed twice per refresh. One present in flight, which is
what this did before, left a heavy scene at half the display rate.
- Bound the pacing gate at 250 ms so an occluded window cannot freeze the
app's clocks, and let pending screenshot requests through it.
- Treat WouldBlock on the display flush as transient.
Vulkan
- Bound the frame fence wait and the swapchain acquire on Linux instead of
waiting forever.
- Keep the per-frame packet arena mapped, recycle completed frame
resources on the window path, and ask for one more swapchain image on
Linux, where the pacing can keep two presents queued.
- Skip CPU devices unless `MAKEPAD_GPU=vulkan` asks for Vulkan explicitly.
OpenGL
- Stop repainting forever at rest: poll the texture lifetime fence once
per frame, and check for time-driven shaders only after the
zero-instance skip, as Vulkan does. The explicit
`Cx::frame_completion_serial` poll still always arms a fence.
- Upload draw-call uniforms only when they changed; they were uploaded
twice per draw call per frame. A zbias shift now marks them dirty, so a
call skipped that frame still uploads when it next draws.
- Compute the retained-instance upload plan once per buffer per frame; it
was computed three times.
- Target remote screenshot requests at the presenting window. Every
`--remote` grab timed out on OpenGL before this.
- Emit the `gpu.present` trace with render and swap timings.
Retained instances
- `upload_plan` settles segments that kept their slot and offset by `Arc`
identity, scans for the first few that moved, and only then builds a
pointer-keyed map. On a large map this took a plan from 0.3-1.5 ms to
about 0.07 ms. Results are identical to the previous planner.
- Add `collect_backlog` so a renderer can drain retirements once a frame.
Runtime backend consistency
- `CxOs::vulkan_active()` replaces the compile-time branches that decided
between the two renderers, so a build that fell back to OpenGL releases
its uniform buffers, shares host swapchains and retires textures the way
an OpenGL build does.
Wayland teardown
- Drop windows before the `Connection`, and destroy a window's EGL surface
and `wl_egl_window` before its `wl_surface`. Every OpenGL exit on
Wayland segfaulted inside NVIDIA's egl-wayland.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Make Vulkan the default on desktop Linux, with mipmaps and hosted fallback
Every desktop Linux binary now carries both renderers and picks at startup,
instead of only the apps that asked for Vulkan by name. Three things had to
be true first.
Gate the feature where the fallback exists. build.rs derived `use_vulkan`
from `target_os == "linux"` alone, which also matches OpenHarmony and every
other Linux triple, none of which carry `naga`, and it ignored
`MAKEPAD=linux_direct`, whose DRM/KMS renderer has no OpenGL fallback of its
own. The feature now only reaches x86_64/aarch64 gnu windowed builds;
`MAKEPAD=linux_direct+vulkan` remains the way to ask for direct Vulkan.
Give Vulkan a mip chain. `image_cache_use_mipmaps` was off for Vulkan
because the uploader only ever filled level 0, so every minified image
aliased. Images now allocate their full chain and fill levels below the
first with `vkCmdBlitImage`, the way `glGenerateMipmap` does, skipping
formats the device cannot linearly blit. On Robrix's sign-in icons this
takes Vulkan from 2153 pixels differing from the OpenGL render by more than
8, to 400.
Choose the hosted renderer at runtime too. `--stdin-loop` mode was
Vulkan-only in a Vulkan-capable build and panicked when no device answered,
while its host, on an X11 session, had already fallen back to OpenGL: with
the feature on by default that combination would have killed every child the
wm launches. The hosted path now selects the way the windowed one does, its
import follows the renderer the process actually started, and a hosted child
rejects software devices for the same reason a window does.
Video and `Texture::read_back` are still OpenGL-only; the video error now
names `MAKEPAD_GPU=gl`, and the feature comment says so.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* workspace: drop the renderer-routing argument and its build-time wording
One desktop Linux binary now carries both GPU backends and picks at startup,
so an app honouring a saved renderer choice passes it to the platform as
MAKEPAD_GPU and restarts itself. Nothing produces `--renderer-routed` any
more; an argument this parser does not know was already ignored, so dropping
its arm changes nothing for anyone still passing it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Keep the shared Android and direct-display paths as they were
An audit of what this branch reaches on platforms that share these files
found five places where it changed behaviour it was never meant to touch.
All of them come from code this branch made runtime-selected or relaxed.
Mipmaps are desktop Linux only, matching `image_cache_use_mipmaps`, which
is what asks for the format. An Android or Quest Vulkan build shared the
new chain code and would have allocated levels and recorded blits that
nothing there requests and nothing measured.
The mip chain also needs more of the format than it asked for. It checked
only that the format samples linearly, while `record_mip_chain` blits
between levels, so it now requires BLIT_SRC and BLIT_DST too and keeps a
single level otherwise.
Shader compilation stays a compile-time answer off desktop Linux. Whether
a draw shader is compiled to SPIR-V became a runtime `vulkan_active()`
test, which on Quest would follow an Android Vulkan init failure instead
of the build. Only desktop Linux has that fallback.
The hosted loop compiles GLSL only when OpenGL is the renderer. Losing its
cfg left it calling `gl()` in a Vulkan hosted child, which has no EGL
context, so it panicked. Its Wayland sibling already guards this way.
The direct display build keeps its software-buffer upload. `texture_for_draw`
gained a `not(linux_direct)` that was never needed: `MAKEPAD=linux_direct`
without Vulkan has its own `upload_presentable_image_software_buffer` in
os/linux/presentable.rs, and the outer gate already excludes the direct
Vulkan build.
Also: `gpu_preference` is now gated exactly where its caller is compiled,
since `vulkan_linux.rs` builds for every `target_os = "linux"` under
`use_vulkan`, and the hosted loop's imports follow the block that uses
them, which the direct Vulkan build does not compile.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Retained renderer support is back for Scope on Metal, Vulkan, OpenGL,
WebGL and the simulated GPU. A `vulkan` cargo feature picks Vulkan on
desktop Linux, Cx::gpu_backend() names the compiled GPU API, and the
direct WM builds again. Settings.renderer in libs/workspace keeps the
saved GPU API choice (Vulkan | OpenGL) behind the --renderer-routed
argument. The Android build keeps the texture alloc types imported for
OES adoption, and that import stays off the web build. The simulated
GPU builds on Linux again.
Squashed from work, without the cargo vendor snapshot the retained
renderer commit carried there:
- platform: a `vulkan` cargo feature picks Vulkan on desktop Linux; Cx::gpu_backend() names the compiled GPU API; the direct WM builds again
- workspace: Settings.renderer — the saved GPU API choice (Vulkan | OpenGL) and the --renderer-routed argument
- Restore retained renderer support for Scope
- platform: Android builds again — the texture alloc types stay imported for OES adoption
- platform: the Android texture-adoption import stays off the web build
- platform: the simulated GPU builds on Linux again
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Button: add `label_align` to center a wrapped label
`Button::draw_walk` passed `Align::default()` to `draw_text.draw_walk`, so
a label that wrapped onto more rows left-aligned them under each other even
when the button itself centered its content, and no script property could
reach that argument.
* New `#[live] label_align: Align`, mirroring the one `TextInput` already has.
* Defaults to left, so every existing button draws exactly as before.
* Pair it with a `Fill`-width `label_walk` to give the rows room to move.
* macOS: don't paint into a drawable the layer has since outgrown
The prefetching `DrawableWorker` hands back a drawable that `nextDrawable`
acquired on the previous beat. A frame that straddles a resize therefore
attaches a texture of the old size to a viewport derived from the new one.
* moving a window between displays of different DPI drew the whole UI at
the old scale, and it stuck: a dpi change dirties the pass exactly once
* dragging a border fast left the strip the old texture didn't cover
unpainted, which macOS shows as magenta
Check the invariant where the drawable is consumed: a texture whose size
isn't the layer's `cal_size` is dropped and one is acquired on this beat,
the way the pre-worker path did every beat. The pool was just rebuilt for
the new size, so that acquire doesn't block. Traced on the `present` topic.
* Window: don't put an app icon in the caption bar by default
`AppIcon` falls back to a generic placeholder tile for any app id makepad
ships no artwork for, so every third-party app got a meaningless icon next
to its title, and an off-centre title with it.
* `AppIcon` gains `visible`, which it had no way to express before
* the caption icon defaults to hidden; apps opt in with
`caption_icon +: {visible: true}`
* Quiet the startup and per-decode log spam
A plain run printed ~1500 lines before anything happened.
* `zune-qoi` was the only zune crate with `log` in its default features,
and cargo unifies that onto `zune-core`, so every zune decoder logged
per image. zune-core's macros became real logging in this tree, so what
used to be inert now floods the log
* memory budget, task-pool priority/summary and the Metal retained-upload
budgets move onto the `MAKEPAD_TRACE` topics this tree added
(`memory`, `pool`, `gpu.upload`)
* drop the studio-websocket line, which only says a disabled thing is off
* Make the UI-hang sampler opt-in
It started with every `Cx`, so a shipped app carried a thread waking ~16
times a second forever, and any stall over 250ms got the UI thread
suspended once per sample while its stack was walked.
`MAKEPAD_UI_HANG_MS` is now the switch as well as the threshold. Unset, no
thread starts and the phase guards see a null registration, which is a TLS
read and a null check.
* Wake the event loop from render workers without raising the UI signal
`Event::Signal` means "a worker has something for you" and is dispatched to
the whole widget tree. The submitter, the instance allocator and the
drawable worker raised it after every commit, so an app painting at 120fps
walked its tree 120 extra times a second. Measured in robrix: 119 signals
against 111 repaints, down to ~0.3 per frame.
They only ever wanted the loop awake, so give them `wake_ui_loop()`, which
is what `set_ui_signal` already called underneath. A dirty pass is what
keeps the paint clock armed, so nothing depends on the flag to get painted.
* Compare the resident instance bytes instead of hashing them
`immediate_payload_hash` FNV'd every byte of every dirty draw call to skip
the upload when nothing changed. That suits a few big payloads, not a 2D
frame: robrix scrolls ~1090 draw calls of ~110 bytes, and the hash cost
2.3ms a frame in a debug build to skip ~12% of 110KB of uploads.
Instance buffers are StorageModeShared, so the resident copy can just be
compared. `memcmp` stays fast in an unoptimized build, and an exact
comparison can't collide into a stale frame the way a hash can.
* Install a platform stylesheet only when an app asks for one
`current()` picked "ios"/"android" straight off `OsType`, so any app built
for a phone was silently restyled: ~270 theme tokens including the fonts,
over whatever the app had already set.
Worse, it only half-landed. `apply_theme` runs from `widgets_mod` but
`apply_widgets` runs from `script_mod`, so an app that calls the
`theme_mod` + `widgets_mod` pair got the mobile palette with desktop
metrics. Every in-tree user already calls `install` or sets
`MAKEPAD_WIDGET_STYLE`, so that variable is now the only implicit route.
* Let an internal drag deliver its pointer events on Linux and wasm
Moving internal drag handling into shared code changed it in two ways that
the macOS and old X11 paths never had, and a dock tab shows both.
The pointer event was replaced by the drag event rather than followed by
it, so no widget saw the `MouseUp`. `Tab` sets `is_dragging` on FingerMove
and clears it only on FingerUp, so a tab could be reordered once and then
never dragged again. Dispatch the pointer event first and append the drag
one, with a flag so that dispatch doesn't produce the drag event again.
A release that never moved after `start_dragging` also produced no Drop and
no DragEnd at all, so `Dock` never cleared `dragging_tab` and kept painting
the ghost. `Tab::min_drag_dist` has no default, so a press, one motion and a
release reaches it. Every other backend ends the drag unconditionally.
* Split the UI signal so makepad's own queues don't broadcast Event::Signal
`set_ui_signal` was the one wake for everything, and every platform loop
answered it by running makepad's handlers AND broadcasting `Event::Signal`
to the whole tree. So termination, the network runtime, live reload and
every pool completion (label shaping, per frame) woke every widget.
* `set_internal_signal`: the loops run their handlers and don't broadcast
* `TaskPool::submit_internal`: a job whose result makepad polls at draw
* `Event::Signal` is documented, and the loops treat the app flag as a
superset of the internal one, so nothing left on it can regress
The scheduler keeps the app signal on purpose: `service_scheduler` re-arms
the platform timer from `call_event_handler`, which only the app half runs.
Media device changes still go through `SignalToUI::set`, whose instance API
is app-facing; they are hotplug-rare, so splitting that is left alone.
* Harden the drawable re-acquire, the internal drag and the opt-in sampler
Follow-ups from reviewing the five commits above.
* the resize re-acquire only runs while the drawable pool has a free slot.
Exhausted, `nextDrawable` blocks the UI thread on the compositor, which is
what the worker exists to avoid; skip the beat and stay dirty instead
* the byte compare no longer skips an item the GPU has evicted, which would
leave it invisible in a pass that then repaints forever
* the internal drag suspends its items across the pointer dispatch instead
of holding a flag. An unwound dispatch now ends the drag rather than
wedging it for the life of the process, and a widget that starts a new
drag from that dispatch keeps it instead of tripping "start drag twice"
* `tests/ui_hang.rs` opts the sampler in, since it is the thing under test
`Button::draw_walk` passed `Align::default()` to `draw_text.draw_walk`, so
a label that wrapped onto more rows left-aligned them under each other even
when the button itself centered its content, and no script property could
reach that argument.
* New `#[live] label_align: Align`, mirroring the one `TextInput` already has.
* Defaults to left, so every existing button draws exactly as before.
* Pair it with a `Fill`-width `label_walk` to give the rows room to move.
Squash of 55 work commits (Sep 1–12):
0fd356d windows: the vendored bindings are generated from a checked-in filter
79882b5 fabric: a photo or a live camera to a fitted sewing pattern
1e93309 AGENTS.md: designs stay local; no OS screenshots; focus and hidden-window laws
aa96dbd cargo-makepad wasm: package the bin target's wasm and create dirs before minifying
14d0723 cargo-makepad wasm: production packaging — strip, small profile LTO, optional binaryen, size report
79e7526 sqlite: a page-store seam — the file backend as before, an in-memory backend, and open_memory / open_with
60ee978 cargo-makepad: package artifacts carry a content hash so a re-upload is a new URL
611c9eb cargo-makepad: production packaging stays off fat LTO; script VM under LTO investigated
ab8febc cargo-makepad: fonts packaged from the app's font manifest
f6bbee9 wasm bridge: shared memory asks for the 4 GiB wasm32 ceiling and steps down where the engine refuses
fb1416d cargo-makepad: the threaded wasm module is linked with the 4 GiB wasm32 memory ceiling
fd5d70c cargo-makepad: the app's own resources are packaged under its bin name, which is how self:// resolves
f51ca07 workspace: the wasm interpreter's tests build at opt-level 1 — its own profile setting is ignored inside a workspace, and opt-level 0 overflowed the script eval stack
942ff86 sqlite: the browser store has one owner — its locks never wait on a clock
82d0cfa web path: the trace helper keeps its doc, the journal nonce steps a counter where there is no clock or pid
6f8c08f web-server: POST /api/crash stores crash reports in a rotating log on both servers
106b38c wasm bridge: the imported memory honours the module's declared limits
94b8726 dj-pack: tracks in, stems through the hub, a site store snapshot out
d9f04fc dj-pack: pack reads caches, never creates them; dry-run writes nothing
e6a305c ai-hub + dj-pack: a whole track fits a stems job; long tracks split into spans
d1910b8 web-server: a store snapshot's extensionless routes are served with the types the exporter recorded
c1f7b53 asset-client + dj-pack: a long description never rejects a snapshot; the packer writes one bounded line
453197d dj-pack: analyse produces the beat grid, overview and loop-splat caches the demo cache ships
9b4a3ca network: every completion raises the UI signal
fad1c49 web server: audio and text files are served, and models/ is immutable like maps/
569b4a7 dj-pack: every CC BY and CC BY-SA version and the public domain mark are redistributable licences
dc9cce6 workspace: no std clock on the web in any crate the web apps link — the last start-up worker death is gone
c7639f0 clippy: timed std waits (sleep, recv_timeout, wait_timeout, park_timeout) are disallowed — they read the std clock and panic on wasm workers
d748753 wasm bridge: the page environment carries js_worker_wait so the module links — the pool landing added the import for workers only
ec40fdb vj + widgets: double-click a knob or fader to reset it to its default — the Slider handles tap_count 2 and emits its normal Slide action; the deck controls carry their neutral defaults (pitch 0, gain/EQ/stems 1, filter centre, crossfader centre)
fe23e06 AGENTS.md: the execution policy — zero locking on the UI thread as one mechanism for native and wasm, no temporary threads (the pool), the standard operating flow (Codex codes, Fable designs and reviews, Grok tests), and the tweaker on Shift+F10
e689aea web_server + geodata + route: live radar, wind and weather on makepad.nl/api — one bounded poller per feed, hourly, disk-backed cache served from an Arc snapshot (a restart never re-polls early), 503 warming until the first result, health reports ok/warming/unavailable with timestamps; KNMI key from --knmi-key-file, the documented anonymous open-data key otherwise; libs/geodata fetches through the platform HTTP client instead of shelling out to curl; the client retries 503 after 30 s and disables a layer only on 404
cd33943 web_server: radar and weather run on KNMI's documented anonymous key when no --knmi-key-file is given; without --live-cache the pollers keep an in-memory cache and say so once
2f3e393 web_server: a directory path without its trailing slash (/score) redirects to /score/ instead of 404, query preserved
9a31d21 flow-ui + widgets: a chosen model shows no node list, and a closed ComboBox shows the start of a long label
22b2c78 docs: streamline agent runbook and extract reference guides
6058284 terminal: add hostable session multiplexing via tools/screen
8a9345b tools: migrate Cargo.toml lookups to segment-path keys
8749b91 counter: keep app state across Splash reloads
3ffd485 tools: add agent launcher and AIHub node update and smoke scripts
c33a9d3 screen: add bypass and resume menu options
c40d234 AGENTS.md: current delegation hierarchy (Grok mechanical, Codex hard, Fable manages)
4184455 Workspace: scope lives at apps/scope (clone of makepad/scope)
27844c9 makepad arch usb builder
dd967eb Workspace: drop nine members that are not in the repository
1961768 AGENTS.md: hierarchy 2026-09-11 — Fable builds, Codex reviews, Grok proves
9cd9f94 AGENTS.md: rendering is verified on the real GPU backend, headless is for logic tests only
5f53254 AGENTS.md: GPU proofs may run hidden; only the headless CPU backend is out
e950b08 docs: app-remote — hidden GPU runs vs the simulated-GPU backend, measured grab cadence, remote hazards
9c94a77 arch: the platform plan names the simulated-GPU backend gpusim
3009257 micro_serde: serde_json-style JsonValue accessors, pretty printer, depth limit and strict parse
134cd76 gitignore: alternate target directories, root scratch dirs and stray logs are never source
60ba86e arch: the render node refs name platform/src/os/gpusim/mod.rs
1dc571a tools/arch_usb: Wi-Fi, Intel GPU firmware, the AI hub service and game-hardware udev rules on the Arch image; the WM session script picks the saved compositor GPU
cc3b05a tools/arch_usb: a polkit rule lets the arch account start, stop and restart the WM service
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 5 work commits (Sep 2–9):
066c782 piano model: the lower register is hammered, not plucked
f2b0253 drumkit: a sample-based kit for the score preview; the physical model parked as drumkit_phys
3dfe5aa show-control: restore sixteen DMX scenes with local save overlays
b49e2e5 piano-model: calibrate partial gains and decay against recordings
d73cd40 piano_model: add an interactive piano-physics explainer page
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 7 work commits (Sep 1–12):
f7093bf map_nav: the search db's positioned read builds on Windows
3c03397 geodata: a data library keeps its own clock — the GUI platform is not a dependency
64d2d3a map-build: an unfinished bake resumes or restarts itself, and the maps root does not depend on the cwd
cb572ae map-tiles: makepad-map-repack rewrites an archive to what the renderer reads
30fc13b map-tiles: repack runs on all cores, resumes per shard, reports --status
2db9d48 mkmap: a root record may decode to 512 MiB — the densest world shards list over five million tiles
e8be900 deps: drop osmpbf and serde_json from the root workspace
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 1 work commits (Sep 9–9):
842ea96 git: topological log, worktree lifecycle and bounded line diff in libs/git
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 25 work commits (Sep 1–12):
237da90 render: the sprite lane hands the screen draw back the way it found it
895b9a7 particles: an emitter can ride a body's own frame
914471f ai-hub: a feed session whose last socket left ends on its idle timeout; skin: parent, skinned centroid and a nodes-only rig for retargets
3fcccf3 sim: the whole world is implicitly editable, and one seam says where the ground is
5692fab sim: the landform world proves itself — walker through the tunnel included
f4af6df sim: terrain knows who changed it — a plan layer over player history
adbb078 render: a water volume can be physics without a picture
c17480f render: a non-rigid body may carry its own orientation
acad401 sim: an agent with no route holds and retries instead of walking into the wall
22b2bf9 sim + chat: the composed world surface takes a map floor; the chat gets plan tools
faf8112 web path: the tessellator's lap timer, the trace span and the fusion cycle timer have no clock on the web
d3472eb tsdf: the clock-taking XR helpers are native-only — the browser has no depth camera and no Instant
4946c9e sim + render: a repaint is not a world edit — colour and glow restyles never rebake the lightmap
4317f58 sim + render + chat: walk decks as a surface, the filmed body is no obstruction, the brief never asks
9791279 render: support rigged models and custom materials across viewers
3ce2792 sim: use deck geometry for collision and sensing
c8b79ff Add portable PBR, rig and soft-body authoring support
108d423 Add transactional polygon modeling and editable asset documents
38f4d3b Refine editable modeling and firm yarn character behavior
08a2637 sim: add entity-owned lights and vehicle headlights
00d96a7 render: add clustered lights, local shadows and incremental GI
cf916af render: import glTF asset extensions and wire clustered GI
c6d2cca model: cut transaction memory and raise capacity limits
41af47a raytrace: add a CPU probe-ray BVH budget example
c963d0a libs: the game sim splits into makepad-scene and makepad-soft-body; render, model, fab and the asset importer retarget
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 55 work commits (Sep 3–9):
2d4037d flow: every asset-creator generation kind as a prelude prototype, and every pipeline as a flow template — inventory and gaps alongside
90dfcb6 flow: the language — splash prelude for nodes, evaluate a flow file into a graph with typed ports and located errors, tool schema from Input/Output, canonical writer
2a902c6 flow: the recipe prelude loads behind the core one and every pipeline template evaluates, round-trips and is canonical — a test per template
13f9359 flow: the server host (state thread, two planes over bounded_http, definition routes, events long-poll, lock/listen/token files, watcher) and the client (fail-closed connect, session, subscriber, embed policy); apps/flow-server bin and the flow-ui shell that hosts the server in-process
2b98a1f flow: the run engine — instances with an inputs table, one VM per run with live Fn closures, a dataflow scheduler, five executors (chat over the hub session, gen over the fleet, fn, http under an egress policy, ask that parks a run), content-addressed values with a RAM budget and spill
b3da0c5 flow: ports declare their types — ports: {in: {name: @type}, out: {…}} — so a second image port (mask, last_frame, reference) is as typed as the first; Inpaint, edit references, Music.lyrics, Paint.reference_image, Control.control restored; DREAM closes its loop again; the node catalog walks the prelude
098ba77 flow: three hostile test batteries — the server routes over real sockets, the language, the engine — with the findings pinned as ignored tests naming the bug
428d23e flow: instances, runs and values on the wire — instance CRUD and inputs (answers a parked Ask), runs with cancel, values on the data plane with ETag/Range and PUT upload, run events fanned into the ring by topic, autostart, janitor, bounded shutdown; real seams by default with with_seams for tests
58f0700 flow: media inputs route by (domain, port) — inpaint takes image and mask as named inputs, control's image is the primary — and every gen node's declared port types are authoritative, including derived Image and Upscale; ocr template typed; empty legacy port arrays warn
c36f1e7 flow-ui: the AI service bridge — a flows service (authoring + what is running) and one service per definition on the aichat bus, per-turn context, tools answered off worker threads
a2e0c43 flow: the old pipelines ship as templates — GET /v1/templates lists them with label, brief, inputs and outputs; POST /v1/flows/{name} {template} creates a definition from one
09e4bf1 flow: what the batteries pinned — 3xx refused, a node object where a port reference is required is an error, undeclared Fn ports refuse edges, parameter errors point at the field, the omitted node is named, allowed HTTP methods listed, a 192 KiB source cap, an unterminated Flow{ is a parse error
263d6f0 flow-ui: the canvas — node frames with faces evaluated in an isolate per instance, typed port dots, bezier wires, drag-move and drag-connect, palette, inspector, source pane, App view, Running list — integrated with the chat bridge and wired to the run routes
7b10d61 flow: the hub's models on the wire — GET /v1/models snapshots the discovered fleet (health + model list per node, refreshed on demand, fleet hints for tests) and /v1/nodes carries the model ids per domain
14b194b flow: the contact-sheet acceptance and an instance/run/value battery over real sockets — Http read, Fn, two images in parallel, an Ask that parks and resumes the run, a third image, an Http POST of the result
a40ad74 flow-ui: the empty-state icon is the node's output type — a picture card waits with the picture icon, not the text glyph
662a121 flow: the gen executor renews the hub lease every two seconds and says bye on shutdown, so fleet boxes stop reaping our jobs; flow-ui's bridge publishes run and instance events as messages on the bus, long runs return early with a subscription, and the AI gets templates, models and create tools
64f6d41 flow: pin the exact chat hand-off the Llm node makes (system, user turn, model) as a regression test
2dbd1b7 flow + hub: a flow's LLM turn asks the node not to think and sends no token cap for zero, so a fleet Qwen node answers with a visible paragraph; the empty-state placeholder draws one icon
df9f449 flow: the style picker lives on the node whose input it sets — the add_style Fn, not the image
e2a37c9 flow-ui: a format picker with common image sizes and a swap beside width and height, the picture clipped inside the card under the selection outline, a face-declared control mounts as a labelled row on the node it sets with a hint when it binds elsewhere, the Fn face gets a strip for them
8b05496 flow + flow-ui: width and height snap to the type's step (16 for images, the doc is the authority) in the fields, the inspector, the presets and the evaluator with a warning, so a fleet backend never sees an illegal size; text in cards and the inspector scrolls inside a bounded area that follows streaming until you scroll up
4aec005 flow-ui: wires route around cards with fillets and a pulse that travels the cable when a value lands; Clear drops an instance's generated state and keeps its inputs, as a route, a toolbar button, a menu entry and a shortcut
f95e34b flow-ui: the inspector has one purpose — the selected node's header and note, the settings its card does not show, its connections as chips, its result, the raw face source folded under Advanced; a full-window image viewer with cursor-anchored zoom, pan, fit and 1:1, arrows through the instance's pictures, save and copy digest
8ff1d1c flow-ui: wires never pass behind a card — endpoint cards are obstacles except at their own port stub, targets below or below-left route through a clear corridor; the selection outline is a stroke on the card's own rounded body under the port discs; a press on a picture box drags the card and a still click opens the viewer
b7d95d8 flow-ui: the image viewer opens fitted and centred — the picture syncs after the stage has drawn instead of reading its cleared area as empty; a sampling-mode field replaces the abuse of the image shader's rotation; pan clamps at the picture's edge, 1:1 means one image pixel per device pixel, the checker stays fixed to the screen
6139a73 flow + flow-ui: a card can face right-to-left — flip: true in the file mirrors its ports; the router is directional; a card auto-flips when that cuts its cable length below 80 percent, a hand flip pins it; the flip animates
b41fab4 flow-ui: a wire takes the simplest shape the geometry allows — the clear S-curve wins over any corridor route, tangents scale with the hop, level ports get a straight line, and no route has more bends than an obstacle forces
4781378 flow-ui: the image viewer is modal for input — wheel, pointer and keys stop at it, and the canvas ignores a wheel under any open overlay
2ed495f flow-ui: the flip decision scores length, crossings, bends and loops instead of length alone, so a crossed pair unflips; ports and wire midpoints carry direction chevrons that hide below half zoom
8c40d28 flow-ui: a close button at the top-left of the image viewer, fixed to the screen; the bar and the button fade with the viewer
4c37048 flow-ui: wires are stable and selectable — a route depends only on its own obstacles and keeps its kind unless a new one is clearly better, with a fixed tie-break; a click within six pixels selects a cable, the inspector shows the connection, Delete removes it
c90df1f flow + hub: a node advertises only the models it can admit, the flow picks admitted nodes for the named model, retries on refusal, and names every node's reason when none can take it
8f23e3b flow-ui: the port chevron sits between the disc and its label, never inside the disc
f5ec84e flow-ui: the port disc's shape carries the flow direction — outputs end in a point, inputs have the matching dimple
cf3b648 flow-ui: oval port discs with a small input notch, the icon visually centred, and wires that start at the output's tip
c6890fd flowgraph: the flow canvas and wire router become libs/flowgraph, a widgets-only crate any UI can host; flow-ui is its first consumer
27f02f2 flowgraph: the canvas's style table resolves its registered type, so node and port icons draw again
2a51724 flowgraph + flow-ui: two wire modes never mixed — bezier or routed — and routed wires use narrow gaps
5863c43 flow-ui: an input card is one text area filling the card, with no name beside it
30ef925 flow-ui: the image generator card shows only its settings; the picture lives on the Output card it feeds
d20a208 flowgraph: a routed wire turns right after its port and hugs its own card
7e397cf flow + flow-ui: Publish end node writes to the asset library; Assets tab in the Inspector; prompt-to-library template
c71d639 flow + flow-ui + flowgraph: a stale keep-alive is replayed on a fresh socket; a palette drop rewrites the file text so the new card mounts; the progress bar is centred in the header and inset past the corners
a777488 flow + flow-ui + flowgraph: the loaded flow is the editable design (Input.value lives in the file); Play clones it into a numbered locked run instance with a Design button back; inspector text rows follow the graph
767e260 flow + flow-ui: parallel runs — random seeds with a die, a fleet parallelism estimate (generation nodes bound it; the chat box never caps the image fleet), batch routes, the Run dropdown (1 / max · N), Ctrl+Enter queues a run, and the Running panel becomes a Queue of every run with per-row and per-batch cancel and Clear all
acd2328 flow + flow-ui: a cancelled run clears its cards and bars at once and its live nodes read cancelled; layout edits (move, size, flip) work while a run is shown; the queue row is one centre line — name, strip, state · time, x
8f51704 flow-ui: a Templates menu on the bar lists every template the server serves; choosing one creates and opens a flow from it
778dbb8 flow: accept bodyless 204 responses
b0b0e41 flow-ui: select runs from the whole queue row
2b4bff9 widgets + flow-ui: menu entries fire again — the bar's Closed action no longer hides the Selected one behind it, and an open menu takes pointer and key input before the panels beneath its popup
6f2fdfa flowgraph: a wire between nearly level ports takes the short run instead of a trip around the card (the fillet rule rejected any interior segment under two radii); fewer bends win only among routes of comparable length; six screenshot fixtures and the routing review
39a1b74 flow-ui: a locked run's pictures and clips still take clicks — the viewer opens from the Output card again; only the inputs stay inert
83a00d2 flow: archive generated assets and add live source editing
47a043e flow: add leased, reroutable fleet job admission for gen nodes
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 54 work commits (Sep 1–12):
6251f7c ai-hub: body domain — live pose packets ride the realtime session
ea50c77 chat_ui: the feed's session gets its profile brief back
f51b5f3 ai-body: the crate for the native SAM 3D Body port, with its weights reader
8211ae6 ai-body: the MHR rig and the pose head's parameter decoding, oracle-exact
9e343a8 ai-body: the DINOv3 ViT-H+/16 backbone, crop and ray conditioning; Metal gains rope-half and affine layer norm
69d842c ai-body: the promptable pose decoder and its refinement loop, oracle-matched on Metal
66e5e2f ai-hub: SAM 3D Body runs natively — `sam3dbody` on the body domain, oracle-matched end to end
a634198 ai-hub: the body-native commit carried a peer's in-flight hub hunks; put them back where they were
9ff44e8 ai-hub: the body-native wiring, this time only the lane's hunks
6a1c16b ai-body: third-party notices — what the port is implemented after, and what it is not
d78411a ai-body: the per-step work moves to the GPU
b22259b ai-body: the context stays on the GPU; only the pose token leaves the loop
346f31f ai-body: flash attention for the head-dim-64 blocks
45b5b98 ai-body: the crop size is a runtime knob, and the loop reports where its time goes
4be6d19 ai-body: the test modules import the grid constants they still use
7598346 ai-body: tensor-core GEMMs for the backbone, and the rig's correctives only where they count
a9ce596 ai-body: the crop warp runs across cores
8964ba6 ai-body: an FP8 backbone mode, off by default, measured against the oracle
a2aaa8f ai-body: the FP8 bias rides a column-broadcast add on the device
d53c77d metal: a device-resident ViT stack, and the body backbone rides it
d006d0a metal: resident f32 linears keep their weight on the device
525ba1c metal: a device-resident two-way decoder layer, and the body decoder rides it
c9e6d88 ai-body: the hands pass — hand crops, the hand decoder, the hand-mode rig and the wrist fusion
62dff26 ai-body: the mask prompt — a person's segmentation mask conditions the body pass
a648cf8 ai-hub: body session options — hands, detect, persons=N
8c568df ai-hub: drop the SAM 3D Body reference worker backend
7ff875a ai-hub: keep a peer's in-flight beats/notes/local work out of the body commits
31e5faa ai-hub: local model runner, licence acknowledgements, a shared install panel; Beat This!, Basic Pitch and the Salamander drum-kit entries
b94bc58 ai-services: the wire, the app port and the panel state — one conversation, many apps
2acb798 ai-services: wire v2 — endpoints, receiver-side caps, result disposition
8ae0ffb ai-services: the engine core — registry, router and conversation, tested against a scripted model
2308736 ai-services: the real models behind the engine feature — local through the hub, Claude, and none
c3f631d livepipe: one reusable pipe from a camera to a fleet node and back
ff62db3 ai libs: the runtime env-var cleanup — precision is a per-caller policy, not an environment side channel
04a94ef realtime: one service-log line when a live session opens and one when it closes
0ecb81c ai models: the model-crates env-var cleanup — 172 research knobs gone, the unset default is the code
4ca36c1 ai hub + services: the assistant's model comes from wherever it is resident — the fleet chat box, with tools, then the local weights
432121e aichat engine + wm: launch, then use — the assistant continues in the same turn once the app it started is on the bus
7a5bf69 ai-hub registry: the Salamander drumkit samples come from the makepad.nl mirror — the GitHub repo only carries the .sfz files
102ffc5 ai-services: messages on the bus — a manifest declares topics, the engine subscribes on a tool's behalf or by ToolResult.subscribe, a service publishes Message frames, an idle conversation wakes on a message as an event turn under rate laws; the WM bus forwards the new frames; every app that matches the wire gets its arm
a837792 hub + flow: a whitespace-only chat completion is retried once and then fails instead of passing as an answer; a flow's model is a fleet model id unless it names a weight file on disk; chat models show under the text domain in /v1/models
bc6c620 hub + flow: what the chat review found — the in-process route retries an empty completion too, a node says whether its prefill opened thinking so a brief-mode answer is never discarded, a preferred model falls back to normal election when no node has it, discovery keeps looking for the preferred model until patience runs out
75c3441 hub: the PRO 6000 serves image as well as chat and text
ad5e98b hub registry: flux2-dev's VRAM estimate is its measured peak, 30 GB
c7241e0 hub: a node that evicted every resident releases its cached allocator pool before refusing a load or publishing usable VRAM
30575f0 flow: route generation by request workload
1be1e21 ai-hub: gate downloads by disk capacity and recover fleet admission
df6b394 filesystem_watcher, bounded_http, ai services: live and tool prerequisites
79ebdb9 ai-hub: add a native Pixal3D image-to-3D backend
0ba0d74 ai-hub: propagate typed refusals under reject queue policy
cc6c872 Speed up H3 conditioning and video decoding
e512059 Fix Qwen vision residency and generated material colors
2864f68 ai-hub http client: bound every plain TCP connect to 3 s per address
3d93229 ai: CUDA is a Linux/Windows-only dependency; the hub library defaults to llm + stt
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 39 work commits (Sep 1–9):
0d7185e asset chat: catalog SQL answers in-process, and the game brief stops teaching the y mistake
c18b7f4 game brief: railways are one call too
b7fe32c game brief: a destructible world, path-edited railways, and models that load now
0e94bba game brief: rivers, highways, junctions and gates are one call
c189b03 game brief: agents follow the rules by themselves — you never script a stop
c86a750 chat: a game session reads its doctrine first and is not the generation assistant
1ef6f59 game brief: on a diet and world.plan-first
e18cc69 chat: a tool call the model wrapped inside its own think block still executes
d7f9813 game brief: to change a map, edit the plan you were given; vegetation is a layer
876e3c4 game brief: buildings have insides — ask for a program at the door
47ac158 asset-client: portable primitives — completion transport, cache store, static-site config
9ae91ac asset-store: static export contract — versioned static index and a sanitized deterministic export-static tool
f233756 game brief: races, rings, mocap and biomes are one call each
942476f vj: the whole app type-checks for wasm32 — portable hub discovery and typed-unavailable client facades
2e48c5b asset-client: the static-store read mode and polled runtime
e2a262b asset-store: the portable core — embedded feature, page-store and CAS seams, a synchronous in-process API
9942e26 asset-store: the embedded module lives in its directory now (the browser-durability landing's deletion)
670cc76 asset-store: the platform storage API is a hard dependency on the web, where the embedded store is the only store
6b77318 chat: the in-game builder never asks before building
9630337 asset-store: export-static selects what the namespace holds and says why when it does not
0684b41 asset-store: export-static reads every content schema the server serves
5fdd5d5 vj: on the web the explorer lists the site store's music and a deck loads it with its stems
40e483f vj: the web music browse actually fires — proven on the live path
44b3a7f dj-pack + vj: every track carries its artist, licence and source, and the DJ UI shows them
cb75d36 asset client + store: no std clock on the web — the catalog worker no longer dies at start-up
78a1681 bounded_http: the store's HTTP/1.1 server parser is its own crate — the store keeps only its JSON response shim; flow-server is the second consumer
285bc98 asset-ui: workers from the runtime pool, the video player owns its frames and audio state
90c4f59 asset-store: bump the static-export golden snapshot for the new rights fields
b37b116 media_view: the video, audio, mesh and splat viewers leave asset-ui for one crate; flow-ui shows every media type on the Output card and in the modal viewer
88048aa assets: extend authoring pipelines and typed asset search
0ac0892 Expose bounded modeling tools and reference-image continuations
36121c3 Keep modeling conversations running until completion or manual stop
2cea218 asset: centralize library and store path resolution
72dbc5a asset-creator: run pipelines through Flow graphs
0047e77 asset-store: add guarded, alias-scoped atomic publish
e85e36a asset-store: refresh model-preview sessions with a lease
933d885 asset-chat: compact repeated image diagnostics; require 45-degree views
626e797 asset-importer: gate pack import to desktop Unix; add missing newest field
f5e136d asset-importer: shade world previews with vertex colours
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 2 work commits (Sep 2–2):
7f1fefd terminal + browser: the assistant can read the screen, run a command, read and steer the tabs
a772ec8 terminal + browser: a warm-pool standby joins the AI bus only once adopted
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 3 work commits (Sep 2–6):
5c40761 cef: the build script downloads its CEF distribution itself, on every host, with no shell
eec516e cef: a Windows backend — the browser's page renders on Windows
3dce90e Propagate browser appearance changes to Chromium page media
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 34 work commits (Sep 2–9):
cb97fc5 vj: the loop splat — a song sliced into per-stem, beat-quantized loops on the APC40 grid
5b309b3 vj: 32-bit usize constants no longer overflow on wasm32
441d604 vj: dropped or picked audio publishes into the connected store
6be1118 vj: the whole app on the web — site store for content, embedded store for local imports, native-only seams Unavailable
aa7345d vj: the whole app on the web — site store for content, embedded store for local imports, native-only seams Unavailable
511048c vj: no filesystem paths on the web startup path — the store is the disk there
78bfd5c vj: the web explorer shows the store's tracks — the listing reaches the rows
cc8ee5c vj: the web music browse fires when the store is ready, and startup never stalls the UI thread
53f0e96 vj: the DJ/VJ app exposes itself to the desktop assistant
ffd62da dj-pack + vj: the demo cache carries every per-track analysis — slices, beat grid, overviews — and the web app reads instead of computes
f35dd1d vj: effect thumbnails actually render and persist on the web
6fddf9e vj: loading a track onto a deck fetches and decodes its audio on the web
5c78ac0 vj: web effect thumbnails show the effect, not a black frame
21fce80 vj: stale-recipe thumbnails are swept from storage on startup
afdffd7 vj: clicking an effect tile puts it in a channel on the web vj: clicking an effect tile puts it in a channel on the web
cc1f256 vj: cached thumbnails appear within a second vj: cached thumbnails appear within a second and the pipeline never hangs
4f98c73 vj: a deck shows what it is loading — fetch, decode, stems — with real progress vj: a deck shows what it is loading — fetch, decode, stems — with real progress
66617cd vj: stem separation is a setting — hub by default, local only on purpose, never auto-started on the operator's machine
645de42 vj: a deck plays and draws while it decodes, and the stems swap in sample-aligned — one streaming path for native and web
c3d9314 vj: each deck's transport sits on two larger rows under its mixer — every button visible on both decks
b0ec2a4 vj: the audio engine owns the mix state — the UI sends commands over a wait-free ring and reads snapshots, no lock on either thread
acb8689 vj: the loop grid is built and refined on the web too — one pool + channel path for both targets
707d702 vj: every worker thread comes from the platform spawner — the loop ½ button no longer panics the web app
71c0e1e vj: a paused deck is never moved by sync or the loop grid, a loop cell plays exactly its bars with the view held, and the stems banner clears
35ac364 vj: the catalog runtime pump no longer logs every poll
5a9fa62 vj: no timed std waits on wasm workers — blocking channels, one tagged deck+background inbox for the stems/lyrics workers, a lost-wakeup fix in the thumb queue, native-only gates on the video decode loops; the DJ web app stops losing a worker at start-up
32bea89 vj: a playing slice keeps the big waveform of the whole sample — the active slot is an overlay on it (accent band, bracket edges, wrapped playhead, slot number) while the lane's viewport stays fixed; the grid cells are selectors only, their mini waveforms are gone
dd51a02 vj: a go-to-start transport button per deck — seeks to 0:00 through the normal seek path, keeps the playing state, leaves the loop alone; skip_start.svg icon; decks test
bf9418e vj: ironfish / synth rack, program mix, and a clean release check
28016ca vj: keep generated clips at the head of the grid
f45793a vj: batch live video publishes into grid rows
75b295c vj: synchronize decks and queue remote stem processing
ba662d2 Add responsive DJ and VJ modes with system and black-orange appearances
a18b2ee vj: route DREAM runs through the shared Flow engine
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 8 work commits (Sep 2–10):
7b9ed2c aichat: the assistant as an app — the panel owns the engine, the bus client, settings with the local-only lock
e0c6e74 aichat: the progress bar and system lines use the theme's highlight colour
8b46ce0 aichat: the composer's hint is a dark grey Ask AI, not the typed colour
b99a631 toml_parser, rust_tokenizer: rewrite both for the code analyser
3bbcea2 aichat: add Studio evaluation-feedback widget
b61845f studio: Architecture view, the third workspace mode
d7a76cf studio: add bounded code context and source APIs
524142a Split Studio into makepad director (public) and makepad scope (private)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 59 work commits (Sep 1–12):
b0380ba image-tiles: the picture-wall engine as a library — tape atlases, a baker CLI, and the TileGrid widget
83d8eac mpsheets: demo feature and the SheetDocs seam
daff390 finance: demo feature — generated ledger, SQLite target-gated, import hidden
13b4c48 mpfiles: demo build — procedural fake filesystem, still-image thumbnails, chat feature
58e1f72 mpsheets: review fixes for the demo seam
3203e32 finance: review fixes — id remapping on persist, structural determinism tests, one cfg seam
0537687 mpfiles: distinct repo-owned pictures for the demo thumbnail pool
fe920c8 files: review fixes — demo scan exclusions, Zipf sizes, trash root guarded, depth bound, tests
4144dc4 files: the spy test filesystem implements the clock
1940f3d fonts: leave apps/wm untouched — its font-set declaration waits for the aichat lane to land
b0e34c2 wm: the AI pane and its bus — the aichat child seated in the slot, F10, the os service with the typed open
de937ac route: the web build is a one-to-one recompile — native UI, service seams behind it
2f5a984 files: the file browser on the AI bus — four read-only tools through a correlated, cancellable runner
2b035ff route: the navigation session keeps time on the platform clock — the std clock traps on wasm
1b70899 files: the space view is where a tab starts
412b3c9 files: the space view rescans through the virtual filesystem — the web has no other disk
0105fa5 files: the space view opens in 2.5D
5d007e0 files: an unset projection preference means 2.5D
6857d86 files: the projected treemap clips boxes at the near plane and keeps the camera above them
d3cd233 wm: one desk for every target — the host seam, the assistant seated in-process, the web profile, and the assistant up at boot
6d47c3f wm: the omarchy themes moved to omacom/omarchy — the importer follows
82def6b wm: every app under the desk — the chat keeps the keyboard through an automatic refocus, a pool that gives up, polite closes for browser, sheets and aichat
6105b61 sheets + files: the assistant reads and writes cells, makes folders, renames and trashes
82ac768 photos: the picture wall as an app — the SMBC archive on the tile engine, a module from its first line; fabric in the launcher
190062e route: the maps app exposes its tool table to the desktop assistant
3768653 image_tiles: the wall is box-packed — justified rows keep every picture's own shape and fill the width
3691875 wm: launcher icons — a photo for Photos, a shirt for Fabric, a globe for Route, a play badge for Video, a pulse for the task manager
8625076 aichat + photos + image_tiles: make me a picture and it lands on the wall; the wall filters as you type, tiles flying to their places
1dcca41 wm: a theme with no wallpapers fetches them on its own
ff6cfec wm: minimise, maximise and close in the bar's top right on Windows and Linux
62f38a5 route: --hour=N pins the theme hour for harness grabs
362ac59 wm: the checkout is found from the working directory too, and a sibling binary is an .exe on Windows
0fdfba7 files: the web's makepad home is a fixed virtual path — std's temp_dir panics there
daf88e1 route demo: the tiles come from the repacked world archive at its own never-cached path
15c3b11 ai-services: what the pubsub review found — modules get a subscription seam and a publish sink through both WM paths, lease end flushes unsubscribes to hosted services, the subscription cap counts closing rows, an endpoint's queue is dropped after Unregister, the prompt drops a subscription on final
d852699 route: no tile-source dropdown — makepad.nl is the tile source through the local range cache; a world.mkmap in the saved maps folder is the only override; the stale preference file is removed on start
aa271c3 map + route + geodata: the Terrain layer through the archive plane — TerrainSource (hosted .mkmap elevation shards fetched by range through the shared reader, a local MBTiles only as a dev override), the hillshade rendered on the pool's heavy lane with reused TerrainScratch buffers (web capped at 2048×1536 at DPR 1, native DPR-aware), one request in flight; the demo checkbox now renders terrain like native
dbc7838 route: the maps-folder field and its save button are gone from the settings panel; the maps root is automatic
a53d9b6 route: no clock-driven night theme — the theme is the user's toggle, remembered in cx.storage, default day (the hour rule fired on the web for the first time once wasm had a clock and darkened the map after 19:00)
ca92ff9 route: the open sea on the web — the ocean-low/ocean-high overlays are an always-on group in the shared hosted overlay table, read through the archive plane (makepad.nl, cached); native keeps the local ocean files only as a dev override; the native-only OCEAN_MBTILES path is gone
4cd24c4 route demo: the first location fix flies the map to the user at zoom 14, as native does; Amsterdam stays the default until a fix arrives
2e869f8 route: the ocean-high overlay comes from the re-sharded archive (58 shards of at most 16 MB, one small leaf directory each) instead of two shards whose directories decoded to 407 MiB
8021bb0 Add Clock and Weather with shared mobile tile views and persistent alarms
77b8309 Make application layouts and custom widgets follow desktop and mobile themes
b357164 Add desktop and mobile compositor shells with OS switching and dock transitions
11dec2e Keep hosted app output readers off the shared task pool
c4002c5 Round complete desktop surfaces and add Windows snap layouts
a5f8dd8 Frame home photo tiles and clip the picture wall below app controls
678721d Give mobile app libraries real search focus and hosted keyboard input
4794cdf Keep window title fills opaque at the application surface join
2cc584e Match Windows 2000 and NeXTSTEP window chrome to original screenshots
4a876a8 Give Files navigation and storage tools a clearer hierarchy
7821c90 Keep prewarmed browsers in the active desktop appearance
5bfe694 Compact the Files toolbar and repair navigation and selection actions
44fef36 Preserve Photos subjects and zoom across host view and aspect changes
ae20efc Draw larger macOS traffic lights with centered hover symbols
2bc4d89 wm: the WM is a library plus a desktop binary, Linux controls, hosted tick pacing
d643eb4 apps: the in-process app wave — mail, notes, calendar, reminders, calculator; clock, weather, finance, photos and route as modules; civil time, read-only sqlite, Linux CEF
56c1dee wm: a timer beat missed while the child renders is serviced on its acknowledgement
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 1 work commits (Sep 12–12):
e74b919 platform: the CPU simulated-GPU backend is `gpusim` — the word "headless" now means only window-less
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 2 work commits (Sep 9–12):
8d851ef platform: per-draw alpha blend, macOS waker, texture lifetime, file drop
cc2fa8f platform: the Metal display link is gone; the hang line prints its tick deficit; hidden instances refuse App Nap
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 27 work commits (Sep 3–12):
aa907ca platform: a frame trace on both desktop backends, and a window that cannot present stops ticking at 600 Hz
e3abf4d map: the hosted-tile cache keeps the same ~2x-visible margin as the local one — a budget pinned at the visible set evicted the trailing edge of every pan on the next frame and refetched it a moment later
8d542fe platform: a child pass orphaned by its attaching draw list is no longer painted — the window's gauss_scene pass stayed a live_with_parent child after the map went flat and re-encoded a frozen 400-item list every pan frame with stale geometry ids (new tile meshes × old instance counts, tens of millions of triangles into a texture nobody read); make_child_pass records the recording list + redraw id, stale passes are skipped, a cached View re-attaches on a cache hit; tests for the orphan gate and the pool generation contract
bb49fe6 map + platform: retained per-tile draw lists — each resident tile owns one DrawList2d per carto pass (fill, casing, stroke, icon, icon-high, shadow) and the label glyph batches are retained the same way, recorded when the bake, LOD ring, fringe/icon gates, flat/tilted or clip change and re-attached otherwise; a pan/zoom/tilt frame pushes this frame's uniforms onto the retained calls (DrawVars::update_uniforms_on_area, resolved slot table) and uploads zero instance bytes; the tilted per-pass depth is a pass_depth uniform; a held list's zbias resolves at entry (zbias_hold in every backend); the shimmer heartbeat patches shiny_time in place without a redraw; a freed/reused sub-list id is skipped by every draw-tree walker and the mask list re-records empty on the flat transition (contract test). Web pan tail 1,098 → 20 MiB/s, flat pan 2.4 → 0.43 MiB/frame; Metal grabs within the run-to-run noise floor
313265d Studio code atlas: geometry code views, live filter, 3D size lens, lanes as terminals
9c4e0cc Studio code atlas: performance round — retained uploads, worker labels, exact search, no forks
7235d7e Studio code atlas: stall fix, GPU working set, lens hard switch, filter masks, parallel index
56a6da5 platform: per-pass GPU counter timing on Metal; retained publications replace in place
72e2443 platform: present-path trace (1 Hz cause histograms), bounded drawable wait and retirement on macOS
1c5d583 platform: bounded retained maintenance on empty paint beats; republish actual backend debt
edfed73 platform: retained residency high/low water and hysteresis; no distance eviction without pressure
acab6a0 platform: critical upload class serves present-blocking items first; identical immediate re-records upload nothing
906c774 platform: uniform_range on DrawVars and patch_retained_uniforms on retained draw lists
c29031c platform/draw/widgets: heap-keyed script resources and RecordingBuffer draw items — the files today's commits depend on
cd0964f platform headless: homogeneous near-plane clipping before the perspective divide
61d424d platform: release retained bindings of released textures so pool evictions complete
a00287a platform: texture-tile cache support — per-item instance ranges, painted pass receipts, display-dpi pass uniform, retained render targets, present gate on the drawable pool
74b63be platform: retained upload floor reverted, unconfirmed presents counted
c03fcc5 platform: tile-cache round 3 support — O(1) demand on re-recorded lists, evictions counter, allocated_size, lost-button release, Vec2d::round
e515d75 platform: shared instance publications — the DL-0/DL-1 contract, additive beside the retained path
142a337 platform: shared instance publications — close the seven review items (DL-1b)
6811a19 platform: Debug for SharedInstances, WeakSharedInstances and PublishReceipt
c061e47 platform: Metal draws are resident by construction — the hole path and its gates are gone (DL-2)
0bdbb29 platform: drop the unreachable InstancesNotResident present cause
de3c868 platform: `drawlist` trace names the holder of a stale draw-list id; the per-frame upload line moves to `gpu.upload`
721c3d8 platform: publication backends — Metal per-publication backings, lease-keyed uniform ring, receipts on every backend, Vulkan draws attached items (DL-3)
393de54 platform: integrated deletion — the draw-list system is generic again (DL-5)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 7 work commits (Sep 2–12):
95fd7d6 platform: a hosted window with a dpi override lays out in its own points and gets the host's pointer remapped
c2c7f51 platform: a hosted window on Windows draws again — the depth buffer matches the shared target's allocation
3b1a8c2 platform: an in-app drag works without an OS drag session (web, Linux) — effect tiles drop into the channels on the web platform: an in-app drag works without an OS drag session, so effect tiles drop into the channels on the web
5ff7397 platform: typed geometry uploads on OpenGL; the typed gate names only the backend that still lacks it
80bf3d3 platform: Linux hosted GPU transport and routing, hosted tick pacing, WGSL packed vertex members, Vulkan typed geometry
e2d8a0a platform: the Linux GL and gpusim cfgs build warning-free again
c8c757a vulkan: honor tile draw inputs and retire submitted frames correctly
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 2 work commits (Sep 2–2):
a6d0338 widgets: popup menu items run in the popup owner's script VM; app_main! releases its borrow after a trap
232909d script: the VM reaches std and its slot through one host — no aliased references
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 10 work commits (Sep 2–12):
9633ded web server: site static serving plus the first nav backends
ba1010d asset-client: review fixes — additive web feature, shared transport contract, guarded cache and base URL
81ef71c network: native backends honour the response body cap
4d75e65 web server: hardening after security review — worker-only parsing, FD-relative static opens, strict framing, bounded work, panic recovery, Cloudflare-aware limits
dfe2087 web-server: O(1) report admission, one connection deadline, shared route sampler, static fallbacks, cache policy
9c9c72b web-server: bodies land before workers, client keys normalized, verbs fail closed
6c32c88 web-server: ETags from size and mtime, deadlines from size, Allow per resource
7613f3e web-server: one deadline per response from its size, 404 before 405 for unknown API paths, one Earth radius
7deb052 web-server: ETags from size and mtime, never from content; upgrades only where a socket route exists
f407342 libs, network: in-tree shims replace crates.io libc/log in the Wayland and zune crates; deterministic math; the websocket flushes control messages at once
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 10 work commits (Sep 2–12):
3389475 trace: one switch — MAKEPAD_TRACE=<topics>, trace!(topic, …), and /trace on the bridge
c55a315 platform: monotonic clock beside the wall clock, trap-safe dispatch, studio worker only with a studio
89fe453 platform: wheels and flight sticks are game inputs, with an output-report handle
e5d37e0 platform: a web file picker and file drop that hand apps bytes
19cf377 platform: the thread runtime contract — spawner, tasks, pools, scheduler, UI waker
cf2b8ca platform: no std::time on web — clippy guard and the platform clock everywhere a web build runs
c527cd8 vj: the web thumbnail pipeline never blocks the main thread
2a064d3 workspace: add loader, haptics, voice, and runtime fixes
541c886 platform: name heavy pool jobs over 250 ms; headless Startup sent once per Cx
d476e52 Fix Linux worker sizing and screen recording defaults
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 3 work commits (Sep 2–12):
debd8c1 rename: the mp prefix goes — apps/wm, files, terminal, browser, task, sheets, image, video, pdf; libs/wm_api and wm_theme
6dcca00 workspace: no timed std waits on web-reachable paths — the clippy gate covers every web demo's dependency set
9d8e314 platform: drop two in-band coordination notes that were committed with the tree
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 39 work commits (Sep 2–3):
ad24534 map: persistent asynchronous tile archive over a completion-based byte source
5d28ed5 map: tile archive review fixes — legacy MBTiles path restored, decode off the UI thread, retry, cancellation, blob dedup, validation
a7eae82 map: the tile build keeps time with the platform clock
d931f9d map: tile archive third pass — per-pass pruning, validated watcher metadata, shared blob bytes, timer watchdog, atomic cancellation
4b529a1 map: tile ranges fetch once, centre-out, and never time out while queued
04a36de map-build: the bake produces only what the renderer reads; a bad tile is skipped and logged
383b83e map: the Amsterdam bake report — the real archive through the real bake path, bytes per stream and milliseconds per tile
2d4ff16 map: POI symbols and building walls are instances — one shared mesh per symbol slot, one record per footprint edge, extruded and placed in the vertex shader
72b60b6 map: the memory diet folded in — CPU staging freed after upload, budgets from one platform number, bakes capped by bytes in flight, per-zoom bake profiles, a memory report; instance records count as retained CPU
a6f495d map: street trees are instances of one template per tile; the CPU staging of every uploaded stream is dropped; the memory diet keeps its platform half only
9047600 map: the bake report can dump the raw and decoded tiles it measured
57995b6 map: uploaded staging is freed on a pool worker — a large free on the UI thread of the threaded web build contends the allocator lock, and a contended lock there is an Atomics.wait the main thread may not make
02b58fc map: ground fills on a 20-byte vertex — position, colour, variant + AA coverage, depth ticks; DrawMapFill is the fill path of the map shader
ef25495 map: building shadows derived at draw time in a screen-space mask pass
9d664ca map: roads on a 32-byte vertex
5b49fee platform, map: the UI thread never futex-waits on wasm
99d2180 map: roofs on a 20-byte vertex, contact shadows as instances
5a5eda2 map: road, fill and roof streams on the typed vertex formats, u16 indices
f987651 map: the analytic road fringe is baked only when the view is flat
7e66991 map: finished bakes reach the screen on the next frame
d8539d8 map: the detail parse and merge stop allocating
7a2c09a map: road-union faces on a 16-byte vertex
a2cbe38 map, platform: the tile budgets follow the platform's one memory number
fef8058 map: marker stalks and stoplights are instances
662d141 map: building wall instances on a 20-byte record
d6d3241 map: round road caps are a fragment SDF, dead cap rows gone
8ae7c3c map: every typed stream index stays u16 — streams chunk under 65,536 vertices
4e61e44 route + map: the desktop app streams the makepad.nl archive through a persistent on-disk range cache, chosen in settings
a8b0382 map: the water/foliage shimmer read draw_pass.time, which flags every DrawMap* shader as animated and repaints the whole map at display rate forever — a Rust-stamped shiny_time uniform instead; the 20 Hz heartbeat drives it
0d3a9b8 map: nothing animates at rest — the water/foliage shimmer heartbeat runs only during interaction or a camera animation, plus a 1 s settle tail, then freezes at its last phase; flat and tilted views alike
1f24428 map bake: a face-band triangle is a face only when all three records pack (a mixed triangle stays on the road path); a panicking pool job no longer takes the worker down — caught, reported once, the tile fails; the bake panic that blacked the native map and killed the web workers
199be9e map + route: the six overlays (EV chargers, transit, nature, districts, building age, population) read through the same archive plane as the base tiles — OverlaySource {name, TileSourceConfig}, hosted .mkmap archives on makepad.nl fetched by range through the shared archive reader and disk cache, a local .mbtiles only as a dev override; one layer table (apps/route/src/overlays.rs) for the native and demo builds, the demo checkboxes now set overlays like native
01e77d9 map: the shimmer clock is one pass-level uniform (draw_pass.shiny_time, a map-owned slot, never draw_pass.time) written once per heartbeat tick instead of patched into every retained draw call's block — the settle tail stops re-uploading 600 uniform blocks per tick
d641cbf map: labels no longer vanish — the gesture label budget is charged from the placement loop, not from candidate collection (4-7 ms on the web at z15-16, up to 6000 candidates in space warp), so a place can no longer commit an empty cache; a truncated pass is never a strict cache hit, arms its own settle wake, and the at-rest follow-up chain is capped at 4
635c3a3 map: ready tiles are inserted at most two per frame (byte budget kept), queued visible ring first then margin ring, the stale tile drawn until its replacement lands — a restyle burst of 4-9 refined tiles no longer stalls a frame for 70-120 ms
ddc4709 map: touch gestures — one-finger pan and double-tap zoom, two-finger pinch zoom around the midpoint, rotate with a 5° dead zone, and a parallel vertical slide for tilt, one state machine native and web; the web page keeps browser pinch-zoom out (non-passive touch listeners, touch-action none, maximum-scale 1)
8eaa6ec map: two-finger tilt follows the phone convention — fingers up tilt into 3D, down flattens
018ce73 map: labels hold still through a gesture and never pop — the settled placement rides the camera delta while anything moves (pan now part of the motion signature; re-place only beyond the pan/zoom law), every draw uses the rect-centre fold pivot so CPU placement and the GPU warp agree (a fresh place drew about the screen corner: the giant space-warp labels), and a re-place cross-fades: survivors keep their birth, newcomers fade in, dropped labels retire from their own camera over 250 ms
c79e84e map: a tilt is two fingers moving together up or down — same vertical direction for both, the pair's stroke within a fifth of vertical; spread and angle no longer matter, so real fingers trigger it
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 38 work commits (Sep 2–6):
ed5b2fa web: wasm32 portability in libs and web startup geometry deferral
0ccd384 platform web: focus dispatch, window-zero geometry and generation-correct ids after the startup deferral
a7af3ea platform + mpfiles: platform clock instead of std::time on the web, unwind-safe event dispatch
52251b7 platform: a namespaced async key/value storage API on Cx — files natively, IndexedDB on the web
84b46c1 mbtile reader: file-backed readers are native-only so the map stack builds for wasm
6b661bf web: crashes report themselves — panic text, breadcrumbs, memory, workers; a dead instance stops pumping
372bfd7 asset-store: browser durability — generation extents over cx.storage, chunked CAS, quota and GC
0f967ce web path: the stream trace and the sqlite pager never wait on a clock the browser does not have
4e58ab9 vj: the output window exists only once opened — never on the web
93232a2 platform: two pool workers on wasm until the allocator is per-thread
c7fe851 vj: effect thumbnails render, encode and persist in browser storage on the web
5b33781 platform: thread-caching allocator for the threaded wasm build
463de64 web: shaders compile per draw list, link in parallel
e7be0e3 vj + platform: the browser UI thread never waits on a lock, and the web hot paths log only errors and summaries
7b33f7b vj + platform: a loaded deck actually plays on the web vj + platform: a loaded deck actually plays on the web
dc85eb0 web + vj: render-to-texture passes keep their 3D camera on WebGL, thumbnails wait for shader compile, bundled tiles re-ask — effect thumbnails match native
b39d301 web audio: the worklet links the whole module — every platform import the audio thread does not serve is stubbed, clocks and the UI wake are real
1e2dcd0 web: a pass without a draw list is skipped instead of taking the app down (F12 layers overlay)
0ef0311 web audio: a throw inside the worklet's process() is reported with its real text instead of a bare ErrorEvent
97e0701 vj + platform: the web audio thread never waits on a lock — a loaded deck plays
58d3fd5 web audio: the output is created inside the first gesture, and a stalled worklet module load is retried on a fresh context
410acd0 web + vj + route: the console carries failures and one-line summaries, nothing per request, per tile or per hiccup
a3248d1 web audio: the worklet gets the audio access pointer as its context — the thread-stack call gained a request id and the audio start was still passing the pointer in its place
9e2d65b pdf + photos + task + wm + video + automate + widgets: no per-job threads — pool jobs and start-up workers
639887d webgl: texture passes get their depth target — the tilted map (and every 3D scene drawn into a texture) was draw-order only: hollow buildings, no roofs, landmarks buried
2aa0780 webgl: BGRA uploads become RGBA at upload and sample_as_bgra is a plain sample on the web, as on every native backend — the tilt-shift's sharp band showed red/blue-swapped water
de9aba3 vj + web: the Layer button works on the web — a second Window is a queried capability (OsType::is_single_window; the web creates none and reports it once), so the output becomes an in-page full-canvas layer with browser fullscreen (Esc, the browser's own fullscreen exit, or a double-click leave it); a pass without a draw list settles its dirty flag instead of erroring every frame; exitFullscreen fixed; fullscreenchange feeds the window geometry
f615054 tweaker + webgl: click-to-climb continues only from the widget the climb started on, so a press on a sibling picks that sibling (the empty draw_bg was a bare View being pinned); the Shader tab says when a layer has no live draw call; the WebGL paint walk no longer resets every draw list's view_transform — the magnified material well drew at the window origin on the web
0d7ddee webgl: a uniform block is uploaded only when its generation moved — per draw call (uniforms_gen), per draw list (uniforms_gen, recording_gen), per pass (pass_uniforms_gen) and per shader scope block; the JS side caches (ptr, len, gen) per uniform buffer and re-records/recompiles reset it; direct camera writers (vj effects, render scene, the web flipped copy) bump the pass generation
e48b056 Revert "webgl: a uniform block is uploaded only when its generation moved — per draw call (uniforms_gen), per draw list (uniforms_gen, recording_gen), per pass (pass_uniforms_gen) and per shader scope block; the JS side caches (ptr, len, gen) per uniform buffer and re-records/recompiles reset it; direct camera writers (vj effects, render scene, the web flipped copy) bump the pass generation"
b043332 webgl: uniform blocks upload only when their generation moved — one global monotonic counter on Cx hands out every generation (draw call create/dirty/zbias, draw list allocate/transform/re-record, pass allocate/time/dpi/ortho/camera and the web flipped copy, shader scope writes), so a reused pool slot can never match a cached generation; clear_draw_items, pool reuse and VAO recreation reset the caches; the JS caches key on the generation alone. Proven on a local build: the pan screenshot keeps every tile, the settle tail drops 20.5 → 12.8 MiB/s
3c2530d web memory diet: the 805 MiB at load was the ocean-high archive's 13 M-entry leaf directory decoded to 407 MiB per lookup round and evicted at once — leaves now parse streaming into a window around the waiting tile ids (LeafParseLimits); a phone policy on the web (deviceMemory, UA, touch + short side) caps wasm at 512 MiB with a 320 MiB budget; archive leaf/range caches, reads and bakes in flight follow the budget; packed tile bytes stay packed until the bake decodes them; terrain scratch sized to the viewport and dropped with the layer; Cx::memory_report by owner. Phone viewport 1334 → 308 MiB after a minute of pans, desktop 1208 → 588
303458d webgl: a 2D texture pass builds its camera through set_ortho_matrix like every other backend, so the exploded z-layer view's camera reaches the GPU — the hand-built ortho branch uploaded an identity view and clipped every exploded draw, leaving the tweaker's layers view a bare window on the web; the Y flip for render-to-texture is one helper shared with the keep-camera branch
1801e38 Harden web renderer and make Route location opt-in
09fa1f0 Restore WebGL text with complete fallback samplers
9e61553 Keep Route 3D buildings under bounded web memory pressure
b005c6e Preserve complete Route geometry within measured web memory budgets
0832b35 platform: support float GI targets and retained mesh snapshots
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 10 work commits (Sep 2–11):
120b7e2 score view: the engraver as a shared library, with drum and pitched score builders
09b41a8 fonts: FontSet and FontPolicy — one application choice, selected-only loading, a font-assets manifest
6981e90 fonts: manifest generated from the chains, app-level font assets, a symbol fallback, deprecated i18n aliases
77d9138 wm: the module contract and the first in-process app — sheets in a tile, in an isolate of its own
55a3810 platform: typed compact vertex formats and u16 indices
83a8d4f fonts: the web demos start with the Latin set — CJK and emoji faces load on the first glyph that needs them
1980c24 platform: a draw call whose geometry id went stale is skipped, not drawn with whatever mesh now sits in the reused slot — the runaway triangle count that took a web map pan to 1 fps; reported with a power-of-ten backoff, never per frame
a75fe91 layout: extend turtle sizing and add Grid
4429551 draw, widgets: text clips by the list clip; GaussChain; map colour roles
6e02468 draw: restore Cx2d::set_current_pass_dpi_factor (raster density) beside the display-dpi setter
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 9 work commits (Sep 1–5):
929f822 video: the single-frame mp4 encode exists on every platform
294cb9e video: the single-frame mp4 is written on every platform, not stubbed
730b5b8 video: the Windows H.264 stream decoder pulls output before it knows the format
c1febc7 windows h264 stream decoder: low-latency mode, output type before first ProcessOutput, real MF_E codes, trace file
d6cc49a windows mft: PROVIDES_SAMPLES is bit 0x100, not bit 0
dcf7d21 windows h264 decoder: ICodecAPI low-latency, per-pump trace, access-unit dumps, and the stream tests run on Windows
d1e7a6f windows h264 decoder: AVLowLatencyMode is a VT_UI4; the round-trip test tolerates the MF encoder's access unit delimiters
aa816ed windows h264 decoder: pictures come out one access unit later — rewrite the SPS level so the DPB is one picture deep
2f44d20 apple: avoid blocking video clocks and release native players once
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Squash of 36 work commits (Sep 1–12):
176433a tweaker: click-again climbs the pick — the container under the children is reachable
97e9572 speech: one STT/TTS API on every platform, through the ai-hub
8ea3684 widgets: EventOrder reachable from the DSL; DataGrid hit-tests where it was drawn
0fd1ea2 route: demo profile — native/demo features, side-panel and provisioning seams, hosted tiles, HTTP nav client
cf4c84c keys: F10 is the assistant — the exploded-view debugger moves to Shift+F10, the screen recorder to Ctrl+F10
126d8c9 route: the demo profile runs in the browser — merged panel draw, platform clock, unavailable backends tolerated
e21db96 route: demo review fixes — route origin, rain lifecycle, hosted route validation, request context, provisioning
c24c206 aichat: the Window overlay — F10 in every standalone app, the in-process port, the /ai bridge routes, sheets as the pilot
dbe43bd wm + widgets: the AI panel on the left, pushing the body in
5184340 platform + vj + map + files + widgets + image_tiles: the runtime owns one warm two-lane task pool — jobs never spawn threads
697215e route + converse + example-map: every worker comes from the runtime pool or a start-up worker
862f3bd asset widgets + chat ui + render: fan-outs and jobs on the runtime pool, the transcript read without a lock on draw
976ea0e tweaker: Shift+F10 toggles it on every platform (KeyEvent::is_tweaker_toggle, one call site; the web page swallows exactly Shift+F10 so the browser never sees it); the exploded z-layer view has no keyboard shortcut any more — it is a button in the tweaker
4e8e4cd flow-ui: the design pass — menu bar and toolbar with the total run bar, continuous zoom through the draw-list view transform with pointer remapping, dark checker canvas with grid steps, shadowed cards with icon labels and port icons, glowing wires, per-node progress bars, full-bleed image cards, palette cards you drag out, the fab edit controls in the inspector, a template picker behind New, model pickers from the hub; MenuBar widget in the shared crate
075e1a7 flow-ui: pickers filled from the hub for image and text nodes, popups anchored through the canvas transform, labelled face controls, add_style explains itself, cards resize from a grip with size: vec2 kept in the file, full-bleed pictures, a click anywhere on a card selects it and still reaches the face, no remount on layout-only edits, panels float over the canvas
a50750f flow-ui: Flows, Running and Palette as their own rounded panels with splitters, the inspector and source pane likewise, columns resizable; gaussian frame shadows; keys and IME reach the focused face field through the canvas transform
43302a6 flow-ui: every card owns a draw list and draws in z order, selection brings it to the front; and the design review's findings — every event kind remapped through the camera, run events keyed by run id, no remount mid-run, an input journal that survives a failed PUT, terminal states reconciled, the total bar from the planned node set, isolate ownership on instance change, popups retired before an isolate is freed, the Ask face answers on a button, the menu bar navigates by keyboard, no per-frame allocation in the canvas draw
4ee4f4c flow-ui: the resize path sets walks and fits through typed setters, never a script apply from the main VM on an isolate's widget — a failed apply had left a freed script object behind and wedged every frame; a resized card fills its picture box, clips its face and lets the last flexible element take the height
cff15ac widgets: a fab number field drops a label that cannot fit instead of crushing it to a dot
24c927a flow-ui + widgets: every dropdown is the searchable ComboBox
a6c5f15 widgets: FabValueInput honours visible, so the seed picker's random mode hides the number field
1181a3b flow + flow-ui + widgets: every creator pipeline is a template — 55 templates in six groups (Image, Video, Audio, 3D, Vision & text, Utilities), all evaluated and engine-exercised in tests; the New picker, the flows.templates tool and the palette group the same way; the Templates menu shows them under group headings, and a menu taller than the window scrolls
f8b9a67 widgets: preserve numeric edit completion and menu focus
ed5f2e2 Add hotloadable OS themes and preserve widget state across style changes
f1d3b39 Center resized app recordings on a fixed black canvas
915fcae Remove icon rim highlights and align compact home tile contents
bfa7805 Keep terminal palettes theme-aware and resize above mobile keyboards
7535ce8 Fix workspace build regressions (#1220)
2233cbc Replace legacy Studio with docked and canvas agent workspace
708aa9f code_editor: range views, anchors, prepared documents, read-only, tab stops
0eae276 widgets: capture Studio evaluation feedback and recordings
487c602 widgets: let Studio pump dock bodies across presentations
c5adb93 Studio code atlas: settle-line diagnostics, index progress, chrome fades, exact search budget
ee9ab46 widgets: every screen capture lands in the repo's local/screencap, named by app
dcc9673 draw, widgets: the phone shell's glass, hosted-view and overlay support, app icons for the new apps
2fbc679 wm: preserve app caption controls and add Scope to the launcher
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Event: trace cancel scopes, and gate StackNavigationView's Back on ownership
A scope held by a widget that has stopped being the active thing wedges
Escape and the back gesture for everything behind it, and the only symptom
is that the gesture silently stops working -- which is indistinguishable
from there being nothing to cancel. MAKEPAD_CANCEL_TRACE=1 now logs every
scope begun and ended, and which one each press was stamped to, each named
by the call site that began it.
That location comes from #[track_caller] on both Cx::begin_cancel_scope and
CxCancelScopes::begin: the attribute propagates through the chain, so
Location::caller() names the widget rather than either of makepad's own
frames. No signature changes, no caller passes anything new, and the
existing tests needed no edits. Releases are logged from Drop rather than
end(), so giving a scope up by dropping it -- including a widget being torn
down, the case most likely to leak -- is reported exactly once on either
route.
StackNavigationView called the consuming back_pressed() whenever it was
Active, with no ownership check. A modal or pane opened over a pushed stack
view owns that press, but the view could consume it first and pop: the
wrong thing acts and the owner is starved, on one gesture. It worked only
because children are dispatched before the closure request, which is
precedence by traversal order -- the thing cancel scopes exist to replace.
A pushed view genuinely is what Back should pop when nothing is in front,
so it now holds a scope while Active and acts only when it owns the press.
Its five state writes route through a single set_nav_state that moves the
state and the scope together, acquired at the transition because ownership
is stamped before dispatch. The left_button and mouse-back-button paths
stay ungated: those are unambiguous clicks on this view, matching Modal,
which gates only back_pressed().
* Fix Escape and Back ownership across widget lifecycles
Allow gesture-specific scopes, preserve held Escape ownership across Back and focus changes, and suppress repeated Android Back dispatch without invoking Activity fallback first.
Release popup, modal, drag, and navigation scopes on every exit; support suspended navigation, isolate Pop actions, and finalize wide-window hide animations. Add focused ownership and lifecycle regressions.
Validated with 14 platform cancellation tests, 14 widget cancellation tests, Android Rust and Java checks, and a release modal Escape smoke test.
* Resolve cancel ownership from the active widget hierarchy
Bind widget scopes to their owners and resolve visibility and descendant priority only when Escape or Back begins. Retained inactive pages, collapsed controls, and unfocused windows no longer require application activation callbacks.
Preserve press ownership through repeats and release, suppress scoped or repeated TextInput Escape actions, and remove the StackNavigation cancellation activation API. Cover hierarchy, container, wrapper, focus, and gesture ownership regressions.
* Simplify cancel traversal and remove unsafe root lookup
* Reuse validated widget paths for repeated activity queries
* Remove PR-added cancellation tests and tracing
* Arbitrate the mouse back button with cancel scopes
The mouse's back button is the same navigation gesture as Android Back, but it
never received a cancel owner: handle_event clears press_owner for every event
and only restores it for Escape and BackPressed. owns_cancel was therefore false
for every scope while a MouseUp was delivered, so a widget could not gate that
button on ownership at all. The ones that tried had to fall back on ad-hoc
conditions -- "is my tab the visible one" -- which cannot express the thing that
actually decides it, namely that something else is in front.
Stamp a Back press for Event::MouseUp with the back button: in
resolve_widget_owner so widget-bound scopes are resolved against the hierarchy,
and in handle_event so ownership is settled before dispatch, exactly as for the
gesture itself.
StackNavigationView's mouse-back path is gated on that ownership to match its
back_pressed(). A pane or modal opened over a pushed view now takes the first
click and the view stays put; the second pops it. The left_button path stays
ungated, being an explicit click on the view's own header rather than a gesture
something in front of it could have a better claim to.
* Close a Modal on the mouse's back button
The back button is the desktop equivalent of the back gesture, and is arbitrated
by the same cancel scope, but Modal acted only on Escape, BackPressed, and a
click on its backdrop. A back-click inside the content did nothing at all, and
one outside it closed the modal only incidentally, as a background click.
Gated on ownership like the other two, so a modal opened over another one keeps
its place, and left inside can_dismiss so a non-dismissible modal still ignores
it. This is what lets a full-screen modal's content -- an image viewer, say --
respond to the back button without handling the gesture itself.
* Fold Modal's Escape and mouse-back checks under one ownership test
Same behaviour with one ownership test instead of two, matching how the other
cancel-gesture handlers read. Back consumption stays outside can_dismiss, so a
non-dismissible modal still blocks back-navigation for the widgets behind it.
* Event: let the foreground widget own a cancel gesture
Several widgets act on Escape, and today more than one can act on a single
press: a modal closes and background dictation stops; a popup closes and the
microphone keeps recording. Dispatch order cannot arbitrate this. Siblings are
handled in reverse declaration order, a parent runs before its children, and
declaration order doubles as the z-order knob, so dispatch order is not
foreground order and cannot be made into it.
Add a stack of cancel scopes on Cx. A widget begins a scope when it becomes the
active thing -- a modal opens, a drag starts, a dictation session begins -- and
ends it when it stops being; the most recently begun live scope is in front. On
a fresh Escape key-down or a back gesture, call_event_handler records which
scope is in front, and that scope owns the whole press, its repeats and its
release included. A widget asks owns_cancel() and acts only if the press is its
own, so exclusivity needs no consumption primitive: only one scope is in front.
A scope that ends part-way through a press does not hand the rest of it to
whatever was behind, so an Escape that stops dictation cannot also close the
modal it was running in front of. Dropping a scope gives it up, so a widget torn
down without a tidy close cannot wedge the key for everything behind it.
Nothing changes for a widget that never begins a scope, so adoption is
incremental.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Widgets: adopt cancel scopes for Escape and the back gesture
Every widget that treats Escape or the back gesture as "cancel" now holds
a CancelScope while it is active, and acts on a press only when it owns
it. Foreground order decides who cancels, not dispatch order: a modal
opened in front of another modal takes the press, and nothing behind it
acts on the same one.
Widgets whose active state can end by several routes reconcile their
scope from that state on each event rather than trusting a single close
path. That also fixes the tweaker holding its drag state open after the
panel is toggled off with F12.
* Fix cancel-scope timing and Back gesture ownership
Acquire drag and color-popup scopes at activation and release them on each exit path, before the next cancel event chooses its owner. Gate Back consumption on scope ownership across modals and their popup/drag controls; non-dismissible foreground modals consume Back without closing.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Admin <info@makepad.nl>
max_height() forwards its walk to next_walk_width(), so a height is
resolved against the horizontal axis: for Size::Fill it takes the space
the width flow rules pick, then subtracts the walk's left and right
margins instead of its top and bottom.
Only Size::Fill diverges, which is why this has gone unseen. Both
routines return v.max(0.0) for Size::Fixed, and Size::Fit never reaches
the call thanks to the early return. DrawText::draw_walk_laidout is
where it would surface: it builds its box from max_width() and
max_height() and passes both to walk_turtle() as Size::Fixed, so text
drawn with a Fill height would take its height from the available width.
draw_scroll_bar passes the inner rect to ScrollBar, which places the bar
at `view_rect.size.x - bar_size` relative to the turtle's outer origin.
The bar therefore lands a right padding short of the edge, covering the
last few pixels of every wrapped line, and moves further into the text
the wider that padding is.
Draw it along the input's right edge instead, inside the padding. A text
input's visible area is not the area its bar runs along, so ScrollBar
gains draw_scroll_bar_along, which takes the track separately from the
visible size; draw_scroll_bar now delegates to it and every existing
caller keeps its geometry. TextInput also gains scroll_bar_inset, so an
app can keep the bar clear of anything it overlays on the input.
Sync the view total into the bar before the scroll position too:
clamping the position against the previous content height left the
handle a frame behind whenever the text grew and scrolled.
Makepad's log! is its own printer rather than `tracing`, so an app cannot
filter it: anything logged this way is printed unconditionally, and an app
developer reading their own output has to scroll past it.
Six calls on always-taken success paths are commented out rather than
deleted, so they are one uncomment away for anyone debugging that area:
macos_window.rs titlebar container swapped, once per window
macos_app.rs display link pinned, paint pacing, once per window
macos_app.rs PIN stats, on every pointer-lock release
audio_unit.rs voice input native format and ducking level, on every
microphone open, which a dictating app does repeatedly
None of them report a problem or anything the app developer can act on;
they describe internal decisions in makepad's own vocabulary. Error and
warning paths beside them are untouched, as are logs already gated behind
an env var, a feature, or a once-per-process flag.
* macOS: don't deliver IME-consumed keys as KeyDown
`process_ns_event` hands each NSEvent to AppKit via `sendEvent:` before
emitting Makepad's own KeyDown. When an IME has marked (composition)
text, that dispatch lets the IME consume the key: Return/Space commit
the candidate, digits pick one, arrows navigate, Escape discards,
Backspace edits the preedit. A committing key clears the marked text
during dispatch, so the old post-dispatch `hasMarkedText` check (which
only covered Backspace) could not see it, and the Return that merely
committed a pinyin candidate was also delivered as KeyDown(ReturnKey).
TextInput then treated it as a submit.
Snapshot `hasMarkedText` before `sendEvent:` and skip the KeyDown
callback when the IME was composing. This subsumes the Backspace check
and also fixes the case where Backspace deleted the last preedit
character and then fell through to delete committed text.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017HLgZDz8vuuqqMya1nCWKf
* macOS: preserve IME commands and pair consumed key releases
---------
Co-authored-by: ymote <151983+ymote@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Html: keep collapsed details with void tags from swallowing table closures
* Html: harden the parser, the walker and the widget against malformed input
Follows the void-tag fix in the `<details>` skip loop by auditing the rest of
the HTML code for the same class of defect. Robrix renders `formatted_body`
straight from Matrix events, so every parser crash here is reachable from a
message any stranger can send.
Crashes, all reachable from a chat message:
- Numeric character references were parsed as `i64` and cast to `u32`, then
handed to `char::from_u32(..).unwrap()`. `�`, `�`, `&#-1;`
and `�` aborted the process. They are validated now, and a
reference that names no scalar value stays literal text.
- An unterminated `&` stayed pending across a tag boundary or a closing
attribute quote, so a later `;` could fire `decoded.truncate()` and
retroactively invalidate byte ranges of nodes already emitted —
`<p>&am<b>p;</b></p>` produced out-of-bounds and mid-character ranges.
The pending entity is dropped at each of those boundaries.
- An unquoted attribute value beginning with a multi-byte character recorded
`decoded.len() - 1` as its start, splitting the character.
- `</summary>` with no `<summary>` popped an empty tracker stack, and stray
`</td>`, `</tr>`, `</li>` and friends reached `cx.end_turtle()` with nothing
to end. The widget now tracks what it opened and ignores unmatched closes.
- `('A' as u8 + count as u8 - 1)` overflowed on an attacker-controlled `start`
or `value`; alphabetic list markers now number a..z, aa, ab, ...
Content silently lost or mangled:
- `jump_to_close` counted every open tag toward depth, but a void element
written without a slash emits no close tag, so it overshot and swallowed the
rest of the document. `<a href=u>x<br>y</a>` hid everything after the link.
Only tags with the same id affect depth now, and an element with no close tag
leaves the walker where it is. `mod_html::find_close_tag` had it too.
- A `<` that cannot start a tag is literal text, the way a browser reads it.
`5<10 and 6<12` used to parse `<10` as an element and drop the rest.
- `?` mid-tag-name and `<!-->` / `<!--->` ran to end of input.
- `/` in an unquoted value ended it, truncating `href=http://host/path` at the
first slash; only a slash immediately before `>` closes the tag now.
- `<a href=>text</a>` took `>` as the value's first character, so the tag never
closed and its content leaked out as text.
- Unquoted values never decoded entities at all, unlike quoted ones.
- `<pre>`/`<code>` whitespace preservation was a single flag that any nested
tag cancelled, so a syntax-highlighted code block lost its indentation. It is
a depth counter now.
- HTML's whitespace set is five ASCII characters, not Unicode's;
`char::is_whitespace` collapsed ` ` runs and ate the full-width spaces
in CJK text.
- `find_text` returned the zero-length node the parser emits before every tag,
so `<a href=x><b>label</b></a>` rendered an empty link. `find_tag_text`
matched the case-sensitive id and missed any tag carrying an attribute.
- Duplicate `id` attributes bound two elements to one cached sub-widget, so a
second link could render its own text over the first link's href.
- `<li>a<li>b` and `<td>a<td>b` now implicitly close the previous item, and
anything a document leaves open is unwound before `TextFlow::end`.
Entity table, which had been generated by folding names case-insensitively:
- 146 names took their case-twin's code point. `é` rendered `É`,
`α` rendered `Α`, `→` rendered `⇒`, `𝕔` rendered `ℂ`.
- `Igrave`/`Icirc`/`Iuml` had been transcribed as `Lgrave`/`Lcirc`/`Luml`, and
`Iacute` was missing outright; the invented l-spellings are removed.
- `permil` mapped to the Windows-1252 byte 0x89 rather than U+2030, and an
empty-string key sat where it belonged, so `&;` decoded to `‰`.
- `tilde`, `lang` and `rang` were wrong.
The ALL-CAPS aliases the table also carries are left as they were.
Also: dropped the `unwrap` in `ElementSelfClose`, memoised table column counts
(quadratic in the number of `<table>` tags), and replaced the backward node
scan on every tag close with the depth counter.
Adds 18 tests covering each of the above. Verified by exhaustive enumeration of
all 12.2M inputs up to length 6 over a markup-heavy alphabet, and 6M randomized
structured cases, both checking that no input panics and that every node's byte
range is ordered, in bounds, on a character boundary, non-overlapping, and
agrees with its `all_ws` flag.
* Html: recover from malformed tags without leaking them into the text
A second pass over the same code, after the first round of fixes changed what
the edge cases look like.
- `</` followed by something that cannot name an element is literal text, the
rule `<` already follows. `i </3 u` used to emit a close tag named `3` and
drop the rest of the line.
- Junk inside a tag is discarded up to its `>` rather than resuming text in the
middle of it, which leaked the tag's own `>` into the output: `a</p x>b` and
`a<br/x>b` rendered `>b`.
- A custom widget with no close tag of its own is void, so it has no text.
Reading ahead picked up the *following* sibling's text, and now that
`jump_to_close` correctly stays put, the main loop drew that text a second
time: `<img src=x>caption` showed `caption` twice.
- `table_columns_cache` is keyed by node index, so it has to be cleared per
draw or a recycled widget lays a table out with a previous document's column
count.
- `<ol start="2147483647">` overflowed the item counter.
* Html: bound jump_to_close's scan and cut the measured hot spots
Benchmarked against the branch point (best-of-7, black_box'd, release).
- `jump_to_close` stops at the first close tag belonging to an enclosing
element instead of reading to the end of the node vector. It tracks the
elements opened inside this one so a descendant's close tag is still
matched correctly, and allocates nothing for the common case of an element
whose content is plain text.
- Numeric character references were compared against all ~1500 named-entity
arms before reaching the catch-all. Dispatching on the leading `#` first
makes them 2.9x faster (991us -> 342us for 3000 references).
- `process_entity` is `#[inline]`; it is called once per character.
- `decoded` is reserved up front, worth ~4% on text-heavy input. `nodes`
deliberately is not: its length tracks tag count rather than byte count, and
sizing it from `body.len()` cost a tag-sparse document a large pointless
allocation — that made the numeric-entity case 3x *slower* before it was
measured and removed.
- The widget rejects an unmatched close tag from a tally instead of scanning
the whole open-element stack, which was quadratic on a message combining
deep nesting with stray close tags.
- `align_keyword_to_x` compares in place rather than lowercasing into a fresh
String for every aligned cell on every draw.
Tag-heavy parsing is ~2-3% slower than the branch point, which is the standing
cost of the `<pre>` depth tracking, the literal-`<` guard and the entity state
carried across characters. Plain text is ~4% faster.
* Html: follow the tokenizer's recovery rules and resolve element ends at parse time
The parser's states now mirror the WHATWG tokenizer's, so malformed input
produces the tokens a browser would build from it rather than a guess:
- `</` followed by anything but a letter opens a bogus comment that runs to
the next `>`, `</>` is dropped, and `<?...>` is a bogus comment too. `<`
or `</` at the very end of input is text.
- `<a/b>` reads as `<a b>`: the slash was not a self-closing marker, so no
close tag is synthesized. `<x/>` still emits one — the SVG parser is built
on this walker and XML needs it — which is the one deliberate departure.
- In an unquoted attribute value a `/` is just another character, so
`href=http://host/path` keeps its path and `<img src=x/>` is `src="x/"`.
- `<!--x--!>` closes a comment, `<!-x>` is a bogus comment, and a tag cut
off by the end of input is dropped whole.
- Numeric character references follow the tokenizer's end state: zero, a
surrogate, or anything past U+10FFFF becomes U+FFFD, and the C1 range is
read as Windows-1252, so `—` is an em dash as legacy content intends.
Digits are accumulated with saturation so a forty-digit reference lands on
U+FFFD rather than an error. A decoded space collapses like a literal one.
- `<pre>`/`<code>` are tracked as a stack: a stray `</code>` cannot cancel an
enclosing `<pre>`, and `</pre>` closes a `<code>` left open inside it.
Every element's end is now resolved once at parse time (`HtmlDoc::closes`),
with the recovery a browser applies: a close tag ends the innermost open
element of its name and everything still open inside it, and a close tag
that matches nothing is ignored. `jump_to_close` and the new
`HtmlWalker::close_index` are lookups, which removes the last quadratic
case — a paragraph of thousands of `<img>` tags cost 3.4ms a frame — and a
stray `</span>` no longer stops a link's `</a>` from being found. The tally
that rejects stray close tags hashes `LiveId` through an identity hasher,
since it is already a 64-bit hash; with SipHash the pass cost 20%.
Widget:
- A `<summary>` left open is closed by `</details>` or the end of the
document, so its bold run and glyph tracker no longer leak into everything
drawn after it.
- Implicit closes follow the tree builder's scope rules — `<li>` closes an
open item up to its list, a cell up to its row, a row with its cells, a
heading directly following a heading, and any block element an open `<p>`
— rather than only the innermost element.
- A custom widget's label is all the text inside it, so
`<a href=x><b>Click</b> me</a>` reads "Click me", and a void one has none.
- Sub-widgets are keyed only by node index. Keying by the `id` attribute let
a document choose cache keys, and a repeated id bound two links to one
widget.
- `TrimWhitespaceInText`, `combine_spaces` and `ignore_newlines` are gone:
all three were written at every site and read at none.
- List markers are borrowed rather than allocated per item per draw, table
cell alignment compares in place, and link hit-testing no longer clones
its area list on every event.
Script module: `.html` printed raw hex for every tag and attribute name,
because the document was parsed without interning; it is interned now and
text and attribute values are escaped on the way out, so the output parses
back to the same document. `find_elements` counted every open tag toward
depth, the void-element bug again; it steps by resolved close index.
23 parser tests, exhaustive enumeration of all inputs up to length 6 over a
markup-heavy alphabet, and 6M randomized structured cases, checking that no
input panics and that every node range and close index is consistent.
* Html: resolve every element's end in the tokenizer, and close the review's findings
An adversarial review of the previous commit against the WHATWG tokenizer,
the branch point and a reference parser found the gaps below. All fixed.
The parser now keeps the open-element stack as tags stream past, so each
element's end is resolved in the same pass that tokenizes it — the recovery a
browser's tree builder applies: a close tag ends the innermost open element
of its name and everything still open inside it; a close tag that matches
nothing is ignored; the spec's void elements are whole at their open tag;
what is still open at end of input ends there. `HtmlDoc` records both the
element's own close tag (`close_index`) and where it ends (`end_index`).
That distinction was missing: an element ended by an ancestor looked the
same as a void one, so the script module gave `<li>a<li>b` items empty
ranges — no `.text`, no `.html`, children promoted to siblings — and the
widget dropped the label of a link ended by `</td>`. Both read correctly now.
Because the whitespace-preserving stack is the same stack, a `<pre>` ended
by an enclosing element's close tag stops preserving at that tag, which it
did not before.
Tokenizer fixes, each per the spec's state machine:
- `<!>` and `<!->` are complete bogus comments; they used to swallow text up
to the next `>`.
- A numeric character reference ends at the first non-digit whether or not
`;` follows (`& b` reads `& b`), and has no length limit: forty digits
saturate to U+FFFD as the previous commit claimed but did not do.
- An end tag followed by junk and then end of input is dropped like any
other tag cut off there; it used to emit its close tag anyway.
- `\r\n` and lone `\r` become `\n`, as the input stream preprocessing says.
- A repeated attribute name on one tag is dropped, so a consumer iterating
attributes sees the first `data-mx-color` rather than the last.
- A comment is not content, so `a <!-- c --> b` collapses to one space.
- `find_tag_text` answers for the first matching element and does not fall
through to a later one.
The maps that reject stray close tags and duplicate attributes are keyed
with a per-parse random seed and a multiply-fold hash: the previous identity
hasher let crafted tag names collide and made the pass quadratic, and the
standard SipHash cost a quarter of the parse time.
Widget:
- A `<summary>` is tied to the `<details>` that owns it. A `<details>` opened
inside a summary was taken for the owner, and `</details>` then popped an
empty tracker stack — a panic reachable from a chat message.
- `</summary>` and `</details>` end whatever was opened inside them, so an
`<li>` or a table cell opened in a summary no longer swallows the content
that follows.
- A collapsed body is skipped to the element's resolved end, so a
`<details>` ended by an ancestor no longer hides everything after it.
- `count_table_columns` ends the first row at the next `<tr>` as well as
`</tr>`; a table written without `</tr>` had every column halved.
- The `<p>` rule runs before the heading rule, as the tree builder orders
them, so `<h1><p>a<h2>` no longer nests the second heading in the first.
Script module: ranges are `(open, end)` with an exclusive end; `parse_query`
no longer panics on `a]b[`.
30 parser tests, exhaustive enumeration of all inputs up to length 6 over a
markup-heavy alphabet, and 6M randomized structured cases, checking every
node range, every `close_index`/`end_index`, nesting consistency, and
determinism.
* Html: build the tree builder's implicit closes into the parser, and end every element where it says
Two verification rounds against the previous commit — a spec-conformance
review, a stack-based reference for element ends, a simulation of the widget's
draw loop over exhaustive and random tag soups, and a round-trip check of the
script module — found the gaps below. All fixed.
The parser now applies the tree builder's implicit closes as it builds the
element stack: a block start tag closes an open `<p>`; a heading closes a
heading that is the current node; `<li>` closes an open item up to its list,
`<dd>`/`<dt>` likewise; a cell closes an open cell up to its row; `<tr>` closes
a row and its cells; a table section closes section, row and cells; a second
`<a>` closes the first. Every consumer therefore sees the tree a browser
builds: `<li>a<li>b` is two items, `<a href=1>x<a href=2>y</a>` two links,
and `<li><a href=u>one<li>two` gives the first link the label "one" rather
than "onetwo". The widget's own copy of these rules is gone; it closes each
element at the index the parser resolved, before that node is handled, and
`<details>`/`<summary>` without a close tag of their own are ended the same
way. Two bugs that fell out of them being special:
- a `<details>` ended by an enclosing close tag stayed on the stack, a later
`<summary>` bound to it, and the collapse-skip resumed *behind* the walker.
One stale level drew the text twice; N of them re-walked the document 2^N
times — a 380-byte message hung the UI. A resume is now never behind the
walker, and no level is left behind to be claimed.
- a `<summary>` ended by an enclosing close tag never popped its bold run and
glyph tracker, which leaked into everything drawn after it.
Per-name depth stacks replace the per-name counts, so finding the innermost
open element of a name, or the outermost one above a scope boundary, is a
lookup; scanning the stack made a document of nested `<div>`s quadratic.
A tag with thousands of attributes no longer makes every later tag pay to
clear the attribute-name set. Every nesting shape measured is linear.
Tokenizer and tree builder, per the spec: `</br>` is read as `<br>`, so
`x</br>y` breaks the line; the newline immediately after `<pre>` is not
content; NUL is dropped from text and replaced in attribute values.
Widget: a table whose first row is empty is sized by the first row that has
cells rather than falling back to 100px columns.
Script module: `.html` always writes `=""` and doubles a newline that starts
a `<pre>`, so its output parses back to the same document; `.text` is the
decoded text verbatim, no longer inventing a space inside a word split by a
comment or an inline tag; a query on a selection searches inside it, as
`querySelectorAll` does; descendant steps skip ranges already scanned, which
made `b b` on deeply nested `<b>` quadratic; `parse_query`'s grammar is
documented as implemented.
Deliberately unchanged: the entity table's omissions (`€`, ...), named
references without `;`, and an unquoted attribute value ending in `/` before
`>` (per the tokenizer the slash is part of the value; XML requires quotes).
33 parser tests; exhaustive enumeration of all inputs up to length 6 over a
markup-heavy alphabet and 6M randomized structured cases, checking every node
range, every `close_index`/`end_index`, nesting consistency, attribute
dedupe and determinism.
* cargo-makepad: declare native speech recognition metadata
* cargo-makepad: make Apple plist customization opt-in
* cargo-makepad: use macOS tools for plist overlays
* TextInput: add replace_range for edits that aren't typing
Anything that writes into a text field without being the keyboard —
dictation, autocomplete, a paste button — had only set_text and
restore_state to work with. Both clear the undo history, neither emits
Changed, and both end an IME composition that the platform keyboard still
thinks is in progress, so the next commit from the keyboard lands on text
the widget no longer agrees about.
replace_range(range, text, UndoGroup) goes through the same edit path as
typing: the input filter applies (text it rejects outright is refused
rather than deleting the range), the edit lands in the undo history,
Changed is emitted, and the selection is carried across it (anything that
was inside the range ends up after the replacement). UndoGroup::Extend
joins consecutive external edits into one undo step, so every revision of
a dictated phrase undoes together, while any typing in between splits
them. The IME composition belongs to the keyboard: an edit beside it moves
it and pushes the new text to the platform, and an edit overlapping it is
refused with ReplaceRangeError::Composing. is_composing() lets callers
wait for it to clear, and force_new_edit_group is now reachable from the
ref as well.
Undo and redo now end a composition, since the text the IME was composing
is gone with the rewind; the widget used to keep pointing at it, which
also stalled its IME syncing until focus was lost.
* ios: report the keyboard's marked text as the IME composition
The UITextView bridge forwarded marked text (kana awaiting conversion and
the like) to the widget as plain text with no composition range, so
TextInput never knew the keyboard was mid-composition on iOS:
is_composing() stayed false, replace_range's Composing refusal never
engaged, and the widget's next push did a whole-text setText: that
detached the keyboard's composition from the buffer.
forward_state_to_makepad now reads markedTextRange and carries it through
full_state_sync, so the widget records it exactly as it does for Android.
When the widget pushes text while it still holds a composition (an edit
beside it through replace_range), set_ime_text writes the text around the
composition and marks the composed part again with setMarkedText: instead
of committing it.
* macos: complete the handshake with a development launcher
`cargo run` starts a bare executable, which macOS gives no bundle identity.
Microphone, speech and location prompts are then attributed to the terminal
or editor that spawned it, and denied outright when that process has no
matching usage description, so a permission-using app cannot be developed
with plain `cargo run` at all. The way around it is a cargo runner that
launches a real .app through LaunchServices.
Three things are then lost, because LaunchServices forks the process itself
and starts it in `/`: the runner never learns the app's pid, so it has
nothing to forward a Ctrl-C to; it cannot pass on the terminal's working
directory; and it never sees the app's exit code, so `cargo run` always
reports success. All three are only knowable in-process.
The macOS event loop now reports them through the directory named by
MAKEPAD_DEV_LAUNCH_DIR, adopting MAKEPAD_DEV_WORKING_DIR before any
resource is loaded. Apps launched any other way see neither variable and
do nothing, so this replaces the same handshake being hand-written in every
app's main() that wants to develop against a permission-gated API.
* button: honour grab_key_focus on press
Button's FingerDown handling took keyboard focus twice: once guarded by
grab_key_focus, and again unconditionally at the end of the arm. That
second call is `self.set_key_focus(cx)`, which resolves through
WidgetNode to `cx.set_key_focus(self.area())` — and the derived `area()`
comes from the `#[redraw] draw_bg` field, so it is exactly the call the
guard above it wraps.
The upshot was that `grab_key_focus: false` did nothing on press, and a
button that deliberately opts out of focus still pulled it away from
whatever held it. Robrix's dictation hit this: tapping the microphone took
focus off the composer, hiding the caret the transcript is inserted at and
dismissing the soft keyboard on mobile, so it had to hand focus back by
hand afterwards.
Dropping the unconditional call leaves behaviour identical wherever the
flag is true, which is its default and every use in this repository.
* cargo-makepad: declare native speech recognition metadata
* cargo-makepad: make Apple plist customization opt-in
* cargo-makepad: use macOS tools for plist overlays
* TextInput: add replace_range for edits that aren't typing
Anything that writes into a text field without being the keyboard —
dictation, autocomplete, a paste button — had only set_text and
restore_state to work with. Both clear the undo history, neither emits
Changed, and both end an IME composition that the platform keyboard still
thinks is in progress, so the next commit from the keyboard lands on text
the widget no longer agrees about.
replace_range(range, text, UndoGroup) goes through the same edit path as
typing: the input filter applies (text it rejects outright is refused
rather than deleting the range), the edit lands in the undo history,
Changed is emitted, and the selection is carried across it (anything that
was inside the range ends up after the replacement). UndoGroup::Extend
joins consecutive external edits into one undo step, so every revision of
a dictated phrase undoes together, while any typing in between splits
them. The IME composition belongs to the keyboard: an edit beside it moves
it and pushes the new text to the platform, and an edit overlapping it is
refused with ReplaceRangeError::Composing. is_composing() lets callers
wait for it to clear, and force_new_edit_group is now reachable from the
ref as well.
Undo and redo now end a composition, since the text the IME was composing
is gone with the rewind; the widget used to keep pointing at it, which
also stalled its IME syncing until focus was lost.
* ios: report the keyboard's marked text as the IME composition
The UITextView bridge forwarded marked text (kana awaiting conversion and
the like) to the widget as plain text with no composition range, so
TextInput never knew the keyboard was mid-composition on iOS:
is_composing() stayed false, replace_range's Composing refusal never
engaged, and the widget's next push did a whole-text setText: that
detached the keyboard's composition from the buffer.
forward_state_to_makepad now reads markedTextRange and carries it through
full_state_sync, so the widget records it exactly as it does for Android.
When the widget pushes text while it still holds a composition (an edit
beside it through replace_range), set_ime_text writes the text around the
composition and marks the composed part again with setMarkedText: instead
of committing it.
* macos: complete the handshake with a development launcher
`cargo run` starts a bare executable, which macOS gives no bundle identity.
Microphone, speech and location prompts are then attributed to the terminal
or editor that spawned it, and denied outright when that process has no
matching usage description, so a permission-using app cannot be developed
with plain `cargo run` at all. The way around it is a cargo runner that
launches a real .app through LaunchServices.
Three things are then lost, because LaunchServices forks the process itself
and starts it in `/`: the runner never learns the app's pid, so it has
nothing to forward a Ctrl-C to; it cannot pass on the terminal's working
directory; and it never sees the app's exit code, so `cargo run` always
reports success. All three are only knowable in-process.
The macOS event loop now reports them through the directory named by
MAKEPAD_DEV_LAUNCH_DIR, adopting MAKEPAD_DEV_WORKING_DIR before any
resource is loaded. Apps launched any other way see neither variable and
do nothing, so this replaces the same handshake being hand-written in every
app's main() that wants to develop against a permission-gated API.
* cargo-makepad: declare native speech recognition metadata
* cargo-makepad: make Apple plist customization opt-in
* cargo-makepad: use macOS tools for plist overlays
* TextInput: add replace_range for edits that aren't typing
Anything that writes into a text field without being the keyboard —
dictation, autocomplete, a paste button — had only set_text and
restore_state to work with. Both clear the undo history, neither emits
Changed, and both end an IME composition that the platform keyboard still
thinks is in progress, so the next commit from the keyboard lands on text
the widget no longer agrees about.
replace_range(range, text, UndoGroup) goes through the same edit path as
typing: the input filter applies (text it rejects outright is refused
rather than deleting the range), the edit lands in the undo history,
Changed is emitted, and the selection is carried across it (anything that
was inside the range ends up after the replacement). UndoGroup::Extend
joins consecutive external edits into one undo step, so every revision of
a dictated phrase undoes together, while any typing in between splits
them. The IME composition belongs to the keyboard: an edit beside it moves
it and pushes the new text to the platform, and an edit overlapping it is
refused with ReplaceRangeError::Composing. is_composing() lets callers
wait for it to clear, and force_new_edit_group is now reachable from the
ref as well.
Undo and redo now end a composition, since the text the IME was composing
is gone with the rewind; the widget used to keep pointing at it, which
also stalled its IME syncing until focus was lost.
* ios: report the keyboard's marked text as the IME composition
The UITextView bridge forwarded marked text (kana awaiting conversion and
the like) to the widget as plain text with no composition range, so
TextInput never knew the keyboard was mid-composition on iOS:
is_composing() stayed false, replace_range's Composing refusal never
engaged, and the widget's next push did a whole-text setText: that
detached the keyboard's composition from the buffer.
forward_state_to_makepad now reads markedTextRange and carries it through
full_state_sync, so the widget records it exactly as it does for Android.
When the widget pushes text while it still holds a composition (an edit
beside it through replace_range), set_ime_text writes the text around the
composition and marks the composed part again with setMarkedText: instead
of committing it.
`process_ns_event` hands each NSEvent to AppKit via `sendEvent:` before
emitting Makepad's own KeyDown. When an IME has marked (composition)
text, that dispatch lets the IME consume the key: Return/Space commit
the candidate, digits pick one, arrows navigate, Escape discards,
Backspace edits the preedit. A committing key clears the marked text
during dispatch, so the old post-dispatch `hasMarkedText` check (which
only covered Backspace) could not see it, and the Return that merely
committed a pinyin candidate was also delivered as KeyDown(ReturnKey).
TextInput then treated it as a submit.
Snapshot `hasMarkedText` before `sendEvent:` and skip the KeyDown
callback when the IME was composing. This subsumes the Backspace check
and also fixes the case where Backspace deleted the last preedit
character and then fell through to delete committed text.
Claude-Session: https://claude.ai/code/session_017HLgZDz8vuuqqMya1nCWKf
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The fe5b75d92 merge took upstream's widget_async.rs and dropped the
wm isolate-entry API (IsolateEntry, enter_isolate, leave_isolate)
while lib.rs and apps/flow-ui + apps/wm still use it, breaking
makepad-widgets. Restored verbatim from 77d91385f; structs unchanged
so the code applies as-is. makepad-widgets, makepad-app-flow-ui and
makepad-wm all check clean.
- bundle_crate_secondary_dex: merge OUT_DIR/classes.dex from dependency
build scripts (robius-sms/trigger/ussd javac+d8 Java for manifest
receivers/services) into the APK as classesN.dex. Without it the
manifest declares BootReceiver/SmsReceiver/AccessibilityService that
are missing at runtime (ClassNotFoundException on boot).
- has_explicit_lib_target: a crate with src/main.rs containing app_main!
is an app, not a lib — generate the android wrapper ([lib] path =
src/main.rs) so the JNI entry (activityOnCreate) is linked. Fixes
UnsatisfiedLinkError for app crates that also ship src/lib.rs.
Nine agent skills for building Makepad/Rust games, ported from
majidmanzarpour/threejs-game-skills. Same director-routed workflow and
premium bar; runtime rewritten for this fork's game crates.
- makepad-game-director entrypoint, routing, continuity, asset probe
- makepad-gameplay-systems loop, movers vs rigid bodies, input, camera, netplay
- makepad-aaa-graphics-builder lighting, shaders, budget, visual scorecard
- makepad-game-ui-designer HUD, menus, touch and XR UI
- makepad-debug-profiler defect bisection and profiling
- makepad-qa-release verification ladder, evidence, packaging
- makepad-3d-generator CC0 model search, casts, procedural geometry
- makepad-image-generator texgen textures, palettes, sky, icons
- makepad-audio-generator sample bank, mixer, material impacts, 3D audio
Written against the real APIs in libs/game/*, libs/sim and apps/arcade:
the game.* verb table, GameRenderer adaptive quality, the packed 6-float
GameMeshVertex layout, script_mod! splash styling, makepad-test driving,
and the BUDGETS.md numbers. No paid generation API is required - the CC0
library plus seeded makepad-game-gen replaces them.
Includes install.sh (Codex/Claude), validate-skills.sh and a repo-aware
probe_assets.sh; both scripts verified against this checkout.
Use cached transparent 2D and cube textures for unallocated optional samplers without bypassing invalid-resource, target, or framebuffer-feedback guards.
Require actual glyph draws in release smoke tests and keep shader compilation asynchronous in probe instrumentation.
Bound drawable, geometry, texture, image, map and radar work; validate WebGL submissions and retire GPU resources safely. Treat context loss as terminal without re-entering Wasm after worker termination.
Add explicit Route location consent and regression coverage, including software-WebGL release probes for six deployed demos.
Reintroduce apps/arcade and the ten game crates that were ripped out of
the fork (assets, audio, blocks, coedit, gen, net, pkg, render, script,
session), from makepad-internal/dev. Reconcile against the fork's layout:
- makepad-game-sim/math come from the fork's existing libs/sim + sim/math;
the copied internal libs/game/{sim,math} are dropped and the kept game
crates plus arcade point at ../../sim and ../../sim/math.
- arcade AI is rewritten off the removed makepad-ai agent API onto the
makepad-ai-hub headless ChatProvider worker (example/cad pattern):
AiWorker{send,cancel,poll}, ai_worker_loop driving
ClaudeApiChatProvider(ClaudeCli), AiWorkerEvent availability/delta/
done/error mapped into the chat feed and authoring land_edit, with
main.rs send_message/cancel_request/event-drain rebuilt around it.
- the game-script sandbox is adapted to the fork's jailed splash storage:
every isolate gets mod.fs = splash_storage jail; splaes splash_storage
set_root_for_heap is now public so the ScriptHost (or Splash) can grant
a per-game jail root. ENT font: build.rs/dispatch.rs entities fill new
fork sim Entity/Part fields via ..Default::default(); world_raycast now
borrows mutably and returns the material id.
cargo check -p makepad-arcade and the game + arcade test suites pass.
Host or attach the asset service, archive generated and terminal outputs, and provide image/video/audio/text/3D viewers with fullscreen and gallery navigation. Load only visible bounded previews, generate video posters and text excerpts, and normalize legacy thumbnails off the UI thread.
Expose video frame counts and Sandbox chat providers, make failed queue cleanup idempotent, compact the panels, and correct short-wire direction. Replace App view with a movable, resizable CodeEditor overlay with debounced source saves, live graph updates, invalid-source preservation and close/reopen geometry.
Validation: release Flow, Flow UI and flowgraph suites passed 335 tests (two ignored). Updated Flow UI suite passed 107 tests; asset route suites passed nine tests after legacy preview fixes. The rebuilt release app passed remote editing, dragging, resizing, invalid-source, close/reopen and App removal checks; final All assets scrolling measured 16.5 ms median and 18.3 ms maximum over 12 inputs. Test instances were closed through the remote protocol.
Update deck and mixer synchronization, apply matched deck rates to splats, and queue remote stem extraction through the fleet. Refresh catalog and creator pipeline integration.
Validation: release VJ suite reports 890 passed, 10 failed, nine ignored. Eight mixer failures reproduce in serial execution (gain, transitions, sample alignment and video fades); two asynchronous media tests also failed in the full run. The new matched-rate splat test passes. These remaining failures are not claimed as fixed.
Bundle the recovered numbered scene bank, support sixteen slots, and keep editable current state and overrides in the local overlay. Validate legacy control mapping.
Validation: 27 release tests passed.
Use output/presentation timestamps instead of AVPlayerItem.currentTime on the UI thread. Consume only fresh frames and stop polling paused players after their poster arrives. Balance Objective-C ownership and make native cleanup idempotent.
Validation: release Flow playback smoke and process sampling; the previous currentTime mutex hotspot is absent in the updated sample.
Find numeric completion actions reliably and restore menu focus using tracked areas. Handle touch dismissal and focus changes without leaving menus open.
Add sandbox authoring effects and character/composite creation, improve creator submission handling, and extend publishing/search metadata. Update importer conversions and the asset UI integration.
Validation: 31 store search tests, ten composite tests and two author-policy tests passed in release mode.
Account for missing and partial model files per volume, reserve headroom, and reject disk-constrained workers before accepting a job. Preserve typed admission failures so callers can choose another peer, and make activity gating and cancellation recover cleanly.
Validation: 592 release hub tests passed, one ignored; required-CUDA builds deployed to six idle Windows workers.
* wayland: stop inverting the scroll direction
Wayland's wl_pointer axis values already carry Makepad's scroll
convention -- positive vertical means scroll down, i.e. the viewport
moves down. The backend negated them, so wheel and touchpad both
scrolled backwards relative to X11, macOS, Windows and web.
The spec pins the sign in wl_pointer::axis_relative_direction, whose
`identical` case is a user's fingers moving down producing a
"vertical_scroll down" axis event. libinput, which produces the values
compositors forward, documents the same: "the positive direction being
down or right". Makepad's own convention matches -- ScrollBar applies
`scroll_pos + e.scroll.y` against a position clamped to
[0, view_total - view_visible], and the turtle draws content at
`origin - layout.scroll` inside a clip rect fixed at the unshifted
origin, so a positive delta moves the viewport down.
The negation came from #875, which read a positive axis value as content
sliding down and cited winit's negation as precedent. But winit's
MouseScrollDelta is documented as positive = content moves down, the
inverse of Makepad's convention -- winit's own comment reads "Wayland
sign convention is the inverse of winit" -- so copying it was a double
negation. Whether a toolkit negates is decided by its own convention,
not by anything about Wayland: GTK, which shares Makepad's convention,
passes the values through; SDL and Chromium negate because theirs are
inverted, and SDL negates vertical only, which is self-consistent just
in case Wayland's +y is down and +x is right. #875 also cited the web
backend as agreeing, but web forwards DOM deltaY unnegated, and deltaY
is positive when scrolling down.
The AxisDiscrete and AxisValue120 handlers added later inherited the
sign, so all six sites flip together; the spec states each expresses its
direction along the same axis as the coupled axis event.
Natural scrolling needs no client-side handling. libinput applies it in
evdev_notify_axis_*, below the compositor, so the delivered axis value
already reflects the user's setting -- the negation was not implementing
that, it inverted both settings equally. AxisRelativeDirection stays
ignored, which is correct for scrolling content; it exists so widgets
that should track the physical wheel regardless of the setting (the
spec's example is a volume slider) can recover the direction.
Fixes#1173
* wayland: classify the scroll source, and choose each axis's delta on its own
Five defects in the wl_pointer frame handler, adjacent to the sign fix in
the previous commit but independent of it.
The detent-vs-pixel choice was made once for both axes, so a frame
carrying detents on one axis and only a smooth value on the other scaled
that second axis by a zero detent count and silently dropped it. Each
axis now chooses on its own.
`scroll_is_wheel` collapsed a five-valued classification into "Wheel vs
everything else", and its false default meant "finger gesture". So a
wheel tilt discarded its detents, a continuous source — a trackpoint, or
button-held scrolling — was reported as a touchpad gesture, and so was a
frame from a compositor that sent no axis_source at all, the event being
optional and sent only when the source is known. That default is the one
classification that can strand a widget: ScrollPhase::Ended is what
springs a stretched rubber band back, only a finger source is guaranteed
an AxisStop, and the spec tells clients to treat every other source as
unterminated by default. The bool gives way to the source itself, and a
sourceless frame is classified by whether it carried detents.
A bare AxisStop no longer dispatches for a source with no gesture to end.
Compositors stop an axis whenever its value reaches zero, whatever the
source, and a zero-delta ScrollPhase::None clears a widget's overscroll
and cuts short a running bounce.
Nor is a stop arriving alongside live motion treated as lift-off. Per the
frame event: "When a wl_pointer.axis and a wl_pointer.axis_stop event
occur within the same frame, this indicates that axis movement in one
axis has stopped but continues in the other axis." And because
axis_source is per-frame and optional, a gesture in flight now carries
its classification forward, so a lift-off frame that omits the source
still ends the gesture instead of losing the terminator.
The raw-pixel fallback for an axis without detents stays unscaled, which
is a deliberate non-change rather than an oversight. No units-per-detent
constant exists to scale it by — compositors disagree, and hwdb ships
wheels from 10 to 30 degrees per click — and a physical wheel never
reaches it: the fallback is for virtual pointers, whose axis value the
protocol already defines as a distance.
Finally, the claim that ScrollPhase::Ended lets widgets run their own
momentum fling was wrong. Widgets start their fling on
ScrollPhase::Momentum, which only macOS emits, so Wayland touchpads have
no kinetic scrolling at all; the comment now says that rather than its
opposite.
The frame decision moves into `frame_scroll`, which puts every case above
under a unit test instead of leaving it to be re-derived by reading.
Makepad windows on Wayland had no drop shadow, which on GNOME reads as
broken next to everything else on the desktop. Mutter implements no
server-side decoration protocol at all -- it advertises neither
zxdg_decoration_manager_v1 nor any KDE equivalent, and its shadow code
(MetaShadowFactory) lives in src/x11/ and isn't even in the
introspection surface. Every shadow on that desktop is drawn by the app
that owns the window.
So draw one, out of eight wl_subsurfaces hung outside the toplevel: four
corner tiles and four edge strips, backed by one memfd wl_shm pool and
sized with wp_viewport, with xdg_surface.set_window_geometry keeping them
out of the window's logical bounds. GTK instead oversizes its own surface
and paints the shadow into a transparent margin. Subsurfaces keep the GL
surface exactly window-sized, so the shadow costs no per-frame GPU fill,
and no margin ever crosses the platform/widget boundary -- which is the
entire class of off-by-a-margin bugs the other approach invites.
The profile is libadwaita 1.9's, computed rather than sampled. A
rectangle's Gaussian shadow is separable, so each box-shadow layer's 2-D
coverage is the product of two 1-D normal CDFs, and evaluating that for a
*square* rectangle is what makes the corners hug the window: sampling a
rounded window's shadow gives 14/255 where a square corner needs 44/255,
and fades the edge out over the last 20px before every corner. The
straight-edge profile this produces matches a capture of the real
libadwaita output to within 1/255, which is what the test pins. Corner
tiles reach 16px along each edge, far enough that they join the strips
bit-identically at any scale.
Resizing happens in the gutter, the way it does for every native app.
The shadow surfaces carry input regions whose union is the window rect
grown by 12px -- the same halo libadwaita gives its toplevels -- and each
piece maps to exactly one edge, so landing on a surface is the hit test.
Window controls no longer compete with the corner grabs for the pointer,
which is what let the close button swallow the top-right corner.
Server-side decorations are requested wherever a compositor offers them,
overridable per process with --wayland-decoration= or
MAKEPAD_WAYLAND_DECORATION, and fall back to the frame above. KWin and
wlroots grant them; GNOME cannot.
Alongside, the caption bar gains double-click-to-maximize, a right-click
window menu, resize cursors keyed off the wl_pointer.enter serial the
protocol actually asks for, and tiled/constrained edges that suppress the
grabs they cannot service -- degrading a corner to its free axis rather
than dropping it.
Finally, declare the toplevel's opaque region, under the same
`!transparent && backdrop == None` condition macOS already uses for its
layer's opaque flag. The buffer is ARGB8888, so without that promise a
compositor cannot learn the alpha is uniformly solid short of reading
every pixel: it must blend the whole window, cannot cull what the window
covers, and cannot scan a fullscreen buffer out directly.
Verified against a WAYLAND_DEBUG trace: over 67 committed frames the
shadow issues no protocol traffic at all, and set_window_geometry,
set_opaque_region and the nine wl_regions are each sent and destroyed
exactly once.
Clocked piano/ironfish/drum rack, program-bus mix, splat/mixer/music
updates. Silence the unused warnings that show up in `cargo check -p
makepad-vj --release`.
ViewSplat gains a typed set_scene_bytes so a viewer inside a face isolate never needs a script apply; MeshView gains visible.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The picker's own label counts the ready nodes, so the GPU list under a
chosen model was clutter; it stays only for a model no node can serve,
where it names why. A closed ComboBox reset its text field to the tail of
the label after set_text; the cursor now sits at the start.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Model and format pickers, the inspector's one-of rows, the Ask face's
choice and face-declared pickers all render as ComboBox; the popup and
its input map through the canvas zoom. ComboBoxRef gains changed_label
and set_selected_by_label so the bindings did not have to change shape.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The straight run out of a port is 10 px (8 px minimum) with the endpoint
fillets shrinking to fit, and the first and last vertical runs keep only
the narrow 6 px envelope from the wire's own card; other cards keep the
two-tier clearance and parallel wires keep their cable spacing.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The generator's own preview was clutter next to the Output card's
picture. The Image face drops its preview and generator cards are no
longer full-bleed; the two picture tests follow the picture to the
Output card.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The host wrapped every bound face control in a labelled row, so the
prompt card showed a "value" label beside a fixed-height box. A
multi-line text area is now its own row and follows the card's size.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A 54 px width chip left the "w" label one pixel column, which rendered
as a stray dot beside the number.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Routed mode never falls back to a curve: a wire with no corridor takes the
least-collision orthogonal path. Clearance is two-tier: the comfortable
12 px card clearance with 16 px fillets first, then 6 px per side with
8 px fillets, so a routed wire passes a 20 px gap. Bezier mode is a pure
cubic per wire. View → Wires switches the mode (in-process until the app
has a settings store).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The lib's script named FlowCanvasStyles unqualified before it was in
scope; the styles object never applied and every icon lookup came back
empty (the colours survived through the per-kind colour properties). The
style types are registered as components and the default references
mod.widgets.FlowCanvasStyles.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 5090 kept about 1.5 GB of CUDA allocator pool after evicting every
model, so a card that fits flux2-dev refused it (30510 MB free reported,
32090 MB in a fresh process). Admission now trims the pool when the last
resident is gone and re-measures before refusing; usable VRAM is measured
after the same trim; the refusal names the pool it released.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
makepad-flowgraph carries the canvas (camera, cards, ports, wires,
selection, edits out), the router, a string-keyed view model
(GraphView/NodeView/PortView/EdgeView), script-registered node and port
styles keyed by kind, and a three-method NodeFaces trait for per-node
face widgets. It depends on makepad-widgets only. flow-ui projects its
splash-evaluated Graph through a 147-line adapter (graph_view.rs), keeps
its faces, file writer, panels and services, and renders pixel-identical
to before (hidden before/after grabs, zero differing pixels).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The input notch was as wide as the output point; it is now a small notch.
The disc is a slight oval along the flow axis and the type icon shifts a
little toward the point (outputs) or away from the notch (inputs) so it
reads centred. Wires anchor at the tip of an output's point and at the
apex of an input's notch instead of the disc centre, so a wire leaves the
point along its axis.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Replaces the loose chevron beside the label. The cable side of an output
disc is drawn as a sharp > point, the cable side of an input disc as a >
dimple the point would fit into; both follow the card's flip. The type
icon stays centred and the labels sit close to the disc again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The direction arrow shared the disc with the type icon and crowded it.
It now centres in the gap between the disc edge and the port name, so an
input reads "disc > label" and an output "label > disc"; the type icon
returns to the disc centre and the labels move out to make the room.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Measured on the RTX 5090 (2026-09-04, 1024x1024, 8 steps, nvidia-smi 250 ms
samples): 30.5 GB used at peak, the run completed in 52 s. The old 29 GB
was a pre-measurement guess that under-reported the model by 1.5 GB.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
flux2-dev cannot fit on the 5090 at the default reserve, and the role
table barred the only card that can hold it. The user opened image on
10.0.0.165 ("let the rtx serve images too"); the role test and the
flow's role-aware listing test follow.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 5090 listed flux2-dev ready (total VRAM passed the gate) then refused
every job (29696 + 2048 reserve > ~30510 usable); the flow picked it by
domain ETA and never retried. Nodes now publish vram_usable_mb and mark
un-admittable models too_small; the fleet gate uses usable VRAM; the
flow's gen executor picks admitted nodes for the requested model (ready
first), retries up to three nodes after an admission refusal, and when no
node can take the model its error says why per node (role, too small with
the numbers, waiting for VRAM). The flow's model listing drops (model,
node) pairs the node's fleet role bars, so the picker no longer counts the
chat-only PRO 6000 as ready for image; its label reads ready/absent/too
small with the GPUs named.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* View: an on_item_tap hook for script-rendered lists
Rows built by `on_render` can't carry `on_click` closures (they stop the
list re-rendering), so lists had no way to be tappable.
* `on_item_tap: |index|` on the container fires with the direct child
under a tap
* Runs after the scroll bars with capture overload, so a press still
starts a drag scroll and a Button child keeps its own click
* View: on_item_tap hit-tests rows with clipped_rect, so scrolled lists map to the right row
* View: a press that catches a fling never counts as an item tap
* an isolate's widget prelude snapshots `mod.theme` at boot, which was always the default dark theme even under a light host, so default labels and pressed buttons went light-on-light
* `set_splash_theme(SplashTheme)` names the theme applied between `theme_mod` and `widgets_mod` for every new isolate
`call_script_fn` looked names up in the module body scope, but a
`let`/`fn` that shadows a name already in scope opens a child scope,
and everything the script defines after it lands there, invisible from
the module scope. The Splash prefix's own `let fs` / `let host` can be
that shadow, so app hooks never resolved.
* The VM records the scope a root frame ended in (`ScriptBody::end_scope`)
* Splash looks hooks up there, falling back to the module scope
Zoom is three discrete sizes with a pan-only camera: pointer hit-testing
ignores the draw-list view transform. Faces bind with bind := "node.port"
because typed widgets refuse unknown properties.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
44b3a7f02 added artist/artist_url/album/source_url/license/license_url to
the exported manifest.json search block, which is part of the hashed
snapshot payload -- the contract changed on purpose, so the deterministic
golden in deterministic_golden_rewrites_graph_and_indexes_every_route was
stale (expected 78d089..., actual 7331ed62...). Verified the export is
still deterministic (same hash across repeated runs) and updated the
golden constant, with a failure message that names the printed `left`
value as the new golden so the next bump is a one-liner.
Real adapters by default (HubChat, FleetGen, HubHttp); seams for tests; the
gen path is proven end to end against the in-process testpattern hub service.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The two lanes were written blind against the same wire spec; the seams were
reconciled by hand: one FlowSummary, an optional graph on a definition, the
epoch-stamped string event cursor, and strict_json::parse_depth for bodies
that nest deeper than the default cap.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The generated android wrapper re-creates a standalone workspace and only
forwarded [patch.*] sections from the workspace root manifest, so deps
declared via [workspace.dependencies] + workspace = true failed to inherit
in wrapped crate builds. Extract the [workspace.dependencies] section the
same way patches are handled and inject it into the wrapper manifest.
- makepad_test/runtime.rs: forward NIGIG_TEST_MODE from host env to the
Android app via 'am start' intent extra; add wait_timeout (60s) used by
wait_visible/wait_hidden/wait_count; make query_widgets tolerant of
snapshot timeouts; grant READ_CONTACTS during adb setup
- makepad-platform android_jni.rs: read makepad.NIGIG_TEST_MODE intent
extra and surface it as the NIGIG_TEST_MODE env var via apply_studio_env
- cargo_makepad compile.rs: support verbatim custom AndroidManifest.xml in
addition to the templated variant
- makepad-xr xr_root.rs: add ortho camera controls (ortho, ortho_height,
min/max), derive Debug on XrCamera
- docs: ANDROID.md and DESKTOP_VISIBLE.md for makepad_test
The GrantPermissionsActivity pops up during navigation and blocks the
app's event loop, preventing hub responses. Pre-grant all runtime
permissions after APK install to avoid this.
PID 28203 (rs.robius.robrix) was the actual zombie reclaiming foreground
and killing our test app - not our own package. Force-stop both the
target package and known interfering Makepad apps (Robrix) during test
setup to prevent cross-app foreground competition.
Also remove the pm disable-user approach as it doesn't help against
a different package's zombie process.
Samsung devices keep killed app processes alive and bring them back to
the foreground ~15s later, killing our fresh test instance. force-stop
and kill -9 don't prevent this. pm disable-user fully prevents the
zombie from being restarted. Re-enable before launching the new instance.
The Android platform never sent BeforeStartup or AfterStartup messages
via the studio websocket. Desktop platforms send these through their
stdin event loops, but Android uses websockets instead of stdin.
Without AfterStartup, the hub never broadcasts AppStarted to UI
clients, causing makepad-test to time out waiting for app startup.
Adds feature-gated optional deps and re-exports (makepad-test, makepad-csg,
makepad-gltf, makepad-mbtile-reader, makepad-fast-inflate) so a downstream
workspace can depend on makepad-widgets as its sole Makepad source.
Adds the Android test runtime to makepad_test: builds the APK with
cargo-makepad's standard Java path, installs and launches via adb with
makepad.STUDIO_* intent extras (incl. STUDIO_BUILD), connects the app to an
in-process hub over adb reverse, and waits for startup + responsiveness.
Adds clean in-process hub shutdown (HttpServerHandle + GatewayHandle Drop)
and the STUDIO_BUILD intent parsing on the app side. No native-activity or
NDK APK compilation code is included.
Measured first: the tilted start view carried 15.1 MiB of u16 and
11.6 MiB of u32 indices against 77.3 MiB of vertices, the u32 premium
5.7 MiB, all on the big tiles (fill up to 149 k and casing up to 98 k
vertices). Streams now split at bake time into triangle-safe chunks of
fewer than 65,536 vertices, one geometry per chunk (at most four on any
tile, 191 duplicated vertices in 10.9 M), so u32 leaves the typed
streams. Amsterdam start view: tilted 114.3 -> 108.6 MiB (fill 28.4 ->
24.7, casing 40.4 -> 38.4), flat 311.7 -> 287.7 (fill 83.9 -> 67.3,
fringe 57.1 -> 51.6); the harness prints vertex/index counts and widths
per stream. Same triangles in the same order; Metal hidden grab within
label noise of the head without it, web GPU gate clean (IDX1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A round cap was a hard disc fan of up to 32 vertices per road end with no
anti-aliasing; a Butt/Square start pushed a feather pair it never
indexed. The cap is now carried by the last body pair extended by half a
width, with uv.y = 1 + cap_axis, and the fragment computes the circle's
coverage with the same derivative-based AA as the band edge. Amsterdam
start view: casing 42.5 -> 40.4 MiB (shape 100 17.1 -> 16.5, shape 110
15.2 -> 14.5), tilted total 116.4 -> 114.3 MiB, flat 313.9 -> 311.7.
Cap edges gain AA; Metal hidden grab vs the head without it 1.23 % /
0.27 % start (label placement in the crops), web GPU gate clean
(CAP1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A wall edge was eleven f32 (44 B): both ends, base, height, normal, AO,
colour, z-bias. It is now i16 ends, u16 centimetre heights (max error
8 um in the fixtures), f16 AO and z-bias ticks and a UNorm8x4 colour; the
normal is derived from the edge in the shader with the courtyard winding
preserved. Both the wall draw and the projected wall-shadow draw read the
new record. Amsterdam start view: wall_inst 19.3 -> 8.8 MiB, tilted total
127.0 -> 116.4 MiB, flat unchanged. Metal hidden grab within noise of the
head without it (0.99 % / 0.27 %); web GPU gate clean (WALL2 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
MAKEPAD_TRACE=frames prints, every two seconds per window, what woke
each tick (the waitable beat, a timeout, a message, a timer, a drain,
the display link), the NextFrame gap histogram, the present gap
histogram, the flip lead and refused presents — one bool check per
tick when off, readable over --remote. It was written to chase the DJ
beat wave's judder on Windows and found the measurable defect on the
way: when no window could present (minimised, hidden, or a disconnected
RDP session where the compositor refuses every present), the paint
tick slept a millisecond and re-ran, dispatching NextFrame and
redrawing at about 600 Hz for frames nothing would show. That case is
paced at 8 ms now, the same cadence as video pacing, the idle beat and
a hidden macOS window; a visible window that drops a frame keeps its
1 ms retry.
The judder itself could not be measured hidden — a disconnected RDP
session presents nothing at all — and the reading is that a connected
RDP session's bursty ~30 fps frame delivery is what the person saw;
the trace settles it in one visible run. platform 153 tests; both
desktop targets check.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* remote: honor MAKEPAD_REMOTE in requested(), and hush the close notices when the bridge is off
`requested()` only scanned argv, while `requested_bind()` also reads
MAKEPAD_REMOTE. So `MAKEPAD_REMOTE=1` started the bridge but everything
keyed off `requested()` still said no. Just delegate, so there's one
answer to "did this process ask for the remote bridge".
Also stop printing `[makepad-remote] user closed window ...` to stdout
from every app on every window close -- that line is for the agent
driving the app, so only print it when the bridge is actually up. The
log ring still gets it either way, for /log.
* devtools: put the F10/F12/Shift+F12 overlays behind an opt-in
Three dev tools are currently wired into every app with no way to turn
them off, each on a bare function key:
F10 the exploded draw-list view. Intercepted in
Cx::call_event_handler *before* the app's handler, and once
it's up it also eats Escape, the arrows, +/-/0, I and H --
no modifier needed -- plus every drag outside the flat band.
F12 the design tweaker, a child of every Window. Once it's up it
swallows every pointer event over the body, so the app looks
frozen to the mouse.
Shift+F12 the screen recorder, which starts writing mp4s to disk.
None of that is something a shipped app wants a user to find by accident,
and there was no flag, env var or property to stop it.
So: one gate, platform/src/devtools.rs. `--devtools`, or
MAKEPAD_DEVTOOLS=1, and --remote implies it since the /snap + /click loop
drives the tweaker. An explicit MAKEPAD_DEVTOOLS=0 wins over all of it,
which also keeps the off path testable under --remote.
Only the hotkeys are gated, not the tools. Cx::sploded_toggle,
set_tweak_on and ScreenCap::toggle are untouched and still public, so an
app that wants any of this puts it on a key of its own choosing -- that's
the app deciding, rather than a key nobody knew was bound.
Gating F10 and F12 is enough to reach all of it: everything else these
two claim sits behind `sploded.active` / `tweak_is_on()`, and with the
hotkeys gated the only remaining ways in are the /tweak routes (already
--remote, which implies devtools) and an app's own call.
* text_input: drop the Ctrl+Enter submit clause again
`|| mods.control` made Ctrl+Enter submit a multiline input. On
Linux/Windows that's already what is_primary() means, so it changed
nothing; on macOS it turned Ctrl+Enter from "insert a newline" into
"send", which is a surprise in the middle of a chat app's composer.
The comment right above it already described the old behavior, so this
puts the code back in line with it.
Every floating POI or charger marker in 3D mode drew two crossed wall
quads on the 48-byte generic layout, and every signal a pole plus three
light quads; together they were the whole 17.1 MiB fill_3d_misc stream.
They are now one 24-byte instance each (anchor, arm/height, colour,
z-bias) over one shared stalk template and one shared stoplight template
per tile, drawn by a prop draw struct on the same lighting path.
Amsterdam start view: fill_3d_misc 17.1 -> 0.0 MiB, stalk_inst 0.1 MiB
(4,887), stoplight_inst 0.2 MiB (1,866); tilted total 143.7 -> 127.0 MiB,
flat unchanged (313.9). Metal hidden grab within the label-noise band of
the head without it (1.78 % / 0.42 % start); web GPU gate clean
(STALK1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The crate had a macOS backend and a stub for every other OS whose
Browser::new refused, so on Windows the app drew its chrome and the
page stayed black. The macOS file becomes native.rs with its islands
gated (the Metal blit, the IOSurface target, the app bundle and
re-exec) and Windows islands beside them: libcef.dll loaded with the
dist's Release dir on the search path, the process handle as the main
args, the exe as its own subprocess, the resources hard-linked flat
beside libcef where it looks for them (the dist keeps icudtl and the
paks in Resources, which crashes libcef), the app's own timer as the
pump, software paint into our texture, and a per-process profile when
a sibling browser holds the profile lock (the unix singleton check
does not exist there). The API hash is pinned to 13800, the layout the
bindings were written for — asking each dist for its newest misaligned
the browser settings struct on 144 and 151. Linux keeps the stub.
Verified on the Windows box with CEF 151: google.com renders in a
standalone browser and in a WM tile (a warm instance adopted in 88 ms,
the next standby up with its own profile); on this Mac with CEF 138 the
accelerated path still renders. cef 4 tests; the browser checks for
windows-msvc. Windows paints in software for now; the D3D11 shared
texture is the next lane.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cx::memory_budget_bytes() is set once at startup from the device
(ProcessInfo on macOS/iOS, ActivityManager on Android, GlobalMemoryStatusEx
on Windows, /proc/meminfo on Linux, the wasm maximum on the web) through
one policy: desktop keeps 1536 MiB unless RAM is under 8 GiB (then RAM/4),
mobile takes RAM/4 clamped to 384..1536 MiB. The map's four byte budgets
are fractions of it (upload 1/64, pending 1/4, tile cache 25/32, HTTP
cache 5/32; the tile cache caps at 1/2 on web heaps under 1 GiB) instead
of constants. At 1536 MiB they are the binary sizes 24/384/1200/240 MiB,
about 5 % above the old decimal constants. Harness totals unchanged; Metal
grab and web GPU gate within noise (BUDGET1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Shape-0 Boolean union faces (plazas, road bodies, tunnel and bridge
faces) leave the 28-byte road vertex for FaceVertexTyped: i16 anchor,
UNorm8x4 colour, F16x2 class/material + depth tick, F16x2 deck lift +
coverage. The road shader draws them through a second vertex format on
the same pass; face streams use u16 indices. Amsterdam start view:
casing 73.6 -> 58.7 MiB (42.5 casing + 16.2 face), tilted total
158.6 -> 143.7 MiB, flat 328.8 -> 313.9, 433 ms/tile. Metal hidden grab
within the noise floor of the head without it (1.26 % / 0.23 %, label
placement); web GPU gate clean. Faces now draw as one batch per tile
before the stroke bands, so a translucent shadowed face over a crossing
casing blends ground instead of stroke at that pixel — not visible in
the crops (FACE1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
platform: an in-app drag works without an OS drag session, so effect tiles drop into the channels on the web
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
A wm started with cargo run from the repo root but with its target dir
elsewhere (CARGO_TARGET_DIR) found no checkout above its exe and fell
back to sibling binaries — which never exist as bare names on Windows —
so the launcher listed only the linked modules. The checkout is now
looked for above the exe and then above the current directory, and a
sibling binary carries the .exe extension on Windows. Every app is one
cargo run away again.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Per-layer tag arenas and views replace a HashMap per feature, the tag
whitelist is applied while parsing, and the worker's output scratch
buffers are reused. Release, Amsterdam: mvt-parse 377,074 -> 3,071 and
detail-merge 2,713,553 -> 19,817 allocations (135x fewer); parse median
5.9 -> 1.8 ms, merge 63.6 -> 9.7 ms; bake 429 -> 344 ms/tile, totals
unchanged (158.6 MiB tilted). Buffers, labels and icons byte-identical
across the 25 fixtures; Metal grab within the noise floor (0.9 % / 0.1 %)
of the head without it; web GPU gate clean (ALLOC1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The first WebGL frame compiled every registered program synchronously;
on ANGLE-Metal that is 8.6 s of from_wasm dispatch for the DJ app (35 ms
of wasm), during which no timer, fetch completion or animation frame can
run. SwiftShader hid it by compiling at first draw. Programs are now
queued only when a draw list references them and linked with
KHR_parallel_shader_compile; the first draw waits only for the programs
it needs. DJ app on the GPU: localhost fetches 8.7 s -> 0.2 s, 39
programs ready in 300 ms; route first draw at 224 ms (STARTUP1 lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A runtime the platform did not create (the asset client builds its own)
has no wake fn, so on the web its responses sat until an unrelated timer
pumped the event loop. EventSink::emit now sets the UI signal as well;
the DJ app polls its store session on Event::Signal instead of waiting
for the next poll tick.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The worker drain runs once per frame and uploads until a 6 ms / 24 MB
budget is spent (nearest tiles first). Its continuation was a redraw,
and a Draw never re-enters handle_event, so the rest of the queue waited
for an unrelated timer (2-16 s in the web demo). When tiles remain the
drain now also asks for a next frame; that event arrives after the pass
and drains again. Real GPU, no probe, no input: all 25 start-view tiles
inserted between 2 and 4 s at 60 fps (DRAIN1 lane + continuation fix).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The route theme follows the wall clock (night from 19:00), so a grab taken
in the evening compares dark against a daytime baseline. The flag is a
harness override only; without it the clock still rules.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Mac-side only: no in-place rewrite, no push, no fallback source. Shards are
written atomically and skipped on resume; root.mkidx lands once at the end.
Ranged --verify streams the output back. 16 jobs: 4.2 tiles/s on the
500-tile sample (REPACK2b lane).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A new binary in tools/map_tiles streams an .mkmap shard by shard into a
new archive: every tile's protobuf is decoded, the osm_* tag tables are
rewritten to the reader's key whitelist, the field-101 shadow sections are
stubbed (regions and building groups stay byte-identical), field 100 is
kept, and the tile is re-brotli'd with the archive's own codec and
dictionary; leaf directories and root.mkidx are rebuilt, output is
deterministic and resumable per shard with a sidecar manifest, --tiles
limits a run to a Hilbert range or a z/x/y list, --dry-run reports and
--verify decodes both archives and checks the policy. On the 25 Amsterdam
start-view tiles: 95.1 -> 47.6 MB decoded, 30.6 -> 16.7 MB compressed.
A label sort tie-break makes the bake byte-deterministic for the parity
test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The fringe stream (AA skirts around the road-union faces) is drawn only
below 25 degrees of tilt, yet every tile baked it: 57 MiB of the Amsterdam
start view that the tilted demo never drew. The bake now takes a fringe
flag the way it takes the 3D flag: wanted while the tilt is under 30
degrees, dropped only above 40, decided in the frame loop next to the
2D/3D flag and re-baked through the same restyle path with a full road
core rebuild; the harness prints both the tilted and the flat totals.
Harness: 215.5 -> 158.6 MiB for the 25 start-view tiles tilted (fringe 0),
328.8 MiB flat (fringe 188.3), 429 ms/tile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Roads move from eight f32 lanes to a 28-byte RoadVertexTyped (i16 anchor
at 1/64 tile unit, f16 offset / depth / uv pairs, unorm8 colour, f16
params, exact f32 deck), ground fills and roofs to 16 bytes (i16 anchor,
unorm8 colour, f16 params; u16 depth ticks for fills, exact f32 height for
roofs); indices are u16 whenever a stream has fewer than 65,536 vertices.
The three shaders read the typed POD fields directly (the fetch converts),
uploads go through Geometry::update_typed with the shader's layout, the
space-warp subdivision decodes / interpolates / re-encodes the typed
records, and a tile that lands before its shader has drawn waits in the
pending queue instead of panicking. The shader compiler types a packed
field access as vec2f / vec4f, and the Metal name table gains the packed
short / ushort / uchar / char vector names.
Harness: 249.6 -> 215.5 MiB for the 25 start-view tiles, 384 ms/tile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
std's wasm allocator is dlmalloc behind one global spin lock taken by every
thread on every allocation, so eight workers baking map tiles serialised on
it: a tile that bakes in 0.4 s natively took 15-20 s (mvt-parse 0.7-4.5 s,
detail-merge 2.3-12.5 s), and the whole start view needed 150 s. This
installs a #[global_allocator] for wasm32+atomics: per-thread size-class
free lists (16 B .. 32 KiB) carved from 64 KiB chunks of the dlmalloc heap,
remote frees pushed onto per-chunk Treiber stacks and drained by the owning
thread, exit-safe reclamation, the main thread's caches pre-filled at
startup so its hot path never touches the global lock.
Measured on the same capture with 8 workers: mvt-parse 7-68 ms, detail-merge
27-415 ms per tile (native 4-17 / 23-70), zero traps, zero Atomics.wait,
the full start view. The two-worker stop-gap in worker_count goes with it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Draw shaders can declare geometry POD fields as F16x2/F16x4, U16x2/I16x2
(plain or normalised) and UNorm8x4/SNorm8x4; the fetch converts them to
vec2f/vec4f on Metal and WebGL2 (per-attribute vertexAttribPointer with
type, normalised flag, byte stride and offset), the headless JIT decodes
the same formats, and Vulkan/OpenGL/D3D11 refuse compact layouts with a
logged skip rather than drawing garbage. Attribute packing computes
physical offsets with natural alignment for the whole record; the all-F32
path stays byte-identical (stride = slots * 4, packed_geometry_N on GL).
Geometry::update_typed / update_typed_with_recycled_buffers take
IndexData::{U16,U32} plus a byte vector and the shader's input layout;
updates are validated (stride, whole vertices, index range) and every
geometry carries its index width and a layout signature that survive
releasing the CPU staging, so a draw checks layout and index type against
the resident buffers, never the staging enum. Compact fields are rejected
in instance PODs and in nested aggregates at shader validation; SNORM
minima clamp to -1 like WebGL2.
On Metal the decoded (logical) vertex is reached through the shader
context like the instance is, so every shader function that reads
geometry compiles.
No map stream is converted yet; that is the next cut.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Lifted shape-0 roofs leave the 48-byte universal vertex for a five-slot
RoofVertexPacked (anchor, unorm8 colour, exact metre height, f16
material | zbias ticks); DrawMapRoof reconstructs the fixed vector
channels and shares the colour pass, the fade, the LOD band and the
shadow-mask projection / footprint cut-out. Roof records that need more
(parapet AO depth variants, marker stalks) stay on the generic layout in
a fill_3d_misc stream. The tree and signal contact shadows are no longer
tessellated ten-segment discs in a vertex stream but four floats per disc
drawn as an instanced unit quad into the shadow mask, with the same
radial ramp.
Harness: 281.5 -> 249.6 MiB for the 25 start-view tiles, 387 ms/tile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
std's wasm allocator spins, so the Atomics.wait that killed one WebGL2
run in four came from std Mutex / Once contended between the browser main
thread and a tile worker. Every lock the UI thread shares with a worker
(task pool state, cancellation generation, task results, the scheduler,
the web request table, the archive read states and shard file cache) now
goes through lock_from_ui, which spins on try_lock on wasm32 and is a
plain lock elsewhere. The icon mesh cache, icon slot table and tag-key
whitelist are warmed on the UI thread before the tile pool starts so no
Once can wait either.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Casing, stroke and fringe records leave the 48-byte universal vertex for
an 8-slot road layout: full-precision anchor and deck height, f16 offset /
depth / tessellator uv pairs, unorm8 colour, and one f16 integer that
packs class, material, dash id, kind (stroke / union face / fringe) and an
explicit expanded flag. DrawMapRoad reproduces DrawMapVector's expansion,
terrain, tilt, space-warp and shadow-mask behaviour for those streams;
casing/stroke draw through it in the colour passes, the fade cross-fade
and the shadow-mask projection, and the space-warp subdivision has a
road-stride midpoint.
Harness: 339.0 -> 281.5 MiB for the 25 start-view tiles, 437 ms/tile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The bake no longer unions every building's projected roof and silhouette
into a material-6 ground fill per tile (122.5 MiB of the Amsterdam start
view, plus the b-shadow bake laps). MapView renders one child pass per
frame: wall records swept along the sun as instanced quads, roof/deck
projections of the lifted geometry, footprint cut-outs, and the tree/signal
contact discs (split from the icon stream); every ground shader samples the
mask at its screen position with the same alpha the decals used.
Metal returns the pass texture bottom-up, so the sample is flipped per OS
(WebGL2 flips its render-to-texture projection instead). The draws inside
the mask pass unbind the mask sampler (feedback loop). The compact fill
shader sets the shadow varyings so land and water are shadowed too.
Harness: 475.5 -> 339.0 MiB for the 25 start-view tiles, 411 ms/tile.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Under the window manager a child renders into a shared texture the
host allocates at the next power of two, while the Window pass carried
a depth texture sized to the pass rect; D3D11 refuses render targets
whose colour and depth views differ in size, so the clear landed (it
goes to the view directly) and every draw was dropped — each hosted
tile on Windows showed only its clear colour, the terminal's blank
rectangle among them. The pass setup now sizes the depth buffer from
the target's allocation when it draws to a texture, from the pass rect
otherwise (a pure helper with tests). The hidden-windows switch is
honoured by the Windows backend on the first show, so a test run stays
off the desktop.
Verified on the box: a hosted terminal's first frame 720 ms after
launch with the prompt in it, a typed dir listed. platform 132 tests;
the windows-msvc target checks.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The omarchy bar is the caption strip and the frame is fully
client-sized there, so a WM window had no window controls at all. The
bar's right cluster now ends in three of our SVG buttons after the
status modules — minimise, maximise (restore once maximised), close —
wired to the window's own minimize, maximize, restore and close;
hover washes the slot, the close carries the accent, tooltips name
them, and the drag query answers Client over them. macOS keeps its
traffic lights on the left. The gallery shows them on every platform.
wm 155; the WM checks for windows-msvc, linux-gnu and wasm32. A real
click on Windows is the box's to prove.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The browser could not build from a clean clone on Windows: the fetch
was a bash script needing python3 and tar. It is Rust in build.rs now:
the spotify index is read by a strict scanner, the newest stable
standard build for the platform is picked (or MAKEPAD_CEF_VERSION pins
one), the archive is downloaded to a .part file with its size and sha1
checked, extracted beside it, and a current-<platform>.txt pointer file
names the dist — read before the old symlink, so the Mac keeps its
pinned 138 while a fresh box gets the current build; a platform that
already has a dist is never bumped. MAKEPAD_CEF_OFFLINE refuses with the
dir and pointer named; MAKEPAD_CEF_DRY_RUN prints the plan (with
MAKEPAD_CEF_PLATFORM to resolve another host's). Build-time deps only:
ureq (rustls), bzip2 (bundled), tar, sha1_smol. The shell script is gone.
Proven on this Mac: a pinned re-download of the 138 macosarm64 archive
(255 MB, sha1 verified) differs in nothing from the existing dist over
1,193 files; dry runs for linux64 and windows64 pick the archives already
on disk; the browser checks with no download; the cef crate's build deps
compile for the windows-msvc and linux-gnu hosts. Picker and pointer
tests 5. An actual download on Windows or Linux is not yet run.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The bundled default theme ships its colours, not its pictures, so a
fresh install had nothing behind the desk until someone ran the
importer. The desk now fetches the current theme's wallpapers from the
omarchy repo on a thread when its backgrounds folder is empty and shows
the first one the moment it lands; the importer shares the fetch. No
network or no pictures means nothing changes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gen.image{prompt} is a service inside the assistant: on a worker it
runs the creator pipeline for the image domain against the node the
runner picks (LAN discovery, ETA-ranked, the resident flux model wins),
streams the node's progress into the tool card, and writes the picture
under the makepad home's gen/ dir; the creator library gains
generate_bytes — the request, poll and fetch that generate_and_publish
now shares — so no second pipeline exists. photos.add{path} bakes that
one file into the open library (only from the gen dir or the person's
home), re-opens the wall and glides onto the new picture; the module
executor answers it later through the host's reply sink. The model
chains them, launching photos in between. A registry seam found on the
way: a call dispatched in the same event a link was adopted reached the
port before its Registered, so the registry now answers a leading
Register at once.
The wall gained a search box: every keystroke re-cuts the packing over
the matches (every word in a title or link; title beats link, a whole
word beats a substring), each picture flies from its drawn rect to its
target over 450 ms, dropped pictures shrink and fade, returning ones
grow back; Esc clears; ⌘F or a bare slash focuses the box; the status
reads "35 of 293 · ro". photos.filter{query} sets the same from the
pane; instances are rebuilt only while a flight runs, and the clock is
the platform's.
Proven on the fleet: "make me a picture of a red bicycle on the moon
and put it in photos" → the file in 40 s, photos launched, added and
shown among the comics; typing r, ro, rob re-packs the wall live; the
filter through the pane in a process tile and as a module. ai-services
39, aichat 5, photos 9, image_tiles 12, creator 9; aichat and photos
check for wasm32.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A service over the engine the UI already drives: status (what is live
and next, the fader, overlay, autopilot, bpm), search over the catalog,
cue (the UI's content-click path), fader, next, autopilot with a style,
overlay, and the decks — deck_play, deck_stop, deck_load, crossfade —
through the same functions the console's controls call. A context line
follows the live item, fader and autopilot. Written by the delegate from
the sheets/files shapes; vj's ai tests 4, the binary suite 821 (the two
media decode-pool timing tests fail on this Mac before and after).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
std's wasm allocator is one global spin lock, so workers that allocate
serialise on it and per-job throughput collapses with the worker count
while the browser main thread starves behind them. Measured on the map
bake in the route web demo: a tile takes 15-20 s with 8 workers and
0.1-0.5 s with 2; the start view completes in 80 s instead of 150 s.
Every web app's pools get the cap through worker_count. It goes when the
thread-caching allocator (ALLOC2) lands.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 59096700d56cd8f3f15724e24129c0b428bf6ae7)
Five 16 px icons in the set's own style (our SVGs, stroke 1.2,
currentColor), wired into the shell's icon set and the launcher map.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A pure packer (pack.rs) lays pictures in rank order into rows of one
target height and scales each full row to span the wall exactly, so
tall strips sit beside wide panels with no gaps and no overlap; the
last row keeps the target height instead of stretching two pictures
into giants; a 4 % gutter is split per cell. TileGrid gains the packed
wall as its default (packed: false gives the old uniform grid), frames
its camera on the packing's bounds, hit-tests through the row
partition, and picks each atlas page's level of detail from the
largest visible tile in it rather than one unit cell. photos needs no
change. image_tiles 10 (5 new), photos 7; the photos lib checks for
wasm32. Driven hidden: the 293 comics in justified rows, zoom, pan, a
click on a comic naming it, and photos.show gliding onto one.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A stdin-loop child stored the host's WindowGeomChange raw and never
recorded the host dpi as the window's native dpi, while the native
path converts every OS geometry through the app's dpi_override and
remaps every pointer. VJ is the app that sets an override (its console
scale shrinks the dpi to fit a tile's height), so under the window
manager it laid out in larger points than the pointer it was handed —
every click landed short — and each host resize re-set the raw host
size under the override, so consoles of the wrong physical size were
drawn into the shared framebuffer over the previous full one: the
stacked stale consoles the user saw. Terminals never showed it; they
set no override.
The stdin arms of every platform now apply a host geometry the way the
native path does (the host dpi recorded, the size converted through
the override) and remap pointer positions with the override scale;
window hit-tests compare in native points. No host-side change: the
crop is per presented frame and the swapchain handoff only bridges a
resize.
Proven hidden under the WM: the explorer tab answers a click, typing
filters the music list, a split and its close re-lay the console out
with no ghosts. platform 128 tests; Linux and Windows targets check.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The same tools route's own assistant has — plan, add and remove stops,
status, along-the-way search, map fly-to and trip framing, layers,
theme, markers, geocoding, weather, trip history, navigation — go on
the AI bus from one table (the definitions local_agent already maps),
dispatched through the one execute_tool; reads are Read, anything that
moves the map or changes the trip is Act. The context line carries the
map view and the trip; the desktop pane opening closes route's own
assistant panel. Written by the delegate from the sheets shape; route
25 tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dormant instances registered their services, so the assistant could
type into a shell or steer a page nobody could see. The port opens at
startup for a real launch and on WmEvent::Adopted for a standby, the
way files already did.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An ok os.launch result is held while the launched app is not on the
bus and delivered when it registers (within 8 s), rewritten as "<App>
is running now. Its tools: … Call them directly." with the tool table
rebound mid-turn where the model can (the local model prepends the
update to the tool result; Claude keeps its native table until the next
turn and gets the names in the text); past the patience the result says
the app has not connected yet. An assistant entry that never got
visible text is dropped before a tool call and at the end of a turn,
and the panel draws an empty one as nothing — the blank block above
the first card is gone. The model's context now opens with the running
apps and their tool names and lists the others as os.launch candidates;
the WM's launch description says a running app's tools are already
there, and its answer for one that is already running says so and
brings it to the front.
Proven on the fleet model: "open the photos app and find the pictures
about dogs" → launch (running 4 s later) → search, summary, search →
the answer, one turn, the tile beside the pane. ai-services 38, wm 153,
aichat 2; aichat checks for wasm32.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The placeholder used the focused-text colour, and the composer is
always focused, so the hint looked like an entry.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The hub chat session's election no longer stops at this machine: a
co-located serving holder first, then a fleet chat node heard by
discovery and role-allowed for chat (a short patience for beacons),
then the weights on this machine — and an honest answer naming both
misses and where weights may be put when none of those exists. Tool
packs ride every route: the system text carries the tool table as the
node's chat_system, one splitter strips thinking and collects
<tool_call> bodies across deltas, the in-process worker's parser is the
one parser, and tool results go back as tool turns. A node that fails
mid-turn ends that turn with the node named; the person's next line
re-elects, served first on the new route. The tools-only guard on the
proxy is gone. The session exposes its route, and the panel's chip
shows it.
The local lookup is independent of the working directory: the env
override, then the makepad home's weights (Qwen preferred, largest),
then the checkout the binary came from, then the cwd — so an assistant
launched from a binary copy finds the same weights as one run from the
checkout, and the Local provider always builds even with none.
Proven live with no local weights: from the sheets overlay a plain
line reached the fleet's 27B (the node with the model resident won the
pick), the model called sheets.summary and answered in 12 s; the app
stayed at 254 MB. hub_chat + local_llm 11, services 34.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
apps/photos (lib + bin) puts libs/image_tiles' TileGrid in a tile: it
opens a baked library found by name (IMAGE_TILES_HOME, the cwd's
local/image-tiles, the checkout the binary was built in — the smbc
collection first), shows a status line with the bake command when
there is none, and exposes photos.search (every query word in a title
or link), photos.show (the camera glides onto one picture) and
photos.summary — the same answer for the port and for the module's
executor. PhotosModule mints the view inside an isolate with an open
schema that takes a collection name, never a path; the WM links it
behind app-photos and lists it in the menu. image_tiles learned to
decode GIF and WebP and to read a manifest's pictures from disk, so
the bake can take local/smbc's mirror: the last 300 comics are baked
under local/image-tiles/smbc (293; seven of the mirror's files are
saved error pages). fabric joins the launcher with a title and a
polite close.
Driven hidden: the wall in a process tile beside the left pane, panned
and zoomed; search and show cards; the same wall as an in-process
module; fabric launched and closed; standalone photos with the F10
overlay on the left. photos 7, image_tiles 5, wm 152; the photos lib
checks for wasm32.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
sheets: read_range (one cell or an A1 rectangle, up to 2000 cells, rows
as tab-separated display values), find (case-insensitive over the used
range, 50 hits), write_cell and set_range (Act) — edits go through the
same commit path as paste and direct entry, so recalculation, undo and
the chrome all see them; the module executor answers the same tools.
files: mkdir, rename (a bare name, no separators) and trash (into the
app's own trash dir, never a delete) — Destructive, so the router shows
a confirm card first; every path through the jail, on the demo VFS as
on disk. Written by the delegate from the existing shapes; sheets 88,
files 158 tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Two services on the AI bus. terminal: read_screen (the visible grid as
text, cursor and cwd in the note), read_scrollback (up to 2000 recent
lines) and run — which types the line into the live, unsandboxed shell
exactly as the person would, refuses control characters and anything
over 4 kB, and answers at once so the model reads the outcome with
read_screen next. browser: page (the active tab's title and url — CEF
exposes no page text, so none is promised), tabs, navigate and new_tab,
http(s) or about:blank only. Context lines: the shell's cwd, the active
tab. Written by the delegate from the sheets/files shapes; terminal
331 + 5 tests, browser checks.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- libs/sim: DeckStrip (deck.rs) — every laid corridor registers its walk
deck; movers pin to it exactly instead of climbing 2.5 m slab kerbs;
feet inside a static box lift onto its top; the world composes floor,
terrain, materials and voxel in one sampler
- libs/sim + libs/render: camera_path_limit takes the filmed body and never
treats it as an obstruction (the coaster's ride boom was clamped to 1 m);
the shadow gate's settle test gets a hair of float slack
- libs/asset/chat: the in-game builder never asks before building; the
brief teaches game.driver and the race countdown, under budget
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Decision 17: the chat never sits over the app. Under the WM the pane is
a left column of the desk row — its walk width is the reserved strip,
eased from nothing to the card plus a gap as it slides, the card
right-aligned in the strip so it comes in from off the desk's left edge,
its edge on the right — and the desk's layout takes what is left, tiles
snapping to it while the strip moves the way they follow a drag. In a
standalone Window the F10 slot does the same: it reserves the strip as
the body's left inset every frame, lays the body out beside it, and
reads the app's own ground colour after its retint. The background
switcher's index lives on App, not in a static.
Verified by hidden grabs: a terminal full width; F10 → the pane on the
left and the desk narrowed by 450 px with the terminal filling it; F10
→ full width again. sheets standalone: F10 → the grid starts right of
the card, nothing over cells; F10 → back. wm 152, widgets 138 (+ the
two known widget_tree failures), sheets 84; the WM checks for wasm32.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Driven native, one app at a time, with the assistant up at boot:
terminal, files, task and browser warm-adopted; sheets, image, video
and pdf launched cold; sheets also as an in-process module; the tools
answering through the pane (os.list/launch/close/open, files' listing,
a refused jail escape, a treemap, sheets' summary both ways).
What broke and is fixed: when a window closed or a client died the
layout's automatic refocus took the keyboard from the OPEN chat, so an
os.close typed in the pane sent the next console line into a sheet —
the refocus now leaves the keys with the pane while it is open (a click
on a tile still moves focus, through focus_client). A warm spawn that
cannot start (an installed layout without the binary) counts as a crash
so the pool gives that app up instead of retrying every tick. browser
and sheets link the WM API: a title in the bar and a polite close on
CloseRequested; aichat quits on CloseRequested (the desktop going down).
Measured (WM log, launched → first flip): warm-pooled apps 25–50 ms;
cold launches through cargo's freshness check 0.7–3.7 s (up to 14 s
under a parallel launch's target lock); the same binaries run plain
0.3–1.5 s after macOS has scanned them. wm 148, sheets 84, aichat 2.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
basecamp/omarchy answers 301 on the API and the importer fetched no
wallpapers; the repo is omacom/omarchy now, same quattro branch.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The desk (layout, tiles, the shell, the AI pane, the module host, the
bus) is one program everywhere; what differs around it is asked through
host.rs: a monotonic clock in platform seconds (std::time traps on
wasm), whether child processes exist at all, a child's environment, the
theme choice kept in the desk's own storage namespace. Every Instant in
the WM is gone; the hub, the warm pool and process launches are skipped
where there are no processes, and the bar's clock falls back to the
platform epoch when it cannot fork date.
The pane has a second body: where the chat is linked in as a module —
the web build, or a desktop that switched aichat to module hosting — the
pane instantiates mod.widgets.AiChatOverlay by name, exactly as a
Window's F10 slot does, and the WM's own os service and every module
instance reach it as in-process links (pane_links.rs), never as frames.
The registry's web profile makes every linked module a Module and
answers os.launch of anything else with unavailable. sheets' Open and
Save go to the instance's storage jail, asynchronously, on every
platform.
The assistant starts with the desktop (decision 15): the aichat child
(native) or the overlay (in-process) comes up at startup with the pane
closed and no focus taken, drawn just off the desk's edge so the child
is configured and presents before any F10 — which now only slides it
in. The gallery fixture leaves the desk's DSL for a by-name host (its
construction overflowed the wasm stack).
Platform bug found by the web drive and fixed in libs/wasm_bridge: the
ToWasm reader added a block's absolute end to a relative base, so two
JS→wasm messages in one buffer decoded garbage and trapped; it seeks to
the stored end, with a batched-buffer test.
Verified natively by a hidden drive: the child's first frame 1002 ms
after boot, before any F10; F10 → the pane at once with the composer
focused; F10 → gone. On the web (threaded build, 1.5 MB brotli, COOP/
COEP): desk, pane with No model, os.list, an in-process Sheets tile and
its summary, terminal answered unavailable, zero traps. wm 148, sheets
84, app-module 3, wasm-bridge 1; wasm32 checks and the std::time clippy
gate clean for wm, sheets, app-module, aichat, wasm-bridge.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
"make me a giant forest with roads" answered with a plan and "Want me to
go ahead and build it now?" — the player is holding a controller. The
agentic doctrine and the game brief now say it outright: a request is an
instruction, decide the open details, build in this turn, report in a
sentence; a turn that ends in a question is a failed turn.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Crossroads rebaked its lightmap on every stoplight phase (70 bakes a
minute): the traffic kit's lamp repaint bumped the one static revision
the renderer keys its slabs, occlusion, shadow receivers AND the GPU
lightmap kick on. The world now carries a paint revision beside the
geometry revision; the static slabs repack on either, the bake kick
only on geometry. Tests pin both.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
libs/app_module (makepad-app-module) is the contract (aicontrol §3):
AppModule (id, label, register into an isolate, an OpenSchema, create
into InstanceParts, capabilities), InstanceHandles (an InstanceScope
owner token, the storage jail as a cx.storage namespace, the viewport,
a ReplySink for calls that finish later), a ServiceExecutor addressed
apart from the root, and an OpenSchema that refuses raw paths — a file
is a handle the host issued or nothing. Only types live there.
The WM hosts one (module_host.rs): allocate the isolate, retint its
theme from the palette, register, create — one trusted entry into the
isolate, never a second &mut Cx beside the VM; teardown drops the root
first, runs shutdown in the isolate, frees it. MpModuleView is the tile
for an instance's root, drawing and dispatching with the isolate
INSTALLED on Cx (enter_isolate/leave_isolate, new in widgets) and
gating keys on the WM's focus; a TileHost trait covers both tile kinds
so the desk and the focus logic never ask which. The registry overlay
(apps.rs) links modules per app-* feature; the desktop default stays
Process, switched per app in ~/.makepad/wm/apps.splash or by a dev
--module flag. The bus gains its in-process leg: an instance is an
m<id> endpoint the pane addresses like any other — the leg the web
superbuild runs everything on.
sheets is lib + bin: SheetsModule mints MpSheets{} in the isolate and
answers sheets.summary on its executor; the standalone binary keeps its
Window and F10 overlay behind the standalone feature.
The drive found a platform bug: a shader compiled on a widget minted in
an isolate hit the draw-shader object cache by object index alone, so
an isolate's Button reused the main heap's entry and drew with zero
instance slots. The cache is keyed by (heap, object) in every backend,
and add_instance refuses a zero-slot draw call instead of dividing by
it. Isolate cost, measured (the P2 entry gate): 12 ms and ~8 MB per
isolate, flat to 32 — no pre-warming needed.
Verified by hidden grabs under the WM: /os.launch sheets → an
in-process Sheets tile (no child process), the apps row Desktop ·
Sheets, /sheets.summary answered through the in-process leg, a warm
terminal beside it, F10, close → the instance torn down and its isolate
freed. wm 145, sheets 84, app-module 3, services 33; platform and draw
check for windows-msvc, linux-gnu and wasm32.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- libs/sim: meshfloor.rs FloorRaster (one storey per column from a map's nav
graph); composed_surface_sample_at(floor, terrain, materials, voxel) is the
one seam nav, corridors and lots read; in-eval voxel ops no longer bump the
history revision
- libs/asset/chat: world.get_plan / world.set_plan (shape-validated, stale
revisions refused), the authoring state prefixed to every turn, map turns
routed to the plan tools; game.md: the foundation line, the capability kinds
verbatim, no order-independence claims
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The per-family model crates (flux, h3, paint, music, speech, stems,
vision, sfx, rife, trellis, beats, common) and the libs/diffusion
research binaries read some 190 environment variables that were
research knobs: tensor dumps, per-stage timing, oracle-parity and
fixture rigs, experiment toggles. The path taken with none of them set
is the one that ships; every such knob is deleted with the code it
gated, and every losing branch of an experiment toggle goes with its
toggle — dead kernels, fields and functions included. What remains are
the real configuration variables (the FLUX_*_MODE family, FLUX_GRAPH,
the VAE pool cap, the FLUX2 text-encoder residency, H3_VAE_BATCH, the
music3 caches and official modes, the stems/beats f16 switches, the
weight and data roots) and the build-script variables.
Rebased on the runtime cleanup: precision stays explicit everywhere
(GemmPrecision, f16_attention_operands, the H3 text precision, DA3's
StrictF32 in code); no act16, no H3_ACT_F16, no FLUX_ATTN_F16 or
FLUX_VAE_CONV_GEMM reads survive.
Reviewed by the delegate reviewer (APPLY, no findings) and gated on the
Windows CUDA box: all seventeen model crates check, motion and vision
tests, the hub and the diffusion bins — the gate caught one CUDA-only
tap marker the Mac never compiles, removed here. On this Mac: the same
checks plus motion 24, paint 158 and vision 23 tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A headless box's service log showed only the startup banner for a whole
session; now it records the model and wire encodings on open, and on close the
elapsed time, frames in/out, fps, and the dropped/undecodable/unencoded counts.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Measured on the body node: the Microsoft H.264 decoder accepts MF_LOW_LATENCY and
CODECAPI_AVLowLatencyMode and ignores both; it holds pictures until the DPB it
derives from level_idc is full (six access units in, nothing out until DRAIN), and
appending access unit delimiters changes nothing. With level_idc rewritten to 1.0
in every SPS the DPB is one picture and each picture is emitted as the next access
unit arrives (5 of 6 while streaming, the last on flush). flush() now sends
COMMAND_DRAIN. The captured-stream test replays dumped access units and requires
the pictures while streaming; the keyframe-request check is its own test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The AI runtime crates read their precision, activation and kernel
choices from environment variables; the code path taken with none set
is the one that ships, so every knob that selected it is now an explicit
argument and every losing path is gone. GemmPrecision { f16_accumulate,
f16_activations } is passed by each caller: the default {true, true} is
the old unset Flux route; H3's DiT and text encoder pass {false, false}
(H3's >1e4 activations saturate f16 — the policy H3 used to set through
FLUX_GEMM_F16ACC=0 on itself), its VAE {true, false}; DA3's StrictF32
selects f32 packed attention in code; Hy-Motion carries an explicit
f16_attention_operands flag through its text refiner, its double and
single blocks and the CUDA backend (true in production, false only in
its full validator). The libs/diffusion bins — a separate workspace —
are migrated to the same shapes.
Benches and validators no longer set variables on themselves: llama's
skip-logits is a session option (the CUDA bench turns it on), OCR takes
explicit use_f16_gemm and tiled_roformer options, the lane speculative
probe reads its CLI. The live gates the first cut had deleted are back
as explicit-config tests: MMQ M=129, the strided-f32 MMV path, the
RMS+MUL CPU oracle. The loader's THREADS and CHUNK_MB stay real settings.
The Metal quantized-matmul experiment (metal_qmm and its vendored MLX
kernels) was reachable only through a knob and goes with it.
Reviewed in three rounds by the delegate reviewer (the last round
accepted everything but one Hy-Motion call site, fixed in round four
and reviewed here), and gated on the Windows CUDA box: lib checks of
common/paint/loader/cuda/llm/motion/vision, motion 24 and vision 23
tests, the hub check, the diffusion bins, llm 253 passed / 1 ignored.
On this Mac: the same checks plus the motion and vision tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Every Window now carries an AiChatSlot beside the tweaker: zero cost
while off, inert when the window manager hosts the process (the WM's
pane is the chat then), and on the first bare F10 it instantiates the
chat's module root BY NAME — mod.widgets.AiChatOverlay — which exists
when the app links makepad-aichat and calls its script_mod; an app that
does not gets one log line. The overlay slides in from the right over
the body with the WM pane's motion, owns the pointer inside its rect and
the keyboard through its composer, keeps ticking while hidden, and draws
in the window's retained overlay list so it composites over the deferred
body. Requests from outside the tree ride Cx globals, never statics.
An app exposes itself with one call: AiServicePort::open(cx, manifest)
is the hosted transport under the WM and, standalone, an in-process link
parked on Cx (PendingServiceLinks) that whichever chat root is up adopts
into its registry — the overlay today, the superbuild's pane later. The
registry wakes the UI when it sends, so an in-process call is answered
without waiting for a pointer event. NoModel leaves the engine feature
so a build without a model runtime still answers honestly and keeps the
tool console; aichat's engine is a default-on feature.
The bridge: Cx::ai_callback beside tweak_callback, /ai?on=1|0, /ai?say=…
and /ai/transcript (the overlay publishes the transcript as JSON after
each state change), installed by makepad_aichat::script_mod.
sheets links aichat and exposes sheets.summary (name, used range, header
row, selection) through the port: standalone the overlay answers it,
under the WM the bus does.
The local model no longer loads on a registration: its session starts
on the first user line, so an app joining the bus costs nothing.
Verified by hidden grabs: sheets standalone /ai?on=1 → the overlay with
the summary card and no model; F10 closes it; under the WM the sheets
tile shows no overlay and F10 toggles the WM pane. widgets 135 + 4
(two widget_tree find_within tests fail before and after this change),
aichat 33, sheets 83 + 2, services 29; widgets and aichat check for
wasm32.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Microsoft H.264 decoder answers a VT_BOOL CODECAPI_AVLowLatencyMode with
"VT_UI4 != pValue->vt" (seen on the body node), so send the number. The
Media Foundation encoder opens each access unit with NAL 9, which the
keyframe check now skips.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A partial route to the nearest reachable cell, a hold at its end, and
re-planning with backoff (15 to 240 ticks) replace the old head-
straight-at-it fallback; cell derivation prefilters entities per chunk.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Trace every ProcessOutput HRESULT/status, keep the first access units beside the
trace so a stream can be replayed offline, ask for CODECAPI_AVLowLatencyMode through
ICodecAPI as well as the MF_LOW_LATENCY attribute, and let the round-trip test plus a
captured-stream replay test run on a Windows box.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Microsoft H.264 decoder reports output stream flags 0x107 (whole samples,
single sample per buffer, fixed size); reading bit 0 as "the MFT allocates its
own samples" handed ProcessOutput no buffer and every call failed with
E_INVALIDARG — seen in the MAKEPAD_H264_DEBUG trace on the body node.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The live decoder never produced a frame on the body node: MF_E_TRANSFORM_STREAM_CHANGE
was hand-derived as 0xC00D6D60, which is MF_E_TRANSFORM_TYPE_NOT_SET; the real stream
change (0xC00D6D61) was treated as a hard error, the output type was only negotiated
lazily, and without MF_LOW_LATENCY the decoder holds a reorder window a 2-3 frame live
pipeline never fills. Set MF_LOW_LATENCY on the transform, commit NV12 before the first
ProcessOutput, re-negotiate on TYPE_NOT_SET/STREAM_CHANGE/BUFFERTOOSMALL, drain on
NOTACCEPTING, and stamp packets with monotonic 100 ns timestamps. MAKEPAD_H264_DEBUG=<file>
traces packets, HRESULTs, negotiations and frames for headless services.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Hosted by the window manager, files opens one AiServicePort with a
manifest built from the same table the app's own panel reads its tool
specs from, so the two can never drift: list_dir, read_file, stat,
treemap_summary, all Read. The calls run on a worker of their own
(ai_service.rs): every job carries the engine's call id and a cancel
flag the folder walk checks on every entry, a queued job cancelled
before its turn never runs, treemap_summary reports a permille per
direct child, and results come back by id whatever the order. The old
panel's order-only runner stays beside it for the old chat, behind the
chat feature it belongs to.
The port opens at startup unless the process is a warm-pool standby,
which waits for Adopted so a dormant instance never shows as a running
Files; a context line (folder, view, selection) follows every change;
ChatOpen from the desktop closes the app's own panel; the jail resolves
every path exactly as before.
Verified by hidden grabs under the WM: /os.launch files → the pane's
apps row reads Desktop · Files; list_dir, a refused jail escape, and a
treemap summary answered as cards. 151 tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The chat is a child app (apps/aichat) seated in the WM's right-side pane
instead of a tile. The WM gives it the slot (shell/ai_pane.rs: 440 px
clamped to 40 % of the desk, easeOutQuint in/out, opaque card with a
2 px edge, one MpRunView that keeps ticking the child while hidden), the
bus (ai_bus.rs: up-frames stamped with the WM's own endpoint ids, every
registration remembered and replayed when the pane connects, down-frames
routed by endpoint, a synthetic Unregister when a client dies, ChatOpen
broadcast on every slide) and its own os service on that bus: list (text
+ JSON rows), launch/focus/close by registry id, and open through the
same typed OpenRequest a file browser's double-click takes.
Bare F10 toggles the pane before the keymap and before any tile
(decision 8); the pane owns the pointer inside its rect and the keyboard
while open, claimed after its run view's next draw; the child's Close
means hide; its death empties the slot; shutdown kills it. The pane's
child is invisible to everything the desk enumerates. The WM declares
its font set (International, with the emoji family it uses).
aichat: the composer takes the keyboard after its first draw and keeps
it across sends; the running card's bar is a pixel width.
Verified by hidden --remote grabs: F10 → the pane with the os
registration in the apps row; /os.list, /os.launch terminal (tile beside
the pane, keys stay on the chat), /os.focus; F10 again → pane gone.
142 tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
IOHIDManager claims known wheels and joystick-class devices, parses
them by HID usage (steering, pedals, twist, throttle, hat, buttons) and
hands out an IOHIDDeviceSetReport handle so an app can drive force
feedback from userspace — no kext, no DriverKit. The studio wire learns
RemoteJoystick.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Source (webcam by device, a still for tests), one VideoToolbox encoder
session per run for the H.264 wire with raw as the fallback, the hub
realtime session on a node chosen by domain, typed events (status,
connected, aux, rate, ended) through a mailbox to any consumer. The
sandbox's mocap is the first; fabric is next.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The decode loop returned before ProcessOutput until the output type was
negotiated — but the MFT only reports MF_E_TRANSFORM_STREAM_CHANGE, the
event that starts negotiation, FROM ProcessOutput. A node fed Annex-B
access units never yielded a frame. Now the pump always pulls, a stream
change picks NV12, sets the type and refreshes size and stride, need-
more-input ends the pump, and rows are de-strided on the way out. The
decision is a pure function with a regression test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
std::time::Instant trapped on every wasm tile worker; the profilers and stage timers
now use Cx::monotonic_now, and the native watcher, writer and their tests are gated
to native targets.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
Latin (IBM Plex) is the web default, International (adds the CJK and emoji faces) the
desktop default; app_main! records the choice before widgets register, the theme only
registers the selected chains, a glyph miss never starts a load, and the resolved font
resources are emitted as the makepad.font-assets.v1 section for the packager.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
apps/aichat is a lib and a bin. The panel widget owns the registry, the
model and the transcript, draws EngineState as user lines, markdown
answers, tool cards with progress and confirm buttons, and system lines
over a composer; every event pumps the engine and a busy turn asks for
the next frame. The bus client turns the window manager's stamped
studio frames into registry links under the WM's own endpoint ids
(registry.register_as). Settings persist under ~/.makepad/aichat with
the cloud lock kept in code. The binary is both the standalone window
and the WM's special child.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
LocalModel drives the hub chat session; a changed tool table or system
prompt rides the next turn as an update block instead of restarting an
append-only context. ClaudeModel owns the history and rebuilds the
per-turn provider with the new native tool array, dots mapped to the
double underscore Anthropic allows. NoModel answers nothing but says so,
so the web demo's pane, services and tool console work without a
backend. Provider rows report honest availability, the local model file
policy is one function, and the crate no longer needs the converse seam.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The registry issues endpoints, answers each port's registration, keeps
where every instance lives, builds the tool table (each app once,
canonical names) and the briefs, pumps the links and forgets dead ones.
The core drives a Cx-free Model seam: routes each tool call to the
instance the model named (or the focused one), stripping the selector;
holds destructive calls for a confirm card, with host risk floors that a
service can raise but never lower; times out silent services, extended
by progress up to a hard cap; answers unknown names with the real ones;
reconfigures the model when the registry moves and restarts it when it
cannot rebind; honours result dispositions (end the turn, reset the
conversation); caps tool rounds; and offers a local tool console that
drives services without a model. Time is host-supplied seconds, never
Instant, so the same core runs on the web.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The web feature no longer hides the native API; both transport adapters validate
responses through one upper layer with framing headers refused and redirects off;
the platform transport passes a body cap to makepad-network (honoured by the web
backend); the filesystem cache verifies before deduplicating; BaseUrl canonicalizes
and rejects dot segments; ClientLocation is authoritative.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
An app id says what a service is; a host-issued EndpointId says which
instance. Ports register with a nonce, learn their endpoint from the
host's Registered answer, take only frames addressed to it, and never
stamp themselves on the way up. Hosted frames are refused over a size
cap before deserialization and every variant is validated on receipt;
tool schemas must describe an argument object. Results carry bounded
structured data and a disposition (continue, end the turn, reset the
conversation); Cancelled and TimedOut are outcomes of their own.
InstanceMeta records where an instance lives for the model.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The platform's exploded z-layer viewer consumed F10 before any app saw
it. F10 now belongs to the AI chat (the WM pane and the Window overlay);
the debugger is Shift+F10 and the recorder Ctrl+F10, beside the
designer's F12. The recorder widget gains a hotkey_ctrl setting.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Cx::monotonic_now backs every elapsed and deadline measurement (Instant natively,
performance.now on the web, reachable from workers); dispatch keeps its handler
installed across traps and rejects re-entry without panicking; the studio websocket
worker spawns only when a studio is configured and waits with park_timeout instead of
spinning.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
tools/web_server becomes the deployable site server on our own HTTP stack: Range, HEAD,
brotli negotiation, ETags, cache and cross-origin headers, error reporting, and
/api/healthz, /api/search, /api/route and /api/along over map_nav with bounded query
and route workers and a data directory outside the public root. makepad-network learns
HEAD and exposes raw header lines.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
The demo build has no AI panel, no first-run bake and no local files: tiles come from the
hosted .mkmap archive, and search, routing, along-route lookups, weather, radar and wind
go through the site's API with client-side trip state.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
A completion-based Transport with the native TCP adapter and a platform-HTTP adapter over a
client-owned network runtime, a CacheStore seam with the filesystem cache and a memory
LRU, BlobContent / ClientLocation / Unavailable types, and native/web features so the
client builds for wasm32; static-site connect is a typed Unavailable until the runtime lane.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
The startup trace keeps its topic gate and runs on the platform clock so it stays
wasm-safe.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
apps/files, apps/sheets, libs/wm_api, libs/wm_theme and the renamed env vars and script
namespaces meet the demo seams; added demo files move to the renamed paths; the files app
keeps its own makepad_home so demo builds without the chat feature do not link the AI hub.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
The per-crate *_TRACE / *_DEBUG / *_DUMP environment switches in
platform, widgets and cef become topics of one process-wide set:
startup, frame, present, timer, studio, gpu.*, shader.*, gl.*,
runview.dpi, wayland, tweak, map.*, cef. Hierarchical matching with
negation and all; read once at init; settable on a running instance
through GET /trace?topics=…; an atomic short-circuit keeps the
no-tracing case off the lock in per-frame code; spans are absent on
wasm32 where Instant panics.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Packages are makepad-<name> with the short name as the binary. Env vars
follow (MAKEPAD_WM_*, MAKEPAD_FILES_*, MAKEPAD_TERMINAL_*), config moves
under ~/.makepad/<app>/, the theme namespaces are mod.wm_theme and
mod.browser_theme, the hosted AI envelope key is wm_ai. platform/video
becomes makepad-platform-video so the video app can be makepad-video.
Carries the Score entries that were pending in the WM's curated table.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
std::time is unsupported on wasm32-unknown-unknown; the demo paths now use Cx::time_now
and the pinned demo clock, and Cx::call_event_handler restores its handler slot on unwind
with an explicit re-entry diagnostic instead of cascading into a second panic.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
Persisting the generated ledger remaps every id through an explicit map so category
parents and scheduled entries resolve in a fresh native database; determinism is proven
by a structural fingerprint plus reference-integrity and transfer-pair tests; native
persistence and import live behind one cfg-gated module interface with a runtime
pinned day (2026-08-28 in demo builds) used by all shared range code.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
A package whose bin name differs from the package name (apps/finance: finance) no longer
fails at packaging; the bin is resolved from cargo metadata, default-run or --bin, and the
minified JS copies are written after their directories exist.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
Visibility wrappers around the picker and disk controls, initial document and demo
loading owned by SheetDocs, a clipboard seam instead of a platform cfg in the view,
loading a demo resets history, selection and scroll, ASCII help text, stronger
bundled-document tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
libs/mbtile_reader gains pure root/leaf/blob parsing; widgets/map gains archive.rs with
a ByteSource trait, a file backend on the worker pool and an HTTP-Range backend over
cx.http_request, a TileArchive that fetches the root once, caches leaf directories and
dedups in-flight reads, and MapView::set_source_config so local archives and hosted
archives share one request path into the existing tile build and ready-tile delivery.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
The chat feature (default on) owns the AI hub dependency so a demo graph never names it;
demo builds install a seeded procedural DemoVfs of ~38k files with Zipf sizes and deep
paths, read text through Vfs::read_bytes, take thumbnails from an embedded picture pool
with one still per video, run listing/scan/thumbs inline, and keep prefs in memory.
Process spawning is confined to the native seam.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
Review fixes: focus/lost-focus events dispatch unconditionally again, only window
zero's creation updates the shared browser geometry, the guarded sites resolve the
generation-correct id of physical slot zero, and four regression tests cover the
deferred-geometry state machine.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
seed::generate(years, today) is the pure deterministic generator; native first-run
persists it through the unchanged db path, demo builds keep the ledger in memory with a
pinned day so screenshots are stable. makepad-sqlite is a non-wasm dependency and the db
module only exists outside demo builds; the CSV import screen is absent in demo builds.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
A coaster car upside down at the apex of a loop is a body whose
quaternion is not the default; the renderer now honours it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The 4-16 GiB cache budgets are computed in u64 and saturate to usize::MAX on 32-bit
targets, same as the shared libs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
A demo build carries three bundled formula sheets instead of disk CSV open/save; the
seam is one trait with a native fs impl and a bundled impl chosen at start, the
Save/path chrome hides behind can_save, and the wasm clipboard write is skipped.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
libs/ai/loader, libs/sqlite_query, libs/video_flow: 32-bit const overflows and unix-only
file locking/bulk reads gated so the app crates type-check for wasm32-unknown-unknown.
platform web: ToWasmInit arrived before the app's Window widget allocated window zero and
indexed an empty pool; the browser geometry is now stored and applied once the window
exists, and the other id_zero index sites are guarded.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WV6BzHQiJEvvK9EPc1d4ks
An app exposes an AI service (manifest, tools with a risk class, a port
that receives calls and answers them later); a host aggregates every
connected service into one chat. Hosted by mpwm the port rides the
studio protocol's Custom frames under the mpwm_ai envelope; embedded, it
is a channel pair. The engine behind the feature flag and the panel that
draws EngineState land in the next lanes.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The generated android wrapper re-creates a standalone workspace and only
forwarded [patch.*] sections from the workspace root manifest, so deps
declared via [workspace.dependencies] + workspace = true failed to inherit
in wrapped crate builds. Extract the [workspace.dependencies] section the
same way patches are handled and inject it into the wrapper manifest.
- makepad_test/runtime.rs: forward NIGIG_TEST_MODE from host env to the
Android app via 'am start' intent extra; add wait_timeout (60s) used by
wait_visible/wait_hidden/wait_count; make query_widgets tolerant of
snapshot timeouts; grant READ_CONTACTS during adb setup
- makepad-platform android_jni.rs: read makepad.NIGIG_TEST_MODE intent
extra and surface it as the NIGIG_TEST_MODE env var via apply_studio_env
- cargo_makepad compile.rs: support verbatim custom AndroidManifest.xml in
addition to the templated variant
- makepad-xr xr_root.rs: add ortho camera controls (ortho, ortho_height,
min/max), derive Debug on XrCamera
- docs: ANDROID.md and DESKTOP_VISIBLE.md for makepad_test
The GrantPermissionsActivity pops up during navigation and blocks the
app's event loop, preventing hub responses. Pre-grant all runtime
permissions after APK install to avoid this.
PID 28203 (rs.robius.robrix) was the actual zombie reclaiming foreground
and killing our test app - not our own package. Force-stop both the
target package and known interfering Makepad apps (Robrix) during test
setup to prevent cross-app foreground competition.
Also remove the pm disable-user approach as it doesn't help against
a different package's zombie process.
Samsung devices keep killed app processes alive and bring them back to
the foreground ~15s later, killing our fresh test instance. force-stop
and kill -9 don't prevent this. pm disable-user fully prevents the
zombie from being restarted. Re-enable before launching the new instance.
The Android platform never sent BeforeStartup or AfterStartup messages
via the studio websocket. Desktop platforms send these through their
stdin event loops, but Android uses websockets instead of stdin.
Without AfterStartup, the hub never broadcasts AppStarted to UI
clients, causing makepad-test to time out waiting for app startup.
Adds feature-gated optional deps and re-exports (makepad-test, makepad-csg,
makepad-gltf, makepad-mbtile-reader, makepad-fast-inflate) so a downstream
workspace can depend on makepad-widgets as its sole Makepad source.
Adds the Android test runtime to makepad_test: builds the APK with
cargo-makepad's standard Java path, installs and launches via adb with
makepad.STUDIO_* intent extras (incl. STUDIO_BUILD), connects the app to an
in-process hub over adb reverse, and waits for startup + responsiveness.
Adds clean in-process hub shutdown (HttpServerHandle + GatewayHandle Drop)
and the STUDIO_BUILD intent parsing on the app side. No native-activity or
NDK APK compilation code is included.
The water shader blends premultiplied, so an alpha-zero sheet meant to
hide the river's buoyancy boxes became additive and painted bright
stepped rectangles over the ribbon and the meadow — every claim-level
invariant green while the screen was wrong. WaterVolume::draw_sheet
splits physical from visual: river volumes keep floating things and
never draw; declared pools still do.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
33.7k bytes to 16.4k: the plan doctrine at the top, the laws, the one-
call list, the build order for a new game, how to read assists and
refusals; per-verb prose left to the verb docs. Back under the context
budget the crate's own test enforces.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The system prompt chose the generation persona and the <<tool>> JSON
guidance whenever image.generate was among the tools — which a game
session legitimately carries for missing art — and appended the game
doctrine 19k characters later. Against the exact live prompt the model
answered "I can build that for you." and stopped; the same request
with the doctrine first emitted the call. Agentic is now decided by the
presence of world.* tools: doctrine first, an in-game builder persona,
the model's trained tool template, and no duplicate capability text.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The kit that plays drum scores is now a sample player over the Salamander Drumkit (velocity layers with round-robins, derived toms, a synthesised clap), loaded off the audio thread and swapped in atomically. The physically modelled kit stays as makepad-drumkit-phys for a later pass.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
apps/fabric runs SAM 3D Body in-process (install + licence through the hub panel), measures the rest-pose body mesh (libs/fabric/measure: 25 tape measurements from plane slices and landmarks) and drafts parametric patterns (libs/fabric/draft: T-shirt, A-line skirt, easy trousers; nesting, true-scale SVG, tiled A4 PDF). Live mode streams the webcam at a few frames a second, shows the posed body with the measurement rings riding on it, and re-drafts when the numbers settle. Measurements sit in a DataGrid that copies as tab-separated text.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A View can now say event_order: EventOrder.Down in script. The DataGrid cached its geometry at draw time, before a Fill sibling could shift it, so clicks in a right-hand column landed outside; it re-anchors to its drawn rect on every event.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Eight song sections by drums, bass, vocals, other and mix; launch and swap on the bar, right-click halves and quarters, per-cell waveforms with score blocks, a full-width score popup (drums, bass tab, melody with lyrics) played back through the piano and the sample kit, an NMF drum transcriber, Basic Pitch for pitched stems and Beat This! grid refinement when installed, beat jump and phase-flip buttons, a loading overlay for the grid, and the shared model install panel in INSTALL MODELS.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
libs/score_view carries the engraving, spacing, font and document code out of the score app so any app can show notation: a ScoreView widget with width/page/content fits, pan and zoom, a playhead, a dark palette, drum-key labels, lyrics under melody notes, and builders for drum, pitched and bass-tab scores. The score app uses it. Vector glyphs no longer write depth for transparent fragments, and the Metal screenshot staging sizes itself from the source texture.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
LocalModels runs registry models in-process (install state, resumable downloads, recorded licence acknowledgements at $MAKEPAD_HOME/license_acks.json, weight paths by file role) and libs/ai/hub_ui is the install panel + licence modal every app can embed. New native ports: Beat This! (beats + downbeats) and Basic Pitch (note transcription) with their registry entries; the Salamander Drumkit samples (CC BY-SA 3.0, 37 files pinned by size and sha256) as a sample bank the downloader fetches like a model.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Chunks keep the player's bytes apart from the composed density: plan
presses land in an owner-keyed PlanTerrainPatch that is retracted with
its feature and never touches history, so removing a railway takes its
bed with it and a human fill under it survives. Surface queries answer
Surface | Hole | Outside instead of pretending the heightfield continues
over a hole or past the border; landforms issued inside a plan eval are
plan, outside it history. Seven tests pin the layering, including
same-inputs-twice equality.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* widget_tree: keep manually inserted children linked across a refresh
A container that owns a child outside its own child vec inserts it with
`insert_child_deep` and never reports it from `children()`, so a refresh
unlinked it from every top-down search and the removal pass then deleted
its subtree. Everything under it went quiet, and invisibly: an empty
`WidgetRef` is a silent no-op.
Mark such children `manual` and keep the live ones linked, clearing the
flag once the parent reports the child itself.
* script: survive a foreign-heap value in the GC mark walk
`GenVec`'s `Index` bounds-checks the raw Vec before the generation check,
and `len` never shrinks, so an index past the end belongs to a different
heap. Aborting the process over one takes the whole app down for a fault
confined to a single script heap.
Add bounds-checked accessors and use them in `mark_value_fields!`, so
marking skips and reports a foreign value; name the table where an index
does still panic.
* splash: contain isolate panics, and report the silent failures
A Splash isolate runs user script on the UI thread, and the VM swap in
`with_isolate_installed` was not panic-safe: a panic could leave the app
VM swapped out, with later script resolving against the wrong heap.
Restore through the unwind, and contain panics at each entry point (pump
arms, timers, host callbacks, isolate GC, body eval, hook calls).
Report what used to fail silently too: callback errors, a script->widget
call whose target is gone, and wrong-VM routing. Adds three headless
regression tests.
The last two hub commits staged whole files and carried uncommitted hunks
of another lane (beats-native, notes-native, the local runner, new
domains and license keys) that reference files not yet in the tree. This
restores those files to the body changes only; the other lane's edits
stay in its working tree.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The native port covers everything the Python reference worker did (body,
hands, mask prompt, multi-person), so the subprocess backend, its fake
worker harness, the sam3dbody-ref registry entry and the
MAKEPAD_SAM3DBODY_* environment go. The packet validator moves to the
native backend.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The sam3dbody backend reads its options from the request's prompt string
(`hands`, `detect`, `persons=N`): `hands` runs the full mode and the
packet carries which hands were fused and their boxes; `detect` finds up
to N persons with SAM 3.1 (an optional native-segment role on the body
entry, the same artifact the segment entry pins) and runs one body pass
per person with its box and mask, so the packet's people array grows.
The body crate shares one body pass between the packet, mask and hands
entry points, and infer_full takes the mask prompt too.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BodyModel::infer_masked takes a provided person box and its full-frame 0/1
mask: the mask is warped with the crop's own affine (bilinear, rounded back
to 0/1 like the reference's uint8 warp), encoded by the prompt encoder's
mask CNN (four stride-2 convolutions with channel LayerNorm and erf GELU,
then a 1x1 to 1280) and added to the backbone tokens before the ray
conditioning, replacing the folded no-mask term. Oracle parity on the
mask fixture: warp exact, every CNN stage within f32 accumulation noise,
conditioned context 8e-4, end to end kp3d 2.2 mm / kp2d 0.25 px. 41 tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BodyModel::infer_full runs the reference's full mode natively: the body
pass's hand boxes become two 512 crops (the left one cut from the mirrored
image, padding 0.9), each goes through the backbone, its own ray
conditioning and the hand decoder (the body decoder with the *_hand
tensors), the hand-mode rig pre-transform (local_to_world_wrist, wrist and
root offsets, non-hand parameters zeroed) and the hand camera head's
scale factor of 10; the left result is un-mirrored. The fusion gates each
hand (wrist angle, box size, keypoint spread, wrist distance), re-prompts
the body decoder with the trusted wrists and elbows as point prompts (the
decoder now takes N prompt tokens and a previous estimate), and writes the
fused wrist angles, hand parameters and hand scale/shape back before the
final rig pass. pose.rs gains the roma xyz (extrinsic) and XZY (intrinsic)
euler pairs; mhr.rs returns joint global rotations.
Oracle parity on both full-mode fixtures: every hand-decoder stage per
step (tokens 7e-3, heads 4e-4, rig params 1e-4), fusion reports as the
reference (one hand trusted on the standing photo, both on the crop), and
end to end kp3d 1.6 mm / kp2d 0.85 px. 36 tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One backend call per SAM-style decoder layer (PE norms, token
self-attention, token-to-image cross-attention, erf-GELU feed-forward,
final norm) inside one command buffer, with the layer's f32 weights cached
on the device under their content identity and pooled transients.
gpu_two_way_layer_resident in the common backend; CUDA declines and the
per-op path stays. Body decoder loop on the M3 Max: layers 100 -> 9 ms,
frame 405 -> 266 ms, oracle parity unchanged (kp3d 1.5 mm).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
gpu_linear_f32_resident promised a resident weight but the Metal tensor
backend re-uploaded it every call, which for the MHR rig's 664 MB pose
corrective matrix cost 50 ms a step. Metal tensors now carry a content
identity (fresh at creation and after every in-place write, so a cache
keyed by it can never serve a stale weight), and the resident linear caches
the device buffer under it. Body frame on the M3 Max: 705 -> 405 ms (rig
302 -> 20 ms, refine 41 -> 13 ms, heads 10 -> 6 ms).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Metal tensor backend is host-Vec based: every op copied its inputs up
and its result back, which put the DINOv3 backbone at 2.1 s a frame on an
M3 Max. try_vit_backbone_resident_f32 runs a whole pre-norm ViT stack
(LayerNorm, q/k/v, rotate-half rope from tables, flash attention, out,
LayerNorm, SwiGLU, down; residuals in place) inside one command buffer
against cached bf16 weights, with a new kernel_makepad_rope_half_tables_f32
kernel for the rope. The precompiled metallib now carries the bf16 GEMM
kernels the runtime source compile already enabled on bfloat devices.
The common backend exposes it as gpu_vit_backbone_resident (CUDA declines;
its per-op path is already resident) and the body backbone tries it first:
233 ms a frame on the M3 Max, same oracle parity (kp3d 1.5 mm).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The FP8 backbone mode added each linear's bias by downloading, tiling and
re-uploading it per call, which cost more than the FP8 GEMM saved. A
gpu_add_cols_broadcast op (CUDA kernel; host loop on the Metal tensor
backend) adds a cols-wide bias to every row on the device, and each linear
keeps its bias resident after the first upload.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
BodyModel::set_backbone_fp8 quantises every backbone weight to E4M3 with
a per-tensor absmax scale and runs the tensor-core FP8 GEMM (bias
broadcast after). A backend without FP8 turns it off per layer on the
first refusal, so Metal keeps bf16. The oracle test reports its
accuracy and timing next to bf16; the default stays bf16 until the
numbers say otherwise.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The backbone's linears now take the cuBLASLt bias-epilogue / bf16 mm
paths (bf16 operands, f32 accumulation, bf16 output — the reference's
precision) with the f32-accumulating GEMM as the fallback. The rig's
pose correctives run on the final refinement step only by default: the
intermediate steps only feed keypoints back into the decoder, and the
oracle shows the difference is 0.4 mm (2.1 vs 1.7 mm against the
reference) for half the loop's rig time; `correctives_every_step` keeps
the exact mode.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The backbone, conditioning and decoder now take any square crop whose
side is a multiple of 16 (512 is the trained size); BodyModel::set_crop_size
selects it and caches the dense positional grid per size. Measured on the
oracle image against the reference: 512 gives 1.7 mm mean keypoint error,
384 and 256 about 2 cm mean (6 to 7 cm worst joint), 192 falls apart —
the knob is a real accuracy trade, not free speed. The decoder loop
reports its split (layer chain, heads, rig+camera, refinement) so the
next optimisation is chosen on numbers.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Apps ask the hub for a recognizer or a voice and get one; where it runs is
the hub's decision. AiHub::start_stt / start_tts return poll-driven
sessions shaped like the chat session. The Auto ladder is Whisper/Kokoro in
this process (weights present, machine election), on the machine node over
loopback, on a LAN node, else the OS engine; SpeechReach::Local is the
"don't reach out" knob. Audio always comes back as PCM: the app owns the
device.
Three layers:
- makepad-ai-speech is the whole speech model family, engines only.
libs/voice (Whisper + Silero VAD) folds in as the `whisper` and `vad`
modules next to kokoro and indextts, each a cargo feature; the Apple
bridges and the Speaker/VoiceTranscriber selection leave it.
- makepad-system-speech (new) is the OS speech services as blocking fns:
Apple SpeechAnalyzer/AVSpeechSynthesizer via Swift, Windows.Media.Speech*
on the vendored bindings, Android SpeechRecognizer/TextToSpeech through
MakepadSpeech.java (API 26 floor), espeak-ng on Linux. It models the two
STT shapes honestly: PCM in (Whisper, Apple) versus an engine that owns
the microphone (Android, Windows), with capabilities the caller reads.
- the hub grows speech sessions, in-process Whisper/Kokoro workers with the
residency election, a `whisper` wire backend (stt domain, registry entry
pinned to ggerganov/whisper.cpp) so a Mac can serve a Quest, and a
`language` field on the generate request.
Consumers: the Window voice input runs on an STT session and switches to
engine-mic mode when the recognizer owns the microphone; converse's
SpeechOutput is a lazily started TTS session plus a pump thread; route
drops its private speech copy for converse; vj's lyrics fallback and the
alignment bakes call the engines directly.
Verified here: speech-roundtrip through the real sessions (Apple voice in,
in-process Whisper on Metal out, 4.3% WER); system-speech-test TTS->STT
verbatim; hub/converse/system-speech unit tests; msvc, aarch64-android and
linux-gnu cross-checks; Java against android-34. Windows, Android and Linux
bridges are compile-checked only.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
libs/windows/windows-rs/src/Windows/mod.rs was a hand-pruned snapshot with
no recipe behind it; adding a WinRT namespace meant hand-copying vtables.
Now tools/windows_bindgen (standalone, windows-bindgen 0.62.1) regenerates
it from filter.txt: package mode with the COM `_Impl` traits, Win32 imports
linked through windows_core, upstream's 800-column rustfmt (the repo-root
rustfmt.toml disables formatting and has to be overridden), the filter
closed over every dependency the generator would otherwise skip a member
for, the namespace tree folded into one file with every feature gate kept,
and the two spots where the published 0.62.1 generator predates the vendored
core 0.62.2 normalized (Error::from_thread, imp::array_proxy).
The filter is the old file's item list plus Windows.Globalization.Language,
Windows.Media.SpeechRecognition and Windows.Media.SpeechSynthesis for the
OS speech engines. The generated `deprecated` gate is declared as a feature.
Checked on x86_64-pc-windows-msvc: platform, video, network, mpterm,
system-speech.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The backbone (20 heads) and the decoder (8 heads) both have 64-wide
heads, which the stack's FA2 kernel covers: f16 operands with f32
softmax and accumulation, the reference's own precision class, instead
of the composite path that materialises the 1029 x 1029 scores per head.
The composite path stays as the fallback where a backend lacks the
kernel. Oracle parity unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The ray-conditioning conv used to download the 1024 x 1280 embedding,
concatenate the 99 ray features on the host and upload the result; it is
now two resident linears over the two column blocks with the no-mask
term folded into the bias, plus an add (32 ms -> a few on the 4090).
The decoder loop downloaded the whole normalised token block every
layer for its one pose row; it now slices that row and fetches the
block once at the end (or per layer under a trace). Same numbers.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
On the 4090 the frame was 885 ms of which 840 were host scalar loops in
the decoder loop: the three 70-row refinement FFNs (about 240M
multiply-adds a step), the two heads, and the rig's 55k x 3000
corrective output layer plus its identity blendshape sum. Those are now
GPU-resident linears (heads.rs GpuStepHeads, MhrRig::prepare_gpu), the
skinning computes one transform per joint instead of one per influence,
and the results are identical (all 29 oracle tests unchanged). The rig
still runs entirely on the host when no GPU side was prepared.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The recut in a6341981d applied its patch against the wrong directory
and restored the six shared files to the previous tree without the
body-native hunks. This commit adds exactly those: the `body-native`
feature and optional dependency, the pinned `sam3dbody` registry entry
and its test, the backend arms and the module declaration. Working
tree untouched.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
66e5e2f11 committed the working tree of the shared hub files and with it
another lane's uncommitted edits (a new domain, request fields, a
backend arm, manifest lines). This commit restores those files to the
previous tree plus only the body-native hunks. The working tree is
untouched: the peer's edits stay on disk as their uncommitted work,
exactly as before.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
model.rs closes the loop: crop -> backbone -> ray-conditioned context ->
six decoder steps, each through the pose head, the rig, the camera and
the projection, then the packet the sandbox already reads (kp3d/kp2d in
camera axes, the 204 rig parameters, global rotation, camera translation,
joint positions). Against the reference on the oracle image, on Metal:
3D keypoints within 1.7 mm, 2D within 0.4 px, rig parameters, camera
and rotation within 2e-3. packet.rs writes the JSON by hand with the
reference worker's rounding and field order.
The hub gains the `body-native` feature (default on): registry entry
`sam3dbody` pinned to the Comfy-Org repack by revision, size and sha,
body_native_backend.rs beside the subprocess reference backend with the
same live_step contract, the `body` capability advertised when the
feature is compiled, and a stubbed test double for the CPU-only tests.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
decoder.rs builds the 145-row token set (pose, previous, prompt, two
hand-box rows, 70 keypoint rows, 70 3D-keypoint rows), runs the six
layers with the SAM-style repeated positional encoding (none on the
first layer's self-attention), cross-attends against the ray-conditioned
context, and after each step hands the normalised pose token to the
heads and updates the keypoint rows from the caller's feedback: the
2D-keypoint positional FFN and the bilinearly sampled context features
on the valid rows, the pelvis-centred 3D positional FFN on the rest.
heads.rs holds the host-side ReLU FFN heads, the refinement FFNs, the
hand-box MLP and the hand classifier. Against the oracle on Metal, every
layer's residual stream is within 6e-3, every step's pose head within
8e-4 and camera head within 2e-5; token assembly and the host heads
match to 1e-4.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
dino.rs is the 32-block ViT-H+/16 on the gpu_* surface after the TRELLIS
conditioner: bf16-resident linears with f32 accumulation, layer scale
folded into the output projections, rotate-half rope, SwiGLU, the
model's own final norm. preprocess.rs is the crop (box -> 1.25 pad ->
3:4 -> square -> 512 bilinear warp, ImageNet normalisation), the CLIFF
condition vector and the patch rays; condition.rs is the dense
positional encoding and the ray-conditioned decoder context. Against the
reference oracle on Metal: backbone 1.1% mean relative (bf16 noise),
ray-conditioned context 8e-5, crop within one u8 rounding step, rays and
dense PE 1e-7. One reference detail the paper does not state: its shrink
of the ray field is an antialiased filter whose taps clip at the image
edge, so the two edge patches sample inside their block centre (9.03 and
501.97 rather than 7.5 and 503.5); block centres left 0.1 of error.
The Metal tensor backend was missing rope_half (it aliased the
interleaved layout) and layer_norm_mul_add; both now exist with the
CUDA contract, which is what lets this backbone run on Apple silicon.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
mhr.rs is the Momentum Human Rig on the CPU: blendshapes, the 889x249
parameter transform, parents-first similarity kinematics (x,y,z,w
quaternions, Rz Ry Rx eulers, 2^s scales), the sparse-then-dense pose
corrective MLP over joints 2.., linear blend skinning and the 308-row
keypoint regression. pose.rs decodes the 519-wide head output: 6d
rotations, the 23 ball / 58 hinge / 6 translation body layout, the
mixed-dof hand layout, scale and hand component bases, the camera
translation and the perspective projection. Against the reference
oracle: vertices within 1e-4 cm with correctives on, keypoints within
1e-6 m, rig parameters within 1e-7. Two conventions the spec could not
settle on paper are now settled by the fixture: the head's global
rotation triple arrives Z,Y,X-ordered, and the corrective features start
at joint 2 (750 wide). Fixture tests skip cleanly without the oracle
directory or the weights.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
makepad-ai-body joins the AI model workspace: the constants of the
architecture (DINOv3 ViT-H+/16 at 512, the 6-layer promptable decoder,
the 519-wide pose head, the 127-joint / 18439-vertex MHR rig) and the
single-file safetensors reader for the Comfy-Org repack, which fails
closed on a Meta checkpoint-style header and checks the shapes the port
is written for at load. The backbone, decoder and rig modules follow
in their own lanes against the spec under local/agent_state/sam3dbody.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
encode_intra_frame_mp4 on Windows and Linux now goes through the
platform file encoder (Media Foundation sink writer, GStreamer): one
keyframe-only stream, one frame, a scratch file read back and removed.
It pays the container machinery the Apple session path avoids, but a
tile tape baked on any machine reads on any other, which is what the
image-tiles library sits on. A round-trip test encodes a frame and
decodes it through the platform decoder on whichever platform runs it.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
encode_intra_frame_mp4 was macOS-only, and libs/image_tiles imports it
unconditionally, so image-tiles, its example and source-library did not
build for Windows (found by a Windows-target cargo check sweep of the
workspace; every other crate checks clean). Other platforms now get the
same signature answering with an error, the way the rest of this file
stubs what it cannot do yet.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The doctrine catches up with the engine: landforms and tunnels are one
call and digging works anywhere; railways and roads are edited by their
path points (crossings generate, styles switch, any model drives);
freshly modeled aliases are live immediately — never park a display
substitute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The voxel layer's volume gate is gone — any edit materializes chunks
anywhere on the map (volumes remain as styling), with the lazy-chunk
economics proven by test: zero storage until the first op. The composed
surface seam (GameWorld::surface_height_at: heightfield where untouched,
voxel surface where a chunk owns it) is what ground queries, senses and
the drape corridor read, so a mountain raised mid-game and a dug crater
are the same truth the vehicles drive on. landform.rs adds the macro
shapes (mountain/hill/ridge/valley/crater/plateau, fbm detail, slope
rock + snow recolor) and the tunnel op rides the capsule-filtered
materialization.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
searchdb imported std::os::unix::fs::FileExt for read_exact_at, which
does not exist on Windows (reported from a Windows checkout of work).
A small ReadExactAt trait now wraps unix pread and Windows seek_read,
both cursor-free, so the shared reader keeps serving lookups from
several threads without a lock. Checked on x86_64-pc-windows-msvc.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A realtime feed session parked in its wait-for-a-frame loop forever when
the client died without sending stop (a sandbox quit left job-2 live on
.123 for five minutes holding the GPU slot). The wait loop now returns
to the top of the session loop once no socket is left, where the idle
timeout counts a socketless session down. Test covers it.
SkinnedModel gains node_parent/node_count, joint_skinned_centroid (the
direction a leaf limb actually runs, from the flesh it skins) and
from_nodes (a mesh-less rig for hierarchy-maths tests) — what the
sandbox's webcam mocap retarget needs.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B626urtY1Xo4hQdLzvSK6F
EmitterAnchor::EntityLocal carries an offset that yaws with the entity —
a locomotive's chimney keeps its smoke over the funnel through every
corner. The step lookup resolves (position, yaw) instead of position.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same orphaning as the sandbox: context::assemble had no caller post-P8,
so gen/vj sessions ran brief-less. The executor's capability doc leads
with the assembled profile layer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The verbs existed with the right doc strings, but the model reads the
brief, not the verb table — and the brief's ONE CALL list never said
railway, so it hand-placed track pieces, horrendously. Now it says it,
with the two-line essential shape.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Source Library wall's pixel engine, extracted for anyone's pictures:
libs/image_tiles carries the NV12 slot pyramid, the 32x32 shard atlas
geometry and the one-HEVC-intra-frame-per-file tape codec (tape.rs), a
small SQLite index (db.rs), a priority decode pool (store.rs), and the
TileGrid widget — instanced tiles batched per atlas page, anchor-locked
wheel zoom with log-space glide, continuous per-shard LOD with
crossfades, full-resolution promotion under byte budgets with LRU
eviction, and uniform-only re-presents inside the pad of the last build.
image-tiles-bake downloads a manifest of URLs (fetch pool free to be
wide, encode pool hard-capped — concurrent VT session churn has panicked
the encoder's IOMMU) and bakes a library; examples/image_tiles views one.
No JPEGs on disk; decode and encode are the hardware's.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Post-P8 the game session still advertised assets.query/assets.schema but
the in-process executor answered both Unavailable — the model's main
alias-search tool was dead, and every "find me X" turn gave up on the
store. Both now execute over the server's bounded /v1/assets/query route
(schema via sqlite_master plus the usage notes). The game brief gains the
new-game-vs-edit rule, interior design law, generate-after-search, and
explicit y semantics so player_pos().y stops being pasted into
ground-relative fields.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One DrawSceneScreen serves both the in-world video screen and every
sprite billboard, and the sprite loop left its last pose, size and atlas
behind — so the next frame's "is there a screen" gate (zero screen_size
draws nothing) read the leftover and drew the whole spritemap as one
opaque quad: under the last-drawn unit in C&C, and hanging in the sky of
any level that draws no sprites at all. Snapshot the host's pos/size/
texture before the lane and restore after.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New `body` backend (sam3dbody-ref): a persistent length-prefixed-PNG /
JSON-lines worker subprocess seam with ready handshake, per-frame timeout,
bounded restarts. LiveFrameOut grows aux_json — structured per-frame JSON
sent to the client before the frame — and output_encoding "none" makes a
session pose-only (refused with loop_mode feedback, also on control flips,
which upgraded apply_control to Result). Worker code+model stay
box-provisioned via MAKEPAD_SAM3DBODY_WORKER; the repo carries only the
MIT seam. Codex lane + Fable review (ready handshake, spawn timeout).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0165w1ZL1f1TruX5u2qC7mSX
The deepest-wins pick could never land on a container its children fully
cover (the chat panel, any rounded-bg pane). Now clicking inside the
pinned widget again walks the pin up one ancestor per click, skipping
design-transparent views, zero-rect wrappers and ancestors whose area
misses the click (a splitter's grab bar); at the window the climb wraps
back to the deepest pick. The window itself is never borrowed — it is
mid-dispatch when the handler runs (tree lookups decide before any
widget borrow).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The VJ's attach-or-host logic (health probe, beacon listen, the main
store's server.lock as proof-of-life; loopback-only Host with the library
publisher, rooted in the main library when one exists) moves into
makepad-app-asset-server as embed::resolve, steered by <PREFIX>_ASSET_
EMBED/ROOT/PORT. vj/local_store.rs becomes the thin VJ-flavoured wrapper.
First second consumer: the sandbox.
console_scale defended the width and rearranged the very short, but
between the fold thresholds (~820 points) and the height the console
actually needs (~1000) lay a dead zone where the lower region simply
clipped — an mpwm tile at 844 points lost its rail from MAP down and half
a grid row. TARGET_HEIGHT_POINTS closes it: console_dpi now takes the
window's physical height and a short console shrinks exactly as a narrow
one does, to the same MIN_SCALE floor, before the folds take over —
measured in the shrunk points. A wide, short window still prefers the
lists-beside arrangement (judged at native scale, so the chain stays a
pure function of the physical window). The whole rule chain threads the
height; tests cover the tile shape that exposed this and the new ladder.
The tiny icon page-tabs are gone. The special pages join the lane rail as
first-class chips: when one is up no content lane reads selected, and
clicking VIDEO (or any lane) flips the lower region back to the grid — the
persisted ARCHIVE page can no longer read as an empty library.
1.98's sharper macro-use tracking flags the live-id macro globs five script
modules kept without using; windows-strings' strlen extern takes *const
c_char with a cast at the two call sites instead of tripping
suspicious_runtime_symbol_definitions (PCSTR is transparent over *const u8,
so the ABI never changed).
the vision and ocr backends' ContentBackend machinery lives behind the
llm feature; the imports and the lane-refill helpers now do too, so a
client crate compiling the hub with a leaner feature set (vj, chat-ui)
builds without warnings. pipeline_submit_collect stays feature-free for
its tests and says so.
Attach still wins whenever discovery finds a server (health probe, UDP
beacon, main-store lock). When nothing answers, the VJ now boots the
standalone asset-server's Host instead of a bare AssetServer: catalog +
CAS plus the ai-content library publisher over local/ai_content_library,
so a self-hosted VJ sees the same rows asset-ui publishes — not an empty
seed store. The fallback root is the user's main library when one holds a
catalog (server.lock arbitrates the race; losing it means attach). Both
planes bind 127.0.0.1 and no beacon runs: a private host is not a thing
the network can discover. HostConfig grows a blob_refs knob so the VJ
keeps its reference-import policy; the store decision is logged at boot.
Squashed from work:
- frametween: the VJ's in-betweener becomes a library — the whole mode set, one definition
- rtsmap: one seeded generator for tiled strategy maps
- workspace: register the score crates, archive_org and mp4_index
- map: bake a runnable Amsterdam test map from inside the route app
- platform: file dialogs on Android and iOS, and unbreak the Android build
- ai-hub: makepad-asset-ai becomes makepad-ai-hub at libs/ai/hub, the chat pane becomes makepad-chat-ui, the service bin
- strict-json: the dependency-free JSON module gets its own crate; asset-client re-exports it so nothing downstream move
- asset-creator: the pipeline library is born — specs, the deps gate, and the derived-state law (aicore §9)
- makepad_ai is deleted — every backend is a hub pipe, the agent seam lives with its consumers (aicore §14, decided 2026
Squashed from work:
- docs: button shader annotations — widgets/button.rs complete + splash demo buttons
- tweaker: the Shader tab shows the pinned widget's ANIMATOR STATES as little posed swatches under the well — one per tr
- widgets: the glass stops reading draw_pass.time, so glass apps idle again
- render, sim, platform: what a strategy round needs underneath
- draw, platform: overlays now composite above content that uses draw_depth
- asset: mp4 sample index for range-streaming, chat tools, import profiles
- platform: native file and save dialogs, in-house on all three desktops
- sim: a per-unit decision hook — one commandable unit can think for itself, before the kit steers
- makepad_ai is deleted — every backend is a hub pipe, the agent seam lives with its consumers (aicore §14, decided 2026
- libs: the zero-warning sweep — stitch casts say what they mean, xatlas keeps upstream's surface quietly
- zero-warning sweep, round two — the first full-workspace pass
- zero-warning sweep, round three — the model lanes and the deep examples
- zero-warning sweep, round four — the last stragglers
Squashed from work:
- pdf_parse: indirect /Length, and recover the glyph names a symbol font uses
- mpwm: make the Linux desktop integration usable (#1204)
- mpfiles: the size map goes all the way down, and remembers what it found
- mpfiles: the treemap walk streams every directory at every depth — no more opaque growing blocks over deep heavy subtr
- mpfiles: local qwen chat sidepanel over the treemap selection
- mpfiles: a camera over the treemap — scroll zooms at the cursor with real re-layout detail, drag pans, double-click fi
- mpfiles: a 2.5D treemap — the map extrudes by nesting depth under a 3d toolbar mode
- mpfiles: a real orbit camera over the block view — left-drag orbits, right-drag pans, click stays a click; the project
- mpfiles: the wheel glides, the filter stops paying per frame and per keystroke — measured weights cached against the t
- mpfiles: the 3d view wears no name tags — labels only on hover, the tooltip carries the numbers
- mpfiles: a camera gesture never re-flows the map — mid-motion the picture rides one rigid remap of the last layout, an
- mpfiles: a small zoom keeps the map still — the layout is only remade when the camera truly leaves it (a 1.3x detail b
- mpfiles: a camera reveal is not an arrival — cells the cull swings into view appear instantly at full strength, cells
- mpfiles: the treemap packs in canon space — the arrangement is zoom-invariant by construction; zoom only refines the t
- mpfiles: the map stops breathing — insets are fractions of map space, names reserve no room and float on scrims with a
- mpfiles: a camera settle is calm — zoom detail materialises in place, no arrival fade; the ceremony stays with filter
- mpfiles: camera motion animates nothing but the camera — flat labels freeze per relayout and ride the remap; a calm se
- mpfiles: a fast zoom-out never outruns the map — cull escape relayouts the same frame, the glide's destination is laid
- mpfiles: wheel zoom in the raised modes anchors on the tile top under the cursor, not the ground hiding behind it
- mpfiles: first consumer of the ai-hub — the ask panel rides AiHub::start_local_chat, its private engine is gone (aicor
- ai-hub: chats run the machine election — route to a serving holder, wait on a loading one, claim and publish when open
- zero-warning sweep, round two — the first full-workspace pass
- zero-warning sweep, round three — the model lanes and the deep examples
Co-authored-by: Alex <blackanger.z@gmail.com>
Squashed from work:
- vj decks: sync is a held lock — pinned master, per-pump rate servo
- vj: the deck explorer opens on music
- frametween: the VJ's in-betweener becomes a library — the whole mode set, one definition
- frametween: the gate says which tier moved the picture, from pixels
- frametween: BGRA words go in as they are, and a host may own the clock
- vj: archive.org as a content source
- platform: native file and save dialogs, in-house on all three desktops
- vj: rounder slider caps, and the cap body reads as a blob
- ai-hub: makepad-asset-ai becomes makepad-ai-hub at libs/ai/hub, the chat pane becomes makepad-chat-ui, the service bin
- vj: DREAM runs execute in the app — pipelines.rs becomes the run it used to watch (aicore §9 / F1)
- vj: plain generations execute in the app too — the store's job queue loses its last vj client (aicore §9 / F2)
- asset-creator: the runner — generate one thing and put it in the catalog, one implementation for every surface (aicore
- importer + asset-server host: the coordination era ends (aicore P7)
- client + chat dispatcher: the dead wire comes out (aicore P7/P8)
- zero-warning sweep, round five — vj and chat-ui
- zero-warning sweep, round six — three cascades
- zero-warning sweep, round seven — the last two
Squashed from work:
- rtsmap: one seeded generator for tiled strategy maps
- map: bake a runnable Amsterdam test map from inside the route app
- route: the first-run test map starts itself, and its buttons are guarded
- map: the test map bakes its road faces too, from the same shared pass
- map_tiles: mkmap-extract — the weave is reversible, world-cells reconstructible from world.mkmap
- route: the local dispatcher rides the hub — its copied engine is deleted, limits preserved (aicore P1)
- route + converse: off makepad_ai — the Agent seam moves to converse, route's cloud dispatcher rides the hub's Claude p
- ai-hub: chats run the machine election — route to a serving holder, wait on a loading one, claim and publish when open
- libs: the zero-warning sweep — stitch casts say what they mean, xatlas keeps upstream's surface quietly
Squashed from work:
- score: a headless music engraving, playback and notation engine
- score: the notation app — pianist mode, editing, playback
- piano_model: it was a plucked string by construction, and 20 voicings
- score: one document you can pan, zoom and navigate
- piano_model: the body tap was a click, and the objective was rewarding noise
- score: add the sound panel and library modules
- piano_model: a second engine, and the attack that finally sounded right
- score: two instruments, reverb and brightness — and the rest of the panel gone
- score model: a note remembers how it was struck, and the score remembers the pedal
- score import: keep the velocities and the pedal the file was carrying
- score playback: play the performance, not a flattened copy of it
- score ui: the music list moves to the sidebar, and the view stops fighting itself
- score: the application ships its font and eight performances
- piano_model: a limiter that rides the music, so the knee stops shaping chords
- piano_model: the forte bell was the treble's dynamic slope, and the bass was dying at its own prompt rate
- piano_model: the bridge decides each partial's decay, and a fixed multiplier cannot say that
- piano_model: each partial gets its own two coupled modes, from the eigen algebra
- piano_model: a median that fell between the peaks made every bass partial a drain
- score-ai: LocalBroker — the seam's in-process implementation over the session engine (aicore P8)
- client + chat dispatcher: the dead wire comes out (aicore P7/P8)
- score_pdf: the score model grew a pedal map — the pdf importer initialises it
- libs: the zero-warning sweep — stitch casts say what they mean, xatlas keeps upstream's surface quietly
- score app: the shipped-piece test speaks the PERFORMANCES table
- zero-warning sweep, round three — the model lanes and the deep examples
Squashed from work:
- asset-ai: FastH3 4-step fast video backend; clip keyframes on the wire
- asset-ui: loop video chains — text→image→video that ends where it began
- h3: safetensors -> pruned-Q4_K GGUF quantizer for the 24GB DiT tiers
- h3_quant_gguf verify: row-error gates calibrated to the measured Q4_K floor
- asset-ai realtime: the feedback loop — the source anchors, the drifted frame inits
- asset-ai realtime: a feedback loop survives a resize and travels by default
- asset-ai realtime: the feedback loop frees itself from the feed handshake and pauses for its listener
- asset-ai realtime: the outbound encode leaves the loop's critical path
- asset-ai ocr: the ocr domain — Chandra 2 at page resolution, and the tower goes planner-owned
- llm slots: a lane can hold an image span — embedding prefill and a rope cursor of its own
- vision tower on CUDA: the encode leg gets its two missing kernels
- llm/ocr: one M-RoPE grid encoder for both image paths, and a livelock made an error
- vision tower on CUDA: the f16 GEMM keeps the precision it was throwing away
- live: a feed that moves box takes its trip with it — one seed image
- vision tower on CUDA: the tiled attention becomes bit-exact, and tensor cores go
- llm prefill on CUDA: the MMA attention kernel gets the tile a 4-to-1 model needs
- asset-ai ocr: the CUDA encode lane joins the integration — vision-parity sits beside run's three arms, and the kernels
- Merge branch 'ocr-perf-integration' into work
- asset-ai: the live anchor can follow the trip, and text leaves the 5090
- asset-ai: the camera moves the world, and the world starts still
- asset-import: the EA strategy classics, in the one 2D contract
- rtsmap: one seeded generator for tiled strategy maps
- asset-ui: one card for the strategy classics, with a pack dropdown
- asset-ai: music3 reference-audio path, ocr/h3 backends, registry
- asset: mp4 sample index for range-streaming, chat tools, import profiles
- cnc: tiberium is twelve growth frames, not twelve empty variants
- platform: native file and save dialogs, in-house on all three desktops
- chat: the scan holds out for a lane home
- chat: a full home queues you — take the free lane
- chat: the preload has a percentage, and the boundless cap stops showing
- llm cuda: the 32x2 attention tile — even GQA ratios stay on MMA
- sa3 gets a bake path: the sfx model's tables precomputed by a diffusion-side bin
- sqlite_query: anti-join regression test
- td import: HARV's second frame block is its harvesting cycle, not a turret
- asset-ui: sprite enhancement runs on the 32B dev DiT — distillation, not the prompt, was the ceiling
- ai-hub: makepad-asset-ai becomes makepad-ai-hub at libs/ai/hub, the chat pane becomes makepad-chat-ui, the service bin
- asset-ui: test health fixtures grow the realtime field they were born without
- ai-hub: one home at ~/.makepad — weights/ run/ cache/ logs/, the service cache migrates from ai_content by a single re
- ai-hub: subprocess workers die with the node — process groups everywhere, PDEATHSIG on linux, one KILL_ON_JOB_CLOSE Jo
- ai-hub: the hub object — AiHub::in_process, pipes vocabulary, and the local LLM engine generalized out of mpfiles (aic
- strict-json: the dependency-free JSON module gets its own crate; asset-client re-exports it so nothing downstream move
- ai-hub: the machine layer — node entries, the 0600 machine token, and the residency election that IS the lock (aicore
- ai-hub: MPHUB1 — the fabric beacon only dedicated nodes can send (aicore §4)
- ai-hub: job leases — work lives only while it is renewed (aicore §8)
- asset-creator: the pipeline library is born — specs, the deps gate, and the derived-state law (aicore §9)
- ai-hub: RAM residency facts — the CPU-side twin of residency.rs (aicore §3)
- ai-hub: ETA placement primitives — relative GPU throughput, the four-term estimate, and an observable breakdown (aicor
- ai-hub: leases go live on the wire — origin fields on submit, /job/<id>/keepalive, /bye, and the reaper that cancels w
- ai-hub: the chat providers move in — fleet qwen, openai, grok, claude/codex/grok CLIs, the responses driver, and the w
- asset-creator: the engine — one pipeline run against the hub, deps-gated, spliced, cancellable, resumable-by-construct
- ai-hub: the machine node mode — --machine binds loopback, registers in ~/.makepad/run, and exits on its own once idle
- asset-creator: makepad-creator-run — the detached client for runs that must outlive a window (aicore §9)
- ai-hub: a native Claude Messages-API provider — API-key or Claude Code OAuth, bounded SSE streaming, injected tools (a
- route + converse: off makepad_ai — the Agent seam moves to converse, route's cloud dispatcher rides the hub's Claude p
- asset-creator: the preset tables move in — fifteen chain-policy constants shared by every creator app (aicore §9 / P6)
- makepad_ai is deleted — every backend is a hub pipe, the agent seam lives with its consumers (aicore §14, decided 2026
- ai-hub: loads hold the machine residency election — set_model_state claims on Loaded and publishes the service port (a
- ai-hub: chats run the machine election — route to a serving holder, wait on a loading one, claim and publish when open
- ai-hub: pick_for_domain_eta — ETA-ranked placement over the shared hard-filter core (aicore §6 / P4)
- asset-creator: the engine picks a provider per stage at dispatch time — a chain's later stages see fresh fleet state (
- ai-hub: the fabric secret gates the service HTTP surface — bearer on everything but /health and the ticketed peer path
- vj: DREAM runs execute in the app — pipelines.rs becomes the run it used to watch (aicore §9 / F1)
- asset-creator: the runner — generate one thing and put it in the catalog, one implementation for every surface (aicore
- chat-ui: the session runs in the app — no broker anywhere on the chat path (aicore P8 / F5)
- asset-store: assets.query is a first-class query endpoint — the bounded SQL surface outlives the broker (aicore P8 / F
- asset-creator: CreatorTools — the chat tool pack for a store that only stores (aicore §9 / P8)
- asset-store: the shrink — the store stores (aicore P7)
- importer + asset-server host: the coordination era ends (aicore P7)
- store config purge + asset-ui goes fleet-direct; the derive protocol gets its route proof (aicore P7)
- client + chat dispatcher: the dead wire comes out (aicore P7/P8)
- ai-hub: 0.3.0 — the health version says which era a node runs
- ai-hub: the default fleet is 'gen' — apps hear the LAN without env plumbing
- ai-hub: the preload note percents the prefill, not the job bar
- ai-hub: conversations keep their KV — the wire mirror, the lane identity, the in-turn dynamic context (aicore §7)
- ai-hub: an open-think model is thinking from its first token
- libs: the zero-warning sweep — stitch casts say what they mean, xatlas keeps upstream's surface quietly
- zero-warning sweep, round two — the first full-workspace pass
- zero-warning sweep, round three — the model lanes and the deep examples
- zero-warning sweep, round four — the last stragglers
- zero-warning sweep, round five — vj and chat-ui
- zero-warning sweep, round six — three cascades
Co-authored-by: Claude <info@makepad.nl>
Squashed from work:
- every window can record itself: SHIFT+F12 writes picture and sound to local/screencap
- the frame has witnesses now: presents, uploads, encode time, gpu time, and a pinned display link
- the upload counter names its kind: instances and textures split, and any single item over half a megabyte logs itself
- MPINPUT: input-to-glass latency in the present pulse
Squashed from work:
- platform: native file and save dialogs, in-house on all three desktops
- platform: file dialogs on Android and iOS, and unbreak the Android build
Squashed from work:
- map: overlay and label rendering
- map: bake a runnable Amsterdam test map from inside the route app
- map: gate the @cam readout; a .mkmap archive is a directory
- map: the view's own draw list carries its clip
- the map's centre is clamped to the world, not wrapped past it
- a country is drawn in hairlines: carto's road ladder joins by zoom
Squashed from work:
- draw: DrawVector reused one geometry slot for every session in a frame
- draw, platform: overlays now composite above content that uses draw_depth
- DrawVars::set_uniform_on_draw_list — one uniform into every retained call of a shader in a list, pass repainted
Squashed from work:
- render, sim, platform: what a strategy round needs underneath
- platform: midi pitch bend was sent with its bytes the wrong way round
- http: the connect slot is a gate, not a turnstile — many connects at once
Squashed from work:
- glass: a glass button can carry an icon
- widgets: the fab palette cell's colour is a field, not an object
- widgets: the glass stops reading draw_pass.time, so glass apps idle again
- live-with-parent passes: the glass blurs the world in realtime again
- fold_header: visually open is open
- fold_header: the ease snaps to its ends
- fold_header: settled state is the truth, the pane edge is the law, the area is the fold
Squashed from work:
- video: hardware first-frame decode straight from RAM — no temp files
- video: the encoder transform is reported once, not once per encoder
- video: a single still does not need a whole AVAssetWriter
Squashed from work:
- widget_tree: skip-search nodes bound path-cache invalidation
- tweaker: fold the shader source view; plain TextInput, not CodeView
- docs: button shader annotations — widgets/button.rs complete + splash demo buttons
- tweaker: the Shader tab shows the pinned widget's ANIMATOR STATES as little posed swatches under the well — one per tr
- tweaker: the animator-state swatches are full-size wells stacked vertically under the main material well (same width,
- tweaker: typed editors for structured values — a reflected Vec2/3/4 is fused x/y/z/w scrub fields (the whole vector re
- shader: const-table mode — /** name min..max step s */ float literals inside fn bodies compile to hot-patchable scope-
- tweaker: the Props tab opens with TWEAKABLES — every annotated value (a /** */ doc on the key) hot-first, each with it
- tweaker: the sidebar row templates are hoisted into shared let bindings (one source of truth for the Props list, the S
- docs: shader-code constants annotated in the six core widgets
- tweaker: SHADER CONSTANTS — the Props tab opens with the annotated literals inside the pinned widget's draw-layer fn b
- tweaker: undo/redo resolve the pinned widget when a history step's path runs through anonymous segments (a '-' or a li
- shader const table: whole-number literal operands lift too — the parser packs `x + 6.`, `y - 2. - b`, `w * 4.`, `n - 1
- tweaker: the Shader tab reads well stack → SHADER CONSTANTS for the mirrored layer → INPUTS (its uniforms/instances, a
- tweaker: the panel cleaned up to the user's list — the filter (with the exploded-view and note buttons) is the top row
- tweaker: three panel polish nits — a section whose rows are all secondary leads with its first three instead of an emp
- tweaker: the VJ session's fix batch — (1) mirrored material wells clip to their scroll viewport: the well is its own d
- tweaker: theme colours, chunk 1 — the panel reads the app's palette from mod.theme's cascade once per session (every c
- tweaker: the theme hover pulse — hovering a colour chip pulses that theme colour live across the whole app, and grabs
- tweaker: the theme palette strip in the colour popover — hover names and pulses a theme colour, a click binds the prop
- tweaker: the Theme tab — every theme colour and number in a fourth panel tab, colours edited live app-wide, all of it
- tweaker: F12 works without the bridge
- tweaker: the selection wears viewfinder corners, not a box
- tweaker: radius handles come to the hand, not the eye
* windows: publish shader-cache entries atomically, and unwind a cancelled resize
Two ways a second instance, or an interrupted drag, leaves a window permanently
degraded. Both were found auditing the backend rather than reported, and both
are cheap.
The DXBC cache is a plain per-user directory that every makepad process on the
machine reads and writes, and entries were written straight to their final path.
`fs::write` truncates first, so a second instance starting at the same moment
could read the prefix of a shader the first was still writing -- the file exists
and the read succeeds, so nothing notices until `CreateVertexShader` is handed a
truncated blob. `shader_bytes_cached` even documented the read path as catching
this, which it did not. Entries are now written to a temporary file and renamed
into place, which is atomic, and a blob that is not a complete DXBC container
(the 32-byte header and its magic) is recompiled instead of used. The temporary
name carries the process id so two writers cannot collide on it either.
`is_in_resize` and the 8 ms resize timer are armed by WM_ENTERSIZEMOVE and were
disarmed only by WM_EXITSIZEMOVE. Win32 does not guarantee that pairing, and a
drag that ends without it leaves the timer forcing repaints forever and the
window presenting unpaced for the rest of its life -- it never returns to vsync.
WM_CANCELMODE now unwinds the loop, gated on having actually been in it, since
that message also arrives for menus and capture changes and unwinding a resize
that was not running would cost a needless ResizeBuffers each time.
Verified against a release build: two instances launched simultaneously on a
cold cache both come up correctly with no panics and no temporary files left
behind, and window resizing is unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* d3d11: validate a cached shader against the size its own header declares
Checking the magic and a 32-byte minimum accepts a blob that was truncated
after its header, which is exactly the shape a half-written cache entry takes.
The DXBC header carries the container's own total size at offset 24, so
comparing it to the length rejects those too.
Publishing entries atomically stops this backend from writing a partial one,
but the cache is a plain shared directory that older builds have already
written to, so the read path still has to be able to tell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* windows: stop double-scaling a popup window's position
`CxOsOp::CreatePopupWindow` arrives with a position the caller has already put
in physical screen pixels -- it adds the parent window's physical origin to an
offset it scaled by the parent's per-monitor DPI -- and `new_popup` then
multiplied it by the DPI again. The popup landed at `dpi` times its intended
screen coordinates: exact at 100%, and progressively further away above it.
The size was scaled by the *system* DPI, which is the primary display's and
goes stale after a live scale change, so on a second display of a different
scale it was the wrong number twice over. It is now passed through unscaled,
because it is provisional either way: `init` runs `set_inner_size` immediately
afterwards, which scales by the window's own per-monitor DPI once the HWND
exists on its target display.
Note for reviewers: this is unreachable today. `WindowHandle::new_popup` is the
only producer of the op and nothing in the tree calls it, so there is no symptom
to reproduce -- which is also why the arithmetic was free to drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* x11: implement window resizing, and stop a protocol error killing the app
Two things the X11 backend did not do, both of which it silently pretended to.
`set_inner_size` and `set_outer_size` were empty function bodies, so
`CxOsOp::ResizeWindow` dispatched to nothing: an app calling `WindowHandle`'s
resize on X11 got no error, no log and no resize. They now call
`XResizeWindow`, clamped to the CARD16 range the protocol encodes an extent in,
since a zero is a BadValue. `set_outer_size` delegates rather than pretending:
the window manager owns the decoration frame, so a client can only ask for its
own extent.
Xlib's default error handler prints to stderr and calls `exit(1)`, and makepad
installed none, so a single rejected request killed the process outright -- a
bad geometry, a race against a window the WM has already destroyed, a missing
extension. Protocol errors are asynchronous and frequently not caused by the
code that happens to be running, so terminating is never the proportionate
response. A handler is now installed before the first request and logs the
error, request and minor codes plus the resource id.
`XResizeWindow` and `XSetErrorHandler` were not in the hand-written Xlib
bindings; both are added, along with the `XErrorHandler` callback type.
Untested: the author has no X11 machine. Both changes are small, local and fail
closed -- an unusable size is clamped rather than sent, and the handler only
turns an existing hard exit into a log line -- but neither has been run.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* opengl/wayland/metal: name three failures the backends currently swallow
None of these is device-loss recovery -- that stays a D3D11-only feature. They
are the cases where a backend already fails and says nothing useful, so a bug
report arrives as "the window went black" with no cause attached.
EGL: `eglMakeCurrent` and `eglSwapBuffers` failures were logged unlatched on
paths that run every frame, so a persistent failure emitted thousands of lines a
second. Both are latched once per outage and cleared on the next success, and
`EGL_CONTEXT_LOST` (0x300E) is now named explicitly, since that is EGL saying
the GPU reset and every GL object is dead -- which this backend cannot yet
recover from, and should at least say so. `OpenglCx::make_current` returned `()`
while discarding the result of a call that can fail; it returns `bool` now, so a
caller can skip GL work that would otherwise run with no current context and be
silently dropped.
Wayland: `wp_viewport.set_destination` raises the `bad_value` protocol error --
which disconnects the client -- on a zero or negative extent, and a float-to-int
cast turns both a negative and a NaN into zero. `WaylandWindow::new` clamps the
size for the EGL call but stores it unclamped into the geometry, so an app
created at a degenerate size reached that request. The two destination writes
are floored the way the EGL extent already is.
Wayland `CxOsOp::ResizeWindow` was an empty arm. A client has no "set my size"
request, but window geometry defaults to whatever the surface commits, and the
paint path derives the EGL extent and viewport destination from
`window_geom.inner_size` -- so writing it is the whole operation. Refused for a
maximized or fullscreen toplevel, which must keep the configured geometry or the
compositor raises `invalid_surface_state`. `RepositionWindow` stays a no-op and
now says why: `move` is interactive and serial-gated, and `reposition` is an
xdg_popup request needing a protocol version this backend does not bind.
Metal: a command buffer that ends in `MTLCommandBufferStatusError` produced no
pixels, and nothing noticed -- the completion handler runs either way, so the
in-flight queue drains and the hang watchdog stays quiet over a stale window.
The status is now checked in the handler that already exists and the error named,
capped at 32 reports.
Untested: none of these three backends can be run here. All three are
type-checked for their targets. Deliberately NOT written: anything for Vulkan
(vulkan.rs is not compiled by any target available here, so it cannot even be
type-checked) or the web backend (no JS runtime available to syntax-check the
shim), and any Metal device-loss latch or recovery, because `MTLCreateSystemDefaultDevice`
returns the non-removable SoC GPU and loss in the D3D11 sense does not occur there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* x11/wayland: cap a window extent, and refuse a resize that says nothing
Running the branch on Linux turned up four things it and #1197 let through.
A saved size is floored but never capped, and Wayland is the one backend with
no displays to fit against, so `{"inner_size":[1e9,1e9]}` reached
`eglCreateWindowSurface` and tripped its `assert!`. The app then died before it
could rewrite the state file that was killing it, so every later launch died the
same way -- the exact failure #1197 exists to prevent, one line further down.
`sanitize_window_geom` now caps as well as floors, and both Wayland EGL surface
creations fall back to the default size rather than taking the process with them.
`CxOsOp::ResizeWindow` had the same hole and no cap at all on Wayland:
`resize(100000, 100000)` went straight into the window geometry, which left the
surface EGL_BAD_SURFACE and corrupted the size the toplevel restores to -- a
maximize/restore round trip came back 1259x1259, a fullscreen one 100000x100000.
X11 had the opposite problem: it clamped a zero or a negative up to the CARD16
floor and produced a one-pixel window, silently, where Wayland refused the same
request and said why. Both now go through one `sanitize_resize`, so they answer
a bad request identically.
Last, the new X11 error handler is an `extern "C"` frame and logging panics on a
closed stdout -- `app | head` is enough -- so a protocol error aborted the
process instead of the `exit(1)` the handler exists to prevent. Reporting is
wrapped in `catch_unwind`.
Verified on Ubuntu 25.10 / GNOME on both backends: the 1e9 file now starts and
repairs itself, the resize matrix caps at 16384 with no EGL error and restores
correctly, and the two backends log the same refusal. 76 unit tests.
Still open, deliberately not fixed here: a Wayland self-resize dispatches no
WindowGeomChange. handle_platform_ops holds the Cx borrow for its whole loop, so
sending one needs a deferred-event path that does not exist yet.
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* windows: publish shader-cache entries atomically, and unwind a cancelled resize
Two ways a second instance, or an interrupted drag, leaves a window permanently
degraded. Both were found auditing the backend rather than reported, and both
are cheap.
The DXBC cache is a plain per-user directory that every makepad process on the
machine reads and writes, and entries were written straight to their final path.
`fs::write` truncates first, so a second instance starting at the same moment
could read the prefix of a shader the first was still writing -- the file exists
and the read succeeds, so nothing notices until `CreateVertexShader` is handed a
truncated blob. `shader_bytes_cached` even documented the read path as catching
this, which it did not. Entries are now written to a temporary file and renamed
into place, which is atomic, and a blob that is not a complete DXBC container
(the 32-byte header and its magic) is recompiled instead of used. The temporary
name carries the process id so two writers cannot collide on it either.
`is_in_resize` and the 8 ms resize timer are armed by WM_ENTERSIZEMOVE and were
disarmed only by WM_EXITSIZEMOVE. Win32 does not guarantee that pairing, and a
drag that ends without it leaves the timer forcing repaints forever and the
window presenting unpaced for the rest of its life -- it never returns to vsync.
WM_CANCELMODE now unwinds the loop, gated on having actually been in it, since
that message also arrives for menus and capture changes and unwinding a resize
that was not running would cost a needless ResizeBuffers each time.
Verified against a release build: two instances launched simultaneously on a
cold cache both come up correctly with no panics and no temporary files left
behind, and window resizing is unaffected.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* d3d11: validate a cached shader against the size its own header declares
Checking the magic and a 32-byte minimum accepts a blob that was truncated
after its header, which is exactly the shape a half-written cache entry takes.
The DXBC header carries the container's own total size at offset 24, so
comparing it to the length rejects those too.
Publishing entries atomically stops this backend from writing a partial one,
but the cache is a plain shared directory that older builds have already
written to, so the read path still has to be able to tell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* windows: stop double-scaling a popup window's position
`CxOsOp::CreatePopupWindow` arrives with a position the caller has already put
in physical screen pixels -- it adds the parent window's physical origin to an
offset it scaled by the parent's per-monitor DPI -- and `new_popup` then
multiplied it by the DPI again. The popup landed at `dpi` times its intended
screen coordinates: exact at 100%, and progressively further away above it.
The size was scaled by the *system* DPI, which is the primary display's and
goes stale after a live scale change, so on a second display of a different
scale it was the wrong number twice over. It is now passed through unscaled,
because it is provisional either way: `init` runs `set_inner_size` immediately
afterwards, which scales by the window's own per-monitor DPI once the HWND
exists on its target display.
Note for reviewers: this is unreachable today. `WindowHandle::new_popup` is the
only producer of the op and nothing in the tree calls it, so there is no symptom
to reproduce -- which is also why the arithmetic was free to drift.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Squashed from work; the fine-grained history is under tag archive/work-2026-08-29:
- mp* wave: mpwm window manager + the mp app family, WM API, theme bridge, PDF engine fix
- Modal claims no layout slot: the DJ page fills its window again
Squashed from work; the fine-grained history is under tag archive/work-2026-08-29:
- mp* wave: mpwm window manager + the mp app family, WM API, theme bridge, PDF engine fix
- mpwm polish wave: terminal key focus, focus-history close order, pop-back-to-origin, occupied-workspace cycling, demo
- work: land the sources the last commits reference
- route: the assistant icon the committed UI references
- fast_inflate: the benches name their dev-deps
- gif/weezl: drop the vendored benches nobody can run
- weezl: the decode tests generate their own LZW fixture
Squashed from work; the fine-grained history is under tag archive/work-2026-08-29:
- mp* wave: mpwm window manager + the mp app family, WM API, theme bridge, PDF engine fix
- mpwm polish wave: terminal key focus, focus-history close order, pop-back-to-origin, occupied-workspace cycling, demo
- mpwm: ghost-launch/menu-flash root cause, scroll z-gate, browser binds, path-free status, viewers delisted, Fab opens
- mpwm: Hyprland-exact close animation over a frozen snapshot
- mpwm: warm-instance pool, flat-luminance opens, flicker-free CEF resize
- work: land the sources the last commits reference
- mpwm: quick-look gap fixes; image cache eviction on preview unload
- tweaker: material thumbnails + vibecode popup + ctrl-space notes, undo/redo over the edit ledger, capture-semantics pi
- tweaker: tabbed side panel (Props/Shader/Tree) - shader tab with checkerboard material well + prompt, complete widget-
- mpwm+mpterm: text quick-look — pty teardown deadlock fix, in-place retarget verified, debug probes stripped
Squashed from work; the fine-grained history is under tag archive/work-2026-08-29:
- mp* wave: mpwm window manager + the mp app family, WM API, theme bridge, PDF engine fix
- mpwm polish wave: terminal key focus, focus-history close order, pop-back-to-origin, occupied-workspace cycling, demo
- work: land the sources the last commits reference
- vj: responsive DJ mixer + Windows drag-and-drop, cherry-picked from PR #1199 (vjroger)
- vj particles: image mode homes on a 16:9 picture plane
- vj: DREAM pipeline — expand -> flux1 still -> minimax i2v loop, with a canvas picker
- vj: the row shows the real prompt, a flux picker, XF default, and new clips stop vanishing
- a DREAM run is one declared graph the store runs without us
Squashed from work; the fine-grained history is under tag archive/work-2026-08-29:
- mp* wave: mpwm window manager + the mp app family, WM API, theme bridge, PDF engine fix
- work: land the sources the last commits reference
- kenney: catalogue all 50 free 3D kits; Modal dismissed() never fired
- asset store: central vision-annotation queue; Kenney donate prompt
- asset-ai: vision domain — image + prompt -> text on every fleet node
- annotation runs the fleet's vision services through the normal job queue
- vj: responsive DJ mixer + Windows drag-and-drop, cherry-picked from PR #1199 (vjroger)
- store search: WordNet synonym expansion, query-side; per-term seeks
- video scheduling: a cold model pin never downloads past a warm one, and a dedicated box only serves its role
- an expansion can never lose a run — and `expand: true` finally means something
- fleet panel: a slow box is not a missing box
- h3: first+last keyframe conditioning (the weights were always FL2VA)
- registry: the three H3 FL2VA tiers name their real conditioning
- fleet scheduling: spread before stacking, and a stuck job moves
- a download never steals a job from a box that has the weights
- the faster GPU takes the tie: 6000 > 5090 > 4090
- an evicted flux model gives the card its VRAM back
- a job now records what each of its stages was handed
- open a stage in RUNS and read what it sent
- every expanded song was an instrumental: the lyrics had nowhere to go
- the writer never overwrites words the person wrote
- store: a dependent job's body is spliced from its deps' results at claim
- a fleet box's job row says what that job was asked for
- the store runs a whole pipeline, and one record says how far it got
- a client can declare a whole run, watch it, and stop it
- 100% now means published, and the expander is a job you can queue
- one card says what a spawned task is doing, everywhere
- a run that ends says so, instead of being noticed later
- when every box holding the model is busy, buy another copy
Squashed from work; the fine-grained history is under tag archive/work-2026-08-29:
- mp* wave: mpwm window manager + the mp app family, WM API, theme bridge, PDF engine fix
- mpwm polish wave: terminal key focus, focus-history close order, pop-back-to-origin, occupied-workspace cycling, demo
- mpwm: warm-instance pool, flat-luminance opens, flicker-free CEF resize
- work: land the sources the last commits reference
- kenney: catalogue all 50 free 3D kits; Modal dismissed() never fired
- platform: windows check green again — SetWindowTextW binding
- map: exact warp-aware inverse projections — pointer ops work folded
- mpwm: quick-look gap fixes; image cache eviction on preview unload
- tweaker: material thumbnails + vibecode popup + ctrl-space notes, undo/redo over the edit ledger, capture-semantics pi
- tweaker: vibe popup card chrome + dispatch order, ctrl-space notes verified, sploded design v2 chapter
- tweaker: tabbed side panel (Props/Shader/Tree) - shader tab with checkerboard material well + prompt, complete widget-
- sploded v2: nesting-depth z, hairline scope frames, body pass
- sploded: pin the depth convention with a test, kill the draw_depth residue
- sploded: real body-pass split (scene-pass capture, panel flat) + y-convention source of truth with anti-flip gate test
- sploded: hollow outlines, flat-band input, ray-pick unprojection
- tweaker: shader tab defaults to the selection's first draw layer, stale hint trimmed
- sploded: outlines become clipped, antialiased strips; tighter deck
- sploded: merge the lane's v2 (nesting-depth z, clipped AA strip outlines, flat-band input, unproject, SplodedStack bod
- sploded: the exploded view is a LIVE view — pointer events route through the inverse explode transform (ray -> plane -
- tweaker: tabs are real widgets (uid, tree node under the dock, own plane in 3D) and pickable; navigation-class clicks
- sploded: pinned/hover outlines render on the widget's own plane in 3D — per-widget nesting depth lives on the platform
- tweaker: the material well renders the pinned widget's actual shader — the swatch byte-copies the widget's live draw c
- tweaker: the Shader tab shows the shader as written — the layer's pixel/vertex fn source (nearest definition up the co
- sploded: I = true isometric preset (yaw 45°, pitch atan(1/√2))
- tweaker: eyedropper — the colour popover's pick button arms a pixel probe; the next press in the app samples that devi
- tweaker: the shader loop closes — /tweak/apply resolves the pinned widget by uid (anonymous path segments never round-
- vj: responsive DJ mixer + Windows drag-and-drop, cherry-picked from PR #1199 (vjroger)
- tweaker: the material well is a magnifier — the mirrored instance draws at the widget's native size in the well's own
- tweaker: per-layer material thumbnails — the Widget derive emits WidgetNode::layer_areas() (every #[live] Draw… field
- tweaker: the shader source view is the real CodeView (syntax highlighting, selection, editing) when the app registers
- tweaker: Ctrl+Enter sends on every platform (TextInput treated only Cmd as primary on macOS, so Ctrl+Enter inserted a
- Modal claims no layout slot: the DJ page fills its window again
- tweaker: every fn apply recompiles (eval_chunk ran every chunk under ONE synthetic callsite, so the script body — and
- tweaker: an apply whose draw shader fails to compile is rejected — the layer goes back (last live fns / the fn as writ
- tweaker: the Shader tab's source view owns its scrolling (the ScrollYView around the CodeView double-scrolled the care
- widgets: set_visible belongs to every widget, not just View (#1194)
- script: a dead heap's resource handles must not outlive it (#1195)
- Resources: search the executable's directory, not only the working directory (#1196)
- Windows: fit a restored window to the displays that are actually attached (#1197)
- d3d11: a failing GPU call reports the loss instead of killing the process (#1198)
Co-authored-by: Kevin Boos <1139460+kevinaboos@users.noreply.github.com>
* d3d11: a failing GPU call reports the loss instead of killing the process
The backend already notices a removed device when `Present` returns
DXGI_ERROR_DEVICE_REMOVED, but a device rarely dies at a moment as convenient
as a present. It dies between frames, and the next thing that touches it is a
resource creation or a buffer map -- of which this file had 76 unwrapped, plus
three `std::process::exit(1)`. So the usual outcome of a GPU driver reset,
a TDR or a hybrid-GPU transition across suspend/resume was a panic, and the
graceful path was unreachable.
Route the calls a dead device actually reaches through `D3d11Cx::note_error`,
which asks `GetDeviceRemovedReason` rather than pattern-matching the HRESULT the
failing call happened to return -- creation calls do not reliably return the two
DXGI device-lost codes, while the device itself always knows and keeps saying
so. That answer sets a process-wide `device_lost` latch and logs once.
The softened sites are the ones a dead device lands on first: draw-list and pass
uniform buffers, which upload every frame with no dirty gate; texture and render
target creation; and shader object creation, whose `CxOsDrawShader::new` already
returned `Option` with both callers handling `None`.
Three latent bugs fall out of auditing them, each of which loses content
permanently rather than noisily:
- `update_vec_texture` consumed the dirty flag with `take_updated()` and then
returned early when the pixel buffer was out on loan, so a texture that hit
that window was never uploaded again. For the glyph atlas that means all
text disappears for the life of the process. The Metal backend already
guards this; D3D11 did not.
- `hlsl_compile_shaders` drains `compile_set` destructively, so a shader whose
object creation failed was dropped from the queue forever and everything
drawn with it silently stopped rendering. Failed creations go back in the
queue.
- `render_view` unwrapped the geometry index buffer but passed the vertex
buffer through as an `Option`, so a missing one bound null and drew nothing
with no error anywhere. Both are now checked together, and a draw call with
either missing is skipped under a `debug_assert!` that it only happens on a
lost device.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit cc980805b5774253e592e183f577c5ba08ce85db)
* d3d11: rebuild the device and every GPU resource after a device loss
Detection landed already: present() sets a per-window device_lost and the paint
loop stops re-dirtying the pass. Nothing rebuilt anything, so the window stayed
frozen until the app was restarted.
Recovery runs at the top of win32_event_callback, the one place holding both
&mut D3d11Cx and &mut Vec<D3d11Window> while nothing is mid-render. It releases
each window's swap chain, back buffer, view and paint-beat registration (DXGI
allows one flip-model chain per HWND, so the dead one must be gone first),
recreates the device tier when the device really is gone, drops every GPU handle
the Cx holds, and rebuilds each swap chain against the same HWND.
Clearing handles is only half of a sweep. Geometry and instance uploads are
gated on dirty flags cleared unconditionally once the upload runs, and textures
on a dirty rect consumed by take_updated, so every gate is re-armed or the empty
slot is never refilled. The CPU-side sources all survive: texture pixels live in
TextureFormat::Vec*, geometry in CxGeometry, and shaders keep their compiled
DXBC plus the on-disk cache, so recovery recreates shader objects without
compiling any HLSL.
Retries are spaced 250ms to 4s and driven by the existing signal heartbeat
rather than a new timer, because the GPU can stay absent for a long time. While
lost, the loop is forced to Wait at both EventFlow decisions -- every condition
that would otherwise choose Poll is unsatisfiable when nothing can paint, so it
would spin for the whole outage -- and pending screenshot requests are failed,
both to release their requester and because a non-empty queue is itself one of
those conditions.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit d603d3e7996cd71b5da6ed3372c9dc75eebb4a77)
* d3d11: bind the hot-path buffers by reference, not by clone
The device-loss bails introduced an AddRef/Release pair per uniform-buffer
upload and per draw call, on paths that run for every draw list, pass and
geometry every frame. Borrowing reads the same Option without touching the
refcount; only IASetVertexBuffers genuinely needs an owned Option, which is what
the code built before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit ecfab9a9355d50754a7117de8303d66f8919b2ee)
* d3d11: make the device-loss recovery actually work, and add a way to exercise it
Three defects, each of which stopped recovery dead, and none of which is visible
without running it. `MAKEPAD_D3D11_TEST_DEVICE_LOSS=<seconds>` forces a full
device recreation on a timer so the path can be exercised without a driver
reset; a real removal cannot be provoked from inside the process. It is a
stronger test than merely setting the latch, because the device really is
replaced, so any GPU object the sweep fails to rebuild still belongs to the old
device and cannot render against the new one.
- The sweep called `set_updated` on every texture, which panics for anything
that is not a `Vec*` format. The first render target it reached took the app
down. Only vec textures carry a dirty rect; render targets, depth buffers
and shared textures have no CPU-side contents and get their alloc record
cleared instead.
- Every rebuilt swap chain failed with `E_ACCESSDENIED`. DXGI allows one
flip-model swap chain per HWND at a time and D3D11 destroys lazily, so the
immediate context's own reference to the back-buffer view kept the old chain
-- and its claim on the window -- alive after the application had dropped
every handle it held. `ClearState` + `Flush` once, after all the windows have
released and before any rebuild.
- The post-recovery redraw marked every pass slot dirty, including ones
nothing had drawn into, and `draw_pass_to_texture` unwraps
`main_draw_list_id` immediately. Only passes that have one are marked.
Verified against a release build: six consecutive forced device recreations,
no panics, the UI rendering correctly after each (text included, so the glyph
atlas re-uploads from its retained pixels), `ResizeBuffers` working on a rebuilt
chain, handle count flat across recoveries, and the loop idle afterwards.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit bb7d1c019612a4fb2668640f47b8e16c3886e1cf)
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
An app that persists its window geometry has to restore it into a display
arrangement that may have changed completely since it was saved: the display
the window sat on can be gone, a docked laptop can be back on its built-in
panel, and the file can hold values no display ever had. Nothing validated any
of it -- `configure_window` stored the numbers and each backend passed them
straight to `CreateWindowExW` / `initWithContentRect:` / `XCreateWindow` -- so a
window could come back off-screen or too small to grab, with no way back except
deleting the state file.
Windows also manufactured those values. Win32 reports a minimized window at
`(-32000, -32000)` with a zero-sized client rect, `WM_SIZE` published that as
the window's authoritative geometry, and an app saving on shutdown wrote it
down. `WM_MOVE` meanwhile published nothing, so a window that was dragged but
not resized persisted its pre-drag position (X11's `ConfigureNotify` and macOS's
`windowDidMove:` both already published).
Add `platform/src/screen.rs`, holding the policy in one place:
- `sanitize_window_geom` needs no display knowledge and every backend reaches
it through `CxWindow::create_geom`. It is what protects the backends a fit
cannot help: Wayland enumerates no displays for a client and hands the size
to `wl_egl_window_create`, which rejects a non-positive one -- a persisted
`0` or `NaN` panicked the app at startup -- and X11 encodes extents as
unsigned 16-bit and answers a zero with a protocol error that, with no error
handler installed, terminates the process.
- `clamp_point_to_screens` pins the origin BEFORE the window is created. The
fit alone is too late: `set_inner_size` runs in between and works relative
to wherever the window landed.
- `fit_window_rect_to_screens` corrects the finished rectangle. A window
already wholly on the desktop is returned untouched, including one
deliberately spanning two adjacent displays; anything else moves to the
display it overlaps most, or nearest by centre, capped to that work area.
Displays come from `EnumDisplayMonitors` + `GetMonitorInfoW` on Windows (both
absent from the vendored bindings, so linked here), `NSScreen.screens` on macOS,
and the root geometry plus EWMH `_NET_WORKAREA` on X11. Wayland is unaffected by
the class of bug: a client there cannot know or choose where its windows go.
Report a minimized window from `GetWindowPlacement().rcNormalPosition`,
converted out of workspace coordinates, so what an app persists is the geometry
the window actually returns to; skip publishing on `SIZE_MINIMIZED`; publish on
`WM_MOVE`, deferred to `WM_EXITSIZEMOVE` during a user drag because the Cx
handler redraws on every geometry event.
Positions are now documented and implemented as physical screen pixels on
Windows and X11 (points on macOS) and are never DPI-scaled, matching
`get_position`, `create_position` and the platform calls. `set_position` alone
had been scaling its argument, so `set_position(get_position())` moved a window
to twice its coordinates on a 200% display.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
A packaged desktop build addresses its resources through a relative package root
-- `cargo packager` and `robius-packaging-commands` both use `.`, with the
resource trees sitting beside the executable -- and a relative `File::open`
resolves against the process working directory. Any launcher that sets no
working directory therefore starts the app somewhere unrelated and every font,
icon and image open fails: a URL-protocol handler (`HKCR\<scheme>\shell\open\
command` carries no working directory), a file association, a service, a
shortcut created without one.
The result is not a clean failure. The window comes up and lays out correctly,
shader-drawn shapes and buttons render, and network-loaded images appear, but
every glyph and every bundled icon is missing, because those are the parts that
need a file. It reads as a renderer bug rather than a missing directory.
macOS avoids this through `apple_bundle_load_dependencies`, and a Linux `deb`
package uses an absolute `/usr/lib/<name>`, so Windows is the only desktop
target whose resource lookup depends on where it was started from.
Retry a failed open against the directory holding the executable. This is
purely additive -- a path that resolves today resolves identically, and only an
open that would have failed reaches the fallback -- so a dev build's
workspace-relative dependency paths keep working unchanged.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* widgets: set_visible belongs to every widget, not just View
`ui.value_lg.set_visible(w >= 150)` logged "widget method set_visible not
found for uid WidgetUid(1236)" once per resize, and the widget simply never
reflowed. Label, Button and every other leaf refused a method that View
alone implemented — even though visibility is a Widget-trait property that
`#[visible]` derives for exactly those widgets.
it went unnoticed because the fixed-slot list pattern wraps its rows in
Views. what it breaks is the widget tiles, where a bare Label is toggled by
an on_widget_resize, and the failure reads as a layout that just doesn't
respond to its size.
handled once now, in WidgetRef::script_call, after the widget's own
script_call declines the method — so set_visible (and a visible() getter)
work on anything, and View's copy is gone. a bad argument still keeps the
current visibility and returns an error instead of guessing true.
* script: a dead heap's resource handles must not outlive it
the launcher died about one run in five, always inside the GC and never
anywhere near what caused it:
gc.rs:300: index out of bounds: the len is 21 but the index is 22
only after an isolate had been torn down and another started — closing a
widget preview, or granting `network` (alloc-time, so the app's isolates
restart).
CxScriptResources caches (heap_key, abs_path) -> that heap's LOCAL handle,
and a heap_key is an allocation ADDRESS. the only cleanup was
CxScriptResourceGc, which runs when the owning heap's own GC sweeps that
handle — and a heap that is dropped wholesale, as a Splash isolate's is,
never sweeps anything. so the entries outlived the heap, and the next
isolate whose root_objects landed on that freed address asked for the same
font and was handed the dead heap's handle index. it stored it in its own
FontMember{res, asc, desc}, where 22 means nothing in a table of 21 —
and nothing noticed until that heap's next collection walked the font
object it had every right to walk.
gc_heaps() drops a dead heap's entries, and detaches handles no surviving
heap still maps to (handle values are heap-local, so two heaps' handles can
be equal). called from gc_dead_splash_isolates beside the storage and
bridge purges, which already runs before a new isolate can allocate.
anything keyed by heap_key needs to be in that function, for this reason.
the same hunt turned up a second crossing, fixed here too:
View::script_call(render) built its `me` object in whatever VM happened to
be calling, protoed off the SOURCE view's heap, and forwarded the caller's
args object into the target VM. render a view whose isolate has since been
torn down and that object stays behind in the CALLER's heap holding a dead
heap's index. it refuses now when the two heaps differ.
`ui.value_lg.set_visible(w >= 150)` logged "widget method set_visible not
found for uid WidgetUid(1236)" once per resize, and the widget simply never
reflowed. Label, Button and every other leaf refused a method that View
alone implemented — even though visibility is a Widget-trait property that
`#[visible]` derives for exactly those widgets.
it went unnoticed because the fixed-slot list pattern wraps its rows in
Views. what it breaks is the widget tiles, where a bare Label is toggled by
an on_widget_resize, and the failure reads as a layout that just doesn't
respond to its size.
handled once now, in WidgetRef::script_call, after the widget's own
script_call declines the method — so set_visible (and a visible() getter)
work on anything, and View's copy is gone. a bad argument still keeps the
current visibility and returns an error instead of guessing true.
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: the thumbnail pipeline becomes one honest machine, and effects go livecodable
- widgets: boxed labels center on their ink, not on the font's line box
- metal: a fresh texture forgets nothing it never had — reallocated vec textures upload whole
- fab: a 3D creation shell and the viewer built on it
- texcomp: the block codec and the container every texture will travel in
- mixer: a live LR-Mix surface for the XR18 — strips paired the way the desk is run, auto-connect, EQ that bends its own curve, and a sweep paced so the console drops nothing
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: the thumbnail pipeline becomes one honest machine, and effects go livecodable
- asset-ai: the chat tells the truth while it works
- audio: a FLAC decoder from the specification, beside the MP3 and Vorbis ones
- asset-ui: the classic import surface follows what the importer now produces
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- mixer: a live LR-Mix surface for the XR18 — strips paired the way the desk is run, auto-connect, EQ that bends its own curve, and a sweep paced so the console drops nothing
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: the music explorer gets IMPORT, and catalog controls fold away in local mode
- vjfx: three lanes land — engine hooks + hold stage, the videomesh engine, and the audio picture
- vj: the thumbnail pipeline becomes one honest machine, and effects go livecodable
- vj: thumbnails become mp4 — hardware-coded sheets at measured-4K cells, and the bake stops racing the GPU
- store: the ceremony dies — batch publish, one transaction, and the engine stops re-reading its own log
- store: the ceremony dies — batch publish, one transaction, and the engine stops re-reading its own log
- vj: the console grows real transports, and the deck stops lying about reverse
- vj: reverse earns a memory, and the effects stop aging
- vj: video goes NV12 end to end, and the GPU does the unpacking
- vj: windows hands the main thread one megabyte, and the script tree wants two
- vj: the GPU learns to see motion — realtime frame tweening on every deck
- vj: the tweener learns — RIFE runs on the Mac and feeds the warp
- vj: the classical tweener grows up, and every deck gets a tween chip
- models: an interrupted install can never load broken
- vj: the transport becomes a platter — velocity in, position out, one map
- vj: the tween presenter reads the platter — one clock per deck, cued once at the frame on screen
- vj: the presenter switch lands without its scaffolding
- vj: the next pair's fields are fetched ahead of the change under the capacity law — a pair change costs an ordinary beat; macos: the layer's own display link paces the frame when the system offers it, the old path stays as fallback
- vj: AI3 subdivides adaptively — one, three or seven neural frames per pair, chosen from measured synth time against the pair's own period, with classical flow between them and a 7-3-1-FL fallback; the deck shows the depth
- vj: local store, lyrics and model plumbing, and the frame-interpolator's device parity check
- vj: DJ-tab scroll knobs, title-click reset, crossfade fix, stem headroom — plus the pre-Ampere CUDA fix (#1193)
- vj: the deck explorer lists music-tagged audio, the sfx surface filters by tag not category, the track list fills the window, and the modal host has no layout footprint
Co-authored-by: Rogier de Leeuw <vjroger@gmail.com>
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: thumbnails become mp4 — hardware-coded sheets at measured-4K cells, and the bake stops racing the GPU
- vj: the console grows real transports, and the deck stops lying about reverse
- vj: reverse earns a memory, and the effects stop aging
- vj: video goes NV12 end to end, and the GPU does the unpacking
- vj: the GPU learns to see motion — realtime frame tweening on every deck
- vj: the tweener learns — RIFE runs on the Mac and feeds the warp
- vj: the classical tweener grows up, and every deck gets a tween chip
- vj: the tween clock tells presented time, not producer time
- vj: the transport becomes a platter — velocity in, position out, one map
- vj: the tween presenter reads the platter — one clock per deck, cued once at the frame on screen
- vj: the OFF tier joins the platter — a resident clip's picture is cache[nearest(pos)]
- vj: the media thread loses its second clock — resident clips park the decoder
- vj: the producer gets a contract — keyed ladders, deadlines, and a capacity law
- vj: two decks, one law — identical inputs are bit-identical, and the warp agrees to the byte
- vj: the presenter switch lands without its scaffolding
- vj: the next pair's fields are fetched ahead of the change under the capacity law — a pair change costs an ordinary beat; macos: the layer's own display link paces the frame when the system offers it, the old path stays as fallback
- vj: AI3 subdivides adaptively — one, three or seven neural frames per pair, chosen from measured synth time against the pair's own period, with classical flow between them and a 7-3-1-FL fallback; the deck shows the depth
- video_flow: the flow debug bins, declared behind the convert feature so --no-default-features skips them instead of failing
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vjfx: 104 new presets — the transition lane fills out and the screen family goes wide
- vjfx: three lanes land — engine hooks + hold stage, the videomesh engine, and the audio picture
- vj: the thumbnail pipeline becomes one honest machine, and effects go livecodable
- vj: thumbnails become mp4 — hardware-coded sheets at measured-4K cells, and the bake stops racing the GPU
- store: the ceremony dies — batch publish, one transaction, and the engine stops re-reading its own log
- store: the ceremony dies — batch publish, one transaction, and the engine stops re-reading its own log
- vj: the console grows real transports, and the deck stops lying about reverse
- vj: reverse earns a memory, and the effects stop aging
- fab: a 3D creation shell and the viewer built on it
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- fab: a 3D creation shell and the viewer built on it
- raytrace: the traced pane starts coarse and doubles to native, with the raster underneath
- fab: a colour picker, a material's textures, and dials that move the scene while they drag
- texcomp: the block codec and the container every texture will travel in
- fab: FAB_PROBE_MAT — per-material triangle counts, texture presence and uv spread in the roof probe
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- fab: a 3D creation shell and the viewer built on it
- raytrace: the traced pane starts coarse and doubles to native, with the raster underneath
- texcomp: the block codec and the container every texture will travel in
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- texcomp: the block codec and the container every texture will travel in
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- render+sim: the two hooks the model viewer already relies on
- viewport: Realtime gets the engine's cascaded shadow pass, the NOAA sun, metered exposure with sky ambient, a haze knob, a time-of-day slider in the header, and no grid — the building itself still waits for its casters and direct light
- render: the engine's receiver shader takes the sun with two-sided normals and the shadow term for the model batches
- fab: a 3D creation shell and the viewer built on it
- render: two shaders that never compiled — a let is not assignable, a var is
- sim: a declared map facing becomes a body's heading through one rule
- render: a HUD that already reads as a game's before anyone styles it
- asset+sim: the two modules their own commits already declared
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- audio: a FLAC decoder from the specification, beside the MP3 and Vorbis ones
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: the tweener learns — RIFE runs on the Mac and feeds the warp
- asset-ai: the chat tells the truth while it works
- llm: a cold turn on the solo slot takes the session-native speculative path — think-mode turns no longer re-ingest the whole conversation through the draft head (66 → 122 tok/s on the four-lane box)
- llm: the step cost model is chosen per device — the RTX PRO 6000's measured verify curve (13.7 + 3.17·B ms) beside the 5090's; the bench warms every tail shape and times two windows
- vj: local store, lyrics and model plumbing, and the frame-interpolator's device parity check
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: the thumbnail pipeline becomes one honest machine, and effects go livecodable
- repo: context_ladder scratch bin stays local, not shipped
- vj: thumbnails become mp4 — hardware-coded sheets at measured-4K cells, and the bake stops racing the GPU
- store: the ceremony dies — batch publish, one transaction, and the engine stops re-reading its own log
- vj: the console grows real transports, and the deck stops lying about reverse
- models: an interrupted install can never load broken
- importer: the classic worlds stop being mirror images
- asset-ai: the chat tells the truth while it works
- sqlite: derived tables get their real names, their predicates, and all their arms
- llm: the step cost model is chosen per device — the RTX PRO 6000's measured verify curve (13.7 + 3.17·B ms) beside the 5090's; the bench warms every tail shape and times two windows
- importer: a sound and a single-tile sprite publish a picture like everything else
- asset: hardware sha256 kernels, proved against the software oracle before they run
- sim: a declared map facing becomes a body's heading through one rule
- asset: the batch publish route, with the hostile cases it has to refuse
- asset: an example that asks a live store which assets carry a thumbnail
- asset+sim: the two modules their own commits already declared
- asset: ActorDef::scaled — every linear quantity follows the map's person height — plus the place-dump and retire-stale store examples, and the game chat context stops reporting work it did not do
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- store: the ceremony dies — batch publish, one transaction, and the engine stops re-reading its own log
- sqlite: derived tables get their real names, their predicates, and all their arms
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: the console grows real transports, and the deck stops lying about reverse
- vj: video goes NV12 end to end, and the GPU does the unpacking
- widgets: boxed labels center on their ink, not on the font's line box
- metal: a fresh texture forgets nothing it never had — reallocated vec textures upload whole
- fab: a 3D creation shell and the viewer built on it
- widgets: a dock redraws its panels, not just its own frame
- draw: hsv2rgb takes vector bounds, so it compiles
- texcomp: the block codec and the container every texture will travel in
- widgets: a Splash keeps the walk its host declared across a body rebuild, and logs a body that fails to evaluate instead of drawing nothing
- vj: DJ-tab scroll knobs, title-click reset, crossfade fix, stem headroom — plus the pre-Ampere CUDA fix (#1193)
Co-authored-by: Rogier de Leeuw <vjroger@gmail.com>
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: video goes NV12 end to end, and the GPU does the unpacking
- script: parser, with the case that caught it in the test suite
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: thumbnails become mp4 — hardware-coded sheets at measured-4K cells, and the bake stops racing the GPU
- windows: the paint beat becomes the swapchain's own beat, and /g learns to see
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: thumbnails become mp4 — hardware-coded sheets at measured-4K cells, and the bake stops racing the GPU
- vj: the console grows real transports, and the deck stops lying about reverse
- vj: the GPU learns to see motion — realtime frame tweening on every deck
- macos: the paint beat becomes the display's own beat
- windows: the paint beat becomes the swapchain's own beat, and /g learns to see
- metal: vec textures ride the command stream — the CPU stops overwriting what the GPU is still reading
- metal: instance buffers stop being rewritten under a live draw
- macos: next frames and draws are stamped with the flip they aim at
- metal: a fresh texture forgets nothing it never had — reallocated vec textures upload whole
- metal: a watchdog for stalled command buffers — it names the pass, and only aborts when asked
- macos: resetCursorRects no longer aborts the app when AppKit re-enters it
- macos: nothing panics across resetCursorRects — the callback is shielded and its cursors are retained
- vj: the next pair's fields are fetched ahead of the change under the capacity law — a pair change costs an ordinary beat; macos: the layer's own display link paces the frame when the system offers it, the old path stays as fallback
- macos: a drawable from the layer's display link is presented plainly — presenting it at a time is forbidden and raised in every visible window
- macos: a window paced by the layer's display link never asks the layer for a drawable — the beat waits for the link's update; ObjC exceptions are logged with their reason before they unwind
- macos: the layer's display link is opt-in (MAKEPAD_METAL_DISPLAY_LINK=1) until it paces at the display's rate — 11 fps visible against 62 on the proven path
- macos: the layer's display link asks for the screen's maximum rate, consumes every drawable it hands out, and traces updates/consumed/presented per second — still 75 ms per present under a drag, so it stays opt-in
- fab: a 3D creation shell and the viewer built on it
- raytrace: the traced pane starts coarse and doubles to native, with the raster underneath
- macos: a link-paced beat blocks till the next flip, and an armed paint clock means wait — the main thread no longer polls at 100% CPU between frames
Squashed from work; the fine-grained history is under tag archive/work-2026-08-26:
- vj: thumbnails become mp4 — hardware-coded sheets at measured-4K cells, and the bake stops racing the GPU
- vj: reverse earns a memory, and the effects stop aging
- vj: video goes NV12 end to end, and the GPU does the unpacking
- windows: the paint beat becomes the swapchain's own beat, and /g learns to see
- platform: the time repaint stops resurrecting passes their owner left behind
- metal: a fresh texture forgets nothing it never had — reallocated vec textures upload whole
- fab: a 3D creation shell and the viewer built on it
- raytrace: the traced pane starts coarse and doubles to native, with the raster underneath
A Turing box (RTX 2080 Ti, sm_75) lost ALL of CUDA because two kernel
files refused to compile for it, and one failed kernel build means the
stub store — surfaced in the VJ as "stems: model error: no compiled-graph
device" on the DJ tab.
diffusion_ops.cu used three sm_80-only pieces unguarded: bf16 wmma
fragments (the type itself is incomplete before Ampere), cp.async, and
the m16n8k16 mma shapes. The cp.async helpers now fall back to
synchronous copies below sm_80 — the f16 wmma flash/sdpa kernels lose
their prefetch overlap on Turing, not their contents — while the bf16
and FA2 kernels are compiled out and their launchers refuse pre-sm_80
devices with cudaErrorNotSupported instead of returning a buffer the
kernel never wrote.
fattn/common.cuh made mkllm_unused_vars constexpr: the no-cp.async
branch of ggml_cuda_fattn_mma_get_nstages calls it, and a non-constexpr
callee poisoned the constexpr config chain on exactly the pre-Ampere
device pass — the arch nobody had compiled for.
Stems verified on the 2080 Ti: stems-ops-check all green (SNR 137-147 dB
against the CPU reference), two tracks separated end to end, output
confirmed clean by ear.
Co-authored-by: vjroger <r.deleeuw@qogni.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
`current_view` only advances once a push or pop transition finishes, so
mid-transition it still reports the outgoing view. Callers that want to
address the view the app considers current, e.g. to retitle it, had to
either duplicate the id themselves or special-case `is_transitioning`.
* Add `destination_view()`, which reports the incoming view as soon as a
transition starts and falls back to `current_view` when settled.
- INSTALL MODELS under the music explorer: a download dialog naming both
MIT weight sets (BS-RoFormer splitter 527MB, whisper large-v3-turbo
1.6GB), where they land and their licenses; resumable sha256-pinned
downloads through the asset-ai downloader (featureless dep — the same
slice the asset UI links); cancel mid-flight (the button flips to
CANCEL, .part resumes later), MB progress, and the row disappears on a
provisioned machine. When the last model lands the loaded decks
separate immediately: the stems worker now re-probes the checkpoint
per job instead of latching its absence, and the lyrics transcriber
unlatches too (the Apple fallback yields to whisper mid-session).
- DrawWaveLane's stem palette moves from instance inputs to uniforms:
36 vertex inputs blew D3D11's vs_5_0 limit of 32 (error X4506), which
left the music decks with NO waveform at all on Windows.
- --remote HOST:PORT binds a named interface so another machine can
drive an app over the LAN (fleet-box testing); bare --remote stays
loopback.
- queue chip: the + glyph centres in its 26x18 box.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The VJ's EFFECT surface is a small renderer of its own. An effect is a
`.splash` DOCUMENT — engine choice, stages, fields, parameters and now the
SHADERS THEMSELVES live in the document rather than in Rust. That is the
shape this commit introduces (dev has never seen an intermediate one): a
document is the whole effect, and the Rust side is the engine families that
documents draw with.
The engine families: particles and emitters, GPU sim swarms and fluid, static
meshes (firefly synchrony, harmonograph loom, domino liturgy), tiles, flock,
clouds, city, pipes, stock charts, an SDF raymarcher with a subclassable
`scene_sdf`, and a mountain-jet endless range with a beat-pulsed fighter.
Three things make them a system rather than a demo reel:
- SIM FIELDS — a float simulation-texture primitive, so an engine can carry
GPU state across frames (wind, particles, fluid) instead of being a pure
function of the clock.
- CONTENT COUPLING — `content:` in a document and `input0` on every engine's
tex0, so an effect can consume the program video: drapes, backdrops,
mirrors, billboards, chrome, frescoes, canopy, glass, silk, wall, swarm,
pen and mosaic families all take the picture playing behind them.
- MUSIC BINDING — shaders read the beat, so the whole library moves with the
track rather than on its own clock.
Roughly a hundred seeded preset documents ship with it, each with a lazily
rendered animated thumbnail (rendered 4x and box-downscaled, 16-tap SSAA);
the thumbnail pass went from four minutes to eleven seconds. CONTRACT.md is
the document contract and its verify recipe; IDEAS.md is the campaign tracker.
A VJ console that is its own asset store: five surfaces (VIDEO program tiles,
MUSIC DJ decks, SFX, EFFECT slots, LIGHTS) over the asset server, driven by
APC40 hardware or the screen.
The transport is the centre of it. The SWEEP LAW: one direction, one beat
step, and a pacer that never hiccups — a loop wrap is never fast-forwarded and
the grid keeps no holes. On top of that: bracket-to-bracket scrub math so the
playhead learns the user's trim, SCRATCH as a sprung shuttle on every deck
transport, ping-pong and per-slot mute, hot-standby decks, one rate authority,
and beat sync that fits a whole clip into N bars at a musical rate while
cached loops keep real wrapping pts so the phase survives.
VARIABLE FRAMERATE VIDEO IMPORT measures the flow field a clip needs without a
model (libs/video_flow), which is what makes free-rate bounce-looping GPU
playback possible; the enhance pipe uprezes and tweens a deck clip in one
decode and one encode, with the motion vectors inside the mp4.
The console itself: makepad orange and the brand lockup, Blender-style BPM,
system tooltips, popover sliders, bracket trim handles, one button family and
a no-push band, doc-labeled slot dials with MIDI learn and strict typing, a
BEATS dropdown and slow-biased jog, an IMPORT panel that arms, runs and stops,
and BLAST — one press invents a visual for every parallel pipe. Generation
runs six jobs in flight across the fleet, and a job row names what it made.
The library grid fills as a relay — page, detail, manifest, blob, decode,
texture — and every hand-off used to be picked up by the 20Hz poll timer, so
each hop cost a tick however fast the store answered. The hand-offs now run on
the frame while anything is owed, manifests jump the queue like the details
that produced them, and resolve/decode/worker width all widen while nobody is
on stage and narrow together the moment the program window opens or a deck
plays. The visible page resolves first. Measured on the same warm cache: a
48-tile page 4.7s -> 1.9s, a tab's first visit 4.3s -> 1.1s, decode wait 48ms
-> 9ms, a second visit painted in the same frame with zero decodes, and no
UI hitches across the session.
Also here: wave analysis (tempo, grid, downbeat, tiles) with a judge suite
that scores the detector against click tracks and a human drummer, stem
separation, karaoke lyric display, Art-Net/DMX light control, and an output
window that projects without janking on Windows mouse movement.
466 tests, zero warnings.
- README: a build quick-start for macOS and Windows, the honest Linux
story, what CUDA is for and how to install the separation model.
- AGENTS.md: the `--remote` control surface protocol, so the harness it
documents is usable without reading platform/src/remote.rs.
- Cargo.toml: workspace membership for the crates this series adds and
removes.
- tools/: the Windows box scripts (wincmd, winps and friends, winrun) and
remote_smoke.sh — how a build gets driven on a remote machine.
- apps/asset-server: the standalone server binary and its README.
- Small follow-ups in libs/{windows,apple_sys,makepad_test,mbtile_reader,
converse} and apps/route, plus .gitignore and makepad.splash.
Four new examples, each of which is also the test surface for the thing it
demonstrates:
- datagrid — the DataGrid widget across five tabs (sheets with a formula
engine, big data, charts, pixels, widgets).
- portallist_hit — PortalList hit-testing, with a UI test suite.
- render_to_texture — the offscreen pass, with a UI test suite.
- splat_bench — Gaussian splat sorting and drawing under load.
uizoo gains a dropdown tab; the splash example's UI test follows the splash
host-services change.
The asset browser becomes the front end for everything the AI stack can do:
- analysis — a content analysis pass over imported assets.
- mask_paint — paint a mask over an image for inpainting.
- music_page — the music generation surface.
- webcam — live capture as a generation input.
- fast_presets, store_content — preset and store-content plumbing.
Around them the existing surfaces get the corrections the store and the video
widget forced: sprite-split and child-pass thumbnail fixes, one video widget
everywhere (opens stop fighting each other and the rail never reflows),
grouped classic/Duke import that lands 102 assets instead of a card flood,
chat that greets with silence rather than a banner, and a pipeline that
survives restarts.
libs/render picks up the two biggest new modules in the group: `level.rs`
(the imported-world runtime) and `player_nav.rs` (walkable-surface planning
with clearance bands). The renderer, shader set, GPU lightmap and skinning
paths all grow with them — a chart-edge texel is no longer trusted with a
skirt's light, a lamp only receives what the sky is not already delivering,
and lamp photometry comes from the fixture rather than the mesh scale. A
`walk_probe` example drives the navigation directly.
xr gets splat packing and a GPU splat sort (`splat_pack.rs`, `splat_sort.rs`,
with tests), and view_splat is largely rewritten on top of them.
remesh, xatlas, gltf, splat and sim carry the supporting work: the xatlas
unwrap hang is fixed and the pass is several times faster, the glTF writer
emits the rig and vehicle contracts, and sim grows the entity layer the
imported worlds drive.
Another dependency the app should not be asking the platform for:
- audio_decode — MP3 (layer 3, LSF tables, synthesis) and Ogg Vorbis
(codebooks, floor, residue, MDCT) decoders, with tag reading. Both are
checked against oracle fixtures rather than against our own expectations.
- audio_encode — an Ogg Vorbis encoder: MDCT, psychoacoustics, floor and
Huffman coding, setup tables, plus `oggenc` and `audiobench` binaries.
- audio_picture — waveform and spectrogram rendering, and compositing.
- audio_lyrics — word-level lyric alignment (DTW plus a DP snap) and the
baked schema behind karaoke timing.
- audio_sidechannels — the side-channel plumbing between them.
libs/voice grows a CUDA backend and an alignment path beside its CPU decoder,
with a `whisper_parity` binary to keep the two honest.
The model code was spread across eight crates that had grown into each other:
ggml and cuda and mlx each owned part of a tensor runtime, llama and tts and
voice2 each owned part of a model, and libs/diffusion owned everything else.
They are now one tree with an explicit shape:
libs/ai/cuda — kernels and launch surface
libs/ai/metal — Metal shaders and the shim
libs/ai/llm — the language-model runtime (sessions, lanes, contexts,
the CUDA and Metal executors, the compiled Metal path)
libs/ai/models/ — common, flux, h3, music, paint, speech, stems, vision
libs/diffusion is not deleted but demoted: what remains is the VALIDATOR
crate — several dozen `*_validate.rs` oracles that check a native
implementation against a reference, which is where they belong now that the
implementations live next door.
The functional work inside the move is mostly in the LLM runtime: N lanes that
draft while one verify batch serves all of them, per-slot prefill over a shared
folded attention arena, speculation that survives batching, and a scheduler
that reports rather than publishes. And in the CUDA build: a machine without
usable CUDA must still LINK (and say so), the default kernel arch is the
building machine's GPU, `NO_CUDA` forces the stub even where the toolkit
exists, and kernels compile in parallel with progress.
libs/video_flow is new here: classical optical flow estimation and the `mkfl`
motion-field payload — a flow field measured from a clip without a model,
which is what drives free-rate bounce-looping playback and the uprez/tween
enhance pipe.
The asset store now uses libs/sqlite_query as its ONLY engine — not a feature
flag, not a fallback. That closes the Windows gap (the embedded store starts
there now, and a SHARED->EXCLUSIVE upgrade is handled rather than assumed
free) and takes the C dependency out of the build everywhere else.
Around it:
- store: a garbage collector, catalogued content that is referenced in place
instead of copied, the `vjeffect` kind, and host/chat routes that keep up
with the chat wire below.
- importer: the unified map contract reaches quake2, quake3, doom and duke —
world placement, nav, welding, prelit maps and glTF node handling shared
rather than reimplemented per game. Music import, billboards and stateful
props move to the data crate so readers stop linking the importer.
- ai: the serving side of multi-lane chat — per-lane conversations, honest
progress and acceptance reporting, penalties and a watchdog, context as a
per-box number that compacts instead of erupting, a realtime session mode,
and inpaint/flux2 backends. `chat_bench` measures the rate the way the
client meter computes it.
- client / chat / chat_ui: a publication can NAME a file instead of carrying
it; the wire says whether a turn is warm and whether it is thinking, so a
client stops guessing; transcript and feed widgets render history the way
the model wrote it. `SessionConfig::catalog_runtime` lets a host size the
catalog runtime's lanes itself — a browsing UI puts every listing, every
per-tile resolve and every thumbnail blob through that one runtime and
wants a wider fast lane than the shared default, while media lanes keep
it (a few big transfers, not a thousand small ones).
- widgets: the shared asset widgets — one video view (knobbed seek,
transport, bracket trim, rail playback) used everywhere, plus thumb,
preview, scene view, walk-world and the lyric reader.
A from-scratch, dependency-free SQLite implementation: file format reader and
writer (b-tree read and write paths, pager, journal, WAL), a SQL lexer,
parser and AST, a planner, and an executor — plus locking, integrity checking
and a `sqlq` CLI.
It exists because the asset store needs a database on every platform the app
ships to, without a C toolchain in the build and without a system library
whose version is somebody else's decision. The test suite is the argument:
DML, DDL, concurrency, crash recovery, a query corpus and a DML fuzzer, all
checked against real SQLite behaviour rather than against our own reading of
the spec.
Six new widgets, all built for the console-density end of the spectrum:
- DataGrid — a 2D-virtualised grid (rows AND columns), the table/spreadsheet
counterpart to PortalList. Cells host arbitrary widgets from templates.
- Chart — trend and sparkline drawing that composes inside docks and grids,
which the old DrawVector-based chart could not.
- ComboBox — a text input with a filtered, keyboard-navigable popup list;
type-to-filter rather than pick-from-a-menu.
- Tip / TipLayer — system tooltips: a shared overlay layer, hover timing and
placement handled once instead of per widget.
- ValueInput — a numeric field you can also drag, Blender-style.
- DropSlider — a slider that lives in a popover, for consoles with no room
for a permanent one.
widget_tree gets the larger share of the changed lines: observation and
patching paths reworked so a structural rebuild is not the answer to every
change. PortalList picks up the scroll-distance readout and the dead-isolate
guard from upstream; window.rs grows maximize/restore forwarders; splitter
exposes a color hook; fold_header, scroll_bar, text_flow and drop_down2 get
follow-ups.
An app built with `--remote` now serves a localhost HTTP control surface:
window list, per-window PNG grabs, real mouse/key/text injection, widget
rects, a log ring buffer, and `/gq` (grab every window, then quit). It exists
so a test or an agent can DRIVE a running app instead of reasoning about it
from source — the protocol is documented in AGENTS.md. Grabs are targeted per
window (`/g?w=N`), so a multi-window app is captured window by window rather
than whichever pass happens to present first, and `log!` mirrors into the ring
buffer without anyone owning the app's stdout.
platform/video grows a streaming half beside the file half. StreamEncoder /
StreamDecoder with Apple VideoToolbox and Windows Media Foundation backends,
Annex-B framing, and all-intra bound through pEncodingParameters on Windows —
the only control that MFT actually honors, as the readbacks claim success for
everything else. The file decoder can now be asked for a SPECIFIC frame rather
than only the next one, which is what frame-exact seek and bounce playback
need. Tests cover file seek and the stream round trip.
Draw shaders gain `Rgba16F` and `Rgba32F` color formats to pair with the
float render textures: blending off, whole-texel writes, meant for GPU
simulation state (particle position/velocity, fluid fields) rather than
pictures.
Windowing and dialogs:
- `CxOsOp::SetChromelessWhenMaximized` drops the native maximized border
strip on Windows, so a maximized window reads as a clean picture.
- `Cx::open_select_folder_dialog` opens the native folder picker with a
title and start location, answered by a `FileDialogAction` in the actions
pass; cancelling is a first-class outcome, not an error.
- Windows reports a user close the way macos.rs already did.
- macOS swaps the titlebar container so WindowDragQuery alone decides window
drags, and the delegates carry a panic shield.
- `Windows::id_iter()` enumerates window slots generation-correctly.
Headless: the virtual GPU and its rasterizer are substantially rebuilt around
the shader runtime preamble, making `MAKEPAD=headless` render-to-PNG a real
test surface rather than a smoke check. `PerfMonitor::frames_painted()` lets a
scripted driver pace itself to PRESENTED frames instead of queueing passes
faster than the GPU retires them.
* Wayland: mark windows as created, fixing dpi override and pass dpi
Wayland was the only backend that never set `is_created = true` on the Cx
window; every other one does it in `CxOsOp::CreateWindow`.
That flag gates `Cx::dpi_override_scale()`, so every pointer event skipped
the native->layout remap and clicks missed their widgets by the UI zoom
factor. It also gates `get_delegated_dpi_factor()`, which was returning a
hardcoded 1.0 for every draw pass on Wayland, so pixel snapping and the
shader pixel size (SDF AA fringe) used the wrong scale on HiDPI screens.
`SetWindowVisuals` and `set_topmost` were dropped for the same reason.
Also seed the Cx window's geom at creation like the x11 backend does,
otherwise it sits at dpi_factor 0.0 until the first configure arrives.
* convert the seeded wayland geom to layout points and record os_dpi_factor
Seeding the raw native geom left window_geom in native units (and the
os_dpi_factor fallback unset) until the first configure arrived, which is
exactly the pre-configure window the dpi override needs to be correct in.
Do the same conversion the WindowGeomChange path already does.
When an area that already captured a touch sees another touch start,
hits() returns a FingerDown for it (so the owner can handle multi-touch
gestures like pinch) but never marked the touch as handled. Widgets
behind the owner could then capture that second touch themselves: the
second finger of a pinch atop a fullscreen overlay could drag-scroll a
list behind it, or even press a button back there.
* mark such a touch as handled if it actually hit-tests within the
area, mirroring the normal capture path below it
* only do so if nothing else has handled it yet, preserving the claim
of a child widget that captured it earlier in the same dispatch
Killing a Splash isolate while widgets it minted are still in the tree
could panic the whole process in the GC, somewhere else entirely:
platform/script/src/gc.rs:300
index out of bounds: the len is 12 but the index is 19
script_ref_vm_id() resolves a widget's owning VM from a ref the widget
holds. Isolate heaps live in heap_to_vm; anything else was assumed to be
the app VM. But an isolate's widgets outlive it by a frame or two — a
tile dropped mid-gesture, an app force-stopped while its buttons are
still on screen — and their refs were minted by a heap that is gone. So
those calls were routed INTO the app VM, which then stored a dead heap's
object ids in an args object of its own (make_call_args_object_with_
context). Nothing complained: the checked stores silently skip an index
they can't resolve. The next GC walked that object, indexed the app heap
with the other heap's index, and blew up with no trace of the cause.
Reclaimed heaps are now remembered, so a ref from one is told apart from
an app-VM ref and its call is dropped rather than redirected — the same
thing script_timer_dispatch_hook already does with a dead isolate's
timers. Three related tightenings while here:
- with_script_vm_id checks "is this VM already installed?" BEFORE the
main-VM shortcut, and debug_asserts in the main branch. with_vm runs
against whatever VM is parked on Cx, which during an isolate's own
execution is that isolate's — so the old order could silently run
app-VM work in an isolate's heap.
- gc_dead_splash_isolates purges the queues holding a dying isolate's
values before dropping the heap those values live in, matching what
gc_bridge above it already did.
- splash_host_respond checks it landed in the heap the request came
from before minting the answer object there. One usize compare turns
a future routing mistake into an undeliverable answer instead of a
corrupted heap.
Found by a launcher that force-stops a mini-app for hammering the host
bridge: ~40 requests in flight, isolate torn down in the same event
pass, reopened moments later. That reproduced it every time; with this
it no longer does.
The styled mod.widgets.DropDown2 block re-applied hover/active/up/enabled
as instance(...) markers onto the already-typed f32 fields of the draw
shaders, which fails at widget instantiation with "type mismatch:
expected f32, got object". The instance() declarations belong to the
script_shader type registrations; the style block just sets plain
float defaults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds `PortalListRef::user_scroll_travel()`, the running sum of every
user-driven scroll delta: wheel/trackpad (including OS momentum), touch
drags, scroll-bar drags, and selection auto-scroll. Deltas absorbed at a
clamped edge still count, while the list's own movement (smooth scrolls,
fling coasting, bounce-back, tail-following) does not.
Sampling it at two points and subtracting tells a caller whether the
user scrolled in between and in which direction, which comparing
`first_id()` cannot do once items are inserted or removed.
A bare `if cond { ... }` statement whose final body statement is a
non-void expression compiled to an empty `if(cond){ }`: the expression
never reached the generated shader, so its side effects were lost. In
robrix this silently removed every border drawn as
if self.border_size > 0.0 {
sdf.stroke(self.border_color, self.border_size)
}
A call is not written to the output when it is compiled, it is pushed on
the stack as a string, and it only reaches the output via POP_TO_ME.
Since e0a5a23f2 the enclosing statement's POP_TO_ME is emitted as a
standalone opcode at the if's jump target instead of being fused onto the
body's last call, and the shader compiler closes an `IfBody` as soon as
`ip >= target_ip`, so the opcode sitting exactly at the target is never
seen while the body is open. The body's value was then dropped on the
floor by the `no outer phi` arm, whose comment assumed that could not
happen.
* Emit the leftover value as a statement inside the branch when nothing
consumes it, mirroring the void path a few lines above.
* Add `gpu_stage_4m`, which asserts the call survives into the generated
shader. It fails without the fix.
The parser side is deliberately untouched: `last_jump_target` is load
bearing for the widget-loss fix that `on_render_emission` guards.
* Splash: a host-services bridge so isolates can ask for brokered capabilities
Mini-apps are sandboxed hard (fs/run/res stripped, net gated), which also
means they can't do anything real. This adds the one doorway back: a
mod.host module in every isolate whose host.request(service, args, cb)
queues {app_tag, heap_key, req_id, service, args_json} on a thread-local
the EMBEDDING HOST drains and answers (splash_host_respond re-enters the
isolate under the normal budget and calls the callback with {ok, data,
error}). No policy lives in makepad: an undrained request never resolves,
tags are host-assigned (Splash::set_host_tag) so scripts can't spoof who
they are, and host.capabilities() just echoes whatever grant list the
host last pushed (set_host_caps). Callbacks are rooted ScriptFnRefs keyed
by heap, GC'd with the isolate alongside the storage-jail roots.
Also: call_script_fn_with_strings (string args must be minted in the
callee's own heap), 'let host = mod.host' in both Splash prefixes (line
offsets documented per prefix; the net prefix was already one line off),
and mod.cx.quit is now nil'd in isolates - a mini-app could quit the
whole host process with one call.
* script: stop validation from blessing scripts that failed to parse
The parser RECOVERS from errors (dangling else, missing expression), logs
them, sets had_error - which nothing ever read - and hands back a runnable
module. Nothing enters the trap queue, so a host validating with a
captured_errors sink + take_errors() got an empty list and reported
success; three freshly-written mini-apps shipped real parse errors straight
through host_launcher's validate this way, visible only as stray [E] log
lines.
report_error now also records the formatted message on the parser
(ScriptParser::errors), and both eval paths (eval_with_source and the
streaming eval_with_append_source) drain that into bx.captured_errors when
a sink is installed. No sink = logs only, exactly as before. Regression
tests in tests/parse_error_capture.rs, including the exact fn-final
if/else shape that slipped through.
* splash_host: review fixes — is_ok result field, silent surfaces, JSON hardening
Three classes of fixes from an adversarial review of the bridge:
- The result object's success field is now is_ok. 'ok' is the script
dialect's ok-test KEYWORD, so r.ok never parsed as a field access — every
callback that read it silently died. A pure-VM regression test
(fn_ref_callback.rs) now exercises the exact store-callback-then-answer
flow the bridge uses.
- SplashHostRequest carries may_prompt, set per isolate via
Splash::set_host_prompts: background surfaces (home-screen widget tiles)
are marked silent so a host can fail their permission-needing requests
instead of popping consent dialogs nobody asked for. splash_host_respond
also reports an outcome now (Delivered / NoCallback / IsolateGone) so
hosts can log undeliverable answers, and Splash::isolate_heap_key lets a
host relate a request to a specific widget (IPC fan-out skips the
sender's own isolate with it).
- heap.to_json hardening: a cyclic object graph (script-buildable, host-
serialized on every bridge request) recursed to a stack overflow — now a
depth cap emits null leaves; backslashes were mis-escaped as a single
backslash (invalid JSON downstream), tab and other control chars weren't
escaped at all, and a handle serialized as unquoted junk.
* script: a closure's captured varargs must not shadow its own parameters
A call binds positional args by INDEXING the fn object's vec, which holds
declared parameters — but also, past that, any varargs the call received
(unnamed_fn_arg pushes them with a NIL key). A closure captures the scope
it was minted in, so those leftovers ride along ahead of the closure's own
parameters.
Concretely: script timers invoke their callback with one number (the time).
Hand start_timeout a zero-arg closure and that number lands in the scope as
a NIL-keyed vararg; any closure created in that body then binds its FIRST
parameter against the leftover — first failing the typecheck ("arg 0 (nil)
type mismatch: expected number, got object"), and once that was relaxed,
binding the value under the NIL key so the real parameter stayed nil. It
cost a full debug cycle in host_launcher, where every host-service callback
created inside a boot timer silently never ran.
Both binding paths now walk the DECLARED (named) entries in order, so
captured varargs can never be mistaken for a parameter. Regression test in
tests/extra_arg_typecheck.rs reproduces the timer shape exactly.
(Pre-existing and unrelated: widget_tree's test_observe_and_find_single_node
and test_property_patch_no_structural_rebuild fail on upstream dev too.)
* script: stop parse_json silently dropping negative numbers
The tokenizer emits a leading `-` as its own Operator token, and none of
the three JSON value positions (object value, array element, root) had a
case for it. The sign was swallowed — and inside an object the KEY went
with it, because the minus consumed the value slot and the parser resynced
on the next token.
So `{"lat":37.7,"lon":-122.4}` parsed to `{"lat":37.7}`. No error, no
warning, just a missing field. That is how it was found: a mini-app asked
the host where it was, got coordinates with no longitude, and quietly fell
back to a default city. Sub-zero temperatures and negative UTC offsets
(New York is -14400) were being dropped the same way.
A pending-sign flag is applied to the next number in all three positions.
Bare scalar roots stay unsupported ("42" never parsed either) — separate
pre-existing gap, not touched here. Tests in
platform/script/tests/json_negative_numbers.rs.
* splash_storage: let the host raise a single isolate's jail quota
The jail's 16MB whole-app cap is a constant, so "this app may keep more
than the standard amount" had nowhere to live. A per-heap quota map beside
SANDBOX_ROOTS gives the host one, set through Splash::set_storage_quota
and cleared with the isolate like every other per-isolate binding. Script
still can't see or raise its own cap.
Lowering a quota never deletes anything — it just stops further growth —
so revoking the grant is safe on an app that already wrote past the
default.
host_launcher uses this for a `storage-large` permission (64MB), which is
the point: a capability the user can revoke and have it actually mean
something.
Drop the archived old/ tree, root AI notes, splashgame.md (Arcade lives
in the private sandbox), widgets-dll leftovers, and LAN/oracle helper
scripts. Move download_*.sh into tools/, and move sim/math out of
libs/game/ so the public repo no longer has a game/ directory.
Also quiet first-party compile noise and skip asset-ui tests that need
sandbox kits or uncommitted fixtures.
hotload_ui and xr pulled a dylib widgets wrapper that made
`cargo build --workspace` fail. XR now uses normal widgets.
The vendored weezl async test needed tokio, which we do not ship.
Drop the private sandbox clone from required workspace members
and Studio runnables. Finish the platform_ops VecDeque merge
(push_back / Option remove), land the mip-repeat texture API
the renderer already calls, and unbreak the Q3 importer plus
the godot example template that .gitignore had hidden.
Point the workspace at libs/asset and libs/render. Drop Arcade
frontend, gamemaker, rig, pack registry, physics, and the old
libs/game gameplay crates. Those live in the private sandbox repo.
Repository-wide rules. Read the linked references when the task needs them;
use current source for API signatures and working examples.
## Execution Policy
- Visual UI programs must be launched and controlled through the Makepad Studio remote protocol.
- Always use release builds for runtime validation, profiling, benchmarks, timing checks, or any performance-sensitive command. Use `--release` unless the user explicitly asks for a debug build.
- Do not use mount observation or runnable discovery from the bridge client. The bridge must not claim mount ownership from Studio desktop.
- Do not launch UI programs with raw `cargo run`, `cargo makepad`, or ad hoc cargo invocation when a runnable item exists.
- Do not use bridge `Cargo` requests to run applications. Only launch apps from runnable items via bridge `RunItem`.
- For UI runnable targets, do not prebuild or precheck the app from the shell before launching it in Studio. Let the Studio `RunItem` build be the single build path so Cargo fingerprints, env vars, target dirs, and flags stay identical.
- Before starting a new UI run for the same target, send `ClearBuild` for the previous build so Studio stops it and removes its run/log/profiler tabs.
- `cargo check` or `cargo build` never counts as UI verification. After changing UI/runtime code, you must clear the old build and start a fresh Studio run before trusting screenshots, widget dumps, or interaction results.
- Do not keep inspecting an older already-running app after code changes. Re-run the target and verify against the new `build_id`.
- Command-line-only tasks (builds, tests, linting, file ops, grep/ripgrep, etc.) can be run directly in the shell.
- Prefer studio remote control for any workflow that needs screenshots, widget queries, clicks, typing, or runtime UI inspection.
- Before using Studio protocol tools (`FindInFiles`, `ReadTextRange`, `WidgetTreeDump`, `WidgetQuery`, `Screenshot`, `Click`, `TypeText`, `Return`), always start one persistent Studio remote bridge process and reuse it for the entire interaction.
- When adding a new example crate, update both the Cargo workspace and `makepad.splash` so Studio exposes the new example as a runnable item.
## Assumptions
- Studio is started manually by the user.
- Studio remote target is `ip:port` only (no `http://`, no `ws://`), normally `127.0.0.1:8001`.
- Use `127.0.0.1:8002` only if Studio reports fallback because `8001` is occupied.
- Keep one persistent studio remote process for the whole interaction.
- **Designs stay local.** Design documents, plans, and reports are local files
(`local/agent_state/<topic>/DESIGN.md`) that lanes read from disk. Never
publish them to the web (no Artifacts, no hosted pages); summarize in the
terminal instead.
- Launch UI programs as standalone release binaries from this checkout. Do
not use the Studio remote bridge, `ObserveMount`, `RunItem`, or any
`cargo-makepad studio` websocket client.
- Launch with `--remote` whenever you intend to look at or drive the app,
and finish with `GET /gq`. **Nothing of yours may outlive your task** —
never leave a test window on the user's screen.
- Always use release builds for runtime validation, profiling, benchmarks,
timing checks, or any performance-sensitive command. Use `--release`
unless the user explicitly asks for a debug build.
- Build with `cargo build --release -p <package>`, then launch the
resulting executable so its provenance is unambiguous. Do not use raw
`cargo run` / `cargo makepad` to start a UI you will keep inspecting.
- Stop or replace an older standalone instance of the same target before
launching a freshly built one.
- Keep an interactive standalone app running when the user asks to play
with it. Use a separate self-terminating capture run only when a
screenshot is also needed.
- `cargo check` or `cargo build` never counts as UI verification. After
changing UI/runtime code, rebuild and relaunch before trusting what you
see. Do not keep inspecting an older already-running binary.
`import`, `scrot`, `grim`, `xwd`, PowerShell/Win32 screen grabs, or any
other OS screen capture — not of the display, not of a window, not
"just the caption". The user's screen is private. The only image of a
running app you may ever take is the app's own `--remote` grab (`/g`,
`/gq`, `/tweak/grab`), which renders the app's own drawable and nothing
else. If something only shows in the OS layer (native caption buttons,
other apps, the desktop), ask the user for a screenshot instead of
taking one.
- When adding a new example crate, update both the Cargo workspace and
`makepad.splash`.
- **Zero locking on the UI thread, one mechanism everywhere.** The UI
thread never takes a `Mutex`/`RwLock`/`Condvar` that another thread can
hold, and never blocks on a channel. UI → workers/audio is commands over
a channel (bounded, non-blocking send; a full queue is reported and
retried next frame). Workers/audio → UI is snapshots over atomics, a
triple buffer, or a channel read with `try_recv`. Large payloads (PCM,
stems, grids, images) travel as `Arc` through the channel; a replaced
payload is handed back so the UI thread does the drop, never a realtime
thread. A realtime callback (audio) owns its state, never takes a lock
the UI can hold, never allocates on the hot path. This is ONE code path
for native and wasm — no `cfg` fork where desktop keeps shared mutexes.
On wasm both the browser UI thread and the AudioWorklet thread abort on
`Atomics.wait`, and a spinning fallback against a busy audio callback is
a 100 % CPU feedback loop that kills audio and frame rate together (DJ
web, 2026-09-03). `lock_from_ui` is only acceptable on state provably
touched by the UI thread alone.
- **Standard operating flow — who does what.** The main session (Fable)
designs, briefs, manages and reviews; it does not write the code itself
except one-line fixes. **Codex writes the code**: every implementation lane
is a Codex lane with a precise brief (observations, files, rules, the
verification commands) launched through `local/tools/delegate` /
`local/agent_state/webdemos/tasks/queue.sh` and landed through
`local/tools/integrate`. **Grok does the tests and the token-heavy work**:
test suites, audits, surveys, log reading, conflict resolution passes,
reviews of large diffs (`delegate grok` / `research-grok` / `review-grok`).
A Fable subagent is the exception, only for a design-level change the
other two cannot carry (a new platform mechanism), and it stops as soon as
the API is fixed so Codex can do the conversions. Keep at most six lanes
per provider; land everything through the integrator; the user tries the
result — no routine captures.
- **No temporary threads — use the pool.** Never spawn a thread for one
job (`std::thread::spawn` is unsupported on wasm anyway; the platform
spawner works everywhere). Background work goes to the platform thread
pool (`cx.thread_spawner()` / the pool `TaskHandle` API) or to a
long-lived worker created once at start-up and fed over a channel. On
the web a Web Worker takes hundreds of milliseconds to come up, so a
per-job thread is a stall; on desktop it is still churn. One mechanism
on both targets.
## Start Studio Remote
- Command:
- `target/release/cargo-makepad studio --studio=127.0.0.1:8001`
- Send newline-delimited JSON requests on stdin.
- Read newline-delimited JSON responses on stdout.
- Protocol shape is raw `ClientToHub` requests on stdin and filtered `HubToClient` responses on stdout.
- Do not send `ObserveMount` from the bridge. It can take `primary` UI ownership for the mount and divert RunView/framebuffer traffic away from Studio desktop.
## Standalone Launch
1. `cargo build --release -p <package>` from this checkout.
2. Kill any older process of that same executable.
3. Run `target/release/<bin> --remote` from the repo root (so resource paths
resolve), parse the port from the startup line, drive it over HTTP.
4. After code changes, repeat 1–3 before drawing conclusions.
5. `GET /gq` when you are done. Always.
## Request Protocol (JSON Lines)
- `{"ListBuilds":[]}`
- `{"ClearBuild":{"build_id":[6]}}` stops a running build and immediately clears its Studio UI tabs; use this before rerunning the same app.
- `{"StopBuild":{"build_id":[6]}}` stops/kills a running build but does not clear Studio tabs.
- `BuildCleared` is a Studio frontend cleanup signal routed to the primary UI for the build's mount; bridge clients should not wait for it before starting the next run.
- `RunViewFrame` and the terminal stream are not exposed by the bridge.
- `Screenshot` responses include file metadata (`path`, `width`, `height`) and not inline PNG bytes.
- `WidgetTreeDump` responses include text dump content keyed by `request_id`.
- `FindInFiles` responds as `SearchFileResults` with concise entries (`path`, `line`, `column`, `line_text`) and `done`.
- `FindInFiles` defaults to searching only `.rs`, `.md`, `.toml` files unless `glob` is provided.
- `ReadTextRange` responds as `TextFileRange` with `path`, requested `start_line`/`end_line`, `total_lines`, and `content`.
- Query-scoped responses are lane-filtered by `query_id.client_id`; only this bridge client's query results are emitted.
- Build ids and query ids are `QueryId` tuple structs, so JSON encodes them as one-element arrays like `[6]`.
- `FindInFiles`/`SearchFiles` execution is worker-pooled in backend (not main dispatch thread).
> **Who may open a visible window.** Subagent/lane verification runs HIDDEN:
> launch with `MAKEPAD_HIDE_WINDOWS=1 <bin> --remote` — the window never
> appears, grabs (`/g`), `/snap`, `/m`, `/k`, `/t` all still work offscreen.
> Only the integrating session opens the one visible, unfocused window the
> user watches; several look-alike windows on screen made the user "go
> insane" (2026-08-26).
## Recommended Control Flow
1. Start studio remote process once.
2. Determine the target runnable item name locally from the repo or from the user request.
3. Call `ListBuilds` and find any existing build for the same runnable item.
4. Send `ClearBuild` for that old `build_id`; do not wait for an acknowledgment before the next launch.
5. Start the new UI app through `RunItem`, and wait for `BuildStarted` and `AppStarted`.
6. After any code change that affects runtime/UI behavior, repeat steps 3-5 before doing screenshots, widget dumps, clicks, or visual conclusions.
7. For code search, use `FindInFiles` first, then `ReadTextRange` to window exact regions.
8. Use direct shell cargo commands for non-launch tasks such as `check`, `build`, `test`, or `bench`.
9. Use `WidgetQuery` / `WidgetTreeDump` to get click targets.
10. For text input, click field first, then send text, then return.
11. Keep control packets compact (`auto_dump:false` on click/type/return for low latency).
## `RunItem` Launch
- `RunItem` executes a Studio-defined runnable item by name.
- Use the runnable item name shown in Studio, not a Cargo package name.
- `RunItem` does not implicitly replace an older build tab; agents should clear the old build themselves first with `ClearBuild`.
Any makepad app launched with `--remote` runs a localhost HTTP server inside
the process and prints one line before the UI appears:
## One-Flow Input Burst
- Send this as one stdin write (multiple JSON lines, no sleeps):
- `Click` (input field center)
- `TypeText`
- `Return`
- Then request `WidgetTreeDump` or `Screenshot` to confirm.
```
[makepad-remote] listening on 127.0.0.1:53412 pid=9931 app=makepad-example-splash grabs=/var/folders/…/T/makepad-remote/makepad-example-splash-9931
```
## Coordinates
- Use coordinates from dump as-is.
- `W3` dump uses integer pixel coordinates in the same space expected by `Click`.
- Do not apply extra DPI math in the agent loop.
Port, pid, app name and the grab directory — everything needed to drive and
clean up the instance, with no discovery step. `--remote=PORT` pins the port;
`MAKEPAD_REMOTE=1` (or `=PORT`) does the same via the environment. No app code
is involved: it lives in `app_main!`, so every app gets it for free.
## Reliability Notes
- `Screenshot` can arrive before visible redraw after rapid input bursts.
- If screenshot looks stale, request a follow-up `WidgetTreeDump`/`Screenshot`.
- If input does nothing:
- Verify `build_id` with `ListBuilds`.
- Refresh dump and retry click on input before typing.
- If request errors with no active websocket:
- app is not connected yet; wait for startup completion and retry.
### Cheat sheet
Every route is a plain `GET`. Every answer is **one line of JSON** with short
keys and real numbers. Errors are `{"err":"..."}` with HTTP 404.
`GET /` returns this table as plain text, so an agent that finds the port
learns the whole API in one request.
| Route | Answer | Notes |
|---|---|---|
| `/``/help` | plain-text cheat sheet | self-describing; read this first |
| `/click``?x=&y=&w=&wait=` | `{"ok":1}` | alias for `/m?k=click` (move + down + up) |
| `/k``?t=TEXT` or `?k=down\|up\|press&c=CODE` | `{"ok":1}` | `t=` goes through the IME text path; `c=` takes `KeyA`/`a`/`enter`/`Escape`/`ArrowLeft`/`F1`/`Key1`… plus `&shift=1&ctrl=1&alt=1&cmd=1` |
| `/t``?t=TEXT` | `{"ok":1}` | same as `/k?t=` |
| `/snap``?q=&w=&all=` | `{"s":[{"i":"id","ty":"Button","r":[x,y,w,h],"w":0,"t":"Click me"}]}` | **how you find things to click.**`q=` filters id/type/text; rects are window-local, ready to feed to `/click` |
| `/d``/dump` | plain text widget tree | one indented line per widget, ending `x y w h` |
| `/log``?n=50&since=N` | `{"n":lastseq,"l":["[E] …"]}` | ring buffer of the app's own log output — see errors without owning stdout |
| `/close``?w=ID` | `{"ok":1}` | closes one window the normal way |
| `/quit` | `{"ok":1}` | graceful shutdown, no final grab |
Add `&wait=1` to any input route to have it answer only **after the next frame
is drawn**, so a following `/g` sees the result with no `sleep`.
Add `&w=ID` to target a window; omit it for the first one.
`POST` the same routes with a flat JSON body (`{"x":10,"y":20}`) when quoting a
query string is painful; the key names are the long ones (`window`, `kind`,
- **Backends:** macOS/Metal is fully supported. Linux GL and Vulkan support
grabs too. Windows/D3D11 has no screenshot readback yet, so `/g` there times
out with `{"err":"grab timeout …"}` while every other route works. Android,
OHOS and wasm compile to a no-op.
- **Cost when idle is zero.** The event loop only upshifts its paint clock
while a remote request is in flight.
### The TWEAKER (`/tweak/*`) — design feedback and live styling
Every `--remote` app carries a design-feedback overlay (plan of record:
repo-root `tweaker.md`; implementation: `widgets/src/tweaker.rs`). Off it
costs nothing. On, the person (or you) points at the UI: pointer events over
the window body are swallowed before widget dispatch — **clicking a Button in
tweak mode outlines it and never fires it** — and the window grows a property
sidebar next to the (compressed) app UI. Shift+F10 toggles it in-app; every edit,
theirs or yours, lands in one shared diff log.
| Route | Answer | Notes |
|---|---|---|
| `/tweak``?on=1\|0&annotate=1\|0` | `{"on":1,"annotate":0}` | toggle the overlay / the freehand draw mode (Alt-drag draws too) |
| `/tweak/state` | `{"on":1,"sel":{path,ty,r,band},"props":[{n,v,set}],"hover":…,"diff":[…],"ann":[…]}` | the STRUCTURE feedback: pinned selection, its real reflected properties (`set:1` = explicitly applied), the edit log, annotation strokes with the widget paths they touch |
| `/tweak/apply` (POST) | `{"ok":1,"path":…,"changed":[{path,prop,old,new}]}` | body `{"path":"a.b.c","splash":"{padding: Inset{left: 20}}"}` or the one-property shorthand `{"path":…,"prop":"draw_bg.border_radius","value":"8"}`. Evaluates the chunk onto that ONE instance through the ordinary apply machinery (`+:` merge rules intact) and triggers a full relayout. Answers after the next drawn frame |
| `/tweak/diff` | `{"diff":[{path,prop,old,new}…]}` | the raw edit log, in order |
| `/tweak/final` | `{"final":[…coalesced…],"ann":[…],"drew":0\|1,"png":path?}` | **read this when tweaking is done**: per (path, prop) only the original and final value, churn collapsed. When the user drew, `png` is the composited screenshot — look at it, the strokes mean something |
| `/tweak/grab` | like `/g` | the overlay (outlines, strokes, sidebar) draws in the window's own pass, so any grab is already composited |
`local/tools/tweak` wraps all of this:
`tweak PORT on`, `tweak PORT state`, `tweak PORT apply PATH PROP VALUE`,
`tweak PORT splash PATH 'CHUNK'`, `tweak PORT final`, …
**How to listen.** Sidebar edits push to you: each one emits a marked
`TWEAK sidebar <path> <prop> <old> -> <new>` line into the app log — the
`/log` tail is your ear; you never poll `/tweak/state` for changes. Talk back
on `/tweak/apply` (values or whole shader chunks) to the same selected
instance.
**Write-back (you do this part — the overlay never writes source).** When the
session is done, take `/tweak/final` and edit the splash source:
1. Resolve each entry's widget path to its DSL site: the dotted path mirrors
the `script_mod!` tree (`/d` shows the same ids). `-` segments are
anonymous containers — skip them when searching the source.
2. Write each property at the **most specific existing site** — the widget's
own `name := Type{…}` block if it has one; create one only when none
exists.
3. Respect the merge law: a property inside a typed sub-struct goes through
`+:` (`draw_bg +: { border_radius: 8 }`), never a replacing
Use the Studio bridge runnable-item flow instead of launching UI apps directly from the shell:
Launch UI apps as standalone release binaries from this checkout. Do not
use the Studio remote bridge.
1. Start the Studio remote bridge once.
2. Determine the runnable item name locally.
3. If an older instance is still running, clear it with `{"ClearBuild":{"build_id":[N]}}` and launch the replacement immediately without waiting for an acknowledgment.
4. Launch it with `{"RunItem":{"mount":"makepad","name":"<runnable-name>"}}`.
5. After editing UI/runtime code, do not inspect the previously running build. Always verify against the newly started build id from step 4.
```bash
cargo build --release -p makepad-app-asset-ui
# stop any older instance of the same binary, then:
./target/release/makepad-app-asset-ui
```
Do not use `ObserveMount` from the bridge. That call is for mount ownership/subscription and can steal RunView/framebuffer routing away from Studio desktop.
For one-shot visual smoke of a small example:
Use direct shell cargo commands only for non-UI tasks such as library checks, tests, and file/search operations. Do not run shell `cargo check`, `cargo build`, or `cargo run` for UI runnable targets that will be launched via Studio.
When those non-UI tasks are used for runtime behavior or performance measurements, prefer their release variants (`cargo run --release`, `cargo test --release`, `cargo build --release`).
To look at or drive a running app, add `--remote`: the app serves a localhost
HTTP control surface (window list, PNG grabs, real mouse/key/text injection,
widget rects, log tail) and prints its port on startup. Finish every session
with `GET /gq`, which grabs each window and quits — never leave a test window
let app = ci.launch({package: "makepad-aichat" binary: "aichat" cwd: "."})
ci.step("comes up", fn(){
ci.sleep(3.0)
app.no_errors()
let shot = app.grab("start")
ci.accept(shot, "This is a screenshot of one application window taken from a Mac. A thin bright red frame runs around the very edge of the picture; it is the test harness's marker and is not part of the application. Ignore it. The application is a chat window for talking to an AI assistant, with a message list and a text input. It came up when these are true: 1. The picture is not blank: it is not one flat colour from edge to edge, and it is not entirely black or entirely white. 2. Some user interface is visible: at least two different things among text labels, buttons, icons, panels, lists, input fields, a toolbar, a drawing or a picture. 3. No error panel covers the window: there is no large block of text with words such as panic, error, failed, backtrace or unwrap. Write one short bullet line for each numbered point saying what you see. YES means points 1, 2 and 3 are all satisfied. Then write the verdict as the last line, in exactly this form: VERDICT: YES or VERDICT: NO")
let app = ci.launch({package: "makepad-browser" binary: "browser" cwd: "."})
ci.step("comes up", fn(){
ci.sleep(3.0)
app.no_errors()
let shot = app.grab("start")
ci.accept(shot, "This is a screenshot of one application window taken from a Mac. A thin bright red frame runs around the very edge of the picture; it is the test harness's marker and is not part of the application. Ignore it. The application is a web browser. It came up when these are true: 1. The picture is not blank: it is not one flat colour from edge to edge, and it is not entirely black or entirely white. 2. Some user interface is visible: at least two different things among text labels, buttons, icons, panels, lists, input fields, a toolbar, a drawing or a picture. 3. No error panel covers the window: there is no large block of text with words such as panic, error, failed, backtrace or unwrap. 4. A browser is visible: an address bar, which is a wide text field near the top, with a web page or an empty page area below it. Write one short bullet line for each numbered point saying what you see. YES means points 1, 2, 3 and 4 are all satisfied. Then write the verdict as the last line, in exactly this form: VERDICT: YES or VERDICT: NO")
<pathd="M 6.00 7.40 L 7.40 6.00 L 12.00 10.60 L 16.60 6.00 L 18.00 7.40 L 13.40 12.00 L 18.00 16.60 L 16.60 18.00 L 12.00 13.40 L 7.40 18.00 L 6.00 16.60 L 10.60 12.00 Z"/>
<pathd="M 20.00 12.00 L 19.73 14.07 L 18.93 16.00 L 17.66 17.66 L 16.00 18.93 L 14.07 19.73 L 12.00 20.00 L 9.93 19.73 L 8.00 18.93 L 6.34 17.66 L 5.07 16.00 L 4.27 14.07 L 4.00 12.00 L 4.27 9.93 L 5.07 8.00 L 6.34 6.34 L 8.00 5.07 L 9.93 4.27 L 12.00 4.00 L 14.07 4.27 L 16.00 5.07 L 17.66 6.34 L 18.93 8.00 L 19.73 9.93 L 20.00 12.00 L 18.40 12.00 L 18.18 10.34 L 17.54 8.80 L 16.53 7.47 L 15.20 6.46 L 13.66 5.82 L 12.00 5.60 L 10.34 5.82 L 8.80 6.46 L 7.47 7.47 L 6.46 8.80 L 5.82 10.34 L 5.60 12.00 L 5.82 13.66 L 6.46 15.20 L 7.47 16.53 L 8.80 17.54 L 10.34 18.18 L 12.00 18.40 L 13.66 18.18 L 15.20 17.54 L 16.53 16.53 L 17.54 15.20 L 18.18 13.66 L 18.40 12.00 Z"/>
<pathd="M 11.20 4.40 L 12.80 4.40 L 12.80 19.60 L 11.20 19.60 Z"/>
<pathd="M 4.40 11.20 L 19.60 11.20 L 19.60 12.80 L 4.40 12.80 Z"/>
<pathd="M 11.00 5.00 L 13.00 5.00 L 13.00 11.00 L 19.00 11.00 L 19.00 13.00 L 13.00 13.00 L 13.00 19.00 L 11.00 19.00 L 11.00 13.00 L 5.00 13.00 L 5.00 11.00 L 11.00 11.00 Z"/>
<pathd="M 15.75 5.50 L 17.07 6.47 L 18.14 7.70 L 18.93 9.13 L 19.39 10.70 L 19.49 12.33 L 19.24 13.94 L 18.65 15.46 L 17.75 16.82 L 16.57 17.95 L 15.17 18.80 L 13.62 19.32 L 12.00 19.50 L 10.38 19.32 L 8.83 18.80 L 7.43 17.95 L 6.25 16.82 L 5.35 15.46 L 4.76 13.94 L 4.51 12.33 L 4.61 10.70 L 5.07 9.13 L 5.86 7.70 L 6.93 6.47 L 8.25 5.50 L 9.25 7.24 L 8.28 7.94 L 7.49 8.85 L 6.92 9.90 L 6.58 11.04 L 6.51 12.24 L 6.69 13.42 L 7.12 14.54 L 7.79 15.54 L 8.65 16.36 L 9.68 16.98 L 10.81 17.37 L 12.00 17.50 L 13.19 17.37 L 14.32 16.98 L 15.35 16.36 L 16.21 15.54 L 16.88 14.54 L 17.31 13.42 L 17.49 12.24 L 17.42 11.04 L 17.08 9.90 L 16.51 8.85 L 15.72 7.94 L 14.75 7.24 Z"/>
<pathd="M 18.45 5.17 L 14.25 2.77 L 14.65 8.77 Z"/>
<pathd="M 16.00 10.00 L 15.80 11.55 L 15.20 13.00 L 14.24 14.24 L 13.00 15.20 L 11.55 15.80 L 10.00 16.00 L 8.45 15.80 L 7.00 15.20 L 5.76 14.24 L 4.80 13.00 L 4.20 11.55 L 4.00 10.00 L 4.20 8.45 L 4.80 7.00 L 5.76 5.76 L 7.00 4.80 L 8.45 4.20 L 10.00 4.00 L 11.55 4.20 L 13.00 4.80 L 14.24 5.76 L 15.20 7.00 L 15.80 8.45 L 16.00 10.00 L 14.00 10.00 L 13.86 8.96 L 13.46 8.00 L 12.83 7.17 L 12.00 6.54 L 11.04 6.14 L 10.00 6.00 L 8.96 6.14 L 8.00 6.54 L 7.17 7.17 L 6.54 8.00 L 6.14 8.96 L 6.00 10.00 L 6.14 11.04 L 6.54 12.00 L 7.17 12.83 L 8.00 13.46 L 8.96 13.86 L 10.00 14.00 L 11.04 13.86 L 12.00 13.46 L 12.83 12.83 L 13.46 12.00 L 13.86 11.04 L 14.00 10.00 Z"/>
<pathd="M 14.20 13.00 L 15.60 14.40 L 20.60 19.40 L 19.20 20.80 L 14.20 15.80 Z"/>
/// The bus-facing subset of the webview. Keeping it as a trait makes the
/// closed dispatcher testable without starting CEF or a window.
pubtraitBrowserTarget{
fnpage(&self)-> Option<PageState>;
fntabs(&self)-> Vec<TabState>;
fnnavigate(&mutself,url: &str)-> bool;
fnnew_tab(&mutself,url: &str);
}
pubfnmanifest()-> ServiceManifest{
ServiceManifest::new(
"browser",
"Browser",
"The live web browser. Its read tools report the active page and all tabs; its action tools steer the active tab or open a new one.",
)
.with_tool(ToolDef::new(
"page",
"Read the active tab's displayed title and URL. The current CEF binding does not expose visible page text or source, so this tool cannot return page text.",
let app = ci.launch({package: "makepad-calculator" binary: "calculator" cwd: "."})
ci.step("comes up", fn(){
ci.sleep(3.0)
app.no_errors()
let shot = app.grab("start")
ci.accept(shot, "This is a screenshot of one application window taken from a Mac. A thin bright red frame runs around the very edge of the picture; it is the test harness's marker and is not part of the application. Ignore it. The application is a calculator. It came up when these are true: 1. The picture is not blank: it is not one flat colour from edge to edge, and it is not entirely black or entirely white. 2. Some user interface is visible: at least two different things among text labels, buttons, icons, panels, lists, input fields, a toolbar, a drawing or a picture. 3. No error panel covers the window: there is no large block of text with words such as panic, error, failed, backtrace or unwrap. 4. A calculator keypad is visible: a grid of round or square buttons carrying digits and operators such as + and =, with a number display above it. Write one short bullet line for each numbered point saying what you see. YES means points 1, 2, 3 and 4 are all satisfied. Then write the verdict as the last line, in exactly this form: VERDICT: YES or VERDICT: NO")
let app = ci.launch({package: "makepad-calendar" binary: "calendar" cwd: "."})
ci.step("comes up", fn(){
ci.sleep(3.0)
app.no_errors()
let shot = app.grab("start")
ci.accept(shot, "This is a screenshot of one application window taken from a Mac. A thin bright red frame runs around the very edge of the picture; it is the test harness's marker and is not part of the application. Ignore it. The application is a calendar. It came up when these are true: 1. The picture is not blank: it is not one flat colour from edge to edge, and it is not entirely black or entirely white. 2. Some user interface is visible: at least two different things among text labels, buttons, icons, panels, lists, input fields, a toolbar, a drawing or a picture. 3. No error panel covers the window: there is no large block of text with words such as panic, error, failed, backtrace or unwrap. 4. A calendar is visible: a grid of days with day numbers, or a week view with hours down the side. Write one short bullet line for each numbered point saying what you see. YES means points 1, 2, 3 and 4 are all satisfied. Then write the verdict as the last line, in exactly this form: VERDICT: YES or VERDICT: NO")
let app = ci.launch({package: "makepad-clock" binary: "clock" cwd: "."})
ci.step("comes up", fn(){
ci.sleep(3.0)
app.no_errors()
let shot = app.grab("start")
ci.accept(shot, "This is a screenshot of one application window taken from a Mac. A thin bright red frame runs around the very edge of the picture; it is the test harness's marker and is not part of the application. Ignore it. The application is a clock. It came up when these are true: 1. The picture is not blank: it is not one flat colour from edge to edge, and it is not entirely black or entirely white. 2. Some user interface is visible: at least two different things among text labels, buttons, icons, panels, lists, input fields, a toolbar, a drawing or a picture. 3. No error panel covers the window: there is no large block of text with words such as panic, error, failed, backtrace or unwrap. 4. The time is visible: a round clock face with hands, or large digits showing hours and minutes. Write one short bullet line for each numbered point saying what you see. YES means points 1, 2, 3 and 4 are all satisfied. Then write the verdict as the last line, in exactly this form: VERDICT: YES or VERDICT: NO")