Add comment reply threads - #140
Conversation
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>
|
Warning Review limit reached
Next review available in: 10 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (14)
📝 WalkthroughWalkthroughAdds 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. ChangesComment reply foundation
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
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. Comment |
There was a problem hiding this comment.
💡 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".
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
Co-authored-by: Codex CLI <fgilio+codex-cli@publica.la>
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (9)
tests/Browser/CommentRepliesTest.php (1)
141-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRaw
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 winThe final assertion passes even if the delete never happened.
find('r-page-undo')being non-null is also true ifdelete-comment-replysilently no-oped, so the undo path isn't actually pinned. Assert the row is gone between the dispatch and theundocall.💚 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 valueLoop over filters hides which case failed.
If
codex-cliregresses, the failure reads the same asfollow-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 valueDrop the redundant outer
collect().
CommentReply::collect()already returns a plain array of DTOs; wrapping it in anothercollect(...)->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 valueHoist 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 whileauthorKeyalready usesCommentAuthor::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 winAuthor-identity derivation belongs on the reply DTO, not in the view.
Lines 16–17 reach into
CommentAuthorType::HumanandCommentAuthor::UI_KEYfrom 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. ACommentReply::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
expandedstate 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 winConsider covering wildcard filter input.
LoadCommentsDrawerActionmatches in SQL withlike '%'.$filter.'%'(unescaped) but recomputesisReplyFilterMatchin PHP withStr::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
📒 Files selected for processing (72)
agents/20260727-comment-replies-plan-feedback.mdagents/20260727-comment-replies-plan.mdapp/Actions/AddCommentReplyAction.phpapp/Actions/CommentReplyWorkflowAction.phpapp/Actions/ContextCommentWorkflowAction.phpapp/Actions/CreateCommentThreadSnapshotsAction.phpapp/Actions/DeleteCommentReplyAction.phpapp/Actions/DeleteCommentThreadsAction.phpapp/Actions/LoadCommentThreadAction.phpapp/Actions/LoadCommentsDrawerAction.phpapp/Actions/LoadContextCommentsAction.phpapp/Actions/ResolveCommentAnchorAction.phpapp/Actions/ResolveContextCommentAnchorAction.phpapp/Actions/RestoreCommentReplyAction.phpapp/Actions/RestoreCommentThreadsAction.phpapp/Actions/RestoreDiscardedFileAction.phpapp/Actions/ReviewCommentWorkflowAction.phpapp/Actions/SessionStateAction.phpapp/Actions/UpdateCommentReplyAction.phpapp/Concerns/ManagesCommentReplies.phpapp/Concerns/ReviewPage/ManagesReviewTrash.phpapp/Console/Benchmark/BenchmarkIsolation.phpapp/Console/Benchmark/DiffFixtureFactory.phpapp/Console/Benchmark/PerfScenarioRunner.phpapp/DTOs/Comment.phpapp/DTOs/CommentAuthor.phpapp/DTOs/CommentReply.phpapp/DTOs/CommentReplyMutation.phpapp/DTOs/CommentThreadDeletion.phpapp/DTOs/CommentThreadSnapshot.phpapp/Enums/CommentAuthorType.phpapp/Enums/CommentSurface.phpapp/Models/Comment.phpapp/Models/CommentReply.phpcomposer.jsondatabase/factories/CommentReplyFactory.phpdatabase/migrations/2026_07_27_113228_create_comment_replies_table.phppublic/js/comment-thread.jsresources/CLAUDE.mdresources/views/components/comment-display.blade.phpresources/views/components/comment-replies.blade.phpresources/views/livewire/⚡comments-drawer.blade.phpresources/views/livewire/⚡diff-file.blade.phpresources/views/pages/⚡context-page.blade.phpresources/views/pages/⚡review-page.blade.phptests/Arch/LayerDependenciesTest.phptests/Browser/CommentRepliesTest.phptests/Browser/CommentsDrawerTest.phptests/Js/comment-thread.test.jstests/Performance/PerfScenarioRunnerTest.phptests/Unit/Actions/CommentReplyWorkflowActionTest.phptests/Unit/Actions/ContextCommentWorkflowActionTest.phptests/Unit/Actions/DeleteCommentThreadsActionTest.phptests/Unit/Actions/ExportContextFeedbackActionTest.phptests/Unit/Actions/ExportReviewActionTest.phptests/Unit/Actions/LoadCommentsDrawerActionTest.phptests/Unit/Actions/LoadContextCommentsActionTest.phptests/Unit/Actions/ResolveCommentAnchorActionTest.phptests/Unit/Actions/ResolveContextCommentAnchorActionTest.phptests/Unit/Actions/RestoreCommentThreadsActionTest.phptests/Unit/Actions/RestoreDiscardedFileActionTest.phptests/Unit/Actions/ReviewCommentWorkflowActionTest.phptests/Unit/Actions/SessionStateLoadTest.phptests/Unit/CommentTest.phptests/Unit/Console/BenchmarkPerformanceCommandTest.phptests/Unit/DTOs/CommentReplyTest.phptests/Unit/DTOs/CommentThreadSnapshotTest.phptests/Unit/Livewire/CommentsDrawerTest.phptests/Unit/Livewire/ContextPageTest.phptests/Unit/Livewire/ReviewPageBranchDivergenceTest.phptests/Unit/Livewire/ReviewPageTest.phptests/Unit/Models/CommentReplyTest.php
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>
Closes:
Related:
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
comment_repliestable with deterministic ordering and cascading root deletion.Testing
Results: 1,732 PHP tests, 349 JavaScript tests, 206 browser tests, and 3 performance tests passed. One browser test remained skipped.
Deployment
comment_repliesmigrationSummary by CodeRabbit
New Features
Bug Fixes