From 4274b69018e15a46537c76645d13e4796f3e8cf3 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Fri, 7 Aug 2026 01:23:09 +0200 Subject: [PATCH 01/71] 113 - Promote article routing above the headline --- resources/views/livewire/articles.blade.php | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/resources/views/livewire/articles.blade.php b/resources/views/livewire/articles.blade.php index 6e2930fd..7a92e7cb 100644 --- a/resources/views/livewire/articles.blade.php +++ b/resources/views/livewire/articles.blade.php @@ -71,20 +71,25 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-
+
+ + + + {{ $routeArticle->feed?->name ?? 'Unknown feed' }} + + {{ $routeArticle->platformChannel?->name ?? 'Unknown channel' }} +

{{ $routeArticle->article->title ?? 'Untitled Article' }}

{{ $routeArticle->article->description ?? 'No description available' }}

-
- - - - - {{ $routeArticle->feed?->name ?? 'Unknown' }} → {{ $routeArticle->platformChannel?->name ?? 'Unknown' }} - - {{ $routeArticle->created_at->format('M d, Y H:i') }} +
+ {{ $routeArticle->created_at->format('M d, Y H:i') }}
-- 2.45.2 From e0056864af61f6081248582062b4b99ea7b38048 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Fri, 7 Aug 2026 02:14:58 +0200 Subject: [PATCH 02/71] 113 - Add per-feed colour dots to article routing --- app/Enums/FeedColorEnum.php | 36 +++++++++++++++++++ app/Models/Feed.php | 9 +++++ ..._01_01_000016_add_color_to_feeds_table.php | 22 ++++++++++++ resources/views/livewire/articles.blade.php | 6 ++-- tailwind.config.js | 11 ++++++ tests/Unit/Models/FeedTest.php | 34 +++++++++++++++++- 6 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 app/Enums/FeedColorEnum.php create mode 100644 database/migrations/2024_01_01_000016_add_color_to_feeds_table.php diff --git a/app/Enums/FeedColorEnum.php b/app/Enums/FeedColorEnum.php new file mode 100644 index 00000000..96ce6721 --- /dev/null +++ b/app/Enums/FeedColorEnum.php @@ -0,0 +1,36 @@ + 'bg-slate-500', + self::AMBER => 'bg-amber-500', + self::SKY => 'bg-sky-500', + self::VIOLET => 'bg-violet-500', + self::TEAL => 'bg-teal-500', + self::ROSE => 'bg-rose-500', + self::INDIGO => 'bg-indigo-500', + self::ORANGE => 'bg-orange-500', + }; + } + + public static function forId(int $id): self + { + $cases = self::cases(); + + return $cases[$id % count($cases)]; + } +} diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 51537ae0..f5f3d9be 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Enums\FeedColorEnum; use Database\Factories\FeedFactory; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -20,6 +21,7 @@ * @property int|null $language_id * @property Language|null $language * @property string $description + * @property FeedColorEnum|null $color * @property array $settings * @property bool $is_active * @property Carbon|null $last_fetched_at @@ -46,6 +48,7 @@ class Feed extends Model 'provider', 'language_id', 'description', + 'color', 'settings', 'is_active', 'last_fetched_at', @@ -55,8 +58,14 @@ class Feed extends Model 'settings' => 'array', 'is_active' => 'boolean', 'last_fetched_at' => 'datetime', + 'color' => FeedColorEnum::class, ]; + public function displayColor(): FeedColorEnum + { + return $this->color ?? FeedColorEnum::forId($this->id); + } + public function getTypeDisplayAttribute(): string { return match ($this->type) { diff --git a/database/migrations/2024_01_01_000016_add_color_to_feeds_table.php b/database/migrations/2024_01_01_000016_add_color_to_feeds_table.php new file mode 100644 index 00000000..c06f6f25 --- /dev/null +++ b/database/migrations/2024_01_01_000016_add_color_to_feeds_table.php @@ -0,0 +1,22 @@ +string('color')->nullable()->after('description'); + }); + } + + public function down(): void + { + Schema::table('feeds', function (Blueprint $table) { + $table->dropColumn('color'); + }); + } +}; diff --git a/resources/views/livewire/articles.blade.php b/resources/views/livewire/articles.blade.php index 7a92e7cb..49ec9be8 100644 --- a/resources/views/livewire/articles.blade.php +++ b/resources/views/livewire/articles.blade.php @@ -75,9 +75,9 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline- class="flex items-center flex-wrap gap-x-1.5 text-xs font-medium mb-1.5" aria-label="Routed from {{ $routeArticle->feed?->name ?? 'Unknown feed' }} to {{ $routeArticle->platformChannel?->name ?? 'Unknown channel' }}" > - - - + @if ($routeArticle->feed) + + @endif {{ $routeArticle->feed?->name ?? 'Unknown feed' }} {{ $routeArticle->platformChannel?->name ?? 'Unknown channel' }} diff --git a/tailwind.config.js b/tailwind.config.js index 94ed3525..ce93ead3 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -7,6 +7,17 @@ export default { './vendor/livewire/livewire/dist/livewire.esm.js', './storage/framework/views/*.php', ], + // Feed dot colours are built in PHP; nothing here is reachable by the scanner. + safelist: [ + 'bg-slate-500', + 'bg-amber-500', + 'bg-sky-500', + 'bg-violet-500', + 'bg-teal-500', + 'bg-rose-500', + 'bg-indigo-500', + 'bg-orange-500', + ], theme: { extend: {}, }, diff --git a/tests/Unit/Models/FeedTest.php b/tests/Unit/Models/FeedTest.php index 274659c6..62e4812e 100644 --- a/tests/Unit/Models/FeedTest.php +++ b/tests/Unit/Models/FeedTest.php @@ -2,6 +2,7 @@ namespace Tests\Unit\Models; +use App\Enums\FeedColorEnum; use App\Models\Article; use App\Models\Feed; use App\Models\Language; @@ -17,12 +18,43 @@ class FeedTest extends TestCase public function test_fillable_fields(): void { - $fillableFields = ['name', 'url', 'type', 'provider', 'language_id', 'description', 'settings', 'is_active', 'last_fetched_at']; + $fillableFields = ['name', 'url', 'type', 'provider', 'language_id', 'description', 'color', 'settings', 'is_active', 'last_fetched_at']; $feed = new Feed; $this->assertEquals($fillableFields, $feed->getFillable()); } + public function test_display_color_falls_back_to_a_deterministic_color(): void + { + $feed = Feed::factory()->create(['color' => null]); + + $this->assertSame(FeedColorEnum::forId($feed->id), $feed->displayColor()); + $this->assertSame(FeedColorEnum::forId($feed->id), $feed->fresh()->displayColor()); + } + + public function test_display_color_prefers_the_stored_color(): void + { + $feed = Feed::factory()->create(['color' => FeedColorEnum::ROSE]); + + $this->assertSame(FeedColorEnum::ROSE, $feed->fresh()->displayColor()); + } + + public function test_color_fallback_wraps_around_the_palette(): void + { + $paletteSize = count(FeedColorEnum::cases()); + + $this->assertSame(FeedColorEnum::forId(0), FeedColorEnum::forId($paletteSize)); + } + + public function test_every_palette_dot_class_is_safelisted_for_tailwind(): void + { + $config = (string) file_get_contents(base_path('tailwind.config.js')); + + foreach (FeedColorEnum::cases() as $color) { + $this->assertStringContainsString("'{$color->dotClass()}'", $config); + } + } + public function test_casts_settings_to_array(): void { $settings = ['key1' => 'value1', 'key2' => ['nested' => 'value']]; -- 2.45.2 From 03282d24639159277632f82b4a118cff07e45acd Mon Sep 17 00:00:00 2001 From: myrmidex Date: Sun, 9 Aug 2026 02:19:31 +0200 Subject: [PATCH 03/71] 113 - Collapse pending articles into per-feed accordions --- app/Livewire/Articles.php | 75 +++++++++-- app/Support/PendingFeedGroup.php | 24 ++++ .../components/route-article-card.blade.php | 93 +++++++++++++ resources/views/livewire/articles.blade.php | 127 +++++------------- tests/Feature/Livewire/ArticlesTest.php | 126 ++++++++++++++++- 5 files changed, 342 insertions(+), 103 deletions(-) create mode 100644 app/Support/PendingFeedGroup.php create mode 100644 resources/views/components/route-article-card.blade.php diff --git a/app/Livewire/Articles.php b/app/Livewire/Articles.php index 2d26538f..1375748e 100644 --- a/app/Livewire/Articles.php +++ b/app/Livewire/Articles.php @@ -4,8 +4,12 @@ use App\Enums\ApprovalStatusEnum; use App\Jobs\ArticleDiscoveryJob; +use App\Models\Feed; use App\Models\RouteArticle; +use App\Support\PendingFeedGroup; use Illuminate\Contracts\View\View; +use Illuminate\Database\Eloquent\Collection as EloquentCollection; +use Illuminate\Support\Collection; use Livewire\Component; use Livewire\WithPagination; @@ -19,13 +23,28 @@ class Articles extends Component public bool $isRefreshing = false; + /** @var array */ + public array $expandedFeeds = []; + public function setTab(string $tab): void { $this->tab = $tab; $this->search = ''; + $this->expandedFeeds = []; $this->resetPage(); } + public function toggleFeed(int $feedId): void + { + if (isset($this->expandedFeeds[$feedId])) { + unset($this->expandedFeeds[$feedId]); + + return; + } + + $this->expandedFeeds[$feedId] = true; + } + public function updatedSearch(): void { $this->resetPage(); @@ -64,12 +83,20 @@ public function refresh(): void public function render(): View { + $pendingCount = RouteArticle::where('approval_status', ApprovalStatusEnum::PENDING)->count(); + + if ($this->tab === 'pending') { + return view('livewire.articles', [ + 'routeArticles' => null, + 'pendingFeeds' => $this->pendingFeeds(), + 'pendingCount' => $pendingCount, + ])->layout('layouts.app'); + } + $query = RouteArticle::with(['article.feed', 'feed', 'platformChannel']) ->orderBy('created_at', 'desc'); - if ($this->tab === 'pending') { - $query->where('approval_status', ApprovalStatusEnum::PENDING); - } elseif ($this->search !== '') { + if ($this->search !== '') { $search = $this->search; $query->whereHas('article', function ($q) use ($search) { $q->where('title', 'like', "%{$search}%") @@ -77,13 +104,45 @@ public function render(): View }); } - $routeArticles = $query->paginate(15); - - $pendingCount = RouteArticle::where('approval_status', ApprovalStatusEnum::PENDING)->count(); - return view('livewire.articles', [ - 'routeArticles' => $routeArticles, + 'routeArticles' => $query->paginate(15), + 'pendingFeeds' => null, 'pendingCount' => $pendingCount, ])->layout('layouts.app'); } + + /** + * @return Collection + */ + private function pendingFeeds(): Collection + { + $counts = RouteArticle::query() + ->selectRaw('feed_id, COUNT(*) as aggregate') + ->where('approval_status', ApprovalStatusEnum::PENDING) + ->groupBy('feed_id') + ->pluck('aggregate', 'feed_id'); + + return Feed::whereIn('id', $counts->keys())->get() + ->map(fn (Feed $feed): PendingFeedGroup => new PendingFeedGroup( + $feed, + (int) $counts->get($feed->id, 0), + isset($this->expandedFeeds[$feed->id]) + ? $this->routeArticlesForFeed($feed->id) + : null, + )) + ->sortByDesc('count') + ->values(); + } + + /** + * @return EloquentCollection + */ + private function routeArticlesForFeed(int $feedId): EloquentCollection + { + return RouteArticle::with(['article.feed', 'feed', 'platformChannel']) + ->where('approval_status', ApprovalStatusEnum::PENDING) + ->where('feed_id', $feedId) + ->orderBy('created_at', 'desc') + ->get(); + } } diff --git a/app/Support/PendingFeedGroup.php b/app/Support/PendingFeedGroup.php new file mode 100644 index 00000000..d07019c6 --- /dev/null +++ b/app/Support/PendingFeedGroup.php @@ -0,0 +1,24 @@ +|null $routeArticles + */ + public function __construct( + public readonly Feed $feed, + public readonly int $count, + public readonly ?Collection $routeArticles, + ) {} + + public function isExpanded(): bool + { + return $this->routeArticles !== null; + } +} diff --git a/resources/views/components/route-article-card.blade.php b/resources/views/components/route-article-card.blade.php new file mode 100644 index 00000000..1511c300 --- /dev/null +++ b/resources/views/components/route-article-card.blade.php @@ -0,0 +1,93 @@ +@props(['routeArticle', 'tab']) + +
+
+
+
+ @if ($routeArticle->feed) + + @endif + {{ $routeArticle->feed?->name ?? 'Unknown feed' }} + + {{ $routeArticle->platformChannel?->name ?? 'Unknown channel' }} +
+

