Major rebranding, i18n, dashboard, shared folders, role capabilities

- Rebrand: new app/agent icons, SKALD.md, warm "paper" CSS palette
  (terracotta accent, --radius tokens, WCAG contrast, reduced-motion),
  updated favicon, tray icon, skaldkonur asset
- i18n: backend crate (i18n.rs, locale column, ui_locale config),
  frontend library (web/lib/i18n.js, I18nMixin, t(key)),
  translation files (web/i18n/), every component wired
- Dashboard: <dashboard-page> replaces old home-page content;
  <app-copilot> becomes the landing page (full/dock layout modes)
- Shared folders: API endpoints (shared_folders.rs), frontend page,
  can_write membership, container mount topology, user_fs routing
- Role capabilities: new db table & authorization seam (data not enums),
  roles.attrs JSON for ui_mode / interface select
- Setup: skald-setup prompts for language + password, sets ui_locale
- General: components migrated to CSS variables, Lit conventions cleanup,
  connectors/catalog/marketplace/approval refactoring
This commit is contained in:
2026-07-18 21:38:42 +01:00
parent 2b35312abd
commit 126886e309
109 changed files with 6228 additions and 1390 deletions
+48 -8
View File
@@ -15,6 +15,9 @@ use sqlx::SqlitePool;
pub struct SharedFolder {
pub id: i64,
pub folder_name: String,
/// What the folder holds — injected into the agent's system context so it
/// knows what to store here and when to read it. Admin-authored (§6).
pub description: String,
pub created_at: String,
}
@@ -59,25 +62,50 @@ pub async fn list_for_user(pool: &SqlitePool, user_id: &str) -> Result<Vec<Share
}
pub async fn list_all(pool: &SqlitePool) -> Result<Vec<SharedFolder>> {
let rows = sqlx::query_as::<_, (i64, String, String)>(
"SELECT id, folder_name, created_at FROM shared_folders ORDER BY folder_name",
let rows = sqlx::query_as::<_, (i64, String, String, String)>(
"SELECT id, folder_name, description, created_at FROM shared_folders ORDER BY folder_name",
)
.fetch_all(pool)
.await?;
Ok(rows
.into_iter()
.map(|(id, folder_name, created_at)| SharedFolder { id, folder_name, created_at })
.map(|(id, folder_name, description, created_at)| SharedFolder {
id,
folder_name,
description,
created_at,
})
.collect())
}
pub async fn get(pool: &SqlitePool, folder_id: i64) -> Result<Option<SharedFolder>> {
let row = sqlx::query_as::<_, (i64, String, String, String)>(
"SELECT id, folder_name, description, created_at FROM shared_folders WHERE id = ?",
)
.bind(folder_id)
.fetch_optional(pool)
.await?;
Ok(row.map(|(id, folder_name, description, created_at)| SharedFolder {
id,
folder_name,
description,
created_at,
}))
}
pub async fn get_by_name(pool: &SqlitePool, folder_name: &str) -> Result<Option<SharedFolder>> {
let row = sqlx::query_as::<_, (i64, String, String)>(
"SELECT id, folder_name, created_at FROM shared_folders WHERE folder_name = ?",
let row = sqlx::query_as::<_, (i64, String, String, String)>(
"SELECT id, folder_name, description, created_at FROM shared_folders WHERE folder_name = ?",
)
.bind(folder_name)
.fetch_optional(pool)
.await?;
Ok(row.map(|(id, folder_name, created_at)| SharedFolder { id, folder_name, created_at }))
Ok(row.map(|(id, folder_name, description, created_at)| SharedFolder {
id,
folder_name,
description,
created_at,
}))
}
/// The members of a folder — the set of users whose containers mount it.
@@ -98,15 +126,27 @@ pub async fn members(pool: &SqlitePool, folder_id: i64) -> Result<Vec<FolderMemb
/// Creates a folder, returning its id. `folder_name` must already be validated as
/// a safe path component (see [`is_valid_folder_name`]).
pub async fn create(pool: &SqlitePool, folder_name: &str) -> Result<i64> {
let id = sqlx::query("INSERT INTO shared_folders (folder_name) VALUES (?)")
pub async fn create(pool: &SqlitePool, folder_name: &str, description: &str) -> Result<i64> {
let id = sqlx::query("INSERT INTO shared_folders (folder_name, description) VALUES (?, ?)")
.bind(folder_name)
.bind(description)
.execute(pool)
.await?
.last_insert_rowid();
Ok(id)
}
/// Updates a folder's description — the agent-facing text. No-op if `folder_id`
/// no longer exists.
pub async fn set_description(pool: &SqlitePool, folder_id: i64, description: &str) -> Result<()> {
sqlx::query("UPDATE shared_folders SET description = ? WHERE id = ?")
.bind(description)
.bind(folder_id)
.execute(pool)
.await?;
Ok(())
}
/// Adds (or updates the capability of) a member. Idempotent on the PK.
pub async fn add_member(
pool: &SqlitePool,