Skip to content

[Prototype] POS UI extension intercept declaration (capabilities.intercepts)#8057

Draft
henryStelle wants to merge 9 commits into
mainfrom
henry/pos-intercept-failsafe-config
Draft

[Prototype] POS UI extension intercept declaration (capabilities.intercepts)#8057
henryStelle wants to merge 9 commits into
mainfrom
henry/pos-intercept-failsafe-config

Conversation

@henryStelle

@henryStelle henryStelle commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Prototype — not for merge. The CLI slice of a cross-repo POS intercept prototype. The isolated Core/server slice is shop/world#943060.

What this adds

Schema support for a UI extension to manually declare the intercept events planned for each target in shopify.extension.toml:

[[extensions.targeting]]
target = "pos.app.ready.data"
module = "./src/index.ts"

[extensions.targeting.capabilities]
intercepts = ["beforecheckout"]

intercepts is target-level configuration, not extension-level configuration. Event names are simply the same names exposed by the approved shopify.intercept() runtime API. The CLI carries the explicit declaration through the deploy flow; Core/server validates at deploy time which events are allowed for each target and API version.

This configuration proposal and the approved runtime APIs have separate responsibilities:

  • intercepts declares the events a target plans to register.
  • shopify.intercept() registers the actual runtime validators.
  • This prototype uses the existing, approved shopify.capabilities API to expose the merchant-approved validation severity for each declared event. The per-event severity capability names are proposed by this configuration contract.

For a declared beforecheckout event, the proposed capabilities are beforecheckout.error, beforecheckout.warning, and beforecheckout.info. beforecheckout.error permits ERROR, WARNING, and INFO; beforecheckout.warning permits WARNING and INFO; and beforecheckout.info permits INFO. Lower severities are explicitly cumulative in the returned array: error approval returns all three strings, warning approval returns beforecheckout.warning plus beforecheckout.info, and info approval returns only beforecheckout.info.

Implementation

  • Adds InterceptEventsSchema in packages/app/src/cli/models/extensions/schemas.ts for the structural array-of-strings and uniqueness rules.
  • Adds optional intercepts to the existing TargetCapabilitiesSchema used by each [[extensions.targeting]] entry. The CLI deliberately defines no event-name regex or separate grammar; Core validates the exact Intercept API event name against the target and API version.
  • Uses the existing UI extension transform and deploy flow, which carries each target's capabilities into the literal extension-point config.
  • Does not change extension-level capabilities or checkout's block_progress.
  • Adds focused schema tests for free-form string forwarding, non-string values, and duplicate declarations.

Why explicit target-level declarations are recommended

  • Developers manually and explicitly declare every intercept event their target plans to register. The declaration is reviewable and remains reliable when registration is indirect or dynamic.
  • Target-level granularity is intentional. Separate targets can register different event sets, and this leaves room for different target-specific configurations in the future.
  • Checkout's block_progress is not the right model. It is one boolean for a single buyer-journey blocking concept, while POS can intercept distinct workflow boundaries. Merchants need to know exactly where participation can occur and what severity they approved per event.
  • Core/server can validate target/event/API-version support at deploy time.
  • POS can compare declared events with validators actually registered through shopify.intercept() and detect a missing registration as an extension failure. This is particularly important for compliance validators, where failure to run can have legal implications.

Alternative explored

#8058 explores automatically deriving intercept events from extension source. That follows the CLI's preference for derived defaults, but it is not the recommended approach: source analysis is complex, incomplete, and cannot handle every valid registration pattern. Explicit declarations support deploy-time target/event/API-version validation and provide a complete runtime contract for registration verification and compliance failure detection, while also supporting merchant visibility.

Validation

  • pnpm test packages/app/src/cli/models/extensions/schemas.test.ts — 26 tests passed
  • pnpm nx run app:lint
  • pnpm nx run app:type-check
  • git diff --check

Related work

POS UI extensions can now declare, in shopify.extension.toml, which
host-mediated intercept events they participate in and whether they
intend to block progress on each. This is the event-scoped POS analogue
of checkout's single `capabilities.block_progress` capability.

TOML shape (top-level array of tables):

  [[intercepts]]
  event = "beforecheckout"
  block_progress = true

- event: required, validated against POS_INTERCEPT_EVENTS
  (beforecheckout, beforepayment) — extensible const
- block_progress: optional boolean, defaults to false
- duplicate events are rejected
- declarations flow through deployConfig for later backend wiring

Prototype: schema + parsing + validation + tests (Area B, step 1).
Assisted-By: devx/a246d980-dba0-4a06-b4d4-27c89d8ca9bf
Per design confirmation, replace the [[intercepts]] array-of-tables with
a single POS-specific capability: a string array of blockable events.

  [capabilities]
  intercepts = ["beforecheckout", "beforepayment"]

Membership in the array = the extension may block that event (collapses
"intercepts at all" vs "can block" into one blockable set, intended for
the prototype).

- Export shared CapabilitiesSchema; POS extends it with an optional
  `intercepts` array of POS_INTERCEPT_EVENTS enums. Shared block_progress
  boolean is untouched (checkout depends on it); intercepts is POS-only.
- Unknown events rejected with a clear message; duplicates rejected via
  a uniqueness superRefine on the array.
- deployConfig emits capabilities (incl. intercepts); mapping kept thin
  for the pending cross-repo field rename.
- Tests updated: valid/empty parse, coexists-with-shared-capabilities,
  unknown-event + duplicate rejection, deployConfig includes/omits.

Assisted-By: devx/a246d980-dba0-4a06-b4d4-27c89d8ca9bf
Drop the newly-added pos_ui_extension.test.ts. This is a prototype and
the tests should not gate it. All source/schema changes are kept.

Note: with the test gone, knip flags POS_INTERCEPT_EVENTS as an unused
export (the test was its only external consumer). The const is still
used internally by the schema; left as-is intentionally.

Assisted-By: devx/a246d980-dba0-4a06-b4d4-27c89d8ca9bf
The const is still used internally by the intercept schema, but has no
external consumer now that the prototype tests are gone. Keep it local
to satisfy knip; re-add `export` when real consumers land.

Assisted-By: devx/a246d980-dba0-4a06-b4d4-27c89d8ca9bf
@github-actions github-actions Bot added the Area: @shopify/cli @shopify/cli package issues label Jul 10, 2026
Intercept events are functionally equivalent to extension targets, which
are free-form strings validated server-side (the CLI never hardcodes the
valid target list). Match that precedent so the intercept event set stays
forward-compatible: new events can ship without a CLI release and won't be
rejected by older CLI installs.

- intercepts: array(enum(POS_INTERCEPT_EVENTS)) -> array(string) with a
  light lowercase-identifier format check (structural only, not a value
  allowlist). Server remains the source of truth for valid event names.
- Keep the duplicate-rejection superRefine (structural, forward-compatible).
- Delete the POS_INTERCEPT_EVENTS const entirely.

Assisted-By: devx/a246d980-dba0-4a06-b4d4-27c89d8ca9bf
Per design: intercept event names are lowercase a-z only, no digits.
Change the format check from /^[a-z][a-z0-9]*$/ to /^[a-z]+$/.
Everything else (array(string), dedupe refine) unchanged.

Assisted-By: devx/a246d980-dba0-4a06-b4d4-27c89d8ca9bf
Structural validation only; Core is the sole authority on which targets
support intercepts. Future-proof declaration shape on capabilities.intercepts.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: @shopify/cli @shopify/cli package issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant