diff --git a/app/Actions/Telegram/HandleTelegramUpdateAction.php b/app/Actions/Telegram/HandleTelegramUpdateAction.php index 17e8948..60b10af 100644 --- a/app/Actions/Telegram/HandleTelegramUpdateAction.php +++ b/app/Actions/Telegram/HandleTelegramUpdateAction.php @@ -72,7 +72,7 @@ class HandleTelegramUpdateAction /link CODE: link your Content Machine account /videos: your workspace's most recent videos /posts: your workspace's most recent posts - /notes: your Scratch Pad note count and most recent captures + /notes: your active Scratch Pad note count and most recent ones /note : save a Scratch Pad note /post : create a draft, or send the next photo/voice/audio /approve P-123: approve a generated draft @@ -1182,29 +1182,26 @@ private function recentPosts(TelegramBotConfig $config): string private function recentNotes(TelegramBotConfig $config): string { - $notes = fn () => ScratchpadEntry::query()->where('workspace_id', $config->workspace_id) + // Only active (untriaged) notes count; triaged and dropped ones are filed or gone. + $notes = fn () => ScratchpadEntry::query()->where('workspace_id', $config->workspace_id)->where('status', 'new') ->when($this->voiceTranscriptionId !== null, fn ($query) => $query->whereDoesntHave( 'transcriptions', fn ($transcriptions) => $transcriptions->whereKey($this->voiceTranscriptionId))); $entries = $notes()->orderByDesc('captured_at')->limit(10)->get(); if ($entries->isEmpty()) { - return 'No Scratch Pad captures yet.'; + return 'No active Scratch Pad notes.'; } // The list is capped at 10, so the total comes from the database, never from $entries. - $byStatus = $notes() - ->selectRaw('status, count(*) as aggregate')->groupBy('status')->orderBy('status') - ->pluck('aggregate', 'status'); - $total = (int) $byStatus->sum(); - $breakdown = $byStatus->map(fn ($count, $status) => "{$count} {$status}")->implode(', '); - $header = 'You have '.$total.' Scratch Pad '.Str::plural('note', $total)." ({$breakdown})."; + $total = $notes()->count(); + $header = 'You have '.$total.' active Scratch Pad '.Str::plural('note', $total).'.'; $header .= $total > $entries->count() ? " Latest {$entries->count()}:" : ''; return $header."\n".$entries->map(function (ScratchpadEntry $entry) { $preview = $entry->title ?? $entry->body; $preview = $preview === null ? '(no preview)' : Str::limit($preview, 60); - return "{$entry->kind}: {$preview} ({$entry->status})"; + return "{$entry->kind}: {$preview}"; })->implode("\n"); } diff --git a/tests/Unit/Actions/Scratchpad/GistVoiceNoteActionTest.php b/tests/Unit/Actions/Scratchpad/GistVoiceNoteActionTest.php index b81ced1..2384d5e 100644 --- a/tests/Unit/Actions/Scratchpad/GistVoiceNoteActionTest.php +++ b/tests/Unit/Actions/Scratchpad/GistVoiceNoteActionTest.php @@ -169,7 +169,7 @@ public function test_a_voice_notes_lookup_does_not_count_the_voice_note_itself() $this->transcribe(AiTranscriptionResult::success(text: 'how many notes are in my scratch pad', language: 'en', model: 'whisper-1')); $reply = TelegramOutboundMessage::query()->orderByDesc('id')->first()->chunks[0]; - $this->assertStringStartsWith('You have 1 Scratch Pad note (1 new).', $reply); + $this->assertStringStartsWith('You have 1 active Scratch Pad note.', $reply); $this->assertNull($this->entry->fresh()); $this->assertSame(1, ScratchpadEntry::query()->count()); } diff --git a/tests/Unit/Actions/Telegram/HandleTelegramUpdateActionTest.php b/tests/Unit/Actions/Telegram/HandleTelegramUpdateActionTest.php index ee1add5..5f020d4 100644 --- a/tests/Unit/Actions/Telegram/HandleTelegramUpdateActionTest.php +++ b/tests/Unit/Actions/Telegram/HandleTelegramUpdateActionTest.php @@ -267,7 +267,6 @@ public function test_notes_command_lists_recent_scratchpad_captures_for_the_work $this->assertStringContainsString('text', $this->lastReply()); $this->assertStringContainsString('remember to renew the domain', $this->lastReply()); - $this->assertStringContainsString('new', $this->lastReply()); } public function test_notes_command_leads_with_the_true_total_not_the_capped_list_length() @@ -275,15 +274,19 @@ public function test_notes_command_leads_with_the_true_total_not_the_capped_list $config = TelegramBotConfig::factory()->connected()->create(); $user = User::factory()->create(); TelegramBotLink::factory()->create(['telegram_bot_config_id' => $config->id, 'user_id' => $user->id, 'telegram_user_id' => 1]); - ScratchpadEntry::factory()->for($config->workspace)->count(12)->create(['status' => 'new']); - ScratchpadEntry::factory()->for($config->workspace)->triaged()->count(2)->create(); + ScratchpadEntry::factory()->for($config->workspace)->count(12)->create(['status' => 'new', 'title' => null, 'body' => 'active note']); + ScratchpadEntry::factory()->for($config->workspace)->triaged()->count(2)->create(['captured_at' => now()->addMinute()]); + ScratchpadEntry::factory()->for($config->workspace)->dropped()->count(3)->create(['captured_at' => now()->addMinute()]); ScratchpadEntry::factory()->count(5)->create(); $this->action()->handle($config, $this->update(1, '/notes')); $lines = explode("\n", $this->lastReply()); - $this->assertSame('You have 14 Scratch Pad notes (12 new, 2 triaged). Latest 10:', $lines[0]); + $this->assertSame('You have 12 active Scratch Pad notes. Latest 10:', $lines[0]); $this->assertCount(11, $lines); + foreach (array_slice($lines, 1) as $line) { + $this->assertStringEndsWith(': active note', $line); + } } public function test_notes_command_with_one_note_says_singular_total() @@ -295,7 +298,7 @@ public function test_notes_command_with_one_note_says_singular_total() $this->action()->handle($config, $this->update(1, '/notes')); - $this->assertStringStartsWith("You have 1 Scratch Pad note (1 new).\n", $this->lastReply()); + $this->assertStringStartsWith("You have 1 active Scratch Pad note.\n", $this->lastReply()); } public function test_notes_command_with_none_yet_says_so() @@ -306,7 +309,20 @@ public function test_notes_command_with_none_yet_says_so() $this->action()->handle($config, $this->update(1, '/notes')); - $this->assertSame('No Scratch Pad captures yet.', $this->lastReply()); + $this->assertSame('No active Scratch Pad notes.', $this->lastReply()); + } + + public function test_notes_command_with_only_triaged_or_dropped_notes_says_none_active() + { + $config = TelegramBotConfig::factory()->connected()->create(); + $user = User::factory()->create(); + TelegramBotLink::factory()->create(['telegram_bot_config_id' => $config->id, 'user_id' => $user->id, 'telegram_user_id' => 1]); + ScratchpadEntry::factory()->for($config->workspace)->triaged()->create(); + ScratchpadEntry::factory()->for($config->workspace)->dropped()->create(); + + $this->action()->handle($config, $this->update(1, '/notes')); + + $this->assertSame('No active Scratch Pad notes.', $this->lastReply()); } public function test_clearnotes_command_deletes_recent_untriaged_notes() @@ -768,7 +784,7 @@ public function complete($credential, $systemPrompt, $userContent): AiCompletion $this->assertSame(0, ScratchpadEntry::query()->where('body', 'show me my scratchpad')->count()); $this->assertStringContainsString('remember to renew the domain', $this->lastReply()); - $this->assertStringStartsWith('You have 1 Scratch Pad note (1 new).', $this->lastReply()); + $this->assertStringStartsWith('You have 1 active Scratch Pad note.', $this->lastReply()); } public function test_when_every_provider_fails_the_reply_says_so_before_capturing_anyway()