41 KiB
Tessera source
| Module | Responsibility |
|---|---|
| main.rs | Native window event loop, namespace state and API startup |
| api.rs | Code CRUD, live Console/reset and compatibility evaluation endpoints, worker-owned session, request observation, coalesced native viewport/background-task updates, epoch checks, stale source/destination tab-drop rejection and diagnostics |
| activity.rs | Bounded in-memory HTTP exchanges with marked previews and allowlisted request headers |
| endor_host.rs | Aster compilation, fresh PUT candidates retained after successful publication, shared Console/program VM, asynchronous completion/fault boundaries, bounded execution, compiled source/call-graph effect query and rebinding coverage, root cleanup and AST developer tree for function/class/member assignment nodes |
| endor_script.rs | Batch complete inputs for Aster's Interactive parser/compiler, retain Module entry, and rebase Aster spans to the original snapshot |
| ui_bridge.rs | Inheritable native nominal constructors with source fields, shared type method tables and bound receiver dispatch, configured unified Function classification and source/field/override checks with deferred typed public/native constructor signatures and split option/field preflight plus shared native arity intervals plus resolved types/unions/effects on methods, tab registry, stable-slot insertion, source-dispatched tab reorder/drop and callback registration checks, Page lifecycle/navigation, Event returns/effect bounds, intrinsic handler recognition and immutable frame extraction |
| binding.rs | Target-owned managed property bindings, weak Subscription tokens, zero-argument callback preflight, read-only compute and borrowed dependency/output caches |
| block.rs | Aster-managed Block/Cell/data graph, stable child slots, content-owned field storage, shared assignment, cycle validation and explicit text bindings |
| snapshot.rs | Property and visual-method identity dependencies across shared field-storage paths, typed method attribute validity and protocol function payload-version dependencies, reverse parent links and cached immutable visual derivation |
| mount.rs | Typed per-occurrence methods and nominal Bounds results, managed contexts, weak reverse links, ordered mount/unmount hooks, geometry and focus queries |
| events.rs | Bounded native input transport with kind-specific tab-drop/desktop-position metadata to the owning VM |
| event_dispatch.rs | Typed Event factory and managed Event/Subscription methods, weak Subscription tokens, Block-owned connections and occurrence-specific capture/target/bubble through a budgeted VM driver |
| protocols.rs | Register typed Visual/EventTarget/Children (including insert)/Presentable managed trait requirements, using canonical signatures and explicit native type declarations |
| presentation.rs | Per-window layout, Mount/focus/tab state and read dependencies; VM changes fan out before any view consumes work |
| io_tasks.rs | Lazy bounded file-provider pool, plain-data mailbox and cancellation leases without managed roots |
| tasks.rs | Typed Task/ui.after/ui.readText/ui.fetchText/result/error methods, managed owner scopes and callbacks, weak timer index, request-key replacement, lifecycle cancellation and budgeted worker delivery |
| tabs.rs | Immutable tab chrome, shared draw/hit geometry, overflow navigation, drag gaps/anchors/insertion markers and opaque published Page identities |
| window.rs | Typed native Window queries/effects, construction/close, context ownership, ordered Page roots, read-only guards, Page reordering, physical desktop placement and coalesced GUI projections |
| vm_sequence.rs | Shared validated call-sequence builder and EXTENDED_ARG-aware same-frame repetition for layout, event and lifecycle work |
| source_layout.rs | Read-only source layout, repeated constraint/geometry solving with cycle detection, per-Mount measurement caches with subcall dependencies and actual scroll-viewport cross-axis constraints, viewport constraints, typed/slot validation, concrete intrinsic recognition and occurrence geometry |
| source_hit.rs | Read-only source/native pointer target selectors, reverse paint order, strict Bool results and matching press/release targets |
| source_paint.rs | Read-only source paint dispatch with concrete intrinsic recognition, shared VM budget, paired text-wrap bounds and checked command conversion and versioned object dependencies |
| native_visual.rs | Managed native visual protocol queries with resolved type/read-only signatures, occurrence validation, concrete implementation/operation identity, arranged-viewport child constraints, value conversion and bounded results |
| object_list.rs | Typed factory envelopes, read-only indexed access and traced results shared by Mount queries and visual protocol results |
| layout.rs | Shared native geometry/paint functions, iterative measurement/arrangement, stable occurrence IDs, measurement cache, solve-pass counters, geometry budgets and width-sensitive native text measurement |
| document.rs | Published source/AST/UI snapshot, revisions, invalidation, frame validation, single-line fault banner (full cause retained in Frame), validated optional text-wrap bounds and display commands |
| app.rs | Window events, logical viewport publication, redraw, native multi-window tab dragging and system widget pointer/keyboard/IME routing |
| clipboard.rs | UTF-8 macOS clipboard adapter independent of Finder locale, with strict decoding and surfaced errors |
| system_ui/ | Host-only management widget library: stable IDs, interaction, theme and reusable control drawing |
| scroll.rs | Per-Mount offsets, nested scrollIntoView, shared geometry/clip projection and wheel ancestor selection |
| scene.rs | Page, tab-drop marker and developer-tools display-list composition |
| developer_tools.rs | Unified Console prompt/output stream, caret/IME mapping, full Sources, Network inspector, theme, scrolling, overlay splitters and focus |
| sources.rs | Disconnected cloud workspace and provider boundary for listing/opening complete source files |
| display_list.rs | Rendering primitives, bounded wrapped text and color representation |
| geometry.rs | Native points and rectangles |
| software_renderer.rs | Software rasterization, per-text bounds and nested rectangular clipping for independently scrolling panes |
| text.rs | Shared system font discovery, shaping, real measurements, antialiased glyphs and bounded caches keyed by wrap width |
| README.md | Module map |
Aster is an independent source submodule pinned to the published managed type/native-call implementation. Language parsing, AST definitions, compilation, bytecode and VM execution come from its libraries. Tessera implements only host UI behavior and layout. No CLI, HTTP server or Docker process is needed to execute Endor in the application.
A complete-PUT candidate owns the desktop and UI service roots, with dynamic Block allocation through scoped NativeContext. Block -> Cell -> data and all child/history/forwarding references are managed VM edges. Native functions declare their captured owner edges. The bridge holds only weak diagnostic references to allocated Blocks; the tab registry owns open Pages. Closing a tab releases that edge, without destroying a still-referenced Page. Console retains the actual VM; a successful complete PUT also retains its candidate as the active program, accessible from Console and native input. Failed PUT preserves the previous heap. Cleanup releases globals/service roots, collects cycles and requires an empty VM. Window frames contain only immutable native data.
Tests in api.rs execute real HTTP CRUD, persistent Console/reset/PUT isolation and a controlled queued-PUT/DELETE race without opening a window. Tests in endor_host.rs cover lifecycle, direct script and module entry, page methods, stable-slot insertion and tab drop/override/stale-target behavior, multiline inputs, original source ranges, malformed input, owner/task cancellation, hidden-object delivery, stale request and callback failure, entry checks, source function/declared-field annotation checks, constructor side-effect prevention, native callback signature/pre-registration validation, shared field forwarding/function identity and REPL class redefinition, budgets, failure preservation, live object identity, explicit rendering, compile-before-execute behavior and stale-result invalidation. They run only in remote CI. Font tests in text.rs and software_renderer.rs cover real advances, multiline height, fractional scaling, CJK fallback, grapheme-safe fitting, antialiasing and transparency. A process-wide mutex keeps font discovery, shaping and glyph caches shared between the worker's layout and window redraw. The 256-string/512-KiB source cache and 4096-glyph/32-MiB image cache are discarded when their budgets are exceeded. See the project guide for public behavior.
The expression endpoint deliberately uses Aster's Expression entry in a fresh VM; it has no host UI, no history replay and no published document. API observation notifies the window for accepted and completed requests, including failures. The worker still owns source compilation and snapshot publication. Console sends real loopback HTTP requests on a separate client thread and reports transport failures through the native event loop. CloudSourceProvider is an optional host-side adapter, not a cloud service. A future network adapter should deliver cached/asynchronous results to the workspace rather than block the native event loop during I/O.
The System UI library owns management control appearance and interaction. It imports no Endor runtime or guest UI bridge; developer tools supply typed actions and keep all execution/source behavior outside the widget layer.
Console sessions use endor_host::ReplSession on the same worker thread. Real VM
globals, live Blocks and _ survive between inputs. Parse/compile precede execution;
only a successful input commits a pending mount or publishes changes to an existing mount.
Runtime-failed input can retain earlier completed bindings, but cannot publish its
pending render. Session budgets cover cumulative source/AST and live Block counts.
page.unmount releases current content; unreachable object cycles are reclaimed between inputs.
Explicit reset drops the VM. Successful PUT replaces it with the fresh program; failed PUT restores the displayed program’s event epoch. Code DELETE invalidates the context. Source snapshots carry annotated AST ranges for the
actual Console transcript; source history is never rerun to reconstruct memory.
The Aster pin includes independent Assignment syntax and generic opt-in assign_from support.
Live reference tests cover alias updates, nested mounts, cycle rejection, detached branch release and unmount/remount.
Stopped runtimes have a separate host-only secondary-window teardown path. It detaches Mounts and releases window/Page roots without executing source lifecycle callbacks, and does not resume the failed VM. Preserved old-generation pictures can be closed without borrowing a new Console VM's same-number resource. Secondary viewport inputs are filtered by their published runtime generation. Native window/surface failures are returned to the host diagnostics rather than panicking the event loop; main window initialization errors propagate to process exit. The stopped-window and cross-generation regression sources are compiled, not locally executed.
Native visual scalar parameters live in the Block's managed property storage. Snapshot extraction validates and reads those fields, and records exact property invalidation keys. Payload kind values remain constructor defaults; drawing uses the property projection. Binding refresh traverses the weak visual registry and deduplicates forwarded content, including hidden objects. It records object-level read versions and borrowed output values for equality without adding GC roots; cache updates do not mutate the connection's business version. Target content traces the compute function. Subscription tokens are weak and also support bindings. All binding work settles before published frames; compute failure stops the VM.
The host registers Trait definitions through Aster's checked nominal capability API. Source subclasses validate inherited method arity before class publication. Presentable dispatch uses a generated validated bytecode continuation inside the current call budget, passes the current managed Window, and validates a real Block result before opening it. Native base visual methods and runtime result checks remain the rendering boundary. Matching present names without an explicit implementation keep the summary fallback and do not execute guest methods.
ScrollBlock keeps viewport dimensions in the managed Block properties and keeps scroll offsets on Mounts. Layout measures content without a finite viewport bound; a post-layout projection clamps offsets, translates descendants and intersects ancestor clips before committing Mount geometry. It rebuilds native commands and per-command clips; source painting preserves the same clip table. Input, focus outlines and public hit queries consume this projection. Wheel transport carries finite signed deltas, validates the frame/window/session, and invokes the actual Mount.scrollBy method without changing guest focus. Mount requests mark a per-window projection refresh; snapshots/caches never own business objects.
Tab drops validate source and destination window/session/layout plus the actual VM Page order before callbacks. The bridge invokes managed Page methods, respecting source overrides that decline or redirect a window move. Reordering within a window retains Mounts; moving windows retains Page identity and replaces Mounts. Native drag feedback is host state, with no additional managed roots. Desktop placement is an asynchronous physical outer-origin request, protected by the existing session/commit boundary. Regression sources cover the HTTP event allowlist, stale frame reuse, stable insertion, source overrides and signed positions; these were compiled, not locally executed.
NativeCallable implementations publish positional metadata, including internal layout/paint/hit/event continuations. The VM validates it before entering Rust. Event delivery uses the same source/native/bound arity query; binding and Task registration reject incompatible or unknown metadata before changing ownership. Native parameter/return types and effects remain checked by existing host boundaries rather than this count-only metadata.
Native TextBlock measurement and painting now share width-sensitive shaping. Column/Button cross-axis constraints feed both the native fast path and source base calls; direct base measurement also uses its supplied viewport. RenderCommand text has optional wrap_bounds, converted to a clipped WrappedText primitive. Source PaintCommand uses paired wrapWidth/wrapHeight and legacy text remains unwrapped when both are absent. Source-arranged dimensions now participate in repeated constraint solving; general flex allocation, resumable work and axis-specific ScrollBlock wrapping remain future work.
Layout convergence uses exact finite snapshots of constraints/sizes/bounds and source-selected limits. The same immutable driver loops within one shared VM budget, clears all provisional measurement geometry between passes and rejects nonconsecutive repeated states. Aster ReadAccess distinguishes annotation validation from normal reads; the bridge skips only validation-only Mount reads after independently validating the live Mount/owner context. Actual Mount reads remain volatile; all other recorded dependencies are retained.
ScrollBlock.axis is stored in its managed properties and projected into the visual snapshot. Axis-aware child constraints use constructor/clamped size for initial measurement and actual occurrence bounds after arrangement. Scroll projection clamps disabled offsets/limits; the same geometry feeds paint and input.
Container mainAlign projects from managed properties into UiNode and its shallow equality. Native arrangement receives the actual allocated Size, computes positive remaining main-axis space and keeps child sizes unchanged.
Block.flex is a nonnegative managed weight, default 0. In native containers, fixed children retain measured main-axis sizes; positive weights share the remaining allocated space after padding/gaps with zero basis. This does not request extra parent size. Actual allocated child bounds feed back into measurement before publication. Source arrangement overrides remain authoritative. The additional flexBasis, flexMin, flexMax and flexShrink attributes control bounded allocation and opt-in shrinking along the parent main axis; see the policy below.
Trees with positive flex weights use the settling driver even when all layout methods are native. This remeasures wrapping and ancestor geometry before publishing. Fast-path eligibility checks actual intrinsic implementations, rather than comparing against a mutable Block method table.
Flex main-axis policy: flexBasis is "auto" by default (zero basis when flex > 0, otherwise the first measured size of this occurrence in the current solve); a number supplies an explicit basis. flexMin defaults to 0, flexMax to 1000000, and flexShrink to 0. All numbers are finite/nonnegative and at most 1000000; flexMin must not exceed flexMax. The parent clamps bases to min/max, distributes positive space by flex, and redistributes space left after a maximum is reached. Deficits shrink by flexShrink times the original basis, freezing at minima. A fixed child can opt into shrink without growth. Unresolvable minimum overflow is preserved, and remaining positive space follows mainAlign. The policy applies to width in a Row and height in Column-like containers; these are not independent width/height bounds. Source arrange overrides remain authoritative. Preferred sizes are derived per occurrence and reset for each new solve, never shared through Block state. See examples/flex-constraints.endor. Layout continuation remains unfinished.
When an occurrence's cross-axis constraint changes during settling, its preferred main-axis size is invalidated and remeasured. Any obsolete native flex allocation on that axis is removed for the replacement measurement, so narrowing a nested Column can increase wrapped text height. Source arrange allocations remain explicit. Preferred-size presence and values participate in convergence/cycle detection.
The source layout driver now uses Aster's resumable read-only call API with a soft quantum of 256 instructions. Pauses retain the same VM frames, operand/local roots, return contracts and read tracking; total instruction/frame limits still apply across slices. ui.stats().layoutSlices counts slices in the most recent solve (native fast path: 1). The worker currently resumes Pending immediately after yielding its OS time slice. Parking the solve across worker turns, scheduling other work and handling newer inputs before completion are not implemented yet. Native callbacks are not preempted. Only the completed settled frame is published.
source_layout::start now returns either Complete(Draft) or Pending(Work). The host-owned Work retains the unpublished frame, derived runtime/cache snapshots, convergence plan and prepare root without borrowing caller frame/cache variables. Work::resume consumes the saved job and advances the same VM continuation; Work::cancel closes that continuation and releases the prepare root without merging a draft. A second start is rejected while any read-only execution is active. Every pending Work must be resumed or explicitly cancelled with its owning VM; dropping it is not cancellation. Synchronous ReplSession operations drain the same saved refresh state. Mount preparation/lifecycle cancellation and parking jobs in the execution worker remain unfinished.
UiBridge::begin_layout_frame/resume_layout_frame now expose PendingLayout around the source job. This bridge-level continuation also owns its target window, page snapshot, tab projection, viewport restoration and retained lifecycle hook root. Only the Complete branch projects scroll/focus, captures dependency versions, merges derived caches, commits Mount geometry and runs deferred hooks. Resume restores the saved window context, and failure releases hook roots. ReplSession uses this protocol through its saved refresh state. Viewport refresh supports cross-worker-turn scheduling and prepared-Mount rollback; callers must not drop a pending bridge layout as a substitute for cancellation.
ReplSession now owns a resumable RefreshWork containing window traversal, pending bridge layout, unpublished per-window documents and the cumulative refresh work budget. poll_refresh advances one refresh stage or VM quantum; only a complete stable pass replaces the session's full document map. Synchronous callers drain the same state machine. Failure stops the domain, releases any pending bridge work and preserves the externally published picture. refreshYields counts cooperative refresh returns, not wall-clock time. Only still-pending Mount preparation is rolled back; completed callbacks remain committed.
Viewport refresh now advances through ReplSession's saved refresh state on worker idle turns. A turn advances one traversal stage or a 256-instruction layout quantum; a pending job shortens the next worker wait to 1 ms. Tasks/window commands and document publication wait for complete refresh. ui.stats().refreshYields reports cooperative idle returns. Mutating requests wait across worker turns for the existing job, so they cannot run inside its read-only scope. Mutating requests are serialized; newer viewport targets coalesce by cancelling unpublished layout. Initial source execution remains atomic; its layout can park across worker turns. Explicit REPL/event refresh can park across worker turns. Idle tasks deliver at most one callback per turn and park subsequent layout; callbacks and individual native nodes remain atomic. Large intrinsic trees use the sliced driver; small intrinsic trees keep the fast path.
Runtime mutation entry points reject direct calls while refresh work is pending, before executing source or changing metadata; task polling defers. The API worker drains the saved refresh before accepting those mutations, keeping the runtime usable. Incremental refresh keeps the prior complete document map until a stable pass, including across window traversal boundaries where no VM read-only frame is currently active.
Mount preparation now has an explicit checkpoint that roots the previous dictionary, Mount payloads and metadata values. It can restore geometry/relations/registry after cancelling an unpublished layout, or release its roots after completion. Task cancellation moves from speculative preparation to immediately before the actual onUnmount callback, so cancelling preparation does not destroy the old occurrence's tasks.
Pending bridge layouts now retain a Mount checkpoint. cancel_layout_frame closes the VM continuation, restores the old Mount payloads/links/dictionary and marks the presentation dirty for a new solve; discarded lifecycle hooks do not run. Successful finalization releases checkpoint roots after callbacks. This restores speculative Mount preparation, not arbitrary application writes or already completed lifecycle callbacks.
New viewport targets now coalesce pending refresh work. The runtime cancels only an unfinished bridge layout, restores its Mount checkpoint, preserves completed per-window drafts/callback effects and restarts traversal with the latest viewport map. refreshRestarts counts target replacements; layoutCancels counts replacements that actually closed a pending VM layout. Unchanged targets resume the same job. A new external target resets its convergence budget; internal settling does not. Ordinary source/event mutations still serialize behind refresh rather than roll back application effects.
Cancellation checkpoints own explicit VM roots until restored or released. Initial presentations may have no mount dictionary; cancellation restores that absence too. Saved metadata values retain object-valued attributes. A pending refresh can restart while keeping already completed window drafts; no onMount/onUnmount callback is replayed merely because the viewport target changed. Mount-owned tasks are cancelled immediately before an actual onUnmount callback, never during abandoned preparation.
source_layout uses the same resumable node driver for trees over 64 occurrences even when measure/arrange are intrinsic. Small intrinsic trees retain LayoutRuntime::frame. The larger path keeps LayoutRuntime measurement caches and yields between node calls; structural extraction, bytecode preparation, a single node, settling and paint remain atomic.
Worker idle polls use ReplSession::poll_tasks_incremental with the current viewport map. Scheduler::poll accepts a checked 1..=64 work limit, counting both delivery and ineligible-task cancellation; idle uses one, synchronous compatibility uses 64. Once delivery begins layout, further tasks and window-command projection wait until the saved refresh completes. The viewport stamp allows cancellation of task-induced pending layout without repeating callback effects.
REPL and event mutations now have incremental refresh entry points. They execute source/callbacks once, retain scalar response metadata, then park layout with the current viewport target. While pending, mutation, command extraction and garbage collection wait; layout can be retargeted by a newer native size. Synchronous helpers remain for host regression coverage. Discarding a ReplSession explicitly cancels any still-pending bridge layout before the Candidate releases service/global roots. This does not preempt source/callback execution, structural preparation or paint.
The API worker now owns a pending HTTP request plus its scalar result while REPL/event layout is parked. It advances the layout with current native viewport targets and responds only after the stable full-window publication and command extraction. Later mutation requests remain queued; GET /code can observe the last complete publication. A newer accepted PUT invalidates queued inputs but lets the already-running owner settle before candidate construction, so failed replacement can preserve its consistent heap. DELETE revokes that owner and prevents late publication. This queue ordering does not roll back already-executed source effects.
Complete-source candidates now separate one-time compilation/source execution from initial layout. load_incremental owns the isolated VM while its first layout is pending; the current live VM stays separate. Cancellation uses the same Mount checkpoint/continuation cleanup as later refresh. Synchronous load helpers remain for host regression coverage. This does not slice parsing, compilation, mutating source execution, structural preparation or final command projection.
The API keeps the old runtime alongside a pending PUT candidate. Newer submissions cancel only the obsolete candidate; failures restore the published runtime's submission owner. Background epoch checks distinguish accepted replacements from DELETE revocation, so queued/superseded candidates do not destroy the previous live heap. Candidate resumption reads only the primary shell viewport, avoiding reuse of an old secondary window's dimensions for a new VM's resource with the same numeric ID. Success publishes the complete candidate document and commands together, then replaces the old runtime.
Source paint now uses a resumable read-only VM call with the same 256-instruction quantum and a cumulative 1,000,000-instruction budget. The pending job owns the unpublished frame, visual snapshot, decoded command buffer and prepare root. The refresh worker resumes paint before advancing to another window and publishes only the full stable window set. New viewport targets cancel unfinished paint, discard partial commands and force that window to refresh. Already completed layout/Mount geometry and lifecycle callbacks remain committed; cancelling paint does not replay or undo them. Failure/discard explicitly closes the read-only continuation and releases its roots. paintSlices reports the most recently completed paint solve (1 when the solve completes within its first quantum); paintCancels counts viewport replacements that closed pending paint. Layout checkpoint rollback remains limited to unfinished layout preparation. Native callback bodies, driver preparation, command decoding/projection and rasterization remain atomic.
Layout and paint drivers now prepare one occurrence at a time. Their fixed-size outer bytecode repeats on the same VM frame; a short-lived helper retains only the current call, its bounds/arguments and collector. The helper is released before the next occurrence. Layout keeps bottom-up measure, top-down arrange and the same settling loop. Paint advances even intrinsic method classification through VM quanta. The weak occurrence list and native scratch/output data remain proportional to the actual tree, but preparation no longer allocates managed callables/Bounds for the whole tree before the first slice. All helpers share the original read-only scope and cumulative instruction/frame limits. Weak-list copying, structural extraction, settling and final projection remain bounded atomic native work.
Presentation dependency diagnostics record a counter and the last invalidated object kind/identity and version transition. ui.stats exposes dependencyInvalidations and lastInvalidDependency without retaining the dependency or rendering its data. windowDependencyDiagnostics includes nonempty reasons from every window, prefixed by window identity. Snapshot replacement also records its last invalidating object/property or change-journal overflow, so cache invalidation can be distinguished from source layout/paint dependencies. These help distinguish expected input invalidation from repeated background refresh.
ui.readText(owner,key,path[,callback]) returns an owner-scoped Task and reads a regular UTF-8 file off the VM thread. Paths use native filesystem syntax; absolute paths are recommended. The optional callback takes zero arguments and must return Unit; it runs only on successful read and can use task.result(). Without a callback, poll status then read result from code. result() is available while a success callback is running or after completion (timers return Unit); error() returns an empty String or the provider/callback error. A provider failure marks the task failed and skips its callback without stopping the VM; a source callback failure still stops the shared domain. Owner/key replacement, explicit cancellation, close/reset/VM discard and GC drop the provider lease, remove queued completion and reject late delivery. In-progress OS reads may finish, but cannot deliver after cancellation. Only the owning VM worker updates managed results and rechecks owner/key identity. No VM handles enter provider threads beyond opaque weak identities.
The lazy shared pool has four threads and a 64-job queue. Each runtime permits 16 outstanding I/O requests (one temporary replacement reservation), and each read is capped at 1 MiB by metadata plus actual streaming length; non-regular files, non-UTF-8 data and over-limit content fail rather than truncate. These limits do not constitute a full filesystem sandbox. The provider returns text in the same managed Task, not a second application-state store. Task await is supported through ui.spawn; see the await contract below. See examples/async-file.endor; from REPL call reader.read("/absolute/path.txt") to update its actual TextBlock, even when that object is hidden but retained.
ui.fetchText(owner,key,url[,callback]) performs an asynchronous HTTP(S) GET using the same Task, owner/key replacement and success-only callback protocol as readText. Example: fetcher.fetch("http://127.0.0.1:4737/agent") after loading examples/async-network.endor. The example retains the complete result in fetcher.response and displays completion status; it does not put arbitrary network content into a TextBlock, whose own 16 KiB limit is independent of the provider limit. Only 2xx responses produce a UTF-8 result; HTTP failures, invalid UTF-8, oversize bodies and transport errors set status() to failed with error(). It verifies HTTPS certificates, follows at most five HTTP(S) redirects, has a 10-second connect and 30-second total transfer timeout (starting when a worker takes the request), and bounds decoded text to 1 MiB without truncation. URLs are limited to 8192 bytes and must not contain credentials or control characters. This GET API does not accept request bodies, custom headers or authentication.
The current transport uses a separate system curl process per request (/usr/bin/curl on Unix, curl.exe on Windows); missing or incompatible curl fails the Task. User curlrc and URL globbing are disabled; URL data is passed through escaped stdin configuration rather than command arguments. TLS and normal system proxy behavior remain enabled. Cancellation kills an active HTTP process immediately and the provider worker reaps it; the VM thread never waits for the process. All file and HTTP requests share the per-runtime 16-task limit and global four-worker/64-job queue. In-progress file reads remain cooperatively cancelled at delivery. Network access is not a security sandbox, and this is a text GET provider rather than a general HTTP client. Task await is supported through ui.spawn; see below.
ui.spawn(owner,key,function) creates an owner-scoped cooperative Task for a zero-argument function; its returned value is retained as the Task result. Each owning VM poll resumes at most a 256-instruction quantum per selected Task, rotates runnable tasks fairly, and retains source frames in an Aster-managed coroutine edge. Calls and writes may interleave between quanta; prior writes are not rolled back on cancellation or error. Pending/running Task replacement, owner closure, GC and reset release parked frames through the same managed graph. Native calls remain atomic and all quanta share the 128-frame/100000-instruction callback limit; a native entry may run during preparation. result() is unavailable until spawn completes. A source error still stops the shared runtime. See examples/cooperative-tasks.endor. Endor await expressions now suspend these calls on a Task without replaying the operand. See the await contract below.
Inside a function run by ui.spawn, value = await task waits for any timer/file/network/spawn Task and resumes at the same expression with its retained result. A source function may return a Number, String, managed Object or Unit; normal source annotations still apply. Task.result() and await share the result. ui.sleep(owner,key,milliseconds) creates a callback-free timer Task returning Unit. Plain calls/REPL can await an already completed Task; a pending await requires a coroutine and fails explicitly rather than blocking. There is no new async function declaration: ui.spawn explicitly schedules an ordinary Endor function. See examples/await-file.endor.
A waiting coroutine retains its evaluated operand and frames as managed edges; waiting performs no VM execution or active 1ms spin. Source side effects and IO initiation before await are not replayed. When the dependency completes, the owning VM worker resumes the waiter through the existing fair quantum scheduler. Cancelling/replacing/closing the waiter releases its frames and operand; it does not automatically cancel an independently owned or shared dependency. Dependency cancellation propagates cancellation to its waiters without running the remainder. An unhandled dependency failure becomes a source await error (with the original provider message), fails the waiter and follows the existing shared-runtime fault rule; provider failure alone still only fails its own Task. Self-await and cyclic Task waits fail explicitly. Read-only layout/paint cannot await. Waiting state is reported as running, while active() remains true; result() is unavailable until completion. These APIs do not introduce try/finally or change the existing exception model.
While a native tab drag is held over an overflow arrow area, the strip waits 350ms then reveals one hidden tab every 120ms, including in a compatible destination window. The preview scroll does not activate Pages or change business state. Drawing, hit testing and insertion gaps share the same transient visible range; release uses its full-order insertion index. Leaving the strip stops the timer; release, Escape, a new press or runtime generation change restores the normal active-tab-centered strip. A resize or changing tab count clamps the range. Native drag/mixed-DPI behavior still requires platform validation; application-exit cleanup is described below.
CI regression fixtures use the published frame's session identity for input, current Endor statement boundaries/type names and the retained runtime's actual layout/paint pipeline. Failed first Console rendering discards unpublished Page registrations through host-only cleanup, preventing the next successful query from silently opening a previously failed pending render. Earlier ordinary bindings remain live; explicit page.render can subsequently publish the Page.
Application exit now uses a private VM-worker control channel, independent of HTTP queue capacity. Main native close and a managed primary Window.close request asynchronous cleanup; duplicate requests coalesce and mutation requests are rejected with 503 while exiting. An unpublished candidate/input refresh is cancelled without replay. The current VM cancels all Task entries, closes managed windows, detaches occurrences child-before-parent per window, and invokes normal onUnmount hooks once. It does not run layout/paint during shutdown. All windows are closed before hooks run; new windows and tasks are forbidden. A stopped/faulted VM detaches without source hooks; reset/discarded pictures cannot revive an old VM. A hook error aborts remaining source hooks, releases prepared hook roots, drops the VM and reports failure before native exit. Completed shutdown is acknowledged to the GUI, which then ends the event loop. An OS exit bypassing CloseRequested uses a five-second bounded final fallback; non-cooperative native calls remain atomic and force-kill/crash cannot guarantee notifications. Native stderr records the window/mount/task-entry counts and whether source hooks were enabled, or the cleanup error. See examples/exit-lifecycle.endor. Cross-platform exit/drag validation remains broader follow-up work.
Foreground REPL/events now retain a BeforeInput request while an existing idle layout/paint refresh advances one stage/quantum per worker turn. They execute their source only after that refresh settles, so mutations never enter its read-only scope. New task callbacks are not delivered ahead of this accepted input. Shutdown can cancel the waiting request with 503, and superseded submissions still return 409. Reset cancels/discards the old VM instead of synchronously draining its unpublished refresh. Source parsing/compilation and ordinary synchronous source/native calls remain atomic boundaries; this change removes the worker's synchronous pre-input refresh drain.
Source layout/paint now validate name-level read observations from Aster: each globals/builtins lookup records its text key and observed presence/value, while dictionary kind checks remain separate. Updating the REPL result _ or another unread global no longer invalidates these frames solely because the whole globals dictionary version changed. Reading _ still observes its value; adding a previously missing global correctly invalidates a builtin fallback. Ordinary native dictionary iteration/content reads retain whole-object version dependencies. Measurement cache hits replay their recorded observations into the outer solve; they do not drop dependencies merely because source execution was skipped. Cache observations contain borrowed handles (including object-valued names), add no GC roots, and never write comparison snapshots back into language objects. Invalid-name diagnostics report only identity/key, not captured values. General Block binding and other legacy readers keep their existing conservative object dependencies. See examples/global-dependencies.endor.
Source layout/paint and measurement caches exclude every presentation's private desktop identity, including the primary desktop captured by shared native visual methods. Per-window bookkeeping must not invalidate another window's source output. This exclusion does not cover managed Window objects, viewport input records, type definitions or business objects; their observed changes remain dependencies.
The gc-lifecycle example and endor_host regression combine a mounted Page/Block cycle, source measure/paint dependencies and an owner-scoped coroutine suspended on sleep. Closing the tab preserves a referenced Block's workflow; releasing the external Page/Task holders lets normal collection reclaim the cycle before the wait completes. Regression assertions check both the same-program object baseline and weak identities.
The API worker logs the submission identity once when a newly accepted input enters BeforeInput waiting. It omits source and runtime values, and resume quanta do not repeat it. This distinguishes cancellation of an accepted pre-input wait from cancellation of an unpublished candidate or a request still in the transport queue.