Skip to content

Add comment reply threads - #140

Merged
fgilio merged 22 commits into
mainfrom
feat/comment-replies
Jul 27, 2026
Merged

Add comment reply threads#140
fgilio merged 22 commits into
mainfrom
feat/comment-replies

Conversation

@fgilio

@fgilio fgilio commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Closes:

  • none

Related:

  • none

Summary

Add durable, UI-driven reply threads to review and context comments, backed by reusable Actions for the upcoming CLI interface.

Why

RFA comments need conversation primitives before Codex CLI, Claude Code CLI, and human users can collaborate through the same thread. Replies must remain separate from root-comment anchoring, drafts, submission, and count semantics.

Solution

Store flat chronological replies in a dedicated table, normalize them through immutable DTOs, and expose scoped single-use-case Actions for loading and mutation. Livewire pages coordinate those Actions through targeted events so one thread updates without re-rendering every diff component.

Changes

  • Backend: add reply models, identities, DTOs, scoped Actions, workflows, atomic thread deletion, and bulk restoration.
  • UI: render, add, edit, delete, copy, filter, and restore replies in review, context, and comments-drawer surfaces.
  • Integrity: preserve complete threads through root delete, clear-all, discarded-file restore, and undo operations.
  • Performance: eager-load reply collections, bound restore query counts, and benchmark reply filtering.
  • Tests: add unit, architecture, JavaScript, browser, and performance coverage.
  • Database: add the comment_replies table with deterministic ordering and cascading root deletion.
  • Dependencies: none.

Testing

composer test:all
composer test:lint
composer test:types
composer validate --strict

Results: 1,732 PHP tests, 349 JavaScript tests, 206 browser tests, and 3 performance tests passed. One browser test remained skipped.

Deployment

  • Migrations: run the new comment_replies migration
  • Cache clear: none
  • Monitor: reply creation, root-delete cascades, and undo restoration

Summary by CodeRabbit

  • New Features

    • Added threaded replies to review and context comments.
    • Users can add, edit, copy, and delete replies.
    • Added undo support for deleted replies and complete comment threads.
    • Submitted comment threads can be expanded and searched, including by reply content or author.
    • Added edited indicators and consistent reply rendering across comment views.
  • Bug Fixes

    • Preserved replies when comments are loaded, restored, or discarded.
    • Comment and reply exports continue to include only root comments.

fgilio and others added 12 commits July 27, 2026 15:05
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@fgilio, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 10 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 431ffaea-7613-4521-801a-5a536598d01b

📥 Commits

Reviewing files that changed from the base of the PR and between 4922bf6 and 9d944df.

📒 Files selected for processing (14)
  • agents/20260216-architecture-audit.md
  • agents/20260315-benchmark-isolation-plan.md
  • agents/CLAUDE.md
  • app/Actions/DeleteCommentThreadsAction.php
  • app/Actions/RestoreDiscardedFileAction.php
  • app/Concerns/ManagesCommentReplies.php
  • app/Console/Benchmark/PerfScenarioRunner.php
  • database/migrations/2026_07_27_113228_create_comment_replies_table.php
  • resources/views/livewire/⚡comments-drawer.blade.php
  • tests/Browser/CommentRepliesTest.php
  • tests/Performance/PerfScenarioRunnerTest.php
  • tests/Unit/Actions/DeleteCommentThreadsActionTest.php
  • tests/Unit/Actions/RestoreDiscardedFileActionTest.php
  • tests/Unit/Livewire/ReviewPageTest.php
📝 Walkthrough

Walkthrough

Adds durable flat comment-reply threads with persisted author identity, scoped CRUD and undo workflows, transactional thread snapshots, reusable Livewire/Alpine UI components, drawer filtering, targeted updates, and broad automated coverage.

Changes

Comment reply foundation

