Chat bubbles, icons for chats, more vivid and distinct colour tags

- Bubbles: own messages right-aligned in their own colour, agent answers left.
- Chat icons (22, validated on the server), shown in list, top bar, project page.
- Colour tags: eight fixed hues (new: pink) with theme-matched lightness and the
  highest chroma sRGB allows; stronger background tint.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Richard ZinkandClaude Sonnet 5.5 committed 2026-11-14 23:31:52 +01:00
1 parent f34a0c7a73
commit ce0d8afe69
13 files changed
+180 -37

No files matched your search

+11 -1
View File
@@ -647,6 +647,7 @@ async fn create_session(Extension(user): Extension<User>, State(app): State<AppS
telegram_quiet: true,
summary: String::new(),
color: dstr("color").filter(|c| codecraft_core::types::SESSION_COLORS.contains(&c.as_str())),
icon: dstr("icon").filter(|c| codecraft_core::types::SESSION_ICONS.contains(&c.as_str())),
};
let s = app.save_session(s)?;
Ok(Json(json!(s)))
@@ -942,6 +943,8 @@ struct UpdateSession {
telegram_quiet: Option<bool>,
/// Colour tag ("" removes it).
color: Option<String>,
/// Icon ("" removes it).
icon: Option<String>,
}
async fn update_session(Extension(user): Extension<User>, State(app): State<AppState>, Path(id): Path<String>, Json(body): Json<UpdateSession>) -> ApiResult {
@@ -957,6 +960,9 @@ async fn update_session(Extension(user): Extension<User>, State(app): State<AppS
if body.color.as_deref().is_some_and(|c| !c.is_empty() && !codecraft_core::types::SESSION_COLORS.contains(&c)) {
return Err(ApiError(StatusCode::BAD_REQUEST, "invalid colour".into()));
}
if body.icon.as_deref().is_some_and(|c| !c.is_empty() && !codecraft_core::types::SESSION_ICONS.contains(&c)) {
return Err(ApiError(StatusCode::BAD_REQUEST, "invalid icon".into()));
}
if body.permission_mode.as_deref().is_some_and(|p| !crate::permission::MODES.contains(&p)) {
return Err(ApiError(StatusCode::BAD_REQUEST, "invalid permission mode".into()));
}
@@ -999,6 +1005,9 @@ async fn update_session(Extension(user): Extension<User>, State(app): State<AppS
if let Some(c) = body.color {
s.color = (!c.is_empty()).then_some(c);
}
if let Some(i) = body.icon {
s.icon = (!i.is_empty()).then_some(i);
}
})?;
Ok(Json(json!(s)))
}
@@ -1454,7 +1463,7 @@ async fn put_project_defaults(Extension(user): Extension<User>, State(app): Stat
let s = writable(&app, &user, &id)?;
let data = json!({
"model": s.model, "agent": s.agent, "permission_mode": s.permission_mode,
"color": s.color, "instructions": s.instructions, "folder": s.folder,
"color": s.color, "icon": s.icon, "instructions": s.instructions, "folder": s.folder,
});
app.store.set_project_defaults(&s.cwd, Some(&data))?;
Ok(Json(data))
@@ -2145,6 +2154,7 @@ async fn job_assistant(Extension(user): Extension<User>, State(app): State<AppSt
telegram_quiet: true,
summary: String::new(),
color: None,
icon: None,
};
let s = app.save_session(s)?;
st.assistant_session = Some(s.id.clone());
+1
View File
@@ -323,6 +323,7 @@ pub async fn run_with(app: &Arc<App>, job: &Job, trigger: &str, payload: Option<
telegram_quiet: true,
summary: String::new(),
color: None,
icon: None,
};
let session = app.save_session(session)?;
app.store.job_started(&job.id, &session.id, now)?;
+1
View File
@@ -899,6 +899,7 @@ fn create_child(app: &Arc<App>, ctx: &ToolCtx, agent_name: &str, description: &s
telegram_quiet: false,
summary: String::new(),
color: None,
icon: None,
};
let child = app.save_session(child)?;
let msg = Message::new(Role::User, vec![Part::Text { text: prompt }]);
+1
View File
@@ -543,6 +543,7 @@ async fn new_session(app: &Arc<App>, u: &User, bot: &Bot, chat: i64, arg: &str,
telegram_quiet: false,
summary: String::new(),
color: None,
icon: None,
})?;
update(app, &u.id, |st| st.session = Some(s.id.clone()));
bot.send(chat, &format!("{} <b>{}</b> · {} · {}", tr(de, "Neue Sitzung", "New session"), esc(&s.cwd), esc(&s.agent), esc(&s.model)), None).await?;