Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions docs/smart-ui.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
Smart UI ([#2484](https://github.com/LHagfoss/rustcode/issues/2484)) lets
the agent answer with a small interactive component, such as a filterable
table or a view driven by a few controls, that the macOS app runs in the
transcript. It is **experimental and off by default**: Settings › General ›
Chat › Smart UI. The engine learns the setting when it starts, so after
transcript. It is **on by default** (it shipped opt-in, and agents almost
never reached it): Settings › General › Chat › Smart UI turns it off. The engine learns the setting when it starts, so after
switching it Settings offers **Restart Engine** (shown once no chat is
running).

Expand Down Expand Up @@ -179,7 +179,9 @@ clicks do not reach WebKit.

## Recommendation

**Ship it limited**: opt-in, experimental, a single live view, with the
It shipped limited: opt-in, experimental, a single live view, with the
controls above. The cost is bounded and paid only when a block runs, and the
isolation held in every check. Keep it off by default until it has been used
for a while with real pointer input and longer sessions.
isolation held in every check. Opt-in meant agents almost never used it, so
it is now on by default, and the prompt asks for visuals even unasked.
T3 Code saw the same thing: its rule "render a visual only when asked" took
inline charts from 8 of 8 eval cases to 0 of 8 (pingdotgg/t3code#25766).
2 changes: 1 addition & 1 deletion docs/visualizations.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Shared fields:

A typed content block or a tool-result artifact would need protocol changes
in every provider adapter and client. Interactive HTML uses the same fence
approach instead: the macOS app's opt-in [Smart UI](smart-ui.md) runs
approach instead: the macOS app's [Smart UI](smart-ui.md) runs
`rustcode-ui v1` blocks in a sandboxed web view.

## Widget allowlist (version 1)
Expand Down
6 changes: 3 additions & 3 deletions macos/Sources/RustCode/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ private struct GeneralSettings: View {
@AppStorage(StreamingProse.revealKey) private var revealsText = true
@AppStorage(ToolVerbosity.key) private var verbosity = ToolVerbosity.terminalDefault
@AppStorage(ComposerPredictions.key) private var predictions = true
@AppStorage(SmartUI.enabledKey) private var smartUI = false
@AppStorage(SmartUI.enabledKey) private var smartUI = SmartUI.enabledByDefault
@AppStorage(Orchestration.enabledKey) private var threadMessaging = false
@AppStorage(DesktopModel.groupsByAttentionKey) private var groupsByAttention = true
@AppStorage(TurnCompletionCard.enabledKey) private var showsTurnCompletion = true
Expand Down Expand Up @@ -566,9 +566,9 @@ private struct GeneralSettings: View {
.toggleStyle(.switch)
}
RowDivider()
SettingsRow(title: "Smart UI (experimental)",
SettingsRow(title: "Smart UI",
subtitle: smartUI == model.engineSmartUI
? "Let the agent reply with small interactive views when you ask for one. They run sandboxed, with no network, file or app access"
? "Let the agent reply with small interactive views when they help. They run sandboxed, with no network, file or app access"
: "The agent learns this when the engine restarts; threads in progress keep running until then") {
HStack(spacing: 8) {
// The engine reads the setting at initialize.
Expand Down
2 changes: 1 addition & 1 deletion macos/Sources/RustCode/SmartUIView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ struct SmartUIView: View {
/// The reply is still arriving, so an open fence may yet close.
var isStreaming = false
@ObservedObject private var host = SmartUIHost.shared
@AppStorage(SmartUI.enabledKey) private var isEnabled = false
@AppStorage(SmartUI.enabledKey) private var isEnabled = SmartUI.enabledByDefault
/// The block was written while on screen, so it runs once complete.
@State private var wasBuilding = false
@State private var showsSource = false
Expand Down
10 changes: 7 additions & 3 deletions macos/Sources/RustCodeKit/SmartUI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@ public enum SmartUI {
public static let version = 1
public static var infoString: String { "\(infoName) v\(version)" }

/// The setting that turns Smart UI on. Off by default; the engine is told
/// at `initialize`, so a change applies once the engine restarts.
/// The setting that turns Smart UI on. On by default (#2484 shipped it
/// opt-in; agents rarely used it); the engine is told at `initialize`,
/// so a change applies once the engine restarts.
public static let enabledKey = "smartUIEnabled"
public static var isEnabled: Bool { UserDefaults.standard.bool(forKey: enabledKey) }
public static let enabledByDefault = true
public static var isEnabled: Bool {
UserDefaults.standard.object(forKey: enabledKey) as? Bool ?? enabledByDefault
}
/// The `_meta.rustcode.smartUi` version advertised while it is on.
/// Version 2 also answers `previewMethod`, so the engine offers the agent
/// `smart_ui_preview` (`engine/src/smart_ui.rs`).
Expand Down
13 changes: 13 additions & 0 deletions macos/Tests/RustCodeKitTests/SmartUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ final class SmartUITests: XCTestCase {
XCTAssertEqual(on["clientCapabilities"]?["_meta"]?["rustcode"]?["visualizations"],
.number(Double(Visualization.capabilityLevel)))
}

func testSmartUIIsOnUntilTheUserTurnsItOff() {
let defaults = UserDefaults.standard
let was = defaults.object(forKey: SmartUI.enabledKey)
defer { defaults.set(was, forKey: SmartUI.enabledKey) }
defaults.removeObject(forKey: SmartUI.enabledKey)
XCTAssertTrue(SmartUI.isEnabled)
defaults.set(false, forKey: SmartUI.enabledKey)
XCTAssertFalse(SmartUI.isEnabled)
defaults.set(true, forKey: SmartUI.enabledKey)
XCTAssertTrue(SmartUI.isEnabled)
}

// MARK: - smart_ui_preview requests

func testPreviewRequestReadsHtmlAndSessionAndTheCardTitle() throws {
Expand Down
2 changes: 1 addition & 1 deletion rustcode/engine/src/skills_builtin/smart-ui.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: smart-ui
description: Write a Smart UI (rustcode-ui v1) interactive component for the RustCode macOS chat. Use only when the prompt's Smart UI section says it is on and the user asks for an interactive view, or sorting, filtering, sliders or toggles clearly help.
description: Write a Smart UI (rustcode-ui v1) interactive component for the RustCode macOS chat. Use when the prompt's Smart UI section says it is on and the user asks for an interactive view, or sorting, filtering, sliders or toggles would help them explore the answer, even unasked.
triggers: smart ui, interactive view, rustcode-ui
keywords: interactive, widget, component, html, slider, filter, sort, toggle
---
Expand Down
6 changes: 3 additions & 3 deletions rustcode/engine/src/tools/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2477,7 +2477,7 @@ One line, no headings or lists, and never one per call. Skip it only when the ba
/// Short guidance for `rustcode-viz` blocks; the full format lives in
/// `docs/visualizations.md` and the TUI renderer validates every payload.
pub(crate) const VISUALIZATION_PROMPT_HINT: &str = "# Visualizations\n\
When a chart, table, or tree beats prose, add a ```rustcode-viz fence with one JSON object: \
When a chart, table, or tree beats prose (trends, comparisons, breakdowns, hierarchies), add a ```rustcode-viz fence even if not asked with one JSON object: \
{\"version\":1,\"type\":\"bar_chart\",\"title\":\"..\",\"summary\":\"..\",\"items\":[{\"label\":\"..\",\"value\":1}]}; \
\"table\" takes columns, rows (arrays), optional sort_by {column,descending}; \"tree\" takes nodes [{label,note,children}]. Max 30 bars, 50 rows, 120 nodes.\n\n";

Expand Down Expand Up @@ -2518,8 +2518,8 @@ pub(crate) fn append_visualization_hint(
/// advertise `_meta.rustcode.smartUi`; the format lives in `docs/smart-ui.md`.
pub(crate) const SMART_UI_PROMPT_HINT: &str = "# Smart UI\n\
This app can run one small interactive component in a reply, written as a ```rustcode-ui v1 fence of self-contained HTML (no network). \
Use it only when the user asks for an interactive view or controls such as sorting, filtering or sliders clearly help; \
otherwise use Markdown or rustcode-viz. Before writing one, load the `smart-ui` skill for the format and limits.\n\n";
Use it, even unasked, when controls such as sorting, filtering, sliders or toggles let the reader explore what a static chart cannot show; \
for a plain chart, table or tree use rustcode-viz. Before writing one, load the `smart-ui` skill for the format and limits.\n\n";

/// Sent instead of [`SMART_UI_PROMPT_HINT`] when the frontend could run the
/// blocks but the user has the setting off. Without it an agent asked for
Expand Down
Loading