diff --git a/src/modules/nostr-feed/components/NostrFeed.vue b/src/modules/nostr-feed/components/NostrFeed.vue
index 9c431df..b3dfc22 100644
--- a/src/modules/nostr-feed/components/NostrFeed.vue
+++ b/src/modules/nostr-feed/components/NostrFeed.vue
@@ -99,7 +99,16 @@ const { getDisplayName, fetchProfiles } = useProfiles()
const { getEventReactions, subscribeToReactions, toggleLike } = useReactions()
// Use scheduled events service
-const { getEventsForSpecificDate, getCompletion, toggleComplete, allCompletions } = useScheduledEvents()
+const {
+ getEventsForSpecificDate,
+ getCompletion,
+ getTaskStatus,
+ claimTask,
+ startTask,
+ completeEvent,
+ unclaimTask,
+ allCompletions
+} = useScheduledEvents()
// Selected date for viewing scheduled tasks (defaults to today)
const selectedDate = ref(new Date().toISOString().split('T')[0])
@@ -255,14 +264,40 @@ async function onToggleLike(note: FeedPost) {
}
}
-// Handle scheduled event completion toggle
-async function onToggleComplete(event: ScheduledEvent, occurrence?: string) {
- console.log('🎯 NostrFeed: onToggleComplete called for event:', event.title, 'occurrence:', occurrence)
+// Task action handlers
+async function onClaimTask(event: ScheduledEvent, occurrence?: string) {
+ console.log('👋 NostrFeed: Claiming task:', event.title)
try {
- await toggleComplete(event, occurrence)
- console.log('✅ NostrFeed: toggleComplete succeeded')
+ await claimTask(event, '', occurrence)
} catch (error) {
- console.error('❌ NostrFeed: Failed to toggle event completion:', error)
+ console.error('❌ Failed to claim task:', error)
+ }
+}
+
+async function onStartTask(event: ScheduledEvent, occurrence?: string) {
+ console.log('▶️ NostrFeed: Starting task:', event.title)
+ try {
+ await startTask(event, '', occurrence)
+ } catch (error) {
+ console.error('❌ Failed to start task:', error)
+ }
+}
+
+async function onCompleteTask(event: ScheduledEvent, occurrence?: string) {
+ console.log('✅ NostrFeed: Completing task:', event.title)
+ try {
+ await completeEvent(event, occurrence, '')
+ } catch (error) {
+ console.error('❌ Failed to complete task:', error)
+ }
+}
+
+async function onUnclaimTask(event: ScheduledEvent, occurrence?: string) {
+ console.log('🔙 NostrFeed: Unclaiming task:', event.title)
+ try {
+ await unclaimTask(event, occurrence)
+ } catch (error) {
+ console.error('❌ Failed to unclaim task:', error)
}
}
@@ -514,8 +549,12 @@ function cancelDelete() {
:event="event"
:get-display-name="getDisplayName"
:get-completion="getCompletion"
+ :get-task-status="getTaskStatus"
:admin-pubkeys="adminPubkeys"
- @toggle-complete="onToggleComplete"
+ @claim-task="onClaimTask"
+ @start-task="onStartTask"
+ @complete-task="onCompleteTask"
+ @unclaim-task="onUnclaimTask"
/>
diff --git a/src/modules/nostr-feed/components/ScheduledEventCard.vue b/src/modules/nostr-feed/components/ScheduledEventCard.vue
index dfc48af..d6e4ef1 100644
--- a/src/modules/nostr-feed/components/ScheduledEventCard.vue
+++ b/src/modules/nostr-feed/components/ScheduledEventCard.vue
@@ -15,18 +15,22 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from '@/components/ui/collapsible'
-import { Calendar, MapPin, Clock, CheckCircle } from 'lucide-vue-next'
-import type { ScheduledEvent, EventCompletion } from '../services/ScheduledEventService'
+import { Calendar, MapPin, Clock, CheckCircle, PlayCircle, Hand } from 'lucide-vue-next'
+import type { ScheduledEvent, EventCompletion, TaskStatus } from '../services/ScheduledEventService'
interface Props {
event: ScheduledEvent
getDisplayName: (pubkey: string) => string
getCompletion: (eventAddress: string, occurrence?: string) => EventCompletion | undefined
+ getTaskStatus: (eventAddress: string, occurrence?: string) => TaskStatus | null
adminPubkeys?: string[]
}
interface Emits {
- (e: 'toggle-complete', event: ScheduledEvent, occurrence?: string): void
+ (e: 'claim-task', event: ScheduledEvent, occurrence?: string): void
+ (e: 'start-task', event: ScheduledEvent, occurrence?: string): void
+ (e: 'complete-task', event: ScheduledEvent, occurrence?: string): void
+ (e: 'unclaim-task', event: ScheduledEvent, occurrence?: string): void
}
const props = withDefaults(defineProps
(), {
@@ -53,12 +57,29 @@ const occurrence = computed(() => {
// Check if this is an admin event
const isAdminEvent = computed(() => props.adminPubkeys.includes(props.event.pubkey))
-// Check if event is completed - call function with occurrence for recurring events
-const isCompleted = computed(() => props.getCompletion(eventAddress.value, occurrence.value)?.completed || false)
+// Get current task status
+const taskStatus = computed(() => props.getTaskStatus(eventAddress.value, occurrence.value))
// Check if event is completable (task type)
const isCompletable = computed(() => props.event.eventType === 'task')
+// Get completion data
+const completion = computed(() => props.getCompletion(eventAddress.value, occurrence.value))
+
+// Status badges configuration
+const statusConfig = computed(() => {
+ switch (taskStatus.value) {
+ case 'claimed':
+ return { label: 'Claimed', variant: 'secondary' as const, icon: Hand, color: 'text-blue-600' }
+ case 'in-progress':
+ return { label: 'In Progress', variant: 'default' as const, icon: PlayCircle, color: 'text-orange-600' }
+ case 'completed':
+ return { label: 'Completed', variant: 'secondary' as const, icon: CheckCircle, color: 'text-green-600' }
+ default:
+ return null
+ }
+})
+
// Format the date/time
const formattedDate = computed(() => {
try {
@@ -110,28 +131,102 @@ const formattedTimeRange = computed(() => {
}
})
-// Handle mark complete button click - show confirmation dialog
-function handleMarkComplete() {
- console.log('🔘 Mark Complete button clicked for event:', props.event.title)
+// Action type for confirmation dialog
+const pendingAction = ref<'claim' | 'start' | 'complete' | 'unclaim' | null>(null)
+
+// Handle claim task
+function handleClaimTask() {
+ pendingAction.value = 'claim'
showConfirmDialog.value = true
}
-// Confirm and execute mark complete
-function confirmMarkComplete() {
- console.log('✅ Confirmed mark complete for event:', props.event.title, 'occurrence:', occurrence.value)
- emit('toggle-complete', props.event, occurrence.value)
- showConfirmDialog.value = false
+// Handle start task
+function handleStartTask() {
+ pendingAction.value = 'start'
+ showConfirmDialog.value = true
}
-// Cancel mark complete
-function cancelMarkComplete() {
- showConfirmDialog.value = false
+// Handle complete task
+function handleCompleteTask() {
+ pendingAction.value = 'complete'
+ showConfirmDialog.value = true
}
+
+// Handle unclaim task
+function handleUnclaimTask() {
+ pendingAction.value = 'unclaim'
+ showConfirmDialog.value = true
+}
+
+// Confirm action
+function confirmAction() {
+ if (!pendingAction.value) return
+
+ switch (pendingAction.value) {
+ case 'claim':
+ emit('claim-task', props.event, occurrence.value)
+ break
+ case 'start':
+ emit('start-task', props.event, occurrence.value)
+ break
+ case 'complete':
+ emit('complete-task', props.event, occurrence.value)
+ break
+ case 'unclaim':
+ emit('unclaim-task', props.event, occurrence.value)
+ break
+ }
+
+ showConfirmDialog.value = false
+ pendingAction.value = null
+}
+
+// Cancel action
+function cancelAction() {
+ showConfirmDialog.value = false
+ pendingAction.value = null
+}
+
+// Get dialog content based on pending action
+const dialogContent = computed(() => {
+ switch (pendingAction.value) {
+ case 'claim':
+ return {
+ title: 'Claim Task?',
+ description: `This will mark "${props.event.title}" as claimed by you. You can start working on it later.`,
+ confirmText: 'Claim Task'
+ }
+ case 'start':
+ return {
+ title: 'Start Task?',
+ description: `This will mark "${props.event.title}" as in-progress. Others will see you're actively working on it.`,
+ confirmText: 'Start Task'
+ }
+ case 'complete':
+ return {
+ title: 'Complete Task?',
+ description: `This will mark "${props.event.title}" as completed by you. Other users will be able to see that you completed this task.`,
+ confirmText: 'Mark Complete'
+ }
+ case 'unclaim':
+ return {
+ title: 'Unclaim Task?',
+ description: `This will remove your claim on "${props.event.title}" and make it available for others.`,
+ confirmText: 'Unclaim Task'
+ }
+ default:
+ return {
+ title: '',
+ description: '',
+ confirmText: ''
+ }
+ }
+})
+ :class="{ 'opacity-60': isCompletable && taskStatus === 'completed' }">
@@ -143,26 +238,50 @@ function cancelMarkComplete() {
+ :class="{ 'line-through': isCompletable && taskStatus === 'completed' }">
{{ event.title }}
-
+
-
-
- ✓ {{ getDisplayName(getCompletion(eventAddress, occurrence)!.pubkey) }}
+
+
+
+
+
+
+
+ {{ getDisplayName(completion.pubkey) }}
@@ -200,10 +319,20 @@ function cancelMarkComplete() {
{{ event.description || event.content }}
-
-
- ✓ Completed by {{ getDisplayName(getCompletion(eventAddress, occurrence)!.pubkey) }}
-
- {{ getCompletion(eventAddress, occurrence)!.notes }}
+
+
+
+ ✓ Completed by {{ getDisplayName(completion.pubkey) }}
+ - {{ completion.notes }}
+
+
+ 🔄 In Progress by {{ getDisplayName(completion.pubkey) }}
+ - {{ completion.notes }}
+
+
+ 👋 Claimed by {{ getDisplayName(completion.pubkey) }}
+ - {{ completion.notes }}
+
@@ -211,17 +340,70 @@ function cancelMarkComplete() {
Posted by {{ getDisplayName(event.pubkey) }}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -232,14 +414,14 @@ function cancelMarkComplete() {
diff --git a/src/modules/nostr-feed/composables/useScheduledEvents.ts b/src/modules/nostr-feed/composables/useScheduledEvents.ts
index e56c002..e8e8f42 100644
--- a/src/modules/nostr-feed/composables/useScheduledEvents.ts
+++ b/src/modules/nostr-feed/composables/useScheduledEvents.ts
@@ -1,6 +1,6 @@
import { computed } from 'vue'
import { injectService, SERVICE_TOKENS } from '@/core/di-container'
-import type { ScheduledEventService, ScheduledEvent, EventCompletion } from '../services/ScheduledEventService'
+import type { ScheduledEventService, ScheduledEvent, EventCompletion, TaskStatus } from '../services/ScheduledEventService'
import type { AuthService } from '@/modules/base/auth/auth-service'
import { useToast } from '@/core/composables/useToast'
@@ -64,8 +64,78 @@ export function useScheduledEvents() {
return scheduledEventService.isCompleted(eventAddress)
}
+ /**
+ * Get task status for an event
+ */
+ const getTaskStatus = (eventAddress: string, occurrence?: string): TaskStatus | null => {
+ if (!scheduledEventService) return null
+ return scheduledEventService.getTaskStatus(eventAddress, occurrence)
+ }
+
+ /**
+ * Claim a task
+ */
+ const claimTask = async (event: ScheduledEvent, notes: string = '', occurrence?: string): Promise
=> {
+ if (!scheduledEventService) {
+ toast.error('Scheduled event service not available')
+ return
+ }
+
+ try {
+ await scheduledEventService.claimTask(event, notes, occurrence)
+ toast.success('Task claimed!')
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Failed to claim task'
+ if (message.includes('authenticated')) {
+ toast.error('Please sign in to claim tasks')
+ } else {
+ toast.error(message)
+ }
+ console.error('Failed to claim task:', error)
+ }
+ }
+
+ /**
+ * Start a task (mark as in-progress)
+ */
+ const startTask = async (event: ScheduledEvent, notes: string = '', occurrence?: string): Promise => {
+ if (!scheduledEventService) {
+ toast.error('Scheduled event service not available')
+ return
+ }
+
+ try {
+ await scheduledEventService.startTask(event, notes, occurrence)
+ toast.success('Task started!')
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Failed to start task'
+ toast.error(message)
+ console.error('Failed to start task:', error)
+ }
+ }
+
+ /**
+ * Unclaim a task (remove task status)
+ */
+ const unclaimTask = async (event: ScheduledEvent, occurrence?: string): Promise => {
+ if (!scheduledEventService) {
+ toast.error('Scheduled event service not available')
+ return
+ }
+
+ try {
+ await scheduledEventService.unclaimTask(event, occurrence)
+ toast.success('Task unclaimed')
+ } catch (error) {
+ const message = error instanceof Error ? error.message : 'Failed to unclaim task'
+ toast.error(message)
+ console.error('Failed to unclaim task:', error)
+ }
+ }
+
/**
* Toggle completion status of an event (optionally for a specific occurrence)
+ * DEPRECATED: Use claimTask, startTask, completeEvent, or unclaimTask instead for more granular control
*/
const toggleComplete = async (event: ScheduledEvent, occurrence?: string, notes: string = ''): Promise => {
console.log('🔧 useScheduledEvents: toggleComplete called for event:', event.title, 'occurrence:', occurrence)
@@ -82,19 +152,19 @@ export function useScheduledEvents() {
console.log('📊 useScheduledEvents: Current completion status:', currentlyCompleted)
if (currentlyCompleted) {
- console.log('⬇️ useScheduledEvents: Marking as incomplete...')
- await scheduledEventService.uncompleteEvent(event, occurrence)
- toast.success('Event marked as incomplete')
+ console.log('⬇️ useScheduledEvents: Unclaiming task...')
+ await scheduledEventService.unclaimTask(event, occurrence)
+ toast.success('Task unclaimed')
} else {
console.log('⬆️ useScheduledEvents: Marking as complete...')
await scheduledEventService.completeEvent(event, notes, occurrence)
- toast.success('Event completed!')
+ toast.success('Task completed!')
}
} catch (error) {
const message = error instanceof Error ? error.message : 'Failed to toggle completion'
if (message.includes('authenticated')) {
- toast.error('Please sign in to complete events')
+ toast.error('Please sign in to complete tasks')
} else if (message.includes('Not connected')) {
toast.error('Not connected to relays')
} else {
@@ -108,19 +178,19 @@ export function useScheduledEvents() {
/**
* Complete an event with optional notes
*/
- const completeEvent = async (event: ScheduledEvent, notes: string = ''): Promise => {
+ const completeEvent = async (event: ScheduledEvent, occurrence?: string, notes: string = ''): Promise => {
if (!scheduledEventService) {
toast.error('Scheduled event service not available')
return
}
try {
- await scheduledEventService.completeEvent(event, notes)
- toast.success('Event completed!')
+ await scheduledEventService.completeEvent(event, notes, occurrence)
+ toast.success('Task completed!')
} catch (error) {
- const message = error instanceof Error ? error.message : 'Failed to complete event'
+ const message = error instanceof Error ? error.message : 'Failed to complete task'
toast.error(message)
- console.error('Failed to complete event:', error)
+ console.error('Failed to complete task:', error)
}
}
@@ -147,15 +217,21 @@ export function useScheduledEvents() {
})
return {
- // Methods
+ // Methods - Getters
getScheduledEvents,
getEventsForDate,
getEventsForSpecificDate,
getTodaysEvents,
getCompletion,
isCompleted,
- toggleComplete,
+ getTaskStatus,
+
+ // Methods - Actions
+ claimTask,
+ startTask,
completeEvent,
+ unclaimTask,
+ toggleComplete, // DEPRECATED: Use specific actions instead
// State
isLoading,
diff --git a/src/modules/nostr-feed/services/ScheduledEventService.ts b/src/modules/nostr-feed/services/ScheduledEventService.ts
index 21af01f..7524b01 100644
--- a/src/modules/nostr-feed/services/ScheduledEventService.ts
+++ b/src/modules/nostr-feed/services/ScheduledEventService.ts
@@ -28,14 +28,16 @@ export interface ScheduledEvent {
recurrence?: RecurrencePattern // Optional: for recurring events
}
+export type TaskStatus = 'claimed' | 'in-progress' | 'completed' | 'blocked' | 'cancelled'
+
export interface EventCompletion {
id: string
eventAddress: string // "31922:pubkey:d-tag"
occurrence?: string // ISO date string for the specific occurrence (YYYY-MM-DD)
- pubkey: string // Who completed it
+ pubkey: string // Who claimed/completed it
created_at: number
- completed: boolean
- completedAt?: number
+ taskStatus: TaskStatus
+ completedAt?: number // Unix timestamp when completed
notes: string
}
@@ -158,7 +160,19 @@ export class ScheduledEventService extends BaseService {
return
}
- const completed = event.tags.find(tag => tag[0] === 'completed')?.[1] === 'true'
+ // Parse task status (new approach)
+ const taskStatusTag = event.tags.find(tag => tag[0] === 'task-status')?.[1] as TaskStatus | undefined
+
+ // Backward compatibility: check old 'completed' tag if task-status not present
+ let taskStatus: TaskStatus
+ if (taskStatusTag) {
+ taskStatus = taskStatusTag
+ } else {
+ // Legacy support: convert old 'completed' tag to new taskStatus
+ const completed = event.tags.find(tag => tag[0] === 'completed')?.[1] === 'true'
+ taskStatus = completed ? 'completed' : 'claimed'
+ }
+
const completedAtTag = event.tags.find(tag => tag[0] === 'completed_at')?.[1]
const completedAt = completedAtTag ? parseInt(completedAtTag) : undefined
const occurrence = event.tags.find(tag => tag[0] === 'occurrence')?.[1] // ISO date string
@@ -166,7 +180,7 @@ export class ScheduledEventService extends BaseService {
console.log('📋 Completion details:', {
aTag,
occurrence,
- completed,
+ taskStatus,
pubkey: event.pubkey,
eventId: event.id
})
@@ -177,7 +191,7 @@ export class ScheduledEventService extends BaseService {
occurrence,
pubkey: event.pubkey,
created_at: event.created_at,
- completed,
+ taskStatus,
completedAt,
notes: event.content
}
@@ -189,7 +203,7 @@ export class ScheduledEventService extends BaseService {
const existing = this._completions.get(completionKey)
if (!existing || event.created_at > existing.created_at) {
this._completions.set(completionKey, completion)
- console.log('✅ Stored completion for:', completionKey, '- completed:', completed)
+ console.log('✅ Stored completion for:', completionKey, '- status:', taskStatus)
} else {
console.log('⏭️ Skipped older completion for:', completionKey)
}
@@ -310,15 +324,49 @@ export class ScheduledEventService extends BaseService {
*/
isCompleted(eventAddress: string, occurrence?: string): boolean {
const completion = this.getCompletion(eventAddress, occurrence)
- return completion?.completed || false
+ return completion?.taskStatus === 'completed'
+ }
+
+ /**
+ * Get task status for an event
+ */
+ getTaskStatus(eventAddress: string, occurrence?: string): TaskStatus | null {
+ const completion = this.getCompletion(eventAddress, occurrence)
+ return completion?.taskStatus || null
+ }
+
+ /**
+ * Claim a task (mark as claimed)
+ */
+ async claimTask(event: ScheduledEvent, notes: string = '', occurrence?: string): Promise {
+ await this.updateTaskStatus(event, 'claimed', notes, occurrence)
+ }
+
+ /**
+ * Start a task (mark as in-progress)
+ */
+ async startTask(event: ScheduledEvent, notes: string = '', occurrence?: string): Promise {
+ await this.updateTaskStatus(event, 'in-progress', notes, occurrence)
}
/**
* Mark an event as complete (optionally for a specific occurrence)
*/
async completeEvent(event: ScheduledEvent, notes: string = '', occurrence?: string): Promise {
+ await this.updateTaskStatus(event, 'completed', notes, occurrence)
+ }
+
+ /**
+ * Internal method to update task status
+ */
+ private async updateTaskStatus(
+ event: ScheduledEvent,
+ taskStatus: TaskStatus,
+ notes: string = '',
+ occurrence?: string
+ ): Promise {
if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to complete events')
+ throw new Error('Must be authenticated to update task status')
}
if (!this.relayHub?.isConnected) {
@@ -335,14 +383,17 @@ export class ScheduledEventService extends BaseService {
const eventAddress = `31922:${event.pubkey}:${event.dTag}`
- // Create RSVP/completion event (NIP-52)
+ // Create RSVP event with task-status tag
const tags: string[][] = [
['a', eventAddress],
- ['status', 'accepted'],
- ['completed', 'true'],
- ['completed_at', Math.floor(Date.now() / 1000).toString()]
+ ['task-status', taskStatus]
]
+ // Add completed_at timestamp if task is completed
+ if (taskStatus === 'completed') {
+ tags.push(['completed_at', Math.floor(Date.now() / 1000).toString()])
+ }
+
// Add occurrence tag if provided (for recurring events)
if (occurrence) {
tags.push(['occurrence', occurrence])
@@ -359,17 +410,17 @@ export class ScheduledEventService extends BaseService {
const privkeyBytes = this.hexToUint8Array(userPrivkey)
const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
- // Publish the completion
- console.log('📤 Publishing completion event (kind 31925) for:', eventAddress)
+ // Publish the status update
+ console.log(`📤 Publishing task status update (${taskStatus}) for:`, eventAddress)
const result = await this.relayHub.publishEvent(signedEvent)
- console.log('✅ Completion event published to', result.success, '/', result.total, 'relays')
+ console.log('✅ Task status published to', result.success, '/', result.total, 'relays')
// Optimistically update local state
console.log('🔄 Optimistically updating local state')
this.handleCompletionEvent(signedEvent)
} catch (error) {
- console.error('Failed to complete event:', error)
+ console.error('Failed to update task status:', error)
throw error
} finally {
this._isLoading.value = false
@@ -377,11 +428,13 @@ export class ScheduledEventService extends BaseService {
}
/**
- * Uncomplete an event (publish new RSVP with completed=false)
+ * Unclaim/reset a task (removes task status - makes it unclaimed)
+ * Note: In Nostr, we can't truly "delete" an event, but we can publish
+ * a deletion request (kind 5) to ask relays to remove our RSVP
*/
- async uncompleteEvent(event: ScheduledEvent, occurrence?: string): Promise {
+ async unclaimTask(event: ScheduledEvent, occurrence?: string): Promise {
if (!this.authService?.isAuthenticated?.value) {
- throw new Error('Must be authenticated to uncomplete events')
+ throw new Error('Must be authenticated to unclaim tasks')
}
if (!this.relayHub?.isConnected) {
@@ -397,38 +450,40 @@ export class ScheduledEventService extends BaseService {
this._isLoading.value = true
const eventAddress = `31922:${event.pubkey}:${event.dTag}`
+ const completionKey = occurrence ? `${eventAddress}:${occurrence}` : eventAddress
+ const completion = this._completions.get(completionKey)
- // Create RSVP event with completed=false
- const tags: string[][] = [
- ['a', eventAddress],
- ['status', 'tentative'],
- ['completed', 'false']
- ]
-
- // Add occurrence tag if provided (for recurring events)
- if (occurrence) {
- tags.push(['occurrence', occurrence])
+ if (!completion) {
+ console.log('No completion to unclaim')
+ return
}
- const eventTemplate: EventTemplate = {
- kind: 31925,
- content: '',
- tags,
+ // Create deletion event (kind 5) for the RSVP
+ const deletionEvent: EventTemplate = {
+ kind: 5,
+ content: 'Task unclaimed',
+ tags: [
+ ['e', completion.id], // Reference to the RSVP event being deleted
+ ['k', '31925'] // Kind of event being deleted
+ ],
created_at: Math.floor(Date.now() / 1000)
}
// Sign the event
const privkeyBytes = this.hexToUint8Array(userPrivkey)
- const signedEvent = finalizeEvent(eventTemplate, privkeyBytes)
+ const signedEvent = finalizeEvent(deletionEvent, privkeyBytes)
- // Publish the uncomplete
- await this.relayHub.publishEvent(signedEvent)
+ // Publish the deletion request
+ console.log('📤 Publishing deletion request for task RSVP:', completion.id)
+ const result = await this.relayHub.publishEvent(signedEvent)
+ console.log('✅ Deletion request published to', result.success, '/', result.total, 'relays')
- // Optimistically update local state
- this.handleCompletionEvent(signedEvent)
+ // Optimistically remove from local state
+ this._completions.delete(completionKey)
+ console.log('🗑️ Removed completion from local state:', completionKey)
} catch (error) {
- console.error('Failed to uncomplete event:', error)
+ console.error('Failed to unclaim task:', error)
throw error
} finally {
this._isLoading.value = false