solverforge-ui 0.1.0

Frontend component library for SolverForge constraint-optimization applications
Documentation

solverforge-ui

Frontend component library for SolverForge constraint-optimization applications. Emerald-themed, zero-framework, vendor-ready. One line to mount, zero npm, zero webpack.

// Cargo.toml
solverforge-ui = { path = "../solverforge-ui" }

// main.rs
let app = api::router(state)
    .merge(solverforge_ui::routes())      // serves /sf/*
    .fallback_service(ServeDir::new("static"));
<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">
<script src="/sf/sf.js"></script>

That's it. Every asset is compiled into the binary via include_dir!.

Screenshots

Planner123 — Gantt chart with dependency arrows, project-colored bars, and constraint scoring:

Planner123 Gantt

Furnace Scheduler — Timeline rail with resource cards, temperature/load gauges, and positioned job blocks:

Furnace Scheduler

Philosophy

Every backend element has a corresponding UI element. The library grows alongside the solver. When you scaffold a new SolverForge project with solverforge new, it's already wired in.

Quick Start

<body class="sf-app">
<script>
  var backend = SF.createBackend({ type: 'axum' });

  var header = SF.createHeader({
    logo: '/sf/img/ouroboros.svg',
    title: 'My Scheduler',
    subtitle: 'by SolverForge',
    tabs: [
      { id: 'plan', label: 'Plan', icon: 'fa-list-check', active: true },
      { id: 'gantt', label: 'Gantt', icon: 'fa-chart-gantt' },
    ],
    onTabChange: function (id) { SF.showTab(id); },
    actions: {
      onSolve: function () { solver.start(); },
      onStop:  function () { solver.stop(); },
    },
  });
  document.body.prepend(header);

  var bar = SF.createStatusBar({ constraints: myConstraints });
  header.after(bar.el);

  var solver = SF.createSolver({
    backend: backend,
    statusBar: bar,
    onUpdate: function (schedule) { render(schedule); },
  });
</script>
</body>

API Reference

Components

Factory Returns Description
SF.createHeader(config) HTMLElement Sticky header with logo, title, nav tabs, solve/stop/analyze buttons
SF.createStatusBar(config) {el, updateScore, setSolving, updateMoves, colorDotsFromAnalysis} Score display + constraint dot indicators
SF.createButton(config) HTMLButtonElement Button with variant/size/icon/shape modifiers
SF.createModal(config) {el, body, open, close, setBody} Dialog with emerald gradient header, backdrop, Escape key
SF.createTable(config) HTMLElement Data table with headers and row click
SF.createTabs(config) {el, show} Tab panel container
SF.createFooter(config) HTMLElement Footer with links and version
SF.createApiGuide(config) HTMLElement REST API documentation panel
SF.showToast(config) void Toast notification (auto-dismiss)
SF.showError(title, detail) void Danger toast shorthand
SF.showTab(tabId) void Activate a tab panel by ID

Timeline Rail

Factory Returns Description
SF.rail.createHeader(config) HTMLElement Day/period column header above resource cards
SF.rail.createCard(config) {el, rail, addBlock, clearBlocks, setSolving} Resource lane with identity, gauges, stats, and block rail
SF.rail.addBlock(rail, config) HTMLElement Positioned block (task/job) inside a rail
SF.rail.addChangeover(rail, config) HTMLElement Diagonal-striped gap between blocks

Gantt (Frappe Gantt)

Factory Returns Description
SF.gantt.create(config) {el, mount, setTasks, refresh, changeViewMode, highlightTask, destroy} Split-pane Gantt with grid table + Frappe Gantt chart

Solver Lifecycle

Factory Returns Description
SF.createBackend(config) Backend adapter HTTP or Tauri IPC transport
SF.createSolver(config) {start, stop, isRunning, getJobId} SSE state machine with auto status bar updates

Utilities

Function Description
SF.score.parseHard(str) Extract hard score from "0hard/-42soft"
SF.score.parseSoft(str) Extract soft score
SF.score.parseMedium(str) Extract medium score
SF.score.getComponents(str) {hard, medium, soft}
SF.score.colorClass(str) "score-green" / "score-yellow" / "score-red"
SF.colors.pick(key) Tango palette color for any key (cached)
SF.colors.project(index) {main, dark, light} from 8-color project palette
SF.colors.reset() Clear the color cache
SF.escHtml(str) HTML-escape a string
SF.el(tag, attrs, ...children) DOM element factory

Button Variants

SF.createButton({ text: 'Solve',    variant: 'success' })   // white bg, emerald text
SF.createButton({ text: 'Stop',     variant: 'danger' })    // red bg, white text
SF.createButton({ text: 'Save',     variant: 'primary' })   // emerald-700 bg
SF.createButton({ text: 'Cancel',   variant: 'default' })   // gray border
SF.createButton({ icon: 'fa-gear',  variant: 'ghost', circle: true })
SF.createButton({ text: 'Submit',   variant: 'primary', pill: true })
SF.createButton({ text: 'Delete',   variant: 'danger', outline: true })
SF.createButton({ text: 'Sm',       variant: 'primary', size: 'small' })

