This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

solverforge-ui

Embedded frontend components, scheduling views, backend adapters, and asset serving for SolverForge web applications.

solverforge-ui is SolverForge’s frontend component library for constraint-optimization applications. It ships embedded assets, UI primitives, solver lifecycle helpers, and scheduling views without requiring npm in the runtime integration path.

What It Provides

  • Drop-in components for headers, status bars, buttons, modals, tabs, tables, footers, API guides, and toasts
  • Scheduling views with both timeline rail and split-pane Gantt primitives
  • Solver lifecycle helpers via SF.createBackend(...) and SF.createSolver(...)
  • Embedded asset serving under /sf/* via .merge(solverforge_ui::routes())
  • Stable and versioned bundles for compatibility and cache-friendly production deployments

Installation

[dependencies]
solverforge-ui = "0.3.1"

Minimal Workflow

let app = api::router(state).merge(solverforge_ui::routes()); // serves /sf/*
<link rel="stylesheet" href="/sf/sf.css" />
<script src="/sf/sf.js"></script>
<script>
  var tabs = SF.createTabs({
    tabs: [{ id: 'plan', content: '<div>Plan view</div>', active: true }],
  });
  document.body.appendChild(tabs.el);

  var header = SF.createHeader({
    title: 'SolverForge UI',
    tabs: [{ id: 'plan', label: 'Plan', active: true }],
    onTabChange: function (id) {
      tabs.show(id);
    },
  });
  document.body.prepend(header);
</script>

When To Use It

Use solverforge-ui when you want to ship SolverForge-backed web interfaces quickly without rebuilding common UI primitives or bundling your own asset pipeline.

It is a strong fit for:

  • operations dashboards and schedule review screens
  • solver result exploration and troubleshooting tools
  • embedded admin UIs in Axum-, Tauri-, or static-asset-served applications

Sections

  • Getting Started — Mount /sf/*, include the bundled assets, and wire the verified primitives into an app
  • Components — Core factories, return values, and unsafe HTML opt-ins
  • Scheduling Views — Timeline rail and Gantt APIs with shipped examples
  • Integration & Assets — Backend adapters, asset serving, cache behavior, and example route contracts

External References

1 - Getting Started

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:

  1. add the crate
  2. mount /sf/* assets with .merge(solverforge_ui::routes())
  3. include the bundled CSS and JS
  4. instantiate components plus the backend and solver helpers

Add the Dependency

[dependencies]
axum = "0.8"
solverforge-ui = "0.3.1"

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 schedule/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: '<div>Plan view</div>', active: true },
          { id: 'gantt', content: '<div>Gantt view</div>' },
        ],
      });
      document.body.appendChild(tabs.el);

      var backend = SF.createBackend({ type: 'axum' });

      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);
        },
      });
      document.body.prepend(header);

      var statusBar = SF.createStatusBar({ header: header, constraints: [] });
      header.after(statusBar.el);

      var solver = SF.createSolver({
        backend: backend,
        statusBar: statusBar,
        onUpdate: function (schedule) {
          console.log('updated schedule', schedule);
        },
      });
    </script>
  </body>
</html>

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 default adapter contract documented in Integration & Assets, or add an application-side compatibility layer while your routes are still in transition.

Next Steps

2 - Components

Core factories, return values, and helpers in the shipped solverforge-ui surface.

Components

solverforge-ui ships a verified component surface for common scheduling and operations UIs.

Core Factories

The current public API documents these factories:

FactoryReturnsDescription
SF.createHeader(config)HTMLElementSticky header with logo, title, nav tabs, and solve/stop/analyze actions
SF.createStatusBar(config){el, bindHeader, updateScore, setSolving, updateMoves, colorDotsFromAnalysis}Score display with constraint indicators
SF.createButton(config)HTMLButtonElementButton with variant, size, icon, and shape modifiers
SF.createModal(config){el, body, open, close, setBody}Dialog with backdrop and header
SF.createTable(config)HTMLElementData table with headers and row click support
SF.createTabs(config){el, show}Tab panel container with instance-scoped tab switching
SF.createFooter(config)HTMLElementFooter with links and version
SF.createApiGuide(config)HTMLElementREST API documentation panel
SF.showToast(config)voidAuto-dismissing toast notification
SF.showError(title, detail)voidError toast shorthand
SF.showTab(tabId, root?)voidActivate tab panels globally or within one root

Composition Example

var tabs = SF.createTabs({
  tabs: [
    { id: 'plan', content: '<div>Plan view</div>', active: true },
    { id: 'gantt', content: '<div>Gantt view</div>' },
  ],
});
document.body.appendChild(tabs.el);

var header = SF.createHeader({
  title: 'My Scheduler',
  subtitle: 'by SolverForge',
  tabs: [
    { id: 'plan', label: 'Plan', active: true },
    { id: 'gantt', label: 'Gantt' },
  ],
  onTabChange: function (id) {
    tabs.show(id);
  },
});
document.body.prepend(header);

var statusBar = SF.createStatusBar({ header: header, constraints: [] });
header.after(statusBar.el);

Unsafe HTML APIs

Default content is text-rendered. These opt-ins accept trusted HTML:

FactoryUnsafe HTML field
SF.el(tag, attrs, ...)unsafeHtml
SF.createModal(config)unsafeBody
SF.createTabs(config)tabs[].content.unsafeHtml
SF.createTable(config)cells[].unsafeHtml
SF.gantt.create(config)unsafePopupHtml, columns[].render(task).unsafeHtml

Escape user-provided content before interpolation. SF.escHtml(...) is the shipped helper for that.

Button Variants

SF.createButton({ text: 'Solve', variant: 'success' });
SF.createButton({ text: 'Stop', variant: 'danger' });
SF.createButton({ text: 'Save', variant: 'primary' });
SF.createButton({ text: 'Cancel', variant: 'default' });
SF.createButton({ icon: 'fa-gear', variant: 'ghost', circle: true });

Useful Helpers

These helpers are documented in the current public API:

  • SF.score.parseHard, parseSoft, parseMedium, getComponents, colorClass
  • SF.colors.pick, project, reset
  • SF.escHtml(...) for safe HTML escaping
  • SF.el(tag, attrs, ...children) for lightweight DOM element creation

3 - Scheduling Views

Build timeline-rail and split-pane Gantt views with the shipped solverforge-ui APIs.

Scheduling Views

solverforge-ui ships two complementary scheduling view styles:

  • Timeline rail for compact, lane-by-lane operator workflows
  • Gantt for dense, timeline-first planning and diagnostics

Timeline Rail API

The shipped rail surface is built around:

  • SF.rail.createHeader(config)HTMLElement
  • SF.rail.createCard(config){el, rail, addBlock, clearBlocks, setSolving}
  • SF.rail.addBlock(rail, config)HTMLElement
  • SF.rail.addChangeover(rail, config)HTMLElement

Rail Example

var header = SF.rail.createHeader({
  label: 'Resource',
  labelWidth: 200,
  columns: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri'],
});
container.appendChild(header);

var card = SF.rail.createCard({
  id: 'furnace-1',
  name: 'FORNO 1',
  labelWidth: 200,
  columns: 5,
  type: 'CAMERA',
  typeStyle: {
    bg: 'rgba(59,130,246,0.15)',
    color: '#1d4ed8',
    border: '1px solid rgba(59,130,246,0.3)',
  },
  badges: ['TEMPRA'],
  gauges: [{ label: 'Temp', pct: 85, style: 'heat', text: '850/1000°C' }],
  stats: [{ label: 'Jobs', value: 12 }],
});
container.appendChild(card.el);

card.addBlock({
  start: 120,
  end: 360,
  horizon: 4800,
  label: 'ODL-2847',
  meta: 'Bianchi',
  color: 'rgba(59,130,246,0.6)',
  borderColor: '#3b82f6',
  late: false,
});

SF.rail.addChangeover(card.rail, { start: 360, end: 400, horizon: 4800 });

card.setSolving(true);

Gauge styles include heat, load, and emerald. badges accepts either plain strings or { label, style } objects.

Gantt API

SF.gantt.create(config) returns:

  • el, mount, setTasks, refresh, changeViewMode, highlightTask, destroy

Gantt Example

var gantt = SF.gantt.create({
  gridTitle: 'Tasks',
  chartTitle: 'Schedule',
  viewMode: 'Quarter Day',
  splitSizes: [40, 60],
  columns: [
    { key: 'name', label: 'Task', sortable: true },
    { key: 'start', label: 'Start', sortable: true },
    { key: 'end', label: 'End', sortable: true },
  ],
  onTaskClick: function (task) {
    console.log('clicked', task.id);
  },
  onDateChange: function (task, start, end) {
    console.log('moved', task.id, start, end);
  },
});

gantt.mount('my-container');

gantt.setTasks([
  {
    id: 'task-1',
    name: 'Design review',
    start: '2026-03-15 09:00',
    end: '2026-03-15 10:30',
    priority: 1,
    projectIndex: 0,
    pinned: true,
    custom_class: 'project-color-0 priority-1',
    dependencies: '',
  },
]);

gantt.changeViewMode('Day');
gantt.highlightTask('task-1');

View modes include Quarter Day, Half Day, Day, Week, and Month. Sortable headers are opt-in per column.

Choosing Rail vs Gantt

Use rail when operators need readable resource cards, gauges, and changeover-aware lanes.

Use Gantt when analysts need sortable task grids, dependency arrows, and high-density timeline review.

4 - Integration & Assets

Backend adapters, asset serving, cache behavior, and example route contracts for solverforge-ui.

Integration & Assets

This page summarizes how solverforge-ui connects frontend code to backend APIs and how static assets are delivered.

Backend Adapters

Create adapters with SF.createBackend(...) and pass the result into SF.createSolver(...).

Axum (default)

var backend = SF.createBackend({ type: 'axum', baseUrl: '' });

Use this when your backend exposes the default solverforge-ui scheduling contract.

Tauri Adapter

var backend = SF.createBackend({
  type: 'tauri',
  invoke: window.__TAURI__.core.invoke,
  listen: window.__TAURI__.event.listen,
  eventName: 'solver-update',
});

Use this when solver traffic is bridged through Tauri IPC.

Generic Fetch Adapter

var backend = SF.createBackend({
  type: 'fetch',
  baseUrl: '/api/v1',
  headers: { 'X-CSRF-Token': csrfToken },
});

Use this when your app needs a custom HTTP shape, extra headers, or a compatibility layer during a route transition.

Default Axum Adapter Contract

The default Axum adapter expects these routes:

  • POST /schedules
  • GET /schedules/{id}
  • GET /schedules/{id}/events
  • GET /schedules/{id}/analyze
  • DELETE /schedules/{id}
  • GET /demo-data/{name}

The adapter also expects createSchedule() to resolve to either:

  • a plain schedule/job id string, or
  • an object containing one of id, jobId, job_id, scheduleId, or schedule_id

If your current app still uses a legacy quickstart route shape, add an application-side compatibility layer or use the generic fetch adapter until the routes converge.

Solver Lifecycle

SF.createSolver(...) builds the client-side solver state machine on top of the backend adapter.

var solver = SF.createSolver({
  backend: backend,
  statusBar: statusBar,
  onUpdate: function (schedule) {
    render(schedule);
  },
});

The shipped solver helper exposes start, stop, isRunning, and getJobId.

Asset Serving Under /sf/*

solverforge_ui::routes() serves GET /sf/{*path} from the crate’s embedded asset directory.

Common assets include:

  • /sf/sf.css
  • /sf/sf.js
  • /sf/vendor/fontawesome/css/fontawesome.min.css
  • /sf/vendor/fontawesome/css/solid.min.css

Cache Behavior and Versioned Bundles

The crate emits both stable and versioned bundle filenames:

  • stable: /sf/sf.css, /sf/sf.js
  • versioned: /sf/sf.<crate-version>.css, /sf/sf.<crate-version>.js

src/lib.rs serves them with different cache policies:

  • stable bundles use Cache-Control: public, max-age=3600
  • versioned bundles use Cache-Control: public, max-age=31536000, immutable
  • fonts/, vendor/, and img/ assets are also served as immutable

A practical strategy is to use stable URLs during development and versioned bundles in production or CDN environments.

Optional Modules

When the optional map module is shipped in static/sf/modules/, include it alongside Leaflet:

<link rel="stylesheet" href="/sf/vendor/leaflet/leaflet.css" />
<script src="/sf/vendor/leaflet/leaflet.js"></script>
<link rel="stylesheet" href="/sf/modules/sf-map.css" />
<script src="/sf/modules/sf-map.js"></script>

For route geometry, travel-time, and map-data pipeline details, see the existing solverforge-maps docs.

Non-Rust Integration Path

static/sf/ is self-contained. If you are not serving assets from Rust, copy, submodule, or symlink it into your static-files directory so /sf/* resolves the same way in production.