Documentation
Mount the bundled assets and wire verified solverforge-ui primitives into an Axum app.
Getting Started with solverforge-ui
This guide covers the verified integration path:
- add the crate
- mount
/sf/*assets with.merge(solverforge_ui::routes()) - include the bundled CSS and JS
- instantiate components plus the retained-job backend and solver helpers
Add the Dependency
[dependencies]
axum = "0.8.9"
solverforge-ui = { version = "0.9.0" }
# Pin a specific GitHub release tag when you need exact reproducibility.
# solverforge-ui = { git = "https://github.com/SolverForge/solverforge-ui", tag = "v0.9.0" }
Use the Git tag form when you need exact source-tag reproducibility instead of the crates.io package.
Use Rust 1.95+ for the current crate line.
Mount /sf/* Routes in Axum
use axum::{routing::get, Router};
async fn index() -> &'static str {
include_str!("../static/index.html")
}
fn app() -> Router {
Router::new()
.route("/", get(index))
.merge(solverforge_ui::routes())
}
solverforge_ui::routes() serves the embedded /sf/* assets. Your application
still owns its HTML pages and any job/solver API routes.
Include Required Assets in HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>solverforge-ui quickstart</title>
<link
rel="stylesheet"
href="/sf/vendor/fontawesome/css/fontawesome.min.css"
/>
<link rel="stylesheet" href="/sf/vendor/fontawesome/css/solid.min.css" />
<link rel="stylesheet" href="/sf/sf.css" />
</head>
<body class="sf-app">
<script src="/sf/sf.js"></script>
<script>
var tabs = SF.createTabs({
tabs: [
{ id: "plan", content: { unsafeHtml: "<div>Plan view</div>" }, active: true },
{ id: "gantt", content: { unsafeHtml: "<div>Gantt view</div>" } },
],
});
document.body.appendChild(tabs.el);
var backend = SF.createBackend({ type: "axum" });
var solver;
var header = SF.createHeader({
title: "SolverForge Scheduler",
subtitle: "by SolverForge",
tabs: [
{ id: "plan", label: "Plan", active: true },
{ id: "gantt", label: "Gantt" },
],
onTabChange: function (id) {
tabs.show(id);
},
actions: {
onSolve: function () {
solver.start();
},
onPause: function () {
solver.pause();
},
onResume: function () {
solver.resume();
},
onCancel: function () {
solver.cancel();
},
},
});
document.body.prepend(header);
var statusBar = SF.createStatusBar({ header: header, constraints: [] });
header.after(statusBar.el);
solver = SF.createSolver({
backend: backend,
statusBar: statusBar,
onProgress: function (meta) {
console.log("progress", meta.currentScore, meta.bestScore);
},
onSolution: function (snapshot, meta) {
renderAndSync(snapshot, meta);
},
onPaused: function (snapshot, meta) {
renderAndSync(snapshot, meta);
},
onComplete: function (snapshot, meta) {
renderAndSync(snapshot, meta);
},
onCancelled: function (snapshot, meta) {
renderAndSync(snapshot, meta);
},
onFailure: function (message, meta, snapshot, analysis) {
console.error(message, analysis);
renderAndSync(snapshot, meta);
},
});
function renderAndSync(snapshot, meta) {
try {
if (snapshot && snapshot.solution) {
render(snapshot.solution);
}
} finally {
syncMarkers(meta);
}
}
function render(solution) {
// Replace this with the app's domain-specific renderer.
console.log("solution", solution);
}
function syncMarkers(meta) {
document.body.dataset.jobId = (meta && meta.jobId) || "";
document.body.dataset.snapshotRevision =
meta && meta.snapshotRevision != null ? String(meta.snapshotRevision) : "";
document.body.dataset.lifecycleState = (meta && meta.lifecycleState) || "IDLE";
}
</script>
</body>
</html>
SF.createSolver(...) now follows the retained-job lifecycle. Use
onProgress(...) for score and telemetry updates, onSolution(...) for
snapshot-bearing best_solution events, and onPaused(...), onCancelled(...),
or onComplete(...) when you need the exact retained snapshot after the runtime
reaches an authoritative lifecycle state. Snapshot-bearing callbacks are
null-safe: cancellation, failure, or a failed snapshot synchronization can leave
no solution snapshot, so render only when snapshot && snapshot.solution exists
and synchronize lifecycle markers in a finally block.
The header renders the cancel action as Stop. Keep using the onCancel
configuration key and call solver.cancel(); delete() is reserved for
terminal retained-job cleanup.
Application Routes
solverforge-ui does not generate your scheduling API. The crate ships the UI
surface and a set of backend helpers. If you use
SF.createBackend({ type: 'axum' }), follow the canonical retained-job contract
documented in Integration & Assets. Older
Schedule-named route shapes are historical. Current generated apps expose the
retained /jobs/... contract.
Next Steps
- Read Components to build richer page layouts.
- Read Scheduling Views for timeline rail and Gantt examples.
- Read Integration & Assets for backend adapters, optional map modules, asset serving, and versioned bundle behavior.