Timeline Rail

The scheduling hero view. Resource lanes with positioned task blocks, gauges, stats, heatmaps, and changeover indicators.

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

// One card per resource (furnace, vehicle, employee, machine...)
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)' },
  gauges: [
    { label: 'Temp', pct: 85, style: 'heat', text: '850/1000°C' },
    { label: 'Load', pct: 60, style: 'load', text: '120/200 kg' },
  ],
  stats: [
    { label: 'Jobs', value: 12 },
    { label: 'Production', value: '840 kg' },
  ],
});
container.appendChild(card.el);

// Add task blocks (positioned by start/end within horizon)
card.addBlock({
  start: 120,              // minutes from horizon start
  end: 360,
  horizon: 4800,           // total horizon in same units
  label: 'ODL-2847',
  meta: 'Bianchi',
  color: 'rgba(59,130,246,0.6)',
  borderColor: '#3b82f6',
  late: false,
  onHover: function (e, cfg) { /* show tooltip */ },
});

// Changeover gap between blocks
SF.rail.addChangeover(card.rail, { start: 360, end: 400, horizon: 4800 });

// Solving state (breathing emerald glow)
card.setSolving(true);

Gauge styles: heat (blue→amber→red), load (emerald→amber→red), emerald (solid green).

Gantt Chart

Interactive task scheduling with Frappe Gantt. Split-pane layout: task grid on top, SVG timeline chart on bottom. Drag to reschedule, resize to change duration, project-colored bars, dependency arrows.

<link rel="stylesheet" href="/sf/vendor/frappe-gantt/frappe-gantt.min.css">
<script src="/sf/vendor/frappe-gantt/frappe-gantt.min.js"></script>
<script src="/sf/vendor/split/split.min.js"></script>
var gantt = SF.gantt.create({
  gridTitle: 'Tasks',
  chartTitle: 'Schedule',
  viewMode: 'Quarter Day',
  splitSizes: [40, 60],
  columns: [
    { key: 'name', label: 'Task' },
    { key: 'start', label: 'Start' },
    { key: 'end', label: 'End' },
    { key: 'priority', label: 'P', render: function (t) {
      return '<span class="sf-priority-badge priority-' + t.priority + '">P' + t.priority + '</span>';
    }},
  ],
  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,
    custom_class: 'project-color-0 priority-1',
    dependencies: '',
  },
  {
    id: 'task-2',
    name: 'Implementation',
    start: '2026-03-15 10:30',
    end: '2026-03-15 14:00',
    priority: 2,
    custom_class: 'project-color-0 priority-2',
    dependencies: 'task-1',
  },
]);

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

View modes: Quarter Day, Half Day, Day, Week, Month.

Backend Adapters

Axum (default)

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

Expects standard SolverForge REST endpoints:

  • POST /schedules — start solving
  • GET /schedules/{id} — get solution
  • GET /schedules/{id}/events — SSE stream
  • GET /schedules/{id}/analyze — constraint analysis
  • DELETE /schedules/{id} — stop solving
  • GET /demo-data/{name} — load demo dataset

Tauri

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

Generic fetch (Rails, etc.)

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

Optional Modules

