Skip to content

Canonical wide events for deep links, menu clicks, updater outcomes, and context comment writes - #139

Merged
fgilio merged 5 commits into
mainfrom
claude/files-filter-state-loss-4jyent
Jul 21, 2026
Merged

Canonical wide events for deep links, menu clicks, updater outcomes, and context comment writes#139
fgilio merged 5 commits into
mainfrom
claude/files-filter-state-loss-4jyent

Conversation

@fgilio

@fgilio fgilio commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Addresses the warning-level findings from the 2026-07-14 (#136) and 2026-07-21 (#137) wide-events audits.

Changes

  • HandleDeepLink now owns a canonical deeplink.opened event emitted on every terminal outcome — completed, rejected (unsupported_url / missing_path / not_a_project), or error — with rfa.mode, rfa.route, rfa.path_hash, project id/slug, and rfa.duration_ms. Existing JSONL breadcrumbs are unchanged.
  • HandleMenuItemClicked now owns a canonical menu.item.clicked event with rfa.menu_id, outcome, and duration. Sub-handlers return their outcome (completed / cancelled when a dialog is dismissed) instead of void; unknown ids report skipped, throws report error. Project context is added to Context where a navigation resolves.
  • Updater listeners (UpdateAvailable / UpdateDownloaded in NativeAppServiceProvider) no longer hardcode rfa.outcome = 'completed' in finally: a catch flips the outcome to error with rfa.error_class and a stable reason before rethrowing, matching the softRefresh() reference pattern.
  • Context-page comment writes (createComment) own a canonical context.comment.written event with completed / rejected / skipped / error outcomes plus file id, draft flag, and duration. The diagnostic context.comment.rejected warning stays on the rejected branch; its payload keys move to snake_case to match surrounding convention.

Tests

  • HandleDeepLinkTest: canonical event + outcome/reason assertions for success, non-rfa URLs, and unresolvable paths.
  • HandleMenuItemClickedTest: completed (open-repo), cancelled (dialog dismissed), and skipped (unknown id) outcomes.
  • ContextPageTest: completed and rejected paths for context.comment.written, including the retained warning.

The updater catch-path has no direct test (NativePHP Notification::new() isn't cheaply fakeable); the logging arch tests cover the vocabulary and shape rules it must satisfy.

🤖 Generated with Claude Code

https://claude.ai/code/session_01M3Jts3UrUJYfdJFjvqnJZy


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved handling of invalid/unsupported deep links, missing paths, non-project paths, repository registration issues, update failures, and comment-write failures with clearer failure reasons.
  • Reliability

    • Standardized outcome tracking (completed/rejected/cancelled/error) across deep links, menu actions, repository opening/scanning, auto-updates, and comment writing.
    • Added consistent canonical event logging for successful operations.
  • Tests

    • Expanded feature/unit tests to verify deep-link/menu/comment event reporting and the associated outcome metadata.

…ext comment writes

Address the warning-level findings from the 2026-07-14 and 2026-07-21
wide-events audits (PRs #136/#137):

- HandleDeepLink now owns a canonical deeplink.opened event emitted on
  every terminal outcome (completed / rejected / error) with rfa.mode,
  rfa.route, rfa.path_hash, and project context.
- HandleMenuItemClicked now owns a canonical menu.item.clicked event
  with rfa.menu_id, outcome (completed / cancelled / skipped / error),
  and duration; sub-handlers report their outcome instead of void.
- The UpdateAvailable / UpdateDownloaded listeners no longer hardcode
  rfa.outcome = completed in finally: a catch now flips the outcome to
  error (with rfa.error_class and a stable reason) before rethrowing.
- Context-page comment writes own a canonical context.comment.written
  event (completed / rejected / skipped / error); the rejected branch
  keeps its diagnostic warning, whose payload keys move to snake_case.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3Jts3UrUJYfdJFjvqnJZy
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a049510c-23a8-43e8-941b-8470932c6d30

📥 Commits

Reviewing files that changed from the base of the PR and between 06d3430 and 61de8b9.

📒 Files selected for processing (2)
  • app/Listeners/HandleMenuItemClicked.php
  • tests/Feature/Listeners/HandleMenuItemClickedTest.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/Feature/Listeners/HandleMenuItemClickedTest.php
  • app/Listeners/HandleMenuItemClicked.php

📝 Walkthrough

Walkthrough

The change adds structured Laravel Context metadata and canonical logs across project opening, deep-link handling, menu actions, native updater events, and context comment writes. Tests cover completed, rejected, cancelled, skipped, partial, and error outcomes.

Changes

Runtime instrumentation

Layer / File(s) Summary
Project opening metadata
app/Actions/OpenProjectFromPathAction.php, app/Actions/OpenRepositoryDialogAction.php, tests/Unit/Actions/OpenProjectFromPathActionTest.php
Project-opening actions record missing-path, repository rejection, registration failure, and exception-class metadata in Context, with tests for rejection reasons.
Deep-link outcome tracking
app/Listeners/HandleDeepLink.php, tests/Feature/Listeners/HandleDeepLinkTest.php
Deep-link handling records URL, route, project, outcome, reason, exception, and duration metadata, logs deeplink.opened, and rethrows unexpected exceptions.
Menu action outcomes
app/Listeners/HandleMenuItemClicked.php, tests/Feature/Listeners/HandleMenuItemClickedTest.php
Menu handlers return explicit outcomes, record action-specific metadata, log menu.item.clicked, and distinguish picker cancellation, rejection, and errors.
Native updater outcomes
app/Providers/NativeAppServiceProvider.php
Updater listeners record update versions and computed completed or error outcomes while preserving exception metadata.
Context comment outcomes
resources/views/pages/⚡context-page.blade.php, tests/Unit/Livewire/ContextPageTest.php
Comment writes record project, file, draft, outcome, duration, rejection, and error metadata, then log context.comment.written.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant HandleDeepLink
  participant OpenProjectFromPathAction
  participant Context
  participant MainWindow
  participant Log
  HandleDeepLink->>Context: flush and record received URL metadata
  HandleDeepLink->>OpenProjectFromPathAction: resolve project from path
  OpenProjectFromPathAction->>Context: record failure metadata when resolution fails
  HandleDeepLink->>Context: record outcome and route metadata
  HandleDeepLink->>MainWindow: navigate to computed route
  HandleDeepLink->>Log: log deeplink.opened
Loading

Possibly related PRs

  • fgilio/rfa#79: Related Context-based instrumentation in the context comment flow.
  • fgilio/rfa#96: Related exception classification in project-opening actions.
  • fgilio/rfa#129: Related updater and project-opening error-context conventions.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% 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 accurately summarizes the main change: canonical wide events and outcomes for deep links, menu clicks, updater flows, and comment writes.
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.

@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: 1f75164dc1

ℹ️ 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 app/Listeners/HandleMenuItemClicked.php Outdated
Comment thread app/Listeners/HandleMenuItemClicked.php Outdated
Comment thread app/Listeners/HandleDeepLink.php Outdated
…nd scan failures

Address Codex review on #139: OpenProjectFromPathAction and
OpenRepositoryDialogAction collapse distinct failure modes into null,
which the new canonical events mislabeled.

- OpenProjectFromPathAction's swallowed-Throwable branch now marks the
  owner Context (rfa.reason = project_registration_failed +
  rfa.error_class), so HandleDeepLink records error instead of
  rejected/not_a_project for database or filesystem failures.
- OpenRepositoryDialogAction marks not_a_git_repository picks and
  registration failures via Context; HandleMenuItemClicked maps a null
  project to cancelled / rejected / error accordingly.
- scan-directory now reports partial (with repos_found / registered /
  already_tracked / failed counts) when any registration failed, and
  completed only for clean scans.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3Jts3UrUJYfdJFjvqnJZy

@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.

🧹 Nitpick comments (3)
app/Listeners/HandleMenuItemClicked.php (1)

31-66: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Consider hoisting the canonical-event scaffolding into a shared helper. This Context::flush()$startedAttry/catch(rethrow)/finally(outcome + duration_ms + Log::info) dance is duplicated verbatim here, in app/Listeners/HandleDeepLink.php, and across the updater listeners in app/Providers/NativeAppServiceProvider.php. Since this whole PR exists to make those wide events consistent, a single wrapper (e.g. a recordCanonicalEvent(string $log, Closure $body) that owns flush/timing/outcome/finally) would stop the field names and outcome semantics from quietly drifting apart the next time someone touches one copy but not the others. Purely a maintainability nicety — behavior here is correct as-is.

🤖 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/Listeners/HandleMenuItemClicked.php` around lines 31 - 66, Extract the
duplicated canonical-event scaffolding from HandleMenuItemClicked,
HandleDeepLink, and the updater listeners in NativeAppServiceProvider into one
shared helper such as recordCanonicalEvent(string $log, Closure $body). Have the
helper own Context::flush(), timing, outcome initialization and finalization,
error context, duration recording, and Log::info while preserving the existing
outcome semantics and rethrow behavior; update each listener to execute its
event-specific logic through the helper.
resources/views/pages/⚡context-page.blade.php (1)

258-281: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Rejected/skipped returns skip the skipRender() optimization used on the success path.

Both early returns leave $this->comments untouched, so there's no structural change to render — yet only the success branch (line 286) calls skipRender(). Every add-comment attempt that gets rejected or is a no-op (e.g. a bad screen-state click, or saving a blank textarea) triggers a full component re-render for nothing.

⚡ Proposed fix
                 \Illuminate\Support\Facades\Log::warning('context.comment.rejected', [
                     'reason' => $e->reason->value,
                     'file_id' => $fileId,
                     'side' => $side,
                     'start_line' => $startLine,
                     'end_line' => $endLine,
                 ]);
+
+                $this->skipRender();
+
                 return;
             }
         }

             if (! $comment) {
                 $outcome = 'skipped';
+
+                $this->skipRender();
 
                 return;
             }

As per path instructions, resources/views/pages/**/*.blade.php should "Use skipRender() for Livewire actions whose UI is handled client-side or by targeted child updates, and do not wait for a server response before showing locally predictable visual feedback."

🤖 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/pages/`⚡context-page.blade.php around lines 258 - 281, Call
skipRender() before both early returns in the ContextCommentRejectedException
catch block and the !$comment branch, matching the existing success path
behavior. Keep the outcome assignments and rejection logging unchanged.

Source: Path instructions

tests/Unit/Livewire/ContextPageTest.php (1)

235-253: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen the rejected-branch assertion and cover the still-untested skipped/error outcomes.

Log::shouldHaveReceived('warning')->once(); (line 249) doesn't check the message or payload, so it won't catch a regression that changes context.comment.rejected to something else. More importantly, this cohort's createComment() also introduces skipped (empty body) and error (rethrown \Throwable with rfa.error_class/rfa.reason = comment_write_failed) outcomes — neither is exercised by any test here.

✅ Suggested additions
Log::shouldHaveReceived('warning')->once()->with('context.comment.rejected', Mockery::type('array'));
test('addComment records a skipped outcome when the body is empty', function () {
    Log::spy();

    Livewire::test('pages::context-page', ['slug' => 'test-project'])
        ->call('addComment', 'file-1', 'file', null, null, '');

    Log::shouldHaveReceived('info')->once()->with('context.comment.written');
    expect(Context::get('rfa.outcome'))->toBe('skipped');
});

test('addComment records an error outcome and rethrows on unexpected failure', function () {
    app()->bind(ContextCommentWorkflowAction::class, fn () => new class
    {
        public function handle(mixed ...$args): never
        {
            throw new \RuntimeException('boom');
        }
    });

    Log::spy();

    expect(fn () => Livewire::test('pages::context-page', ['slug' => 'test-project'])
        ->call('addComment', 'file-1', 'file', null, null, 'hello'))
        ->toThrow(\RuntimeException::class);

    Log::shouldHaveReceived('info')->once()->with('context.comment.written');
    expect(Context::get('rfa.outcome'))->toBe('error')
        ->and(Context::get('rfa.reason'))->toBe('comment_write_failed');
});

As per path instructions, tests/**/*.php requires "Every code change must be programmatically tested," and both the skipped and error branches are new in this diff.

🤖 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/ContextPageTest.php` around lines 235 - 253, Strengthen
the rejected-outcome test for addComment to assert the warning uses
context.comment.rejected with an array payload. Add coverage for the empty-body
skipped outcome and for unexpected Throwable failures: verify the error is
rethrown, context.comment.written is logged, and Context records rfa.outcome as
error with rfa.reason set to comment_write_failed.

Source: Path instructions

🤖 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.

Nitpick comments:
In `@app/Listeners/HandleMenuItemClicked.php`:
- Around line 31-66: Extract the duplicated canonical-event scaffolding from
HandleMenuItemClicked, HandleDeepLink, and the updater listeners in
NativeAppServiceProvider into one shared helper such as
recordCanonicalEvent(string $log, Closure $body). Have the helper own
Context::flush(), timing, outcome initialization and finalization, error
context, duration recording, and Log::info while preserving the existing outcome
semantics and rethrow behavior; update each listener to execute its
event-specific logic through the helper.

In `@resources/views/pages/`⚡context-page.blade.php:
- Around line 258-281: Call skipRender() before both early returns in the
ContextCommentRejectedException catch block and the !$comment branch, matching
the existing success path behavior. Keep the outcome assignments and rejection
logging unchanged.

In `@tests/Unit/Livewire/ContextPageTest.php`:
- Around line 235-253: Strengthen the rejected-outcome test for addComment to
assert the warning uses context.comment.rejected with an array payload. Add
coverage for the empty-body skipped outcome and for unexpected Throwable
failures: verify the error is rethrown, context.comment.written is logged, and
Context records rfa.outcome as error with rfa.reason set to
comment_write_failed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 981a384c-21c0-4aeb-8112-6746e63df718

📥 Commits

Reviewing files that changed from the base of the PR and between abec7c5 and af4ecaf.

📒 Files selected for processing (9)
  • app/Actions/OpenProjectFromPathAction.php
  • app/Actions/OpenRepositoryDialogAction.php
  • app/Listeners/HandleDeepLink.php
  • app/Listeners/HandleMenuItemClicked.php
  • app/Providers/NativeAppServiceProvider.php
  • resources/views/pages/⚡context-page.blade.php
  • tests/Feature/Listeners/HandleDeepLinkTest.php
  • tests/Feature/Listeners/HandleMenuItemClickedTest.php
  • tests/Unit/Livewire/ContextPageTest.php

claude added 2 commits July 21, 2026 10:54
… outcomes

Address CodeRabbit nitpicks on #139:

- createComment now calls skipRender() on the rejected and skipped early
  returns — neither touches $comments, so the render was pure waste.
- Tighten the rejected-branch test to assert the warning event name and
  payload shape, and add coverage for the skipped (no comment produced)
  and error (rethrown Throwable) canonical outcomes.

The suggestion to hoist the canonical-event try/catch/finally into a
shared helper is intentionally skipped: the wide-events skill's owner
pattern is written inline at each logging owner by design, matching
softRefresh() and the updater listeners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3Jts3UrUJYfdJFjvqnJZy
…ll-project mapping

Apply the fgilio-review branch audit:

- OpenProjectFromPathAction now marks its own null causes via Context
  (path_not_found, not_a_git_repository), so the deep-link and dialog
  flows share one reason vocabulary instead of the deep-link relabeling
  not-a-repo as not_a_project (kept only as the addIf fallback).
- Rename outcomeForDismissedPicker to outcomeForNullProject: it maps
  all three null-project outcomes, not just dismissal.
- Hoist the twice-computed mode and path hash in HandleDeepLink into
  locals shared by Context and the diagnostic breadcrumb.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3Jts3UrUJYfdJFjvqnJZy

@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.

🧹 Nitpick comments (1)
tests/Unit/Livewire/ContextPageTest.php (1)

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

Use it() syntax and action-first descriptions.

Both tests deviate from the specified Pest testing conventions. As per coding guidelines and path instructions, Pest tests should use the it() syntax and start with action-first wording (e.g., "records" instead of "addComment records"). (Note: The arrow-function syntax guideline cannot be fully applied here since PHP does not support multi-statement arrow functions, but the action-first rule and it() syntax still apply.)

  • tests/Unit/Livewire/ContextPageTest.php#L255-L255: change to it('records a skipped outcome when the workflow produces no comment', function () {
  • tests/Unit/Livewire/ContextPageTest.php#L273-L273: change to it('records an error outcome and rethrows on unexpected failure', function () {
🤖 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/ContextPageTest.php` at line 255, Update both tests in
tests/Unit/Livewire/ContextPageTest.php at lines 255-255 and 273-273 to use it()
syntax and action-first descriptions: “records a skipped outcome when the
workflow produces no comment” and “records an error outcome and rethrows on
unexpected failure.”

Sources: Coding guidelines, Path instructions

🤖 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.

Nitpick comments:
In `@tests/Unit/Livewire/ContextPageTest.php`:
- Line 255: Update both tests in tests/Unit/Livewire/ContextPageTest.php at
lines 255-255 and 273-273 to use it() syntax and action-first descriptions:
“records a skipped outcome when the workflow produces no comment” and “records
an error outcome and rethrows on unexpected failure.”

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8bc655d4-90d4-457f-acf4-69561c242c79

📥 Commits

Reviewing files that changed from the base of the PR and between af4ecaf and 06d3430.

📒 Files selected for processing (6)
  • app/Actions/OpenProjectFromPathAction.php
  • app/Listeners/HandleDeepLink.php
  • app/Listeners/HandleMenuItemClicked.php
  • resources/views/pages/⚡context-page.blade.php
  • tests/Unit/Actions/OpenProjectFromPathActionTest.php
  • tests/Unit/Livewire/ContextPageTest.php
🚧 Files skipped from review as they are similar to previous changes (4)
  • app/Actions/OpenProjectFromPathAction.php
  • resources/views/pages/⚡context-page.blade.php
  • app/Listeners/HandleMenuItemClicked.php
  • app/Listeners/HandleDeepLink.php

@fgilio

fgilio commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

@codex review

@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: 06d3430836

ℹ️ 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 app/Listeners/HandleMenuItemClicked.php Outdated
… the project

A stale cached id falls back to the picker, but is_int($cachedId) still
read true, so both the canonical menu.item.clicked event and the
diagnostic breadcrumb attributed picker-based opens to the cache. Track
whether cache resolution actually produced the project instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M3Jts3UrUJYfdJFjvqnJZy
@fgilio
fgilio merged commit 450d29a into main Jul 21, 2026
15 checks passed
@fgilio
fgilio deleted the claude/files-filter-state-loss-4jyent branch July 21, 2026 16:26
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.

2 participants