diff --git "a/journals/20260227-0001-\350\250\255\345\256\232\346\260\270\347\266\232\345\214\226\343\201\250Issue\345\220\214\346\234\237\343\203\273\344\277\235\345\255\230\343\203\220\343\202\260\344\277\256\346\255\243.md" "b/journals/20260227-0001-\350\250\255\345\256\232\346\260\270\347\266\232\345\214\226\343\201\250Issue\345\220\214\346\234\237\343\203\273\344\277\235\345\255\230\343\203\220\343\202\260\344\277\256\346\255\243.md" new file mode 100644 index 0000000..5d156aa --- /dev/null +++ "b/journals/20260227-0001-\350\250\255\345\256\232\346\260\270\347\266\232\345\214\226\343\201\250Issue\345\220\214\346\234\237\343\203\273\344\277\235\345\255\230\343\203\220\343\202\260\344\277\256\346\255\243.md" @@ -0,0 +1,31 @@ +# 2026-02-27: 設定永続化・Issue同期・保存バグ修正 + +## 1. 作業実施の理由と指示 + +- **背景**: デバッグ起動時に「OSにログインしたときに自動で起動する」チェックが永続化しない報告、サーバーコンソールに操作時の受信メッセージを出したい要望があった。また、GitBucket の Issue を API で改訂(Issue 9 を Issue 4 の後に対応する旨を追記)したいが PATCH が 404 になる事象、および設定保存ボタンがファイル保存の成否を無視して成功表示するバグの指摘があった。 +- **意図**: 設定の読み書きを Tauri invoke 優先にし永続化を確実にする。サーバー側で GET/POST /settings と MCP 受信時にログを出す。GitBucket の Issue 編集 API を調査し、未実装ならコメント投稿で代替する。設定保存ハンドラで `saveSettingsToFile` の戻り値を確認し、失敗時は成功表示・localStorage 更新を行わないようにする。 + +## 2. 指摘事項とその対応 + +- **指摘**: チェックが永続化しない。 + - **対応**: フロントの `loadSettingsFromFile` / `saveSettingsToFile` で、Tauri 内では先に `invoke('get_app_settings')` / `invoke('set_app_settings', { settings })` を呼ぶように変更。MCP サーバー起動前でもアプリデータの settings.json を直接読み書きできるため永続化が確実になった。 +- **指摘**: サーバーコンソールに操作時の受信メッセージを出したい。 + - **対応**: `handlers.rs` で GET /settings に `[server] GET /settings 受信`、POST /settings に payload 付きでログを追加。`mcp/mod.rs` で MCP 受信時に `[server] MCP 受信: ` を出力。 +- **指摘**: GitBucket の Issue を API で改訂したいが PATCH で 404。 + - **対応**: 外部調査の結果、公式 GitBucket の `ApiIssueControllerBase.scala` には「Edit an issue」の PATCH ルートが存在せず、全バージョンで未実装と判断。`sync_issues.mjs` で PATCH が 404 のときに **Create an issue comment** API で本文をコメントとして投稿するフォールバックを実装。Issue 9 に「Issue 4 の後に対応」をコメントで反映できるようにした。 +- **指摘**: 「多くのバージョンが未対応」は不正確では。 + - **対応**: 公式ソースで PATCH が未実装であることを確認し、`sync_issues.mjs` と `.agent/rules/issue_management.md` の記述を「公式 GitBucket では全バージョンで未実装」に修正。 +- **指摘**: 設定保存ボタンが `saveSettingsToFile` の戻り値を無視し、失敗時も成功表示・localStorage 更新をしている。 + - **対応**: 保存ハンドラで `const saved = await saveSettingsToFile(payload)` の戻り値を確認。`saved === true` のときのみ localStorage 更新・フォーム反映・「保存しました」表示。`saved === false` のときは「保存に失敗しました(ファイルへの書き込みに失敗しました)」を表示し、localStorage とフォームは変更しないようにした。 + +## 3. 作業詳細 + +- **main-panel.js**: `loadSettingsFromFile` で Tauri 時は `invoke('get_app_settings')` を優先、失敗時のみ fetch。`saveSettingsToFile` で Tauri 時は `invoke('set_app_settings', { settings: payload })` を優先。保存ボタン内で `saved` を判定し、成功時のみ localStorage・フォーム・成功メッセージを更新。 +- **handlers.rs**: `settings_get_handler` 冒頭に `[server] GET /settings 受信`、`settings_post_handler` 冒頭に payload のログを追加。 +- **mcp/mod.rs**: MCP 受信ログを `[server] MCP 受信: ...` に統一。 +- **sync_issues.mjs**: `postIssueComment(issueNumber, commentBody)` を追加。`updateRemoteIssue` で PATCH が 404 の場合に本文をコメントとして投稿。コメント投稿時は `_commentFallback` を返し、ローカル上書きは行わない。コメント・ドキュメントを「公式 GitBucket では全バージョンで未実装」に修正。 +- **issue_management.md**: Issue 編集と 404 の説明を「全バージョンで未実装」に変更(日英両方)。 + +## 4. AI視点での結果 + +設定は Tauri 起動直後から invoke で読み書きされるため、デバッグ時もチェックが永続化する。サーバーコンソールで設定・MCP の受信内容を確認しやすくなった。GitBucket では Issue 本体の編集 API が使えないため、同期時に本文をコメントとして投稿する運用で情報を残せる。設定保存でファイル書き込みに失敗した場合は、誤って「保存しました」と表示されず、ユーザーに失敗が伝わるようになった。 diff --git a/src/backend/src/mcp/handlers.rs b/src/backend/src/mcp/handlers.rs index 1a7881b..f327b5a 100644 --- a/src/backend/src/mcp/handlers.rs +++ b/src/backend/src/mcp/handlers.rs @@ -41,7 +41,8 @@ serde_json::json!({ "min_score": 0.3, "limit": 10, - "run_on_login": false + "run_on_login": false, + "monitor_paths": [] }) } @@ -64,10 +65,15 @@ .get("run_on_login") .and_then(|v| v.as_bool().or_else(|| v.as_i64().map(|n| n != 0))) .unwrap_or(false); + let monitor_paths = obj.get("monitor_paths") + .and_then(|v| v.as_array()) + .cloned() + .unwrap_or_else(|| vec![]); let merged = serde_json::json!({ "min_score": obj.get("min_score").and_then(|v| v.as_f64()).unwrap_or(0.3), "limit": obj.get("limit").and_then(|v| v.as_i64()).unwrap_or(10), - "run_on_login": run_on_login + "run_on_login": run_on_login, + "monitor_paths": monitor_paths }); log::info!("settings_get: returning run_on_login={}", run_on_login); Json(merged) @@ -92,7 +98,7 @@ log::error!("settings_post: create_dir_all {:?}", e); return (axum::http::StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({"error": "failed to create dir"}))); } - let to_write = if payload.get("min_score").is_some() || payload.get("run_on_login").is_some() { + let to_write = if payload.get("min_score").is_some() || payload.get("run_on_login").is_some() || payload.get("monitor_paths").is_some() { payload.clone() } else if let Some(inner) = payload.get("settings").and_then(|v| v.as_object()) { serde_json::to_value(inner).unwrap_or(payload) diff --git a/src/frontend/components/main-panel.js b/src/frontend/components/main-panel.js index 8b4d9ed..1106b8e 100644 --- a/src/frontend/components/main-panel.js +++ b/src/frontend/components/main-panel.js @@ -84,6 +84,14 @@
+ モニター先フォルダ +