+ {{ $routeArticle->article->title ?? 'Untitled Article' }} +

+

+ {{ $routeArticle->article->description ?? 'No description available' }} +

+
+ {{ $routeArticle->created_at->format('M d, Y H:i') }} +
+
+
+ {{-- Status badge (All tab) --}} + @if ($tab === 'all') + @if ($routeArticle->isApproved()) + + Approved + + @elseif ($routeArticle->isRejected()) + + Rejected + + @else + + Pending + + @endif + @endif + + {{-- Action buttons --}} + @if ($routeArticle->isPending()) + + + @elseif ($routeArticle->isRejected()) + + @endif + + {{-- Link to original --}} + @if ($routeArticle->article->url) + + + + + + @endif +
+
+
diff --git a/resources/views/livewire/articles.blade.php b/resources/views/livewire/articles.blade.php index 49ec9be8..91392bd0 100644 --- a/resources/views/livewire/articles.blade.php +++ b/resources/views/livewire/articles.blade.php @@ -67,99 +67,42 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline- {{-- Route articles list --}}
- @forelse ($routeArticles as $routeArticle) -
-
-
-
isNotEmpty()) + @foreach ($pendingFeeds as $group) + @php($isExpanded = $group->isExpanded()) +
+ + @if ($isExpanded) +
+ @foreach ($group->routeArticles as $routeArticle) + + @endforeach
-

