Skip to content

Add embedded plugin directory mode#225

Open
thgO-O wants to merge 13 commits into
masterfrom
feature/plugins-embed-mode
Open

Add embedded plugin directory mode#225
thgO-O wants to merge 13 commits into
masterfrom
feature/plugins-embed-mode

Conversation

@thgO-O

@thgO-O thgO-O commented May 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds an embedded mode for the public plugin directory so BTCPay Server can render the Plugin Builder catalog inside Manage Plugins.

Resolves #114

Changes

  • Adds embed=1 support for the public directory list.
  • Adds embedded plugin details rendering for BTCPay drawer usage.
  • Supports host context messages for hidden plugin identifiers, selected plugin, color mode, BTCPay version, and pre-release state.
  • Filters installed/disabled plugins from the embedded directory.
  • Keeps the standalone public directory unchanged.

@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Review Change Stack

Note

.coderabbit.yaml has unrecognized properties

CodeRabbit is using all valid settings from your configuration. Unrecognized properties (listed below) have been ignored and may indicate typos or deprecated fields that can be removed.

⚠️ Parsing warnings (1)
Validation error: Unrecognized key: "auto_resolve_threads"
⚙️ Configuration instructions
  • Please see the configuration documentation for more information.
  • You can also validate your configuration using the online YAML validator.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

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: 2e9ccd74-6fe4-4fa5-a0a2-e406aa3ae8e5

📥 Commits

Reviewing files that changed from the base of the PR and between b807f30 and def55cf.

📒 Files selected for processing (9)
  • PluginBuilder.Tests/PublicTests/PluginDetailsUITests.cs
  • PluginBuilder.Tests/PublicTests/PublicDirectoryUITests.cs
  • PluginBuilder/Controllers/HomeController.cs
  • PluginBuilder/ViewModels/Home/PluginDetailsViewModel.cs
  • PluginBuilder/Views/Home/AllPlugins.cshtml
  • PluginBuilder/Views/Home/GetPluginDetails.cshtml
  • PluginBuilder/Views/Shared/_LayoutPublicModal.cshtml
  • PluginBuilder/Views/Shared/_PublicHeader.cshtml
  • PluginBuilder/wwwroot/js/public-embed.js
🚧 Files skipped from review as they are similar to previous changes (8)
  • PluginBuilder/Views/Shared/_LayoutPublicModal.cshtml
  • PluginBuilder.Tests/PublicTests/PublicDirectoryUITests.cs
  • PluginBuilder/Views/Shared/_PublicHeader.cshtml
  • PluginBuilder/Controllers/HomeController.cs
  • PluginBuilder.Tests/PublicTests/PluginDetailsUITests.cs
  • PluginBuilder/Views/Home/GetPluginDetails.cshtml
  • PluginBuilder/Views/Home/AllPlugins.cshtml
  • PluginBuilder/wwwroot/js/public-embed.js

Walkthrough

Adds iframe embed mode across controller, view-model, views, client script, and tests. Controllers accept includePreRelease/btcpayVersion/version/embed parameters and map versions to version+pre-release view models. Views (shared layout, header, directory, details) render embed-aware layouts, preserve compatibility/embed query params in links/forms, disable/hide review and install flows when embedded, and add a pre-release confirmation modal. A new public-embed.js posts pb:ready/pb:content-height, handles btcpay:host-context messages, hides specified plugin cards, emits selection/install messages, and tests validate embed interactions and pre-release behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • rockstardev
  • TChukwuleta
🚥 Pre-merge checks | ✅ 4 | ❌ 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 (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main addition: an embedded mode for the plugin directory.
Description check ✅ Passed The description is well-related to the changeset, explaining the purpose of the embedded mode and listing the specific changes made.
Linked Issues check ✅ Passed All primary objectives from issue #114 are addressed: iframe embedding support, host context message handling, and plugin filtering work as specified.
Out of Scope Changes check ✅ Passed All changes are scoped to embedding functionality: controller parameters, view models, UI updates, embed script, and UI tests. No unrelated modifications detected.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/plugins-embed-mode

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.

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
PluginBuilder/Views/Home/GetPluginDetails.cshtml (1)

15-20: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the embed “Write a review” link on the same plugin version.

In embed mode this URL is built from the canonical details route, so it drops version, btcpayVersion, and includePreRelease. Opening the standalone page from a versioned/compatibility-filtered drawer will jump to the default/latest details page instead.

Suggested fix
     var pluginUrl = Url.Action(nameof(HomeController.GetPluginDetails), "Home", new { pluginSlug = Model.Plugin.ProjectSlug }, Context.Request.Scheme, Context.Request.Host.ToString());
-    var writeReviewUrl = Model.EmbedMode ? $"{pluginUrl}`#write-review`" : "`#write-review`";
+    var standaloneDetailsUrl = Url.Action(nameof(HomeController.GetPluginDetails), "Home", new
+    {
+        pluginSlug = Model.Plugin.ProjectSlug,
+        version = Model.Plugin.Version,
+        btcpayVersion = string.IsNullOrEmpty(btcpayVersionRoute) ? null : btcpayVersionRoute,
+        includePreRelease = string.IsNullOrEmpty(includePreReleaseRoute) ? null : includePreReleaseRoute
+    });
+    var writeReviewUrl = Model.EmbedMode ? $"{standaloneDetailsUrl}`#write-review`" : "`#write-review`";
🤖 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 `@PluginBuilder/Views/Home/GetPluginDetails.cshtml` around lines 15 - 20, The
embed "Write a review" link loses version and filter query params because
pluginUrl is built without forwarding Model.PluginVersion, btcpayVersion, and
includePreRelease; when Model.EmbedMode is true the writeReviewUrl uses
pluginUrl which drops these values. Fix by appending the current version and
query params to pluginUrl when building writeReviewUrl in the view: read
Model.Plugin.Version (or Model.PluginVersion) plus
Context.Request.Query["btcpayVersion"] and
Context.Request.Query["includePreRelease"] (btcpayVersionRoute,
includePreReleaseRoute) and include them in the route/query string used by
writeReviewUrl (and preserve writeReviewTarget/writeReviewRel behavior for
EmbedMode).
PluginBuilder/Controllers/HomeController.cs (1)

540-588: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve version and compatibility state through review POST redirects.

These actions only round-trip embed. If a user posts from a details URL like ?version=1.2.3&btcpayVersion=2.1.0&includePreRelease=true, the redirect lands on the default/latest details page instead of the version they were viewing. That breaks the versioned embed flow this PR is adding.

Also applies to: 643-699

🤖 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 `@PluginBuilder/Controllers/HomeController.cs` around lines 540 - 588,
UpsertReview currently only preserves the "embed" query when redirecting back to
GetPluginDetails, so version-related query parameters (e.g. version,
btcpayVersion, includePreRelease) are lost and the user is sent to the
default/latest details page; update the redirect construction in UpsertReview
(the RedirectToAction call when owner and the final Redirect((url ?? "/") +
"`#reviews`")) to read Request.Query for version, btcpayVersion, includePreRelease
(and any other versioning params used by the details view) and add them into the
routeValues passed to Url.Action/RedirectToAction so the same
version/compatibility state is preserved; apply the same change to the other
similar handler(s) referenced around lines 643-699 (same pattern building
URL/redirect after posting reviews) and ensure PluginVersion parsing logic
remains compatible with the preserved query values.
🤖 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 `@PluginBuilder.Tests/PublicTests/PluginDetailsUITests.cs`:
- Around line 97-117: The test assumes the parent page is same-origin which is
false after SetContentAsync(about:blank); fix by ensuring the parent is
navigated to a real origin before accessing iframe.contentWindow or using
window.location.origin: either navigate the page to the detailsUrl origin (e.g.,
await tester.Page.GotoAsync(the origin) or construct a base HTML on that origin)
so contentWindow/document/location access works, or avoid same-origin access
entirely by using a frame handle APIs (Page.Frame / WaitForFrame /
Frame.EvaluateAsync) to locate the iframe by src and call postMessage (or use
targetOrigin='*' for postMessage calls) instead of reading
contentWindow.document/location directly; update places referencing
tester.Page!.SetContentAsync, WaitForFunctionAsync, EvaluateAsync, and the
contentWindow.postMessage/location checks accordingly.

In `@PluginBuilder/Views/Home/GetPluginDetails.cshtml`:
- Around line 206-208: The rendered links use a static Model.Plugin.Version
(asp-route-version) so when the client-side version picker swaps versions via JS
the hrefs still point to the original version; update the view and JS so links
stay in sync: render the anchors with data attributes (e.g., data-version-link
or a data-url-template containing a {version} placeholder) instead of a
hard-coded asp-route-version, and in the existing version-picker change handler
update all anchors with that data attribute by replacing {version} (and
preserving asp-route-btcpayVersion and asp-route-includePreRelease values) or by
rewriting the query param "version" in their hrefs whenever the picker changes;
apply the same change for the other group of links noted around the 239-250
region so rating/filter/sort clicks use the selected version.

---

Outside diff comments:
In `@PluginBuilder/Controllers/HomeController.cs`:
- Around line 540-588: UpsertReview currently only preserves the "embed" query
when redirecting back to GetPluginDetails, so version-related query parameters
(e.g. version, btcpayVersion, includePreRelease) are lost and the user is sent
to the default/latest details page; update the redirect construction in
UpsertReview (the RedirectToAction call when owner and the final Redirect((url
?? "/") + "`#reviews`")) to read Request.Query for version, btcpayVersion,
includePreRelease (and any other versioning params used by the details view) and
add them into the routeValues passed to Url.Action/RedirectToAction so the same
version/compatibility state is preserved; apply the same change to the other
similar handler(s) referenced around lines 643-699 (same pattern building
URL/redirect after posting reviews) and ensure PluginVersion parsing logic
remains compatible with the preserved query values.

In `@PluginBuilder/Views/Home/GetPluginDetails.cshtml`:
- Around line 15-20: The embed "Write a review" link loses version and filter
query params because pluginUrl is built without forwarding Model.PluginVersion,
btcpayVersion, and includePreRelease; when Model.EmbedMode is true the
writeReviewUrl uses pluginUrl which drops these values. Fix by appending the
current version and query params to pluginUrl when building writeReviewUrl in
the view: read Model.Plugin.Version (or Model.PluginVersion) plus
Context.Request.Query["btcpayVersion"] and
Context.Request.Query["includePreRelease"] (btcpayVersionRoute,
includePreReleaseRoute) and include them in the route/query string used by
writeReviewUrl (and preserve writeReviewTarget/writeReviewRel behavior for
EmbedMode).
🪄 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

Run ID: 0e262627-a4a2-4e45-a160-3e8e8384a56c

📥 Commits

Reviewing files that changed from the base of the PR and between 1dbdf20 and eb02569.

📒 Files selected for processing (6)
  • PluginBuilder.Tests/PublicTests/PluginDetailsUITests.cs
  • PluginBuilder.Tests/PublicTests/PublicDirectoryUITests.cs
  • PluginBuilder/Controllers/HomeController.cs
  • PluginBuilder/Views/Home/AllPlugins.cshtml
  • PluginBuilder/Views/Home/GetPluginDetails.cshtml
  • PluginBuilder/wwwroot/js/public-embed.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • PluginBuilder/Views/Home/AllPlugins.cshtml

Comment thread PluginBuilder.Tests/PublicTests/PluginDetailsUITests.cs
Comment thread PluginBuilder/Views/Home/GetPluginDetails.cshtml
@r1ckstardev

Copy link
Copy Markdown
Collaborator

Approve. Clean embed-mode architecture - postMessage protocol is fail-closed on install requests (uses verified hostOrigin), iframe navigation can't escape the plugin-builder origin (URL builder uses window.location.origin + encodeURIComponent), and embed-state propagates consistently through review/vote/delete forms. BtcPayHostVersionModelBinder is a reasonable cut at version normalization - the inline TODO about -rcN whitelisting is a fine follow-up. Tested locally against pr-225 branch (cb7c6de).

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
PluginBuilder/Views/Home/GetPluginDetails.cshtml (1)

15-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the embed “Write a review” link on the same selected version.

In embed mode this always opens the bare plugin details URL, so a user who was looking at a specific version or compatibility-filtered pre-release lands on a different page before reviewing. Please append the current version, btcpayVersion, and includePreRelease values here as well.

🤖 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 `@PluginBuilder/Views/Home/GetPluginDetails.cshtml` around lines 15 - 22, The
embed "Write a review" link ignores current query/route filters; update
construction so when building pluginUrl and subsequently writeReviewUrl in
GetPluginDetails.cshtml you include the current version, btcpayVersion and
includePreRelease values (use currentBtcpayVersion, currentIncludePreRelease and
the existing version value from Model or Context.Request.Query) as route/query
values only when non-empty; keep writeReviewTarget and writeReviewRel behavior
the same but point writeReviewUrl to the augmented pluginUrl (with version,
btcpayVersion, includePreRelease appended) so the embed opens the same selected
version/filters.
PluginBuilder/Controllers/HomeController.cs (1)

559-608: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the active details query across review mutations.

Lines 578, 603-607, 684-687, and 711-714 now redirect with only pluginSlug/sort, so a user viewing ?version=...&btcpayVersion=...&includePreRelease=... gets bounced back to the default details page after submit/vote/delete. That breaks the versioned details flow this PR is trying to preserve. Please thread the current details query through these POSTs and include it in the GetPluginDetails redirect.

Also applies to: 661-715

🤖 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 `@PluginBuilder/Controllers/HomeController.cs` around lines 559 - 608, The
redirects in UpsertReview currently only pass pluginSlug and sort, dropping the
active details query (version, btcpayVersion, includePreRelease, etc.); update
UpsertReview (and the other POST handlers that redirect to GetPluginDetails) to
preserve the current Request.Query by either: 1) extracting the relevant query
keys (e.g., "version","btcpayVersion","includePreRelease","sort") from
Request.Query and include them in the anonymous route values passed to
Url.Action(nameof(GetPluginDetails), "Home", ...) along with pluginSlug, or 2)
build the original query string from Request.Query and append it to the
generated URL before Redirect(); apply the same change to the other POST methods
in this file that call Redirect/RedirectToAction to GetPluginDetails so the
versioned details view is preserved.
🤖 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.

Outside diff comments:
In `@PluginBuilder/Controllers/HomeController.cs`:
- Around line 559-608: The redirects in UpsertReview currently only pass
pluginSlug and sort, dropping the active details query (version, btcpayVersion,
includePreRelease, etc.); update UpsertReview (and the other POST handlers that
redirect to GetPluginDetails) to preserve the current Request.Query by either:
1) extracting the relevant query keys (e.g.,
"version","btcpayVersion","includePreRelease","sort") from Request.Query and
include them in the anonymous route values passed to
Url.Action(nameof(GetPluginDetails), "Home", ...) along with pluginSlug, or 2)
build the original query string from Request.Query and append it to the
generated URL before Redirect(); apply the same change to the other POST methods
in this file that call Redirect/RedirectToAction to GetPluginDetails so the
versioned details view is preserved.

In `@PluginBuilder/Views/Home/GetPluginDetails.cshtml`:
- Around line 15-22: The embed "Write a review" link ignores current query/route
filters; update construction so when building pluginUrl and subsequently
writeReviewUrl in GetPluginDetails.cshtml you include the current version,
btcpayVersion and includePreRelease values (use currentBtcpayVersion,
currentIncludePreRelease and the existing version value from Model or
Context.Request.Query) as route/query values only when non-empty; keep
writeReviewTarget and writeReviewRel behavior the same but point writeReviewUrl
to the augmented pluginUrl (with version, btcpayVersion, includePreRelease
appended) so the embed opens the same selected version/filters.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a7288fe3-ce62-4c08-8034-ab7f9af2835d

📥 Commits

Reviewing files that changed from the base of the PR and between eb02569 and b807f30.

📒 Files selected for processing (5)
  • PluginBuilder.Tests/PublicTests/PluginDetailsUITests.cs
  • PluginBuilder/Controllers/HomeController.cs
  • PluginBuilder/ViewModels/Home/PluginDetailsViewModel.cs
  • PluginBuilder/Views/Home/GetPluginDetails.cshtml
  • PluginBuilder/wwwroot/js/public-embed.js

@r1ckstardev

Copy link
Copy Markdown
Collaborator

Stamp confirms on ef086be + b807f30. Both post-approve commits read clean:

  • ef086be fix: confirm pre-release plugin actions adds a versionPreReleasesQuery SQL fragment joining pre_release flags into the details query and replaces List<string>? with PluginDetailsVersionViewModel { Version, PreRelease } (PluginDetailsViewModel.cs:20 / HomeController.cs:483-491). The UX-side confirmation before installing a pre-release is a clean install-flow safety nudge, and the 99 new lines in PluginDetailsUITests.cs cover the surface.

  • b807f30 fix: keep review mutations out of embed mode drops the embed route param from UpsertReview / VoteReview / DeleteReview signatures + their redirects (HomeController.cs:563/578/606/667/685/696/713) and the corresponding asp-route-embed on the review-mutation forms (GetPluginDetails.cshtml:321/328). Net effect: write actions exit embed mode rather than persist it - consistent with the 026a1a0 fix: make embedded plugin details read-only posture from earlier in the chain. Tighter iframe-isolation surface than what I approved at cb7c6de, not looser.

Approve stands at head b807f30.

@thgO-O thgO-O force-pushed the feature/plugins-embed-mode branch from b807f30 to def55cf Compare June 1, 2026 03:00
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.

[Refactor] Migrate plugin manager to iframe-based integration

2 participants