v1.0.0-RC4
v1.0.0-RC4
This release supersedes v1.0.0-RC3, which was discarded for a compilation performance regression that has since been reverted.
v1.0.0-RC4 makes Kyo a place to build and drive the web. kyo-ui turns a web UI into a pure value that runs unchanged on the client, on the server, or as static HTML, and kyo-browser drives a real browser to exercise it, both on JVM, JS, Native, and now WebAssembly π
The theme is one source reaching more targets, now even past Kyo itself: kyo-ffi binds native C libraries once and enables calling them in JVM, JS, and Native, kyo-compat enables shipping a single library to six effect backends, and WebAssembly joins as a fourth platform, now published to Maven Central. The framework is fully documented now, every module carrying a README whose examples compile in CI.
The new getkyo.io is itself a Kyo app, generated from those same docs.
New Features
New modules
-
kyo-ui(README) is a web UI framework where a signal stands in for a plain value in most places the API takes one, so a setter or child slot takingAalso takesSignal[A]orSignalRef[A]with no wrapper type and no binder operator. Invalid markup does not compile, and updates are fine-grained with no virtual DOM, re-rendering only the subtree bound to a changed signal. The same UI value runs unchanged across three runners (UI.runMountfor a Scala.js client,UI.runHandlersfor a server-driven app,UI.runRenderfor an HTML stream), and markup reaches past HTML to typed SVG and a typedChartlayer that lowers to it. (by @fwbrasil in #1649, #1654, #1664, #1665, #1671) -
kyo-browser(README) is cross-platform browser automation built directly onkyo-http's WebSocket transport speaking Chrome DevTools Protocol, with no WebDriver and no Node-driver shim, so it compiles on JVM, JS, Native, and Wasm. Every page-mutating action routes through a settlement pipeline (frame-lifecycle tracking, a mutation observer, a stability sampler, per-element actionability checks) so a call returns only when the page is ready for the next action, which is what makes it more suitable as the substrate for AI-agent browser driving. It replaces the JVM-onlykyo-playwright. (by @fwbrasil in #1644, #1671) -
kyo-compat(README) lets you write a library once against thekyo.compat.*surface and ship it to six effect backends (Kyo, ZIO, Cats Effect,scala.concurrent.Future, Ox, Twitter Future), selected by the consumer at deploy time.CIO[+A]is a per-backend opaque type and every method is aninline deflowering to the backend primitive, so there is no dispatch and no adapter, and eachkyo-compat-<backend>artifact depends only on its target library, so a non-Kyo consumer pulls zero Kyo code. Backend support is not uniform: Future, Ox, and Twitter Future are JVM-only; Cats Effect is JVM and JS; Kyo and ZIO are all three. (by @fwbrasil in #1627, #1637, @ghostdogpr in #1661, @DamianReeves in #1677) -
kyo-ffi(README) lets you bind a C library once with typed Scala signatures and call it from JVM (Panama), JS (koffi), and Native. You declare atrait X extends Ffiof typed method signatures (primitives, case-class structs, enums, unions, handles, buffers, callbacks) and build-time codegen generates the per-platform implementation. The runtime layers a safe tier over an explicitAllowUnsafetier, models absence and failure withMaybeandResult, and runs blocking JS calls on a fiber. (by @fwbrasil in #1668, #1674) -
kyo-tasty(README) is cross-platform TASTy reflection (JVM, JS, Native, Wasm) that answers reflection questions about Scala 3 code without a live JVM,Class.forName, or an agent. A classpath is bound for a block withTasty.withClasspath(roots) { ... }with noContextthreaded, and navigation, member traversal, subtyping, and rendering are pure instance methods on a sealed, immutable model so matches stay exhaustive. Cold load is optimized via memory-mapped reads, parallel decode, lazy symbol-body decode, and a binary snapshot cache. (by @fwbrasil in #1669) -
kyo-test(README) is a Kyo-native test framework that replaces ScalaTest and now runs most of the project's own test suites. You extendTest[S]and register cases with"name" - { ... }and"name" in { ... }whose body is a captured Kyo computation, so async and sync tests share one shape and an extra effect is declared inSand discharged with.handle. It ships a power-assertwith subexpression diagrams, property-based testing with shrinking and seeded replay, snapshot testing, decorators (retry,timeout,flaky,pending, platform gates, tags,focus), and a runner with console, TAP, and JUnit-XML reporters. (by @fwbrasil in #1658) -
kyo-slack(README) is a Slack Socket Mode client built around one scope-managed entry point,Slack.run(config)(handler), which streams typedSlackEnvelopevalues into the handler. The handler returns oneSlackAckand the framework emits exactly one wire ack from it, so forgetting to ack and double-acking are both unrepresentable. Reconnect defaults to overlap with a seen-id window and residue drain so no envelope is lost or duplicated. It is cross-platform across JVM, JS, Native, and Wasm. (by @fwbrasil in #1673) -
kyo-doctest(README) is a kyo-agnostic sbt plugin plus JVM runner that compiles (does not run) every fenced Scala block in a Markdown file against its module's classpath and validates every internal link, replacing mdoc. A block-level DSL in HTML-comment modifiers controls scope, expectation, target platforms, and setup prelude, and a content-hash cache keeps per-block cost near zero. Every module now carries a README, so the whole project is documented. (by @fwbrasil in #1645) -
kyo-case-app(README) connects case-app to Kyo entrypoints (KyoCaseApp[T],KyoCommand[T]) across JVM, JS, Native, and Wasm, so you can build a declarative CLI with options, positionals, and subcommands and register effectful work after parsing. It also introducesKyoAppRunnerinkyo-coreas the shared, public API for running Kyo effects after a third-party framework has started.KyoAppis refactored onto it, and it is made public so external entrypoints (decline, zio-cli, Spark-style mains) can use it without copying runner code. (by @DamianReeves in #1619) -
kyo-website(README) is a Scala 3 static-site generator plus akyo-uiandkyo-httpsingle-page bundle that replaces the hand-maintained docsify docs site. Content reads the live repo: the root README's Modules table drives the catalog and sidebar, each module README renders to a page, and a sharedSiteApprenderer produces both build-time HTML (JVM) and the browser's first paint (JS). (by @fwbrasil in #1676)
Platform parity
- WebAssembly (WasmGC) as a fourth cross-build target: a
WasmPlatformis added to the sbt cross-build (Scala.js with the experimental WebAssembly linker and ESModule output), and JS and Wasm share platform code through ajs-wasmsource directory. Every module targeting Scala.js now also targets Wasm, except the two cats-effect bridges (kyo-cats,kyo-compat-ce, which lack upstream Wasm support), and the full stack up to and includingkyo-httpservers runs on WasmGC, serving real HTTP and HTTPS over Node sockets. WebAssembly artifacts are published to Maven Central askyo-<module>_sjs1-wasm_3, alongside the JVM, JS, and Native coordinates. Requires Node 24 or later. (by @fwbrasil in #1663)
Improvements
Concurrency and scheduler
-
Reliable fiber interrupt delivery: A lost-update race on the single
@volatileTask.statecould clobber a just-set interrupt bit, so a fiber interrupt could be delayed to reach the worker via the fiber state. Interruption is now sourced fromIOPromise,Task.requestInterruptis removed andTask.statepacks only runtime and preemption, so there is no cross-thread interrupt write to race. (by @fwbrasil in #1628) -
Interrupt starvation under queue pressure: a CPU-bound fiber preempted into a worker's run queue and then interrupted could be starved before observing the interrupt, because a completed task did not reset its accumulated runtime and the heap did not re-sift an in-place priority change. A completed task now calls
Task.resetRuntime(), and each worker rebalances when the global interrupt epoch advances, re-heapifying so the reset task surfaces within a bounded, load-independent delay; the generic queue is replaced by aTask-specialized 4-ary min-heap. (by @fwbrasil in #1667) -
Fatal Throwable no longer kills a worker:
IOTask.eval's panic catch went throughResult.Panic.apply, which rethrows fatal Throwables (LinkageError, OOM), so the rethrow escaped the catch, the worker thread died, theIOPromisewas never completed, and awaiters hung forever. An inner try/catch now falls back to theResult.Panicconstructor so the promise is always completed and the panic surfaces through the normal fiber result channel. This is a best-effort fix at the scheduler boundary. (by @gcsolaroli in #1528) -
Signalreactive combinators:Signal[A]exposed onlymapand streaming helpers, leaving the standard reactive combinators missing. It now carriesswitchMap,zip, andcombineLateston the instance andSignal.awaitAny,zipAll, andcombineLatestAllon the companion, named after their Rx equivalents because they do not satisfy the monad laws. A bug where the first subscriber to a freshSignalRefgot different interrupt semantics than every later one is fixed as well. (by @fwbrasil in #1648) -
Clock.withTimeControlshares one timeline in nested scopes: a nestedwithTimeControlscope used to create a fresh controlled clock with a separate timeline. It now reuses the current localTimeControlwhen one is already active, so nested scopes share one clock timeline. (by @cerredz in #1601)
General
-
Sync.acquireReleaseWithbracket operator: adds a lightweight acquire/use/release bracket onSyncthat releases only after a successful acquire, covering success, use-side panic cleanup, and acquire-side panic. (by @cerredz in #1604) -
KyoAppaccepts non-Throwable aborts:KyoAppused to accept onlyAbort[Throwable]. Its boundary now widens toAbort[Any], converting non-Throwable abort failures to a typedKyoApp.FailureExceptionat the app boundary while keeping the existing Throwable and panic handling. (by @cerredz in #1605)
Streams, parsing, and data
-
Parse.repeatdrops from quadratic to linear:ParseInput.remainingreturnedtokens.drop(position), materializing the entire remaining input on every call, soParse.repeatover n tokens made O(n) calls each copying O(n) data.ParseInputnow caches an indexed chunk derived once at construction and reads positions in constant time. (by @fwbrasil in #1672) -
Stream#zip:Streamlacked the standardzipmethod. It now takes values chunk-at-a-time, emits zipped chunks, and completes when one stream is done with no leftovers remaining; there is no N-ary variant, since deriving the tag for every combination is impractical. (by @johnhungerford in #1539) -
Pathconfinement, piped subprocess stdin, andStream.find: three additive gaps surfaced building filesystem-server features.Pathgainscwd,ancestors,realPath(resolves symlinks), andconfinedTo(root), which realPath-resolves both sides and aborts if the target is not under the root, making it the recommended way to validate untrusted paths.Process.Input.PipeandCommand.pipeStdinstream content into a subprocess' stdin, andStream.findshort-circuits upstream on the first match. (by @fwbrasil in #1656) -
Windows drive letter dropped by
Pathreconstruction:kyo.Pathmodeled an absolute path as a driveless segment list, but on Windows the JVM keeps the drive ingetRoot, sopartsforC:\Windows\System32dropped the drive and any reconstruction silently re-anchored the path to the process's current drive, corrupting I/O and makingconfinedTodrive-blind (a multi-drive sandbox-escape footgun). The jvm-nativePathPlatformSpecific.partsnow emits the drive designator as the leading root segment (gated onPlatform.isWindows), reconstruction is drive-rooted, and the js-wasm variant round-trips identically, so paths preserve their drive andconfinedToenforces confinement per-drive. (by @DamianReeves in #1679) -
Schedulecombinator fixes:Schedule.linear(base)produced a doubling sequence instead of a linear one, one of nine combinator bugs of similar shape.Linearnow carries(current, step),Jitterclamps its factor before multiplying (it was biasing the mean about 12.5% high),Repeatcounts emissions and restarts sofixed(X).repeat(N)emits N, caps are respected, and several smart-constructor reductions are corrected, so the combinators now match their docstrings. (by @fwbrasil in #1632) -
Frame.calleeName:Frame.methodNamereturned the enclosing method's name with no accessor for the syntactic name of the called function.methodNameis renamed tocallerName, and a siblingcalleeNameis added that reads the source backwards from the splice. (by @fwbrasil in #1642)
Schema, HTTP, and integrations
-
kyo-schema YAML support: kyo-schema had no YAML, and a produced CST could be edited and rendered but not consumed as a decode source. Full YAML support now covers parse and visit APIs, schema encode and decode, YAML 1.2 defaults with a 1.1 scalar mode, document-stream selection and merging, tags, anchors, aliases, block scalars, and flow collections, plus a direct decode path with bounded source replay and no JSON bridge. A parsed-and-edited CST becomes a pipeline source that
visit,render,parse, anddecodeaccept, with comments intact. (by @DamianReeves in #1652, #1655) -
kyo-schema Ion codec: adds Amazon Ion encode and decode entry points plus
IonReaderandIonWriter, supporting schema-shaped Ion text (structs, lists, maps, blobs, typed nulls, comments, symbols as strings, timestamps, annotations as metadata).Ion.decodeis a single-value schema decode API; Ion annotations are accepted and validated as input but not preserved or emitted. (by @hearnadam in #1659) -
kyo-schema derivation fixes:
Schema.derived[F[A]]crashed at expansion for any generic case class with a default-valued field, and discriminator-tagged sum types nested inside another case class lost their discriminator on JSON and Protobuf round-trips. Default-method references now apply the case class's type arguments before lifting, and nested-field serialization routes through the transform-aware dispatch layer so the discriminator survives. (by @fwbrasil in #1641) -
caliban GraphQL subscriptions over WebSockets: kyo-caliban had no WebSocket integration, so GraphQL subscriptions did not work over the kyo-http server. A WebSocket endpoint at
${config.path}/wsnow bridges kyo-http'sHttpWebSocketto caliban'sCalibanPipe, supporting both thegraphql-transport-wsand legacygraphql-wssubprotocols and all sixWebSocketHooks. Three pre-existing non-WebSocket bugs are fixed as well: malformed POST, SSE, and upload bodies returned HTTP 500 instead of a GraphQL error envelope. (by @fwbrasil in #1634) -
kyo-http WebSocket frame-size enforcement and reader failures:
HttpWebSocket.Config.maxFrameSizewas not enforced during inbound decoding, its 64 KiB default was too low for realistic single-frame payloads, and an aborted reader silently discarded its typedHttpException. The cap is now enforced while decoding inbound frames on both server and client read loops, the default is raised to 16 MiB, and both monitor fibers pattern-match the reader's result and log the typed exception instead of discarding it. (by @NguyenCong2k in #1625, @fwbrasil in #1643) -
kyo-http client filter scopes: the client side could not apply cross-cutting concerns (auth, request IDs, logging, tracing, metrics) consistently across convenience calls and WebSocket handshakes.
clientFilteris added toHttpClientConfigwithfilter/filtersbuilders andHttpClient.withFilter/withFiltersfor scoped configuration; filters compose in order and apply to WebSocket upgrade handshakes as well as HTTP requests. (by @DamianReeves in #1662)
STM and direct
-
kyo-stm coverage and five concurrency fixes: kyo-stm carried several latent concurrency bugs.
TRef.usenow consults the write lock and aborts on a write-locked ref (it used to permit the read, violating opacity), bounded barging lets a writer make progress under sustained reader pressure, andSTM.runcommits aTRefholdingnull.TTable.insertnow draws ids from a lock-free counter that skips upsert-occupied ids whileTTable.Indexed.upsertdrops stale index entries, so concurrent inserts no longer contend, overwrite upserted records, or leak index entries. (by @fwbrasil in #1629) -
kyo-stm
TMap.initWithandcontains:TMap.initWithdid not apply or return the provided function's result, andTMap.contains(key)checked only map non-emptiness instead of the requested key. Both are fixed. (by @NguyenCong2k in #1624) -
kyo-direct macro hygiene and Validate edge cases: a
valwhose right-hand side used.nowinside adirect { ... }block emitted trees with the wrong owner for the synthetic monad given, failing dotty's owner-consistency check, and three Validate bugs rejected valid programs. The innercps.awaitquote is now built with the correct owner, and Validate handles multi-statement async-shifted lambdas, pattern-bound effect-typed identifiers, and by-name-parameter.nowwith a clear error. (by @fwbrasil in #1633)
Documentation and tooling
-
Unified scaladocs and public-surface cleanup: scaladoc rendering was broken across modules (Scala 2 wiki syntax rendered as literal text, scalafmt collapsing docstrings) and
kyo.*carried implementation-detail and platform-internal traits. This adds sbt-unidoc with a Scala 3 fix and a workflow that publishes scaladocs on release tags, bulk-converts wiki syntax to Markdown across all modules, and relocates a surgical set of types off the public surface (7 or more traits tokyo.internal;RenderedtoRender.Rendered,SubjecttoActor.Subject,HttpFilterFactorytoHttpFilter.Factory, and others). (by @fwbrasil in #1635) -
-Werroron Scala 3: no-Werrorgate existed on Scala 3, so warnings accumulated silently across modules.-Werroris now appended for Scala 3 (Scala 2.13kyo-schedulerexcluded), the resulting warnings are fixed, deprecated FFI shim files and thekyo.IO/kyo.Resourcetop-level aliases are removed, and the codebase is warning-clean. (by @fwbrasil in #1675, #1636) -
Explicit
JVMsuffix on sbt project ids: the JVM variant of each cross-platform module used the bare module name (kyo-core) while JS, Native, and Wasm carried suffixes, a recurring source of confusion. Every JVM project id now carries aJVMsuffix (kyo-corebecomeskyo-coreJVM); published Maven coordinates are unchanged, so only sbt project ids are affected. (by @fwbrasil in #1670) -
Doc clarifications: the docsify site's deep module-README links resolved instead of 404'ing through a repository
basePath(later superseded by thekyo-websiteSSG), and theMemoREADME example is clearer aboutMemo.run's return type. (by @rbobillot in #1651, @cerredz in #1602)
Breaking changes
Frame.methodNamerenamed toFrame.callerName(no alias; siblingcalleeNameadded). (by @fwbrasil in #1642)- The
Texttype is removed: allTextsignatures becomeString,Render[A].asTextbecomesRender[A].asString,t""now returnsString, and theAnsiTextoverloads are gone. (by @fwbrasil in #1646, supersedes @cerredz in #1603) kyo.IOandkyo.Resourcetop-level deprecated aliases removed (useSyncandScope). (by @fwbrasil in #1675)- Public-surface relocations:
RenderedtoRender.Rendered,SubjecttoActor.Subject,HttpFilterFactorytoHttpFilter.Factory,Schema.Builder/Compare/Focus/Modifynested underSchema, andCalibanRunnerandImagerelocated; 7 or more traits moved tokyo.internal. (by @fwbrasil in #1635) - sbt project id change (not a Maven-coordinate change): cross-platform JVM project ids gain a
JVMsuffix (kyo-corebecomeskyo-coreJVM), so<module>/testno longer resolves; use<module>JVM/test. (by @fwbrasil in #1670) - Module removed:
kyo-playwright(replaced bykyo-browser). (by @fwbrasil in #1644) - Native behavior change: trampoline depth lowered from 512 to 256 to match Wasm (native frames are larger). (by @fwbrasil in #1663)
- Wasm build requires Node 24 or later. (by @fwbrasil in #1663)
New Contributors
- @cerredz made their first contribution in #1602
- @NguyenCong2k made their first contribution in #1624
- @DamianReeves made their first contribution in #1619
- @rbobillot made their first contribution in #1651
Full Changelog: v1.0.0-RC2...v1.0.0-RC4