Skip to content

Commit 2ebcb3f

Browse files
MarkShawn2020claude
andcommitted
feat(workspace): 优化 Feature 创建流程与全局导航
- Feature 创建改为 Dialog 模式,支持输入标题和 Markdown 描述 - GlobalFeatureTabs 在所有页面始终显示 - 点击 feature tab 时自动导航到 workspace 视图 - ProjectDashboard 支持查看和恢复已归档的 features - Feature 类型新增 description 字段(前后端同步) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6a673bd commit 2ebcb3f

9 files changed

Lines changed: 473 additions & 138 deletions

File tree

src-tauri/src/lib.rs

Lines changed: 114 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,15 +2117,6 @@ fn list_reference_docs(source: String) -> Result<Vec<ReferenceDoc>, String> {
21172117
Ok(docs)
21182118
}
21192119

2120-
#[tauri::command]
2121-
fn get_reference_doc(path: String) -> Result<String, String> {
2122-
let doc_path = PathBuf::from(&path);
2123-
if !doc_path.exists() {
2124-
return Err(format!("Document not found: {}", path));
2125-
}
2126-
fs::read_to_string(&doc_path).map_err(|e| e.to_string())
2127-
}
2128-
21292120
#[tauri::command]
21302121
fn list_distill_documents() -> Result<Vec<DistillDocument>, String> {
21312122
let distill_dir = get_distill_dir();
@@ -2158,18 +2149,6 @@ fn list_distill_documents() -> Result<Vec<DistillDocument>, String> {
21582149
Ok(docs)
21592150
}
21602151

2161-
#[tauri::command]
2162-
fn get_distill_document(file: String) -> Result<String, String> {
2163-
let distill_dir = get_distill_dir();
2164-
let doc_path = distill_dir.join(&file);
2165-
2166-
if !doc_path.exists() {
2167-
return Err(format!("Document not found: {}", file));
2168-
}
2169-
2170-
fs::read_to_string(&doc_path).map_err(|e| e.to_string())
2171-
}
2172-
21732152
#[tauri::command]
21742153
fn find_session_project(session_id: String) -> Result<Option<Session>, String> {
21752154
let projects_dir = get_claude_dir().join("projects");
@@ -2218,17 +2197,6 @@ fn find_session_project(session_id: String) -> Result<Option<Session>, String> {
22182197
Ok(None)
22192198
}
22202199

2221-
#[tauri::command]
2222-
fn get_distill_command_file() -> Result<String, String> {
2223-
let cmd_path = get_claude_dir().join("commands/distill.md");
2224-
2225-
if !cmd_path.exists() {
2226-
return Err("distill.md command file not found".to_string());
2227-
}
2228-
2229-
fs::read_to_string(&cmd_path).map_err(|e| e.to_string())
2230-
}
2231-
22322200
#[tauri::command]
22332201
fn get_distill_watch_enabled() -> bool {
22342202
DISTILL_WATCH_ENABLED.load(std::sync::atomic::Ordering::Relaxed)
@@ -2267,13 +2235,20 @@ const PLUGIN_SOURCES: &[PluginSource] = &[
22672235
name: "Lovstudio",
22682236
icon: "💜",
22692237
priority: 2,
2270-
path: "../lovstudio-plugins-official", // External path
2238+
path: "marketplace/lovstudio",
2239+
},
2240+
PluginSource {
2241+
id: "lovstudio-plugins",
2242+
name: "Lovstudio Plugins",
2243+
icon: "💜",
2244+
priority: 3,
2245+
path: "../lovstudio-plugins-official",
22712246
},
22722247
PluginSource {
22732248
id: "community",
22742249
name: "Community",
22752250
icon: "🌍",
2276-
priority: 3,
2251+
priority: 4,
22772252
path: "third-parties/claude-code-templates/docs/components.json",
22782253
},
22792254
];
@@ -2330,6 +2305,7 @@ pub struct TemplatesCatalog {
23302305
pub hooks: Vec<TemplateComponent>,
23312306
pub settings: Vec<TemplateComponent>,
23322307
pub skills: Vec<TemplateComponent>,
2308+
pub statuslines: Vec<TemplateComponent>,
23332309
#[serde(default)]
23342310
pub sources: Vec<SourceInfo>,
23352311
}
@@ -2755,6 +2731,46 @@ fn load_single_plugin(
27552731
});
27562732
}
27572733

