# Public API redesign This is the implementation contract for the pre-release API revision. The portable layer owns event ordering, queued payloads, surface identities, and frame validation. Backends translate native behavior into that contract. ## One event stream `yasl_display_poll_event(display, &event)` consumes one display-wide FIFO. Input, window resize/close/state, drag hover/drop, composition, clipboard results, and display quit/reopen/loss use the same `yasl_event`. Events have an increasing `sequence` assigned when YASL queues them, and a monotonic `time_ns`. Sequence orders capture by YASL, not unknowable ordering between OS threads or devices. The runner consumes this same queue; it does not drain separate queues by window. Event strings are copied on enqueue and borrowed until the next successful display poll or display close, even if the recipient is destroyed. Destroying a surface removes its undelivered events. A callback may destroy its recipient; it must stop using that surface. Draws remain synchronous to satisfy native live-resize rules; the runner drains queued events before invoking a frame. Legacy surface polling consumes input from the same queue and discards pending non-input notifications, which were already delivered through legacy callbacks. Applications must choose one polling style; discarding prevents idle spin and unbounded retention in old manual loops. Legacy window callbacks remain for migration, but applications should choose queue delivery and a draw callback. Backend notifications must queue before calling a legacy callback, and must not access a surface after a callback that can destroy it. ## One frame contract Both manual drawing and `yasl_app_desc.frame` receive `struct yasl_frame`. Its `target` describes CPU pixels (NULL for native rendering), device-pixel size, and row stride. The frame also carries frame index, scale, and an estimated animation timestamp. Native drawables are queried through ``; there is no untyped GPU layer or public internal pointer in the portable frame. `yasl_frame_present(frame, rects, count)` submits the exact active frame once. NULL with zero count means full damage; a non-NULL array with zero count means no damage. NULL with nonzero count and negative extents fail with EINVAL. More than the backend rectangle limit falls back to full damage after validating every rectangle; damage must never be silently lost. Targets expire at callback return. Pixels must always describe a complete frame. `yasl_surface_request_frame` is the normal coalescing redraw operation. `yasl_surface_set_pacing` returns success/failure and works with either loop style. Continuous drawing uses native pacing where available and a refresh-based timer otherwise. On-demand surfaces require explicit invalidation. ## Identities, posting, and shutdown `yasl_surface_id(surface)` returns a nonzero identity unique within its display. `yasl_display_post(display, &message)` is thread-safe; messages target an identity (zero means display-wide), never a borrowed surface pointer. The display must remain alive during posting, and producer threads must stop before display close. After a successful post the optional release callback runs exactly once on the display thread, after consumption or cancellation (including surface destruction and shutdown). A failed post leaves payload ownership with the caller. Posting to a retired identity fails. The display queue owns only its copy of the message; the release callback defines payload cleanup. Release may post, but must not poll, dispatch, or destroy YASL objects. Legacy `yasl_post_event` adapts to this mechanism, with the caller responsible for its borrowed target lifetime. Application callbacks may return CONTINUE, QUIT, or ERROR. ERROR makes `yasl_run` return failure after cleanup. `yasl_app_exit(display, result)` permits timers, fd callbacks, and frames to request the same orderly termination. The optional initial window is available through `yasl_app_window(display)`. Surface userdata is independent of legacy callback userdata and accessible through `yasl_surface_set_userdata` / `yasl_surface_userdata`. ## Window requests and units New code spells toplevel creation size `size_logical`; `size` remains an alias for migration. Framebuffer geometry, pointer coordinates, popup placement, damage, IME carets, and drag-image coordinates are device pixels as documented on their structures. Conversion helpers remain explicit. `yasl_surface_set_size(surface, logical_size)`, `yasl_surface_set_fullscreen(surface, enabled)`, `yasl_surface_set_maximized(surface, enabled)`, and `yasl_surface_minimize(surface)` return whether the native request was issued. They set errno on failure: EINVAL for invalid input/non-toplevel surfaces, ENOTSUP for unsupported requests, or an OS error. Success is acceptance of a request, not a promise that the window manager honored it. Resulting size/state is reported through events. No portable absolute window-position setter exists. ## Clipboard and composition `yasl_clipboard_set(display, selection, text)` returns whether a copied UTF-8 offer was issued, with errno on failure. `yasl_clipboard_request(surface, selection)` returns a nonzero request ID, or zero with errno on failure. The backend operation `display_request_paste(d, selection, surface, id)` returns true only after accepting responsibility for one completion. It completes via `yasl_surface_complete_paste(surface, id, status, text, len)`; status is YASL_TRANSFER_OK, UNAVAILABLE, or FAILED. Results use YASL_EVENT_PASTE and its `.paste` payload (data/len/request_id/status); legacy `.text` aliases data/len. Surface destruction cancels delivery and native transfers. A backend must complete errors as well as success, including empty text. Request IDs are monotonic per display. Legacy paste uses ID zero. External Linux transfers time out after 30 seconds. YASL_EVENT_COMPOSITION carries copied UTF-8 preedit text, byte selection offsets (both -1 if unavailable), and an active flag. Each native change queues a snapshot, including composition ending; committed text remains YASL_EVENT_TEXT. The query functions remain migration helpers. ## Backend boundary Common helpers declared in `common/backend.h`: * `yasl_surface_notify_configure(s, geometry)` * `yasl_surface_notify_close(s)` * `yasl_surface_notify_state(s, state)` * `yasl_surface_notify_drag(s, event, position_px)` * `yasl_surface_notify_drop(s, event, position_px)` * `yasl_surface_notify_composition(s, text, begin, end)` (NULL/empty ends it) * `yasl_surface_draw(s, frame)` (fills frame metadata and invokes draw) * `yasl_display_notify_event(d, YASL_EVENT_DISPLAY_QUIT/REOPEN/LOST)` * `yasl_surface_complete_paste(s, request_id, status, text, length)` Backends keep using their existing input_queue; a new `surface_input_queue` operation exposes that queue to common code for attaching its event publisher. Backends must replace direct lifecycle/draw/drag callbacks with these helpers, without weakening their native destruction guards. They implement optional surface_set_size/fullscreen/maximized/minimize and display_copy_text / display_request_paste operations. Common wrappers validate arguments and report unsupported operations uniformly. Linux backends are compiled and tested independently and together. Cocoa changes can be inspected here but require macOS compilation/runtime verification. ## Validation The combined Linux configuration passes all 26 Meson tests, including isolated Weston/Xwayland integration, runner error/cleanup paths, event ordering, clipboard outcomes, composition, and destruction during callbacks. Separate Wayland-only and X11-only builds pass their unit suites and the final public export, C++11, and event-contract checks. AddressSanitizer and UndefinedBehaviorSanitizer pass the common contract tests and the isolated backend integration suite. Leak detection was disabled because LeakSanitizer is unavailable in this execution environment. Doxygen builds with warnings treated as errors. A staged install links both shared and static consumers using only the installed headers and pkg-config metadata. Cocoa code and native regression tests were updated and reviewed, but were not compiled or executed: macOS validation remains required. Existing X11 INCR and Wayland arbitrary-size limitations are reported explicitly rather than hidden.