Skip to content

Conversation

@xerial
Copy link
Member

@xerial xerial commented Feb 4, 2026

Summary

  • Add client-side routing support using the History API for building SPAs
  • Router object for global navigation (push, replace, back, forward)
  • RouterInstance for route matching with reactive outlet and params
  • Route patterns support named parameters (:id) and wildcards (*)
  • SPA-style link() that prevents page reloads and adds active state
  • Query string and hash parsing utilities in Location and RouteParams
  • Reactive location state (pathname, search, hash)

Usage

import wvlet.uni.dom.all.*

val router = Router(
  Route("/", _ => HomePage()),
  Route("/users/:id", p => UserPage(p.pathParam("id"))),
  Route("*", _ => NotFoundPage())
)

def App() = div(
  nav(
    router.link("/", "Home"),
    router.link("/users", "Users")
  ),
  main(router.outlet)
)

// Programmatic navigation
Router.push("/users/123")
Router.back()

Test plan

  • All 201 uni-dom tests pass
  • 29 new RouterTest cases covering Location, RouteParams, Route, RouterInstance, and Router

🤖 Generated with Claude Code

@github-actions github-actions bot added doc Improvements or additions to documentation feature New feature labels Feb 4, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @xerial, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances uni-dom by integrating a robust client-side routing system. The new routing functionality allows developers to build dynamic Single Page Applications (SPAs) with ease, managing navigation and URL state reactively. It provides a structured way to define routes, extract parameters, and programmatically control the browser's history, moving uni-dom closer to a full-fledged web framework.

Highlights

  • Client-side Routing: Introduces comprehensive client-side routing capabilities to uni-dom using the browser's History API, enabling the creation of Single Page Applications (SPAs).
  • Global Navigation (Router object): A global Router object is provided for programmatic navigation actions such as push, replace, back, and forward.
  • Route Matching (RouterInstance): The RouterInstance handles route matching, providing reactive outlet for rendering matched components and params for accessing route parameters.
  • Flexible Route Patterns: Route patterns now support named parameters (e.g., :id) and wildcards (*) for dynamic and catch-all routes.
  • SPA-style Links: A router.link() helper creates <a> elements that prevent full page reloads and automatically apply an 'active' class for styling.
  • URL State Utilities: New Location and RouteParams objects provide utilities for parsing query strings and hash values, and reactive streams for pathname, search, and hash are available.
Changelog
  • plans/2026-02-04-dom-enhancements.md
    • Added a detailed plan outlining the design, API, usage examples, and implementation specifics for the new router feature.
  • uni-dom-test/src/test/scala/wvlet/uni/dom/RouterTest.scala
    • Added 29 new unit tests covering the functionality of Location, RouteParams, Route, Router, and RouterInstance to ensure the new routing system works as expected.
  • uni/.js/src/main/scala/wvlet/uni/dom/Router.scala
    • Introduced the core implementation of the client-side router, including Location for URL data, RouteParams for parsed parameters, Route for route definitions, Router for global navigation, RouterInstance for route matching, CompiledRoute for internal pattern matching, and RouterLink for SPA-friendly navigation elements.
  • uni/.js/src/main/scala/wvlet/uni/dom/all.scala
    • Exported the new routing components (Router, RouterInstance, Route, RouteParams, Location) to the wvlet.uni.dom.all package for convenient access throughout uni-dom applications.
Activity
  • The pull request introduces a major new feature: client-side routing for uni-dom.
  • A comprehensive design document (plans/2026-02-04-dom-enhancements.md) was added, detailing the API and implementation strategy.
  • The core routing logic has been implemented in uni/.js/src/main/scala/wvlet/uni/dom/Router.scala.
  • Extensive unit tests (29 new cases) have been added in uni-dom-test/src/test/scala/wvlet/uni/dom/RouterTest.scala to validate the new functionality.
  • The new routing components are exported for easy use in uni/.js/src/main/scala/wvlet/uni/dom/all.scala.
  • All existing 201 uni-dom tests continue to pass, alongside the new router-specific tests.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a client-side router, a significant and well-implemented feature for uni-dom. The core implementation is solid, with a clean API for defining routes, navigation, and reactive route matching. The use of the History API and handling of navigation edge cases like modifier key clicks are well done.

My review includes a few suggestions for improvement:

  • The design document has a small discrepancy with the final implementation regarding the outlet property, which should be updated for clarity.
  • Some of the new tests are quite weak, only checking types rather than behavior. I've provided a suggestion to make one of the reactive tests more robust by asserting on actual values after state changes.
  • There's a minor bug in the route pattern compilation logic that prevents matching literal * characters in paths.

Overall, this is a great addition. Addressing these points will improve documentation accuracy, test coverage, and robustness.

```scala
class RouterInstance[A](routes: Seq[Route[A]]):
// Current matched route
def outlet: Rx[Option[A]]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The API design for outlet has diverged from the implementation. The implementation provides outlet: Rx[A] (which throws on no match) and a new outletOption: Rx[Option[A]]. This design document should be updated to reflect the actual API. The usage example on line 91 should also be updated to use outletOption (e.g., router.outletOption.map(_.getOrElse(div("Loading...")))).

Suggested change
def outlet: Rx[Option[A]]
def outlet: Rx[A]
def outletOption: Rx[Option[A]]

Comment on lines 156 to 172
test("Router.isActive emits values reactively"):
val router = Router(Route("/", _ => "home"))
var result = false
val cancel = router
.isActive("/")
.run { v =>
result = v
}
// In jsdom test environment, current location may vary
result shouldMatch { case _: Boolean =>
}
cancel.cancel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test only verifies that isActive returns a boolean, but it doesn't check the actual value or test the reactive nature of the operator. To make this test more robust, you should programmatically change the URL using Router.push and assert that the isActive stream emits the expected boolean values in response.

  test("Router.isActive emits values reactively"): 
    val router = Router(Route("/", _ => "home"))
    var result = false
    val cancel = router
      .isActive("/")
      .run { v =>
        result = v
      }

    // Set a known state and verify
    Router.push("/other")
    result shouldBe false

    Router.push("/")
    result shouldBe true

    cancel.cancel

Comment on lines 368 to 370
val regexPattern = paramPattern.replaceAllIn(
scala.util.matching.Regex.quote(route.pattern).replace("\\*", ".*"),
"([^/]+)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation for compiling route patterns has a subtle bug. By calling replace("\\*", ".*") after Regex.quote, it becomes impossible to match a literal * character in a route path, as it will always be treated as a wildcard. This could lead to unexpected routing behavior.

A more robust approach would be to parse the pattern segment by segment, or use a regex that can distinguish between a wildcard * and a path parameter, without using a broad string replacement after quoting.

@xerial xerial force-pushed the feature/dom-enhancements branch from 9ce8ac7 to d126642 Compare February 4, 2026 21:30
Add client-side routing support using the History API for building SPAs:

- Router object for global navigation (push, replace, back, forward)
- RouterInstance for route matching with reactive outlet
- Route patterns with named parameters (:id) and wildcards (*)
- SPA-style links that prevent page reloads
- Query string and hash parsing utilities
- Reactive location state (pathname, search, hash)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@xerial xerial force-pushed the feature/dom-enhancements branch from d126642 to 068664d Compare February 4, 2026 21:34
@xerial xerial merged commit 0b42eb2 into main Feb 5, 2026
13 checks passed
@xerial xerial deleted the feature/dom-enhancements branch February 5, 2026 00:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc Improvements or additions to documentation feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant