Skip to content

feat: add ppg feedback command and Mac app feedback button#124

Open
2witstudios wants to merge 1 commit intomainfrom
ppg/feedback
Open

feat: add ppg feedback command and Mac app feedback button#124
2witstudios wants to merge 1 commit intomainfrom
ppg/feedback

Conversation

@2witstudios
Copy link
Owner

@2witstudios 2witstudios commented Feb 27, 2026

Summary

  • Add ppg feedback --title "..." --body "..." [--label bug|feature|feedback] [--json] CLI command that creates GitHub issues via gh issue create, falling back to opening a pre-filled browser URL when gh is unavailable
  • Add feedback button (speech bubble icon) to the Mac app sidebar footer bar, next to the gear/settings button
  • Add native feedback sheet (FeedbackViewController) with category picker (Bug / Feature Request / General Feedback), title field, body text view, and submit/cancel buttons

Test plan

  • Run npm run build && node dist/cli.js feedback --title "Test" --body "Test body" --json — should create a GitHub issue via gh CLI
  • Test browser fallback by temporarily removing gh from PATH
  • Build Xcode project — verify feedback button appears in sidebar footer
  • Click feedback button — verify sheet opens with category/title/body fields
  • Submit feedback from Mac app — verify issue is created on GitHub
  • npm run typecheck passes

Summary by CodeRabbit

New Features

  • Added feedback submission capability to the macOS application with a new sidebar button and submission form
  • Added feedback command to the CLI supporting GitHub integration via gh CLI or browser-based submission
  • Feedback submissions support title, body, custom labels, and optional JSON output format

Add a `ppg feedback` CLI command that creates GitHub issues via `gh issue
create` when available, falling back to opening a pre-filled issue URL in
the browser. Includes --title, --body, --label, and --json flags.

Add a feedback button (exclamationmark.bubble icon) to the Mac app sidebar
footer that opens a native sheet with category picker, title/body fields,
and submits via the CLI command.
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

📝 Walkthrough

Walkthrough

The changes introduce a new feedback feature across the macOS application and CLI. The macOS app adds a feedback button in the sidebar, a FeedbackViewController for collecting user input with validation and submission, and integrates this flow into the dashboard. The CLI adds a feedback command that submits issues to GitHub either via the gh CLI or by opening a browser URL with pre-filled parameters.

Changes

Cohort / File(s) Summary
macOS Feedback UI
PPG CLI/PPG CLI/DashboardSplitViewController.swift, PPG CLI/PPG CLI/FeedbackViewController.swift, PPG CLI/PPG CLI/SidebarViewController.swift
Introduces a complete feedback submission flow: sidebar button triggers FeedbackViewController sheet with form validation, theme integration, and background submission via PPGService.
CLI Feedback Command
src/cli.ts, src/commands/feedback.ts
Adds CLI feedback command with GitHub issue creation support via gh CLI or browser fallback, includes JSON output mode and environment-aware execution.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Sidebar as SidebarViewController
    participant Dashboard as DashboardSplitViewController
    participant FeedbackVC as FeedbackViewController
    participant Service as PPGService
    participant Shell

    User->>Sidebar: Click feedback button
    Sidebar->>Dashboard: onFeedbackClicked()
    Dashboard->>FeedbackVC: Present as sheet
    User->>FeedbackVC: Fill form & click Submit
    FeedbackVC->>FeedbackVC: Validate title & body
    alt Validation fails
        FeedbackVC->>FeedbackVC: Shake animation
    else Validation succeeds
        FeedbackVC->>FeedbackVC: Disable controls, show spinner
        FeedbackVC->>FeedbackVC: Determine project root
        FeedbackVC->>Service: runPPGCommand(shellCmd)
        Service->>Shell: Execute PPG command
        Shell-->>Service: Command result
        alt Success
            Service-->>FeedbackVC: Result on main thread
            FeedbackVC->>User: Show success alert
            FeedbackVC->>FeedbackVC: Dismiss sheet
        else Error
            Service-->>FeedbackVC: Error with output
            FeedbackVC->>User: Show error alert
        end
    end
