Skip to content

Commit 352a5d5

Browse files
slvnlrtclaude
andcommitted
fix(tags): emit file events on tag delete, refetch files.by_id for inspector
- delete/action.rs: collect affected entry UUIDs before deleting the tag, then emit "file" resource events so the explorer grid updates (removes tag dots). Follows the same pattern as apply and unapply actions. - useRefetchTagQueries: add files.by_id to the refetch list so the Inspector panel updates immediately after tag mutations. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent afc75b1 commit 352a5d5

2 files changed

Lines changed: 75 additions & 3 deletions

File tree

core/src/ops/tags/delete/action.rs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@ use super::output::DeleteTagOutput;
55
use crate::{
66
context::CoreContext,
77
infra::action::{error::ActionError, LibraryAction},
8+
infra::db::entities::{entry, tag, user_metadata, user_metadata_tag},
89
library::Library,
910
ops::tags::TagManager,
1011
};
12+
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
1113
use serde::{Deserialize, Serialize};
1214
use std::sync::Arc;
1315

@@ -31,8 +33,66 @@ impl LibraryAction for DeleteTagAction {
3133
_context: Arc<CoreContext>,
3234
) -> Result<Self::Output, ActionError> {
3335
let db = library.db();
34-
let manager = TagManager::new(Arc::new(db.conn().clone()));
36+
let conn = db.conn();
3537

38+
// Collect affected entry UUIDs BEFORE deleting (same pattern as unapply/action.rs)
39+
let affected_entry_uuids = {
40+
let tag_model = tag::Entity::find()
41+
.filter(tag::Column::Uuid.eq(self.input.tag_id))
42+
.one(conn)
43+
.await
44+
.map_err(|e| ActionError::Internal(format!("DB error: {}", e)))?;
45+
46+
let mut uuids = Vec::new();
47+
if let Some(tag_model) = tag_model {
48+
let umt_records = user_metadata_tag::Entity::find()
49+
.filter(user_metadata_tag::Column::TagId.eq(tag_model.id))
50+
.all(conn)
51+
.await
52+
.map_err(|e| ActionError::Internal(format!("DB error: {}", e)))?;
53+
54+
let um_ids: Vec<i32> = umt_records.iter().map(|r| r.user_metadata_id).collect();
55+
if !um_ids.is_empty() {
56+
let um_records = user_metadata::Entity::find()
57+
.filter(user_metadata::Column::Id.is_in(um_ids))
58+
.all(conn)
59+
.await
60+
.map_err(|e| ActionError::Internal(format!("DB error: {}", e)))?;
61+
62+
// Entry-scoped metadata → direct entry UUIDs
63+
uuids.extend(um_records.iter().filter_map(|um| um.entry_uuid));
64+
65+
// Content-scoped metadata → find all entries with that content
66+
let ci_uuids: Vec<uuid::Uuid> = um_records
67+
.iter()
68+
.filter_map(|um| um.content_identity_uuid)
69+
.collect();
70+
if !ci_uuids.is_empty() {
71+
let cis = crate::infra::db::entities::content_identity::Entity::find()
72+
.filter(
73+
crate::infra::db::entities::content_identity::Column::Uuid
74+
.is_in(ci_uuids.into_iter().map(Some)),
75+
)
76+
.all(conn)
77+
.await
78+
.map_err(|e| ActionError::Internal(format!("DB error: {}", e)))?;
79+
let ci_ids: Vec<i32> = cis.iter().map(|ci| ci.id).collect();
80+
if !ci_ids.is_empty() {
81+
let entries = entry::Entity::find()
82+
.filter(entry::Column::ContentId.is_in(ci_ids.into_iter().map(Some)))
83+
.all(conn)
84+
.await
85+
.map_err(|e| ActionError::Internal(format!("DB error: {}", e)))?;
86+
uuids.extend(entries.iter().filter_map(|e| e.uuid));
87+
}
88+
}
89+
}
90+
}
91+
uuids
92+
};
93+
94+
// Delete the tag and all its relationships (atomic transaction)
95+
let manager = TagManager::new(Arc::new(conn.clone()));
3696
manager
3797
.delete_tag(self.input.tag_id)
3898
.await
@@ -44,18 +104,29 @@ impl LibraryAction for DeleteTagAction {
44104
// tags will reappear on other devices after sync. Tracked for a dedicated
45105
// sync-deletion PR.
46106

47-
// Emit resource event so frontend refreshes tag lists
48107
let resource_manager = crate::domain::ResourceManager::new(
49-
Arc::new(db.conn().clone()),
108+
Arc::new(conn.clone()),
50109
_context.events.clone(),
51110
);
111+
112+
// Emit "tag" event so sidebar refreshes
52113
if let Err(e) = resource_manager
53114
.emit_resource_events("tag", vec![self.input.tag_id])
54115
.await
55116
{
56117
tracing::warn!("Failed to emit tag resource event after deletion: {}", e);
57118
}
58119

120+
// Emit "file" events so explorer grid updates (removes tag dots)
121+
if !affected_entry_uuids.is_empty() {
122+
if let Err(e) = resource_manager
123+
.emit_resource_events("file", affected_entry_uuids)
124+
.await
125+
{
126+
tracing::warn!("Failed to emit file resource events after tag deletion: {}", e);
127+
}
128+
}
129+
59130
Ok(DeleteTagOutput { deleted: true })
60131
}
61132

packages/interface/src/hooks/useRefetchTagQueries.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export function useRefetchTagQueries() {
1111
return useCallback(() => {
1212
queryClient.refetchQueries({ queryKey: ["query:files.directory_listing"], exact: false });
1313
queryClient.refetchQueries({ queryKey: ["query:files.by_tag"], exact: false });
14+
queryClient.refetchQueries({ queryKey: ["query:files.by_id"], exact: false });
1415
queryClient.refetchQueries({ queryKey: ["query:tags.search"], exact: false });
1516
}, [queryClient]);
1617
}

0 commit comments

Comments
 (0)