Skip to content

Repository files navigation

NaHAL — HAL navigator

NaHAL is a graphical navigator for HAL (Hypertext Application Language) APIs, plus the navigation layer it is built on, for Kotlin Multiplatform. Point it at a HAL endpoint and walk the API by following links, expanding URI templates, inspecting embedded resources and building requests — on the desktop, in a browser, or embedded in your own application.

Note

The HAL client library and the example plugins are not part of this repository.

  • C06A/HALDiSh_KMP — the Haldish HAL client (parsing, URI templates, HTTP, the plugin contract). Published as com.helpchoice.nahal:haldish. Change the HAL client there, not here.

  • C06A/HALDiSh_Plugins — the example plugins, published as com.helpchoice.nahal:haldish-plugin-<name>. They also own the tasks that launch NaHAL with plugins active.

This repository builds NaHAL on top of both: :core, :ui, :testkit, :testkit-groovy.

What is in this repository

Module Artifact Role

:core

com.helpchoice.nahal:nahal-core

Navigation layer over Haldish — HalNavigator, LinkSelector, DocLinkResolver, config-driven plugin loading, and JS / Wasm / C facades. No application entry point.

:ui

com.helpchoice.nahal:nahal-ui

The Compose Multiplatform navigator GUI: desktop, browser, iOS.

:testkit

com.helpchoice.nahal:haldish-testkit

Kotlin test DSL for driving HAL APIs in tests.

:testkit-groovy

com.helpchoice.nahal:haldish-testkit-groovy

Groovy/Spock bindings for the testkit.

Supported platforms

Platform What you get How it is delivered

macOS (JVM)

Desktop app

.dmg installer, or ./gradlew :ui:jvmRun

macOS (native)

Desktop app, no JVM required

NaHAL.app bundle built by ./gradlew :ui:bundleMacosArm64App

Linux

Desktop app

.deb installer, or ./gradlew :ui:jvmRun

Windows

Desktop app

.msi installer, or ./gradlew :ui:jvmRun

Browser (JS)

Web app

nahal-ui-web-<version>.zip, served from any static host

Browser (Wasm)

— not shipped yet

The wasmJs target builds klibs only; see Wasm status

iOS

Embeddable view controller

MainViewController() from the nahal-ui klib; no packaged app in this build

Linux / macOS / Windows ©

Shared library

libnahal_core.{so,dylib,dll} + generated header

Running the app

Desktop, any OS (JVM)

./gradlew :ui:jvmRun          # opens a 1280×820 window

Installers are built by Compose Desktop, which only produces the host format — a full set needs a CI matrix across the three operating systems:

./gradlew :ui:packageDistributionForCurrentOS
# output: ui/build/compose/binaries/main/{dmg,deb,msi}/
# e.g. on macOS: NahalNavigator-<version>.dmg

macOS, without a JVM (Kotlin/Native)

The macOS targets also build a standalone executable and wrap it in an app bundle:

./gradlew :ui:runMacosArm64App     # Apple silicon — builds NaHAL.app and opens it
./gradlew :ui:runMacosX64App       # Intel
# bundle only, no launch: ./gradlew :ui:bundleMacosArm64App
# output: ui/build/NaHAL.app
Note

runMacos*App launches through open, and macOS LaunchServices does not pass the shell environment to the app. Environment variables such as HALDISH_CONFIG set in a terminal will not reach it — run the executable directly (ui/build/bin/macosArm64/releaseExecutable/ui.kexe) when you need them.

Browser

Build the bundle, or take the released zip:

./gradlew :ui:jsBrowserDistribution
# output: ui/build/dist/js/productionExecutable/
unzip nahal-ui-web-<version>.zip -d nahal-web
cd nahal-web
python3 -m http.server 8080
# open http://localhost:8080/

The bundle contains index.html, the compiled ui.js, the Skia runtime (skiko.js, skiko.wasm) and composeResources/. Three things to know:

  • It must be served over HTTP. file:// fails, because skiko.js fetches skiko.wasm.

  • The server must send application/wasm for .wasm (Python’s http.server and modern nginx already do; older nginx needs types { application/wasm wasm; }).

  • Paths in index.html are relative, so hosting under a sub-path works unchanged.

Because a browser build is subject to the same-origin policy, every API you navigate to must return Access-Control-Allow-Origin for your host. APIs that work in the desktop build can still fail in the browser for this reason alone.

Wasm status

:ui declares wasmJs { browser() } with no binaries.executable(), so no webpack bundle or distribution task exists for it — only the published klib. Add binaries.executable() to that target to build a Wasm web app alongside the JS one.

iOS

:ui publishes iOS klibs and exposes a Compose entry point; this build produces no .app or .ipa. Embed it from an Xcode project:

// SwiftUI
struct NaHalView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController { MainViewController() }
    func updateUIViewController(_ vc: UIViewController, context: Context) {}
}

Plugins

NaHAL ships without plugins, and activates nothing unless a configuration source names it — a plugin sitting on the classpath or in a drop-in directory stays inert on its own.

Runtime How plugins get in Ordering and properties

JVM