Loading
sequenceDiagram
    participant User
    participant CLI as cli.ts
    participant Feedback as feedbackCommand
    participant GH as gh CLI
    participant Browser
    participant GitHub

    User->>CLI: feedback --title "..." --body "..."
    CLI->>Feedback: feedbackCommand(options)
    Feedback->>Feedback: isGhAvailable()
    alt gh CLI available
        Feedback->>GH: gh issue create ...
        GH->>GitHub: Create issue
        GitHub-->>GH: Issue URL
        GH-->>Feedback: URL in stdout
    else gh not available
        Feedback->>Feedback: buildIssueUrl(params)
        Feedback->>Browser: open(url)
        Browser->>GitHub: Navigate to issue template
    end
    Feedback->>User: Output result (JSON or text)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A feedback button hops into the sidebar, so fine,
Users share their thoughts, and GitHub issues align,
From macOS clicks to CLI commands so swift,
The feedback feature bounces—a developer's gift! ✨
Whether gh or browser, the path's crystal clear,
More voices heard, improvements appear! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: adding a feedback command and Mac app feedback button feature.
Description check ✅ Passed The description covers all required sections (What, Why, How) with clear implementation details and a comprehensive test plan, meeting template requirements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ppg/feedback

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/commands/feedback.ts (1)

51-59: Add error handling for browser open failure.

Similar to the gh path, the browser fallback should handle potential errors when opening the URL.

🛡️ Proposed fix
     const issueUrl = buildIssueUrl(title, body, label);
     info('Opening GitHub issue in browser (gh CLI not found)');