Layer / File(s) Summary
Reply contracts and persistence
app/DTOs/*, app/Enums/*, app/Models/*, database/migrations/*, database/factories/*
Adds reply DTOs, author and surface enums, snapshot/deletion mutation contracts, the CommentReply model, cascade schema, ordering index, and factory support.
Reply mutation and thread lifecycle
app/Actions/*
Adds scoped add/update/delete/restore actions, thread loading, snapshot creation/restoration, transactional root deletion, and review/context workflow integration.
Thread UI and drawer integration
app/Concerns/*, resources/views/components/*, resources/views/livewire/*, resources/views/pages/*, public/js/comment-thread.js
Adds reply rendering and composer behavior, trusted UI author injection, undo handling, targeted diff updates, drawer filtering, and submitted-thread expansion.
Validation and regression coverage
tests/*, composer.json, resources/CLAUDE.md, app/Console/Benchmark/*
Adds DTO, action, model, Livewire, JavaScript, browser, export, performance, and architecture coverage, plus browser test process configuration and reply benchmarks.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ReplyComposer
  participant Livewire
  participant ReplyWorkflow
  participant Database
  User->>ReplyComposer: enter and submit reply
  ReplyComposer->>Livewire: dispatch add/update event
  Livewire->>ReplyWorkflow: execute scoped mutation
  ReplyWorkflow->>Database: persist reply and load ordered thread
  Database-->>ReplyWorkflow: return thread data
  ReplyWorkflow-->>Livewire: return mutation DTO
  Livewire-->>ReplyComposer: update thread event
Loading

Possibly related PRs

  • fgilio/rfa#29: Both changes extend resolved comment anchor data with normalized reply-related fields.
  • fgilio/rfa#79: Both changes modify context comment workflows and context-page comment handling.
  • fgilio/rfa#101: Both changes modify review comment deletion, restoration, and undo workflow architecture.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding durable comment reply threads across the app.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@fgilio
fgilio marked this pull request as ready for review July 27, 2026 15:25

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4922bf63ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread resources/views/livewire/⚡comments-drawer.blade.php Outdated
Comment thread app/Actions/RestoreDiscardedFileAction.php Outdated
fgilio and others added 2 commits July 27, 2026 16:38
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (9)
tests/Browser/CommentRepliesTest.php (1)

141-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Raw evaluate() click where a semantic locator exists.

getByLabel('Show submitted comments')->click() gives you Playwright's auto-wait and actionability checks; the JS click will hard-fail with a null deref if the toggle hasn't rendered yet.

♻️ Proposed tweak
-    $page->page()->evaluate(
-        'document.querySelector(\'[aria-label="Show submitted comments"]\').click()',
-    );
+    $page->page()->getByLabel('Show submitted comments')->click();

As per path instructions, "Prefer semantic locators in browser tests" and "Use CSS selectors as a last resort ... only for structural queries with no semantic alternative".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Browser/CommentRepliesTest.php` around lines 141 - 143, Replace the raw
JavaScript click in the CommentRepliesTest flow with the semantic Playwright
locator for the “Show submitted comments” label, using getByLabel(...)->click()
on the existing page object and preserving the surrounding test behavior.

Source: Path instructions

app/Actions/RestoreCommentReplyAction.php (1)

30-39: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

fresh() is nullable; refresh() says what you mean here.

$model->fresh() is typed ?static, so line 38 is an unchecked deref that PHPStan will grumble about. Since you already hold the saved model, refreshing in place is equivalent and non-nullable.

♻️ Proposed tweak
-        return CommentReplyData::fromArray($model->fresh()->toArray());
+        return CommentReplyData::fromArray($model->refresh()->toArray());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Actions/RestoreCommentReplyAction.php` around lines 30 - 39, Update the
return statement in the restore action to refresh the existing saved model in
place with refresh() before converting it via CommentReplyData::fromArray,
replacing the nullable fresh() call and preserving the current returned data.
tests/Unit/Livewire/ReviewPageTest.php (1)

641-668: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

The final assertion passes even if the delete never happened.

find('r-page-undo') being non-null is also true if delete-comment-reply silently no-oped, so the undo path isn't actually pinned. Assert the row is gone between the dispatch and the undo call.

💚 Proposed tweak
-    Livewire::test('pages::review-page', ['slug' => 'test-project'])
+    $component = Livewire::test('pages::review-page', ['slug' => 'test-project'])
         ->set('comments', [[
             'id' => $root->id,
             'fileId' => 'abc123',
             'file' => 'src/Foo.php',
             'side' => 'right',
             'body' => 'Root',
             'replies' => [$payload],
         ]])
         ->dispatch('delete-comment-reply', replyId: $reply->id)
-        ->assertDispatched('undo-available', type: 'delete-reply', message: 'Reply deleted')
-        ->call('undo', 'delete-reply', $payload);
+        ->assertDispatched('undo-available', type: 'delete-reply', message: 'Reply deleted');
+
+    expect(CommentReply::query()->find('r-page-undo'))->toBeNull();
+
+    $component->call('undo', 'delete-reply', $payload);
 
     expect(CommentReply::query()->find('r-page-undo'))->not->toBeNull();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/Livewire/ReviewPageTest.php` around lines 641 - 668, Strengthen
the test around the Livewire `delete-comment-reply` dispatch by asserting that
`CommentReply::query()->find('r-page-undo')` is null before calling `undo`. Keep
the existing final non-null assertion to verify the undo restores the reply,
ensuring both deletion and restoration are covered.
tests/Unit/Livewire/CommentsDrawerTest.php (1)

126-153: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Loop over filters hides which case failed.

If codex-cli regresses, the failure reads the same as follow-up. A dataset gives you the label for free.

♻️ Optional tweak
-test('filter matches reply body and author without changing root count', function () {
+test('filter matches reply body and author without changing root count', function (string $filter) {
     CommentReply::factory()
         ->for(Comment::findOrFail('c-open-a'))
         ->agent('codex-cli', 'Codex')
         ->create(['body' => 'Agent follow-up']);
 
-    foreach (['follow-up', 'codex-cli', 'Codex'] as $filter) {
-        $component = Livewire::test('comments-drawer', [
-            'repoPath' => '/tmp/proj',
-            'projectId' => $this->project->id,
-        ])->set('open', true)->set('filter', $filter);
+    $component = Livewire::test('comments-drawer', [
+        'repoPath' => '/tmp/proj',
+        'projectId' => $this->project->id,
+    ])->set('open', true)->set('filter', $filter);
 
-        expect(collect($component->get('groupedComments'))->flatten(1)->pluck('id')->all())->toBe(['c-open-a'])
-            ->and($component->get('totalCount'))->toBe(2);
-    }
-});
+    expect(collect($component->get('groupedComments'))->flatten(1)->pluck('id')->all())->toBe(['c-open-a'])
+        ->and($component->get('totalCount'))->toBe(2);
+})->with(['body' => 'follow-up', 'author key' => 'codex-cli', 'author label' => 'Codex']);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/Livewire/CommentsDrawerTest.php` around lines 126 - 153, Update
the filter assertions in the test named “filter matches reply body and author
without changing root count” to use a labeled dataset or equivalent per-case
naming instead of looping over an unlabeled array, so failures identify whether
“follow-up”, “codex-cli”, or “Codex” failed. Preserve the existing assertions
and coverage for all three filters.
app/Actions/ResolveCommentAnchorAction.php (1)

148-150: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the redundant outer collect().

CommentReply::collect() already returns a plain array of DTOs; wrapping it in another collect(...)->map(...)->all() just adds an unnecessary Collection instantiation per comment row.

♻️ Proposed simplification
-                'replies' => collect(CommentReply::collect($row['replies'] ?? []))
-                    ->map(fn (CommentReply $reply): array => $reply->toArray())
-                    ->all(),
+                'replies' => array_map(
+                    static fn (CommentReply $reply): array => $reply->toArray(),
+                    CommentReply::collect($row['replies'] ?? []),
+                ),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Actions/ResolveCommentAnchorAction.php` around lines 148 - 150, In the
replies transformation within ResolveCommentAnchorAction, remove the redundant
outer collect/map/all chain around CommentReply::collect. Use
CommentReply::collect($row['replies'] ?? []) directly as the replies value,
preserving the existing fallback for missing replies.
app/Console/Benchmark/DiffFixtureFactory.php (1)

241-252: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Hoist the comment id and use the enum for authorType.

Line 244 recomputes the same hash('xxh128', ...) as line 234 for every reply, and 'agent'/'human' are hardcoded while authorKey already uses CommentAuthor::UI_KEY — the fixture will silently drift if the enum values ever change.

♻️ Proposed tidy-up
+            $commentId = 'comment-'.hash('xxh128', "{$fileId}-{$i}");
+
             $comments[] = [
-                'id' => 'comment-'.hash('xxh128', "{$fileId}-{$i}"),
+                'id' => $commentId,
                 'fileId' => $fileId,
@@
                 'replies' => collect($repliesPerComment > 0 ? range(1, $repliesPerComment) : [])
-                    ->map(fn (int $reply): array => [
+                    ->map(fn (int $reply): array => [
                         'id' => "r-benchmark-{$i}-{$reply}",
-                        'commentId' => 'comment-'.hash('xxh128', "{$fileId}-{$i}"),
-                        'authorType' => $reply % 2 === 0 ? 'agent' : 'human',
+                        'commentId' => $commentId,
+                        'authorType' => ($reply % 2 === 0
+                            ? CommentAuthorType::Agent
+                            : CommentAuthorType::Human)->value,
                         'authorKey' => $reply % 2 === 0 ? 'codex-cli' : CommentAuthor::UI_KEY,

Requires use App\Enums\CommentAuthorType;.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/Console/Benchmark/DiffFixtureFactory.php` around lines 241 - 252, Update
the fixture’s surrounding comment construction to compute the hashed comment ID
once and reuse that value in the replies mapping instead of recalculating it per
reply. Import and use CommentAuthorType enum values for the reply authorType
branches, keeping the existing authorKey selection and reply data unchanged.
resources/views/components/comment-replies.blade.php (1)

15-24: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Author-identity derivation belongs on the reply DTO, not in the view.

Lines 16–17 reach into CommentAuthorType::Human and CommentAuthor::UI_KEY from Blade to decide both the display name and whether Edit/Delete render. The same ownership rule almost certainly exists server-side in the update/delete actions, so the two can drift — and the "can I edit this?" predicate is the kind of thing you don't want defined twice. A CommentReply::isOwnedByUi() / displayAuthor() pair would keep the view thin.

As per path instructions, Livewire/Blade views are "Thin UI adapters delegating to Actions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/views/components/comment-replies.blade.php` around lines 15 - 24,
Move author-identity derivation out of the Blade block and into the reply DTO,
adding or reusing CommentReply::isOwnedByUi() and displayAuthor() for ownership
and display-name decisions. Update comment-replies.blade.php to call those DTO
methods for the author label and Edit/Delete visibility, removing direct
references to CommentAuthorType and CommentAuthor::UI_KEY while preserving
existing fallback labels and edited-state handling.

Source: Path instructions

resources/views/livewire/⚡comments-drawer.blade.php (1)

93-93: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

$wire.$refresh() re-runs the whole drawer query for one thread update.

Every reply mutation anywhere refreshes the entire drawer (full query + regroup) rather than the affected thread, and the morph may reset the per-comment Alpine expanded state mid-interaction. Fine at current scale — worth revisiting if the drawer grows, since the rest of the page already uses scoped targeting.

As per coding guidelines, "Use event dispatch for targeted child updates, with scoped identifiers and payloads".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@resources/views/livewire/`⚡comments-drawer.blade.php at line 93, Replace the
global `@comment-thread-updated.window` handler in the comments drawer with a
scoped event-dispatch flow that identifies the affected thread and carries its
update payload. Update only the matching thread component or DOM target instead
of calling $wire.$refresh() for the entire drawer, while preserving each
comment’s Alpine expanded state.

Source: Coding guidelines

tests/Unit/Actions/LoadCommentsDrawerActionTest.php (1)

26-47: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider covering wildcard filter input.

LoadCommentsDrawerAction matches in SQL with like '%'.$filter.'%' (unescaped) but recomputes isReplyFilterMatch in PHP with Str::contains. A filter of % or _ therefore matches everything in the query yet nothing in the PHP predicate, so rows appear without their threads expanding. A case here would pin down whichever behaviour you consider correct.

As per coding guidelines, "Every code change must be programmatically tested."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Unit/Actions/LoadCommentsDrawerActionTest.php` around lines 26 - 47,
Extend the LoadCommentsDrawerAction coverage with wildcard filter cases such as
“%” and “_”, asserting consistent behavior between SQL filtering and the PHP
isReplyFilterMatch computation, including whether matching replies expand their
threads. Update the implementation if needed so the chosen wildcard behavior is
preserved and covered by the test.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agents/20260727-comment-replies-plan-feedback.md`:
- Line 21: Update the undo semantics in the plan around the “Undo toast
concurrency” section to consistently specify a LIFO stack that preserves
multiple pending undo entries. Remove the statement that a second delete drops
the first undo, and ensure the implementation and regression-test guidance
references the same stack behavior for reply, root, and consecutive comment
deletions.

In `@app/Actions/CommentReplyWorkflowAction.php`:
- Around line 62-103: Update CommentReplyWorkflowAction::restore so a missing
parent comment during the restore-to-mutation flow does not propagate
ModelNotFoundException. Catch the exception from restoreCommentReply->handle or
mutation, and return the existing soft-failure/no-op response with an
appropriate toast instead of surfacing an unhandled error; preserve normal
restoration behavior when the parent comment exists.

In `@app/Actions/DeleteCommentThreadsAction.php`:
- Around line 38-45: Update DeleteCommentThreadsAction to use the scoped
database query as the deletion authority instead of filtering the UI-provided
$comments collection. Accept the requested comment IDs, load and snapshot the
matching root comments internally, and retain only the empty-result guard; move
any page-state pruning responsibility to the workflow/UI layer.

In `@app/Console/Benchmark/PerfScenarioRunner.php`:
- Around line 334-359: Update filterDrawerReplies() so the project, comments,
and comment replies are created once during benchmark setup rather than on every
invocation. Keep the seeded dataset small, ensure resetState() does not remove
or recreate it between rounds, and leave each benchmark iteration focused on
LoadCommentsDrawerAction::handle().

In `@database/migrations/2026_07_27_113228_create_comment_replies_table.php`:
- Around line 11-26: Update the Schema::create callback defining the
comment_replies table to explicitly declare a : void return type, without
changing the table columns, constraints, or indexes.

In `@resources/views/livewire/`⚡comments-drawer.blade.php:
- Around line 174-200: Update the body button’s click behavior in the comments
drawer so submitted comments toggle expanded only when replyCount is greater
than zero; otherwise call select(...) like non-submitted comments. Add
aria-expanded and aria-controls for the submitted toggle state, matching the
reply-count disclosure control, and preserve the existing behavior for comments
with replies.

---

Nitpick comments:
In `@app/Actions/ResolveCommentAnchorAction.php`:
- Around line 148-150: In the replies transformation within
ResolveCommentAnchorAction, remove the redundant outer collect/map/all chain
around CommentReply::collect. Use CommentReply::collect($row['replies'] ?? [])
directly as the replies value, preserving the existing fallback for missing
replies.

In `@app/Actions/RestoreCommentReplyAction.php`:
- Around line 30-39: Update the return statement in the restore action to
refresh the existing saved model in place with refresh() before converting it
via CommentReplyData::fromArray, replacing the nullable fresh() call and
preserving the current returned data.

In `@app/Console/Benchmark/DiffFixtureFactory.php`:
- Around line 241-252: Update the fixture’s surrounding comment construction to
compute the hashed comment ID once and reuse that value in the replies mapping
instead of recalculating it per reply. Import and use CommentAuthorType enum
values for the reply authorType branches, keeping the existing authorKey
selection and reply data unchanged.

In `@resources/views/components/comment-replies.blade.php`:
- Around line 15-24: Move author-identity derivation out of the Blade block and
into the reply DTO, adding or reusing CommentReply::isOwnedByUi() and
displayAuthor() for ownership and display-name decisions. Update
comment-replies.blade.php to call those DTO methods for the author label and
Edit/Delete visibility, removing direct references to CommentAuthorType and
CommentAuthor::UI_KEY while preserving existing fallback labels and edited-state
handling.

In `@resources/views/livewire/`⚡comments-drawer.blade.php:
- Line 93: Replace the global `@comment-thread-updated.window` handler in the
comments drawer with a scoped event-dispatch flow that identifies the affected
thread and carries its update payload. Update only the matching thread component
or DOM target instead of calling $wire.$refresh() for the entire drawer, while
preserving each comment’s Alpine expanded state.

In `@tests/Browser/CommentRepliesTest.php`:
- Around line 141-143: Replace the raw JavaScript click in the
CommentRepliesTest flow with the semantic Playwright locator for the “Show
submitted comments” label, using getByLabel(...)->click() on the existing page
object and preserving the surrounding test behavior.

In `@tests/Unit/Actions/LoadCommentsDrawerActionTest.php`:
- Around line 26-47: Extend the LoadCommentsDrawerAction coverage with wildcard
filter cases such as “%” and “_”, asserting consistent behavior between SQL
filtering and the PHP isReplyFilterMatch computation, including whether matching
replies expand their threads. Update the implementation if needed so the chosen
wildcard behavior is preserved and covered by the test.

In `@tests/Unit/Livewire/CommentsDrawerTest.php`:
- Around line 126-153: Update the filter assertions in the test named “filter
matches reply body and author without changing root count” to use a labeled
dataset or equivalent per-case naming instead of looping over an unlabeled
array, so failures identify whether “follow-up”, “codex-cli”, or “Codex” failed.
Preserve the existing assertions and coverage for all three filters.

In `@tests/Unit/Livewire/ReviewPageTest.php`:
- Around line 641-668: Strengthen the test around the Livewire
`delete-comment-reply` dispatch by asserting that
`CommentReply::query()->find('r-page-undo')` is null before calling `undo`. Keep
the existing final non-null assertion to verify the undo restores the reply,
ensuring both deletion and restoration are covered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 6ea47e23-b5e7-4bb5-93fc-d4ceaa8bdb71

📥 Commits

Reviewing files that changed from the base of the PR and between 450d29a and 4922bf6.

📒 Files selected for processing (72)
  • agents/20260727-comment-replies-plan-feedback.md
  • agents/20260727-comment-replies-plan.md
  • app/Actions/AddCommentReplyAction.php
  • app/Actions/CommentReplyWorkflowAction.php
  • app/Actions/ContextCommentWorkflowAction.php
  • app/Actions/CreateCommentThreadSnapshotsAction.php
  • app/Actions/DeleteCommentReplyAction.php
  • app/Actions/DeleteCommentThreadsAction.php
  • app/Actions/LoadCommentThreadAction.php
  • app/Actions/LoadCommentsDrawerAction.php
  • app/Actions/LoadContextCommentsAction.php
  • app/Actions/ResolveCommentAnchorAction.php
  • app/Actions/ResolveContextCommentAnchorAction.php
  • app/Actions/RestoreCommentReplyAction.php
  • app/Actions/RestoreCommentThreadsAction.php
  • app/Actions/RestoreDiscardedFileAction.php
  • app/Actions/ReviewCommentWorkflowAction.php
  • app/Actions/SessionStateAction.php
  • app/Actions/UpdateCommentReplyAction.php
  • app/Concerns/ManagesCommentReplies.php
  • app/Concerns/ReviewPage/ManagesReviewTrash.php
  • app/Console/Benchmark/BenchmarkIsolation.php
  • app/Console/Benchmark/DiffFixtureFactory.php
  • app/Console/Benchmark/PerfScenarioRunner.php
  • app/DTOs/Comment.php
  • app/DTOs/CommentAuthor.php
  • app/DTOs/CommentReply.php
  • app/DTOs/CommentReplyMutation.php
  • app/DTOs/CommentThreadDeletion.php
  • app/DTOs/CommentThreadSnapshot.php
  • app/Enums/CommentAuthorType.php
  • app/Enums/CommentSurface.php
  • app/Models/Comment.php
  • app/Models/CommentReply.php
  • composer.json
  • database/factories/CommentReplyFactory.php
  • database/migrations/2026_07_27_113228_create_comment_replies_table.php
  • public/js/comment-thread.js
  • resources/CLAUDE.md
  • resources/views/components/comment-display.blade.php
  • resources/views/components/comment-replies.blade.php
  • resources/views/livewire/⚡comments-drawer.blade.php
  • resources/views/livewire/⚡diff-file.blade.php
  • resources/views/pages/⚡context-page.blade.php
  • resources/views/pages/⚡review-page.blade.php
  • tests/Arch/LayerDependenciesTest.php
  • tests/Browser/CommentRepliesTest.php
  • tests/Browser/CommentsDrawerTest.php
  • tests/Js/comment-thread.test.js
  • tests/Performance/PerfScenarioRunnerTest.php
  • tests/Unit/Actions/CommentReplyWorkflowActionTest.php
  • tests/Unit/Actions/ContextCommentWorkflowActionTest.php
  • tests/Unit/Actions/DeleteCommentThreadsActionTest.php
  • tests/Unit/Actions/ExportContextFeedbackActionTest.php
  • tests/Unit/Actions/ExportReviewActionTest.php
  • tests/Unit/Actions/LoadCommentsDrawerActionTest.php
  • tests/Unit/Actions/LoadContextCommentsActionTest.php
  • tests/Unit/Actions/ResolveCommentAnchorActionTest.php
  • tests/Unit/Actions/ResolveContextCommentAnchorActionTest.php
  • tests/Unit/Actions/RestoreCommentThreadsActionTest.php
  • tests/Unit/Actions/RestoreDiscardedFileActionTest.php
  • tests/Unit/Actions/ReviewCommentWorkflowActionTest.php
  • tests/Unit/Actions/SessionStateLoadTest.php
  • tests/Unit/CommentTest.php
  • tests/Unit/Console/BenchmarkPerformanceCommandTest.php
  • tests/Unit/DTOs/CommentReplyTest.php
  • tests/Unit/DTOs/CommentThreadSnapshotTest.php
  • tests/Unit/Livewire/CommentsDrawerTest.php
  • tests/Unit/Livewire/ContextPageTest.php
  • tests/Unit/Livewire/ReviewPageBranchDivergenceTest.php
  • tests/Unit/Livewire/ReviewPageTest.php
  • tests/Unit/Models/CommentReplyTest.php

Comment thread agents/20260727-comment-replies-plan-feedback.md Outdated
Comment thread app/Actions/CommentReplyWorkflowAction.php
Comment thread app/Actions/DeleteCommentThreadsAction.php Outdated
Comment thread app/Console/Benchmark/PerfScenarioRunner.php
Comment thread database/migrations/2026_07_27_113228_create_comment_replies_table.php Outdated
Comment thread resources/views/livewire/⚡comments-drawer.blade.php
fgilio and others added 8 commits July 27, 2026 16:47
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
@fgilio
fgilio enabled auto-merge (squash) July 27, 2026 16:15
@fgilio
fgilio merged commit 5e7dbbf into main Jul 27, 2026
15 checks passed
@fgilio
fgilio deleted the feat/comment-replies branch July 27, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant