Changelog
Release notes for Oriel 0.18.0, 0.17.3 and earlier.
Oriel 0.18.0 · October 17, 2026
The native renderer (-Dnative_ui) runs full apps that were written for a web view: dialogs, disclosures, the session history, file and camera inputs, date pickers, popovers, Intl, and the drawing such an app’s CSS asks for, drawn to match the web view build on Linux and Android.
Added
<dialog>:show(),showModal()(the top layer over everyz-index, with its::backdrop;:modal; the page outside inert, Tab kept inside),close()andrequestClose(),returnValue,cancelon Escape and the platform’s back,closeafter it, the focus back where it was,method=dialogforms.<details>and<summary>: closed, only the summary shows, with its disclosure triangle; a click, Enter oropentoggles it and firestoggle.- The Popover API:
popover,showPopover(),hidePopover(),togglePopover(),popovertargetbuttons,:popover-open,beforetoggleandtoggle; auto popovers close on Escape, a press outside or another auto popover. - The session history:
history.pushState,replaceState,state,back,forward,go,popstateandhashchange; the platform’s back closes a modal dialog, then walks the history. <input type=file>: the platform’s file dialog (acceptnarrows it on the desktops), or withcaptureon a phone the camera; the pick isfiles[0](read lazily, as a dropped file), theninputandchange;cancelwhen there’s none. New page commandsnui:pick_fileandnui:picked_file(src/modules/dialog/page.zig).<input type=date>: the platform’s date picker on every platform (aGtkCalendarpopover, the Date and Time Picker control, anNSDatePickerpopover, aUIDatePicker,DatePickerDialog), the valueYYYY-MM-DD, the date shown in the user’s format.Intl:NumberFormat,DateTimeFormat,Collator,PluralRulesand thetoLocale*StringandlocaleComparemethods over them (English, Spanish, Portuguese, French, German and Italian data, matching ICU’s output for the common options);navigator.languageis the user’s on Linux and Android.new Image()and<img>:naturalWidth/naturalHeightfrom the file’s header,complete,decode(),loadanderror.conic-gradient()backgrounds (pie and donut charts);background-imagegradients withbackground-size,background-positionandno-repeat;:emptyin style sheets; anappearance: nonecheckbox’s::beforeand::after(a switch’s knob);form.elements,requestSubmit(submitter)andsubmitevents’submitter;datalist.options;env()(the safe-area insets are 0).grid-columnplaces grid items (1 / -1,span N, a start line), as browsers auto-place them row by row; absolutely positioned children take no cell.::placeholder(and::-webkit-input-placeholder) colors a field’s placeholder; without one it’s the platform browser’s (WebKit’sdarkgray, Chrome’s#757575).- Every CSS named color (
darkgray,crimson,rebeccapurple…), not a dozen. alert,confirmandpromptanswer as a browser with its dialogs suppressed (they would block the UI thread) and log a warning.- Events the runtime fires are
isTrusted; a promise rejected with no handler is logged.
Fixed
- The page starts after the app’s
setupon the desktops, as a web view’s page does, so its first commands see whatsetupmade (a tray). - A block-level button centres its content only in its height (its
text-alignplaces the text);justify-contentand gaps apply to flex and grid boxes only. The UA sheet centres a button’s text. - A select with
appearance: nonehides the platform’s arrow when the page draws its own beside it; GTK’s drop-down takes new options. - Stylesheet and script links with a query (
styles.css?v=8) load. - SVG icons follow the page’s CSS
fill,strokeandstroke-widthon the<svg>; an inline::before/::aftertext stays on its line. - Android: fields inside a modal dialog show; a border on only some sides of a rotated box no longer draws hairlines on the others.
- A focused control that gets disabled or leaves the page loses the focus (the focus fixup rule), so its focus ring goes.
matches(),closest()andquerySelector()know:focus,:focus-visible,:hoverand:active.- A modal dialog with
margin: auto, a set width and an auto height is centred, as tall as its content’s room up to itsmax-height. - Grid
frcolumns stay equal beside padded cells (a partial last row ofrepeat(auto-fit, …)), and an auto-fit grid’s column count follows its laid-out width. inline-flexandinline-gridlay their children out asflexandgriddo.- Line heights are in WebKit’s 1/64 px units, so text sits on the same pixel rows as the web view’s; SVG elements have
getBoundingClientRect(). - GTK: text fields and selects take the page’s
font-weight, style and family, and place their text where WebKit does;focus()keeps a field’s selection (Tab still selects its text); a field under a modal’s backdrop shows its text, dimmed; a date shows its year in full; text fields use WebKitGTK’s 1px padding and select widths leave room for the arrow only when it’s drawn;gtk-interface-color-scheme(GTK 4.20, the desktop’s light or dark) setsprefers-color-scheme, and a change of it applies at once. - Android: a date shows its year in full, as Chrome does; a one-line field shows its text from the start until it’s focused, and again once left; a field under a modal’s backdrop stays drawn there, dimmed.
- A transparent box (
opacity: 0, a faded-out toast) no longer hides the fields under it (Android cut a select’s label to a sliver; GTK and Windows hid the field). desktop-entry-release/desktop-entryinstall what the packages put next to the executable (libggml-cuda.so,libggml-vulkan.so,contents.files), so an app installed that way finds its GPU backends.- Phones: when the keyboard comes up, the focused field scrolls into what’s left of the page, as Chrome and Safari bring it into view.
Oriel 0.17.3 · October 17, 2026
Fixed
- Android: apps opened from the launcher icon now appear in Recents. The launcher activity passed its own exclusion from Recents on to the app’s window; windows also clear it from tasks created by earlier versions.
Oriel 0.17.2 · October 16, 2026
Fixed
- Windows: a failed allocation while reading the launch arguments freed them twice.
- Linux: the custom media scheme’s empty responses (errors, 416) no longer read from a stack buffer after the handler returned.
- Linux tray: a menu child is no longer leaked when its parent’s list runs out of memory.
- Windows: the custom media scheme answers 500 when it runs out of memory building its headers, instead of leaving the page waiting.
- System monitor (macOS): the process list no longer reads past its buffer when the buffer couldn’t be allocated.
Changed
- Assertions (Power of 10, rule 5) where outside data enters: IPC, the updater, the native DOM and the system monitor’s samplers. The system monitor’s release builds are ReleaseSafe.
Oriel 0.17.1 · October 15, 2026
Added
- System monitor example: the version in the header and Check for updates next to Settings, with
oriel.updater. Each release attaches a signedsystem-monitor-latest.json; a copy updates from its own entry (its renderer, an executable, the AppImage or the macOS.app), and a deb/rpm copy says a new version is out. The monitor now has Oriel’s version instead of 0.1.0.
Changed
- System monitor example: the Oriel renderer is the default (
-Drenderer=oriel), and the release packages (AppImage, deb, rpm, Windows installer and zip, macOS.app) use it.
Oriel 0.17.0 · October 15, 2026
Oriel tells voices apart: oriel.speakers, speaker embeddings and offline diarization, the module behind GhostPen’s meetings (who said what, and named voices remembered across meetings).
Added
oriel.speakers(.speakers = true, needs.llamaor.whisper; Linux, Windows, macOS): speaker embeddings and offline diarization on voice-detect.cpp (MIT), compiled against the shared ggml build.Encoderloads a GGUF encoder fromcatalog(WeSpeaker ResNet34 by default, ECAPA-TDNN) and embeds 16 kHz speech;diarizeembeds 3 s windows every 1.5 s over the speech regions, clusters them (average-linkage, cosine; or a known speaker count) and returns speaker turns with one centroid per speaker;clusterdoes the clustering on embeddings an app made itself;matchfinds a known voice for a centroid. Progress and cancellation for long recordings. On LibriSpeech voices WeSpeaker put one man’s halves at cosine 0.73, two women at 0.22, a man and a woman at 0.08 (the ECAPA GGUF: 0.80, 0.70, 0.45, hence the default); on three one-minute conversations it found 3, 4 and 1 speakers for 3, 3 and 1. See Speakers.
Oriel 0.16.1 · October 15, 2026
Added
- System monitor example: an Applications list, the default in all three renderers. Each desktop app shows its combined CPU, summed resident memory, threads and process count; Show processes opens its members as a parent/child tree, and All processes lists everything, as before. Apps are recognized from desktop entries (Linux),
.appbundles (macOS) and window owners (Windows), with process ancestry. Its CI runs the model tests and builds the Oriel renderer on Windows and macOS.
Fixed
oriel.chat: a Stop that comes after its reply ended no longer stops the next one. Tokens can reach the page in a burst after the model finished, andcancel()with no reply running stops the next to start, so a Stop pressed during the burst left the next message empty.chat:tokenevents now carry their reply’s id (reply), andcancelReply(id)stops that reply only while it runs; the showcase’s Stop sends it.cancel()is unchanged.- The showcase’s chat UI test (iOS CI) no longer waits 5 minutes for a reply its own late Stop had cancelled: it stops only while the reply runs, and asks again after a reply with no tokens.
- System monitor (Oriel renderer): picking a sort key (CPU, Memory, …) no longer flips the direction; only the direction button does, as in the web frontend.
Oriel 0.16.0 · October 15, 2026
Oriel apps can advertise over Bluetooth LE: oriel.bluetooth sends a beacon nearby devices see, from Zig or the page, on every platform.
Added
oriel.bluetooth: Bluetooth LE advertising, a beacon nearby devices see (docs/bluetooth.md). The same API on every platform, withcapabilities()saying what each stack can send; a field the device can’t send iserror.Unsupported, never dropped. Starts, failures and platform stops come later as the advertisement’s state (onAdvertiseState, the page’sbluetooth:advertiseevent). On when the app declares.permissions.bluetooth(or-Dbluetooth); the page getsoriel.bluetooth.capabilities(),advertise(ad)andstop(id).- Android and ChromeOS: BluetoothLeAdvertiser, every field (tested on a Chromebook’s ARC, one advertisement at a time there).
- Linux: BlueZ over the system bus, every field (tested on a BlueZ adapter).
- macOS and iOS: CBPeripheralManager, service UUIDs and the device name (Apple can’t advertise service data);
advertisewhile the permission is undecided shows the prompt (tested on macOS 15). - Windows: BluetoothLEAdvertisementPublisher, service UUIDs and service data, no device name (not yet tested on a Windows machine).
Oriel 0.15.1 · October 14, 2026
Fixed
- Breakout (Oriel renderer): dragging a finger on the board moves the paddle on touch screens. The board now takes the drag when a finger goes down on it, as the page’s
touchmovepreventDefaultdoes, so Android no longer turns the drag into a scroll and cancels the pointer.
Oriel 0.15.0 · October 14, 2026
A page drawn by the native renderer can now be ported to the Oriel renderer and look the same: .oriel says everything the native renderer computes, a page exports itself as views, and zig build check-views proves it. HollerShare is the first app ported this way. GTK windows paint only what changed.
Added
.orielviews can say everything the native renderer computes from HTML and CSS, so an Oriel-renderer view can look exactly like the web page it came from: per-side borders and border colors, dashed and dotted borders, per-corner and elliptical radii,calc()lengths, gradients, transforms, outlines and focus halos,position: relativeoffsets and sticky, tables, scrollbar styling and color scheme,align-contentand any flex direction, max-/fit-content text, keyboard hints for fields, SVG icons (<icon>with<path>s), and spans that are underlined, highlighted, boxed (inline<code>-style boxes) or links whose clicks reach the app as their own ids.unset="..."gives a prop no value. A test fails the build if a native prop is ever added that no attribute sets.- Porting a page to the Oriel renderer:
ORIEL_NUI_EXPORTandORIEL_NUI_EXPORT_SCRIPTwrite a native-renderer page as.orielviews, its states merged as blocks, andzig build check-viewsproves each view compiles to the props the page had (docs/design/zig-ui-experiment.md, “Porting a page”). Character entities (&, ) in markup. .orielblocks:@when (state)for the app’s states (View.setState), combined with media conditions, up to 64 blocks per view, applied block by block instead of one compiled variant per combination;@when (focus)and@when (hover)looks for the focused and hovered nodes, which also style native fields’ focus. Spans takeclass..orielviews update in place:View.update(tree, base, bindings)edits only the bound texts, tooltips and labels that changed, keeping the view’s native controls, scroll positions and media styles. Typed property updates go straight to the tree (and Android’s mirror) without JSON.zig build bench-ui-updates -Dzig_ui=truemeasures both.- Zig-renderer apps:
Engine.setColorSchemepins dark or light;Driver.dragtakes drags and drops (dropped files’ paths fromEngine.drops); events the app emits reachDriver.eventas “message” on every platform, as they did on Windows and Android.
Improved
- GTK: windows paint only what changed since the last frame (tiles and patches GTK composites), and canvases are drawn at the display’s real fractional scale instead of being resampled every frame. An idle system monitor took 3.7-7.4% of a core, now 1.5%.
ORIEL_NUI_FULL_REDRAW=1paints everything, to compare.
Fixed
- Typed property updates keep the props a backend callback still reads until it returns.
- System monitor (Oriel renderer): the process actions’ copy submenu shows, and copying or ending a process says whether it worked.
- A view mounted where another one was starts from its own look (it showed the old view’s media blocks as already applied).
- Linux: a
-Dnative_uibuild no longer links a WebKitGTK call for transparent windows. - iOS (native and Oriel renderers): the status bar and home indicator areas show the page’s background, as CSS spreads the root’s over the canvas, instead of the system’s white; the status bar’s text follows its lightness.
- System monitor (Oriel renderer): no “0.0 GHz” on Apple Silicon, which doesn’t tell its CPU frequency; the frequency is hidden, as in the web frontend, and the per-core chart drops its “0.0G”.
- Render bench: memory readings on macOS and iOS (resident size and physical footprint, the app’s process), instead of 0 MB.
- Breakout:
zig build test-gamepasses with any renderer (the WebView build left out the canvas the physics rules use). - iOS simulator builds no longer print codesign’s “replacing existing signature” as a failed command.
Oriel 0.14.1 · October 13, 2026
Fixed
- The system monitor’s packages (.deb, .rpm, .AppImage, .dmg, Windows installer and zip) are back on the release: 0.14.0’s moved the example’s sources to
src/and its package builds still looked for the old files.
Oriel 0.14.0 · October 13, 2026
The Oriel renderer grows up enough to run a real interface: .oriel views take a style sheet with media blocks (a phone layout and a desktop one from the same view), styled text runs, and oriel fmt keeps them tidy. The Breakout example now runs in all three renderers and fits a phone held upright.
Added
.orieltext with<span>s: runs in their own color, weight, size, font or monospace, with{binding}text, spaces collapsed as HTML does (“Score 0” as one text, the way the native renderer flattens it)..orielcolors asrgba(r, g, b, a),clickableandbox-sizing="content-box"attributes;visibleis inherited as CSS’s visibility (a hidden panel hides the platform’s widgets in it), andui.setVisibleshows or hides a part and what’s in it..orielviews take a<style>sheet:$tokens, named styles applied withclass, and up to four@media (max-width: …)/(prefers-color-scheme: …)blocks, switched as the window’s width or theme changes;width="auto"lets a media block undo a size.oriel fmtformats.orielviews (--checkfor CI,--stdinfor editors); apps get it aszig build fmt-views.ORIEL_NUI_DUMP=propsdumps each node’s props, andORIEL_NUI_DUMP_ATdumps once after that many seconds, in both renderers.- The Breakout example runs in the Oriel renderer (
-Drenderer=oriel), looking the same as the native renderer’s page.
Changed
- The Breakout example picks its renderer with
-Drenderer=webview|native|oriel; its-Dnative_uioption is gone.
Fixed
.orieldisplay="none"hides what’s in it on every backend: Android still drew a hidden text, laid out at width 0 (a letter a line).- Breakout: a narrow window keeps the header on one line (Settings no longer drops below the score as it grows) and opens the settings panel across the board, its sliders full width; the Oriel renderer’s view carries the page’s
@media (max-width: 520px)in its<style>. - Native renderer (Linux): text fields and buttons partly scrolled out or drawn over show the part in view, instead of disappearing whole.
- Native renderer (Linux): text with
letter-spacingbroke before its last letter (“Breako-ut”): its width now includes the spacing after the last letter, as CSS’s does.
Oriel 0.13.0 · October 13, 2026
Oriel’s two native front ends now stand on shared layers: one engine core and one set of input rules under the native renderer (your HTML, CSS and JavaScript) and the experimental Oriel renderer (.oriel views run from Zig). A fix or feature in a shared layer reaches both. The new Architecture page maps the layers and where new behavior belongs. Breaking only for the example apps’ build flags (see Changed).
Added
- Oriel renderer (experimental,
-Dzig_ui):.orielmarkup compiled at build time into native views driven from Zig (oriel.ui), without QuickJS or the DOM. Elements:row,column,view,text,button(drawn),control(the platform’s button),input,textarea,select/option,checkbox,radio,slider,image,canvas; attributes for layout, drawing, text, accessibility (role,label,description,level,alt) andtooltip, with{binding}text, labels and tooltips. Drawn elements get accessibility entries; Tab, focus rings, Enter/Space, checkboxes, radios and key scrolling behave as the platform’s WebView. Linux; builds for Android. Not yet for apps. - Tooltips on native-renderer nodes (
Props.tip), shown by GTK. - Docs: an Architecture page; diagrams are images rendered ahead of time (
tools/docs/diagrams.mjs), readable on a phone, and a tap opens one full size.
Changed
- One engine core (
src/native_ui/engine_core.zig) for both front ends: frame callbacks, painting at most once per display frame (the Zig engine now coalesces events as the JavaScript one did), a timer when a backend has no frame clock, scrolling. - One set of input rules (
src/native_ui/interaction.zig), moved out of the runtime’s JavaScript: focus rings by element and platform, Tab’s order (macOS without Full Keyboard Access, iOS, radio groups, positive tabindex), what a click focuses, Enter/Space activation, key scroll steps. A key scroll’s glide runs in Zig on display frames, with no JavaScript each frame. Pages see no API change. - Example apps (
render-bench,system-monitor) pick their renderer with-Drenderer=webview|native|oriel; their-Dnative_uioption is gone (zig build -Drenderer=native). - The runtime’s Node tests take the input rules from the engine, compiled to WebAssembly (
zig build interaction-wasm, run bynpm test).
Fixed
- Native renderer: a frame callback (
oriel.canvas’sonFrame) that cancelled itself during a frame could crash or skip the next callback. - 32-bit Android (armv7, x86) builds again.
- Native renderer: Tab with thousands of radios or a huge tabindex no longer stalls or stops working; host functions report a page’s thrown conversions instead of ignoring them.
Oriel 0.12.1 · October 13, 2026
Fixed
- Linux/Wayland: an overlay window (a layer surface) never grabs the keyboard any more. One with
focus_on_showis focused when shown and lets go when the user clicks another app, as on every other platform (it held the keyboard until hidden). One withfocus_on_show = falseno longer takes the keyboard when it appears (Hyprland focused it), and one click still focuses it.
Oriel 0.12.0 · October 12, 2026
Oriel 0.12.0 keeps one implementation and one API per feature: every second way of doing something is gone, the generic, multiplatform form stays. Apps declare everything once, in build.zig, and App.Config takes it from there. The docs are rewritten as one page per feature, each with where it works, a quick start, how it works (with a diagram), examples and the full reference. Breaking throughout: each feature page has a “Migrating from 0.11” section, and the table below lists every change.
Changed
App.Configtakes.app = oriel_app.buildand.window. Everythingbuild.zigdeclares (id, embedded assets, dev server, icon, URL schemes, permissions, isolation) comes fromapp.build; the main window is aWindowOptions(.window = .{ .title, .width, .height, ... }, default 800×600).oriel_appexportsbuildandupdate_public_keyonly.oriel.mainis the one entry point.- Android platform hooks are declared like the Apple ones:
.android.hooks = &.{ .{ .class, .sources, .dependencies } }, implementingOrielAndroidHooks(Kotlin and Java). The generated registry isOrielHooksRegistry.kt; runtime files a newer Oriel no longer ships are deleted from the app’s Gradle project on the next build. - Events are typed:
App.events(Events).emitand.emitTo(label, ...)(any thread) are the app’s way; the untypedApp.emit/emitToandWindow.emitare Oriel-internal. oriel.pathsis the one place for app directories:appData,appCache,appConfigandappExternal(Android’s external files dir).- One
window.orielfor both renderers (src/core/bridge_api.js), one bridge script for every web view (src/core/bridge_js.zig), one dispatch for native pages’ commands (src/core/native_commands.zig). - Shutdown cleanup runs on
oriel.lifecycle’sterminating, sent by every shell before its windows go (Oriel’s own modules included). - Deep links are delivered through the same queue as notification clicks, shares and lifecycle events: links that arrive before
onOpenis set are handed to it once it is. - chat: a reply holds its own lock:
status,deleteandcompareno longer wait for it, andStatus.generatingis true while one runs;chat.deinitfrees the model at exit; acancel()sent before the reply starts stops it. - Catalogs in chat, dictation and tts use
findModel(name) ?*const ModelandmodelPresent(m). - Module namespaces show only their documented API: backends, shells’ entry points and test hooks moved out of reach.
Removed (each with its one replacement)
| Removed | Use |
|---|---|
App.Config.id, .title, .width… .placement, .show_main_window, .on_close, .icon, .deep_link_schemes, .permissions, .assets, .dev, .security.isolation | .app = app.build, .window = .{ ... } (.visible, .hide_on_close) |
App.Config.on_session_end | oriel.lifecycle .terminating |
App.run, App.setProcessArgs | oriel.main |
App.showWindow/hideWindow/toggleWindow, App.closeWindow/postCloseWindow | App.getWindow(label) and the Window’s methods; Window.close() |
App.setMenu | oriel.menu.set |
App.gtk_app, App.main_window and other App internals | oriel.hooks (native handles) |
.android.extensions, OrielAndroidExtension(Context) | .android.hooks entries, OrielAndroidHooks(Context) |
oriel.ios.onSystemEvent; Android trim-memory in onSystemEvent | oriel.lifecycle |
oriel.android.preconnect | nothing: the app’s io resolves through bionic |
PackageOptions.icon, PackageOptions.url_schemes | AppOptions.icon, AppOptions.url_schemes |
oriel.store.configDir/dataDir/cacheDir, oriel.android.paths | oriel.paths |
oriel.network.info, onChange (never implemented) | — |
oriel.deep_link.validateUrl | validate |
oriel.share.read | oriel.share.open |
oriel.input.keyComboX11, typeTextX11 | keyCombo, typeText |
oriel.updater.init/deinit, downloadWithOptions, verifyManifestWithOptions, test hooks | oriel.main does init; download(io, gpa, u, .{ ... }) |
audio_play one-shot start/stop/finished | audio_play.Stream |
dictation.sources | oriel.audio_capture.listSources |
chat/dictation find/present | findModel/modelPresent |
system_ai.download/generate io parameter | — |
oriel.canvas.openIn/onFrameIn/engineOf | open, onFrame |
window.oriel.native | oriel.features.nativeUi |
-Dnative_dom=false (linkedom in apps) | the native DOM |
oriel:window:open min_width spelling | minWidth |
IPC notification:ready, deep_link:ready | events:ready (the bridge’s listen()) |
oriel pake, oriel setup zig, oriel setup webview2, oriel wrap --tray | oriel wrap, oriel zig install, oriel webview2 (oriel setup all installs both); the tray is the default |
Fixed
- Windows and Android:
const { openExternal } = orielthrew (the WebView2 bridge usedthis.invoke). oriel.drop.pathworks on macOS, iOS and Android (only Linux and Windows routed it) and resolves a string or null.fs_watchon Linux and Android:pollintooriel.fs_watch.Eventdidn’t compile.oriel deep-link addwritesAppOptions.url_schemes;oriel permissionacceptsbluetoothandlocal_network;--helpshows the full usage ofpermission,deep-linkanddesktop-entry.- The generated TypeScript has
WindowHandle.startDragging(), thewindow:created/window:closedevents andoriel.drop.
Docs
- One page per feature (
site/content/docs/*.smd, the website), with a Mermaid diagram on each;docs/*.mdis generated from them (tools/docs/gen-md.mjs, checked in CI). Design records are indocs/design/, contributor notes indocs/internal/(the page template isdocs/internal/writing-docs.md). - The examples no longer commit Oriel’s generated Kotlin runtime.
Oriel 0.11.0 · October 12, 2026
Oriel 0.11.0 brings platform hooks to every platform: an app’s own Kotlin, Objective-C, Swift or Zig code reacts to, or takes over, the platform’s app, window and web view events, as Android’s hooks already could. It also adds Objective-C and Swift app extensions on macOS, Swift sources that link any Apple framework they import, an iOS hook for background downloads, and simulator builds that carry their entitlements like Xcode’s. Breaking: .ios.resources takes .{ .from, .to } entries. Validated on Linux, Android (Poco X3), macOS 15.2, the iOS 18.4 simulator and CI’s Xcode 26; the Windows hooks compile but haven’t run on Windows yet.
Added
- Platform hooks in Zig:
oriel.hooks(Linux, macOS, Windows). The app’s Zig code sees each window and web view Oriel creates, with its native handle (GtkWindow* / WebKitWebView*, NSWindow* / WKWebView*, HWND / ICoreWebView2* and its controller):oriel.hooks.subscribe(.{ .window_created, .webview_created, .webview_destroyed, .window_destroyed }), up to 16 subscriptions, on the main thread.webview_createdruns after Oriel installs its bridge, before the first navigation.oriel.hooks.availableis false on Android and iOS, which hook natively. The showcase logs its windows and turns on a WebKitGTK setting through the handle. New page: Platform hooks. - iOS and macOS platform hooks:
.ios.hooks,.macos.hooks. Objective-C or Swift classes conforming to@protocol OrielPlatformHooks(oriel_hooks.h) take part in Oriel’s app and scene delegates, windows and WebKit delegates:registered:(with anOrielHooksContext:presentingViewControlleron iOS,keyWindowon macOS),webViewConfiguration:window:,webViewCreated:window:(before the first navigation),webViewDestroyed:window:, the push token and its failure, iOS scene events andapplicationDidFinishLaunching:options:, macOSapplicationDidFinishLaunching:,windowCreated:label:andwindowWillClose:label:. Interceptions returnYESto own the request (firstYESwins, declaration order): navigation policy, media capture,openURL:(beforeoriel.deep_link),continueUserActivity:(universal links, Handoff; Oriel now implementsscene:continueUserActivity:andapplication:continueUserActivity:restorationHandler:) and, on macOS,applicationShouldHandleReopen:hasVisibleWindows:; on iOS,handleEventsForBackgroundURLSession:completionHandler:(Oriel’s app delegate now forwards background URLSession events, so downloads can continue while the app is suspended). Up to 16 hooks per platform, constructed once at launch before the first scene or window; a class that is also an app extension gets one instance for both. The showcase’s hooks log what they see and own example.com links (an in-app Safari sheet on iOS). Platform hooks - App extensions in Objective-C or Swift on macOS:
.extensions[].macos = .{ .objc = .{ .class, .sources, .swift_sources, .frameworks, .weak_frameworks } }runs the iOS protocol and runtime (OrielAppExtension) on macOS;.macos = .{ .zig = ... }keeps the Zig backend. Either overrides.desktop. Native macOS sources build against the macOS SDK (-Dmacos_sdk,$SDKROOTorxcrun, so a Mac); Swift on macOS raises the minimum to macOS 15 (an explicit lower target is a build error), as Swift does to iOS 18. The showcase’s-Dobjc_echobuildsshowcase.echoin Objective-C on macOS. App extensions
Changed
Breaking:
.ios.resourcestakes named entries, like Android’s.assets:.{ .from = <LazyPath>, .to = "models/yoloe.mlmodelc" }, placed attoinside the bundle.frommay now be a dependency’s path (dep.path(...), e.g. a hash-pinned, lazy model archive in build.zig.zon), not only a source path; a build step’s output is refused.tois checked like Android asset paths (relative, no..).Swift sources link the Swift overlays and frameworks they import (iOS and macOS, extensions and hooks): Zig’s linker ignores swiftc’s autolink entries, so
import CoreML,Metal,Accelerate,simd,os, … failed with undefined__swift_FORCE_LOAD_$_swift...symbols. Oriel now links the frameworks a Swift source imports and everylibswift*.tbdof the SDK withdead_strip_dylibs(only what is used is loaded); every framework and library linked otherwise is marked needed, since Oriel looks classes up by name. The showcase’s Swift hook imports CoreML, CoreGraphics and Metal.Swift sources (extensions’ and hooks’) use
oriel_swift.has their bridging header: bothoriel_extension.handoriel_hooks.h, so one Swift class can conform to both protocols..android.hooksis the name of Android’s Kotlin/Java hook classes (formerly.android.extensions), andOrielAndroidHooks/OrielAndroidHooksContextare Kotlin typealiases ofOrielAndroidExtension/OrielAndroidExtensionContext. The Android page is now Android platform hooks (same URL). The showcase registers a hook class with.android.hooks.
Deprecated
.android.extensions: still registered, after the.android.hooksclasses; a class named in both is registered once.
Fixed
- iOS simulator builds carry entitlements like Xcode’s. The executable embeds
application-identifier(<team>.<bundle id>, or.<bundle id>without.ios.team_id),get-task-allowand the keychain access group in its__TEXT,__entitlementsand__TEXT,__ents_dersections, and on a Mac the bundle is signed ad hoc as a whole. Before, the simulator’s daemons saw no bundle ID: nsurlsessiond refused background URLSessions (“does not have a bundle ID”) and the keychain failed. New option:.ios.team_id. - Docs: the site’s “Intercept file selection” still told apps to build their own camera capture route; since 0.10
oriel.camerahandles<input type="file" accept="image/*" capture>(Oriel’s capture provider, the photo picker, HEIC/AVIF to JPEG), and a custom content provider is only for apps that need their own capture flow.
Oriel 0.10.0 · October 12, 2026
Oriel 0.10.0 adds what porting GhostPantry to iOS showed was missing, so apps no longer write platform code around Oriel: one lifecycle API on every platform, module flags that resolve per target at compile time, app extensions (an app’s own Kotlin, Objective-C, Swift or Zig, called with one API), photo capture, the device’s own language model (Gemini Nano on Android, Apple Foundation Models on iOS 26) and app directories. See App extensions. Breaking: Android’s stub modules are gone; guard calls to desktop-only modules with if (oriel.<module>.available).
Added
oriel.lifecycle: the app’s lifecycle with one API on every platform (core, always on).subscribe(handler)(up to 16, called on the main thread in order) getsbackground/foreground(Android: the last Activity stopped / one started again; iOS: didEnterBackground / willEnterForeground),memory_pressure(moderateorcritical: Android’s onTrimMemory, iOS memory warnings, macOS’s memory-pressure source, Windows’ low-memory notification, Linux’s GMemoryMonitor),suspend_/resume_(macOS NSWorkspace sleep and wake, Windows WM_POWERBROADCAST, logind PrepareForSleep) andterminating(quit, SIGTERM, iOS applicationWillTerminate, Windows WM_ENDSESSION). The page gets them withoriel.lifecycle.on((e) => …)as{ type, level }, queued until it listens; the TypeScript bindings declareLifecycleEventandLifecycleApi.oriel.chat,oriel.dictationandoriel.ttsunload their models on critical memory pressure by themselves;unload_onchanges when (.{ .background = true }, or.{ .memory_pressure = null }for never).- Every module namespace has a comptime
available(see Changed, module flags).window.oriel.featuresin every page (and the native renderer) has the build’s resolved flags in camelCase ({ tray: false, fsWatch: true, ... }), typed asFeaturesin the generatedoriel.ts.zig build --verboseprints one line listing the flags resolved to unavailable for the target. - App extensions (
oriel.extensions, docs/extensions.md): an app’s own native code, declared by name inAppOptions.extensionswith an implementation per platform: Kotlin (.android:OrielAppExtension,OrielCall,OrielExtensionError; sources, Maven dependencies, assets, generated R8 keep rules), Objective-C (.ios: theOrielAppExtensionprotocol inoriel_extension.h, compiled with-fobjc-arc; frameworks, bundle resources), opt-in Swift (.ios.swift_sources, withswiftcfrom Xcode or the Linux toolchain plus xtool’s SDK; it raises the app’s minimum to iOS 18) and Zig (.desktop, or.linux/.macos/.windows).oriel.extensions.get("name")is checked at build time andavailable()is comptime per target;call(arena, method, input, .{ .timeout_ms, .cancel, .diag })runs on the extension’s own workers (.concurrency), with capped inputs and replies (.max_bytes, 32 MiB), a bounded queue, cancellation and timeouts, and is refused on the main thread. Native code emits events (onEvent; the page’sextension:<name>) and getsoriel.lifecycle. - The page reaches extensions only through Zig: an app command, or
oriel.extensions.expose(name, .{ .methods, .filter })andoriel.extensions.call(name, method, payload)/.on(name, cb)in every bridge (TypeScriptExtensionsApi). The showcase’sshowcase.echoruns in Zig, Kotlin and Objective-C, and-Dswift_exampleaddsshowcase.swifton iOS; theextensionUI test calls both where built. - Oriel’s own app extensions: modules can be built on the extension primitive (
oriel.system_aiis the first: Kotlin and Swift backends shipped in Oriel, merged before the app’s). Names starting withoriel.are reserved: an app can’t declare or expose them..ios.weak_frameworksweak-links frameworks newer than the app’s minimum, only when the SDK has them. oriel.camera(-Dcamera, and on when the app declares.permissions.camera; docs/modules.md, “Camera”):capturePhoto(io, arena, .{ .source = .camera | .library, .max_side, .format, .quality })returns the photo upright, scaled and encoded (bytes,width,height,mime), witherror.Cancelled,Unsupported,PermissionDeniedandTooLarge. Android: the camera app into Oriel’s capture provider and the system photo picker, scaled in Kotlin. iOS:UIImagePickerControllerandPHPickerViewController. Desktop:.librarythrough the file dialog (filtered to images) and zigimg, with a JPEG’s EXIF orientation applied;.cameraUnsupported. The page:oriel.camera.capturePhoto({ source, maxSide })→{ dataUrl, width, height, mime }(TypeScriptCameraApi).- Android, with the camera module on:
<input type="file" accept="image/*">opens the camera app (capture; CAMERA asked first) or the system photo picker (ACTION_PICK_IMAGESon 13+, several withmultiple), and HEIC/AVIF picks are transcoded to JPEG (AppOptions.camera.transcode, default true). The generated<app id>.oriel.captureprovider serves only Oriel’s own captures. Apps no longer need their own camera extension; extensions still see the chooser first. Declaring.permissions.cameraalso declaresandroid.hardware.cameraas not required. oriel.dialog.openFile’s.filter = .{ .name, .extensions }(desktop).oriel.system_ai(-Dsystem_ai, off by default; Android and iOS): the device’s own language model.status()(available, downloadable, downloading, unavailable, with a reason),capabilities(),download(io, on_progress),generate(io, arena, .{ .system, .prompt, .images, .temperature, .max_tokens, .schema }, on_token),cancel()andlastError(); one generate or download at a time, from a worker; caps of 64 KiB per text, 4 images of 8 MiB and a 1 MiB reply; timeouts of 20 s, 3 min and 10 min. Android: Gemini Nano through ML Kit’s GenAI Prompt API (genai-prompt1.0.0-beta4, added to Gradle only with the flag), text and one image, streaming, download progress. iOS: Apple Foundation Models (iOS 26+ with Apple Intelligence; text only), weak-linked and compiled only when the SDK has it; Oriel’s Swift raises the app’s minimum to iOS 18. The page:oriel.systemAi.status/capabilities/download/ generate/cancelwithsystem_ai:tokenandsystem_ai:downloadevents (TypeScriptSystemAiApi), in the native renderer too; its promises reject with a lowercase code and the platform’s message, likestatus()("unavailable: …","busy").oriel.features.systemAi. Linux, Windows and macOS reportavailable = false.- Android builds with
-Dsystem_aistop with the lines to change when the Gradle project’s Kotlin plugin is older than 2.3 (projects generated before 0.10 have 2.0.21; ML Kit needs 2.3). See docs/android.md, “Upgrading an existing project”. - Showcase: a Camera card on the Files tab and a
cameraUI test; a System AI card (-Dsystem_ai). oriel.paths(core):appData(gpa, .{ .name })andappCache(gpa, .{ .name }), the app’s private data and cache directories on every platform, created if missing. Android: the files and cache dirs themselves (no subfolder: the sandbox is the app’s); elsewhere the same folders asoriel.store.dataDir/cacheDir(XDG,Library/Application SupportandLibrary/Caches,%LOCALAPPDATA%). Apps no longer reach fororiel.android.paths.filesDir()behind an Android check (docs/modules.md, “App directories”).
Changed
- Module flags are said once, for every target. An app’s
.tray = trueinb.dependency("oriel", ...)means “where it exists”: on a target without the module it resolves to off instead of failing the build (iOS) or building a stub (Android). Guard calls withif (oriel.tray.available), and the code under it isn’t built where the module is missing. A use outside the guard is a build error naming the module and the guard; so is one of a module the app left off.-Dtray=trueon Oriel’s own command line for a target without a tray is still a build error. Apps no longer need per-target flags like.tray = !is_android. See docs/modules.md, “Availability”. - Breaking: Android’s stub modules are gone:
tray,menuandinputno longer build there with calls that fail witherror.Unsupported. An Android build that calls them must guard the calls withif (oriel.tray.available)(ororiel.menu,oriel.input), and code that branched onoriel.options.traycan useoriel.tray.available. - Breaking:
-Dlayer_shellon Oriel’s own command line is a build error outside desktop Linux (it was ignored on Windows and macOS); as an app flag it resolves to off there. - Android:
background/foregroundcome from OrielActivity’s onStart/onStop (a newNativeLib.onLifecyclenative, in the runtime synced into apps). The malloc purge on trim-memory is now a lifecycle subscriber, and also runs when the app goes to the background (it used to on TRIM_MEMORY_UI_HIDDEN).oriel.android.onSystemEventstill gets “trim-memory” and the Android-only events. - The showcase frees its models through
unload_oninstead ofonSystemEventglue. zig build checkfor iOS without the SDK covers apps with C dependencies (sql,llama,whisper,kokoro): their C and C++ type-check against Zig’s own Darwin headers, and the Objective-C that needs framework headers (ggml’s Metal backend, miniaudio’s AVFoundation playback) and the frameworks themselves (Foundation, Metal, MetalKit) are left out. It failed before (missing libc, “unable to find framework”). With an SDK nothing changes.
Deprecated
oriel.ios.onSystemEvent: now a lifecycle subscriber under the old names (“background”, “foreground”, “memory-warning”); useoriel.lifecycle.App.Config.on_session_end: still called on Windows at session end, afterterminating; subscribe tooriel.lifecycleinstead.oriel.android.preconnect: not needed. TheioOriel hands the app resolves host names through bionic’s getaddrinfo, sostd.http.Clientworks on Android as it is (redirects included); remove the call. Oriel’s own model downloads no longer use it.
Fixed
- Android projects generated before 0.9.9 now ship
zig-out/android-assets(Oriel’s runtime data and app extensions’.android.assets): their ownbuild.gradle.ktsnever read it, so those files were left out of the APK silently. Oriel’s managedoriel-dependencies.gradleadds the folder when the project doesn’t. - iOS 26 SDKs: Objective-C sources that import UIKit failed to compile (UIKit’s headers include
<UIUtilities/UIDefines.h>, from the SDK’sSystem/Library/SubFrameworks, now on the framework path). - iOS:
foreground(and the old “foreground” system event) fired while the app launched; UIKit sends willEnterForeground then too. It now means “back from the background”, as on Android. - iOS: a build on a Mac without
-Dapple_sdkor$APPLE_SDKfound no SDK and couldn’t link; it now asksxcrunfor Xcode’s (iPhoneOS or iPhoneSimulator, per target).
Oriel 0.9.16 · October 11, 2026
Oriel 0.9.16 fixes what a code review against the NASA/JPL “Power of 10” rules found: a way for localStorage to lose its data, use-after-free bugs on window close and in Windows mDNS, pages that could hang their window, replies reaching the wrong page, an unpinned download in the CLI, and downgrades through force.
Fixed
- Native renderer:
localStoragecould lose everything. A key or value with a lone surrogate (valid in JavaScript) was saved as invalid UTF-8, the next launch couldn’t read the file and started empty, and the next save overwrote it. Keys and values are stored as valid UTF-8 now, the read limit allows for JSON escaping, and a file that fails to load is moved aside (localStorage.json.corrupt-<time>) instead of overwritten. - Native renderer: closing a window from a frame hook or a promise job freed the page while it was still running; the close now waits until the page is idle, on every backend.
- Native renderer: a script that never returns (
while (true) {}, or a promise chain that keeps requeuing itself) no longer hangs the window for good. A call into JavaScript is stopped after 10 seconds, promise jobs run at most 10,000 per turn (the rest on the next), and the JavaScript heap is limited to 1 GiB. - Native renderer on Linux and Windows: above 256 MB of decoded images the image cache was emptied and every image decoded again each frame; it now evicts the least recently used images.
- Windows mDNS: stopping a browse while lookups were finishing could use freed memory (lookups are reference-counted now) and race on the found services; lookups, names and addresses per browse are capped. Names, addresses and TXT keys with a NUL byte are refused.
- Events and replies reached only the page that should get them: on Windows and Android, an asynchronous reply or event could reach the next page after a navigation, and on every platform events went to whatever page a window showed, bridge or not.
- Windows: a failed request-filter registration, or an early error while serving an asset, let
https://app.localhostrequests reach the network; window creation fails, or the request gets a 500. macOS and iOS fail the request when a security header (CSP, nosniff) can’t be set. - macOS and iOS:
nativePathalways failed (theF_GETPATHresult was misread). - IPC: requests over 32 MiB are refused, the async job queue is capped (1024; extra calls answer
WorkerPoolBusy), a message that can’t be decoded gets anInvalidRequestreply instead of never settling, and window sizes from the page must be 1..32767. - Updates: a manifest older than the running version is never installed by
force;oriel update --versionchecks the tag’s form and that the signed manifest is that version. Plain-HTTP manifest URLs only work in tests. A raw payload’s size is capped, the replaced binary keeps its mode, and on Windows the single-instance lock is released only once the new process has started. orielCLI: the iOSxtooldownload is pinned to 1.21.0 and its SHA-256 checked (it was GitHub’s unpinned latest release, run with your Apple ID).oriel wrapescapes the title, user agent and URL in the code and HTML it writes, and allows only the exact origin unless--allow-subdomainsis given. Signing keys’ temporary folders are created private (0700) from the start.- Android builds: a version part that isn’t a number or is 100 or more, and app names with
" $ \ & < > ', are build errors instead of a wrong version code or a broken project.
Oriel 0.9.15 · October 11, 2026
Oriel 0.9.15 makes Android mDNS discovery reliable on phones older than Android 14, which could miss devices on the network.
Fixed
- Android mDNS browsing before Android 14: a resolve the system never answered blocked every service queued after it (one runs at a time), and a resolve that failed for any reason other than “busy” was never tried again, so the service stayed unlisted until browsing restarted. A resolve is now given up after 10 seconds, and failed ones are tried again after 1, 2, 4… seconds (up to 30) while the service is listed. Older phones could miss other devices on the network because of either.
Oriel 0.9.14 · October 10, 2026
Oriel 0.9.14 fixes a crash: since 0.9.12, every app built with the native renderer on Linux aborted as its window closed.
Fixed
- Native renderer on Linux: closing a window no longer aborts the app (SIGABRT). The text layouts kept per node (0.9.12) were freed before the page, whose nodes then removed themselves from the freed map; a release build caught it as a misaligned pointer. Every
-Dnative_uiapp on Linux crashed as its window closed.
Oriel 0.9.13 · October 10, 2026
Oriel 0.9.13 lets VoiceOver reach the share picker in the native renderer on macOS, and fixes what testing 0.9.12 on a Mac found in the system monitor example: idle processes can be found and terminated, empty CPU chips are hidden, and idle macOS network ports are left out.
Fixed
- Native renderer on macOS: a popover or sheet AppKit shows over the page (the share picker of
oriel.share.send) is in the window’s accessibility tree, so VoiceOver reaches it. The page view listed only the page’s elements and left it out. - System monitor example: the process filter reaches the sampler, so a match (by name, pid or, on macOS, user) is among the rows sent even when idle; filtering for
sleepfinds and can terminate it. Before, only the 256 busiest processes reached the page. - System monitor example: the CPU header hides its frequency and temperature chips when there is no value (Apple Silicon), and macOS leaves out interfaces that never carried a byte (idle Thunderbolt and bridge ports); one that starts carrying traffic appears.
- System monitor example: the sampler tests print a sample taken half a second after the last, not a 0% CPU read 1.5 ms later.
Oriel 0.9.12 · October 10, 2026
Oriel 0.9.12 keeps the native renderer’s localStorage across runs, adds macOS Services to share targets, and turns the system monitor example into a btop-style monitor for Linux, Windows and macOS. Native renderer frames on Linux are cheaper, and JSON.parse no longer depends on the locale.
Added
- Share targets on macOS:
.share_targetalso gives the.appan entry in the Services menu, titled with the share label (else the app’s name), shown without the user enabling it. It sends the selection’s files of the declared types and, when the types take text, plain text and URLs; the app receives them as one share with sourceservice, its files readable throughoriel.share.file, and comes forward.
Changed
- Native renderer:
localStoragelasts beyond the run, as in a WebView. One store per app, shared by its windows, saved as JSON in~/.config/<app id>/data/localStorage.json($XDG_CONFIG_HOMEwhen set;~/Library/Application Support/<app id>/dataon macOS,%APPDATA%\<app id>\dataon Windows). Writes are batched (saved 300 ms after a change, and when a window closes), and a 5 MB quota throwsQuotaExceededErroras browsers do. It was an in-memory map before, gone at exit. On Android and iOS it still lasts the run. - The system-monitor example grew into a btop-style monitor: per-core CPU with frequencies and temperature, load, memory and swap, disks with I/O rates, network, sensors, system information, process user, threads and command line, a responsive layout, and Settings to show or hide each part (kept in
localStorage). It runs on Linux, Windows and macOS (a sampler per OS; the page hides what an OS doesn’t report), and a CI workflow tests each sampler and builds the packages (.deb, .rpm, .AppImage, setup.exe, .dmg). It costs less than btop and the terminal drawing it: a sampler at ~1.6 ms a sample and a virtual process table.
Fixed
- Native renderer:
JSON.parsedropped the fraction of every number (0.43became0) under a locale whose decimal separator is a comma (LC_NUMERIC=es_CL,de_DE…): QuickJS read JSON numbers withstrtod, which follows the locale GTK sets. It reads them with its own locale-independentjs_atodnow. Every command result reaching a page went through it. - Windows builds with
-Dkokoro: the build-time espeak-ng data compiler failed withFileBusyremoving its staging directory, because the intonation compiler’s log was left open. - Native renderer on Linux: wheel scrolling no longer re-hovers a new element at every step. The compositor’s motion after each step (the pointer still) hovered the row now under it, a restyle and a second redraw per step;
:hovernow waits until the scroll settles, as in browsers. The system-monitor example also drops its row hover transition, which kept the window redrawing every display frame while scrolling. - Native renderer on Linux: cheaper frames. Each text keeps its Pango layout from frame to frame instead of being shaped again on every draw, and a box with rounded corners that clips its content clips it to a rectangle, with the rounded path only on what reaches into a corner (a path clip made Cairo composite everything under it through a mask). With the fix above, scrolling the system monitor’s process table went from about 80% of a core to about 15%; a frame’s paint takes about half as long.
Oriel 0.9.11 · October 9, 2026
Oriel 0.9.11 adds location.reload() to the native renderer, makes live tables much cheaper to update, and fixes the macOS and iOS builds of apps with text to speech.
Added
- Native renderer:
location.reload()starts the page again in its window, with a new JavaScript runtime and DOM. It runs on a turn of its own, after the calling script returns; answers to the old page’s commands and its pending timers are dropped instead of reaching the new page.
Improved
- Native renderer: text changes in place more often. A text cell whose box clips its text (
overflow: hidden), a native button elsewhere on the page, and canvas programs that change in the same frame no longer force the page’s rows to be rebuilt. The system monitor example went from 1,000–2,400 rebuilt nodes (20–45 ms) per sample to a few hundred text updates (2–3 ms), and from about 6% to 1–2.5% of one core. - The system monitor example reuses its table rows and writes text only when it changed.
Fixed
- The build-time espeak-ng data compiler builds on macOS and with Apple’s SDK (it used stdio macros that translate-c turns into inline functions).
- iOS builds with
-Dkokoro: espeak-ng gets its endian helpers on Apple targets, and miniaudio’s playback compiles as Objective-C (AVFoundation).
Oriel 0.9.10 · October 9, 2026
Oriel 0.9.10 makes -Dnative_ui apps on Linux free of WebKitGTK, and fixes wide tables on the documentation site.
Improved
- Linux apps built with
-Dnative_uino longer link or load WebKitGTK, JavaScriptCore or libsoup: every window is drawn by the native renderer. The system monitor example uses 14 MB less memory, loads 45 fewer shared libraries, and its stripped binary is 1.2 MB smaller. - Their
.deband.rpmpackages no longer depend onlibwebkitgtk-6.0-4/webkitgtk6.0. - The comparison with Vercel native now has measured numbers for the same system monitor app built with both frameworks.
Fixed
- Documentation tables wider than the page column scroll sideways instead of spilling under the table of contents; the module overview fits again.
Compatibility
- On Linux with
-Dnative_ui,media_scheme.setRootreturnserror.NoWebView: there is no WebView to serve media to. The HTTP media server is unaffected. WebView builds are unchanged.
Oriel 0.9.9 · October 9, 2026
Oriel 0.9.9 adds oriel.tts: offline text to speech that works out of the box, with Kokoro-82M voices, verified downloads, bundled phoneme data and streaming playback on the CPU or the GPU. It also brings mDNS to Windows, drag and drop to the iOS native renderer, and Windows fixes.
Added
oriel.tts: Kokoro-82M text to speech, the counterpart oforiel.chatandoriel.dictation. A catalog of models (kokoro-82m-q8_0, the default, andkokoro-82m-f16) and curated voices for English, Spanish, French, Portuguese, Italian, Japanese, Chinese and Hindi, downloaded from Hugging Face and checked against their SHA-256 (download,delete,status).speakreads Markdown as prose, guesses the language (lang = "auto") and picks a voice for it, and streams playback in adaptive chunks: the first audio comes quickly and there are no gaps when synthesis is faster than real time.stopworks from any thread;warmUploads the model (and compiles GPU pipelines) ahead of the first utterance;unloadIdlefrees it under memory pressure. Options cover model, voice, language, speed, Markdown, backend and threads; results report first-audio time, synthesis time, real-time factor and gaps. Events: “tts:download” and “tts:state”.audio_play.Stream: streaming playback through a bounded ring, one device per utterance (append,seal,finished,stopfrom any thread,playedSeconds/queuedSeconds/starvedSeconds), next to the one-shot API.model_download.fetchVerified: downloads checked against a SHA-256; a mismatching file is deleted anderror.ChecksumMismatchreturned.- espeak-ng’s phoneme data is compiled at build time from the espeak-ng 1.52.0 sources and shipped by
addAppwith every.kokoro = trueapp: next to the executable on Linux and Windows (and in their packages), inContents/Resourceson macOS, in the iOS.app, and in the Android APK’s assets (extracted on first start). Thetts_languagesoption picks the dictionaries (defaulten,es,fr,pt,it,ja,cmn,hi);zig build espeak-datawrites the directory alone. - Kokoro on the GPU with
-Dggml_cuda,-Dggml_vulkanor-Dggml_metal, through ggml’s backend registry, falling back to the CPU. Measured on an RTX 4070: first audio ~54 ms warm on CUDA, real-time factor ~0.02-0.03 on CUDA and Vulkan (Vulkan compiles its pipelines for 1-4 s on first use); 8 threads of a Ryzen 7 7800X3D: ~0.36. Metal compiles in but hasn’t been measured. - Windows
oriel.network.mdnsbackend on dnsapi’sDnsServiceRegister,DnsServiceBrowseandDnsServiceResolve(Windows 10 1809+). TXT values must be UTF-8 text there. - iOS native renderer: drag and drop into the page through
UIDropInteraction: files (by data type), Photos images, text, and links (text/uri-listandtext/plain), matching AppKit’s. - Showcase: a Speak tab (voices, models, speed, CPU/GPU, Markdown, live state, a result card with first audio, real-time factor and gaps), Read aloud on chat replies and notes, a warm-up on the tab’s first visit, and the
--sayand--tts-downloadheadless flags.
Improved
- Phones run Kokoro on their fast cores only and shorten the first chunk to the measured speed. Low-end phones still synthesize slower than real time: a Snapdragon 732G (2 threads) reaches a real-time factor of ~2.75, with first audio after ~4 s and pauses between chunks.
oriel devrebuilds keep a Zig--fork=<path>option.- Windows tray icons are decoded with zigimg instead of WIC, so creating and destroying trays no longer grows the process’s kernel handles.
Fixed
- Windows: closing a window or quitting while a native-renderer context menu or the tray menu is open no longer hangs; a native page that closes its own window from a control’s handler is closed once that handler returns.
- Native-renderer sliders with huge ranges reach their min and max on Windows, and the page-side value no longer becomes
Infinity. - Windows
dialog.saveToFolderrefuses names Win32 programs can’t open (device names such asCONornul.txt, a trailing dot or space,<>:"|?*) witherror.InvalidName. - Android:
audio_playbuilds with NDK 28 (nullability qualifiers in its headers). - Windows: Kokoro links espeak-ng statically (
LIBESPEAK_NG_EXPORT), so its API is no longer declared as imported from a DLL.
Compatibility
KOKORO_ESPEAK_DATA_PATHis now optional: apps no longer supply espeak-ng data. Set it only to use other data than the bundled copy.- Android projects generated before this release need one line in
app/build.gradle.kts, insideandroid { }:sourceSets["main"].assets.srcDirs("../../zig-out/android-assets")(the path from the app module tozig-out). The Kotlin runtime is updated by the build. - Apps built with
.kokoro = truenow ship espeak-ng’s data (GPL-3.0-or-later, about 2.9 MB with the default languages); seeNOTICE.
Oriel 0.9.8 · October 9, 2026
Oriel 0.9.8 is a memory-safety release. It fixes use-after-free, out-of-bounds, overflow and leak defects across the native UI, platform layers, modules and CLI tooling.
Fixed
- Update manifests: base64 signatures, public keys and seeds must decode to exactly their expected size, closing a stack overflow reachable from an untrusted manifest before signature verification.
- Native DOM: mutation observer hooks no longer run page code in the middle of a store operation, so observers can no longer corrupt or free nodes still in use.
host.canvasre-reads its buffer after page code can run, wrappers are re-validated after allocation,:nth-childarithmetic no longer overflows, and serialization and deep cloning no longer recurse per tree level. - Native UI: repeating gradients with many stops no longer write past a stack buffer, focus and blur no longer run page code while fields are being synchronized (GTK, UIKit), page geometry and slider values are clamped before integer conversion, and uninitialized text layout offsets, error-path leaks, stale event pointers and unreleased accessibility elements are fixed.
- Win32: clipboard paste is bounded by the clipboard allocation, controls are no longer destroyed under an open context menu, and GDI, font-cache and event handle leaks are fixed.
- Android: native-page commands are queued instead of running inside the page’s own JavaScript, so a command that closes its window can no longer free the running engine. Dropped-file descriptors and window sizes are handled safely.
- Linux: a rejected window URL no longer leaks a window with a dangling close handler, an empty second-instance command line no longer crashes the primary instance, and windows, queued replies and timers are released at shutdown.
- macOS and iOS: pending WebKit replies are answered before release at shutdown, and single-instance and scene teardown no longer reuse closed descriptors or freed windows. Windows
showWindowno longer reads window state off the main thread. - Modules and plugins: fixes for the macOS file watcher, the store keeping borrowed JSON values, Linux menu and tray leaks, tray D-Bus depth handling, clipboard image decoding, X11 shortcut re-entry, Windows share events and the Wayland keymap.
- AI and audio: empty SQL statements, WAV sample rates, prompt and download sizes, grammar sampling and empty prompts are validated; GPU loaders clear their function pointers when a library fails to load.
- CLI and tooling:
oriel wrapno longer returns a name from freed memory, Android manifest sync no longer reads stale keys, confirmation prompts accept Enter, long deep-link schemes work, and network responses are size-bounded.
Compatibility
- Unpadded or otherwise mis-sized base64 signatures and keys in update manifests are rejected with
error.InvalidPadding. - Native pages: layout stops at 512 levels of nesting, gradients use at most 256 stops, and selectors are limited to 16 nested
:not/:is/:hasand 32 compound parts (longer ones throwSyntaxError). - Window URLs containing control characters are rejected when the window is opened.
App.getWindowandApp.getWindowByHandleare main-thread only. - New errors:
error.SqliteEmptyStatement(oriel.sql),error.PromptTooLong,error.EmptyPromptanderror.SampleFailed(chat),error.DownloadTooLarge(model downloads). WAV input below 4000 Hz is rejected.
Oriel 0.9.7 · October 8, 2026
Oriel 0.9.7 adds opt-in offline text-to-speech with Kokoro-82M and built-in PCM playback.
Added
oriel.kokoro: typed C-ABI bindings for model initialization, voice loading, language selection, synthesis, error reporting and PCM cleanup.oriel.audio_play: mono float32 playback through miniaudio, with progress, natural-completion callbacks, stop/shutdown, andfinished()polling.- Enable TTS with
.kokoro = truealongside.llama = trueor.whisper = true. Kokoro shares Oriel’s GGML build and adds espeak-ng phonemization and Highway SIMD. Apps supply model/voice GGUF files and compatible espeak-ng runtime data. AppOptions.include_pathsforwards app-owned C include directories to desktop, iOS, Android and TypeScript-generation root modules.- Repository and website guides for offline speech synthesis, asset setup, playback ownership, callback behavior and dependency notices.
Fixed
- Playback teardown detaches sample state before joining the device thread, preventing stop/start deadlocks with audio callbacks.
- Lifecycle operations are serialized; stop and finished are safe before initialization, and stopped/completed audio buffers are filled with silence.
- Playback implementation and Zig declarations use the same vendored header, including whisper-only TTS builds and coexistence with llama’s mtmd audio code.
- Optional TTS dependencies are downloaded lazily, keeping default builds lean.
- Invalid synthesis output is rejected before converting C sample counts/rates.
- macOS TTS builds use espeak-ng’s upstream endian compatibility header.
Validation and dependencies
- TTS-enabled tests exercise ABI defaults, PCM buffer completion, and repeated start/stop against miniaudio’s silent null backend. Linux CPU synthesis is verified with the local Kokoro Q8_0 model and English voice pack.
- Release CI enables TTS tests on Linux and macOS in addition to the existing framework, CLI, Windows and iOS checks. GPU synthesis and physical audio-device behavior are not claimed as verified across every platform.
- The optional TTS stack includes espeak-ng (GPL-3.0-or-later). Oriel’s framework remains MIT; upstream component license texts are included in
NOTICEandlicenses/tts. Model assets are supplied separately by apps.
Oriel 0.9.5 · October 8, 2026
Oriel 0.9.5 fixes the validated concurrency, memory-lifetime and security issues from a security and memory-safety review, and documents which reported findings were not supported by the current code.
Fixed
- File-handle tables synchronize access through reads and descriptor duplication, preventing release from recycling a descriptor in use.
- Worker-pool shutdown withdraws the pool, waits for borrowed references, drains accepted jobs, and rejects later submissions without leaking them.
- Async command errors retain their own text until the UI consumes the reply.
- Lazy window creation snapshots registered options before releasing the lock.
- Cross-window
emitTonow checks the same modification permission as other window mutations. Smoke and Showcase explicitly opt into cross-window events. - Isolation frames validate message origins and send replies only to explicit local origins; the Windows/Android bridge skips subframes.
- iOS Inbox cleanup only removes canonical files directly inside the app’s own Inbox, using directory-relative deletion.
- Gzip update expansion is capped at 512 MiB in memory, on disk, and during macOS bundle verification/extraction. Expiring manifests reject invalid clocks.
- Windows media streams serialize Read and Seek and advance by actual bytes read.
- The local media server validates loopback Host headers on every route and scopes its ping response’s CORS header to the configured app origin.
- Zig model downloads require HTTPS on initial URLs and redirects.
- Share-read and dev-server allocation failures release intermediate buffers; internal window and received-file cleanup consistently follows its allocator.
Documentation and validation
- Added the website’s Wrap a web app guide and navigation links.
- Validated every finding in the security and memory-safety review, correcting ownership, tar extraction, token exposure, command-line reader, and optional expiration claims.
- Added regression tests for concurrency, error lifetimes, window permissions, path guards, Host validation, HTTPS URLs, gzip limits, and isolation messages.
Compatibility
- Cross-window events require
security.window_api.allow_modify_other_windows = true. App.getWorkerPool()borrows must be paired withApp.releaseWorkerPool();ThreadPool.post()returns whether it accepted ownership of a task.- Publishers must keep expanded gzip update payloads within the 512 MiB limit.
Oriel 0.9.4 · October 8, 2026
Oriel 0.9.4 adds desktop wrappers for web apps, app-owned Android extensions, and searchable documentation.
Added
oriel wrap <url>(aliasoriel pake) scaffolds a desktop app for a website, with tray controls, close-to-tray behavior, automatic favicon fetching, optional Linux user-agent overrides, and AppImage packaging.- App-owned Android extensions can handle Activity and WebView lifecycles, permissions, activity results, and file selection without editing generated runtime sources. Configure extension classes, Maven SDK dependencies, and repositories in the app’s build configuration.
- Android app manifests can declare vendor native libraries with
uses-native-libraryentries. - Pagefind search on the documentation site, with keyboard navigation and indexed article content.
Improved
- Android templates now use Android Gradle Plugin 8.13.2 and Kotlin 2.3.21, with the typed Kotlin JVM target configuration.
- Native-renderer documentation describes renderer capabilities independently of individual release versions.
Fixed
- Linux disables WebKitGTK DMABUF rendering when the proprietary NVIDIA driver is detected, avoiding compositor-related high CPU usage. An explicit
WEBKIT_DISABLE_DMABUF_RENDERERsetting remains respected.
Oriel 0.9.3 · October 5, 2026
- Android templates and examples now compile and target API 36, meeting the current Google Play target requirement. Updated AGP to 8.9.3 and generated Gradle wrappers to 8.14.3; minimum Gradle is 8.11.1. Android 10 remains supported.
Oriel 0.9.2 · October 5, 2026
Oriel 0.9.2 lets a page find out where a dropped file lives now, so a handler can send it on to the system.
Added
oriel.drop.path(file)for native-renderer pages: where a dropped file lives now, for a handler that forwards it (a transfer engine opens the path). TheFilea drop delivers carries an opaquehandle; the answer is a path string, ornullwhen the file was deleted or changed since the drop, the handle is unknown, or the platform can’t resolve paths. The Linux and Windows native bridges answer it after the command policy check; a WebView page has no handles and nodrop:path. Electron’swebUtils.getPathForFileis the precedent. Seedocs/drag-and-drop-design.md.
Oriel 0.9.1 · October 5, 2026
Oriel 0.9.1 adds system sharing, persistent folder access and service discovery, and improves native controls, accessibility and platform integration.
Added
oriel.network.mdnsservice registration and browsing, with Android’s NsdManager backend and theoriel_mdns_*C ABI for native libraries.- System sharing APIs for sending content, receiving shared files and reading received files from pages, with platform capability reporting.
- Folder picking with lasting write access, including Android’s Storage Access Framework and iOS folder access.
oriel.system.deviceName()to read the name the user gave their device.- Bluetooth and local-network permissions, per-platform permission extras, and support for app-owned Android Kotlin/Java sources and R8 rules.
- Optional Windows MSIX packaging and packaged Share Target support.
- Native push buttons, checkboxes and radio controls across supported backends; Apple accessibility trees for VoiceOver and macOS assistive technology.
- Native renderer forced-colors support, CSS system colors and contrast media queries, including Windows high-contrast settings.
- Windows native-renderer drag and drop, context menus and pointer handling.
Improved
- Native controls follow dark mode and system accent changes, with improved keyboard behavior, read-only fields and accessible names.
- Windows 11 field styling, typography and overlay scrollbars.
- Native text layout, inline geometry, baselines, lists, fieldsets and borders across Linux, Windows, macOS, Android and iOS.
- Events received before page listeners are ready are queued for delivery.
Fixed
- Android mDNS resolution no longer crashes while encoding services with multiple TXT attributes. Attributes are iterated directly rather than copied through Android ArrayMap’s unsupported entry-set
toArray()method. - Windows packages fail clearly when WebView2Loader.dll is missing.
- Windows sharing preserves existing OpenWithProgids registrations.
- Native controls avoid spurious radio changes, clipped select controls and accidental button taps while scrolling on iOS.
Oriel 0.9.0 · October 4, 2026
Oriel 0.9.0 lets apps react to notification clicks and buttons, brings drag and drop to the native renderer, and closes desktop input gaps for ChromeOS and desktop Android. Windows support for these lands in 0.9.1. See Notifications for the notification API.
Added
- Notification click actions:
oriel.notification.notifytakes.actions(buttons), and clicks on a notification or one of its buttons reachoriel.notification.onAction(handler)in Zig and the page’snotification:actionevent as{ id, action }(actionis null when the notification itself was clicked).- Linux: GNotification default action and buttons.
- macOS (
.appbundles) and iOS:UNNotificationCategorybuttons and the notification center delegate. - Android: a tap opens the app and reports the click, also when it launches the app; buttons (at most 3) report theirs without opening the app and dismiss the notification.
- Windows: a click on the balloon is reported; balloons have no buttons.
- The showcase’s Notify button sends a notification with Like and Later buttons and shows which one was clicked.
- Drag and drop into native-renderer pages (
-Dnative_ui) on Linux, Android and macOS:DragEvent,DataTransfer,Blob,File,FileReaderandFileList. Dropped files are opened read-only when they are dropped and read asynchronously; the page never sees their paths. Seedocs/drag-and-drop-design.md. - The page’s
<meta name="theme-color">is reported to its window in both renderers; on ChromeOS and desktop Android it colours the window’s caption. - Native renderer on Android: a mouse right-click opens the page’s context menu, and the pointer shows a hand over clickable elements (links amid text included) and an I-beam over text fields.
Improved
- Native renderer mouse events report the changed
button(2 for the secondary, 1 for the middle button), fireauxclickafter a non-primary release, and give a mouse’scontextmenubutton 2. macOS sends right, middle and other buttons as WKWebView does. - Links and other clickable elements inside a line of text take their own clicks in the native renderer.
- Native renderer border widths snap to device pixels as each platform’s browser does; key scrolling glides like WKWebView’s;
scrollWidthmatches WebKit’s horizontal range. - Android:
white-space: pretext keeps its line breaks.
Security
- A file dropped on a text field no longer inserts its absolute path: native fields leave drops to the page, and in WebView mode the bridge cancels WebKit’s default for a file dropped on an unhandled field.
Oriel 0.8.0 · October 4, 2026
Oriel 0.8.0 introduces the optional, experimental native UI renderer alongside the default system WebView. The same app HTML, CSS and JavaScript run in QuickJS and draw through Oriel’s native platform backends.
Added
- Build with
-Dnative_uion Linux, Windows, macOS, Android and iOS. oriel android dev/buildandoriel ios dev/buildforward Zig-Doptions. For example:oriel android build --abi arm64 --apk -Dnative_ui -Doptimize=ReleaseFast.- Android projects for the Breakout, Canvas Demo and render-bench examples.
- Power sampling instructions and support for Android battery readings and Linux battery or RAPL counters.
Improved
- Android suspends native animation frames while a window is hidden and resumes them when it becomes visible.
- Android text metrics, controls, scrolling, input and slider endpoints more closely match the platform WebView.
- Native canvas recording and transform updates do less per-frame work.
- Inline text, SVG styling, line layout, gradients and DOM cleanup have been improved across platform backends.
- Breakout bounds long-running ball speed, and its Zig mode applies the ball count selected in Settings.
Performance notes
The Android x86_64 emulator completed the render bench at about 60 fps for animation and canvas workloads. This validates that build; it is not an Android before/after comparison and does not measure battery savings. The desktop QuickJS dispatch changes reduced isolated callback overhead, while the full desktop comparison showed no clear overall speedup. See the benchmark results.
The native renderer remains experimental and supports a subset of browser HTML, CSS and APIs. The default WebView build remains available.