Summary#
AssistantPanel is the place an app lets a person ASK — about the record on screen, the numbers on a dashboard, what
to do next. It floats at the right edge over the page (which stays live under it), and on a phone it takes the screen.
It draws the conversation and the box; it does not decide the answers. The app holds the turns, binds the draft and
answers onSend, setting working while it does — an agent, a search or a fixed reply all fit the same seam.
[Route("/")] [Render(CSR)] [AllowAnonymous]
component Home() {
bool asking = false;
string draft = "";
List<AssistantTurn> turns = [];
action Open() { asking = true; }
action Close() { asking = false; }
action Ask() {
var next = new List<AssistantTurn>();
foreach (var t in turns) { next.Add(t); }
next.Add(new AssistantTurn { From = AssistantVoice.Person, Text = draft });
next.Add(new AssistantTurn { From = AssistantVoice.Assistant, Text = "Three tasks are due today; the oldest is from Monday.",
Cites = "Tasks · due today" });
turns = next;
draft = "";
}
action Suggest(string s) { draft = s; Ask(); }
render {
Stack(gap: 4, p: 6) {
PageHead("Today") { Button("Ask", onPress: Open); }
if (asking) {
AssistantPanel("Assistant", turns.ToArray(), value: draft, onSend: Ask, onClose: Close,
suggestions: ["What is due today?", "What changed since yesterday?"], onSuggest: Suggest);
}
}
}
}Signature#
AssistantPanel(string title, AssistantTurn[] turns, Binding<string> value, Action onSend, Action onClose,
bool working = false, string[] suggestions = [], Action<string> onSuggest = null,
string placeholder = "Ask about what you are looking at")
public sealed class AssistantTurn { AssistantVoice From; string Text; string Cites; } // From: Person | AssistantDescription#
Who keeps the conversation?#
The app. turns is an array of AssistantTurn — who said it, what, and (for an answer) what it drew on, shown under
it as Cites. Build a NEW list when a turn is added rather than adding to the old one in place, exactly as with any
list a render reads; a list changed in place keeps its identity and nothing redraws.
What happens while it thinks?#
Set working: true while the answer is on its way: the panel shows a waiting lamp and "Thinking…" under the last
turn, and the box stays usable. Set it back when the answer lands.
What does it look like empty?#
"Ask about anything on this page." and, when onSuggest is given, one press per suggestions entry — pressing one
asks it. A short list of questions the page can genuinely answer teaches more than any placeholder.
How is it opened and closed?#
By the app — a bool it renders the panel under, set from a button anywhere (a shell's actions slot, a record's
head). The panel closes with its own × or Escape, both running onClose.
How do I drive it in a test?#
It is a dialog named "Assistant": Ui.Fill("Ask", "What is due today?"), Ui.Click("Ask it"), then
Assert.Visible("Three tasks", within: "Assistant"); Ui.Click("Close the assistant") closes it.
See also#
- Arrangements — five ways to build a whole app, and which look wears each — the record desk and the command centre, which both open one
- Dock — when the answer belongs beside the list rather than over it