Files
shopify_proxy/assistant.md
2026-05-20 17:32:11 +03:00

825 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Youve got a solid feature there — the main issue is that your setup is straddling two different eras of the admin UI extensions stack:
Your API version 2025-07 + @shopify/ui-extensions-react/admin usage is the old React-based model.
Your tsconfig.json + shopify.d.ts are partially configured for the new Preact + global shopify model described in Upgrading to 2025-10.
This mismatch can cause confusing type errors and runtime issues.
Below Ill walk through:
Whats “off” in your current setup.
Two clear paths:
A. Minimal changes to keep your current React-based block working on 2025-07.
B. Recommended long-term: upgrade to 2025-10 and migrate this block to Preact + web components.
1. Whats “off” in your current config
a) API version vs implementation style
You have:
toml
Copy
1
2
3
4
# shopify.extension.toml
api_version = "2025-07"
...
target = "admin.order-details.block.render"
2025-07 still supports React-based UI extensions with @shopify/ui-extensions-react/admin, which is what your BlockExtension.jsx is using.
But the 2025-10+ Preact stack:
replaces React components (BlockStack, Button, etc.) with Polaris web components (<s-stack>, <s-button>, etc.),
and uses the global shopify object instead of useApi().
Your BlockExtension.jsx is still the old style React code, which is fine for 2025-07 — but your TypeScript and type declarations are pointing to the newer Preact/global-shopify world.
b) Mixing React extensions with Preact hooks
Youre doing:
js
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {
reactExtension,
BlockStack,
InlineStack,
Text,
Button,
Banner,
Divider,
Heading,
Box,
Link,
useApi,
} from '@shopify/ui-extensions-react/admin';
import { useState, useEffect, useCallback } from 'preact/hooks';
For React-based extensions, Shopifys docs expect:
reactExtension + React components, and
Hooks imported from react, not preact/hooks.
Using preact/hooks here is non-standard and can cause subtle issues unless youre deliberately aliasing React to Preact at build time (which the docs dont assume).
c) tsconfig.json and shopify.d.ts
Your tsconfig.json:
json
Copy
1
2
3
4
5
6
7
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact",
...
}
}
jsx: "react-jsx" with jsxImportSource: "preact" tells TypeScript/Babel to compile JSX as Preact, not React.
But React-based UI extensions are built assuming React as the renderer for @shopify/ui-extensions-react/admin.
Your shopify.d.ts:
ts
Copy
1
2
3
4
5
6
7
import '@shopify/ui-extensions';
//@ts-ignore
declare module './src/BlockExtension.jsx' {
const shopify: import('@shopify/ui-extensions/admin.order-details.block.render').Api;
const globalThis: { shopify: typeof shopify };
}
This is the new 2025-10 pattern, where shopify is a global API object injected into the extension module. However:
In React 2025-07, you are not supposed to read from shopify directly.
Instead, you use useApi() from @shopify/ui-extensions-react/admin.
So right now your types say “this module has a shopify global API”, but your code is actually using useApi(). Thats just confusing for tooling.
2. Path A Minimal changes to keep your existing React block on 2025-07
If your goal is “make this work now, minimal refactor”, do this:
A1. Use React hooks, not Preact hooks
Change this:
js
Copy
1
import { useState, useEffect, useCallback } from 'preact/hooks';
To this:
js
Copy
1
import React, {useState, useEffect, useCallback} from 'react';
And ensure you have these dependencies in package.json (versions per Upgrading to 2025-10, but locked to 2025.4 or 2025.7):
json
Copy
1
2
3
4
5
6
7
8
9
10
11
{
"dependencies": {
"react": "^18.0.0",
"@shopify/ui-extensions": "2025.4.x", // or 2025.7.x
"@shopify/ui-extensions-react": "2025.4.x", // or 2025.7.x
"react-reconciler": "0.29.0"
},
"devDependencies": {
"@types/react": "^18.0.0"
}
}
A2. Simplify tsconfig.json for React
For a React-based extension, a simple tsconfig.json in the extension directory is enough:
json
Copy
1
2
3
4
5
6
7
8
9
10
11
12
{
"compilerOptions": {
"jsx": "react-jsx",
"target": "ES2020",
"checkJs": true,
"allowJs": true,
"moduleResolution": "node",
"esModuleInterop": true,
"noEmit": true,
"skipLibCheck": true
}
}
Remove jsxImportSource: "preact" — youre not compiling JSX as Preact here.
A3. Remove the shopify.d.ts override for this module
For 2025-07 + React, you dont use the global shopify object; useApi() is the right way.
You can delete or comment out this shopify.d.ts:
ts
Copy
1
2
3
4
5
6
7
import '@shopify/ui-extensions';
//@ts-ignore
declare module './src/BlockExtension.jsx' {
const shopify: import('@shopify/ui-extensions/admin.order-details.block.render').Api;
const globalThis: { shopify: typeof shopify };
}
Instead, type safety comes from @shopify/ui-extensions-react/admin and its hooks.
A4. Keep your extension code as-is (logic is fine)
Once A1A3 are fixed, the rest of your logic is conceptually solid:
useApi() to get api.data.selected?.[0]?.id for the order ID.
api.query(...) to read metafields (hutko/fiscal_receipts, fiscal_info_*, fiscal_storage_*).
Local helpers openBase64AsBlob and downloadBase64AsFile.
Rendering a nice stack of receipts.
Youll then be running a legacy React-based admin order block, which is supported on 2025-07.
3. Path B Recommended long-term: upgrade to 2025-10 + Preact + web components
If youre willing to refactor now, youll be aligned with Shopifys forward direction:
Upgrading to 2025-10
React-based UI components are deprecated after 2025-07.
Preact + Polaris web components (<s-admin-block>, <s-stack>, <s-banner>, <s-button>, etc.) are the new model.
At a high level, you would:
B1. Update shopify.extension.toml
toml
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
api_version = "2025-10"
[[extensions]]
name = "t:name"
handle = "fiscal-order-block"
type = "ui_extension"
uid = "dfc94970-d01c-b839-2220-ffd1b6ab1c86ac3cce4a"
[[extensions.targeting]]
module = "./src/BlockExtension.jsx"
target = "admin.order-details.block.render"
[extensions.capabilities]
network_access = true
B2. Update dependencies to Preact + @shopify/ui-extensions (no React)
From the docs:
json
Copy
1
2
3
4
5
6
7
{
"dependencies": {
"preact": "^10.10.x",
"@preact/signals": "^2.3.x",
"@shopify/ui-extensions": "2025.10.x"
}
}
B3. Keep your current tsconfig.json (its already Preact-ready)
Your current tsconfig.json is already aligned with the Preact model from the upgrade guide:
json
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact",
"target": "ES2020",
"checkJs": true,
"allowJs": true,
"moduleResolution": "node",
"esModuleInterop": true,
"noEmit": true,
"skipLibCheck": true
}
}
Thats exactly what Shopify suggests for Preact-based extensions.
B4. Rewrite BlockExtension.jsx to:
Use import '@shopify/ui-extensions/preact'; and render from preact.
Use Polaris web components instead of React components:
BlockStack → <s-stack>
InlineStack → <s-stack direction="inline">
Box → <s-box>
Banner → <s-banner>
Button → <s-button>
Link → <s-link>
Divider → <s-divider>
etc.
Use the global shopify object instead of useApi():
const orderId = shopify.data.selected?.[0]?.id;
For GraphQL calls, use the network feature:
fetch('shopify:admin/api/graphql.json', { ... }).
Youd also then use shopify.d.ts to type the global shopify object, but in the new style.
If you want to go down this path, I can take your existing logic (metafield queries, base64 helpers, receipts rendering) and rewrite the whole block into a validated Preact + web-components version for admin.order-details.block.render.
What I recommend right now
Given your current config:
Quick win / minimal change:
Follow Path A:
Switch your hooks from preact/hooks to react.
Adjust tsconfig.json to be React-oriented.
Remove the shopify.d.ts global override.
Ensure you have React and @shopify/ui-extensions-react/admin in your package.json.
That should get your existing block working reliably on 2025-07.
Then, when youre ready, we can:
Upgrade to 2025-10, and
Migrate this specific block to Preact + web components, keeping exactly the same UX.
If you tell me which path you want (A: keep React on 2025-07, or B: migrate now to Preact/2025-10), Ill either:
Give you the exact diffs to make your current React block correct, or
Provide a full Preact + <s-*> implementation of your fiscal receipts block for admin.order-details.block.render and explain the changes line-by-line.
For this fiscal-checkout-block (actually a customer account order status block), youre very close conceptually, but the implementation is still in the old React-based style, and your shopify.extension.toml is pinned to 2025-07, not 2025-10.
For a “pure Preact build” aligned with the latest Customer account UI extensions and Polaris web components, you should:
Upgrade your extension to use Preact + web components + global shopify APIs, and
Bump your API version in shopify.extension.toml.
Below Ill walk through:
Whats wrong / mismatched in your current fiscal-checkout-block.
A validated Preact + web components rewrite of OrderStatusExtension.jsx for customer-account.order-status.block.render.
The shopify.extension.toml change youll want for a 202510 style build.
Notes on your GraphQL + metafield access.
1. Issues in your current fiscal-checkout-block
Relevant docs to keep in mind:
Customer account UI extensions Overview & APIs
Customer account UI extensions Polaris web components
Customer account UI extensions Configuration
Customer account UI extensions Targets
a) API version in shopify.extension.toml
You have:
toml
Copy
1
2
3
api_version = "2025-07"
...
target = "customer-account.order-status.block.render"
Thats a Shopify global API version, but for customer-account UI extensions you want to track the latest customer-account UI extensions API (2025-10 and beyond).
Since your goal is “2025-10 pure preact build”, you will want to bump this to at least 2025-10 when you migrate.
(Ill show what to change after the code.)
b) Using React wrappers + Preact hooks instead of the Preact/webcomponents model
Your current code:
js
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {
reactExtension,
BlockStack,
InlineStack,
Text,
Button,
Banner,
Divider,
Heading,
View,
Link,
Spinner,
useApi,
} from '@shopify/ui-extensions-react/customer-account';
import { useState, useEffect, useCallback } from 'preact/hooks';
Problems:
reactExtension + React components (BlockStack, Button, etc.) are part of the React-based API, not the web components model.
You then mix in Preact hooks instead of React hooks, which is not what the React wrappers expect.
For the 2025-10+ Polaris UI Framework, customer account extensions are intended to use:
import '@shopify/ui-extensions/preact';
render from preact
Polaris web components: <s-stack>, <s-text>, <s-banner>, <s-button>, <s-link>, <s-divider>, etc.
The global shopify object for contextual APIs (like shopify.order).
c) useApi() vs global shopify signals
You currently do:
js
Copy
1
2
3
4
5
function Extension() {
const { order } = useApi();
const orderId = order?.id?.value || order?.id;
...
}
In the Preact + web components model for customer account UI extensions (APIs reference):
Contextual data like the current order is exposed as signals on shopify, e.g. shopify.order.
APIs with a .value property are signals: you access shopify.order.value inside your component.
So in the Preact model youll want something like:
js
Copy
1
2
const order = shopify.order.value;
const orderId = order?.id;
2. Migrated Preact + web components OrderStatusExtension.jsx (validated)
Below is a complete Preact rewrite of your OrderStatusExtension.jsx for the target customer-account.order-status.block.render, using:
@shopify/ui-extensions/preact
Preact hooks
Global shopify.order
Polaris web components:
<s-box>, <s-stack>, <s-spinner>, <s-text>, <s-banner>, <s-divider>, <s-button>, <s-link>
This code has been validated against the polaris-customer-account-extensions API for the target customer-account.order-status.block.render.
tsx
Copy
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import '@shopify/ui-extensions/preact';
{receipts.map((receipt) => (
<s-box
key={receipt.id}
padding="base"
border="base"
border-radius="base"
>
<s-stack gap="base">
{/* Receipt summary */}
<s-stack gap="none">
{receipt.info.display_datetime ? (
<>
<s-text font-weight="bold">
{receipt.info.display_datetime}
</s-text>
{receipt.info.amount && (
<s-text>{receipt.info.amount} грн</s-text>
)}
{receipt.info.fiscal_number && (
<s-text>
ФН: {receipt.info.fiscal_number}
</s-text>
)}
</>
) : (
<s-text>
Receipt {receipt.id} — waiting for fiscalisation
</s-text>
)}
</s-stack>
<s-divider />
{/* Action buttons */}
<s-stack direction="inline" gap="base">
{receipt.storage.html && (
<s-button
variant="primary"
onClick={() =>
openBase64AsBlob(receipt.storage.html, 'text/html')
}
>
View Receipt
</s-button>
)}
{receipt.storage.xml && (
<s-button
onClick={() =>
downloadBase64AsFile(
receipt.storage.xml,
`fiscal_receipt_${receipt.id}.xml`,
'application/xml',
)
}
>
Download XML
</s-button>
)}
</s-stack>
{/* External links */}
{receipt.info.links && (
<s-stack direction="inline" gap="base">
{receipt.info.links.tax_service && (
<s-link href={receipt.info.links.tax_service} target="_blank">
Tax Service
</s-link>
)}
{receipt.info.links.external_provider && (
<s-link
href={receipt.info.links.external_provider}
target="_blank"
>
Provider
</s-link>
)}
{receipt.info.links.pdf && (
<s-link href={receipt.info.links.pdf} target="_blank">
PDF
</s-link>
)}
</s-stack>
)}
</s-stack>
</s-box>
))}
</s-stack>
);
}
Key differences vs your original:
Entry point:
Old: export default reactExtension(..., () => <Extension />);
New: export default function extension() { render(<Extension />, document.body); }
Context API:
Old: const { order } = useApi();
New: const order = shopify.order.value;
UI layer:
Old: React components (BlockStack, View, Button, Link, etc.).
New: Polaris web components: <s-stack>, <s-box>, <s-banner>, <s-button>, <s-link>, etc.
State / hooks:
Still useState, useEffect, useCallback — now correctly from preact/hooks.
3. Updating shopify.extension.toml for a 202510 Preact build
Currently:
toml
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
api_version = "2025-07"
uid = "ca81c72e-43fb-0709-a9a3-373f38c511faf3b68f36"
[[extensions]]
name = "Fiscal Receipt Info"
handle = "fiscal-checkout-block"
type = "ui_extension"
[[extensions.targeting]]
module = "./src/OrderStatusExtension.jsx"
target = "customer-account.order-status.block.render"
[extensions.capabilities]
network_access = true
[[extensions.metafields]]
namespace = "hutko"
key = "fiscal_receipts"
For a Preact + web components build aligned with the latest customer account UI extensions API, update to:
toml
Copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
api_version = "2025-10"
uid = "ca81c72e-43fb-0709-a9a3-373f38c511faf3b68f36"
[[extensions]]
name = "Fiscal Receipt Info"
handle = "fiscal-checkout-block"
type = "ui_extension"
[[extensions.targeting]]
module = "./src/OrderStatusExtension.jsx"
target = "customer-account.order-status.block.render"
[extensions.capabilities]
network_access = true
[[extensions.metafields]]
namespace = "hutko"
key = "fiscal_receipts"
Keep the same target (customer-account.order-status.block.render), which is one of the documented customer account targets.
Just bump api_version and ensure your package.json has the Preact dependencies:
json
Copy
1
2
3
4
5
6
7
{
"dependencies": {
"preact": "^10.10.x",
"@preact/signals": "^2.3.x",
"@shopify/ui-extensions": "2025.10.x"
}
}
4. Notes on your GraphQL and metafield usage
Your original logic:
Index metafield:
Operation
Open in GraphiQL
Copy
1
2
3
4
5
query getOrderMetafield($id: ID!) {
order(id: $id) {
metafield(namespace: "hutko", key: "fiscal_receipts") { value }
}
}
Per-receipt metafields:
Operation
Open in GraphiQL
Copy
1
2
3
4
5
6
query getReceiptData($orderId: ID!, $infoKey: String!, $storageKey: String!) {
order(id: $orderId) {
info: metafield(namespace: "hutko", key: $infoKey) { value }
storage: metafield(namespace: "hutko", key: $storageKey) { value }
}
}
and then JSON parse on each value.
This pattern is totally fine to keep in the Preact version — the only changes I made are:
Bumping API_VERSION from '2025-07' to '2025-10'.
Using shopify.order.value rather than useApi() to get orderId.
If your metafield schema is already live in production, this Preact migration should not require any backend changes.
What to do next
Replace your OrderStatusExtension.jsx with the Preact/web-components version above.
Update shopify.extension.toml to api_version = "2025-10".
Ensure package.json for this extension has:
preact, @preact/signals, @shopify/ui-extensions (and no @shopify/ui-extensions-react/...).
Run:
bash
Copy
1
shopify app dev
And test on the Order status page under the customer-account.order-status.block.render target.
If youd like, you can paste your package.json for this extension and Ill align it exactly with the 202510 Preact + customer-account UI extensions stack, and we can similarly migrate your admin.order-details.block.render block so both sides (admin + customer account) use the same Preact/web-components approach.
Sources
API: Customer Account Ui Extensions
Customer account UI extensions
API: Customer Account Ui Extensions > Polaris Web Components
Polaris web components
API: Customer Account Ui Extensions > Configuration
Customer account UI extensions Configuration