fix: project reload on rebuild and syntax check now gives toast

This commit is contained in:
2026-07-19 12:07:10 +02:00
parent 422f71c8ad
commit 9dab0ca57e
11 changed files with 119 additions and 13 deletions

View File

@@ -46,6 +46,7 @@ Invariants and behaviours in the allium spec should be covered by unit tests of
- there are no "pre-existing" problems - you own every problem, you fix every problem
- don't leave unused code in the codebase, remove it instead
- after implementing / changing things, run the build and run tests to verify all works
- run `cargo test --workspace` with permission for its loopback test servers on the first attempt; AI mock-server and preview-server tests bind localhost and otherwise fail under sandboxing, causing a pointless rerun
- do not reference external JavaScript or CSS on CDNs, always bring it into the project
- do not embedd CSS/JavaScript into HTML, always reference .css and .js files in the project assets
- always make sure you follow proper i18n best practices. no untranslated string constants.

View File

@@ -9,7 +9,7 @@ The project is under active development. Core blogging workflows are broadly ava
- Native Iced desktop workspace with localized menus, tabs, sidebars, dialogs, tasks, and embedded Wry previews.
- Post and translation authoring with draft/published lifecycle, metadata, tags, categories, links, media, and batch gallery-image import.
- Media import, thumbnails, metadata translations, filters, validation, and post assignment.
- Template and Lua script management using a custom Ropey/Syntect/Cosmic Text editor and the documented, bDS2-signature-compatible project-scoped [`bds` host API](docs/scripting/API_REFERENCE.md) across utilities, rendered macros, and Blogmark transforms.
- Template and Lua script management with explicit syntax-check feedback, using a custom Ropey/Syntect/Cosmic Text editor and the documented, bDS2-signature-compatible project-scoped [`bds` host API](docs/scripting/API_REFERENCE.md) across utilities, rendered macros, and Blogmark transforms.
- SQLite and filesystem persistence with frontmatter, sidecars, rebuild, metadata diff/repair, and FTS5 search.
- Markdown/Liquid rendering with native macros, multilingual routes, feeds, sitemap, Pagefind, and incremental site generation through cancellable section task groups.
- Local preview in the app or system browser.

View File

