releases

solverforge-ui 0.8.x: Null-Safe Lifecycle Callbacks and the Bundled Agent Skill

solverforge-ui 0.8.0 makes the retained-job callback contract explicit and null-safe, keeps component content text-rendered by default, documents the timeline and Gantt model rules, and ships the crate version through SF.version and solverforge_ui::assets::version().

solverforge-ui 0.8.0 is published on crates.io, with the matching v0.8.0 source tag. The release keeps the framework-neutral solverforge_ui::assets API and the Axum .merge(solverforge_ui::routes()) integration, and it tightens the browser-facing contract around retained-job callbacks, component content, and versioned asset identity.

Direct UI integrations can install the new line:

solverforge-ui = { version = "0.8.0" }

At publication, solverforge-cli 2.2.3 still targets solverforge-ui 0.7.0. That is a scaffold dependency choice, not a statement that the published UI crate is still on 0.7.x. Generated apps move to 0.8.0 only when the app is deliberately upgraded and validated.

What Changed

Solver lifecycle callbacks are explicit and null-safe

SF.createSolver(...) already distinguished lifecycle states. 0.8.0 documents the exact callback argument shape and the snapshot rules that go with it:

Callback Arguments
onProgress (meta)
onSolution (snapshot, meta)
onPauseRequested (meta)
onPaused (snapshot, meta)
onResumed (meta)
onCancelled (snapshot, meta)
onComplete (snapshot, meta)
onFailure (message, meta, snapshot, analysis)
onAnalysis (analysis, meta)
onError (message)

A snapshot-bearing callback must render only when snapshot && snapshot.solution exists, and should synchronize application lifecycle markers in a finally block. Cancellation, failure, or a failed snapshot synchronization can leave no solution snapshot; the lifecycle markers must still update even if application rendering throws. Keep the rendered plan deep-cloned before sending it back to the solver or mutating it locally.

Component content is text-rendered by default

Component content is text-rendered, and unsafe HTML is an explicit opt-in. A createTabs(...) entry therefore passes an object, not a bare string:

var tabs = SF.createTabs({
  tabs: [{ id: "plan", content: { unsafeHtml: "<div>Plan view</div>" }, active: true }],
});

The documented unsafe fields are SF.el(...) unsafeHtml, createModal(...) unsafeBody, createTabs(...) tabs[].content.unsafeHtml, createTable(...) cells[].unsafeHtml, and SF.gantt.create(...) unsafePopupHtml plus columns[].render(task).unsafeHtml. SF.escHtml(...) remains the shipped escape helper.

Asset identity in both bundles

The crate still emits stable and versioned bundle filenames, with different cache policies:

  • stable: /sf/sf.css, /sf/sf.jsCache-Control: public, max-age=3600
  • versioned: /sf/sf.0.8.0.css, /sf/sf.0.8.0.jsCache-Control: public, max-age=31536000, immutable
  • fonts/, vendor/, and img/ assets are served as immutable

SF.version in either bundle and solverforge_ui::assets::version() both report the crate version that produced the embedded asset set, so a host can assert that the loaded sf.js matches its Rust dependency.

For non-Axum Rust hosts, disable the default feature and serve the same embedded assets through the framework-neutral API:

solverforge-ui = { version = "0.8.0", default-features = false }
let asset = solverforge_ui::assets::get("sf.js").expect("embedded sf.js");
let content_type = asset.content_type();
let cache_control = asset.cache_control();
let bytes = asset.bytes();

Timeline and Gantt model rules

The dense rail timeline keeps its integer-minute model. Day entries may carry label, subLabel or meta, and isWeekend; tick entries may be numbers or { minute, label? } objects. Timeline tones are emerald, blue, amber, rose, violet, cyan, red, and slate; a raw CSS colour or a { background, border, overlay, text } object is also accepted. Detailed blocks still render exact interval geometry, keep adjacent intervals disjoint on one track, and pack true overlaps onto separate track rows.

SF.gantt.create(...) requires a laid-out, non-zero-size mount target. mount(target) accepts an element or element id, setTasks() rebuilds both panes, refresh() refreshes an existing Frappe chart, and destroy() removes the wrapper and releases Split.js and resize observers.

Adapter path and command overrides

The HTTP adapter accepts type: "axum" and type: "fetch", and supports baseUrl, jobsPath (default /jobs), demoDataPath (default /demo-data), and extra request headers. The Tauri adapter uses invoke and listen, and optional commands entries override the default command names.

Bundled agent skill

skills/solverforge-ui/ is a portable, harness-agnostic agent skill that maps a planning model onto the shipped timeline, Gantt, map, rail, and table surfaces while preserving the generated model and the /sf + /jobs + /demo-data backend contract.

./scripts/install-skill                    # opencode + Claude Code + Agent Skills
./scripts/install-skill --only claude      # one harness only
./scripts/install-skill --project ../my-app  # per-harness project directories
./scripts/install-skill --dir ~/skills     # any explicit skills directory
./scripts/install-skill --list             # show install state

The installer copies the skill into each harness’s own skills directory; there is no symlink and no shared location. User-scope defaults are opencode ~/.config/opencode/skills, Claude Code ~/.claude/skills, and Agent Skills ~/.agents/skills, with --project using each harness’s project directory. From the repository root, make install-skill runs the same script. See the solverforge-ui agent skill page for the install matrix, the reference files, and the guardrails the skill enforces.

Patch History

Version Date Notes
0.8.0 2026-09-15 Documents explicit null-safe lifecycle callbacks, keeps component content text-rendered with documented unsafe opt-ins, publishes SF.version and solverforge_ui::assets::version(), tightens timeline and Gantt model rules, and ships the installable agent skill.

Use the solverforge-ui manual for the current direct UI integration surface. Use the solverforge-cli manual when you need to know which UI version fresh generated apps currently pin.