- {{ $routeArticle->article->title ?? 'Untitled Article' }} -

-

- {{ $routeArticle->article->description ?? 'No description available' }} -

-
- {{ $routeArticle->created_at->format('M d, Y H:i') }} -
-
-
- {{-- Status badge (All tab) --}} - @if ($tab === 'all') - @if ($routeArticle->isApproved()) - - Approved - - @elseif ($routeArticle->isRejected()) - - Rejected - - @else - - Pending - - @endif - @endif - - {{-- Action buttons --}} - @if ($routeArticle->isPending()) - - - @elseif ($routeArticle->isRejected()) - - @endif - - {{-- Link to original --}} - @if ($routeArticle->article->url) - - - - - - @endif -
+ @endif
-
- @empty + @endforeach + @elseif ($routeArticles !== null && $routeArticles->isNotEmpty()) + @foreach ($routeArticles as $routeArticle) + + @endforeach + @else
@@ -181,9 +124,9 @@ class="p-1.5 text-gray-400 hover:text-gray-600 rounded-md" @endif

- @endforelse + @endif - @if ($routeArticles->hasPages()) + @if ($routeArticles !== null && $routeArticles->hasPages())
{{ $routeArticles->links() }}
diff --git a/tests/Feature/Livewire/ArticlesTest.php b/tests/Feature/Livewire/ArticlesTest.php index 0b7752ad..b56c3689 100644 --- a/tests/Feature/Livewire/ArticlesTest.php +++ b/tests/Feature/Livewire/ArticlesTest.php @@ -35,6 +35,22 @@ private function createRouteArticle(ApprovalStatusEnum $status = ApprovalStatusE return $routeArticle; } + private function createRouteArticleForFeed(Feed $feed, string $title, ApprovalStatusEnum $status = ApprovalStatusEnum::PENDING): RouteArticle + { + /** @var Route $route */ + $route = Route::factory()->active()->create(['feed_id' => $feed->id]); + $article = Article::factory()->create(['feed_id' => $feed->id, 'title' => $title]); + + /** @var RouteArticle $routeArticle */ + $routeArticle = RouteArticle::factory()->forRoute($route)->create([ + 'article_id' => $article->id, + 'approval_status' => $status, + 'validated_at' => now(), + ]); + + return $routeArticle; + } + public function test_renders_successfully(): void { Livewire::test(Articles::class) @@ -49,11 +65,14 @@ public function test_defaults_to_pending_tab(): void public function test_pending_tab_shows_only_pending_route_articles(): void { - $pending = $this->createRouteArticle(ApprovalStatusEnum::PENDING, 'Pending Article'); - $approved = $this->createRouteArticle(ApprovalStatusEnum::APPROVED, 'Approved Article'); - $rejected = $this->createRouteArticle(ApprovalStatusEnum::REJECTED, 'Rejected Article'); + $feed = Feed::factory()->create(['name' => 'VRT News']); + + $this->createRouteArticleForFeed($feed, 'Pending Article'); + $this->createRouteArticleForFeed($feed, 'Approved Article', ApprovalStatusEnum::APPROVED); + $this->createRouteArticleForFeed($feed, 'Rejected Article', ApprovalStatusEnum::REJECTED); Livewire::test(Articles::class) + ->call('toggleFeed', $feed->id) ->assertSee('Pending Article') ->assertDontSee('Approved Article') ->assertDontSee('Rejected Article'); @@ -169,6 +188,107 @@ public function test_shows_route_name_in_listing(): void ->assertSee('VRT News'); } + public function test_pending_tab_lists_feeds_collapsed_without_articles(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $this->createRouteArticleForFeed($belga, 'Belga Headline'); + + Livewire::test(Articles::class) + ->assertSee('VRT News') + ->assertSee('Belga Press') + ->assertDontSee('VRT Headline') + ->assertDontSee('Belga Headline'); + } + + public function test_pending_feed_badge_shows_total_count_not_page_count(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + for ($i = 0; $i < 20; $i++) { + $this->createRouteArticleForFeed($vrt, "VRT Article {$i}"); + } + + $html = (string) preg_replace('/\s+/', ' ', Livewire::test(Articles::class)->html()); + + $this->assertStringContainsString('VRT News 20 ', $html); + } + + public function test_expanding_a_feed_reveals_only_that_feeds_articles(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $this->createRouteArticleForFeed($belga, 'Belga Headline'); + + Livewire::test(Articles::class) + ->call('toggleFeed', $vrt->id) + ->assertSee('VRT Headline') + ->assertDontSee('Belga Headline'); + } + + public function test_expanding_a_feed_shows_all_its_articles_beyond_one_page(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + for ($i = 0; $i < 20; $i++) { + $this->travelTo(now()->addMinutes($i), function () use ($vrt, $i) { + $this->createRouteArticleForFeed($vrt, "VRT Article {$i}"); + }); + } + + $component = Livewire::test(Articles::class)->call('toggleFeed', $vrt->id); + + for ($i = 0; $i < 20; $i++) { + $component->assertSee("VRT Article {$i}"); + } + } + + public function test_toggling_an_expanded_feed_collapses_it(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + + Livewire::test(Articles::class) + ->call('toggleFeed', $vrt->id) + ->assertSee('VRT Headline') + ->call('toggleFeed', $vrt->id) + ->assertDontSee('VRT Headline'); + } + + public function test_switching_tabs_collapses_expanded_feeds(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + + Livewire::test(Articles::class) + ->call('toggleFeed', $vrt->id) + ->call('setTab', 'all') + ->call('setTab', 'pending') + ->assertSet('expandedFeeds', []) + ->assertDontSee('VRT Headline'); + } + + public function test_all_tab_shows_a_flat_list_not_feed_groups(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT Article'); + $this->createRouteArticleForFeed($belga, 'Belga Article'); + + Livewire::test(Articles::class) + ->call('setTab', 'all') + ->assertViewHas('pendingFeeds', null) + ->assertSee('VRT Article') + ->assertSee('Belga Article'); + } + public function test_empty_state_on_pending_tab(): void { Livewire::test(Articles::class) -- 2.45.2 From 817219df618a9d3b758dc4ee8deae459fd50034f Mon Sep 17 00:00:00 2001 From: myrmidex Date: Sun, 9 Aug 2026 09:41:47 +0200 Subject: [PATCH 04/71] 113 - Filter articles by feed on both tabs --- app/Livewire/Articles.php | 25 ++++ resources/views/livewire/articles.blade.php | 41 +++-- tests/Feature/Livewire/ArticlesTest.php | 157 ++++++++++++++++++++ 3 files changed, 210 insertions(+), 13 deletions(-) diff --git a/app/Livewire/Articles.php b/app/Livewire/Articles.php index 1375748e..199807d2 100644 --- a/app/Livewire/Articles.php +++ b/app/Livewire/Articles.php @@ -21,6 +21,8 @@ class Articles extends Component public string $search = ''; + public ?int $feedId = null; + public bool $isRefreshing = false; /** @var array */ @@ -34,6 +36,12 @@ public function setTab(string $tab): void $this->resetPage(); } + public function updatedFeedId(): void + { + $this->expandedFeeds = []; + $this->resetPage(); + } + public function toggleFeed(int $feedId): void { if (isset($this->expandedFeeds[$feedId])) { @@ -89,6 +97,7 @@ public function render(): View return view('livewire.articles', [ 'routeArticles' => null, 'pendingFeeds' => $this->pendingFeeds(), + 'feedOptions' => $this->feedOptions(), 'pendingCount' => $pendingCount, ])->layout('layouts.app'); } @@ -96,6 +105,10 @@ public function render(): View $query = RouteArticle::with(['article.feed', 'feed', 'platformChannel']) ->orderBy('created_at', 'desc'); + if ($this->feedId !== null) { + $query->where('feed_id', $this->feedId); + } + if ($this->search !== '') { $search = $this->search; $query->whereHas('article', function ($q) use ($search) { @@ -107,6 +120,7 @@ public function render(): View return view('livewire.articles', [ 'routeArticles' => $query->paginate(15), 'pendingFeeds' => null, + 'feedOptions' => $this->feedOptions(), 'pendingCount' => $pendingCount, ])->layout('layouts.app'); } @@ -119,6 +133,7 @@ private function pendingFeeds(): Collection $counts = RouteArticle::query() ->selectRaw('feed_id, COUNT(*) as aggregate') ->where('approval_status', ApprovalStatusEnum::PENDING) + ->when($this->feedId !== null, fn ($q) => $q->where('feed_id', $this->feedId)) ->groupBy('feed_id') ->pluck('aggregate', 'feed_id'); @@ -134,6 +149,16 @@ private function pendingFeeds(): Collection ->values(); } + /** + * @return EloquentCollection + */ + private function feedOptions(): EloquentCollection + { + return Feed::whereIn('id', RouteArticle::query()->select('feed_id')->distinct()) + ->orderBy('name') + ->get(); + } + /** * @return EloquentCollection */ diff --git a/resources/views/livewire/articles.blade.php b/resources/views/livewire/articles.blade.php index 91392bd0..56df96b7 100644 --- a/resources/views/livewire/articles.blade.php +++ b/resources/views/livewire/articles.blade.php @@ -39,7 +39,29 @@ class="whitespace-nowrap pb-3 px-1 border-b-2 font-medium text-sm {{ $tab === 'a
{{-- Tab actions --}} -
+
+
+ + @if ($tab === 'all') +
+ +
+ @endif +
@if ($tab === 'pending' && $pendingCount > 0)
- @else -
@endif
@@ -115,10 +126,14 @@ class="h-4 w-4 shrink-0 text-gray-400 transition-transform {{ $isExpanded ? 'rot @endif

- @if ($tab === 'pending') + @if ($tab === 'pending' && $feedId !== null) + No pending articles for the selected feed. + @elseif ($tab === 'pending') All route articles have been reviewed. @elseif ($search !== '') No results for "{{ $search }}". + @elseif ($feedId !== null) + No articles for the selected feed. @else No route articles have been created yet. @endif diff --git a/tests/Feature/Livewire/ArticlesTest.php b/tests/Feature/Livewire/ArticlesTest.php index b56c3689..acde862d 100644 --- a/tests/Feature/Livewire/ArticlesTest.php +++ b/tests/Feature/Livewire/ArticlesTest.php @@ -11,6 +11,7 @@ use App\Models\RouteArticle; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Support\Facades\Event; +use Livewire\Features\SupportTesting\Testable; use Livewire\Livewire; use Tests\TestCase; @@ -51,6 +52,17 @@ private function createRouteArticleForFeed(Feed $feed, string $title, ApprovalSt return $routeArticle; } + /** + * @param Testable $component + * @return array + */ + private function renderedFeedGroupIds(Testable $component): array + { + preg_match_all('/wire:key="feed-group-(\d+)"/', $component->html(), $matches); + + return array_map('intval', $matches[1]); + } + public function test_renders_successfully(): void { Livewire::test(Articles::class) @@ -289,6 +301,151 @@ public function test_all_tab_shows_a_flat_list_not_feed_groups(): void ->assertSee('Belga Article'); } + public function test_feed_filter_narrows_pending_tab_to_one_feed(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $this->createRouteArticleForFeed($belga, 'Belga Headline'); + + $component = Livewire::test(Articles::class)->set('feedId', $vrt->id); + + $this->assertSame([$vrt->id], $this->renderedFeedGroupIds($component)); + } + + public function test_feed_filter_narrows_all_tab_to_one_feed(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $this->createRouteArticleForFeed($belga, 'Belga Headline'); + + Livewire::test(Articles::class) + ->call('setTab', 'all') + ->set('feedId', $vrt->id) + ->assertSee('VRT Headline') + ->assertDontSee('Belga Headline'); + } + + public function test_feed_filter_combines_with_search_on_all_tab(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'Belgian Politics Update'); + $this->createRouteArticleForFeed($vrt, 'Weather Forecast Today'); + $this->createRouteArticleForFeed($belga, 'Belgian Economy Report'); + + Livewire::test(Articles::class) + ->call('setTab', 'all') + ->set('feedId', $vrt->id) + ->set('search', 'Belgian') + ->assertSee('Belgian Politics Update') + ->assertDontSee('Weather Forecast Today') + ->assertDontSee('Belgian Economy Report'); + } + + public function test_feed_filter_combines_with_tab_status_filtering(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + $this->createRouteArticleForFeed($vrt, 'VRT Pending'); + $this->createRouteArticleForFeed($vrt, 'VRT Approved', ApprovalStatusEnum::APPROVED); + + Livewire::test(Articles::class) + ->set('feedId', $vrt->id) + ->call('toggleFeed', $vrt->id) + ->assertSee('VRT Pending') + ->assertDontSee('VRT Approved'); + } + + public function test_clearing_feed_filter_restores_all_feeds(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $this->createRouteArticleForFeed($belga, 'Belga Headline'); + + $component = Livewire::test(Articles::class)->set('feedId', $vrt->id); + + $this->assertSame([$vrt->id], $this->renderedFeedGroupIds($component)); + + $component->set('feedId', null); + + $this->assertEqualsCanonicalizing([$vrt->id, $belga->id], $this->renderedFeedGroupIds($component)); + } + + public function test_changing_feed_filter_resets_pagination(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + for ($i = 0; $i < 20; $i++) { + $this->createRouteArticleForFeed($vrt, "VRT Article {$i}"); + } + + Livewire::test(Articles::class) + ->call('setTab', 'all') + ->set('paginators.page', 2) + ->set('feedId', $vrt->id) + ->assertSet('paginators.page', 1); + } + + public function test_changing_feed_filter_collapses_expanded_feeds(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + + Livewire::test(Articles::class) + ->call('toggleFeed', $vrt->id) + ->assertSee('VRT Headline') + ->set('feedId', $vrt->id) + ->assertSet('expandedFeeds', []) + ->assertDontSee('VRT Headline'); + } + + public function test_feed_filter_persists_across_tab_switches(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + + Livewire::test(Articles::class) + ->set('feedId', $vrt->id) + ->call('setTab', 'all') + ->assertSet('feedId', $vrt->id) + ->call('setTab', 'pending') + ->assertSet('feedId', $vrt->id); + } + + public function test_feed_filter_options_only_include_feeds_with_route_articles(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $unused = Feed::factory()->create(['name' => 'Unused Feed']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + + Livewire::test(Articles::class) + ->assertSeeHtml('') + ->assertDontSeeHtml(''); + } + + public function test_empty_state_when_feed_filter_matches_nothing_on_pending_tab(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED); + + Livewire::test(Articles::class) + ->set('feedId', $belga->id) + ->assertSee('No pending articles for the selected feed.'); + } + public function test_empty_state_on_pending_tab(): void { Livewire::test(Articles::class) -- 2.45.2 From 36b46b4d2e0273e89eb56721c92edcb6684e62b4 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Sun, 9 Aug 2026 10:37:54 +0200 Subject: [PATCH 05/71] 113 - Scope clearing pending articles to the active feed filter --- app/Livewire/Articles.php | 18 ++++- resources/views/livewire/articles.blade.php | 6 +- tests/Feature/Livewire/ArticlesTest.php | 74 +++++++++++++++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/app/Livewire/Articles.php b/app/Livewire/Articles.php index 199807d2..29ec16a9 100644 --- a/app/Livewire/Articles.php +++ b/app/Livewire/Articles.php @@ -8,6 +8,7 @@ use App\Models\RouteArticle; use App\Support\PendingFeedGroup; use Illuminate\Contracts\View\View; +use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Support\Collection; use Livewire\Component; @@ -76,8 +77,19 @@ public function restore(int $routeArticleId): void public function clear(): void { - RouteArticle::where('approval_status', ApprovalStatusEnum::PENDING) - ->update(['approval_status' => ApprovalStatusEnum::REJECTED]); + $this->clearableQuery()->update(['approval_status' => ApprovalStatusEnum::REJECTED]); + + $this->expandedFeeds = []; + } + + /** + * @return Builder + */ + private function clearableQuery(): Builder + { + return RouteArticle::query() + ->where('approval_status', ApprovalStatusEnum::PENDING) + ->when($this->feedId !== null, fn (Builder $q) => $q->where('feed_id', $this->feedId)); } public function refresh(): void @@ -99,6 +111,7 @@ public function render(): View 'pendingFeeds' => $this->pendingFeeds(), 'feedOptions' => $this->feedOptions(), 'pendingCount' => $pendingCount, + 'clearableCount' => $this->clearableQuery()->count(), ])->layout('layouts.app'); } @@ -122,6 +135,7 @@ public function render(): View 'pendingFeeds' => null, 'feedOptions' => $this->feedOptions(), 'pendingCount' => $pendingCount, + 'clearableCount' => 0, ])->layout('layouts.app'); } diff --git a/resources/views/livewire/articles.blade.php b/resources/views/livewire/articles.blade.php index 56df96b7..8672dc48 100644 --- a/resources/views/livewire/articles.blade.php +++ b/resources/views/livewire/articles.blade.php @@ -62,16 +62,16 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline-