Map (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>
var map = SF.map.create({ container: 'map', center: [45.07, 7.69], zoom: 13 });

map.addVehicleMarker({ lat: 45.07, lng: 7.69, color: '#10b981' });
map.addVisitMarker({ lat: 45.08, lng: 7.70, color: '#3b82f6', icon: 'fa-utensils' });
map.drawRoute({ points: [[45.07, 7.69], [45.08, 7.70]], color: '#10b981' });
map.drawEncodedRoute({ encoded: 'encodedPolylineString', color: '#3b82f6' });
map.fitBounds();
map.highlight('#10b981');   // dim all routes except this color
map.clearHighlight();

SF.map.decodePolyline('_p~iF~ps|U...');  // Google polyline algorithm

Design System

Colors

Token Hex Usage
--sf-emerald-500 #10b981 Primary brand, success states
--sf-emerald-600 #059669 Primary dark
--sf-emerald-700 #047857 Primary buttons, links
--sf-red-600 #dc2626 Danger buttons, hard violations
--sf-amber-500 #f59e0b Warnings, soft violations
--sf-gray-50 #f9fafb Backgrounds
--sf-gray-900 #111827 Primary text

8 project colors for assignment: emerald, blue, purple, amber, pink, cyan, rose, lime.

Fonts

  • Space Grotesk (body, headings) — variable weight 300-700, self-hosted WOFF2
  • JetBrains Mono (code, scores, data) — variable weight 100-800, self-hosted WOFF2

Spacing

--sf-space-{0,1,2,3,4,5,6,8,10,12,16} — 0 to 4rem in quarter-rem increments.

Shadows

--sf-shadow-{sm,base,md,lg,xl,2xl} — elevation scale. --sf-shadow-emerald — colored shadow for branded elements.

Animations

sf-spin / sf-dot-pulse / sf-score-flash / sf-dialog-slide-in / sf-breathe / sf-slide-in / sf-fade-in / sf-late-glow

Project Structure

solverforge-ui/
├── Cargo.toml              # 2 deps: axum + include_dir
├── src/lib.rs              # routes() + asset serving
├── Makefile                # make → cats css-src/ + js-src/ into sf.css + sf.js
├── css-src/                # 16 CSS source files (numbered for concat order)
│   ├── 00-tokens.css       #   design system variables
│   ├── 01-reset.css        #   box-sizing reset
│   ├── 02-typography.css   #   @font-face declarations
│   ├── 03-layout.css       #   .sf-app, .sf-main, tab panels
│   ├── 04-header.css       #   .sf-header
│   ├── 05-statusbar.css    #   .sf-statusbar + constraint dots
│   ├── 06-buttons.css      #   .sf-btn variants
│   ├── 07-modal.css        #   .sf-modal
│   ├── 08-table.css        #   .sf-table + constraint analysis table
│   ├── 09-badges.css       #   .sf-badge variants
│   ├── 10-cards.css        #   .sf-card, .sf-kpi-card
│   ├── 11-tooltip.css      #   .sf-tooltip
│   ├── 12-footer.css       #   .sf-footer
│   ├── 13-scrollbars.css   #   custom webkit scrollbars
│   ├── 14-animations.css   #   @keyframes + toast + api guide
│   ├── 15-rail.css         #   timeline rail, resource cards, blocks
│   └── 16-gantt.css        #   Frappe Gantt + Split.js layout + bar styling
├── js-src/                 # 15 JS source files
│   ├── 00-core.js          #   SF namespace, escHtml, el()
│   ├── 01-score.js         #   score parsing
│   ├── 02-colors.js        #   Tango palette + project colors
│   ├── 03-buttons.js       #   createButton()
│   ├── 04-header.js        #   createHeader()
│   ├── 05-statusbar.js     #   createStatusBar()
│   ├── 06-modal.js         #   createModal()
│   ├── 07-tabs.js          #   createTabs(), showTab()
│   ├── 08-table.js         #   createTable()
│   ├── 09-toast.js         #   showToast(), showError()
│   ├── 10-backend.js       #   createBackend() — axum/tauri/fetch
│   ├── 11-solver.js        #   createSolver() — SSE state machine
│   ├── 12-api-guide.js     #   createApiGuide(), createFooter()
│   ├── 13-rail.js          #   timeline rail, resource cards, blocks
│   └── 14-gantt.js         #   Frappe Gantt wrapper (split pane, grid, chart)
└── static/sf/              # Embedded assets (include_dir!)
    ├── sf.css              # concatenated from css-src/
    ├── sf.js               # concatenated from js-src/
    ├── img/                # SVG logos (ouroboros, favicon, brand)
    ├── fonts/              # Space Grotesk + JetBrains Mono WOFF2
    ├── modules/            # optional: sf-map.js/css
    └── vendor/             # FontAwesome 6.5, Leaflet 1.9, Frappe Gantt, Split.js

Integration Paths

Project Type How It Works
Axum Add crate dep, call .merge(solverforge_ui::routes())
Tauri Add crate dep, serve via Tauri's asset protocol or custom command
Rails Copy static/sf/ into public/sf/, reference in layouts
Any HTTP server Copy static/sf/, serve as static files
solverforge new Automatic — wired into generated project

Non-Rust Projects

The static/sf/ directory is self-contained. Copy it, git-submodule it, or symlink it into any project that serves static files:

# git submodule
git submodule add https://github.com/solverforge/solverforge-ui vendor/solverforge-ui
ln -s vendor/solverforge-ui/static/sf public/sf

Development

# Edit source files
vim css-src/06-buttons.css
vim js-src/03-buttons.js

# Rebuild concatenated files
make

# Compile the crate (embeds updated assets)
cargo build

Acknowledgments

solverforge-ui builds on these excellent open-source projects:

Project Use License Link
Font Awesome Free Icons (Solid subset) CC BY 4.0 (icons), SIL OFL (fonts), MIT (code) github
Frappe Gantt Interactive Gantt chart MIT github
Split.js Resizable split panes MIT github
Leaflet Interactive maps (optional module) BSD-2-Clause github
Space Grotesk Body typeface SIL Open Font License 1.1 github
JetBrains Mono Monospace typeface SIL Open Font License 1.1 github
Axum Rust web framework MIT github
include_dir Compile-time file embedding MIT github

License

Apache-2.0