summaryrefslogtreecommitdiff
path: root/makima/src
diff options
context:
space:
mode:
Diffstat (limited to 'makima/src')
-rw-r--r--makima/src/daemon/process/claude.rs2
-rw-r--r--makima/src/daemon/storage/patch.rs1
-rw-r--r--makima/src/daemon/task/manager.rs6
-rw-r--r--makima/src/daemon/tui/mod.rs1
-rw-r--r--makima/src/db/models.rs12
-rw-r--r--makima/src/db/repository.rs20
-rw-r--r--makima/src/listen.rs2
-rw-r--r--makima/src/llm/groq.rs1
-rw-r--r--makima/src/orchestration/directive.rs99
-rw-r--r--makima/src/server/handlers/listen.rs3
-rw-r--r--makima/src/server/handlers/mesh.rs2
-rw-r--r--makima/src/server/handlers/mesh_daemon.rs2
-rw-r--r--makima/src/server/handlers/mesh_supervisor.rs2
-rw-r--r--makima/src/server/handlers/mod.rs1
-rw-r--r--makima/src/server/handlers/settings.rs196
-rw-r--r--makima/src/server/mod.rs22
16 files changed, 90 insertions, 282 deletions
diff --git a/makima/src/daemon/process/claude.rs b/makima/src/daemon/process/claude.rs
index aa18fab..57c8f77 100644
--- a/makima/src/daemon/process/claude.rs
+++ b/makima/src/daemon/process/claude.rs
@@ -577,7 +577,6 @@ impl ProcessManager {
// On Unix, create a new process group so we can kill all child processes
#[cfg(unix)]
{
- #[allow(unused_imports)]
use std::os::unix::process::CommandExt;
cmd.process_group(0);
}
@@ -763,7 +762,6 @@ impl ProcessManager {
// On Unix, create a new process group so we can kill all child processes
#[cfg(unix)]
{
- #[allow(unused_imports)]
use std::os::unix::process::CommandExt;
cmd.process_group(0);
}
diff --git a/makima/src/daemon/storage/patch.rs b/makima/src/daemon/storage/patch.rs
index c9bc6f5..05c45a3 100644
--- a/makima/src/daemon/storage/patch.rs
+++ b/makima/src/daemon/storage/patch.rs
@@ -387,7 +387,6 @@ fn parse_diff_stat(stat_output: &str) -> (usize, usize) {
}
/// Checkout a specific commit in the worktree.
-#[allow(dead_code)]
pub async fn checkout_commit(worktree_path: &Path, sha: &str) -> Result<(), PatchError> {
let output = Command::new("git")
.current_dir(worktree_path)
diff --git a/makima/src/daemon/task/manager.rs b/makima/src/daemon/task/manager.rs
index f483218..ca97453 100644
--- a/makima/src/daemon/task/manager.rs
+++ b/makima/src/daemon/task/manager.rs
@@ -20,7 +20,7 @@ use crate::daemon::error::{DaemonError, TaskError, TaskResult};
use crate::daemon::process::{ClaudeInputMessage, ProcessManager};
use crate::daemon::storage;
use crate::daemon::temp::TempManager;
-use crate::daemon::worktree::{is_new_repo_request, ConflictResolution, WorktreeInfo, WorktreeManager};
+use crate::daemon::worktree::{is_new_repo_request, ConflictResolution, WorktreeError, WorktreeInfo, WorktreeManager};
use crate::daemon::db::local::LocalDb;
use crate::daemon::ws::{BranchInfo, DaemonCommand, DaemonMessage};
@@ -3706,7 +3706,6 @@ impl TaskManager {
}
/// Handle GetWorktreeDiff command - get git diff for a task's worktree.
- #[allow(dead_code)]
async fn handle_get_worktree_diff(
&self,
task_id: Uuid,
@@ -5623,7 +5622,7 @@ impl TaskManagerInner {
let ws_tx = self.ws_tx.clone();
// For auth error detection
- let _claude_command = self.process_manager.claude_command().to_string();
+ let claude_command = self.process_manager.claude_command().to_string();
let daemon_hostname = hostname::get().ok().and_then(|h| h.into_string().ok());
let mut auth_error_handled = false;
@@ -5631,7 +5630,6 @@ impl TaskManagerInner {
let mut accumulated_output = String::new();
let mut circuit_breaker = CircuitBreaker::new();
let mut iteration_count = 0u32;
- #[allow(unused_assignments)]
let mut final_exit_code: i64 = -1; // Track the final exit code across iterations
// Autonomous loop: we may run multiple iterations
diff --git a/makima/src/daemon/tui/mod.rs b/makima/src/daemon/tui/mod.rs
index 46652ec..e52b12a 100644
--- a/makima/src/daemon/tui/mod.rs
+++ b/makima/src/daemon/tui/mod.rs
@@ -26,6 +26,7 @@ use crossterm::{
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
+use ratatui::prelude::*;
use ratatui::backend::CrosstermBackend;
pub type Terminal = ratatui::Terminal<CrosstermBackend<io::Stdout>>;
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 121897d..c11150f 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -7,7 +7,6 @@ use utoipa::ToSchema;
use uuid::Uuid;
/// Default max retries for task daemon failover (3 attempts)
-#[allow(dead_code)]
fn default_max_retries() -> i32 {
3
}
@@ -3051,19 +3050,24 @@ pub struct DirectiveOrderGroupListResponse {
pub total: i64,
}
-/// User setting record from the database (key-value per owner).
+// =============================================================================
+// User Settings Types
+// =============================================================================
+
+/// A user setting (feature flag / preference) stored in the database.
#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UserSetting {
pub id: Uuid,
pub owner_id: Uuid,
pub key: String,
+ #[sqlx(json)]
pub value: serde_json::Value,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
-/// Request body for upserting a user setting.
+/// Request to upsert (create or update) a user setting.
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UpsertUserSettingRequest {
@@ -3071,7 +3075,7 @@ pub struct UpsertUserSettingRequest {
pub value: serde_json::Value,
}
-/// Response containing a list of user settings.
+/// Response wrapping a list of user settings.
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct UserSettingsResponse {
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index 10633d5..5a912e4 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -21,7 +21,6 @@ use super::models::{
PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState,
Task, TaskCheckpoint, TaskEvent, TaskSummary, UpdateContractRequest,
UpdateFileRequest, UpdateTaskRequest, UpdateTemplateRequest,
- UserSetting,
};
/// Repository error types.
@@ -4912,7 +4911,6 @@ pub async fn sync_supervisor_state(
// =============================================================================
/// Helper to truncate string to max length
-#[allow(dead_code)]
fn truncate_string(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
@@ -6700,9 +6698,11 @@ pub async fn get_available_orders_for_dog_pickup(
.await
}
-// ─── User Settings ───────────────────────────────────────────────────────────
+// =============================================================================
+// User Settings
+// =============================================================================
-/// Get all settings for a given owner.
+/// Get all settings for a user.
pub async fn get_user_settings(
pool: &PgPool,
owner_id: Uuid,
@@ -6720,7 +6720,7 @@ pub async fn get_user_settings(
.await
}
-/// Get a single setting by owner and key.
+/// Get a specific setting by key for a user.
pub async fn get_user_setting(
pool: &PgPool,
owner_id: Uuid,
@@ -6739,7 +6739,7 @@ pub async fn get_user_setting(
.await
}
-/// Upsert a user setting (insert or update on conflict).
+/// Upsert (create or update) a user setting.
pub async fn upsert_user_setting(
pool: &PgPool,
owner_id: Uuid,
@@ -6750,8 +6750,9 @@ pub async fn upsert_user_setting(
r#"
INSERT INTO user_settings (owner_id, key, value)
VALUES ($1, $2, $3)
- ON CONFLICT (owner_id, key) DO UPDATE
- SET value = EXCLUDED.value, updated_at = now()
+ ON CONFLICT (owner_id, key) DO UPDATE SET
+ value = EXCLUDED.value,
+ updated_at = NOW()
RETURNING id, owner_id, key, value, created_at, updated_at
"#,
)
@@ -6762,7 +6763,7 @@ pub async fn upsert_user_setting(
.await
}
-/// Delete a user setting. Returns true if a row was deleted.
+/// Delete a user setting by key. Returns true if a row was deleted.
pub async fn delete_user_setting(
pool: &PgPool,
owner_id: Uuid,
@@ -6778,6 +6779,7 @@ pub async fn delete_user_setting(
.bind(key)
.execute(pool)
.await?;
+
Ok(result.rows_affected() > 0)
}
diff --git a/makima/src/listen.rs b/makima/src/listen.rs
index 6391453..91d616c 100644
--- a/makima/src/listen.rs
+++ b/makima/src/listen.rs
@@ -6,7 +6,6 @@ pub use parakeet_rs::{ParakeetEOU, ParakeetTDT, TimedToken, TimestampMode};
use crate::audio;
-#[allow(dead_code)]
const STREAM_CHUNK_MS: u32 = 5_000;
/// A segment of dialogue with speaker identification and timing.
@@ -18,7 +17,6 @@ pub struct DialogueSegment {
pub text: String,
}
-#[allow(dead_code)]
pub(crate) fn listen() -> Result<Vec<DialogueSegment>, Box<dyn std::error::Error>> {
let audio_path = Path::new("audio-ftc.mp3");
diff --git a/makima/src/llm/groq.rs b/makima/src/llm/groq.rs
index 8da9746..ee01fcf 100644
--- a/makima/src/llm/groq.rs
+++ b/makima/src/llm/groq.rs
@@ -83,7 +83,6 @@ struct Choice {
#[derive(Debug, Deserialize)]
struct MessageResponse {
- #[allow(dead_code)]
role: String,
content: Option<String>,
tool_calls: Option<Vec<ToolCallResponse>>,
diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs
index b9ff3fe..1e025c8 100644
--- a/makima/src/orchestration/directive.rs
+++ b/makima/src/orchestration/directive.rs
@@ -442,59 +442,64 @@ impl DirectiveOrchestrator {
let directives = repository::get_directives_needing_replanning(&self.pool).await?;
for directive in directives {
- tracing::info!(
- directive_id = %directive.id,
- title = %directive.title,
- "Directive goal updated — spawning re-planning task"
- );
+ if let Err(e) = async {
+ tracing::info!(
+ directive_id = %directive.id,
+ title = %directive.title,
+ "Directive goal updated — spawning re-planning task"
+ );
- // If directive already has a PR, remove completed steps that were included in it
- if directive.pr_url.is_some() || directive.pr_branch.is_some() {
- match remove_already_merged_steps(&self.pool, directive.id).await {
- Ok(count) if count > 0 => {
- tracing::info!(
- directive_id = %directive.id,
- removed = count,
- "Auto-removed completed steps already included in PR before replanning"
- );
- }
- Err(e) => {
- tracing::warn!(
- directive_id = %directive.id,
- error = %e,
- "Failed to auto-remove merged steps before replanning"
- );
+ // If directive already has a PR, remove completed steps that were included in it
+ if directive.pr_url.is_some() || directive.pr_branch.is_some() {
+ match remove_already_merged_steps(&self.pool, directive.id).await {
+ Ok(count) if count > 0 => {
+ tracing::info!(
+ directive_id = %directive.id,
+ removed = count,
+ "Auto-removed completed steps already included in PR before replanning"
+ );
+ }
+ Err(e) => {
+ tracing::warn!(
+ directive_id = %directive.id,
+ error = %e,
+ "Failed to auto-remove merged steps before replanning"
+ );
+ }
+ _ => {}
}
- _ => {}
}
- }
- let existing_steps =
- repository::list_directive_steps(&self.pool, directive.id).await?;
- let generation =
- repository::get_directive_max_generation(&self.pool, directive.id).await? + 1;
- let goal_history =
- repository::get_directive_goal_history(&self.pool, directive.id, 3).await?;
+ let existing_steps =
+ repository::list_directive_steps(&self.pool, directive.id).await?;
+ let generation =
+ repository::get_directive_max_generation(&self.pool, directive.id).await? + 1;
+ let goal_history =
+ repository::get_directive_goal_history(&self.pool, directive.id, 3).await?;
- let plan =
- build_planning_prompt(&directive, &existing_steps, generation, &goal_history);
+ let plan =
+ build_planning_prompt(&directive, &existing_steps, generation, &goal_history);
- if let Err(e) = self
- .spawn_orchestrator_task(
- directive.id,
- directive.owner_id,
- format!("Re-plan: {}", directive.title),
- plan,
- directive.repository_url.as_deref(),
- directive.base_branch.as_deref(),
- )
- .await
- {
- tracing::warn!(
- directive_id = %directive.id,
- error = %e,
- "Failed to spawn re-planning task"
- );
+ if let Err(e) = self
+ .spawn_orchestrator_task(
+ directive.id,
+ directive.owner_id,
+ format!("Re-plan: {}", directive.title),
+ plan,
+ directive.repository_url.as_deref(),
+ directive.base_branch.as_deref(),
+ )
+ .await
+ {
+ tracing::warn!(
+ directive_id = %directive.id,
+ error = %e,
+ "Failed to spawn re-planning task"
+ );
+ }
+ Ok::<(), anyhow::Error>(())
+ }.await {
+ tracing::warn!(directive_id = %directive.id, error = %e, "Error in re-planning directive — continuing");
}
}
Ok(())
diff --git a/makima/src/server/handlers/listen.rs b/makima/src/server/handlers/listen.rs
index 7edcdfc..e1bc30e 100644
--- a/makima/src/server/handlers/listen.rs
+++ b/makima/src/server/handlers/listen.rs
@@ -208,7 +208,8 @@ async fn handle_socket(socket: WebSocket, state: SharedState) {
audio_offset = 0.0;
finalized_segments.clear();
file_id = None;
- // authenticated_owner_id and target_contract_id are kept from above
+ authenticated_owner_id = authenticated_owner_id; // Keep from above
+ target_contract_id = target_contract_id; // Keep from above
// Reset models for new session
let mut sortformer = ml_models.sortformer.lock().await;
diff --git a/makima/src/server/handlers/mesh.rs b/makima/src/server/handlers/mesh.rs
index d9d40d7..1a5b9c1 100644
--- a/makima/src/server/handlers/mesh.rs
+++ b/makima/src/server/handlers/mesh.rs
@@ -20,7 +20,7 @@ use crate::db::models::{
use crate::db::repository::{self, RepositoryError};
use crate::server::auth::Authenticated;
use crate::server::messages::ApiError;
-use crate::server::state::{DaemonCommand, SharedState, TaskUpdateNotification};
+use crate::server::state::{DaemonCommand, DaemonReauthStatus, SharedState, TaskUpdateNotification};
// =============================================================================
// Authentication Types
diff --git a/makima/src/server/handlers/mesh_daemon.rs b/makima/src/server/handlers/mesh_daemon.rs
index ed1b603..e5f0a81 100644
--- a/makima/src/server/handlers/mesh_daemon.rs
+++ b/makima/src/server/handlers/mesh_daemon.rs
@@ -1627,7 +1627,7 @@ async fn handle_daemon_connection(socket: WebSocket, state: SharedState, auth_re
);
// Broadcast as task output with auth_required type so UI can display the login link
- let _content = format!(
+ let content = format!(
"🔐 Authentication required on daemon{}. Click to login: {}",
hostname.as_ref().map(|h| format!(" ({})", h)).unwrap_or_default(),
login_url
diff --git a/makima/src/server/handlers/mesh_supervisor.rs b/makima/src/server/handlers/mesh_supervisor.rs
index 3adb850..ebde52b 100644
--- a/makima/src/server/handlers/mesh_supervisor.rs
+++ b/makima/src/server/handlers/mesh_supervisor.rs
@@ -1272,7 +1272,7 @@ pub async fn create_branch(
headers: HeaderMap,
Json(request): Json<CreateBranchRequest>,
) -> impl IntoResponse {
- let (supervisor_id, _owner_id) = match verify_supervisor_auth(&state, &headers, None).await {
+ let (supervisor_id, owner_id) = match verify_supervisor_auth(&state, &headers, None).await {
Ok(ids) => ids,
Err(e) => return e.into_response(),
};
diff --git a/makima/src/server/handlers/mod.rs b/makima/src/server/handlers/mod.rs
index b3c433b..4bdb424 100644
--- a/makima/src/server/handlers/mod.rs
+++ b/makima/src/server/handlers/mod.rs
@@ -20,7 +20,6 @@ pub mod mesh_merge;
pub mod mesh_supervisor;
pub mod mesh_ws;
pub mod repository_history;
-pub mod settings;
pub mod speak;
pub mod templates;
pub mod voice;
diff --git a/makima/src/server/handlers/settings.rs b/makima/src/server/handlers/settings.rs
deleted file mode 100644
index ae52d5a..0000000
--- a/makima/src/server/handlers/settings.rs
+++ /dev/null
@@ -1,196 +0,0 @@
-//! HTTP handlers for user settings (feature flags / preferences).
-
-use axum::{
- extract::{Path, State},
- http::StatusCode,
- response::IntoResponse,
- Json,
-};
-
-use crate::db::models::{UpsertUserSettingRequest, UserSettingsResponse};
-use crate::db::repository;
-use crate::server::auth::Authenticated;
-use crate::server::messages::ApiError;
-use crate::server::state::SharedState;
-
-/// List all settings for the authenticated user.
-#[utoipa::path(
- get,
- path = "/api/v1/settings",
- responses(
- (status = 200, description = "User settings", body = UserSettingsResponse),
- (status = 401, description = "Not authenticated", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = [])
- ),
- tag = "Settings"
-)]
-pub async fn list_settings(
- State(state): State<SharedState>,
- Authenticated(user): Authenticated,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match repository::get_user_settings(pool, user.owner_id).await {
- Ok(settings) => Json(UserSettingsResponse { settings }).into_response(),
- Err(e) => {
- tracing::error!("Failed to list user settings: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Get a specific setting by key.
-#[utoipa::path(
- get,
- path = "/api/v1/settings/{key}",
- params(
- ("key" = String, Path, description = "Setting key")
- ),
- responses(
- (status = 200, description = "User setting", body = crate::db::models::UserSetting),
- (status = 401, description = "Not authenticated", body = ApiError),
- (status = 404, description = "Setting not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = [])
- ),
- tag = "Settings"
-)]
-pub async fn get_setting(
- State(state): State<SharedState>,
- Authenticated(user): Authenticated,
- Path(key): Path<String>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match repository::get_user_setting(pool, user.owner_id, &key).await {
- Ok(Some(setting)) => Json(setting).into_response(),
- Ok(None) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", format!("Setting '{}' not found", key))),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to get user setting: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Upsert a user setting (create or update).
-#[utoipa::path(
- put,
- path = "/api/v1/settings",
- request_body = UpsertUserSettingRequest,
- responses(
- (status = 200, description = "Setting upserted", body = crate::db::models::UserSetting),
- (status = 401, description = "Not authenticated", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = [])
- ),
- tag = "Settings"
-)]
-pub async fn upsert_setting(
- State(state): State<SharedState>,
- Authenticated(user): Authenticated,
- Json(req): Json<UpsertUserSettingRequest>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match repository::upsert_user_setting(pool, user.owner_id, &req.key, &req.value).await {
- Ok(setting) => Json(setting).into_response(),
- Err(e) => {
- tracing::error!("Failed to upsert user setting: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
-
-/// Delete a user setting by key.
-#[utoipa::path(
- delete,
- path = "/api/v1/settings/{key}",
- params(
- ("key" = String, Path, description = "Setting key")
- ),
- responses(
- (status = 200, description = "Setting deleted"),
- (status = 401, description = "Not authenticated", body = ApiError),
- (status = 404, description = "Setting not found", body = ApiError),
- (status = 503, description = "Database not configured", body = ApiError),
- (status = 500, description = "Internal server error", body = ApiError),
- ),
- security(
- ("bearer_auth" = [])
- ),
- tag = "Settings"
-)]
-pub async fn delete_setting(
- State(state): State<SharedState>,
- Authenticated(user): Authenticated,
- Path(key): Path<String>,
-) -> impl IntoResponse {
- let Some(ref pool) = state.db_pool else {
- return (
- StatusCode::SERVICE_UNAVAILABLE,
- Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
- )
- .into_response();
- };
-
- match repository::delete_user_setting(pool, user.owner_id, &key).await {
- Ok(true) => StatusCode::OK.into_response(),
- Ok(false) => (
- StatusCode::NOT_FOUND,
- Json(ApiError::new("NOT_FOUND", format!("Setting '{}' not found", key))),
- )
- .into_response(),
- Err(e) => {
- tracing::error!("Failed to delete user setting: {}", e);
- (
- StatusCode::INTERNAL_SERVER_ERROR,
- Json(ApiError::new("DB_ERROR", e.to_string())),
- )
- .into_response()
- }
- }
-}
diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs
index 025ec85..99dc1f3 100644
--- a/makima/src/server/mod.rs
+++ b/makima/src/server/mod.rs
@@ -18,7 +18,7 @@ use tower_http::trace::TraceLayer;
use utoipa::OpenApi;
use utoipa_swagger_ui::SwaggerUi;
-use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contract_discuss, contracts, daemon_download, directives, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, orders, repository_history, settings, speak, templates, transcript_analysis, users, versions};
+use crate::server::handlers::{api_keys, chat, contract_chat, contract_daemon, contract_discuss, contracts, daemon_download, directives, file_ws, files, history, listen, mesh, mesh_chat, mesh_daemon, mesh_merge, mesh_supervisor, mesh_ws, orders, repository_history, speak, templates, transcript_analysis, users, versions};
use crate::server::openapi::ApiDoc;
use crate::server::state::SharedState;
@@ -281,16 +281,7 @@ pub fn make_router(state: SharedState) -> Router {
.route("/timeline", get(history::get_timeline))
// Contract type templates (built-in only)
.route("/contract-types", get(templates::list_contract_types))
- // User settings (feature flags) endpoints
- .route(
- "/user-settings",
- get(settings::list_settings).put(settings::upsert_setting),
- )
- .route(
- "/user-settings/{key}",
- get(settings::get_setting).delete(settings::delete_setting),
- )
- // Settings endpoints
+ // Settings endpoints (static routes first to avoid conflict with /settings/{key})
.route(
"/settings/repository-history",
get(repository_history::list_repository_history),
@@ -303,6 +294,15 @@ pub fn make_router(state: SharedState) -> Router {
"/settings/repository-history/{id}",
axum::routing::delete(repository_history::delete_repository_history),
)
+ // User settings (feature flags / preferences)
+ .route(
+ "/settings",
+ get(settings::list_settings).put(settings::upsert_setting),
+ )
+ .route(
+ "/settings/{key}",
+ get(settings::get_setting).delete(settings::delete_setting),
+ )
.with_state(state);
let swagger = SwaggerUi::new("/swagger-ui")