@endif
- @if ($tab === 'pending' && $pendingCount > 0) + @if ($tab === 'pending' && $clearableCount > 0) @endif
diff --git a/tests/Feature/Livewire/ArticlesTest.php b/tests/Feature/Livewire/ArticlesTest.php index acde862d..2ff785e8 100644 --- a/tests/Feature/Livewire/ArticlesTest.php +++ b/tests/Feature/Livewire/ArticlesTest.php @@ -163,6 +163,80 @@ public function test_clear_rejects_all_pending_route_articles(): void $this->assertEquals(ApprovalStatusEnum::APPROVED, $approved->fresh()->approval_status); } + public function test_clear_only_rejects_the_filtered_feed(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $vrtArticle = $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $belgaArticle = $this->createRouteArticleForFeed($belga, 'Belga Headline'); + + Livewire::test(Articles::class) + ->set('feedId', $vrt->id) + ->call('clear'); + + $this->assertEquals(ApprovalStatusEnum::REJECTED, $vrtArticle->fresh()->approval_status); + $this->assertEquals(ApprovalStatusEnum::PENDING, $belgaArticle->fresh()->approval_status); + } + + public function test_clear_is_a_no_op_when_the_filtered_feed_has_nothing_pending(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $vrtArticle = $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $belgaApproved = $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED); + + Livewire::test(Articles::class) + ->set('feedId', $belga->id) + ->call('clear'); + + $this->assertEquals(ApprovalStatusEnum::PENDING, $vrtArticle->fresh()->approval_status); + $this->assertEquals(ApprovalStatusEnum::APPROVED, $belgaApproved->fresh()->approval_status); + } + + public function test_clear_confirmation_counts_only_the_filtered_feed(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT One'); + $this->createRouteArticleForFeed($vrt, 'VRT Two'); + $this->createRouteArticleForFeed($belga, 'Belga One'); + + Livewire::test(Articles::class) + ->set('feedId', $vrt->id) + ->assertSee('Reject 2 pending route articles from the selected feed?') + ->set('feedId', null) + ->assertSee('Reject 3 pending route articles?'); + } + + public function test_clear_button_hidden_when_filtered_feed_has_nothing_pending(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + $belga = Feed::factory()->create(['name' => 'Belga Press']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + $this->createRouteArticleForFeed($belga, 'Belga Approved', ApprovalStatusEnum::APPROVED); + + Livewire::test(Articles::class) + ->set('feedId', $belga->id) + ->assertDontSee('Clear Feed') + ->assertDontSee('Clear All'); + } + + public function test_clear_button_labels_its_scope(): void + { + $vrt = Feed::factory()->create(['name' => 'VRT News']); + + $this->createRouteArticleForFeed($vrt, 'VRT Headline'); + + Livewire::test(Articles::class) + ->assertSee('Clear All') + ->set('feedId', $vrt->id) + ->assertSee('Clear Feed'); + } + public function test_pending_count_badge_shows_correct_count(): void { $this->createRouteArticle(ApprovalStatusEnum::PENDING); -- 2.45.2 From 798ddf986281c0c006e47b50c7a92945b29f43a4 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Sun, 9 Aug 2026 13:14:17 +0200 Subject: [PATCH 06/71] 113 - Cover route eyebrow ordering and fix accordion labelling and filter width --- resources/views/livewire/articles.blade.php | 11 +++++++++-- tests/Feature/Livewire/ArticlesTest.php | 22 +++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/articles.blade.php b/resources/views/livewire/articles.blade.php index 8672dc48..c1d8ed3d 100644 --- a/resources/views/livewire/articles.blade.php +++ b/resources/views/livewire/articles.blade.php @@ -44,7 +44,7 @@ class="whitespace-nowrap pb-3 px-1 border-b-2 font-medium text-sm {{ $tab === 'a + {{ __('Remember me') }}
diff --git a/resources/views/components/secondary-button.blade.php b/resources/views/components/secondary-button.blade.php index b32b69fc..3e067e30 100644 --- a/resources/views/components/secondary-button.blade.php +++ b/resources/views/components/secondary-button.blade.php @@ -1,3 +1,3 @@ - diff --git a/resources/views/components/text-input.blade.php b/resources/views/components/text-input.blade.php index e17e5a0e..178a33cd 100644 --- a/resources/views/components/text-input.blade.php +++ b/resources/views/components/text-input.blade.php @@ -1,3 +1,3 @@ @props(['disabled' => false]) -merge(['class' => 'border-gray-300 focus:border-blue-500 focus:ring-blue-500 rounded-lg shadow-sm px-4 py-3']) }}> +merge(['class' => 'border-gray-300 focus:border-blue-500 focus:ring-blue-500 rounded-lg shadow-xs px-4 py-3']) }}> diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index 66028f2d..16712de3 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -7,7 +7,7 @@
-
+
{{ __("You're logged in!") }}
diff --git a/resources/views/livewire/channels.blade.php b/resources/views/livewire/channels.blade.php index 88af793b..756f44c7 100644 --- a/resources/views/livewire/channels.blade.php +++ b/resources/views/livewire/channels.blade.php @@ -2,7 +2,7 @@ @@ -167,7 +167,7 @@ class="w-full inline-flex justify-center rounded-md border border-gray-300 shado @foreach ($availableCommunities as $community) @@ -211,7 +211,7 @@ class="mt-1 block w-full rounded-md border border-gray-300 shadow-sm focus:borde @foreach ($providers as $code => $provider) @@ -136,7 +136,7 @@ class="mt-1 block w-full rounded-md border border-gray-300 shadow-sm focus:borde + {{ __('Remember me') }}
diff --git a/resources/views/livewire/channels.blade.php b/resources/views/livewire/channels.blade.php index 9fa42e90..d4d37fc5 100644 --- a/resources/views/livewire/channels.blade.php +++ b/resources/views/livewire/channels.blade.php @@ -131,7 +131,7 @@ class="inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm font-me