Drop *.jar into $NAHAL_PLUGINS_DIR (default plugins/ in the working directory). The app puts them on a child classloader; that is all it does.

HALDISH_CONFIG — a JSON or YAML file listing plugin class names. File order is chain order; each entry’s children are passed to the plugin’s initialize().

Native

No reflection: plugins are compiled in and registered by class name before the UI starts.

HALDISH_CONFIG (JSON only) selects which registered plugins run, in what order.

Browser

Registered by the hosting page.

window.__nahalConfig.

Any, single artifact

HALDISH_PLUGIN_PATH with no HALDISH_CONFIG — Haldish’s own loader takes that one artifact (a JAR on the JVM, a .dylib/.so/.dll on native). Naming the artifact is the configuration.

Chain several by pointing at a chain artifact. Note the native C ABI carries no properties.

To run the UI with the example plugins chained (CURIE expansion → base-URL rewriting → request logging):

(cd ../HALDiSh_Plugins && ./gradlew :chain:jvmRun)          # desktop, JVM
(cd ../HALDiSh_Plugins && ./gradlew :chain:runMacosX64App)  # desktop, native macOS

The plugin contract itself — hooks, lifecycle, the C ABI, packaging — is specified in PLUGIN_CONTRACT.md.

Using nahal-core as a library

// build.gradle.kts
dependencies {
    implementation("com.helpchoice.nahal:nahal-core:<version>")
}
import com.helpchoice.nahal.core.HalNavigator
import com.helpchoice.nahal.core.LinkSelector
import com.helpchoice.nahal.core.RequestSpec

HalNavigator().use { navigator ->
    // Fetch a starting document
    val root = navigator.send(RequestSpec(url = "https://api.example.com/")).document!!

    // Follow a link by rel — plugins' preLink hook runs, the URI template is expanded,
    // the response is parsed
    val orders = navigator.navigate(root, LinkSelector.TopLevel("orders"))
    println("${orders.statusCode} ${orders.url}")

    // Links inside embedded resources and collection items
    navigator.navigate(root, LinkSelector.InEmbedded("ea:order", linkRel = "self"))
    navigator.navigate(root, LinkSelector.InItems(itemIndex = 0, linkRel = "self"))
}

DocLinkResolver resolves a rel’s documentation URL from the HAL curies relation, walking outward through enclosing resources.

JavaScript / Node.js

The :core JS target publishes a library with an exported navigator facade:

./gradlew :core:jsNodeProductionLibraryDistribution
const nav = new JsCoreNavigator();
nav.linkHref(halJson, 'orders');                       // href for a top-level rel
nav.embeddedLinkHref(halJson, 'ea:order', 'self');     // href inside an embedded resource

On Wasm the same operations are module-level functions (coreLinkHref, coreEmbeddedLinkHref), because Kotlin/Wasm restricts @JsExport to functions.

C / C++

./gradlew :core:linkReleaseSharedMacosArm64   # or …LinuxX64, …MingwX64, …MacosX64
# output: core/build/bin/<target>/releaseShared/
#   libnahal_core.dylib   (.so / .dll)
#   libnahal_core_api.h
const char* core_link_href(const char* halJson, const char* rel);
const char* core_embedded_link_href(const char* halJson, const char* embeddedRel,
                                    const char* linkRel);

The full HAL client C API — HTTP verbs, headers, multipart, URI templates — belongs to Haldish and is documented in its repository.

Building and testing

./gradlew build              # all modules, host platform targets
./gradlew :core:jvmTest      # core tests (JVM is the only target with a standard runner)
./gradlew :core:coverageReport

Because Maven Central currently serves only Haldish 1.0.1, building against the current 2.0.0 needs it published locally first — settings.gradle.kts adds mavenLocal() for exactly this:

(cd ../HALDiSh_KMP && ./gradlew publishToMavenLocal -PRELEASE_SIGNING_ENABLED=false)
./gradlew publishToMavenLocal -PRELEASE_SIGNING_ENABLED=false
(cd ../HALDiSh_Plugins && ./gradlew publishToMavenLocal -PRELEASE_SIGNING_ENABLED=false)

That is also the bootstrap order between the three repositories: Haldish → this build → plugins (:testkit here consumes the CURIE plugin).

Release artifacts

./gradlew stageReleaseArtifacts    # collects into build/release/, uploads nothing
Asset Contents

NahalNavigator-<version>.dmg / .deb / .msi

Desktop installer — host OS format only

nahal-ui-web-<version>.zip

Browser app bundle (JS)

nahal-native-<platform>-<version>.zip

nahal-core shared library + C header, for macos-arm64, macos-x64, linux-x64, windows-x64

<asset>.sha256

Checksum for every asset above

Attach all of them to the GitHub release. Klibs, sources and javadoc are not duplicated here — Maven Central serves those for Gradle and Maven users.

  • HALDiSh_KMP — the Haldish HAL client library this navigator is built on

  • HALDiSh_Plugins — example plugins, and the tasks that run NaHAL with them active

  • HALDiSh — a BASH script implementation of HAL client functionality

  • MockingHAL — a mock HAL server useful for testing plugins

About

Client to Navigating HAL API server

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages