Files

260 lines
52 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 从当前命名空间生成 /agent 使用指南。返回的示例、地址和版本描述来自运行时上下文;这里生成文档,不执行用户源码。
//!
//! Live agent-facing guide generated from the current namespace.
use super::*;
/// 用当前命名空间、地址和版本填充在线指南,示例来自项目内置 Endor 源码。
pub(super) fn agent_guide(namespace: &NamespaceState) -> String {
format!(
r##"# Tessera Agent API
Current namespace: `{name}`
Current revision: `{revision}`
Submit an ordinary Endor script. Tessera embeds Aster's frontend, compiler and VM.
The namespace comes from the request path. Top-level bindings share the candidate
VM's globals; `ui.page().render(root)` selects the visible UI. No main function is
required. Typed helper functions and the older main-returning-View form also work.
## Code CRUD
- Read: `GET http://{address}/v1/namespaces/{name}/code`
- Create or update: `PUT http://{address}/v1/namespaces/{name}/code`
- Delete: `DELETE http://{address}/v1/namespaces/{name}/code`
Send PUT bodies as `text/plain; charset=utf-8`.
```endor
{example}```
## Live Console session
`POST http://{address}/v1/namespaces/{name}/repl` accepts statements, function
definitions and expressions as text/plain. Consecutive requests use one real VM
and globals dictionary on the execution worker. `title = ui.text("Hello")` creates
an object without displaying it. A later `page.render(root)` mounts its page. UI references share replacement state: after mounting, assigning a new UI value to a reference updates its aliases and mounted positions. `page.unmount()` releases the mount.
`let title = ...` is also supported. `_` retains the actual last value. No input
history is replayed. Responses include session/input IDs, value, binding count,
rendered flag and revision. Before the first mount the page/revision stay unchanged; afterwards successful inputs publish changed mounted references automatically.
`DELETE http://{address}/v1/namespaces/{name}/repl` resets Console objects and bindings,
leaving the displayed native snapshot intact. Parsing/compilation happen before
execution. On runtime error earlier statements in that input may remain live, but
its pending render is discarded; previous displayed page is preserved.
Session limits: 256 executed inputs, 1 MiB transcript, 10000 cumulative AST nodes,
4096 retained visual objects and 64 retained pages. Aster collects unreachable objects/cycles between inputs; there is no fixed UI pool. No VM handles cross to the window.
A newer accepted complete-source PUT or code DELETE invalidates queued Console inputs. A running input settles before a queued PUT candidate is built; code DELETE revokes that runtime immediately.
A successful PUT retains its fresh VM as the live program for Console and GUI events;
a failed PUT preserves the previous program. DELETE releases the live context.
GET /code returns the source of the displayed snapshot: exact submitted source for
PUT, an annotated Console transcript after a REPL render. X-Tessera-Source-Kind
identifies source-snapshot versus repl-transcript; a transcript is not a replay log.
The compatibility `POST http://{address}/v1/namespaces/{name}/evaluate` endpoint
still accepts one isolated expression in a fresh VM without ui or previous bindings.
It does not publish a page or change revision. Console does not use this endpoint.
All execution endpoints share the eight-entry queue. These are in-process bounded
host executions, not a complete untrusted-code sandbox or a process-isolation design.
## Layout, scenes and cameras
Use Page with nested Row / Column / Overlay for finite layout, ScrollView for
scrolling, and CanvasViewport(width,height,Scene2D(...)) for a finite view into
2D world coordinates. Each viewport owns an explicit Camera2D by default;
assigning the same camera shares navigation. Viewport3D provides the analogous
finite view into Scene3D using programmable perspective/orthographic cameras.
See docs/scene-architecture.md and examples/world-ui.endor for the current model.
### Legacy Page compatibility
Page.mode is "window" (default), "vertical", "horizontal" or "canvas".
Window fills the available page viewport. Vertical constrains width and grows
height with children; horizontal constrains height and grows width. Scroll
finite pages with the wheel; the offset clamps to their actual extent.
Canvas arranges direct children at signed child.x / child.y world positions.
page.render(root) replaces content; page.append(child) adds another child.
Changing these attributes updates live occurrences, without copying objects.
Canvas uses Page.panX / panY as the world origin and Page.zoom (0.1..4).
Wheel/trackpad pans, Space+left drag or middle drag pans, pinch or Cmd/Ctrl+wheel
zooms around the pointer. Positions are finite and bounded to +/-1000000 by the
existing renderer budget; canvas navigation is not clipped to content extent.
The camera belongs to the Page and survives page switching.
Nested ScrollBlock input is handled before page panning.
Native transport kinds "page-pan" and "page-zoom" use mount=0 and current
window/session/layout_version. Pan delta is a two-component viewport-pixel
displacement. Zoom uses delta=[positive_scale_factor,0] and point=[x,y] in
viewport coordinates. Ordinary click/scroll coordinates remain world coordinates;
the host inverts the camera and the worker rechecks visibility and frame identity.
## Managed visual types
`Object`, `Type`, `Function`, `Task`, `Block`, `ContainerBlock`, `TextBlock`, `RectBlock`,
`Spacer`, `Row`, `Column`, `Button`, `Page`, `Mount`, and `Event` are managed nominal
Type objects. Use `page = Page()` and `label = TextBlock("Hello")` in Endor source.
The existing ui constructors return these same typed objects. `page.type`,
`Page.base`, `Page.name`, `isInstance(page, Block)` and `isSubtype(Page, Block)`
expose actual type relations. Types, bases, instances and native constructors live
in the same GC graph. Ordinary source classes support `class Child: Base`, typed field
declarations, explicit first `self` parameters, positional field construction and
member assignment. Inherited methods bind the actual receiver; overridden methods
are dynamically selected through self. Concrete native visual subclasses reuse real native allocators; abstract bases remain unconstructible. Explicit traits and source parameter/return annotation checks are enabled; declared-field construction/write checks are enabled alongside source function checks; complete initialization and static/effect typing remain pending. Native Block/Page methods use the same type method tables and binding path.
Window is a host-provided managed resource: ui.window(), page.window(), page.open(window), window.pages()/id()/viewport(), resize(width,height), moveTo(x,y), setTitle(text) and focus(). Each window has a collapsible left sidebar for pages and Console/Sources/Network/Settings. Cmd/Ctrl+B toggles it. Collapsed, only the sidebar toggle and native macOS window controls remain over page content. Viewport queries report the actual available page area, with no top strip or content inset. Page selection/closure calls actual managed Page methods; Ctrl+Tab cycles pages, Cmd/Ctrl+T creates one and Cmd/Ctrl+W closes the active page. Window() creates another native resource in the same VM, up to 16. Page.open(window), moveToWindow(window) and detachToWindow() preserve business identity and replace occurrence Mounts. Window.close() releases Page roots and retires its presentation; primary native close exits the application. Resource changes publish only after successful input/candidate completion. Frame/session/window generations reject stale events and commands. Sidebar pointer drag/reorder is not implemented.
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. Native stderr records the submission once when a request first waits for an active refresh; it does not log source text or repeat the line for each quantum. Ready layout/paint/coroutine quanta and pending input/candidates yield to ready OS threads without a fixed timer delay; queue, shutdown, generation and viewport checks still run between quanta. Only idle work or external waits use the normal 100 ms polling wait.
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. Block binding uses the same precise observations through the synchronous read-only API; other legacy callers of the object-list API remain conservative. See examples/global-dependencies.endor.
Page navigation is in the left sidebar; the former horizontal strip and its drag/overflow animation are removed. Managed moveTab/moveToWindow/detachToWindow and the version-checked tab-drop API remain available. Sidebar pointer drag/reorder and mixed-DPI cross-window gestures are not implemented in this revision.
ContainerBlock.insert(position,block) accepts a zero-based position through the current count, returns a new stable slot and preserves existing slots/Mounts. Invalid indices and child cycles fail before changing the child list. Page.moveTab(position) uses a final zero-based index in its owning window. Native drops invoke the actual Page moveToWindow/moveTab/activate methods; a declined or redirected window move skips destination reordering. A drop allows 300000 instructions per method. Source overrides participate; earlier writes do not roll back after failure. Window.moveTo(x,y) queues the physical desktop outer origin, with signed integers within +/-1000000. Detached placement preserves the saved grab offset; the window manager may adjust it and unsupported global placement reports a diagnostic. These movement methods remain callable from Endor and transport clients; the sidebar currently provides selection and closure rather than pointer dragging. See examples/tab-placement.endor.
Source measure and arrange overrides execute bottom-up and top-down in one read-only VM layout call. Structural Mount links exist before first measurement; bounds queries reject premature geometry. Native base layout uses current occurrence child measurements, and Arrangement must assign every stable child slot exactly once. Layout dependencies changed by lifecycle hooks trigger another pass before publication. Page constraints now use the current logical window viewport. Column/Button propagate cross-axis width and Row propagates cross-axis height; desired sizes are clamped. ui.viewport() provides a managed snapshot whose private input participates in rendering dependencies. Native resize updates are coalesced on the same worker without adding source inputs. Source measurement caches use Mount/method/children/constraints and actual subcall object reads, including returned-result decoding; Mount-reading methods conservatively bypass caching. ui.stats() exposes sourceMeasureHits/sourceMeasureMisses outside read-only rendering. Native TextBlock wrapping now uses the occurrence width in measurement/cache/painting; source measurement/arrangement now repeats until constraints and geometry settle; additional allocation policies remain pending; ScrollBlock scrolling is described below.
Source layout solves repeat in one read-only call until constraints/sizes/bounds are unchanged, reusing one bytecode frame. Source-arranged changed dimensions become exact per-axis constraints for this solve; source container measurements differing from native results constrain content using the same container policy. Selected limits reset on later inputs/resizes. Nonconsecutive repeated states report a cycle. No fixed pass/hop cutoff is used; all passes share the 1000000-instruction/128-frame budget and budget failure is explicit. Measurement clears provisional Mount geometry, and only complete settled frames publish. ui.stats().layoutPasses reports work; source/cache counters include all passes. VM annotation checks distinguish validation-only from content reads, preserving Mount-typed independent measurement caching; actual Mount reads still bypass conservatively. Resumable solving remains pending. See examples/layout-negotiation.endor.
TextBlock now wraps words/glyphs at its available width and measures the resulting height. Native and source-base measurement paths use current constraints; native shape/font caches include wrap width. Painting carries allocated width/height, clips glyphs and keeps ancestor scroll clipping. Source text PaintCommand may supply both wrapWidth/wrapHeight (finite nonnegative logical dimensions up to 1000000); neither preserves legacy unwrapped paint, and a partial pair is rejected. Native TextBlock.paint supplies the pair. These are derived draw bounds, not another business object. Management UI text remains unwrapped. Resumable solving and broad fractional-DPI validation remain pending. See examples/wrapped-text.endor.
Source paint overrides execute in the actual frame pipeline under a read-only heap policy. Source hitTest overrides similarly refine native pointer targets in VM paint order; Point.inside(bounds) and Point.inCircle(cx, cy, radius) provide primitive shape queries. Managed DisplayList/ObjectList/PaintCommand and Constraints/Bounds/Point results are ordinary runtime objects.
ScrollBlock(width,height,child) is a ContainerBlock with managed viewport dimensions and independent per-Mount offsets. mount.scrollTo(x,y), scrollBy(dx,dy), scrollPosition() and scrollIntoView() operate on actual occurrences. Position snapshots expose x/y/maxX/maxY from committed layout; relative requests accumulate and offsets clamp after content/viewport changes. Detached Mounts and read-only rendering mutations are rejected. scrollIntoView and requestFocus adjust nested scrollable ancestors. Native/source paint, focus outlines, hit testing and pointer selection share translated geometry and ancestor clips. Remounting starts a new offset. Paint x/y may be signed after scrolling; dimensions/radii remain nonnegative and coordinate budgets still apply. See examples/scroll.endor. Wheel chooses one eligible ancestor (not per-axis split); native scrollbar dragging, touch inertia and PageUp/PageDown remain pending.
Native wheel POST /events uses kind scroll with the normal window/session/layout_version, a scrollable mount ID, point=[x,y], delta=[dx,dy], and no press_point. Deltas are signed finite logical pixels within +/-1000000; positive moves toward the end of content. Other kinds must omit delta. Native Shift+wheel maps to horizontal. The host checks the active committed frame, target attachment and clipped viewport before invoking Mount.scrollBy. These fields are transport metadata; source code uses the Endor methods above.
ui.after(owner,key,milliseconds,callback) returns Task. The zero-argument callback runs on the owning VM worker after at least the integer 0..86400000 ms delay and must return Unit. Idle polling can add up to 100 ms; execution load may delay delivery further. Task.cancel/active/status expose cancellation, eligibility and pending/running/completed/cancelled/failed state. One owner/key replaces the prior pending task. Owner scopes and callback captures are managed strong edges; scheduler and owner links are weak. ui is an application owner. Hidden live objects continue; Page close cancels its and its Mount tasks, even close/reopen within one input. Page movement preserves Page tasks but replaces Mounts. Window close cancels its Pages/tasks; reset/DELETE/program replacement discard old scheduler/VM and generation checks reject stale publication. Failed PUT preserves old tasks. Callback failure stops the domain and cancels remaining tasks; earlier writes do not roll back. Read-only rendering cannot schedule/cancel. Limits: 64 pending per owner, 1024 per runtime, 64 delivered per poll, 128 frames/100000 instructions per callback. Providers support timers, asynchronous text-file reads and HTTP(S) text GET; await is supported in ui.spawn calls.
Source function parameter annotations are checked before the body and return annotations before returning. Object accepts any live value; Unit accepts nil; Number, String/Text and Bool match scalar kinds. Function includes source/native functions and bound methods, while callable class definitions remain Type. View is a compatibility alias for Block. Other names resolve actual Type/Trait objects in the function's globals then captured builtins at each call. The method's own class uses the receiver's actual owning definition, preserving old bound methods after REPL redefinition. Other type names are not frozen at definition. UnknownAnnotation and TypeMismatch report failures; bad returns do not roll back earlier writes. Declared-field construction and set_attr writes are also checked, as described below. Static inference, generic and static effect typing remain pending.
Declared fields are checked at construction and set_attr writes through the same enabled type-check switch. Native subclass fields are validated before native allocation/opening effects. Failure preserves the field; declared deletion is rejected. Inherited fields use their declaring definition/environment, the declaring class name keeps its actual identity after REPL redefinition, and other names resolve dynamically in declaration globals/builtins. Shared Block forwarding selects the current content type/storage. This does not scan old objects or revalidate all referring fields after shared-reference changes; trusted payload/storage/attach operations and complete initialization remain host responsibilities. Static/effect typing and local/captured type annotations remain pending. See examples/field-contracts.endor.
Native methods now publish positional CallArity intervals, checked by the VM before invocation. Source defaults and bound receivers participate in the same query. Traits validate acceptance of their canonical count including self and reject native methods without metadata. Event listeners/onClick require Function metadata accepting zero arguments or one Event, preferring Event if both work. Binding computes and Task callbacks require zero-argument acceptance before registration/replacement. Generic Type constructor reflection and complete native signature coverage remain separate gaps. See examples/native-signatures.endor.
Native CallSignature checks resolved parameter/return types for visual measure/arrange/paint/hitTest, Point queries, Window/Block/Container/Page/Mount/Task/Event/Subscription methods and ObjectList.at. Signature definitions are traced managed edges. Parameters fail before dispatch; final results delegated to source/native functions must satisfy all pending native return contracts, with rooted definitions and the same frame/instruction budgets. Wrong returns release scoped results but do not undo earlier writes. CallEffect ReadOnly allows result allocation; Delegated allows a read-only native wrapper while delegated targets keep the same VM read-only/budget scope (ui.present); Mutating/External calls are rejected before invocation during read-only execution. Block.click returns its generated Event; Block.dispatch returns the delivered Event. Dispatch/click and lifecycle handler contracts permit External effects; the default no-op does not constrain native overrides to ReadOnly. Existing heap barriers still apply, and this does not sandbox trusted native I/O. CallType unions preserve every branch definition. Page.open accepts Window or nil; page.window returns Window or nil; Block.on accepts Bool or String for its optional phase, while phase names retain domain validation. Mount relation queries are nullable, and bounds() is a real Bounds object usable by Point.inside. Native union metadata does not add Endor union syntax. Source method overrides now enforce parameter contravariance, return covariance and accepted-count preservation at class creation/method replacement, with call-time rechecks for dynamic annotation names. Ancestor replacement checks overriding descendants atomically. Unknown forward names may defer until invocation; known incompatibilities reject before effects. Known effects may only shrink; conservative source/call-graph ReadOnly proofs supplement runtime barriers. See examples/method-contracts.endor. Public ui functions, captured state methods and native factories publish signatures after the registry exists. Factories check the Type receiver/general result/effect and split native options from requested-class fields before validating both; omitted Rect/Text options and Row/Column variable children cannot absorb subclass fields. Event/ObjectList keep dedicated member/field checks. Generic Type reflection still has no single fixed constructor signature. See examples/constructor-contracts.endor. Current global/builtins/closure/receiver call graphs may prove ReadOnly without execution; unresolved targets/results, shared writes and Delegated targets remain unknown. Unknown legacy metadata remains unknown. See examples/visual-protocols.endor and examples/layout-negotiation.endor.
Trait definitions Visual, EventTarget, Children and Presentable are managed nominal capabilities. ui.implement(type,capability) explicitly declares an implementation after required-method/positional-arity/type/known-native-effect checks; ui.implements(type,capability) and ui.supports(value,capability) query it. Block declares Visual/EventTarget; ContainerBlock declares Children, inherited by Page. Same-name methods alone never declare an implementation. Source subclass construction and host method replacement validate inherited/affected descendant contracts. Source/native positional intervals are checked, and missing native arity metadata prevents a capability declaration; source parameter/return annotations are checked at invocation. These capabilities use typed signatures: registration checks parameter contravariance, return covariance and known native effects. Actual calls recheck dynamic names and final results across delegation, with rooted return definitions. Explicit typed method annotations remain checked even if a different host disables global source checks; concrete implementations keep wider inputs and optional convenience forms. General source effects and formal trait syntax remain pending. EventTarget requires canonical on/dispatch/onEvent arities 3/2/1 after self; native optional convenience forms remain.
For a plain object explicitly implementing Presentable, present(self, window: Window): Block is invoked by ui.present(value) or ui.open(value), using the current managed Window and the same VM budget. The result must be an actual Block. Blocks pass through directly; unregistered values retain the summary fallback. Source views retain their business object via managed fields/callbacks/bindings, as in examples/presentable.endor. Viewing function definitions does not itself execute them. Rendering methods still run read-only with nominal result checks.
Block.bind(property, compute) binds an existing managed Block field to a zero-argument function and returns Subscription (disconnect/isConnected). This includes source-defined fields and native TextBlock.text/size/color, RectBlock.width/height/radius/color, Spacer.size and container padding/gap. Native fields can also be assigned directly. The target content owns compute/captures; tokens and host registry are weak. Rebinding disconnects the old connection; disconnect keeps the last value. Actual reads track global-name presence/value separately from whole-object reads; ordinary dictionary content access remains conservative. No explicit source-list parameter is required. Unrelated global updates reuse valid compute results, while direct output overwrites still trigger repair. ui.stats().bindingEvaluations counts computations in completed refreshes over this VM lifetime, including initial and hidden bindings. Hidden retained Blocks update at successful input/event stabilization boundaries before all windows publish. Computes are read-only for existing objects and may allocate results. They have 100000 instructions/128 frames each, 128 active bindings per Block and a 10000-computation refresh limit that errors rather than truncates propagation. Binding failure stops the live VM and preserves published pictures. Non-Block targets, explicit dependency lists and general type/effect checking remain pending. See examples/property-bindings.endor.
Native input POST /events is a transport envelope, not Endor source. Each frame and input carries an opaque runtime window identity; omitted input window defaults to 1 for existing clients. GET /code exposes X-Tessera-Window. A window/session/layout identity mismatch is rejected before invoking code. Tab actions use kind tab-activate/tab-close/tab-detach/tab-drop and the mount wire field carries the opaque tab ID from frame.tabs; point/press_point must be omitted. tab-drop additionally requires tab_drop={{window,layout_version,index}}, where index is a published destination gap from 0 through its tab count (at most 64). Both source and destination orders must still match their published frames. tab-detach alone accepts optional screen_position=[x,y] as signed integer physical desktop coordinates within +/-1000000; other kinds must omit it. All spatial/tab actions validate session and layout_version before dispatch. Resource window-close uses mount=0 and no points; it validates the live window/session rather than its old layout version. The window
sends current session, layout_version and Mount identity; stale positions fail without calling a callback. Pointer clicks also include point and press_point [x,y] in logical page coordinates. Both targets are resolved through read-only hitTest, with fallback behind a rejected candidate; a click is dispatched only when both resolve to the same Mount. Keyboard/code clicks omit coordinates and remain semantic activation. GET /code also exposes X-Tessera-Session and X-Tessera-Layout-Version headers for transport clients. Native mouse, Tab, Enter and Space delegate to the same
managed click/focus methods used by code. Events appear in Network, not Console source history.
ScrollBlock.axis is a managed visual attribute: "both" (default), "vertical" or "horizontal". The default preserves two-axis unconstrained content. Vertical restricts content width to the actual occurrence viewport minus padding; horizontal restricts height. Source-arranged viewport dimensions are used in subsequent measurement. The disabled scroll axis has zero offset/limit and is clamped after updates. Composition remains vertical; a Row child can arrange horizontal content. See examples/scroll-axis.endor.
ContainerBlock.mainAlign accepts "start" (default), "center", "end" or "space-between". Native arrangement uses allocated Bounds after padding/minimum gap, distributes positive remaining main-axis space and keeps child sizes unchanged. Overflow keeps start placement. Explicit source arrange placements remain authoritative; this is alignment, not flex sizing.
Finite box layout: Overlay(padding, gap, children...) stacks children (gap is unused); mainAlign aligns horizontally and crossAlign vertically. Row/Column crossAlign accepts "start", "center", "end" or "stretch". Block.alignSelf defaults to "auto" and can override crossAlign per occurrence. marginTop/Right/Bottom/Left participate in measurement, alignment and flex allocation, without collapsing. paddingTop/Right/Bottom/Left override a side of the container padding; "auto" restores its fallback. borderTop/Right/Bottom/Left reserve inner border space; borderColor controls native border paint. Source paint overrides remain authoritative. boxWidth/boxHeight accept "auto", a nonnegative border-box dimension, or "fill" (finite parent required). Main-axis fill shares remaining space with weight 1 unless flex is set; minWidth/minHeight/maxWidth/maxHeight bound each dimension. Stretch changes only auto cross-axis dimensions. overflow is "visible" or "clip"; clipping affects descendant paint and hits. ScrollBlock handles scrolling independently of its content layout. See examples/box-layout.endor and docs/scene-architecture.md. Independent 2D scenes are described below; 3D support is described below. Page.mode remains a compatibility API.
Independent 2D scenes are now implemented: Scene2D(children...) stores world content at child x/y; CanvasViewport(width,height,scene) is a finite layout box with its own Camera2D(). Camera2D(x=0,y=0,zoom=1) fields are ordinary managed attributes; zoom is 0.01–100. Sharing a scene does not share its camera; assigning second.camera=first.camera does. Scene content may contain layout containers and nested finite viewports; Overlay siblings stay fixed. Mount.fitView() fits finite direct scene-child bounds using that occurrence's allocation. Native wheel/pinch/space or middle drag targets the innermost viewport; drag retains its initial target. viewport-pan/viewport-zoom carry the viewport Mount and current frame identities, with signed logical page coordinates. Pan delta is measured before the viewport projection; zoom delta is [factor,0] with an in-content pointer anchor. Worker validation rejects stale/invalid input before invoking the VM. Draw and hit transforms compose consistently while layout geometry and measurement caches remain in world coordinates. ScrollView is a nominal ScrollBlock subtype. The default page uses these APIs; old Page.mode/panX/panY/zoom remain for compatibility. 3D support is described below. See examples/scene-viewports.endor.
3D basics are now implemented: Scene3D/Group3D, Box3D, immutable Geometry3D made from Vec3/Triangle3D ObjectLists, Mesh3D, DirectionalLight, and finite Viewport3D. PerspectiveCamera and OrthographicCamera expose x/y/z, targetX/Y/Z, upX/Y/Z, near/far, fov or height; viewport aspect is derived separately. New viewports have independent cameras and OrbitController; explicit camera assignment shares a camera. A controller can be replaced with an Endor object defining update(camera,gesture), or disabled by assigning "none". Native scene-orbit/scene-pan/scene-zoom events carry viewport Mount/frame identities. Mesh/group translate and rotate use native arithmetic; camera.translate moves position and target. This avoids the fixed Aster version's unsupported numeric arithmetic expressions. Mount.pick3D(x,y) returns nullable RayHit3D with managed mesh and world intersection data; pointer click Event.hit carries the same result. Rendering uses frustum clipping and a depth buffer in a bounded cached surface. World-space UI composition is described below; visibility and resource work is continuing. See examples/scene3d.endor and docs/scene-architecture.md.
World UI is implemented with Panel3D(worldWidth,worldHeight,content). uiWidth/uiHeight provide an independent finite UI layout resolution (integer 1–2048); background is opaque. Ordinary Endor measure/arrange/paint/hitTest, shared objects, distinct Mounts and per-occurrence scroll state remain active. Panel transforms place a perspective-correct UI texture in the world. A visible ray hit maps through the plane to UI coordinates, then through any nested 2D camera; opaque geometry blocks UI input. Panel UI may contain another Viewport3D and further panels (maximum 8 world UI levels). Mount.toView(localX,localY) converts component-local coordinates to the page view; toLocal(viewX,viewY) performs the reverse when visible. Camera transport from a world UI viewport requires a pointer so pan/scroll increments can use the plane's projective mapping. Its scroll and camera gestures retain one target instead of driving both parent and child. Cyclic UI/scene composition and nested pixel allocations are bounded. See examples/world-ui.endor.
Block.flex is a nonnegative weight (default 0). Native containers reserve fixed measured children, padding and gaps, then share remaining allocated main-axis space between positive weights with zero basis. Actual child allocation feeds remeasurement. It does not request parent expansion. Further controls are described below.
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. The worker parks refresh jobs; cancellation restores still-pending Mount preparation.
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.
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; initial layout parks across worker turns. Explicit REPL/event refresh parks 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.
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.
REPL/event requests retain their HTTP request and scalar result until the complete window set settles. The worker advances one stage/quantum per turn, takes current native viewport targets each time and keeps later mutations queued. GET /code keeps returning the last complete publication. Response metadata and native commands publish together after settling; no source/event callback is replayed. A running input can finish before a newer queued PUT, preserving a consistent old runtime if that candidate fails; DELETE revokes the owner and prevents its late publication. Source execution/callbacks and structural preparation and final command projection remain atomic; source paint execution can yield.
Complete-source PUT also parks an isolated candidate VM during initial layout. The worker retains its HTTP request and the prior live VM, advances one candidate stage/quantum per turn and commits only after its full window set settles. New PUT/DELETE submissions discard obsolete candidates and explicitly cancel their pending layout. Failure keeps the prior live owner; accepting a replacement alone does not revoke that owner. Candidate viewport updates use only the surviving primary shell; secondary native resource IDs and sizes still belong to the old VM until successful publication. Parsing, compilation, source execution, structural preparation and final command projection remain atomic; source paint execution can yield.
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.
ui.stats().dependencyInvalidations counts observed layout/paint dependency mismatches. lastInvalidDependency records the phase, object kind/identity and old/current versions without rendering its fields or retaining it. An input changing its globals is an expected invalidation; compare idle document versions before diagnosing a refresh loop.
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 until failed, then 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 polling. 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.
## UI host object
Positional arguments only:
- `ui.text(value, size = 1, color = "#242422")`; integer size 1..8.
- `ui.rect(width, height, radius = 0, color = "#DCE8FF")`.
- `ui.column(padding, gap, child...)` and `ui.row(padding, gap, child...)`.
- `ui.spacer(size)`.
- `ui.page()` creates a page; `page.render(root)` mounts its root; `page.unmount()` releases the mount. The last render
in a successful submission selects the page shown in this namespace.
Defaults above describe the host API; do not write keyword arguments in Endor.
Colors use #RRGGBB or #RRGGBBAA. Dimensions are finite numbers in 0..1000000.
Use newlines between complete top-level statements. Multiline calls, function
bodies and expression continuations are passed to Aster as complete inputs.
Limits: 1 MiB source, 4096 constructed nodes, 64 pages, 64 UI levels, 10000 expanded
layout nodes, 16 KiB per text, 128 VM frames. Script statements each receive an
equal share of a total 1000000-instruction allowance. Older module initialization
and main execution each retain their own 1000000-instruction allowance.
PUT waits for its worker result. Errors return 422 with diagnostics and preserve
the last successful revision. A newer accepted PUT or DELETE supersedes older
work (409); a full eight-entry queue returns 503. DELETE immediately clears the
document. GET remains available while Aster executes. The window redraws from an
owned snapshot; resize does not rerun Endor. Text uses system fonts, antialiasing
and font fallback; Chinese needs an installed CJK font. Size 1 is 14 logical pixels
with 20-pixel line height; larger sizes multiply both. Native Block methods include
setText/setColor, container append/insert/remove/replace/move, Page open/close/navigation,
and code-driven button.onClick/click. ui.state and ui.textOf provide explicit text
binding. Mouse routing, tab chrome/multiple windows and incremental layout remain
in progress; process isolation is future work. Console retains objects
only in its explicitly resettable in-memory VM. It is not an untrusted-code sandbox.
"##,
name = namespace.name,
revision = namespace.revision,
address = API_ADDRESS,
example = include_str!("../../../examples/page.endor"),
)
}