Available Accounts

@foreach ($availableAccounts as $account) -
+
{{ $account->username }} diff --git a/resources/views/components/danger-button.blade.php b/resources/views/components/danger-button.blade.php index d17d2889..0ce850b7 100644 --- a/resources/views/components/danger-button.blade.php +++ b/resources/views/components/danger-button.blade.php @@ -1,3 +1,3 @@ - diff --git a/resources/views/components/dropdown-link.blade.php b/resources/views/components/dropdown-link.blade.php index e0f8ce1d..66c4ba42 100644 --- a/resources/views/components/dropdown-link.blade.php +++ b/resources/views/components/dropdown-link.blade.php @@ -1 +1 @@ -merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-none focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }} +merge(['class' => 'block w-full px-4 py-2 text-start text-sm leading-5 text-gray-700 hover:bg-gray-100 focus:outline-hidden focus:bg-gray-100 transition duration-150 ease-in-out']) }}>{{ $slot }} diff --git a/resources/views/components/nav-link.blade.php b/resources/views/components/nav-link.blade.php index 5c101a29..5d648e07 100644 --- a/resources/views/components/nav-link.blade.php +++ b/resources/views/components/nav-link.blade.php @@ -2,8 +2,8 @@ @php $classes = ($active ?? false) - ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out' - : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-none focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'; + ? 'inline-flex items-center px-1 pt-1 border-b-2 border-indigo-400 text-sm font-medium leading-5 text-gray-900 focus:outline-hidden focus:border-indigo-700 transition duration-150 ease-in-out' + : 'inline-flex items-center px-1 pt-1 border-b-2 border-transparent text-sm font-medium leading-5 text-gray-500 hover:text-gray-700 hover:border-gray-300 focus:outline-hidden focus:text-gray-700 focus:border-gray-300 transition duration-150 ease-in-out'; @endphp merge(['class' => $classes]) }}> diff --git a/resources/views/components/primary-button.blade.php b/resources/views/components/primary-button.blade.php index 6a376dde..de1c8545 100644 --- a/resources/views/components/primary-button.blade.php +++ b/resources/views/components/primary-button.blade.php @@ -1,3 +1,3 @@ - diff --git a/resources/views/components/responsive-nav-link.blade.php b/resources/views/components/responsive-nav-link.blade.php index 43b91e7b..34640f0f 100644 --- a/resources/views/components/responsive-nav-link.blade.php +++ b/resources/views/components/responsive-nav-link.blade.php @@ -2,8 +2,8 @@ @php $classes = ($active ?? false) - ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-none focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out' - : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-none focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out'; + ? 'block w-full ps-3 pe-4 py-2 border-l-4 border-indigo-400 text-start text-base font-medium text-indigo-700 bg-indigo-50 focus:outline-hidden focus:text-indigo-800 focus:bg-indigo-100 focus:border-indigo-700 transition duration-150 ease-in-out' + : 'block w-full ps-3 pe-4 py-2 border-l-4 border-transparent text-start text-base font-medium text-gray-600 hover:text-gray-800 hover:bg-gray-50 hover:border-gray-300 focus:outline-hidden focus:text-gray-800 focus:bg-gray-50 focus:border-gray-300 transition duration-150 ease-in-out'; @endphp merge(['class' => $classes]) }}> diff --git a/resources/views/components/secondary-button.blade.php b/resources/views/components/secondary-button.blade.php index 3e067e30..bf7b1d57 100644 --- a/resources/views/components/secondary-button.blade.php +++ b/resources/views/components/secondary-button.blade.php @@ -1,3 +1,3 @@ - diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 435fb15d..15b46a90 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -90,7 +90,7 @@ class="p-2 rounded-md text-gray-400 hover:text-gray-600 hover:bg-gray-100"
@endif @@ -66,7 +66,7 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md text-sm focus:outline- @@ -236,7 +236,7 @@ class="mt-1 block w-full rounded-md border border-gray-300 shadow-xs focus:borde @@ -244,7 +244,7 @@ class="inline-flex justify-center rounded-md border border-gray-300 shadow-xs px type="submit" wire:loading.attr="disabled" wire:target="createChannel" - class="inline-flex justify-center rounded-md border border-transparent shadow-xs px-4 py-2 bg-blue-600 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed" + class="inline-flex justify-center rounded-md border border-transparent shadow-xs px-4 py-2 bg-blue-600 text-sm font-medium text-white hover:bg-blue-700 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50 disabled:cursor-not-allowed" > Create diff --git a/resources/views/livewire/feeds.blade.php b/resources/views/livewire/feeds.blade.php index c9d9d610..bcb8df16 100644 --- a/resources/views/livewire/feeds.blade.php +++ b/resources/views/livewire/feeds.blade.php @@ -2,7 +2,7 @@ diff --git a/resources/views/livewire/notification-bell.blade.php b/resources/views/livewire/notification-bell.blade.php index 10920bd3..997e7107 100644 --- a/resources/views/livewire/notification-bell.blade.php +++ b/resources/views/livewire/notification-bell.blade.php @@ -1,7 +1,7 @@
@@ -433,7 +433,7 @@ class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition @@ -478,7 +478,7 @@ class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none foc min="1" max="100" placeholder="50" - class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-hidden focus:ring-2 focus:ring-blue-500" >

