Skip to content

Allow app web helpers in Backpex extensions - #2170

Open
Flo0807 wants to merge 4 commits into
developfrom
agent/allow-app-web-helpers-in-extensions
Open

Allow app web helpers in Backpex extensions#2170
Flo0807 wants to merge 4 commits into
developfrom
agent/allow-app-web-helpers-in-extensions

Conversation

@Flo0807

@Flo0807 Flo0807 commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • import Phoenix component helpers for item actions, filters, and metrics without registering a second declarative compile hook
  • allow custom fields to use their application's LiveComponent entrypoint via live_component: {MyAppWeb, :live_component}
  • keep full Phoenix.Component setup in Backpex's built-in filters and value metric because they declare attr
  • document application Gettext/component/verified-route usage and the upgrade impact
  • add compile fixtures covering both use orders and host helpers under compiler verification

Why

PR #2055 fixed duplicate Phoenix.Component.Declarative @before_compile hooks for LiveResources. The same duplicate hook was still introduced by BackpexWeb, :item_action, :filter, and :metric when consumers also used MyAppWeb, :html.

Fields need a different path because they are real LiveComponents. The new option lets the host application's standard :live_component entrypoint perform the LiveComponent setup exactly once while Backpex adds only its field-specific aliases and helpers.

User impact

Item actions, custom filters, typed filters, and metrics may now use MyAppWeb, :html in either order to access application helpers. Extensions that declare their own attr or slot must explicitly use their application HTML entrypoint or Phoenix.Component.

Custom fields can use:

use Backpex.Field,
  config_schema: @config_schema,
  live_component: {MyAppWeb, :live_component}

Existing fields without this option keep the current Backpex LiveComponent setup.

Checks

  • MIX_ENV=test mix lint — 275 tests passed (100 doctests, 175 tests), Credo clean
  • mix test test/backpex/web_helpers_test.exs — passed with no compile or runtime diagnostics

@Flo0807 Flo0807 self-assigned this Aug 7, 2026
@Flo0807
Flo0807 requested a review from Copilot August 7, 2026 14:30
@Flo0807 Flo0807 added the breaking-change A breaking change label Aug 7, 2026
@Flo0807
Flo0807 marked this pull request as ready for review August 7, 2026 14:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves how Backpex extension modules (item actions, filters, metrics, and custom fields) access host-application web helpers (Gettext/components/verified routes) while avoiding duplicate Phoenix.Component.Declarative @before_compile hooks that can fail compilation under --warnings-as-errors.

Changes:

  • Introduce a BackpexWeb, :heex entrypoint and update :item_action, :filter, and :metric to import Phoenix.Component (instead of use), preventing duplicate declarative hooks when combined with MyAppWeb, :html.
  • Add a live_component: {MyAppWeb, :live_component} option to Backpex.Field so custom fields can reuse the host LiveComponent setup (and helpers) exactly once.
  • Add compiler-verification fixtures/tests plus documentation and upgrade notes covering the new usage patterns and required attr/slot setup.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/fixtures/web_helpers/extension_modules.fixture Compile fixture covering both use orders and host helper availability across extensions.
test/backpex/web_helpers_test.exs Test that compiling the fixture produces no compile/runtime diagnostics.
lib/backpex/metrics/value.ex Ensures Backpex’s built-in value metric keeps full Phoenix.Component setup for attr.
lib/backpex/filters/select.ex Ensures built-in select filter keeps full Phoenix.Component setup for attr.
lib/backpex/filters/range.ex Ensures built-in range filter keeps full Phoenix.Component setup for attr.
lib/backpex/filters/multi_select.ex Ensures built-in multi-select filter keeps full Phoenix.Component setup for attr.
lib/backpex/filters/boolean.ex Ensures built-in boolean filter keeps full Phoenix.Component setup for attr.
lib/backpex/field.ex Adds :live_component option handling so custom fields can use host LiveComponent entrypoint.
lib/backpex_web.ex Adds :heex + :field_helpers entrypoints and switches extension entrypoints to use :heex.
guides/upgrading/v0.19.md Upgrade notes explaining the behavior change and required actions for attr/slot.
guides/filter/custom-filter.md Docs for using host :html in custom/built-in filter macros and supported use orders.
guides/fields/custom-fields.md Docs for configuring host LiveComponent entrypoint in custom fields.
guides/actions/item-actions.md Docs for using host :html helpers in item actions (either use order).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/backpex/field.ex
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@Flo0807
Flo0807 requested a review from pehbehbeh August 7, 2026 18:13
Comment thread guides/upgrading/v0.19.md
end
```

## Backpex extension modules now import `Phoenix.Component`

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should move to 0.20

```elixir
use Backpex.Field,
config_schema: @config_schema,
live_component: {MyAppWeb, :live_component}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this fail on compile or on render? It should fail on compile.

(To be honest, I’m not a big fan of the syntax. Feels very much like a workaround. But I can’t think of anything better at the moment.)

Comment thread lib/backpex/field.ex
Comment on lines +243 to +245
if !Keyword.keyword?(opts) do
raise ArgumentError, "expected Backpex.Field options to be a keyword list, got: #{Macro.to_string(opts)}"
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖

What changed. Before the PR, Backpex.Field.__using__ passed the options through quote bind_quoted:, so whatever expression the caller wrote was evaluated inside the caller's module body at compile time. Any expression that produced a keyword list worked. The PR now inspects the options as syntax tree at macro-expansion time: it calls Macro.expand on them, requires the result to literally be a keyword list, pops :live_component out of it, and raises otherwise. Macro.expand only expands macros at the top level. It never evaluates attributes, variables, or function calls, so anything that isn't a written-out [key: value, ...] fails the check.

Shapes that break now. Each of these compiled fine before and now raises at compile time:

  • use Backpex.Field, @opts with the options stored in a module attribute. The AST expands to an attribute read, not a list. This is the case I verified.
  • use Backpex.Field, Keyword.merge(@base, config_schema: ...) or any function call producing the options.
  • use Backpex.Field, opts where opts is a variable bound earlier in the module body.
  • A wrapper macro in the app or a library that forwards options through bind_quoted. That pattern injects a variable at the inner use site, so a base module like this now
    fails:
defmacro __using__(opts) do
  quote bind_quoted: [opts: opts] do
    use Backpex.Field, opts   # opts is a variable here, not a literal list
  end
end

Shapes that still work. A literal keyword list whose values are expressions is fine, because only the outer list shape is inspected. use Backpex.Field, config_schema: @config_schema keeps working, as does a macro that expands directly into a keyword list. All built-in fields and the documented usage are in this group, so most users won't hit it.

Why it matters anyway. The failure is a compile error with a misleading message. Someone with a shared base field module sees expected Backpex.Field options to be a keyword list, got: Module.__get_attribute__(__MODULE__, :opts, 3, true) after a routine upgrade, with no hint that the fix is to inline the options.

Fix. Only pop :live_component when the options are a literal keyword list, and otherwise pass them through untouched with the default use Phoenix.LiveComponent. That restores the old behaviour for every non-literal case. The only limitation is that :live_component itself must be given literally, which is reasonable since it has to be resolved at compile time to emit a use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change A breaking change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants