Repository navigation
Move the HTML UI toolkit into a library of its own #258
Description
Janus renders its UI as HTML, serves it on a loopback port and shows it in an embedded Chromium (JCEF). None of that is wallet-specific, and it has turned out to be a nice way to build a Java desktop app, so let's pull it out into a library other projects can use.
How it works today
The page posts to /action/<name> through htmx. A method annotated @Action("<name>") on a bean answers: it takes nothing or a Form, returns a View that names a Thymeleaf fragment, and the rendered fragment gets swapped into the page. Return nothing and the page stays as it is. ActionExtension picks the methods up while CDI starts, so adding an action is just adding a method.
What to do
- Move the generic parts of
webandshellinto a new repo: server and routing,@Actiondispatch,Guard/SessionToken, the JCEF window and engine handling, and the page scripts (htmx,window.js,failure.js).core, the controllers, views, templates, styling and the installers stay here. - Publish releases to GitHub Packages so Janus can depend on a version from its Maven build. The
webandshellmodules go away. - Strip out what is Janus-specific: the window title, icon, minimum size,
janus.*properties and theUnigridcommand name, the~/.janusfolder, the fixedtemplates/andstatic/roots, and the markup the scripts assume (.card,.step__error). All of that has to come from the application. - Document it: a README with a minimal app, a guide to actions, views, window control and engine location, and Javadoc on everything public.
- Add a small sample app in the library repo that runs from a clean checkout.
Design
I'd keep @Action as a plain method that returns its result. It reads well, a unit test just calls it, one method answers one name, and the return type can grow later (redirect, download). I looked at doing it with CDI events instead and don't think it fits: observers are void and broadcast, so the view would have to be set on the event and we'd have to police that only one observer answers.
Worth adding: @Param("passphrase") String passphrase so form values arrive as parameters, and CDI events the app can observe for lifecycle (ServerStarted, WindowOpened, WindowClosing, FileChosen) rather than the toolkit calling into it. Actions.of stays so a controller can be tested without a container.
Tests are jqwik, as here. @UiTest would be a jqwik lifecycle hook that boots the container and server around each try (Playwright on request) and hands the test a Client, Page or browser Page as a parameter. The test-jar should ship arbitraries for action names, form fields, tokens and file paths, so "nothing the page can post makes an action answer 500" is a @ForAll away.
Open
- Name. Casement, Oriel, Skylight?
- One artifact or two (server, window host)?
Activity
adam-waldenberg commentedon Oct 9, 2026
Too big for one change. Let's split it into sub-issues as we go (the move, the de-Janus work, test support, docs, sample app) and keep this one as the overview.
adam-waldenberg commentedon Oct 29, 2026
Done in #259, now on master.