指定したフォルダ内のファイルを監視し、更新があればインデックスに反映します(複数登録可)。

+
+
+ +
+
+
検索
@@ -110,7 +118,25 @@ // Expose showPanel as method const SETTINGS_KEY = 'telosdb_settings'; - const DEFAULTS = { min_score: 0.3, limit: 10, run_on_login: false }; + const DEFAULTS = { min_score: 0.3, limit: 10, run_on_login: false, monitor_paths: [] }; + + const renderMonitorPathsList = (paths) => { + const listEl = this.querySelector('#settings-monitor-paths-list'); + if (!listEl) return; + const arr = Array.isArray(paths) ? paths : []; + listEl.innerHTML = ''; + const addRow = (value = '') => { + const row = document.createElement('div'); + row.className = 'setting-monitor-path-row'; + row.innerHTML = ` + + + `; + row.querySelector('.setting-monitor-path').value = String(value || ''); + listEl.appendChild(row); + }; + arr.forEach((p) => addRow(p)); + }; const loadSettingsIntoForm = (settingsOverrides) => { try { @@ -126,6 +152,7 @@ if (minScoreEl) minScoreEl.value = String(Number(s.min_score)); if (limitEl) limitEl.value = String(Number(s.limit)); if (runOnLoginEl) runOnLoginEl.checked = Boolean(s.run_on_login); + renderMonitorPathsList(s.monitor_paths); } catch (e) { const minScoreEl = this.querySelector('#setting-min-score'); const limitEl = this.querySelector('#setting-limit'); @@ -133,6 +160,7 @@ if (minScoreEl) minScoreEl.value = String(DEFAULTS.min_score); if (limitEl) limitEl.value = String(DEFAULTS.limit); if (runOnLoginEl) runOnLoginEl.checked = DEFAULTS.run_on_login; + renderMonitorPathsList(DEFAULTS.monitor_paths); } }; @@ -148,6 +176,7 @@ min_score: fileSettings.min_score ?? DEFAULTS.min_score, limit: fileSettings.limit ?? DEFAULTS.limit, run_on_login: Boolean(fileSettings.run_on_login), + monitor_paths: Array.isArray(fileSettings.monitor_paths) ? fileSettings.monitor_paths : DEFAULTS.monitor_paths, }; } } catch (_) { @@ -161,6 +190,7 @@ min_score: fileSettings.min_score ?? DEFAULTS.min_score, limit: fileSettings.limit ?? DEFAULTS.limit, run_on_login: Boolean(fileSettings.run_on_login), + monitor_paths: Array.isArray(fileSettings.monitor_paths) ? fileSettings.monitor_paths : DEFAULTS.monitor_paths, }; } } catch (_) {} @@ -257,6 +287,10 @@ const min_score = Math.max(0, Math.min(1, parseFloat(minScoreEl?.value) || DEFAULTS.min_score)); const limit = Math.max(1, Math.min(100, parseInt(limitEl?.value, 10) || DEFAULTS.limit)); const run_on_login = runOnLoginEl ? runOnLoginEl.checked : DEFAULTS.run_on_login; + const pathInputs = this.querySelectorAll('.setting-monitor-path'); + const monitor_paths = Array.from(pathInputs) + .map((el) => (el.value || '').trim()) + .filter(Boolean); try { // Tauri autostart(OSログイン時自動起動)を反映 try { @@ -269,16 +303,22 @@ } catch (_) { // ブラウザなど Tauri 外では無視 } - const payload = { min_score, limit, run_on_login }; - console.log('[TelosDB] 保存: run_on_login =', run_on_login); - await saveSettingsToFile(payload); - localStorage.setItem(SETTINGS_KEY, JSON.stringify(payload)); - if (minScoreEl) minScoreEl.value = String(min_score); - if (limitEl) limitEl.value = String(limit); - if (runOnLoginEl) runOnLoginEl.checked = run_on_login; - feedbackEl.textContent = '保存しました'; - feedbackEl.classList.remove('error'); - setTimeout(() => { feedbackEl.textContent = ''; }, 2000); + const payload = { min_score, limit, run_on_login, monitor_paths }; + console.log('[TelosDB] 保存: run_on_login =', run_on_login, 'monitor_paths =', monitor_paths.length); + const saved = await saveSettingsToFile(payload); + if (saved) { + localStorage.setItem(SETTINGS_KEY, JSON.stringify(payload)); + if (minScoreEl) minScoreEl.value = String(min_score); + if (limitEl) limitEl.value = String(limit); + if (runOnLoginEl) runOnLoginEl.checked = run_on_login; + renderMonitorPathsList(monitor_paths); + feedbackEl.textContent = '保存しました'; + feedbackEl.classList.remove('error'); + setTimeout(() => { feedbackEl.textContent = ''; }, 2000); + } else { + feedbackEl.textContent = '保存に失敗しました(ファイルへの書き込みに失敗しました)'; + feedbackEl.classList.add('error'); + } } catch (e) { feedbackEl.textContent = '保存に失敗しました'; feedbackEl.classList.add('error'); @@ -286,6 +326,27 @@ }); } + this.querySelector('#settings-monitor-path-add')?.addEventListener('click', (e) => { + e.preventDefault(); + const listEl = this.querySelector('#settings-monitor-paths-list'); + if (!listEl) return; + const row = document.createElement('div'); + row.className = 'setting-monitor-path-row'; + row.innerHTML = ` + + + `; + listEl.appendChild(row); + }); + + this.querySelector('#settings-monitor-paths-list')?.addEventListener('click', (e) => { + if (e.target.classList.contains('setting-monitor-path-remove')) { + e.preventDefault(); + const row = e.target.closest('.setting-monitor-path-row'); + if (row) row.remove(); + } + }); + // Activity Log accordion behavior const activityToggle = this.querySelector('.activity-toggle'); const activityContent = this.querySelector('.activity-content'); diff --git a/src/frontend/styles.css b/src/frontend/styles.css index b4c3103..91b1308 100644 --- a/src/frontend/styles.css +++ b/src/frontend/styles.css @@ -333,6 +333,35 @@ .settings-feedback.error { color: var(--accent-red, #ef4444); } +.setting-hint { + font-size: 0.85rem; + color: var(--text-dim); + margin-bottom: 12px; +} +.setting-monitor-paths-list { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 12px; +} +.setting-monitor-path-row { + display: flex; + align-items: center; + gap: 8px; +} +.setting-monitor-path-row .setting-monitor-path { + flex: 1; + min-width: 0; + padding: 6px 10px; + background: var(--bg-surface); + border: 1px solid var(--border-base); + border-radius: 4px; + color: var(--text-primary); + font-size: 0.9rem; +} +.setting-monitor-path-row .setting-monitor-path-remove { + flex-shrink: 0; +} /* 文書管理パネル */ .docs-toolbar {