2734+
// Scan statuslines/ (.sh files)
2735+
let statuslines_dir = base_path.join("statuslines");
2736+
if statuslines_dir.exists() {
2737+
if let Ok(entries) = fs::read_dir(&statuslines_dir) {
2738+
for entry in entries.filter_map(|e| e.ok()) {
2739+
let path = entry.path();
2740+
if path.extension().map_or(false, |e| e == "sh") {
2741+
let name = path
2742+
.file_stem()
2743+
.unwrap_or_default()
2744+
.to_string_lossy()
2745+
.to_string();
2746+
let content = fs::read_to_string(&path).ok();
2747+
2748+
// Parse description from script header comment
2749+
let description = content.as_ref().and_then(|c| {
2750+
c.lines()
2751+
.find(|l| l.starts_with("# Description:"))
2752+
.map(|l| l.trim_start_matches("# Description:").trim().to_string())
2753+
});
2754+
2755+
components.push(TemplateComponent {
2756+
name: name.clone(),
2757+
path: format!("statuslines/{}.sh", name),
2758+
category: plugin_name.clone(),
2759+
component_type: "statusline".to_string(),
2760+
description,
2761+
downloads: None,
2762+
content,
2763+
source_id: Some(source.id.to_string()),
2764+
source_name: Some(source.name.to_string()),
2765+
source_icon: Some(source.icon.to_string()),
2766+
plugin_name: Some(plugin_name.clone()),
2767+
author: author.clone(),
2768+
});
2769+
}
2770+
}
2771+
}
2772+
}
2773+
27582774
components
27592775
}
27602776

@@ -2787,6 +2803,7 @@ fn get_templates_catalog(app_handle: tauri::AppHandle) -> Result<TemplatesCatalo
27872803
let mut hooks = Vec::new();
27882804
let mut settings = Vec::new();
27892805
let mut skills = Vec::new();
2806+
let mut statuslines = Vec::new();
27902807

27912808
for comp in all_components {
27922809
match comp.component_type.as_str() {
@@ -2796,6 +2813,7 @@ fn get_templates_catalog(app_handle: tauri::AppHandle) -> Result<TemplatesCatalo
27962813
"hook" => hooks.push(comp),
27972814
"setting" => settings.push(comp),
27982815
"skill" => skills.push(comp),
2816+
"statusline" => statuslines.push(comp),
27992817
_ => {} // Ignore unknown types
28002818
}
28012819
}
@@ -2818,6 +2836,7 @@ fn get_templates_catalog(app_handle: tauri::AppHandle) -> Result<TemplatesCatalo
28182836
hooks,
28192837
settings,
28202838
skills,
2839+
statuslines,
28212840
sources,
28222841
})
28232842
}
@@ -3006,6 +3025,62 @@ fn install_setting_template(config: String) -> Result<String, String> {
30063025
Ok("Settings updated".to_string())
30073026
}
30083027

3028+
#[tauri::command]
3029+
fn update_settings_statusline(statusline: serde_json::Value) -> Result<(), String> {
3030+
let settings_path = get_claude_dir().join("settings.json");
3031+
let mut settings: serde_json::Value = if settings_path.exists() {
3032+
let content = fs::read_to_string(&settings_path).map_err(|e| e.to_string())?;
3033+
serde_json::from_str(&content).map_err(|e| e.to_string())?
3034+
} else {
3035+
serde_json::json!({})
3036+
};
3037+
3038+
settings["statusLine"] = statusline;
3039+
3040+
let output = serde_json::to_string_pretty(&settings).map_err(|e| e.to_string())?;
3041+
fs::write(&settings_path, output).map_err(|e| e.to_string())?;
3042+
Ok(())
3043+
}
3044+
3045+
#[tauri::command]
3046+
fn remove_settings_statusline() -> Result<(), String> {
3047+
let settings_path = get_claude_dir().join("settings.json");
3048+
if !settings_path.exists() {
3049+
return Ok(());
3050+
}
3051+
3052+
let content = fs::read_to_string(&settings_path).map_err(|e| e.to_string())?;
3053+
let mut settings: serde_json::Value =
3054+
serde_json::from_str(&content).map_err(|e| e.to_string())?;
3055+
3056+
if let Some(obj) = settings.as_object_mut() {
3057+
obj.remove("statusLine");
3058+
}
3059+
3060+
let output = serde_json::to_string_pretty(&settings).map_err(|e| e.to_string())?;
3061+
fs::write(&settings_path, output).map_err(|e| e.to_string())?;
3062+
Ok(())
3063+
}
3064+
3065+
#[tauri::command]
3066+
fn write_statusline_script(content: String) -> Result<String, String> {
3067+
let script_path = get_claude_dir().join("statusline.sh");
3068+
fs::write(&script_path, &content).map_err(|e| e.to_string())?;
3069+
3070+
// Make executable on Unix
3071+
#[cfg(unix)]
3072+
{
3073+
use std::os::unix::fs::PermissionsExt;
3074+
let mut perms = fs::metadata(&script_path)
3075+
.map_err(|e| e.to_string())?
3076+
.permissions();
3077+
perms.set_mode(0o755);
3078+
fs::set_permissions(&script_path, perms).map_err(|e| e.to_string())?;
3079+
}
3080+
3081+
Ok(script_path.to_string_lossy().to_string())
3082+
}
3083+
30093084
// ============================================================================
30103085
// Context Feature
30113086
// ============================================================================
@@ -4145,8 +4220,8 @@ fn workspace_set_active_project(id: String) -> Result<(), String> {
41454220
}
41464221

41474222
#[tauri::command]
4148-
fn workspace_create_feature(project_id: String, name: String) -> Result<workspace_store::Feature, String> {
4149-
workspace_store::create_feature(&project_id, name)
4223+
fn workspace_create_feature(project_id: String, name: String, description: Option<String>) -> Result<workspace_store::Feature, String> {
4224+
workspace_store::create_feature(&project_id, name, description)
41504225
}
41514226

41524227
#[tauri::command]
@@ -4614,6 +4689,9 @@ pub fn run() {
46144689
check_mcp_installed,
46154690
install_hook_template,
46164691
install_setting_template,
4692+
update_settings_statusline,
4693+
remove_settings_statusline,
4694+
write_statusline_script,
46174695
open_in_editor,
46184696
open_session_in_editor,
46194697
reveal_session_file,
@@ -4633,14 +4711,11 @@ pub fn run() {
46334711
test_openai_connection,
46344712
test_claude_cli,
46354713
list_distill_documents,
4636-
get_distill_document,
46374714
find_session_project,
4638-
get_distill_command_file,
46394715
get_distill_watch_enabled,
46404716
set_distill_watch_enabled,
46414717
list_reference_sources,
46424718
list_reference_docs,
4643-
get_reference_doc,
46444719
get_claude_code_version_info,
46454720
install_claude_code_version,
46464721
set_claude_code_autoupdater,

src-tauri/src/workspace_store.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ pub struct Feature {
7373
#[serde(default)]
7474
pub seq: u32,
7575
pub name: String,
76+
/// Optional description (markdown) - e.g., background, goals
77+
#[serde(default)]
78+
pub description: Option<String>,
7679
pub status: FeatureStatus,
7780
#[serde(default)]
7881
pub pinned: Option<bool>,
@@ -225,7 +228,7 @@ pub fn set_active_project(id: &str) -> Result<(), String> {
225228
}
226229

227230
/// Create a new feature in a project
228-
pub fn create_feature(project_id: &str, name: String) -> Result<Feature, String> {
231+
pub fn create_feature(project_id: &str, name: String, description: Option<String>) -> Result<Feature, String> {
229232
let mut data = load_workspace()?;
230233

231234
let project = data
@@ -238,6 +241,7 @@ pub fn create_feature(project_id: &str, name: String) -> Result<Feature, String>
238241
id: uuid::Uuid::new_v4().to_string(),
239242
seq: 0, // Will be set by frontend using feature_counter
240243
name,
244+
description,
241245
status: FeatureStatus::Pending,
242246
pinned: None,
243247
archived: None,

0 commit comments

Comments
 (0)