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
+20 -7
View File
@@ -1,7 +1,15 @@
import { html, nothing } from 'lit';
import { LightElement } from '../lib/base.js';
import { t, I18nMixin } from '../lib/i18n.js';
export class AppTopbar extends LightElement {
// Stable per-user avatar color: same user, same hue, everywhere.
function avatarColor(name) {
let h = 0;
for (let i = 0; i < name.length; i++) h = (h * 31 + name.charCodeAt(i)) >>> 0;
return `hsl(${h % 360}, 55%, 52%)`;
}
export class AppTopbar extends I18nMixin(LightElement) {
static properties = {
_theme: { state: true },
_copilotCollapsed: { state: true },
@@ -65,23 +73,28 @@ export class AppTopbar extends LightElement {
return name.charAt(0).toUpperCase();
}
get _avatarColor() {
const name = this._me?.username || '';
return name ? avatarColor(name) : 'var(--accent)';
}
render() {
const isDark = this._theme === 'dark';
return html`
<span class="topbar-title">Skald</span>
<span class="topbar-title">${t('topbar.brand')}</span>
<span class="topbar-spacer"></span>
${this._copilotCollapsed ? html`
<button class="topbar-copilot-btn" title="Open copilot"
<button class="topbar-copilot-btn" title=${t('topbar.open_chat')}
@click=${() => window.dispatchEvent(new CustomEvent('copilot-open'))}>
<i class="bi bi-stars"></i>
</button>
` : ''}
<button class="topbar-theme-btn" title="${isDark ? 'Switch to light mode' : 'Switch to dark mode'}"
<button class="topbar-theme-btn" title="${isDark ? t('topbar.to_light') : t('topbar.to_dark')}"
@click=${() => this._toggleTheme()}>
<i class="bi ${isDark ? 'bi-sun' : 'bi-moon-stars'}"></i>
</button>
<div class="topbar-profile-wrapper">
<button class="topbar-avatar" title="Account" @click=${(e) => this._toggleMenu(e)}>
<button class="topbar-avatar" style="background:${this._avatarColor}" title=${t('topbar.account')} @click=${(e) => this._toggleMenu(e)}>
${this._initial}
</button>
${this._menuOpen ? html`
@@ -91,10 +104,10 @@ export class AppTopbar extends LightElement {
<div class="topbar-dropdown-sub">@${this._me?.username || ''}</div>
</div>
<button class="topbar-dropdown-item" @click=${() => this._goProfile()}>
<i class="bi bi-person"></i> Profile
<i class="bi bi-person"></i> ${t('topbar.profile')}
</button>
<button class="topbar-dropdown-item topbar-dropdown-logout" @click=${() => this._logout()}>
<i class="bi bi-box-arrow-right"></i> Logout
<i class="bi bi-box-arrow-right"></i> ${t('topbar.logout')}
</button>
</div>
` : nothing}