Feed cards have no edit option #136

Closed
opened 2026-08-13 00:52:15 +02:00 by myrmidex · 1 comment
Owner

Feed cards on /feeds offer only an active/inactive toggle. There is no way to change a feed's name, provider, language, or description after creation — the only recourse is deleting the feed and recreating it, which makes managing feeds painful.

app/Livewire/Feeds.php currently exposes only toggle(), openCreateModal(), closeCreateModal() and createFeed(). No edit path exists.

Expected

Each feed card has an edit action that opens a modal prefilled with the feed's current values, allowing name, provider, language and description to be updated and saved.

Implementation reference

app/Livewire/Routes.php already implements this pattern — openEditModal() / closeEditModal() / updateRoute() with dedicated editing* properties. Follow the same shape here.

Validation should match createFeed(), including the provider/language availability check that currently throws InvalidArgumentException and is surfaced as an error on the provider field.

Acceptance criteria

  • Feed cards show an edit action
  • Modal opens prefilled with the feed's current name and description (provider and language are shown read-only — see the scope note below)
  • Saving persists the changes and closes the modal
  • Validation mirrors create, including the provider-not-available-for-language casenot applicable under the agreed scope. That check exists only to guard a provider/language change, which is now impossible by design. Name validation mirrors create (required|string|max:255).
  • Feature tests cover opening the modal, a successful update, and validation failures

Scope decision (agreed before implementation)

Editable: name and description. Immutable: provider and language.

CreateFeedAction derives feeds.url from the provider+language pair, and feeds.url carries a unique constraint. Editing either field would re-point the feed at a different source, could collide on that unique index, would change type (also provider-derived), and would leave already-fetched articles attached to a feed that no longer points where they came from.

This matches the safer default that #137 "Channel cards have no edit option" proposes for the community/instance pairing.

Feed cards on `/feeds` offer only an active/inactive toggle. There is no way to change a feed's name, provider, language, or description after creation — the only recourse is deleting the feed and recreating it, which makes managing feeds painful. `app/Livewire/Feeds.php` currently exposes only `toggle()`, `openCreateModal()`, `closeCreateModal()` and `createFeed()`. No edit path exists. ## Expected Each feed card has an edit action that opens a modal prefilled with the feed's current values, allowing name, provider, language and description to be updated and saved. ## Implementation reference `app/Livewire/Routes.php` already implements this pattern — `openEditModal()` / `closeEditModal()` / `updateRoute()` with dedicated `editing*` properties. Follow the same shape here. Validation should match `createFeed()`, including the provider/language availability check that currently throws `InvalidArgumentException` and is surfaced as an error on the provider field. ## Acceptance criteria - [x] Feed cards show an edit action - [x] Modal opens prefilled with the feed's current name and description (provider and language are shown read-only — see the scope note below) - [x] Saving persists the changes and closes the modal - [ ] ~~Validation mirrors create, including the provider-not-available-for-language case~~ — **not applicable under the agreed scope.** That check exists only to guard a provider/language change, which is now impossible by design. Name validation mirrors create (`required|string|max:255`). - [x] Feature tests cover opening the modal, a successful update, and validation failures ## Scope decision (agreed before implementation) **Editable: name and description. Immutable: provider and language.** `CreateFeedAction` derives `feeds.url` from the provider+language pair, and `feeds.url` carries a unique constraint. Editing either field would re-point the feed at a different source, could collide on that unique index, would change `type` (also provider-derived), and would leave already-fetched articles attached to a feed that no longer points where they came from. This matches the safer default that #137 "Channel cards have no edit option" proposes for the community/instance pairing.
myrmidex added this to the v1.4.0 milestone 2026-08-13 00:52:15 +02:00
myrmidex added the
bug
label 2026-08-13 00:52:15 +02:00
myrmidex self-assigned this 2026-08-13 00:52:15 +02:00
Author
Owner

Delivered in 41a1be7.

What shipped

  • Edit button on each feed card, opening an x-form-modal prefilled with the feed's name and description
  • Feeds::openEditModal() / closeEditModal() / updateFeed() with editingFeedId / editName / editDescription, following the Routes.php shape
  • Provider and language rendered read-only in the modal with a note explaining they define the source URL
  • Blank description saves as null, matching createFeed()