@@ -200,6 +200,7 @@ pub enum Message {
ApplySiteValidation,
EngineTaskDone {
task_id: TaskId,
operation: &'static str,
label: String,
result: Result<String, String>,
},
@@ -6299,11 +6300,12 @@ mod tests {
save_template_editor_state_impl,
};
use crate::i18n::t;
use crate::state::ToastLevel;
use crate::state::sidebar_filter::{MediaFilter, PostFilter};
use crate::views::media_editor::{MediaEditorMsg, MediaEditorState};
use crate::views::modal;
use crate::views::post_editor::{PostEditorMsg, PostEditorState};
use crate::views::script_editor::ScriptEditorState;
use crate::views::script_editor::{ScriptEditorMsg, ScriptEditorState};
use crate::views::settings_view::SettingsViewState;
use crate::views::template_editor::TemplateEditorState;
use bds_core::db::Database;
@@ -7704,6 +7706,66 @@ mod tests {
assert!(editor.content.contains("new script"));
}
#[test]
fn script_syntax_check_reports_success_and_failure() {
let (db, project, tmp) = setup();
let mut app = make_app(db, project, &tmp);
let _ = app.update(Message::CreateScript);
let _ = app.update(Message::ScriptEditor(ScriptEditorMsg::CheckSyntax));
assert_eq!(
app.toasts.last().map(|toast| toast.message.as_str()),
Some(t(UiLocale::En, "editor.syntaxValid").as_str())
);
let _ = app.update(Message::ScriptEditor(ScriptEditorMsg::ContentChanged(
"function render(".to_string(),
)));
let _ = app.update(Message::ScriptEditor(ScriptEditorMsg::CheckSyntax));
assert_eq!(
app.toasts.last().map(|toast| toast.level),
Some(ToastLevel::Error)
);
}
#[test]
fn rebuild_completion_refreshes_project_metadata_in_settings() {
let (db, project, tmp) = setup();
let mut app = make_app(db, project, &tmp);
app.settings_state = Some(app.hydrate_settings_state());
let mut rebuilt = bds_core::db::queries::project::get_project_by_id(
app.db.as_ref().unwrap().conn(),
&app.active_project.as_ref().unwrap().id,
)
.unwrap();
rebuilt.description = Some("Description from project.json".to_string());
bds_core::db::queries::project::update_project(app.db.as_ref().unwrap().conn(), &rebuilt)
.unwrap();
let label = t(UiLocale::En, "engine.rebuildStarted");
let task_id = app.task_manager.submit(&label);
let _ = app.update(Message::EngineTaskDone {
task_id,
operation: "engine.rebuildStarted",
label,
result: Ok("done".to_string()),
});
assert_eq!(
app.active_project
.as_ref()
.and_then(|project| project.description.as_deref()),
Some("Description from project.json")
);
assert_eq!(
app.settings_state
.as_ref()
.map(|state| state.project_description.text()),
Some("Description from project.json\n".to_string())
);
}
#[test]
fn create_template_opens_editor_and_refreshes_sidebar() {
let (db, project, tmp) = setup();

View File

@@ -647,6 +647,7 @@ impl BdsApp {
pub(super) fn handle_script_editor_msg(&mut self, msg: ScriptEditorMsg) -> Task<Message> {
let mut run_request = None;
let mut syntax_notification = None;
if let Some(tab_id) = self.active_tab.clone()
&& let Some(state) = self.script_editors.get_mut(&tab_id)
{
@@ -680,14 +681,20 @@ impl BdsApp {
return self.save_script_editor(&tab_id);
}
ScriptEditorMsg::CheckSyntax => {
if let Some(st) = self.script_editors.get_mut(&tab_id) {
match engine::script::validate_script_syntax(&st.content) {
Ok(()) => {
st.validation_error = None;
}
Err(e) => {
st.validation_error = Some(e);
}
match engine::script::validate_script_syntax(&state.content) {
Ok(()) => {
state.validation_error = None;
syntax_notification = Some((
ToastLevel::Success,
t(self.ui_locale, "editor.syntaxValid"),
));
}
Err(error) => {
state.validation_error = Some(error.clone());
syntax_notification = Some((
ToastLevel::Error,
tw(self.ui_locale, "editor.syntaxInvalid", &[("error", &error)]),
));
}
}
}
@@ -713,6 +720,9 @@ impl BdsApp {
tab.is_dirty = st.is_dirty;
}
}
if let Some((level, message)) = syntax_notification {
self.notify(level, &message);
}
if let Some((source, entrypoint, kind)) = run_request {
let offline_mode = self.offline_mode;
let app_handler = crate::platform::script_host::handler(

View File

@@ -213,6 +213,7 @@ impl BdsApp {
Message::ApplySiteValidation => self.apply_site_validation(),
Message::EngineTaskDone {
task_id,
operation,
label,
result,
} => {
@@ -222,6 +223,26 @@ impl BdsApp {
_ if cancelled => {}
Ok(detail) => {
self.task_manager.complete(task_id);
if operation == "engine.rebuildStarted" {
let refreshed = self.db.as_ref().and_then(|db| {
let id = &self.active_project.as_ref()?.id;
bds_core::db::queries::project::get_project_by_id(db.conn(), id)
.ok()
});
if let Some(project) = refreshed {
if let Some(cached) = self
.projects
.iter_mut()
.find(|cached| cached.id == project.id)
{
*cached = project.clone();
}
self.active_project = Some(project);
if self.settings_state.is_some() {
self.settings_state = Some(self.hydrate_settings_state());
}
}
}
self.notify(ToastLevel::Success, &format!("{label}: {detail}"));
}
Err(err) => {

View File

@@ -314,6 +314,7 @@ impl BdsApp {
},
move |result| Message::EngineTaskDone {
task_id,
operation: "engine.reindexStarted",
label: label_for_message.clone(),
result,
},
@@ -321,7 +322,7 @@ impl BdsApp {
}
/// Spawn a blocking engine operation on a background thread via TaskManager.
pub(super) fn spawn_engine_task<F>(&mut self, label_key: &str, work: F) -> Task<Message>
pub(super) fn spawn_engine_task<F>(&mut self, label_key: &'static str, work: F) -> Task<Message>
where
F: FnOnce(PathBuf, String, PathBuf, Arc<TaskManager>, TaskId) -> Result<String, String>
+ Send
@@ -332,7 +333,7 @@ impl BdsApp {
pub(super) fn spawn_grouped_engine_task<F>(
&mut self,
label_key: &str,
label_key: &'static str,
group_name: &str,
work: F,
) -> Task<Message>
@@ -346,7 +347,7 @@ impl BdsApp {
pub(super) fn spawn_engine_task_in_group<F>(
&mut self,
label_key: &str,
label_key: &'static str,
group_name: Option<&str>,
work: F,
) -> Task<Message>
@@ -391,6 +392,7 @@ impl BdsApp {
},
move |result| Message::EngineTaskDone {
task_id,
operation: label_key,
label: label_for_msg.clone(),
result,
},

View File

@@ -274,6 +274,8 @@ editor-discard = Verwerfen
editor-validate = Validieren
editor-run = Ausführen
editor-checkSyntax = Syntax prüfen
editor-syntaxValid = Syntax ist gültig.
editor-syntaxInvalid = Syntaxfehler: { $error }
editor-kind = Art
editor-enabled = Aktiviert
editor-entrypoint = Einstiegspunkt

View File

@@ -265,6 +265,8 @@ editor-discard = Discard
editor-validate = Validate
editor-run = Run
editor-checkSyntax = Check Syntax
editor-syntaxValid = Syntax is valid.
editor-syntaxInvalid = Syntax error: { $error }
editor-kind = Kind
editor-enabled = Enabled
editor-entrypoint = Entrypoint

View File

@@ -274,6 +274,8 @@ editor-discard = Descartar
editor-validate = Validar
editor-run = Ejecutar
editor-checkSyntax = Verificar sintaxis
editor-syntaxValid = La sintaxis es válida.
editor-syntaxInvalid = Error de sintaxis: { $error }
editor-kind = Tipo
editor-enabled = Habilitado
editor-entrypoint = Punto de entrada

View File

@@ -274,6 +274,8 @@ editor-discard = Annuler les modifications
editor-validate = Valider
editor-run = Exécuter
editor-checkSyntax = Vérifier la syntaxe
editor-syntaxValid = La syntaxe est valide.
editor-syntaxInvalid = Erreur de syntaxe : { $error }
editor-kind = Type
editor-enabled = Activé
editor-entrypoint = Point d'entrée

View File

@@ -274,6 +274,8 @@ editor-discard = Scarta
editor-validate = Valida
editor-run = Esegui
editor-checkSyntax = Controlla sintassi
editor-syntaxValid = La sintassi è valida.
editor-syntaxInvalid = Errore di sintassi: { $error }
editor-kind = Tipo
editor-enabled = Abilitato
editor-entrypoint = Punto di ingresso