Higher priority routes are processed first (default: 50) diff --git a/resources/views/livewire/routes.blade.php b/resources/views/livewire/routes.blade.php index 029dcb58..d16f7d4c 100644 --- a/resources/views/livewire/routes.blade.php +++ b/resources/views/livewire/routes.blade.php @@ -2,7 +2,7 @@

@@ -237,7 +237,7 @@ class="px-4 py-2 text-sm font-medium text-white bg-blue-600 border border-transp id="editPriority" wire:model="editPriority" min="0" - class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" + class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-hidden focus:ring-2 focus:ring-blue-500" >

Higher priority routes are processed first

@@ -289,7 +289,7 @@ class="inline-flex items-center p-1 text-gray-400 hover:text-gray-600 rounded-sm wire:model="newKeyword" wire:keydown.enter.prevent="addKeyword" placeholder="Enter keyword..." - class="flex-1 px-2 py-1 border border-gray-300 rounded-sm text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + class="flex-1 px-2 py-1 border border-gray-300 rounded-sm text-sm focus:outline-hidden focus:ring-2 focus:ring-blue-500" autofocus >

-- 2.45.2 From 0626a546c90cfe2b6a6b547c7a20f50fad3aaa51 Mon Sep 17 00:00:00 2001 From: myrmidex Date: Mon, 10 Aug 2026 01:47:30 +0200 Subject: [PATCH 18/71] 109 - Replace deprecated flex-shrink-0 and backdrop-blur-sm aliases --- resources/views/components/form-modal.blade.php | 2 +- resources/views/components/modal.blade.php | 2 +- resources/views/layouts/app.blade.php | 4 ++-- resources/views/livewire/notification-bell.blade.php | 2 +- resources/views/livewire/settings.blade.php | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/views/components/form-modal.blade.php b/resources/views/components/form-modal.blade.php index 7a9f832d..48234b8f 100644 --- a/resources/views/components/form-modal.blade.php +++ b/resources/views/components/form-modal.blade.php @@ -16,7 +16,7 @@