use super::{arg, said, Session};
use crate::client;
use serde_json::{json, Value};
const SHOW: &str = "Show a small card above the rail's foot in snyvi while this panel has focus: a link to \
where the work can be seen, or a one-line word on where it is. For a place the user will want while you work \
(a preview or dev server, a deploy, a PR, a CI run, a doc): kind link with a short label and the url. For \
progress worth a glance (\"Building 3/7 shards\", \"Tests passed 412/412\"): kind text, with tone (run while \
it goes, ok, warn, bad) and for, the seconds it stays (10-3600; leave it out to keep it while this session \
runs). Give an id and send the same id again to update it in place (a count going 3/7, 4/7) rather than \
stacking a new one. The newest three show; a panel keeps six. Never a paragraph, an instruction, or something \
the user must do (that is hand_over); a figure for the whole desk to keep is set_widget. The user can close \
a card with its ✕; a closed card stays closed for the session. Cards go when this session ends.";
const PIN: &str = "Pin a link in the rail's foot of this snyvi desk, beside its repository: a place \
this desk's work lives that the user will want one click away on any day, whichever panel they are in -- the \
live site, a staging or preview deploy that stays, a dashboard, the docs. It stays until the user takes it out \
with its ✕. Pin the same url again to change its label in place. For a place that matters only while you work \
(a dev server, a CI run, this PR), use show_card instead: it goes with this session. The repository and the \
thread's PR are shown already. A desk shows six links; never a link the user did not need, never one to \
something they must do (that is hand_over).";
const CLEAR: &str =
"Take a card you showed with show_card off the rail's foot, by its id: when the link is \
no longer useful or the word no longer true.";
pub(super) fn specs() -> Vec<Value> {
vec![
json!({
"name": "show_card", "title": "Show a card", "description": SHOW,
"inputSchema": { "type": "object", "properties": {
"kind": { "type": "string", "enum": ["link", "text"] },
"label": { "type": "string", "description": "One line: at most 32 characters for a link, 80 for text." },
"url": { "type": "string", "description": "For a link: http or https, this machine included (http://127.0.0.1:7801)." },
"id": { "type": "string", "description": "Letters, digits, - and _, at most 32. The same id updates the card." },
"tone": { "type": "string", "enum": ["run", "ok", "warn", "bad", "none"] },
"for": { "type": "integer", "minimum": 10, "maximum": 3600, "description": "Seconds it stays. Leave it out to keep it while this session runs." }
}, "required": ["kind", "label"], "additionalProperties": false },
"annotations": { "readOnlyHint": false, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false }
}),
json!({
"name": "pin_link", "title": "Pin a link", "description": PIN,
"inputSchema": { "type": "object", "properties": {
"label": { "type": "string", "description": "What it is, at most 32 characters: \"Live site\", \"Staging\"." },
"url": { "type": "string", "description": "http or https." }
}, "required": ["label", "url"], "additionalProperties": false },
"annotations": { "readOnlyHint": false, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false }
}),
json!({
"name": "clear_card", "title": "Clear a card", "description": CLEAR,
"inputSchema": { "type": "object", "properties": {
"id": { "type": "string" }
}, "required": ["id"], "additionalProperties": false },
"annotations": { "readOnlyHint": false, "destructiveHint": false, "idempotentHint": true, "openWorldHint": false }
}),
]
}
impl Session {
pub(super) fn show_card(&self, args: &Value) -> Value {
let Some(p) = self.pane.as_deref() else {
return said(
"This session is not running in a snyvi desk, so there is no rail to show a card in.",
true,
);
};
let mut body = json!({ "kind": arg(args, "kind"), "label": arg(args, "label") });
for k in ["url", "id", "tone", "for"] {
if let Some(v) = args.get(k).filter(|v| !v.is_null()) {
body[k] = v.clone();
}
}
match client::pane_thread(&self.paths, p, "card", body) {
Ok(r) if r.get("closed").and_then(Value::as_bool) == Some(true) => said(
"The user closed this card with its ✕, so it stays out of view for this session. Don't put it again; if something about it needs them, say so in your reply.",
false,
),
Ok(_) => said(
match args.get("id").and_then(Value::as_str) {
Some(id) => format!("The card {id} is above the rail's foot while this panel has focus. Send the same id to update it; clear_card takes it off."),
None => "The card is above the rail's foot while this panel has focus.".to_string(),
},
false,
),
Err(e) => said(format!("snyvi did not take it: {e}"), true),
}
}
pub(super) fn pin_link(&self, args: &Value) -> Value {
let Some(p) = self.pane.as_deref() else {
return said(
"This session is not running in a snyvi desk, so there is no rail to pin a link in.",
true,
);
};
let body = json!({ "label": arg(args, "label"), "url": arg(args, "url"), "by": self.by() });
match client::pane_thread(&self.paths, p, "links", body) {
Ok(r) if r.get("hidden").and_then(Value::as_bool) == Some(true) => said(
"The user took this link out of the rail's foot with its ✕, so it stays out. Don't pin it again.",
false,
),
Ok(r) => said(
format!(
"The link is in the rail's foot of the desk {}. Pin the same url to change its label.",
r.get("desk").and_then(Value::as_str).unwrap_or("")
),
false,
),
Err(e) => said(format!("snyvi did not take it: {e}"), true),
}
}
pub(super) fn clear_card(&self, args: &Value) -> Value {
let Some(p) = self.pane.as_deref() else {
return said(
"This session is not running in a snyvi desk, so it has no cards.",
true,
);
};
let id = arg(args, "id");
match client::pane_thread(&self.paths, p, "card", json!({ "id": id, "clear": true })) {
Ok(_) => said(format!("The card {id} is off the rail."), false),
Err(e) => said(format!("snyvi did not take it: {e}"), true),
}
}
}