@jdubois/boot-ui
BootUI — Copilot instructions
Install
agr install @jdubois/boot-ui --target copilotWrites 1 file into .github/copilot-instructions.md, pinned to git-24049a67.
- .github/copilot-instructions.md
Document
BootUI — Copilot instructions
BootUI is a local-only developer console (Vue 3 SPA + REST API at /bootui) that drops into a host application. It
targets two frameworks from one codebase: Spring Boot 4 and Quarkus. Both adapters serve the same Vue UI,
the same /bootui/api/** JSON contract, and reuse the same framework-neutral engine — so a panel should look and
behave identically on either runtime. The authoritative scope/behavior lives in docs/SPECIFICATION.md, docs/PLAN.md,
docs/FEATURES.md, and docs/QUARKUS-SUPPORT.md — read those before changing public behavior or visible panel behavior.
Maturity, stated honestly so you don't assume parity: the Spring Boot adapter is complete (all panels). The Quarkus
adapter is being built out — panels light up as the shared engine grows. Treat "make it work on both" as the default
design constraint for new shared code, but check QuarkusPanelAvailability / docs/QUARKUS-SUPPORT.md for what is actually
live on Quarkus today rather than assuming a panel already works there.
Dual-framework architecture (read first)
The core idea: push behavior down into a framework-neutral engine, keep each framework as a thin adapter.
bootui-core DTO records (core/dto/*), SecretMasker, BootUiInfo, ValueExposure — zero framework deps
bootui-engine Framework-neutral services + advisor engines, plus the neutral SPI ports each adapter implements
(the io.github.jdubois.bootui.spi package: ExposurePolicy, MemoryRuntimeConfig, BasePackageProvider,
… over core DTOs only); depends on core; no framework/DI annotations
bootui-conformance Test-support: one abstract HTTP contract suite + golden panel fixtures both adapters run
bootui-ui Vue 3 + Vite SPA, built once into META-INF/resources/bootui/
# Spring Boot adapter
bootui-spring-autoconfigure Thin @RestController endpoints + SPI impls + safety filters + EnvironmentPostProcessors
bootui-spring-boot-starter Drop-in dependency (autoconfigure + ui + spring-boot-starter-web + actuator)
bootui-spring-sample-app Reference Spring Boot 4 app for demos + Playwright e2e
# Quarkus adapter
bootui-quarkus Runtime: JAX-RS resources + SPI impls + CDI @Produces + Vert.x safety filter
bootui-quarkus-deployment Build-time wiring (build steps, bean registration, prod gating)
bootui-quarkus-integration-tests @QuarkusTest conformance + smoke (Docker-free)
bootui-quarkus-sample-app Reference Quarkus app (wired via a JDK-gated reactor profile — see below)
Load-bearing rules:
- Dependency direction is one-way:
core ← engine, and each adapter depends on both. Shared modules (core,engine,conformance,ui) must never depend on a framework. This is enforced at build time byEngineBoundaryArchitectureTests(ArchUnit), which bansorg.springframework..,jakarta.servlet..,jakarta.ws.rs..,io.quarkus..,io.vertx..,org.jboss.., and both JSON libraries (tools.jackson..,com.fasterxml.jackson..) from thebootui-engineservices, with a companionSpiBoundaryArchitectureTestspinning the neutral SPI ports (theio.github.jdubois.bootui.spipackage, folded intobootui-engine) framework-free. A leak fails the build. - The engine is JSON-free on purpose. Spring Boot 4 ships Jackson 3 (
tools.jackson.*); Quarkus ships Jackson 2 (com.fasterxml.jackson.*) — incompatible artifact and package. So JSON parsing/serialization lives in the adapter, which feeds the engine already-parsed neutral records and serializes the engine's DTO records back out. The DTO records carry no Jackson annotations, so they serialize correctly under both. - Engine classes carry no framework or DI annotations and are wired by an explicit factory per adapter. Spring uses
@Beanmethods inBootUiEngineConfiguration(@Lazy,@ConditionalOnMissingBean) that commonly construct the engine service inline from its SPI impl (e.g.new MemoryReportProvider(new SpringMemoryRuntimeConfig(environment))); Quarkus uses@ProducesinBootUiEngineProducer. When an adapter actually injects an SPI impl (notably the Quarkus producer), inject the concrete impl (e.g.QuarkusExposurePolicy), not the SPI interface, so adding another impl later can't make CDI resolution ambiguous. - The optional-dependency classloading trap (R2).
<optional>true</optional>stops transitive propagation but does not make classloading safe. An engine class must not statically import an optional type (Flyway/Liquibase/Hikari/ Hibernate/servlet/security). Keep the presence decision in the adapter (@ConditionalOnClass/ a CDI build step), construct the engine service only when the dependency is present, and inject the already-resolved handle into its constructor. Where one optional API is unavoidable, concentrate it in a single engine reader pinned by an ArchUnit rule (precedent:jakarta.persistenceonly inJpaMetamodelReader). - Two repeating extraction templates (the choice recurs for every panel moved into the engine):
- Live-policy SPI interface — for config the operator can change at runtime (exposure/masking, virtual-threads,
health-probe presence). The engine takes an SPI interface and re-reads it per request; each adapter implements it over
its own config (
Environmentfor Spring, MicroProfileConfigfor Quarkus). - Static settings record — for config read once and never rebound (no UI/override path). The engine takes an immutable record; the adapter factory maps its properties onto it inline.
- Decision rule: does the property have a live-override / UI-toggle path? Yes → policy interface. No → settings record.
- Live-policy SPI interface — for config the operator can change at runtime (exposure/masking, virtual-threads,
health-probe presence). The engine takes an SPI interface and re-reads it per request; each adapter implements it over
its own config (
- How a panel is served on each side, given a shared engine service:
- Spring: a thin
@RestControllerunder...autoconfigure.webinjects the engine bean and maps query params; the controller is added to@Import(...)onBootUiAutoConfiguration. - Quarkus: a thin JAX-RS
@Path("/bootui/api/xxx")resource under...quarkus.webinjects the engine bean; the SPI impl bean is pinned unremovable inBootUiQuarkusProcessor, and the panel id is added toQuarkusPanelAvailability.
- Spring: a thin
Toolchain
- Java 17 (compiler
release17,-parameters). Maven Wrapper (./mvnw), Maven 3.9.16; do not require a system Maven. - Spring Boot 4.1.x (
spring-boot.versionin rootpom.xml; currently 4.1.0). - Quarkus 3.37.x for the extension and integration suites (
quarkus.platform.versionin the rootpom.xml; currently 3.37.2). The Quarkus sample app has a separate platform pin aligned with its Quarkus LangChain4j dependency. - Published Maven coordinates use
com.julien-dubois.bootui:*; Java packages remainio.github.jdubois.bootui.*. - Node.js / npm for the packaged Vue app are downloaded automatically by the
frontend-maven-plugin(node.version/npm.versionin rootpom.xml); do not add a manual Node install step for the Maven build. - JDK caveat for the Quarkus sample app: Hibernate ORM's build-time ByteBuddy enhancement (via its pinned Quarkus
platform) cannot read class files newer than the JDKs that platform supports (17, 21 and 25). So
bootui-quarkus-sample-appstays in the always-on reactor (so IDEs such as IntelliJ import it on any JDK), but its Hibernate/Quarkus build-time augmentation is gated: askip-quarkus-build-on-unsupported-jdkprofile (<jdk>[26,)</jdk>) in the module's own pom unbinds the quarkus-maven-plugin (<phase>none</phase>) on JDK 26+, so plain compile (release 17) + jar still run everywhere and the module has no tests to fail. The root-pomquarkus-sample-appprofile (<jdk>[17,26)</jdk>) now gates only the Hibernate integration-test module (its@QuarkusTesttests force augmentation). CI and releases run Java 17 and augment the sample app fully; the extension's own modules (bootui-quarkus*, integration tests) have no Hibernate dependency and build on every JDK.
Build, run, test
# CI-equivalent multi-module build (downloads Node, tests/builds Vue UI, runs both adapters' suites, packages all JARs).
# On Java 17 this also builds + augments the Quarkus sample app via the JDK-gated profile.
./mvnw -B -ntp clean install
# Maven Central release path for non-SNAPSHOT versions.
./mvnw -B -ntp -Prelease clean deploy
Spring Boot adapter
# Backend + UI iteration loop. NOTE: -am pulls in bootui-ui (the starter depends on it), so the Vue app IS rebuilt.
./mvnw -pl bootui-core,bootui-spring-autoconfigure,bootui-spring-boot-starter,bootui-spring-sample-app -am install
# To genuinely skip the Vue rebuild, drop -am (works once bootui-ui has been installed at least once).
./mvnw -pl bootui-core,bootui-spring-autoconfigure,bootui-spring-boot-starter,bootui-spring-sample-app install
# Fastest sample app launch (smoke-test path: http://localhost:8080/bootui).
./mvnw -o -ntp -pl bootui-spring-sample-app -Dmaven.test.skip=true spring-boot:run -Dspring-boot.run.profiles=dev
# If offline mode misses a dependency, drop -o once.
# Single test class / single test method.
./mvnw -pl bootui-core test -Dtest=SecretMaskerTests
./mvnw -pl bootui-core test -Dtest=SecretMaskerTests#detectsCommonSecretKeys
Do not add -am to spring-boot:run: Maven applies the goal to every selected reactor project, including the
parent/core/UI modules, and those modules have no main class. Use -am for build/test reactor work instead
(./mvnw -pl bootui-spring-sample-app -am test).
Quarkus adapter
# Build + test the Quarkus extension and its Docker-free @QuarkusTest conformance suite (works on any JDK).
./mvnw -B -ntp -pl bootui-quarkus,bootui-quarkus-deployment,bootui-quarkus-integration-tests -am install
# Build/run the Quarkus sample app — requires JDK 17/21/25 (see the JDK caveat above). Point JAVA_HOME at a 17/21/25 JDK.
JAVA_HOME=/path/to/jdk-17 ./mvnw -pl bootui-quarkus-sample-app -am install
JAVA_HOME=/path/to/jdk-17 ./mvnw -pl bootui-quarkus-sample-app -am quarkus:dev # console at http://localhost:8082/bootui/
The Quarkus sample app uses Dev Services (Postgres) and LangChain4j/Ollama, so a full quarkus:dev run needs Docker
and/or Ollama; the reactor install only augments it (no runtime, no Docker). The @QuarkusTest integration suite is
deliberately Docker-free.
Front-end
# Inner loop (Vite dev server with HMR; proxies /bootui/api/* to a running sample app — Spring or Quarkus).
(cd bootui-ui/src/main/frontend && npm install && npm run dev)
# Unit tests (Vitest + Vue Test Utils + jsdom; also run by Maven's test phase unless -DskipTests is set).
(cd bootui-ui/src/main/frontend && npm test)
# After changing UI code that needs to be re-bundled into the JAR:
./mvnw -pl bootui-ui install
# Browser end-to-end suite (required for UI, browser-facing API, or sample-app changes).
(cd bootui-spring-sample-app/e2e && npm ci && npx playwright install --with-deps chromium && npm test)
Isolating parallel worktrees
When working from multiple git worktrees in parallel, avoid a shared install: every worktree builds the same
com.julien-dubois.bootui:* version, so installing overwrites the others in the shared ~/.m2/repository. Install into an
isolated local repository and run from that same repo (per-invocation, or via a per-worktree git-ignored
.mvn/maven.config):
./mvnw -Dmaven.repo.local=.m2 -ntp -pl bootui-spring-sample-app -am -DskipTests install
./mvnw -Dmaven.repo.local=.m2 -o -ntp -pl bootui-spring-sample-app -Dmaven.test.skip=true spring-boot:run -Dspring-boot.run.profiles=dev
Live UI iteration (hot reload)
Editing Vue source does not hot-reload the Maven-served console: spring-boot:run / quarkus:dev (and the app's
/bootui URL) serve the pre-built bundle copied into the classpath at build time, so UI edits only appear after
./mvnw -pl bootui-ui install plus a restart. For a fast loop with hot-module reload (HMR) — including live/visual
iteration in the in-app browser (e.g. when using the Impeccable skill) — run two processes and open the Vite URL:
- Backend (REST API). Run a sample app — Spring
spring-boot:run(serveshttp://localhost:8080/bootui) or Quarkusquarkus:dev(serveshttp://localhost:8082/bootui) — or theDev serverscript in.github/github-app.yml(serves$COPILOT_PORTin the Copilot app). - Frontend (HMR). Run
(cd bootui-ui/src/main/frontend && npm install && npm run dev). Serveshttp://localhost:5173/bootui/and proxies/bootui/api/*to the backend.
Open http://localhost:5173/bootui/ (the Vite server) to see edits live. Point the proxy at a non-default backend
port with BOOTUI_API_PROXY_TARGET (the Copilot app's Vite UI dev server script sets this to $COPILOT_PORT). State-
changing panel actions work through the proxy because both adapters compare the Origin/Host host only (not port),
so the browser's :5173 origin is accepted against the backend's host regardless of its port (:8080 for the Spring
MVC sample, :8081 for the Spring WebFlux sample, :8082 for Quarkus, or $COPILOT_PORT).
CI (.github/workflows/build.yml) runs ./mvnw -B -ntp clean install on Java 17 — which builds both adapters, runs the
shared conformance suite against both, runs the frontend Vitest suite through Maven, builds + augments the Quarkus sample
app (JDK-gated profile), installs Playwright Chromium, and runs bootui-spring-sample-app/e2e with npm test. CodeQL covers
Java/Kotlin and JavaScript/TypeScript when code scanning is enabled. The release workflow publishes v* tags to Maven
Central through the release profile and the Sonatype Central Publishing plugin.
Formatting before commits and PRs
- Before committing, pushing, creating/updating a PR, or marking a PR ready, run the formatters for the areas touched by the change; after broad AI-generated edits, run all of them:
./mvnw -B -ntp spotless:apply
(cd bootui-ui/src/main/frontend && npm run format)
(cd bootui-spring-sample-app/e2e && npm run format)
- Do not commit or create/update a PR until the same formatting checks CI uses pass:
./mvnw -B -ntp spotless:check
(cd bootui-ui/src/main/frontend && npm run format:check)
(cd bootui-spring-sample-app/e2e && npm run format:check)
spotless:checkruns over the whole reactor. On Java 17 that includesbootui-quarkus-sample-app(the JDK-gated module); a newly added Quarkus-side file that isn't formatted will fail CI even if a JDK-26 local run skipped it.
Activation & safety model (critical)
Both adapters fail closed at activation: when activation is ambiguous, BootUI stays disabled and silent. The
request-time access policy is shared: both adapters are thin bindings over the framework-neutral
io.github.jdubois.bootui.engine.safety.LocalhostGuard, which is the single source of truth for the local-only policy
(loopback-source trust on the raw TCP peer, Host allow-list / DNS-rebinding defense, and cross-site-write / CSRF
defense), the check order, and the exact canonical 403 messages. The Spring LocalhostOnlyFilter and the Quarkus
BootUiQuarkusSafetyFilter only translate their native request/config into the guard's neutral
LocalhostGuardRequest/LocalhostGuardConfig and render the LocalhostGuardDecision, so the two adapters reject
identically (same JSON {"error":"…"} body). The pure policy is pinned by LocalhostGuardTests in bootui-engine;
each binding adds thin per-adapter tests that it feeds the guard correctly. Per-panel enable / read-only gating
(bootui.panels.*) is enforced on both adapters too — Spring's PanelAccessFilter and Quarkus's
QuarkusPanelAccessFilter are behavioral twins over the same BootUiPanels registry, the same config keys, and the
same canonical JSON 403 body shape.
Spring Boot
- Two autoconfigurations are registered (not component-scanned) in
META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports:BootUiAutoConfigurationandBootUiSpringSecurityAutoConfiguration(the latter permits BootUI routes and wires SPA-friendly CSRF when Spring Security is on the classpath).BootUiAutoConfigurationis gated byBootUiActivationCondition,@ConditionalOnWebApplication(SERVLET), and@ConditionalOnClass(DispatcherServlet). New controllers must be added to@Import(...)onBootUiAutoConfiguration. - Activation (
BootUiActivationCondition.resolve):bootui.enabled=ON|OFFwins, otherwise an active profile inbootui.enabled-profiles(dev,localby default) orspring-boot-devtoolson the classpath turns BootUI on.bootui.disabled-profiles(prod,production) force-off unlessbootui.enabled=ON. LocalhostOnlyFilter(orderInteger.MIN_VALUE, on/bootui/*and/bootui/api/*) is a thin binding over the engineLocalhostGuard. It fails closed for non-loopback callers; beyond the loopback source check the guard validates theHostheader against the built-in loopback names plusbootui.allowed-hosts(DNS-rebinding defense) and rejects cross-site state-changing requests viaOrigin/Sec-Fetch-Site(CSRF defense that works without Spring Security). The only opt-out isbootui.allow-non-localhost=true. The filter owns only the Spring-side plumbing (the trusted-proxies parse cache, the resolve-once container-gateway snapshot, per-reason logging, the JSON 403 render); the policy lives in the engine.PanelAccessFilterruns after it and enforces per-panelbootui.panels.*settings via theBootUiPanelsregistry: it blocks requests to disabled panels and rejects state-changing methods on action-capable panels marked read-only. Register new action endpoints inBootUiPanelsso these toggles apply.- Four
EnvironmentPostProcessors are registered inMETA-INF/spring.factoriesand run while BootUI is active:BootUiOverridesEnvironmentPostProcessor(runtime config overrides),BootUiActuatorDefaultsEnvironmentPostProcessor(local Actuator/tracing defaults; hostmanagement.*settings still win),BootUiStartupEnvironmentPostProcessor(startup-timeline buffer), andBootUiWebApplicationTypeEnvironmentPostProcessor(forces servlet web type when active and otherwise non-web). Add new post-processors to that same file. - All four EPPs are Spring-bootstrap-only and stay in the Spring adapter (R8 classification): none encodes a semantic
default the shared DTO contract needs both adapters to apply identically, so there is no engine-level equivalent and no
Quarkus EPP. Each compensates for a Spring-bootstrap concept Quarkus does not have, or its target panel is reached
differently on Quarkus: web-type (
spring.main.web-application-type) has no Vert.x analog; the Actuator/tracing defaults are Spring Actuator + Micrometer keys with no Quarkus counterpart (the Health panel reads SmallRye directly, and Traces/AI capture is the in-process OTel exporter); the startup buffer isBufferingApplicationStartup(the Startup Timeline panel is a separate, still-unported Quarkus capture concern); and the overrides property source is Spring precedence (the Quarkus equivalent would be a SmallRyeConfigSource, deferred until the Configuration panel is ported). Note the overrides EPP is the one that does not gate onBootUiActivationCondition— it always loads the overrides file. On Quarkus, local Traces/AI full-fidelity capture relies on Quarkus OpenTelemetry's own default sampler (parentbased_always_on) rather than a BootUI-contributedquarkus.otel.traces.samplerdefault; this is at parity with Spring's parent-basedprobability=1.0for the typical host-unconfigured dev case, and the host's own sampler still wins.
Quarkus
- Activation is decided at build time by launch mode in
BootUiQuarkusProcessor.registerConsole: inLaunchMode.NORMAL(production) the data-bearing/bootui/api/**endpoints, CDI beans, and Vert.x safety filter are not wired at all (prod-dark, fail-closed);DEVandTESTwire them (soquarkus:devand@QuarkusTestexercise the console). The static Vue shell underMETA-INF/resources/bootui/is suppressed in production too, by a separate mechanism: Quarkus' static-resource handler servesMETA-INF/resources/**unconditionally, independent of these launch-mode-gated build steps, so an always-onBootUiProdShellGuardFilter(registered by its own never-gated@BuildStep,registerProdShellGuard) answers a plain 404 for the whole/bootuisurface wheneverLaunchMode.NORMALis active. The build step indexes the extension runtime jar so Arc/RESTEasy discover the beans and@Pathresources, and pins the SPI-backed beans unremovable. New SPI impl beans must be added to theaddBeanClasses(...)list there; new@Pathresources are auto-discovered and need no processor change. BootUiQuarkusSafetyFilteris a global Vert.x HTTP route filter (registered via the@Observes Filtersevent), not a JAX-RS@PreMatchingfilter — a Vert.x filter runs for every request before routing, including unmatched paths (an unmatchedPOST /bootui/api/overviewis 404'd by the Vert.x router before the RESTEasy chain, which a pre-matching filter would miss). It is a thin binding over the same engineLocalhostGuardas the Spring filter, so it enforces the full policy at parity over the whole/bootuisurface: loopback-source trust on the raw Vert.x TCP peer (remoteAddress(), never a forwarded header), theHostallow-list (sourced from theHostheader, falling back to the Vert.xauthority()for HTTP/2:authority), and cross-site-write rejection — rendering the same canonical{"error":"…"}JSON 403. The binding owns the Quarkus plumbing: it readsbootui.allow-non-localhost/allowed-hosts/trusted-proxies/trust-container-gatewaylive from MicroProfileConfig(fail-closed), and resolves the container-gateway snapshot eagerly at startup, off the Vert.x event loop (the detector does blocking/proc/DNS work; lazy first-request resolution would trip theBlockedThreadChecker), and only when gateway trust is notOFF. When you change the policy, change the engine guard (not a binding) and keep the exact 403 reason strings identical across adapters (the SPA / e2e may key on them).QuarkusPanelAccessFilteris a second, lower-priority global Vert.x route filter (priority950, vs. the safety filter's1000, so localhost/Host/CSRF rejection always wins first) that enforces per-panelbootui.panels.*settings via the sameBootUiPanelsregistry Spring'sPanelAccessFilteruses — same config keys, same panel resolution, and the same canonical JSON 403 body shape ({"error":"BootUI panel access denied","panel":"<id>","reason":"<reason>"}). Only/bootui/api/**requests are gated; the static UI shell is never panel-gated (its own production suppression isBootUiProdShellGuardFilter, above).- Config is read live from MicroProfile
Configin the SPI impls (e.g.QuarkusExposurePolicy) and the safety filter, fail-closed on missing/invalid values.
API & DTO conventions
- All endpoints live under
/bootui/api/**. The browser UI is at/bootui/(Vitebase: '/bootui/', hash router). - DTOs are immutable Java
records inio.github.jdubois.bootui.core.dto, one record per file, no Jackson annotations — they must serialize identically under Spring Boot's Jackson 3 and Quarkus' Jackson 2. The UI binds to this stable shape; never return raw Actuator descriptors or framework objects. Map them to DTOs (seeConfigController.toDto). - Never let the engine touch JSON or a framework type. If a panel needs to parse an external JSON response (OSV, GraalVM reachability metadata, GitHub), parse it in the adapter and hand the engine a neutral record; the engine owns the policy/shaping, the adapter owns transport + parsing.
- Spring consumes Actuator endpoints in-process via
ObjectProvider<XxxEndpoint>(seeBeansController,LoggersController); always handlegetIfAvailable() == nullby returning an empty DTO — Actuator may be partially disabled. The Quarkus equivalents read the corresponding Quarkus/SmallRye/Micrometer sources. - Any code path that surfaces a property name/value to the browser must route it through
SecretMasker(behind theExposurePolicySPI:BootUiExposureon Spring,QuarkusExposurePolicyon Quarkus) before serialization. SeeConfigController.toDtoandConfigOverrideService.displayValuefor the pattern. - Do not perform dependency vulnerability lookups on page load. The Vulnerabilities panel lists local dependency inventory
first and calls OSV.dev only after the explicit
/bootui/api/vulnerabilities/scanaction; honorbootui.vulnerabilities.osv-enabled=false. - Keep external/network behavior explicit and bounded: scans are user-initiated, timeouts and max package/advisory limits come from config, and failures return a clear error status while preserving local data.
Conformance harness (both adapters share one contract)
bootui-conformanceholds the abstract HTTP contract suite (AbstractBootUiApiConformanceTest) + a small probe (BootUiHttpProbe) + golden manifests (expected-panels-spring.json,expected-panels-quarkus.json). The Spring side runs it viaSpringApiConformanceTest(boots the sample app); the Quarkus side viaBootUiQuarkusApiConformanceTest(@QuarkusTest).- The suite is availability-driven: every panel that is in
DATA_PANEL_ROOT_GETSand reportedavailable:trueis auto-asserted to answerGET /bootui/api/<id>with 200 + JSON. So lighting up a panel on a new adapter automatically adds black-box coverage — no fixture edit needed (the manifest lists all panels; availability is computed per adapter). - Run conformance at the end of every refactor/extraction step, on both adapters, before committing.
Configuration overrides flow (Spring)
Runtime config edits use two cooperating components:
BootUiOverridesEnvironmentPostProcessor(wired viaMETA-INF/spring.factories) runs atHIGHEST_PRECEDENCE + 10andaddFirsts aBootUiOverridesPropertySourcepopulated from.bootui/application-bootui.properties(path configurable viabootui.overrides-file).ConfigOverrideServicemutates that same property source at runtime and persists viaConfigOverridesFileStore.
Because already-bound @ConfigurationProperties beans won't auto-rebind, every mutation returns a ConfigOverrideResult
whose message warns about restart caveats. Preserve that contract when adding new override paths. (The Quarkus override
equivalent reads MicroProfile Config; design it to the same restart-caveat contract when it lands.)
Current panel surface
bootui-ui/src/main/frontend/src/routes.js is the source of truth for routes and sidebar grouping — App.vue renders the
sidebar from router.options.routes, and both adapters render the same sidebar because they serve the same Vue bundle.
docs/FEATURES.md mirrors the route list. The separate BootUiPanels registry (in bootui-engine) is the backend
catalogue of panel ids used by the API access filters and per-adapter availability — not the sidebar/title source.
Availability is per-adapter: Spring's PanelsController (over Actuator/bean presence) and Quarkus'
QuarkusPanelAvailability; docs/QUARKUS-SUPPORT.md is the prose source for Quarkus-live status (docs/FEATURES.md does
not yet carry a per-platform column). The /bootui/api/panels manifest (PanelsReport) also carries a top-level
platform discriminator (spring-boot | quarkus, set by each adapter's manifest builder) so the shared Vue UI can
render framework-correct setup/empty-state copy (e.g. Traces and AI Usage point Quarkus users at in-process
quarkus-opentelemetry capture instead of the embedded OTLP receiver). The UI reads it via inject('panels') and
defaults to spring-boot when absent — keep that field populated in both adapters and in the conformance fixtures
(expected-panels-spring.json / expected-panels-quarkus.json). The stance is to harden all visible panels, not
hide newer ones. Keep API, UI,
docs/FEATURES.md, and e2e coverage aligned across these nine groups (current order):
-
Overview: Overview, Live Activity, GitHub
-
Advisors: Architecture, REST API, Spring, Hibernate, Memory, Security, Pentesting, Vulnerabilities
-
Runtime: Health, HTTP Sessions, Metrics, Live Memory, JVM Tuning, Heap Dump, Threads, Startup Timeline, GraalVM, CRaC
-
Configuration: Configuration, Profile Diff, Loggers, Beans, Conditions, Mappings
-
Database: Database Connection Pools, SQL Trace, Spring Data, Flyway, Liquibase
-
Security: Spring Security, Security Logs
-
Services: Scheduled Tasks, REST Client, Cache, Email, Kafka, AI Usage
-
Diagnostics: Traces, Log Tail, Exceptions, HTTP Exchanges, HTTP Probe
-
Developer Tools: MCP Server, DevTools, Dev Services, Copilot, Claude Code
-
Some panels are framework-specific. The plan (partly implemented — tracked in
docs/QUARKUS-SUPPORT.md) is to replace the Spring advisor with a Quarkus advisor on the Quarkus adapter; the Cache panel (kept under the shared idcache) is already served on Quarkus overquarkus-cache(see the Cache entry below). A few Spring-only panels (e.g. DevTools, Conditions) have no Quarkus equivalent and stay unavailable there. The GraalVM and CRaC advisors are deliberately not ported to Quarkus (they are not "not yet" — they have no meaningful Quarkus equivalent): Quarkus compiles native images itself and generates its own reachability metadata at build time, and CRaC targets the Spring startup model, so both report a panel-specific "not applicable on Quarkus" reason (QuarkusPanelAvailability.NOT_APPLICABLE) rather than the generic "not yet" message. Use the shared registry + per-adapter availability rather than forking the route list. The Quarkus application advisor deliberately keeps the Spring advisor's panel idspring(route/spring,/bootui/api/spring,SpringReportDTO) instead of minting aquarkusid: one sharedSpring.vuereads the manifestplatformandroutes.jsmeta.titleByPlatformso the sidebar/header already render the "Quarkus" label on Quarkus, and a rename would ripple throughroutes.js,App.vuenavTitle, both adapters' panel registries/resources, the MCP catalog, the conformance golden fixtures, and the e2e suite for purely cosmetic gain — the platform-aware label is the intended seam, so reuse the id. -
As of today the Quarkus adapter lights up the large majority of the panel surface. Statically available (no capability gate): Overview (its scoring dashboard renders client-side from the advisor endpoints; the shell-chrome
GET /bootui/api/overviewsupplies the header data), Architecture, the Quarkus application advisor (panel idspring), Pentesting, Vulnerabilities, Memory, Threads, Heap Dump, Live Memory, JVM Tuning, Metrics, Loggers, Log Tail, Health, HTTP Probe, Beans, Mappings, Configuration (read-only — no override write path), Traces, AI Usage, HTTP Exchanges, Live Activity, Exceptions, MCP Server, and Security (a Quarkus-native ruleset — Elytron/OIDC,quarkus.http.auth.permission.*, TLS, CORS,@RolesAllowed— replacing the Spring-Security-coupled advisor; seedocs/QUARKUS-CHECKS.md). Available when their capability or detector is present: Hibernate (Hibernate ORM), Scheduled Tasks (quarkus-scheduler), Cache (quarkus-cache), Flyway, Liquibase, Database Connection Pools (an Agroal datasource), Dev Services, Security Logs (quarkus-security+quarkus.security.events.enabled), SQL Trace (a datasource), REST API (app-owned JAX-RS resources), Profile Diff (active profiles), GitHub (a detected repository), Email (quarkus-mailer), Kafka (quarkus-messaging-kafkawith a configured channel), and Copilot / Claude Code (a detected agent session directory). Action-capable panels behave identically to Spring, all behind the sharedLocalhostGuardwrite floor: the advisor scans (Architecture, the Quarkus app advisor, Pentesting, Hibernate, Security, Memory, REST API, and Vulnerabilities/OSV), Heap Dump (capture/analyze/delete/download), Threads (download), Loggers (set level), HTTP Probe, Cache (clear), Kafka (clear), Email (clear), Flyway (migrate/clean), Liquibase (update), Traces (clear), and the MCP Server toggle. Only GraalVM, CRaC, Conditions, Startup Timeline, HTTP Sessions, Spring Data, Spring Security, DevTools, and the standalone REST Client panel (Spring MVC only for now) stay unavailable, most with a panel-specific not-applicable reason. The Memory advisor is the cleanest port: every scanner/rule/context class was already framework-neutral (JMX +java.lang.managementonly), so it relocated wholesale into the engineMemoryScanner(built viaMemoryScanner.create(ThreadDumpService, Clock)) with the SpringMemoryControllerreduced to thin wiring and a thin QuarkusMemoryResourcemirror; it is statically available (no capability gate) since JMX is on every JVM, andPOST /scanis@Blockingbecause the heap histogram forces a full GC. Architecture is the first advisor lit up on Quarkus: the shared engineArchitectureScannerruns the curated ArchUnit ruleset against the application's own classes, bounded to base packages discovered at build time from the Jandex application index by aregisterBasePackagesbuild step (the runtimeAutoConfigurationPackageslookup the Spring adapter uses has no Quarkus analogue, and runtime package scanning isn't reliable under the Quarkus classloader). The build step reduces the indexed classes to an antichain of root packages (BasePackageRoots, dropping default/single-segment packages that would scan the whole classpath) and emits them as aRunTimeConfigurationDefaultBuildItemthatQuarkusBasePackageProviderreads back.POST /bootui/api/architecture/scantriggers the scan and dismissals persist to.bootui/boot-ui.ymlviaDismissedRulesResource, exactly as on Spring. Discovery is single-module today (sibling modules in a multi-module build aren't auto-discovered; override with thebootui.internal.base-packagesconfig key — a comma-separated package list — if needed). Loggers is served over the JBoss LogManager that Quarkus uses at runtime (the Spring adapter uses Actuator's loggers endpoint) and was the first Quarkus panel with a state-changing action:POST /bootui/api/loggers/{name}sets a level, guarded by the shared engineLocalhostGuardwrite floor,QuarkusPanelAccessFilter's per-panel read-only gating, and the engine's refusal to mutate BootUI's own loggers. HTTP Probe is also action-capable (POST /bootui/api/http-probe): it issues a local-only request to the application's own loopback port — resolved per-probe byQuarkusServerPortSupplier, which selectsquarkus.http.test-portvsquarkus.http.portbyLaunchModeso the probe targets the actually-bound port (incl. a random=0port, which Quarkus rewrites the property to after binding) — behind the sameLocalhostGuardwrite floor. Metrics is read-only and always available on Quarkus, but reports real meters only when the application adds aquarkus-micrometerregistry: the engineMetricsReportProviderresolves the live compositeMeterRegistrythrough a CDIInstance(absent → renders unavailable), and the meter-visibility predicate is the shared engineMeterSelfFilter(so BootUI's own/bootui/**meters stay hidden, exactly as the Spring adapter feedsBootUiSelfDataFilter::shouldIncludeMeter). Traces and AI Usage reuse the engine telemetry services; their read endpoints are always wired (so the panels render even with no data), but spans are only captured when the application addsquarkus-opentelemetry(in-process via a CDISpanProcessor, no OTLP receiver). Hibernate is the second advisor on Quarkus and the first optional-dependency advisor port: the shared engineHibernateScannerruns the same mapping/identifier/fetch ruleset, but the optionaljakarta.persistenceAPI is confined to two runtime classes (BootUiHibernateProducer,QuarkusEntityDiscovery) that the deploymentregisterHibernateAdvisorbuild step excludes (ExcludedTypeBuildItem, referenced by string name) unlessCapability.HIBERNATE_ORMis present and the launch mode is non-prod — so Arc never links the JPA API in an ORM-absent app (the capability gate, not@Lazy, is the safety mechanism, mirroring the SmallRye-Health/OpenTelemetry producers). When present, entities are discovered from the liveEntityManagerFactorymetamodel (all persistence units, de-duplicated by identity); when absent the scanner is fed an empty-discovery supplier soPOST /scanrenders DISABLED rather than failing, and only the panel's availability tracks the build-timebootui.internal.hibernate-presentflag. Persistence configuration is read throughQuarkusHibernatePropertyLookup, which maps the Spring property names the engine rules expect onto theirquarkus.hibernate-orm.*equivalents (ddl-auto→schema-management.strategy, preferred over the deprecateddatabase.generation, with thedrop-and-create↔create-dropvalue alias,show-sql/format_sql/batch_size); the Open-Session-in-View rule is correctly inert (Quarkus has no OSIV, so the lookup returns the effective-disabled constant and the rule never fires), and Spring Data repository hints are Spring-only. GitHub is the first Overview panel lit up on Quarkus: the shared engineGitHubDashboardServiceis framework- and JSON-library-free, so the Quarkus adapter only supplies a Jackson 2 (com.fasterxml.jackson.*, fromquarkus-rest-jackson)GitHubClientimpl in...quarkus.web.GitHubApiClient(the Spring adapter's is the Jackson 3 twin), reuses the framework-freeDefaultGitHubTokenProvider(env +ghCLI), and@Producesthe engine service fromBootUiEngineProducerover the samebootui.github.*MicroProfile Config keys and defaults (null-safeArrays.asListfor the host allow-list). Availability is dynamic like Spring'sPanelsController.githubAvailable()—QuarkusPanelAvailabilityspecial-cases it throughGitHubRepositoryDetector.detect(user.dir, allowed-hosts)rather than the staticAVAILABLE_PANELSset. The thinGitHubResourcemirrorsGitHubController:GET /bootui/api/githubrenders network-free, only the explicitPOST /bootui/api/github/refreshcalls GitHub (gated byapi-enabled+ host allow-list); cache-on-success and the api-disabledDISABLEDshort-circuit live in the shared engine, so both adapters behave identically. Pentesting is the third advisor on Quarkus and the first action-capable advisor port (POST /bootui/api/pentesting/scan, behind the shared engineLocalhostGuardwrite floor): the shared enginePentestingScannerowns the bounded synthetic-loopback probe methodology unchanged, soQuarkusPentestingObservationCollectorsupplies only a framework-neutralPentestingObservation— the live server port (reusing theQuarkusServerPortSupplierlaunch-mode resolution the HTTP Probe panel uses) and thequarkus.http.root-pathcontext path the probes need, plus a deliberately neutral endpoint/security/config snapshot. The empty endpoint inventory is load-bearing, not lazy: a non-zeroapplicationMappingswould fire the engine'sPT-A07-001(HIGH, "spring-security-web not present") false positive on every Quarkus app, so the inventory stays empty and the Spring-Security/Actuator checks short-circuit. No deployment build step is needed (the@Produces PentestingScannerrides on the already-registeredBootUiEngineProducerand thePentestingResourceis auto-discovered from the indexed runtime jar). One honesty caveat: the engine-owned OWASP coverage matrix copy is Spring-worded, so a no-finding category renders a Spring-flavoredPASS/REVIEWeven on Quarkus. Vulnerabilities is the first non-advisor data panel with a user-initiated network action on Quarkus: the local dependency inventory is captured at build time fromCurateOutcomeBuildItem.getApplicationModel().getRuntimeDependencies()(jar-filtered) by aregisterDependencyInventorybuild step and surfaced as the runtime config defaultbootui.internal.dependencies(comma-joinedgroupId:artifactId:version, each coordinate defensively skipped if it contains a comma/$/whitespace so the comma channel can't be corrupted nor trip SmallRye${...}expansion), read back byQuarkusDependencyProvider— the Quarkus analogue of Spring'sDependencyCatalogclasspath scan, which is unreliable under the Quarkus classloader (this mirrors the Architecture base-package discovery exactly).GET /bootui/api/vulnerabilitieslists that inventory and never calls OSV on render; only the user-initiatedPOST /bootui/api/vulnerabilities/scaninvokes the adapter-sideOsvVulnerabilityScanner(a Jackson-2 port —com.fasterxml.jackson, fromquarkus-rest-jackson— of the Spring scanner, delegating all aggregation/ordering to the engineDependencyReports), behind the sharedLocalhostGuardwrite floor. It honorsbootui.vulnerabilities.osv-enabled=false(→DISABLED, no network call) and therequest-timeout/max-packages/max-advisorieslimits; failures return anERRORstatus while preserving the local inventory. No optional-dependencyCapability/ExcludedTypeBuildItemgate is needed (inventory is build-time; OSV uses the JDKHttpClient+ always-present Jackson 2), so the panel is statically available. Beans is served over the live Arc/CDI container (the Spring adapter uses Actuator'sBeansEndpoint): the shared engineBeansServiceowns the neutral sort/filter/query/page concerns, and the framework-neutralBeanProviderSPI is implemented byQuarkusBeanProvider(overBeanManager.getBeans(...)) on Quarkus andSpringBeanProvideron Spring.quarkus-arcis a core extension dependency, so Beans is always-available like Architecture/Metrics — no capability gate, noBootUiQuarkusProcessorchange. The provider filters out BootUI's own beans by FQN prefix and classifies with Quarkus-aware framework prefixes; a few fields are reduced-fidelity because Arc does not expose them at runtime (resource/dependenciesempty, CDIscopevocabulary, synthetic name for unnamed beans, and only Arc-retained beans appear). Scheduled Tasks is the first build-time-capture optional-dependency panel that needs noExcludedTypeBuildItem: the shared engineScheduledTasksServiceowns only the sort +schedulingPresent/totalwrapping, and because the sharedScheduledTaskDtocarries only static@Scheduledconfig (no runtime next-fire/id, which is all the runtimeio.quarkus.scheduler.Schedulerexposes), no runtime class importsio.quarkus.scheduler.*— so there is no R2 classloading trap, no provided-scope dep, andCapability.SCHEDULER+ non-prod is the entire gate. The deploymentregisterScheduledTasks@Record(STATIC_INIT)build step scans theBeanArchiveIndexBuildItemJandex index for@Scheduled(and the@Scheduled$Schedulesrepeatable container, unwrapped manually), records the verbatim member strings into aRawScheduledTasklist (the codebase's first@Recorder+SyntheticBeanBuildItem— records are recordable via a@RecordableConstructorcanonical constructor, the module compiling with-parameters), and emitsbootui.internal.scheduled-present=truesoQuarkusPanelAvailabilitylights up the panel dynamically (Hibernate-style, not the static set). The runtimeQuarkusScheduledTaskProviderinjectsInstance<QuarkusScheduledTasks>(unsatisfied → unavailable), mapscron→CRON/every→FIXED_RATEwith the QuarkusSimpleSchedulerinitial-delay semantics (delay>0 indelayUnitdefaultMINUTESwins overdelayed), parses durations via the always-present quarkus-coreDurationConverter(best-effort MicroProfileConfigresolution of{prop}/${prop}references, else rendered raw), and self-filters via the shared engineInternalPackageMatcher; the read-onlyScheduledResourcemirrors the SpringScheduledController. Annotation-discovered tasks only (programmaticScheduler.newJob()jobs are not captured). Cache (panel idcache, kept identical to Spring) is the firstServicespanel and the first action-capable optional-dependency data panel on Quarkus: the shared engineCacheServiceowns the neutral half — cache topology → Micrometer metric overlay (hit/miss/size/eviction, same meter conventions both frameworks via the sharedMeterSelfFilter), ordering, and the state-changing clear orchestration — while theCacheProviderSPI is the seam each adapter implements (SpringCacheProviderover SpringCacheManager+CacheOperationSource;QuarkusCacheProvideroverio.quarkus.cache.CacheManager, sizes viaCaffeineCache.keySet(), clear viacache.invalidateAll()). It is an optional-dependency port like Hibernate: the soleio.quarkus.cache.*importer (QuarkusCacheProvider+BootUiCacheProducer) is compiled<scope>provided</scope>and excluded by the deploymentregisterCacheAdvisorbuild step (ExcludedTypeBuildItemby string name) unlessCapability.CACHEis present and the launch mode is non-prod — so Arc never links the absent cache API; only the panel's availability tracks the build-timebootui.internal.cache-presentflag, while the cache-API-free engineCacheServiceis@Produces'd unconditionally (itsCacheProviderInstanceresolves empty → renderscacheAvailable:false). The one reduced-fidelity gap is honest to the "replacement" framing: Quarkus binds caching with build-time annotations (@CacheResult,@CacheInvalidate) woven into methods, so there is no runtime registry of cached operations —operations()is empty andSpringCache.vuerenders a Quarkus-specific "Cached operations" note (via theplatformdiscriminator) instead of the Spring@Cacheabletable.GET /bootui/api/cachelists caches + metrics network-free; only the explicitPOST /bootui/api/cache/clearmutates, behind the sharedLocalhostGuardwrite floor. Flyway is the first Database panel and the second action-capable optional-dependency port (after Cache), and the simplest of the optional-dependency ports because both frameworks use the same library —org.flywaydb.core.Flyway: the shared engineFlywayServiceowns the neutral half (per-database migration-history assembly, sort/totals/current/pending, and the state-changing migrate/clean orchestration — target resolution, confirmation gating, clean-disabled gating), while theFlywayProviderSPI is the seam each adapter implements (SpringFlywayProviderover theFlywaybeans in the bean factory, incl. the Spring-Modulith module-aware history block;QuarkusFlywayProviderover the activeio.quarkus.flyway.runtime.FlywayContainerbeans, one per datasource). It is an optional-dependency port like Hibernate and Cache: the soleorg.flywaydb.*/io.quarkus.flyway.*importer (QuarkusFlywayProvider+BootUiFlywayProducer) is compiled<scope>provided</scope>and excluded by the deploymentregisterFlywaybuild step (ExcludedTypeBuildItemby string name) unlessCapability.FLYWAYis present and the launch mode is non-prod — so Arc never links the absent Flyway API; only the panel's availability tracks the build-timebootui.internal.flyway-presentflag, while the Flyway-API-free engineFlywayServiceis@Produces'd unconditionally (itsFlywayProviderInstanceresolves empty → rendersflywayPresent:false). The Spring-Modulith module-aware block is Spring-only (Quarkus has no Modulith), and the clean-disabled message names the framework-correct property (spring.flyway.clean-disabledvsquarkus.flyway.clean-disabled) from the provider.GET /bootui/api/flyway/migrationslists history network-free; only the explicitPOST /bootui/api/flyway/migrateandPOST /bootui/api/flyway/cleanmutate, behind the sharedLocalhostGuardwrite floor, withcleanpreserving Flyway's disabled-by-default, confirmation-gated semantics. Liquibase is the firstDatabasepanel on Quarkus and a second optional-dependency port that reuses the same underlying library on both adapters (liquibase.Liquibase+RanChangeSet— only discovery differs): the shared engineLiquibaseServiceowns the neutral half — applied+pending change-set assembly/ordering/total and the confirmation-gated update orchestration (404 unavailable / 403 read-only / 400 confirmation-required / 200 / 500, byte-identical to the old controller) — while theLiquibaseProviderSPI is the seam each adapter implements (SpringLiquibaseProvideroverSpringLiquibasebeans + aStandardChangeLogHistoryServiceJDBC read;QuarkusLiquibaseProvideroverLiquibaseFactoryUtil.getActiveLiquibaseFactories()— which resolves@LiquibaseDataSource-named datasource qualifiers a plainInstance<LiquibaseFactory>would miss). It is an optional-dependency port like Hibernate/Cache: the soleliquibase.*importer on Quarkus (QuarkusLiquibaseProvider+BootUiLiquibaseProducer) is compiled<scope>provided</scope>and excluded by the deploymentregisterLiquibasebuild step (ExcludedTypeBuildItemby string name) unlessCapability.LIQUIBASEis present and the launch mode is non-prod — so Arc never links the absent Liquibase API; only the panel's availability tracks the build-timebootui.internal.liquibase-presentflag, while the liquibase-API-free engineLiquibaseServiceis@Produces'd unconditionally (itsLiquibaseProviderInstanceresolves empty → renders unavailable). The provider opens/closes aLiquibaseplus its JDBC connection per request in try-with-resources, with aResettableSystemProperties(io.quarkus.runtime.*, always present) declared after theLiquibaseso it closes first — matching the QuarkusLiquibaseRecorderorder.GET /bootui/api/liquibaselists databases + change sets network-free; only the explicitPOST /bootui/api/liquibase/updatemutates, behind the sharedLocalhostGuardwrite floor. Database Connection Pools (panel iddatabase-connection-pools, kept identical to Spring) is the firstDatabasepanel on Quarkus and the first different-pool-library port: Spring reads HikariCP, Quarkus reads Agroal, but theHikariPool*wire contract is deliberately kept unchanged (its JSON field names are already pool-neutral and the Vue UI carries no "Hikari" labels, so Spring stays byte-identical and the SPA/conformance are untouched — same precedent as Cache keeping thecacheid and Beans keeping reduced fidelity). The shared engineConnectionPoolServiceowns the neutral half — assembly, sort, and theSecretMaskerJDBC-URL/username masking orchestration (honoring bothvalueExposure()andmaskSecrets()) — while theConnectionPoolProviderSPI (returning neutralConnectionPoolInfo/…Snapshotcarriers) is the seam each adapter implements (SpringConnectionPoolProvideroverHikariDataSourcebeans +HikariPoolMXBean;QuarkusAgroalConnectionPoolProvideroverAgroalDataSourceenumerated viaAgroalDataSourceUtil.activeDataSourceNames(), mappingAgroalDataSourceMetrics/config into the kept DTO). It is the read-only twin of the Cache optional-dependency port (so no R7 write gate): the soleio.agroal.*importer (QuarkusAgroalConnectionPoolProvider+BootUiAgroalProducer) is compiled<scope>provided</scope>and excluded by the deploymentregisterConnectionPoolsbuild step (ExcludedTypeBuildItemby string name) unlessCapability.AGROALis present and the launch mode is non-prod — so Arc never links the absent Agroal API; only the panel's availability tracks the build-timebootui.internal.connection-pools-presentflag, while the Agroal-API-free engineConnectionPoolServiceis@Produces'd unconditionally (itsConnectionPoolProviderInstanceresolves empty → rendershikariPresent:false, empty state).Capability.AGROALis present whenever any JDBC datasource extension is on the classpath, so the present-key tracks real datasource availability and a zero-datasource app renders the empty state exactly like Spring with HikariCP-on-classpath-but-no-pools. The Agroal→Hikari mapping is documented honestly: active←activeCount, idle←availableCount, total←active+idle, pending←awaitingCount; min/max-size and the acquisition/reap/max-lifetime timeouts map across, but a few Hikari-specific fields have no faithful Agroal analogue and are neutral defaults (validationTimeoutMs/keepaliveTimeMs←-1→ render "—",readOnly←falsebecause agroal-api 2.5 has noreadOnly()accessor,driverClassNameoften null). Pool metrics requirequarkus.datasource.jdbc.metrics.enabled=true; with metrics disabled the configuration still renders but the live snapshot isnulland the pool is marked unavailable with a specific reason (no throw).GET /bootui/api/database-connection-pools/poolslists pools network-free and…/pools/{name}/snapshotreturns a bounded live snapshot; the panel is strictly read-only (no mutating route). Mappings is served by scanning the application's JAX-RS resources from the build-time Jandex index (the Spring adapter reads Actuator'sMappingsEndpoint): the engineMappingsServiceowns the neutral sort/query/page concerns and the framework-neutralMappingProviderSPI is implemented byQuarkusMappingProvideron Quarkus andSpringMappingProvideron Spring. Because Quarkus exposes no clean runtime route-enumeration API (Vert.x reports paths but not the per-route method/produces/consumes theMappingDtocontract needs), aregisterMappings@Record(STATIC_INIT)build step scans theBeanArchiveIndexBuildItemJandex index for@Pathresource classes — not the richerResteasyReactiveResourceMethodEntriesBuildItem, which is produced after the Arc container is built and so would form a build-step cycle when producing aSyntheticBeanBuildItem(the same early-vs-late constraint that drives Scheduled Tasks/Architecture to the Jandex index) — then for each resource method combines the class+method@Path(slash-normalized), renders the handler asdeclaringClass#methodName, joins the@Produces/@Consumesmedia types (method-level wins over class-level), and filters out BootUI's own routes at build time (the single filter site, because it has both the request path and the resource class FQN — the two things Spring'sBootUiSelfDataFilterinspects), recording the rows into a syntheticQuarkusMappingsbean via a@Recorder(the Scheduled-tasks build-time-capture template; the runtimeQuarkusMappingProviderdoes a pure 1:1RawMapping→MappingDtomap).quarkus-restis a hard dependency of the BootUI extension, so the index always carries at least BootUI's own resources and the panel is statically available (no capability gate, noExcludedTypeBuildItem, no R2 classloading trap since the provider imports no RESTEasy types at runtime); the read-onlyMappingsResourcemirrors the SpringMappingsController(GETroot +GET /flat, no write path), and the Quarkus handler format (classFQN#method) is an accepted display-string divergence from Spring's Actuator handler text. Only annotation-discovered JAX-RS resources are captured (no programmatic Vert.x routes). The MCP Server panel is fully live on Quarkus: the JSON-RPC bridge was ported by extracting a framework- and JSON-free dispatch core (bootui-engineMcpDispatcher→ a sealedMcpDispatchOutcome) that owns method routing, per-panel gating, tool lookup andmax-resultscapping, while each adapter keeps a thin per-Jackson envelope codec (QuarkusMcpEnvelope, Jackson 2;BootUiMcpService, Jackson 3), its own tool catalog (QuarkusMcpTools— 20 Quarkus-available tools (incl.get_live_activity/get_exception_detail), gated byQuarkusPanelAvailability.isPanelAvailableso a tool is advertised iff its backing panel is live;graalvm_scan/crac_scanare absent, whileget_overviewis advertised now that the Overview panel is available), live state (McpServerState), and panel policy (QuarkusMcpPanelPolicy— delegates toQuarkusPanelAccessConfig, gating atools/callon the samebootui.panels.*enable/read-only toggles the browser UI andQuarkusPanelAccessFilterobey, mirroring the Spring adapter'sSpringMcpPanelPolicy).POST /bootui/api/mcp(the@BlockingJSON-RPC transport,McpBridgeResource) and thePOST /bootui/api/mcp-server/toggleenable switch (McpServerResource) both sit behind the sharedLocalhostGuardwrite floor; the bridge short-circuits to a-32000error while disabled and answers byte-identically to Spring. Everything else is reported unavailable with a clear reason until its Quarkus backing lands. -
Advisors read their backing analysis rules from
docs/*-CHECKS.md(ARCHITECTURE-CHECKS.md,SPRING-CHECKS.md,HIBERNATE-CHECKS.md,MEMORY-CHECKS.md,SECURITY-CHECKS.md,PENTEST-CHECKS.md,REST-API-CHECKS.md,GRAALVM-READINESS-CHECKS.md; on Quarkus,QUARKUS-ADVISOR-CHECKS.mdbacks the Quarkus application advisor andQUARKUS-CHECKS.mdbacks the Quarkus Security advisor). Update the matching doc when changing advisor logic. -
The Claude Code panel reuses
views/Copilot.vue(component: Copilot); there is no separateClaudeCode.vue. -
The route order and
meta.groupkeys inroutes.js(eachgroupmust exist in itsgroupsmap),docs/FEATURES.md, and the sample-app navigation test (bootui-spring-sample-app/e2e/tests/app-shell.spec.js) must stay consistent when panels are added, renamed, hidden, or reordered. When renaming a route path, add a redirect from the old path (see the redirect block at the end ofroutes.js). -
New browser-facing behavior usually needs a stable DTO, controller tests where practical, Vue route/view updates,
docs/FEATURES.mdupdates, a conformance check, and an e2e spec when the UI or sample app behavior changes. -
Feature screenshots in
docs/images/bootui-*.webpstay at 1600x900 px; they are captured as PNG and re-encoded to WebP (quality 80) by the screenshot script. Seed realistic non-sensitive sample data instead of capturing empty states. Always scroll to the top before capturing — reset both the window and the.bootui-workspacescroll container (page.evaluate(() => { window.scrollTo(0, 0); document.querySelector('.bootui-workspace')?.scrollTo(0, 0) })) after the prepare step and beforepage.screenshot(). The main content scrolls inside.bootui-workspace(not the document), andwaitFor/waitForTexthelpers scroll matching elements into view, otherwise leaving the viewport at the bottom of the panel.
Java conventions
- Package root
io.github.jdubois.bootui.<module>. Inbootui-engine, services + advisor engines live in feature sub-packages (engine.memory,engine.architecture,engine.pentesting, …); the neutral SPI interfaces live alongside them in theio.github.jdubois.bootui.spipackage (samebootui-enginemodule). In the Spring adapter, simple controllers live in...autoconfigure.web, complex features in dedicated sub-packages (...autoconfigure.architecture,...autoconfigure.config), safety code in...autoconfigure.safety. In the Quarkus adapter, JAX-RS resources live in...quarkus.web, SPI impls + producers + the safety filter in...quarkus. - DTOs are immutable Java
records (annotation-free; Jackson-friendly under both adapters). - Compiler is configured with
-parameters(<parameters>true</parameters>); rely on it for@RequestParam/@PathVariable/@QueryParambinding without explicitvalue=attributes. - 4-space indent for Java/XML, 2-space for JS/Vue/JSON/YAML/MD (
.editorconfig). UTF-8, LF, trailing newline. Formatting is enforced by Spotless (palantir-java-format) across the whole reactor. - Tests move with the class. Pure unit tests follow a service into
bootui-engine(with package-private fakes in the same package); the adapter's controller/resource test mocks the engine service and asserts only wiring; a factory test pins each adapter factory's property→record mapping. Keep the conformance suite green on both adapters at every step.
Frontend conventions
- Vue 3 Composition API with
<script setup>, Vue Router 5 withcreateWebHashHistory(), Bootstrap 5.3 + bootstrap-icons. Plain JavaScript (.js/.vue); no TypeScript. - API calls use relative paths (
fetch('api/overview')) so they resolve against the/bootui/SPA base — do not hardcode/bootui/api/.... The UI is framework-agnostic: it talks only to the shared/bootui/api/**contract, so a view must never assume Spring- or Quarkus-specific response shapes. If a panel needs framework-specific copy (e.g./actuator/healthvs/q/health), drive it from a value in the DTO, not hardcoded text. - New panel = add a
views/Xxx.vue, register the route insrc/main/frontend/src/routes.jswith anicon,title, andgroupinmeta; the sidebar inApp.vuerenders fromrouter.options.routes. - Frontend unit tests use Vitest with Vue Test Utils and jsdom. Add focused
*.test.jscoverage for reusable composables/ components and UI logic where Playwright would be too broad or slow. - The Vite dev server proxies
/bootui/api/*to a running sample app (Spring or Quarkus); packaged assets must work from/bootui/without requiring consumers to install Node.
Contribution conventions (from CONTRIBUTING.md)
- Branch names start with the GitHub username, e.g.
jdubois/improve-config-ui. - Keep PRs small and update
docs/whenever public behavior changes. The PR template's checklist (./mvnw clean install, sample-app smoke test, no committed secrets) is enforced in review. - Run focused Vitest tests for changed Vue composables/components, and run the Playwright suite when changing browser flows, browser-facing API response shapes, visible routes, or sample-app behavior.
- Both sample apps are for demos/integration tests and set
<maven.deploy.skip>true</maven.deploy.skip>; do not publish them in Maven Central releases. - Spring Boot 3.x compatibility is out of scope — don't add compatibility shims.
Release plumbing (Maven Central)
Subtle constraints that have burned past releases — preserve them when touching pom.xml files or the release profile.
The published artifacts are the shared modules and both adapters (bootui-core, -engine, -ui,
-spring-autoconfigure, -spring-boot-starter, -quarkus, -quarkus-deployment); the demo/test modules (bootui-spring-sample-app,
bootui-quarkus-sample-app, bootui-quarkus-integration-tests, bootui-conformance) set
<maven.deploy.skip>true</maven.deploy.skip> — they must still build so the publishing plugin sees the full reactor, but
must not be published.
- Source-less modules (
bootui-ui,bootui-spring-boot-starter) must attach their emptyjavadoc.jarat phasepackage, notverify. The parent'sreleaseprofile bindsmaven-source-plugin,maven-javadoc-plugin, andmaven-gpg-plugin:signall toverify, and gpg runs before any child-pom executions in the same phase. If the empty javadoc is attached atverify, gpg signs everything except the javadoc.jar, and Sonatype Central rejects the deployment withMissing signature for file: ...-javadoc.jar. If you add another source-less module, copy the existingattach-empty-javadocsexecution (phasepackage, classifierjavadoc,skipIfEmpty=false, fed fromtarget/empty-javadocs). - The signing GPG public key must be queryable by fingerprint on
keys.openpgp.organd/orkeyserver.ubuntu.com. Sonatype Central validates signatures against those keyservers; if the public key isn't there, every signature comes backInvalid signature ... Could not find a public key by the key fingerprint. After rotating theGPG_PRIVATE_KEYsecret, re-publish the matching public key. On macOS,gpg --send-keysoften fails withInvalid argumentfrom dirmngr; the reliable fallback is the HTTPS upload APIs (POST https://keys.openpgp.org/vks/v1/uploadwith a JSON{"keytext": ...}body, andPOST https://keyserver.ubuntu.com/pks/addwith form fieldkeytext). - A failed deployment still consumes the version coordinate in Central. Once the publishing plugin uploads
com.julien-dubois.bootui:<artifact>:<version>— even if validation rejects it — you cannot re-upload that exact GAV without dropping the failed deployment from the Sonatype Central Portal first. The default path is to run theReleaseworkflow with a new version so it bumps the codebase, commits, tags, and publishes in one run. - Use the
Releaseworkflow (.github/workflows/release.yml) to bump versions rather than runningversions:setby hand. It runsversions:set, updates thedocs/SETUP.mdinstall snippet (the publicREADME.mdonly links to the docs site), and bumps every npmpackage.json/package-lock.json(root docs site,bootui-uifrontend, thebootui-spring-sample-app/e2esuite) vianpm version --no-git-tag-version, then verify-builds, commits, tags, and publishes. Manualversions:setskips the install-snippet rewrite and the npm bumps, leaving them pointing at a stale version. When touching version strings, keep the Quarkus modules'quarkus.platform.versionindependent of the project version.
Design context
BootUI's design system is documented at the repo root (kept out of docs/ so it isn't published to the docs site).
Read and honor both before changing anything the user sees in bootui-ui:
PRODUCT.md— strategic context: register (product), users/personas, purpose, brand personality, anti-references, the five design principles, and the accessibility bar.DESIGN.md— the visual system in Stitch DESIGN.md format, with.impeccable/design.jsonas its machine sidecar: design tokens, typography, elevation, and component specs. North star: "The Calm Control Room."
Load-bearing rules when touching the UI:
- Accessibility is WCAG 2.1 AA in both light and dark themes. Verify contrast for semantic status colors (log levels, severities) and code/identifier text on tinted or selected backgrounds — Bootstrap's contextual colors are tuned for light backgrounds only. Every interactive control needs a visible, branded focus ring.
- Use Bootstrap, never look like default Bootstrap — no AdminLTE / SB-Admin admin-template look, default blue, or untouched utility-class styling.
- The green→blue gradient means "active / selected" only — never a hero backdrop or heading fill. Backgrounds stay cool (no cream/sand). Machine output is monospace; BootUI's own explanation is sans.
- Never surprise the user — no network calls, scans, or mutations on render; honor
prefers-reduced-motion.
These files are also read by the impeccable design skill; re-run /impeccable document if the visual system drifts from
the code.
Trust
Not scanned yet. Artifacts are graded after they are crawled, so a recently discovered one may have no result for a while.
Versions
git-24049a6773a72026-08-04