graphic design tool
git clone https://git.lucas.co/cce-designer.git
feat: get_state reports the status line
The status line carries the load report, node errors and refused edits,
and nothing outside the window could read it. get_state now returns it as
`status`, beside `playbar` and `grid`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CLAUDE.md | 6 +++++-
src/api.rs | 2 +-
src/main.rs | 13 +++++++++++++
src/window.rs | 6 +++++-
4 files changed, 24 insertions(+), 3 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index bd4d841..5c13ed2 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -79,7 +79,11 @@ The main window runs an embedded MCP server on `127.0.0.1:3001`
`claude mcp add --transport http cce-designer http://127.0.0.1:3001/mcp`, or
speak JSON-RPC directly with curl (`initialize` / `tools/list` / `tools/call`).
There is one tool per `McpAction` variant (tool name = the variant's serde
-tag, dispatched in `apply_mcp_call` in `src/window.rs`) plus `get_state`. The
+tag, dispatched in `apply_mcp_call` in `src/window.rs`) plus `get_state`,
+which returns the project beside three app-state fields that are not part of
+the save: `playbar`, `grid` and `status` — the status line's text as shown,
+which is the way to read a load report, a node error or a refused edit when
+the window is off-screen. The
tool list lives in `mcp_tools()` in `src/api.rs`; the protocol layer is
`cce_ui::mcp` (tools-only Streamable HTTP). Keep the enum, the tool list, and
the schemas in sync — `test_mcp_tools_map_to_actions` enforces the mapping.
diff --git a/src/api.rs b/src/api.rs
index e696af9..3633b14 100644
--- a/src/api.rs
+++ b/src/api.rs
@@ -32,7 +32,7 @@ pub(crate) fn mcp_tools() -> Vec<McpTool> {
vec![
tool(
"get_state",
- "Get the current project state (node tree with params, cameras, pan, current path, selection) as JSON.",
+ "Get the current project state (node tree with params, cameras, pan, current path, selection) as JSON, plus the playbar, the network grid and the status line's text (`status`).",
no_args(),
),
tool(
diff --git a/src/main.rs b/src/main.rs
index 65e7fa0..c487fde 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4089,6 +4089,19 @@ mod tests {
assert!(s.last_status_text.contains("Not applied") && s.last_status_text.contains("Threshold"), "{}", s.last_status_text);
}
+ /// get_state carries the status line, so it can be read from outside
+ /// whether or not the window is on screen.
+ #[test]
+ fn get_state_reports_the_status_line() {
+ let mut s = State::new(false);
+ s.update_status_text("Not applied — Threshold: 'abc' is not a number");
+ let (reply, _rx) = std::sync::mpsc::channel();
+ let call = cce_ui::mcp::McpToolCall { name: "get_state".into(), arguments: serde_json::json!({}), reply };
+ let v = s.apply_mcp_call(&call, &mut false).expect("get_state");
+ assert_eq!(v["status"], "Not applied — Threshold: 'abc' is not a number");
+ assert!(v.get("root").is_some(), "the project is still there beside it");
+ }
+
/// A load says when a project holds a value that does not fit — kept,
/// but worth knowing about.
#[test]
diff --git a/src/window.rs b/src/window.rs
index a1f148d..005fd2b 100644
--- a/src/window.rs
+++ b/src/window.rs
@@ -629,7 +629,7 @@ impl State {
/// An MCP tool call: `get_state` returns the project snapshot; every
/// other tool name is an `McpAction` tag — injected into the arguments
/// and run through the shared action path.
- fn apply_mcp_call(
+ pub(crate) fn apply_mcp_call(
&mut self,
call: &cce_ui::mcp::McpToolCall,
needs_redraw: &mut bool,
@@ -659,6 +659,10 @@ impl State {
"configured_pitch": [cfg.pitch_x, cfg.pitch_y],
"configured_node_size": [cfg.node_w, cfg.node_h],
});
+ // The status line as shown — the load report, a node error, a
+ // refused edit. The window may be anywhere, or off-screen; this
+ // is how to read it from outside.
+ v["status"] = serde_json::Value::String(self.last_status_text.clone());
return Ok(v);
}
let mut req = if call.arguments.is_object() {