303 lines
17 KiB
Markdown
303 lines
17 KiB
Markdown
# Tessera
|
|
|
|
Tessera is a native desktop UI host embedding [Aster](vendor/aster/README.md).
|
|
Agents submit ordinary Endor scripts through a local namespace API. Aster parses,
|
|
compiles and executes them; `ui.page().render(root)` selects a Tessera UI tree. Tessera extracts a
|
|
native tree, lays it out and draws it using the existing software renderer.
|
|
The initial page is empty.
|
|
|
|
## Source and library integration
|
|
|
|
`vendor/aster` is an unmodified Git submodule, pinned to an exact published commit.
|
|
Cargo path dependencies directly use its `frontend`, `compiler` and `core`
|
|
(library name `endor_core`) packages, which bring in `objects`. The Aster workspace
|
|
and history remain independent. See [vendor](vendor/README.md) for initialization
|
|
and version updates, and [src](src/README.md) for the implementation map.
|
|
|
|
The runtime now uses Aster's Reader, Parser, AST, Compiler and VM. Tessera's former
|
|
experimental `namespace` / `export scene` / `render` parser has been removed.
|
|
Host UI integration lives in Tessera; Aster needs no Tessera-specific changes.
|
|
Lyra CLI and Hestia Server are independent hosts and are not application dependencies.
|
|
|
|
## Native build and start
|
|
|
|
Use Rust 1.91 or newer. Initialize the source submodule following the dedicated
|
|
codex-bot SSH identity instructions in [vendor](vendor/README.md), then build:
|
|
|
|
```bash
|
|
git submodule update --init --recursive
|
|
cargo build --release --locked
|
|
./target/release/tessera
|
|
```
|
|
|
|
Cargo compiles the required Aster libraries together with Tessera. No separate
|
|
Aster build, service or deployment is required. Local builds download dependencies
|
|
from official crates.io by default. A temporary dependency mirror may be used
|
|
when the official source is unavailable, without changing global configuration.
|
|
Docker and automated tests
|
|
are not used locally. The command above produces a native executable. To create a
|
|
Finder-launchable development app on macOS, run:
|
|
|
|
```bash
|
|
sh scripts/build-macos-app.sh
|
|
```
|
|
|
|
Open `target/release/Tessera.app`. See [packaging](scripts/README.md); this local
|
|
bundle does not include distribution signing, notarization or an installer.
|
|
|
|
The API listens only on `127.0.0.1:4737`. Start integrations with its live guide:
|
|
|
|
```bash
|
|
curl http://127.0.0.1:4737/agent
|
|
```
|
|
|
|
## Submit Endor
|
|
|
|
The active namespace starts as `app.main`:
|
|
|
|
```text
|
|
GET /v1/namespaces/app.main/code
|
|
PUT /v1/namespaces/app.main/code
|
|
DELETE /v1/namespaces/app.main/code
|
|
```
|
|
|
|
Submit the [complete example](examples/page.endor):
|
|
|
|
```bash
|
|
curl --request PUT --header 'Content-Type: text/plain; charset=utf-8' \
|
|
--data-binary @examples/page.endor \
|
|
http://127.0.0.1:4737/v1/namespaces/app.main/code
|
|
```
|
|
|
|
A minimal program is:
|
|
|
|
```endor
|
|
let heading = ui.text("Hello Endor", 2, "#242422")
|
|
let rectangle = ui.rect(240, 80, 12, "#DCE8FF")
|
|
let myui = ui.page()
|
|
myui.render(ui.column(24, 12, heading, rectangle))
|
|
```
|
|
|
|
The namespace comes from the request path. `ui` is a host global declared to
|
|
Aster's compiler and injected into the candidate VM. Top-level variables share
|
|
that VM's globals within this source submission. No `main` wrapper is generated.
|
|
Complete top-level inputs are separated by newlines; multiline calls and function
|
|
bodies are grouped before being handed to Aster's existing Interactive parser and
|
|
compiler. Expression continuations such as a member read on the following line
|
|
remain in the same input. Aster still owns every token, syntax rule, AST and
|
|
bytecode operation; the host only batches inputs and restores original AST offsets.
|
|
|
|
The older [main-returning-View example](examples/main.endor) and the richer
|
|
[function example](examples/hello.endor) remain supported. A source consisting
|
|
entirely of function declarations uses Aster's Module entry and calls zero-argument
|
|
`main`. Mixed function declarations, bindings and expression statements use
|
|
Interactive inputs in source order. Type annotations remain syntactic in Aster;
|
|
Tessera checks actual UI values. The sample files are client-side material; the
|
|
API itself stores source by namespace.
|
|
|
|
## UI host API
|
|
|
|
All arguments are positional. Defaults below describe omitted trailing arguments,
|
|
not Endor keyword argument syntax.
|
|
|
|
| Member | Arguments and defaults |
|
|
| --- | --- |
|
|
| `ui.text` | String, integer size (default 1, range 1..8), color (default `#242422`) |
|
|
| `ui.rect` | Width, height, radius (default 0), color (default `#DCE8FF`) |
|
|
| `ui.column` | Padding, gap, followed by zero or more UI children |
|
|
| `ui.row` | Padding, gap, followed by zero or more UI children |
|
|
| `ui.spacer` | Size |
|
|
| `ui.page` | No arguments; creates a page with a bound `render(root)` member |
|
|
|
|
Colors are `#RRGGBB` or `#RRGGBBAA`. Dimensions are finite numbers in 0..1000000.
|
|
A namespace has one displayed page. If a script creates several pages or renders
|
|
more than once, its final successful render selects the visible root. Rendering
|
|
does not publish partial execution: a later error rejects the entire submission.
|
|
|
|
Aster keeps its exact numeric representation; conversion occurs only at the UI
|
|
boundary. Text is shaped and antialiased with COSMIC Text and Swash, using
|
|
installed system fonts and per-glyph fallback (including Chinese when a CJK font
|
|
is installed). UI text uses a proportional sans-serif font; developer source uses
|
|
a monospace font. The existing size argument remains a 1..8 multiplier: size 1
|
|
is 14 logical pixels with a 20-pixel line height; size 2 is 28/40. Row/Column layout
|
|
uses measured text advances and multiline height. Device scale remains fractional,
|
|
and glyphs are rasterized at the actual display resolution rather than enlarged.
|
|
The application does not bundle proprietary OS fonts. Linux installations need
|
|
fonts such as DejaVu Sans/Mono and Noto CJK; the remote test image installs these.
|
|
|
|
## Candidate execution and publication
|
|
|
|
A dedicated worker performs source parsing, AST budget checks and compilation in
|
|
a fresh candidate VM. It executes each script input with shared globals (or module
|
|
initialization and zero-argument `main` for function-only source), checks the
|
|
selected UI root, extracts native data, builds and validates the frame, then
|
|
cleans up all VM roots. The window receives no VM handles. It relays out the owned
|
|
native tree during redraw and display scale changes without rerunning Endor.
|
|
|
|
The existing callback interface has no allocator access. Tessera therefore
|
|
preallocates 4096 real UI node objects plus a separate 64-page pool with bound
|
|
render callbacks per candidate. Each occupied slot owns one live UI reference.
|
|
Constructors return those borrowed handles; Aster retains call results under its
|
|
existing rules. Native child data owns its own lifetime and contains no managed
|
|
VM edges. Each complete PUT gets a new pool. Console keeps live shared UI references
|
|
and page mounts. Native mount/child edges register explicit Aster retain/release
|
|
roots; unused VM pool slots are recycled only after no managed or native owner
|
|
needs them. A recycled slot gets a fresh native cell. Detached branches are freed
|
|
when their last owner disappears; reset releases the session's roots.
|
|
|
|
Limits are 1 MiB source, 10000 visited AST nodes with a 64-level visitor depth,
|
|
4096 constructed UI nodes, 64 pages, 64 UI levels, 10000 expanded layout visits
|
|
and 16 KiB per text. Every execution has a 128-frame limit. Script inputs each
|
|
receive an equal share of a total 1000000-instruction allowance; function-only
|
|
module initialization and main execution each retain a 1000000-instruction budget. Repeated children count again during layout, preventing a
|
|
small shared tree from expanding without bound. The build queue holds eight
|
|
pending requests.
|
|
|
|
GET reads the last successful source while the worker executes. PUT waits for its
|
|
result; a successful response includes submission and committed revision numbers.
|
|
Parsing, compilation, execution, argument or layout errors return 422 with
|
|
explanations and preserve the last successful document. Source diagnostics include
|
|
line, column and byte ranges; VM tracebacks currently identify function starts and
|
|
instruction offsets, not an exact source location for every instruction.
|
|
|
|
A newer accepted PUT invalidates older work. DELETE clears the document immediately
|
|
and invalidates both queued and executing candidates. Superseded requests return
|
|
409 and cannot republish their page. A full or unavailable queue returns 503;
|
|
rejected queue entries do not invalidate accepted work. Failed builds do not
|
|
increment the committed revision. A worker-caught panic discards the entire
|
|
candidate and returns 500, retaining the previous document.
|
|
|
|
This stage supports live mounted UI in the Console VM and immutable window frames on the existing worker.
|
|
Event callbacks, window-size language inputs and process isolation are future work. Worker threads,
|
|
source budgets and VM instruction limits are not a complete untrusted-code sandbox.
|
|
|
|
## Developer tools
|
|
|
|
Toggle developer tools with Command-Option-I or F12 on macOS, Control-Shift-I or
|
|
F12 on Windows/Linux. The compact Console, Sources, Network and Settings tabs
|
|
share a draggable dock edge. The Sources tree/source
|
|
boundary and Network's request/details boundary can also be dragged. Each content
|
|
area has independent vertical and horizontal scrolling, draggable scrollbar thumbs,
|
|
and trackpad support. Shift-wheel scrolls horizontally. Layout uses logical pixels
|
|
and the actual display scale; resize clamps pane sizes to keep controls reachable.
|
|
|
|
Console is one live execution context: statements, function definitions and
|
|
expressions use the same VM and globals dictionary across submissions. There is no
|
|
Expression/Script selector. The current prompt, submitted commands and results
|
|
share one scrollable surface, with no separate input box, editor splitter or Run
|
|
toolbar. Typing appears at the prompt immediately; Enter executes it and the result
|
|
appears directly below. Shift-Enter inserts a newline. Each command and its result
|
|
are followed by a thin neutral horizontal separator. Input focus uses a text caret,
|
|
without a blue field border or hover fill. The prompt, history and caret share the
|
|
same scroll offset; typing and cursor navigation reveal the current insertion point.
|
|
Create objects with bare assignment or `let`, then mount them when ready:
|
|
|
|
```endor
|
|
title = ui.text("你好,Tessera", 2, "#243047")
|
|
```
|
|
|
|
At this point the object exists in Console memory and the page stays unchanged.
|
|
Later inputs can refer to it:
|
|
|
|
```endor
|
|
note = ui.text("这是 Endor 渲染的页面", 1, "#637082")
|
|
card = ui.rect(320, 100, 16, "#DCE8FF")
|
|
page = ui.page()
|
|
page.render(ui.column(24, 16, title, note, card))
|
|
```
|
|
|
|
Ordinary values keep normal name assignment. UI references opt into Aster's
|
|
`ObjectPayload::assign_from` protocol: assigning another UI object updates the
|
|
existing shared reference and preserves its identity. Aliases and mounted parents
|
|
therefore observe the same replacement, including across function-local slots.
|
|
For example, after `other = card` and `page.render(other)`, assigning
|
|
`card = ui.rect(640, 200, 32)` updates the page without another render. This is
|
|
shared-reference semantics, not variable-name tracking. Assigning a non-UI value
|
|
uses normal value assignment; it does not unmount the old UI reference.
|
|
|
|
`page.render(root)` mounts a live root and selects its page. Changes to mounted
|
|
references publish a new frame after a successful input; unrelated objects do not.
|
|
`page.unmount()` releases that page's mount and clears the frame if it was active.
|
|
Replacing a container updates its whole mounted position. Repeated aliases are
|
|
allowed; replacements that would create a cycle or exceed graph limits are rejected
|
|
before mutation. Scalar constructor arguments are evaluated once, as ordinary values.
|
|
|
|
Bare assignment creates/updates a name; `let` remains a declaration. Aster owns this
|
|
syntax, AST and bytecode; Tessera never inserts `let` or replays history. The actual
|
|
last value is also bound to `_`. Text editing supports cursor positioning, arrow
|
|
keys, Home/End, grapheme-safe deletion, select-all, macOS copy/cut/paste and IME.
|
|
Up/Down recall single-line history; Command/Control-Up/Down recall multiline history.
|
|
|
|
Console sends `POST /v1/namespaces/app.main/repl` as UTF-8 text/plain. Responses
|
|
include value, session/input IDs, bindings, `persistent: true`, rendered flag and
|
|
revision. Before the first mount, object creation never publishes a page or advances revision.
|
|
After mounting, changed shared UI references publish automatically. The last explicit
|
|
render in an input selects the active page, only after that input succeeds. Syntax and
|
|
compilation errors run no statements; runtime errors can retain earlier successful
|
|
statements from that input, while preserving the previously displayed page.
|
|
|
|
Reset session (`DELETE .../repl`) drops the VM and all bindings/objects, preserving
|
|
the displayed immutable frame. Clear only hides the transcript. Limits per session
|
|
are 256 executed inputs, 1 MiB transcript, 10000 cumulative AST nodes, 4096 UI nodes
|
|
and 64 retained pages. Unused slots are reclaimed between inputs; reset releases the whole session. Code and objects are never stored on disk.
|
|
|
|
Existing `PUT .../code` remains a complete program replacement with a fresh VM;
|
|
function-only source still invokes main. Accepted PUT/code DELETE invalidate the
|
|
Console context and older queued Console inputs. The compatibility `POST .../evaluate`
|
|
endpoint remains isolated for existing clients, but is not a Console mode. All use
|
|
the bounded eight-entry worker queue. GET /code returns the displayed source: exact
|
|
PUT source, or annotated Console transcript after a REPL render, distinguished by
|
|
`X-Tessera-Source-Kind`. The transcript shows actual executed input and can contain
|
|
failed statements; it is not guaranteed to replay as a standalone program.
|
|
|
|
External HTTP activity appears in the same transcript. Unsupported language
|
|
operations (currently numeric arithmetic bytecode) show actual diagnostics. Transport
|
|
failures appear in the Console footer and restore an otherwise empty draft.
|
|
|
|
Sources shows the full source snapshot or Console transcript with line numbers, Aster's
|
|
AST and selected source ranges. Selecting an AST node scrolls to its location while
|
|
keeping the entire file available. Cloud files has a separate file navigator and
|
|
source viewer, initially disconnected. The `CloudSourceProvider` boundary in
|
|
`src/sources.rs` supplies file identities/paths and full source on open; there is no
|
|
cloud backend, fake file listing or source persistence in this release.
|
|
|
|
Network observes only HTTP requests handled by this Tessera process on
|
|
`127.0.0.1:4737`, including pending requests, method, path, status, server duration,
|
|
request/response headers and bodies. Filter matches method, path or status. Clear
|
|
clears the Network view without changing the document or Console transcript. A bounded
|
|
ring retains 80 requests with explicitly marked 32 KiB body previews. Request
|
|
headers are allowlisted to Content-Type and Content-Length; credentials and cookies
|
|
are not captured. No external browser or operating-system traffic is represented.
|
|
Console and Network share the bounded capture ring and have independent clear
|
|
markers. Clicking a Console record opens its Network details and restores that view.
|
|
|
|
Settings applies six accent presets or a custom six-digit hex color immediately.
|
|
Very bright custom colors are darkened to keep accents visible on white. Execution
|
|
separators, selected tabs and focus indicators use the accent, while text keeps its
|
|
readable neutral/error/success colors. Settings and source remain in memory only.
|
|
|
|
Management controls use the internal [Tessera System UI](src/system_ui/README.md)
|
|
widget library, independent of Endor-controlled page widgets. Buttons, tabs,
|
|
toggles, rows, input fields, color swatches and drag handles share hover, pressed,
|
|
selected, focus and disabled presentation. Buttons activate on release inside;
|
|
dragging outside cancels. Tab/Shift-Tab navigate enabled controls and Enter/Space
|
|
activate focused buttons. Plain Tab still indents at the Console prompt. Empty
|
|
Clear actions are disabled. Splitters are painted and registered above all pane
|
|
backgrounds, toolbars and status bars, so their entire length stays visible and
|
|
draggable. macOS clipboard reads and writes explicitly use
|
|
UTF-8 even when launched from Finder, and decoding errors are surfaced.
|
|
|
|
## Validation
|
|
|
|
Local native builds and window runs are allowed; do not run local automated tests
|
|
or Docker. [Remote CI](.gitea/README.md) on ces-control runs the existing renderer
|
|
checks and the new Aster script/module execution, page rendering, UI layout, source-range, lifetime, budget,
|
|
failed-update and deletion-invalidation regressions, plus Clippy.
|
|
The API regressions use actual HTTP requests, including DELETE while the worker
|
|
is occupied and a queued PUT is superseded.
|
|
Linux CI does not verify macOS window presentation. Aster has no standalone CI/CD; Lyra CI
|
|
explicitly validates the pinned engine itself. CI uses official Cargo sources
|
|
and standard HTTPS checkout without custom SSH secrets. Font checks cover
|
|
proportional/monospace advances, fractional scaling, multiline height, Chinese
|
|
fallback, grapheme-safe truncation, antialiasing and alpha blending.
|