-    await execa('open', [issueUrl]);
-
-    if (json) {
-      output({ success: true, issueUrl, method: 'browser' }, true);
-    } else {
-      success(`Opened browser: ${issueUrl}`);
+    try {
+      await execa('open', [issueUrl]);
+      if (json) {
+        output({ success: true, issueUrl, method: 'browser' }, true);
+      } else {
+        success(`Opened browser: ${issueUrl}`);
+      }
+    } catch (err) {
+      const message = err instanceof Error ? err.message : String(err);
+      if (json) {
+        output({ success: false, error: message, method: 'browser' }, true);
+      } else {
+        throw new Error(`Failed to open browser: ${message}`);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/feedback.ts` around lines 51 - 59, The browser fallback
currently calls execa('open', [issueUrl]) without handling failures; wrap that
call in a try/catch around the execa invocation (the block using buildIssueUrl
and execa('open', [issueUrl])) and on error log the failure and return the
appropriate JSON or human output: if json is true call output({ success: false,
issueUrl, method: 'browser', error: error.message }, true) otherwise call
info/error or failure similar to success (e.g., error(`Failed to open browser:
${error.message}`)); ensure you still don't throw so downstream flow
(output/success) only runs on success and mirror the existing behavior used in
the gh path.
PPG CLI/PPG CLI/FeedbackViewController.swift (1)

194-200: Shake animation requires layer-backed view.

The shake animation uses view.layer?.add(...), but this will silently do nothing if the view isn't layer-backed. Ensure wantsLayer = true is set before animating.

♻️ Proposed fix
 private func shake(_ view: NSView) {
+    view.wantsLayer = true
     let animation = CAKeyframeAnimation(keyPath: "position.x")
     animation.values = [0, -6, 6, -4, 4, -2, 2, 0].map { view.frame.midX + $0 }
     animation.duration = 0.4
     animation.calculationMode = .linear
     view.layer?.add(animation, forKey: "shake")
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PPG` CLI/PPG CLI/FeedbackViewController.swift around lines 194 - 200, The
shake(_ view: NSView) function may silently fail when the view isn't
layer-backed; before adding the CAKeyframeAnimation to view.layer, ensure the
view is layer-backed by setting view.wantsLayer = true (and verify view.layer is
non-nil) so the animation is applied — update the shake(_:) implementation to
enable layer-backing on the passed NSView before calling view.layer?.add(...).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@PPG` CLI/PPG CLI/FeedbackViewController.swift:
- Around line 189-191: Replace the force-unwrap of view.window in the
FeedbackViewController where alert.beginSheetModal(for: view.window!) is called:
use optional binding (e.g., guard/if let) to safely unwrap view.window, bail out
or handle the nil case gracefully if the window is nil, and call
alert.beginSheetModal(for: window) only when the unwrapped window is available;
update any early-return behavior so dismissal (self?.dismiss) only runs when
appropriate.

In `@src/commands/feedback.ts`:
- Line 53: The call to execa('open', [issueUrl]) is macOS-specific; update the
feedback command to open URLs cross-platform by either switching to the
well-tested "open" npm package or by selecting the OS-specific opener before
calling execa: detect process.platform and use 'open' on darwin, 'xdg-open' on
linux, and the Windows equivalent ('start' via shell) for win32; modify the code
around the execa invocation in src/commands/feedback.ts (the location with the
execa('open', [issueUrl]) call) to perform this platform check or replace it
with the "open" package so opening the issue URL works on Linux and Windows as
well.
- Around line 42-49: Wrap the execa call that runs the GitHub CLI ("const result
= await execa('gh', ghArgs, execaEnv);") in a try/catch and handle rejections
from the "gh issue create" invocation: on error, parse the thrown Error (or
execa error) and emit a user-friendly message (use output({ success: false,
error: err.message }, json) when json is requested or call error(`Failed to
create issue: ${err.message}`) for human output), and ensure the function
exits/returns after handling the error so downstream code (the success() branch)
does not run.

---

Nitpick comments:
In `@PPG` CLI/PPG CLI/FeedbackViewController.swift:
- Around line 194-200: The shake(_ view: NSView) function may silently fail when
the view isn't layer-backed; before adding the CAKeyframeAnimation to
view.layer, ensure the view is layer-backed by setting view.wantsLayer = true
(and verify view.layer is non-nil) so the animation is applied — update the
shake(_:) implementation to enable layer-backing on the passed NSView before
calling view.layer?.add(...).

In `@src/commands/feedback.ts`:
- Around line 51-59: The browser fallback currently calls execa('open',
[issueUrl]) without handling failures; wrap that call in a try/catch around the
execa invocation (the block using buildIssueUrl and execa('open', [issueUrl]))
and on error log the failure and return the appropriate JSON or human output: if
json is true call output({ success: false, issueUrl, method: 'browser', error:
error.message }, true) otherwise call info/error or failure similar to success
(e.g., error(`Failed to open browser: ${error.message}`)); ensure you still
don't throw so downstream flow (output/success) only runs on success and mirror
the existing behavior used in the gh path.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b5be50 and 3fca47f.

📒 Files selected for processing (5)
  • PPG CLI/PPG CLI/DashboardSplitViewController.swift
  • PPG CLI/PPG CLI/FeedbackViewController.swift
  • PPG CLI/PPG CLI/SidebarViewController.swift
  • src/cli.ts
  • src/commands/feedback.ts

Comment on lines +189 to +191
alert.beginSheetModal(for: view.window!) { [weak self] _ in
self?.dismiss(nil)
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid force unwrap on view.window!.

If view.window is nil (e.g., the view was dismissed before this code runs), this will crash. Use optional binding or a guard statement.

🛡️ Proposed fix
 private func showSuccessAndDismiss() {
     let alert = NSAlert()
     alert.messageText = "Feedback submitted"
     alert.informativeText = "Thank you for your feedback!"
     alert.alertStyle = .informational
-    alert.beginSheetModal(for: view.window!) { [weak self] _ in
-        self?.dismiss(nil)
-    }
+    guard let window = view.window else {
+        dismiss(nil)
+        return
+    }
+    alert.beginSheetModal(for: window) { [weak self] _ in
+        self?.dismiss(nil)
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PPG` CLI/PPG CLI/FeedbackViewController.swift around lines 189 - 191, Replace
the force-unwrap of view.window in the FeedbackViewController where
alert.beginSheetModal(for: view.window!) is called: use optional binding (e.g.,
guard/if let) to safely unwrap view.window, bail out or handle the nil case
gracefully if the window is nil, and call alert.beginSheetModal(for: window)
only when the unwrapped window is available; update any early-return behavior so
dismissal (self?.dismiss) only runs when appropriate.

Comment on lines +42 to +49
const result = await execa('gh', ghArgs, execaEnv);
const issueUrl = result.stdout.trim();

if (json) {
output({ success: true, issueUrl, method: 'gh' }, true);
} else {
success(`Issue created: ${issueUrl}`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add error handling for gh issue create failure.

If gh issue create fails (e.g., authentication issues, network errors, invalid label), the promise will reject with an unhandled error. Consider catching and providing a user-friendly error message.

🛡️ Proposed fix
-    const result = await execa('gh', ghArgs, execaEnv);
-    const issueUrl = result.stdout.trim();
-
-    if (json) {
-      output({ success: true, issueUrl, method: 'gh' }, true);
-    } else {
-      success(`Issue created: ${issueUrl}`);
-    }
+    try {
+      const result = await execa('gh', ghArgs, execaEnv);
+      const issueUrl = result.stdout.trim();
+
+      if (json) {
+        output({ success: true, issueUrl, method: 'gh' }, true);
+      } else {
+        success(`Issue created: ${issueUrl}`);
+      }
+    } catch (err) {
+      const message = err instanceof Error ? err.message : String(err);
+      if (json) {
+        output({ success: false, error: message, method: 'gh' }, true);
+      } else {
+        throw new Error(`Failed to create issue: ${message}`);
+      }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/feedback.ts` around lines 42 - 49, Wrap the execa call that runs
the GitHub CLI ("const result = await execa('gh', ghArgs, execaEnv);") in a
try/catch and handle rejections from the "gh issue create" invocation: on error,
parse the thrown Error (or execa error) and emit a user-friendly message (use
output({ success: false, error: err.message }, json) when json is requested or
call error(`Failed to create issue: ${err.message}`) for human output), and
ensure the function exits/returns after handling the error so downstream code
(the success() branch) does not run.

} else {
const issueUrl = buildIssueUrl(title, body, label);
info('Opening GitHub issue in browser (gh CLI not found)');
await execa('open', [issueUrl]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Browser fallback uses macOS-specific open command.

The open command is macOS-specific. On Linux, xdg-open is used, and on Windows, start is used. Consider using a cross-platform approach or documenting this limitation.

♻️ Cross-platform alternative
+import { platform } from 'node:os';
+
+function getOpenCommand(): string {
+  switch (platform()) {
+    case 'darwin': return 'open';
+    case 'win32': return 'start';
+    default: return 'xdg-open';
+  }
+}
+
 // In the else branch:
-    await execa('open', [issueUrl]);
+    const openCmd = getOpenCommand();
+    await execa(openCmd, [issueUrl], { shell: platform() === 'win32' });

Alternatively, consider using the open npm package which handles cross-platform URL opening.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/commands/feedback.ts` at line 53, The call to execa('open', [issueUrl])
is macOS-specific; update the feedback command to open URLs cross-platform by
either switching to the well-tested "open" npm package or by selecting the
OS-specific opener before calling execa: detect process.platform and use 'open'
on darwin, 'xdg-open' on linux, and the Windows equivalent ('start' via shell)
for win32; modify the code around the execa invocation in
src/commands/feedback.ts (the location with the execa('open', [issueUrl]) call)
to perform this platform check or replace it with the "open" package so opening
the issue URL works on Linux and Windows as well.

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