diff --git a/README.md b/README.md index bb624c2..598de65 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ The project is under active development. Core blogging workflows are broadly ava - `bds-cli server` hosting the shared application engines over a loopback-by-default, public-key-only SSH service, with restrictive private key material, live authorization updates, terminal-session transport, CLI-change synchronization, ordered domain/task events, and native desktop remote-project selection. - bDS2-compatible Markdown/Liquid rendering with built-in macros rendered from bundled Liquid templates in isolated scopes (customizable with `macros/*` partial-template slugs), project-description homepage headings, category-controlled list title visibility, descriptive category archive titles, canonical multilingual and flat page routes for every configured blog language, recursive menus, calendar archives, feeds, a root hreflang sitemap, Pagefind, shared cached multicore full-site rendering, change-aware and forced full renders from the native Blog menu/CLI/TUI, and fast route/mtime-based incremental validation whose targeted repair refreshes affected aggregate pages through cancellable section task groups with bDS2-style per-URL progress. - Navigable generated-route preview in the app or system browser, with draft database overlays and published filesystem content. -- Optional one-shot AI translation, description, analysis, taxonomy, and language-detection operations run in background tasks with editor-level waiting indicators, using provider-portable JSON-only requests through independent online and local OpenAI-compatible profiles. Each profile has secure credentials, persistently discovered chat/title/image model selections, explicit tool/vision overrides, chat testing, and restart-persistent status-bar airplane-mode routing. +- Optional one-shot AI translation, description, analysis, taxonomy, and language-detection operations are available immediately after restart and run in background tasks with editor-level waiting indicators, using provider-portable JSON-only requests through independent online and local OpenAI-compatible profiles. Each profile has secure credentials, persistently discovered chat/title/image model selections, explicit tool/vision overrides, chat testing, and restart-persistent status-bar airplane-mode routing. - Persistent conversational AI with safe Markdown, streamed and cancellable responses, model/session/token tracking, bounded project-aware blog tools, and localized conversation management in the Chat workspace. Allowlisted render tools add persistent native cards, charts, forms, lists, metrics, mind maps, tables, and tabs without executing assistant-provided HTML or JavaScript. - SSH-agent-based SCP or rsync publishing. - Integrated Git workflow for each blog project's current repository, with repository initialization, read-only origin discovery, Git LFS image tracking, status and diffs, branch/file history with bDS2-compatible sync-status colors, commits, cancellable fetch/pull/push, and post-pull filesystem reconciliation; network actions respect airplane mode. diff --git a/crates/bds-ui/src/app.rs b/crates/bds-ui/src/app.rs index 7a9b816..f8876b2 100644 --- a/crates/bds-ui/src/app.rs +++ b/crates/bds-ui/src/app.rs @@ -6063,7 +6063,7 @@ impl BdsApp { self.import_editors.clear(); self.chat_editors.clear(); self.tags_view_state = None; - self.settings_state = None; + self.settings_state = Some(self.hydrate_settings_state()); self.style_view_state = None; self.dashboard_state = None; self.site_validation_state = SiteValidationState::default(); @@ -10759,6 +10759,7 @@ mod tests { let mut app = BdsApp::new_for_tests(db, project, temp.path().to_path_buf()); app.sidebar_view = SidebarView::Posts; app.sidebar_visible = false; + app.settings_state = None; let _ = app.dispatch_menu_action(MenuAction::EditPreferences); @@ -10773,6 +10774,33 @@ mod tests { ); } + #[test] + fn fresh_workspace_hydrates_saved_ai_configuration_for_quick_actions() { + let (db, project, temp) = setup(); + ai::save_endpoint( + db.conn(), + &ai::AiEndpointConfig { + kind: ai::AiEndpointKind::Airplane, + url: "http://localhost:11434/v1".to_string(), + model: "llama3.2".to_string(), + api_key: None, + }, + ) + .unwrap(); + + let app = BdsApp::new_for_tests(db, project, temp.path().to_path_buf()); + let settings = app + .settings_state + .as_ref() + .expect("fresh workspace must load persisted AI configuration"); + + assert_eq!( + settings.airplane_ai.endpoint_url, + "http://localhost:11434/v1" + ); + assert_eq!(settings.airplane_ai.chat_model, "llama3.2"); + } + #[test] fn only_transport_loss_closes_an_open_remote_session() { assert!(remote_error_closes_connection("connection_lost")); diff --git a/crates/bds-ui/src/views/modal.rs b/crates/bds-ui/src/views/modal.rs index edfa02d..eb246db 100644 --- a/crates/bds-ui/src/views/modal.rs +++ b/crates/bds-ui/src/views/modal.rs @@ -1275,6 +1275,7 @@ pub fn view( row![ text(format!("{} {}", language.flag_emoji, language.name)) .size(13) + .shaping(Shaping::Advanced) .color(Color::WHITE), Space::with_width(Length::Fill), text(status) diff --git a/specs/ai.allium b/specs/ai.allium index 83389fd..8c3751b 100644 --- a/specs/ai.allium +++ b/specs/ai.allium @@ -366,6 +366,8 @@ invariant EndpointModelPersistence { -- restart. Model dropdowns (settings and chat) are populated from -- the persisted list; it is only overwritten by the next -- successful RefreshEndpointModels for the same kind. + -- Editor AI actions derive availability from this persisted state during + -- initial workspace hydration; opening Settings first is never required. } rule TestEndpointModels { diff --git a/specs/modals.allium b/specs/modals.allium index 103acec..5ea318a 100644 --- a/specs/modals.allium +++ b/specs/modals.allium @@ -126,6 +126,7 @@ value LanguagePickerModal { -- Shown for Translate Post and Translate Media Metadata actions. -- Lists all configured blogLanguages except source language. -- Each row: flag emoji, language name, status badge if translation exists. + -- Flag emoji use fallback-capable text shaping and never render as missing-glyph icons. -- Existing translations show "(draft)" or "(published)" badge. -- Click selects target language and initiates translation flow. -- Cancel closes without action.