Compare commits

...

2 Commits

Author SHA1 Message Date
Bauer, Georg
5968cf47ba Drop the media translation delete button from the spec; scripting API remains the only delete path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:15:47 +02:00
Bauer, Georg
2bf95bfcd0 Spec script deletion as confirm_delete_modal and cover it with a test.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-22 15:15:31 +02:00
5 changed files with 51 additions and 19 deletions

View File

@@ -12498,6 +12498,37 @@ mod tests {
); );
} }
#[test]
fn script_delete_requires_confirmation_before_removal() {
let (db, project, tmp) = setup();
let mut app = make_app(db, project, &tmp);
let _ = app.update(Message::CreateScript);
let script_id = app.sidebar_scripts[0].id.clone();
let _ = app.update(Message::ScriptEditor(ScriptEditorMsg::Delete));
assert!(
bds_core::db::queries::script::get_script_by_id(
app.db.as_ref().unwrap().conn(),
&script_id
)
.is_ok()
);
assert!(app.tabs.iter().any(|tab| tab.id == script_id));
let _ = app.update(Message::ConfirmModal(modal::ConfirmAction::DeleteScript(
script_id.clone(),
)));
assert!(
bds_core::db::queries::script::get_script_by_id(
app.db.as_ref().unwrap().conn(),
&script_id
)
.is_err()
);
assert!(!app.tabs.iter().any(|tab| tab.id == script_id));
assert!(!app.script_editors.contains_key(&script_id));
}
#[test] #[test]
fn rebuild_completion_refreshes_project_metadata_in_settings() { fn rebuild_completion_refreshes_project_metadata_in_settings() {
let (db, project, tmp) = setup(); let (db, project, tmp) = setup();

View File

@@ -219,11 +219,11 @@ config {
ConfirmationAssignment(action: "tag_merge", pattern: confirm_dialog), ConfirmationAssignment(action: "tag_merge", pattern: confirm_dialog),
-- "Merge N tags into {target}? Cannot be undone." -- "Merge N tags into {target}? Cannot be undone."
ConfirmationAssignment(action: "rebuild_operations", pattern: none), ConfirmationAssignment(action: "rebuild_operations", pattern: none),
ConfirmationAssignment(action: "script_delete", pattern: none), ConfirmationAssignment(action: "script_delete", pattern: confirm_delete_modal),
-- shows script title, no reference list
ConfirmationAssignment(action: "menu_item_delete", pattern: none), ConfirmationAssignment(action: "menu_item_delete", pattern: none),
ConfirmationAssignment(action: "metadata_diff_field_sync", pattern: none), ConfirmationAssignment(action: "metadata_diff_field_sync", pattern: none),
ConfirmationAssignment(action: "import_execute", pattern: none), ConfirmationAssignment(action: "import_execute", pattern: none),
ConfirmationAssignment(action: "media_translation_delete", pattern: none),
ConfirmationAssignment(action: "media_unlink", pattern: none) ConfirmationAssignment(action: "media_unlink", pattern: none)
} }
} }

View File

@@ -105,7 +105,6 @@ surface MediaEditorSurface {
MediaLinkToPostRequested(editor.media_id) MediaLinkToPostRequested(editor.media_id)
MediaTranslationEditClicked(editor.media_id, language) MediaTranslationEditClicked(editor.media_id, language)
MediaTranslationRefreshClicked(editor.media_id, language) MediaTranslationRefreshClicked(editor.media_id, language)
MediaTranslationDeleteClicked(editor.media_id, language)
MediaUnlinkPostRequested(editor.media_id, post_id) MediaUnlinkPostRequested(editor.media_id, post_id)
MediaDeleteConfirmed(editor.media_id) MediaDeleteConfirmed(editor.media_id)
TranslationEditModalSaved(editor.media_id, language, title, alt, caption) TranslationEditModalSaved(editor.media_id, language, title, alt, caption)
@@ -140,7 +139,10 @@ surface MediaEditorSurface {
@guarantee TranslationsSection @guarantee TranslationsSection
-- Shown only when language is set. -- Shown only when language is set.
-- List of existing translations: flag emoji + language name + title. -- List of existing translations: flag emoji + language name + title.
-- Per-translation actions: click to edit (opens modal), refresh button, delete button. -- Per-translation actions: click to edit (opens modal), refresh button.
-- No delete action; translations are only removed via the scripting
-- API (script.allium bds.media.delete_translation) or when the
-- media item itself is deleted.
-- "No translations" message when list is empty. -- "No translations" message when list is empty.
@guarantee TranslationEditModal @guarantee TranslationEditModal
@@ -306,15 +308,3 @@ rule MediaTranslationRefresh {
-- overwrites the existing translation fields and rewrites the -- overwrites the existing translation fields and rewrites the
-- translated sidecar -- translated sidecar
} }
rule MediaTranslationDelete {
when: MediaTranslationDeleteClicked(media_id, language)
let item = media/Media{id: media_id}
let translation = media/MediaTranslation{media: item, language: language}
-- No confirmation dialog (action_patterns.allium
-- confirmation_assignments: media_translation_delete)
ensures: not exists translation
ensures: MediaTranslationDeleted(item, language)
-- engine_side_effects.allium DeleteMediaTranslationSideEffects
-- deletes the translated sidecar {path}.{lang}.meta
}

View File

@@ -43,6 +43,7 @@ surface ScriptEditorSurface {
ScriptRunRequested(editor.script_id) ScriptRunRequested(editor.script_id)
ScriptCheckSyntaxRequested(editor.script_id) ScriptCheckSyntaxRequested(editor.script_id)
ScriptDeleteRequested(editor.script_id) ScriptDeleteRequested(editor.script_id)
ScriptDeleteConfirmed(editor.script_id)
@guarantee HeaderLayout @guarantee HeaderLayout
-- Header bar with script title tab. -- Header bar with script title tab.
@@ -123,11 +124,19 @@ rule ScriptRun {
-- Output panel tab, which auto-opens when not visible -- Output panel tab, which auto-opens when not visible
} }
rule ScriptDelete { rule ScriptDeleteAction {
when: ScriptDeleteRequested(script_id) when: ScriptDeleteRequested(script_id)
let target = script/Script{id: script_id} let target = script/Script{id: script_id}
-- No confirmation dialog (action_patterns.allium ensures: ConfirmDeleteModalOpened(script_id)
-- confirmation_assignments: script_delete -> none) -- custom modal (not a native dialog): script title, no
-- reference list; Cancel + Delete (destructive red style)
-- — action_patterns.allium
-- confirmation_assignments: script_delete
}
rule ScriptDeleteExecute {
when: ScriptDeleteConfirmed(script_id)
let target = script/Script{id: script_id}
ensures: DeleteScriptRequested(target) ensures: DeleteScriptRequested(target)
-- DB record + published script file: script.allium DeleteScript -- DB record + published script file: script.allium DeleteScript
ensures: closeTab(script_id) ensures: closeTab(script_id)

View File

@@ -280,6 +280,8 @@ rule UpsertMediaTranslationSideEffects {
rule DeleteMediaTranslationSideEffects { rule DeleteMediaTranslationSideEffects {
when: MediaTranslationDeleted(media, language) when: MediaTranslationDeleted(media, language)
-- No UI affordance; reached only via the scripting API
-- (script.allium bds.media.delete_translation)
ensures: TranslatedSidecarDeleted(media, language) ensures: TranslatedSidecarDeleted(media, language)
} }