feat(dashboard): extract inline script to standalone dashboard.ts/js, add UI build/check recipes, and add route unit tests
This commit is contained in:
1 parent
4b307b55b9
commit
19f9c7ace7
24 files changed
+2343
-1819
No files matched your search
+14
-19
@@ -2028,10 +2028,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_all_meta_handlers_comprehensive() {
|
||||
use crate::handlers::tasks::{
|
||||
AddMilestoneHandler, ListMilestonesHandler, SetAcceptanceCriteriaHandler,
|
||||
UpdateMilestoneHandler, VerifyAcceptanceCriteriaHandler,
|
||||
};
|
||||
use crate::handlers::tasks::{MilestonesHandler, TasksHandler};
|
||||
use crate::handlers::graph::SweepGraphHealthHandler;
|
||||
use crate::handlers::git::QueryGitDiffsHandler;
|
||||
|
||||
@@ -2116,10 +2113,11 @@ mod tests {
|
||||
assert!(q_hyp_res.contains("Caching improves response speed"));
|
||||
|
||||
// AddMilestone & UpdateMilestone & ListMilestones
|
||||
let add_ms = AddMilestoneHandler;
|
||||
let ms_res = add_ms
|
||||
let handler_ms = MilestonesHandler;
|
||||
let ms_res = handler_ms
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"action": "add",
|
||||
"title": "v1.0 Release",
|
||||
"description": "First major release"
|
||||
}),
|
||||
@@ -2130,10 +2128,10 @@ mod tests {
|
||||
assert!(ms_res.contains("Milestone added"));
|
||||
|
||||
let ms_id = state.project.milestones.read_with(|ms| ms[0].id.clone());
|
||||
let upd_ms = UpdateMilestoneHandler;
|
||||
let upd_ms_res = upd_ms
|
||||
let upd_ms_res = handler_ms
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"action": "update",
|
||||
"id": ms_id,
|
||||
"status": "completed"
|
||||
}),
|
||||
@@ -2142,10 +2140,7 @@ mod tests {
|
||||
.await;
|
||||
assert!(upd_ms_res.is_ok());
|
||||
|
||||
|
||||
|
||||
let list_ms = ListMilestonesHandler;
|
||||
let list_ms_res = list_ms.execute(serde_json::json!({}), state.clone()).await.unwrap();
|
||||
let list_ms_res = handler_ms.execute(serde_json::json!({"action": "list"}), state.clone()).await.unwrap();
|
||||
assert!(list_ms_res.contains("v1.0 Release"));
|
||||
|
||||
// SetAcceptanceCriteria & VerifyAcceptanceCriteria
|
||||
@@ -2164,11 +2159,12 @@ mod tests {
|
||||
};
|
||||
state.project.tasks.modify(|t| t.push(task));
|
||||
|
||||
let set_ac = SetAcceptanceCriteriaHandler;
|
||||
let set_ac_res = set_ac
|
||||
let handler_tasks = TasksHandler;
|
||||
let set_ac_res = handler_tasks
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"task_title": "AC Task",
|
||||
"action": "set_criteria",
|
||||
"id": "t_ac_1",
|
||||
"criteria": ["Code compiles cleanly", "Tests pass"]
|
||||
}),
|
||||
state.clone(),
|
||||
@@ -2177,12 +2173,11 @@ mod tests {
|
||||
.unwrap();
|
||||
assert!(set_ac_res.contains("Acceptance criteria set successfully."));
|
||||
|
||||
let ver_ac = VerifyAcceptanceCriteriaHandler;
|
||||
let ver_ac_res = ver_ac
|
||||
let ver_ac_res = handler_tasks
|
||||
.execute(
|
||||
serde_json::json!({
|
||||
"task_id": "t_ac_1",
|
||||
"criteria": "Code compiles cleanly",
|
||||
"action": "verify",
|
||||
"id": "t_ac_1",
|
||||
"proof": "cargo test passed"
|
||||
}),
|
||||
state.clone(),
|
||||
|
||||
@@ -91,130 +91,7 @@ impl McpTool for StickyNotesHandler {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AddStickyNoteHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for AddStickyNoteHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"add_sticky_note"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<AddStickyNoteTool>("add_sticky_note", "Execute add_sticky_note")
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: AddStickyNoteTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let now = crate::handlers::utils::now_secs();
|
||||
let expires_at = if let Some(ttl) = req.ttl_seconds {
|
||||
Some(now + ttl)
|
||||
} else if req.session_only.unwrap_or(false) {
|
||||
Some(now + 14400) // Default 4-hour session TTL
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
state.code.sticky.modify(|notes| {
|
||||
notes.push(StickyNote {
|
||||
timestamp: now,
|
||||
content: req.content,
|
||||
expires_at,
|
||||
});
|
||||
});
|
||||
Ok("Sticky note added.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReadStickyNotesHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for ReadStickyNotesHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"read_sticky_notes"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<ReadStickyNotesTool>(
|
||||
"read_sticky_notes",
|
||||
"Execute read_sticky_notes",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let now = crate::handlers::utils::now_secs();
|
||||
let mut active_notes = Vec::new();
|
||||
|
||||
state.code.sticky.modify(|notes| {
|
||||
notes.retain(|n| {
|
||||
if let Some(exp) = n.expires_at {
|
||||
exp > now
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
active_notes = notes.clone();
|
||||
});
|
||||
|
||||
Ok(serde_json::to_string(&active_notes)?)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeleteStickyNoteHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for DeleteStickyNoteHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"delete_sticky_note"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<DeleteStickyNoteTool>(
|
||||
"delete_sticky_note",
|
||||
"Execute delete_sticky_note",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
let req: DeleteStickyNoteTool = serde_json::from_value(args).map_err(|e| e.to_string())?;
|
||||
let mut success = false;
|
||||
state.code.sticky.modify(|notes| {
|
||||
if req.index > 0 && req.index <= notes.len() {
|
||||
notes.remove(req.index - 1);
|
||||
success = true;
|
||||
}
|
||||
});
|
||||
if success {
|
||||
Ok("Sticky note deleted.".to_string())
|
||||
} else {
|
||||
Err(crate::error::AppError::Internal(
|
||||
"Invalid sticky note index.".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ClearStickyNotesHandler;
|
||||
|
||||
#[async_trait]
|
||||
impl McpTool for ClearStickyNotesHandler {
|
||||
fn name(&self) -> &'static str {
|
||||
"clear_sticky_notes"
|
||||
}
|
||||
|
||||
fn schema(&self) -> Value {
|
||||
crate::mcp::tool_def::<ClearStickyNotesTool>(
|
||||
"clear_sticky_notes",
|
||||
"Execute clear_sticky_notes",
|
||||
)
|
||||
}
|
||||
|
||||
async fn execute(&self, _args: Value, state: Arc<MemoryState>) -> crate::error::Result<String> {
|
||||
state.code.sticky.modify(|notes| {
|
||||
notes.clear();
|
||||
});
|
||||
Ok("All sticky notes cleared.".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HandoffMemosHandler;
|
||||
|
||||
@@ -414,37 +291,36 @@ mod tests {
|
||||
let dir = tempdir().unwrap();
|
||||
let state = Arc::new(MemoryState::new(dir.path().to_str().unwrap()));
|
||||
|
||||
let add_handler = AddStickyNoteHandler;
|
||||
let handler = StickyNotesHandler;
|
||||
let args = json!({
|
||||
"action": "add",
|
||||
"content": "Buy milk",
|
||||
});
|
||||
|
||||
let res = add_handler
|
||||
let res = handler
|
||||
.execute(args, state.clone())
|
||||
.await
|
||||
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
|
||||
.unwrap();
|
||||
assert!(res.contains("Sticky note added"));
|
||||
|
||||
let read_handler = ReadStickyNotesHandler;
|
||||
let res2 = read_handler
|
||||
.execute(json!({}), state.clone())
|
||||
let res2 = handler
|
||||
.execute(json!({"action": "read"}), state.clone())
|
||||
.await
|
||||
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
|
||||
.unwrap();
|
||||
assert!(res2.contains("Buy milk"));
|
||||
|
||||
let delete_handler = DeleteStickyNoteHandler;
|
||||
let args2 = json!({"index": 1});
|
||||
let res3 = delete_handler
|
||||
let args2 = json!({"action": "delete", "index": 1});
|
||||
let res3 = handler
|
||||
.execute(args2, state.clone())
|
||||
.await
|
||||
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
|
||||
.unwrap();
|
||||
assert_eq!(res3, "Sticky note deleted.");
|
||||
|
||||
let res4 = read_handler
|
||||
.execute(json!({}), state.clone())
|
||||
let res4 = handler
|
||||
.execute(json!({"action": "read"}), state.clone())
|
||||
.await
|
||||
.map_err(|e| crate::error::AppError::Internal(e.to_string()))
|
||||
.unwrap();
|
||||
|
||||
+337
-609
File diff suppressed because it is too large.
Load diff
@@ -206,6 +206,8 @@ impl McpTool for ToggleClipboardWatchModeHandler {
|
||||
|
||||
let mut watch_mode = state.clipboard_watch_mode.write().await;
|
||||
*watch_mode = tool_args.enable;
|
||||
drop(watch_mode);
|
||||
state.clipboard_notify.notify_waiters();
|
||||
|
||||
let status_msg = if tool_args.enable {
|
||||
"Clipboard watch mode enabled. Changes will be ingested as StickyNotes."
|
||||
|
||||
Reference in new issue
Block a user