Scope

Name and description only — provider and language are immutable. Reasoning is in the ticket body above. The immutability is enforced in code, not just the UI: updateFeed() builds an explicit two-key array, so a replayed Livewire request cannot reach url, provider, language_id or type. FeedsTest::test_update_feed_leaves_the_source_identity_untouched asserts all four survive an edit, so widening the scope later has to be deliberate.

One deliberate divergence from the Routes.php reference: updateFeed() uses findOrFail() where Routes::updateRoute() scopes a query-builder update(). Routes' version silently no-ops if the row is gone; this one 404s.

Pre-existing bug found and fixed

Feed::$description was documented @property string while the column is ->nullable(). PHPStan trusted the docblock, so two legitimate assertNull($feed->description) calls in CreateFeedActionTest were flagged method.impossibleType — and had been suppressed in phpstan-baseline.neon rather than fixed. Correcting the type to string|null made that suppression stale and it was removed; the assertions now pass on their own merit. Worth checking other models for the same pattern — a baseline entry naming a model property is a signal the docblock is wrong.

Verification

10 new feature tests: edit action present, prefill (including a null description prefilling blank), successful update, blank-to-null coercion, required and max-length validation, discard on close, no-op without an open modal, and source-identity immutability.

1153 tests / 2833 assertions green, Pint clean (338 files), PHPStan clean. code-reviewer returned no critical issues and requested no changes. Confirmed working in a browser.

Note on tooling: this was built directly in the main checkout rather than via /fullTicketFlow. That flow's worktree isolation does not work on this project — docker/dev/docker-compose.yml bind-mounts the main checkout into the app container, so the gates silently lint and test the main tree regardless of the worktree path passed. Recorded in PLATFORM.md.

Delivered in `41a1be7`. **What shipped** - Edit button on each feed card, opening an `x-form-modal` prefilled with the feed's name and description - `Feeds::openEditModal()` / `closeEditModal()` / `updateFeed()` with `editingFeedId` / `editName` / `editDescription`, following the `Routes.php` shape - Provider and language rendered read-only in the modal with a note explaining they define the source URL - Blank description saves as `null`, matching `createFeed()` **Scope** Name and description only — provider and language are immutable. Reasoning is in the ticket body above. The immutability is enforced in code, not just the UI: `updateFeed()` builds an explicit two-key array, so a replayed Livewire request cannot reach `url`, `provider`, `language_id` or `type`. `FeedsTest::test_update_feed_leaves_the_source_identity_untouched` asserts all four survive an edit, so widening the scope later has to be deliberate. One deliberate divergence from the `Routes.php` reference: `updateFeed()` uses `findOrFail()` where `Routes::updateRoute()` scopes a query-builder `update()`. `Routes`' version silently no-ops if the row is gone; this one 404s. **Pre-existing bug found and fixed** `Feed::$description` was documented `@property string` while the column is `->nullable()`. PHPStan trusted the docblock, so two legitimate `assertNull($feed->description)` calls in `CreateFeedActionTest` were flagged `method.impossibleType` — and had been **suppressed in `phpstan-baseline.neon`** rather than fixed. Correcting the type to `string|null` made that suppression stale and it was removed; the assertions now pass on their own merit. Worth checking other models for the same pattern — a baseline entry naming a model property is a signal the docblock is wrong. **Verification** 10 new feature tests: edit action present, prefill (including a null description prefilling blank), successful update, blank-to-null coercion, required and max-length validation, discard on close, no-op without an open modal, and source-identity immutability. 1153 tests / 2833 assertions green, Pint clean (338 files), PHPStan clean. `code-reviewer` returned no critical issues and requested no changes. Confirmed working in a browser. **Note on tooling:** this was built directly in the main checkout rather than via `/fullTicketFlow`. That flow's worktree isolation does not work on this project — `docker/dev/docker-compose.yml` bind-mounts the main checkout into the app container, so the gates silently lint and test the main tree regardless of the worktree path passed. Recorded in `PLATFORM.md`.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lvl0/fedi-feed-router#136
No description provided.