@pancake-pos/app-cli 2026.1.6 → 2026.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +411 -27
- package/docs/llms-full.txt +323 -28
- package/docs/openapi.json +1 -0
- package/package.json +2 -2
- package/runtime/frame.js +2 -1
- package/runtime/frame.js.map +1 -1
- package/runtime/worker.js +219 -18
- package/runtime/worker.js.map +3 -3
package/docs/llms-full.txt
CHANGED
|
@@ -155,6 +155,7 @@ Details: [Data access](/developers/apps/en/data-access).
|
|
|
155
155
|
| `after_save` hook, `headless` action and `*.print-action` run time | 30 seconds |
|
|
156
156
|
| `*.index.column` timeout | 5 seconds |
|
|
157
157
|
| `*.print-action` HTML | 2 MB |
|
|
158
|
+
| Files downloaded with `pos.download` | 10 MB per file |
|
|
158
159
|
| Open API request rate | Under 30 reads (`GET`) and under 100 writes per second, per app + user + shop + endpoint |
|
|
159
160
|
| App token lifetime | 15 minutes, never beyond the user's login session (renewed by the SDK) |
|
|
160
161
|
| Error log per app | The latest 500 errors, kept for 7 days |
|
|
@@ -201,7 +202,8 @@ my-app/
|
|
|
201
202
|
├─ pos-app.json # manifest: permissions, external domains, extensions
|
|
202
203
|
├─ extensions/ # code that runs in the worker (Worker UI or headless)
|
|
203
204
|
│ ├─ order-block.tsx
|
|
204
|
-
│
|
|
205
|
+
│ ├─ order-summary.ts
|
|
206
|
+
│ └─ report.tsx # sample Worker UI page (Standard screens)
|
|
205
207
|
├─ page/ # iframe page (optional), Vite + React
|
|
206
208
|
│ ├─ index.html
|
|
207
209
|
│ ├─ tsconfig.json # separate TypeScript config for React
|
|
@@ -463,7 +465,7 @@ They work together: a `create-pos-app` project already has `AGENTS.md`; just add
|
|
|
463
465
|
|
|
464
466
|
## MCP
|
|
465
467
|
|
|
466
|
-
`pos-app mcp` starts an [MCP](https://modelcontextprotocol.io) server so AI tools can look up the docs, see the shop's real metadata, check the app, read runtime errors and deploy without leaving the chat. It runs on your machine (stdio), in the app's project folder, with the account signed in through `pos-app login`.
|
|
468
|
+
`pos-app mcp` starts an [MCP](https://modelcontextprotocol.io) server so AI tools can look up the docs and Open API endpoints, see the shop's real metadata, check the app, read runtime errors and deploy without leaving the chat. It runs on your machine (stdio), in the app's project folder, with the account signed in through `pos-app login`.
|
|
467
469
|
|
|
468
470
|
### Connect the MCP server
|
|
469
471
|
|
|
@@ -522,6 +524,8 @@ Claude Desktop (`claude_desktop_config.json`) does not run inside the project fo
|
|
|
522
524
|
| `get_target` | `target`, for example `purchase.details.action` | Context data schema, expected return value, manifest fields and a short example |
|
|
523
525
|
| `list_shop_meta` | `resource` | Ids and names the app can use in `conditions` and code: statuses, tags, warehouses, product categories, order sources |
|
|
524
526
|
| `sample_context` | `target`, optional record id | A realistic `pos.data` built from the shop's data, filtered by the app's `access` and the signed-in user's permissions |
|
|
527
|
+
| `search_api` | `query`, for example `order list` or `post order` | Open API endpoints the app may call with `pos.api` (method, path, summary) and the `access.methods` each needs |
|
|
528
|
+
| `get_api` | `path` and `method`, or `schema` | How to call one endpoint: the `pos.api` call, path and query parameters, request body and response shapes, an example. `field` expands a nested object (`data.items`, `body.customer`) |
|
|
525
529
|
| `check_app` | none | The `pos-app check` result: each problem with file, line, rule id and a suggested fix |
|
|
526
530
|
| `dev_start`, `dev_status` | none | Starts `pos-app dev`; returns the local URL, the POS link to open in development mode and build errors |
|
|
527
531
|
| `get_errors` | `version` (`dev` by default), `since` | Runtime errors POS recorded for the app: target, message, stack trace, time |
|
|
@@ -530,15 +534,16 @@ Claude Desktop (`claude_desktop_config.json`) does not run inside the project fo
|
|
|
530
534
|
|
|
531
535
|
- `deploy` never activates a version. An admin reviews access and activates it on the [Custom apps](/developers/apps/en/manage-apps#review-and-activate-a-version) page, exactly as with `pos-app deploy`.
|
|
532
536
|
- If `version` in `pos-app.json` already exists, `deploy` fails with `duplicate_version`; bump `version` and deploy again.
|
|
533
|
-
- Tools act as the signed-in user on the given shop. They never return app tokens or API keys, and they do not write shop data: `list_shop_meta` and `sample_context` only read. App code still reads and writes data through `pos.api`.
|
|
537
|
+
- Tools act as the signed-in user on the given shop. They never return app tokens or API keys, and they do not write shop data: `list_shop_meta` and `sample_context` only read. `search_api` and `get_api` read the Open API copy bundled with the CLI and call nothing. App code still reads and writes data through `pos.api`.
|
|
534
538
|
|
|
535
539
|
### Typical loop
|
|
536
540
|
|
|
537
541
|
1. `get_target` and `search_docs` to pick the right targets and types.
|
|
538
542
|
2. `list_shop_meta` and `sample_context` to write `conditions` and code against real ids and fields.
|
|
539
|
-
3.
|
|
540
|
-
4.
|
|
541
|
-
5. `
|
|
543
|
+
3. `search_api` and `get_api` before each `pos.api` call, to use a published endpoint with its exact parameters and response shape.
|
|
544
|
+
4. Write the code, then `check_app` until it reports no errors.
|
|
545
|
+
5. `dev_start`, try the app in POS, then `get_errors` to fix runtime errors.
|
|
546
|
+
6. `deploy` and ask an admin to activate the new version.
|
|
542
547
|
|
|
543
548
|
### Sample prompts
|
|
544
549
|
|
|
@@ -651,6 +656,7 @@ and a "Send reminder" button that calls https://crm.example.com/api/remind. Read
|
|
|
651
656
|
- [ ] Modules in `extensions/` use no real DOM, UI libraries or CSS.
|
|
652
657
|
- [ ] No tokens, API keys or secrets in code.
|
|
653
658
|
- [ ] Errors and loading states are handled.
|
|
659
|
+
- [ ] Worker UI screens follow [Standard screens](/developers/apps/en/components#standard-screens): no emoji, no repeated title, numbers in `pos-stat`.
|
|
654
660
|
- [ ] `npm run check` reports no errors.
|
|
655
661
|
- [ ] Tested with `npm run dev` (an account with the Settings permission), then deployed to a test shop and tried with a narrow-permission staff account.
|
|
656
662
|
|
|
@@ -665,24 +671,27 @@ Docs: https://pos.pages.fm/developers/apps/llms-full.txt (read before changing c
|
|
|
665
671
|
## Structure
|
|
666
672
|
- pos-app.json: manifest (permissions, external domains, extensions).
|
|
667
673
|
- extensions/: modules that run in a Web Worker and render pos-* elements with Preact.
|
|
668
|
-
-
|
|
674
|
+
- extensions/report.tsx: sample Worker UI page laid out like other POS screens; copy its structure for new pages.
|
|
675
|
+
- page/: iframe page (React + Vite), gets `pos` from `<PosProvider>`/`usePos()` in `@pancake-pos/ui` (or `connect()` in `@pancake-pos/ui-extensions/page`); routes starting with /_pos/ are reserved for POS.
|
|
669
676
|
|
|
670
677
|
## Rules
|
|
671
678
|
- extensions/: no real DOM, window, localStorage, React, UI libraries or CSS. Only pos-* elements from the docs.
|
|
672
679
|
- Text goes inside pos-text/pos-heading/pos-badge/pos-link/pos-button/pos-banner. Event values are in event.detail.
|
|
680
|
+
- Worker UI screens follow Components > Standard screens: pos-page as the page frame, pos-section cards, pos-stat in pos-grid for numbers, one pos-filter-bar for filters, pos-tabs with badge for status filters. No emoji or decorative characters in labels, no repeated page title, no pos-section around every element.
|
|
673
681
|
- POS data only through pos.api with paths like /shops/${pos.shop.id}/... and Open API endpoints.
|
|
674
682
|
- Never hardcode shop ids, API keys, tokens or secrets.
|
|
675
683
|
- Write methods must be in access.methods; external domains must be in network.allowed_origins.
|
|
676
|
-
- Modules: export default async function (data). Headless returns { message }; before_submit returns { ok } or { ok: false, message }.
|
|
684
|
+
- Modules: export default async function (data). Headless returns { message }; before_submit returns { ok, changes? } (changes: note, add_tags) or { ok: false, message }; *.print-action returns { html }; *.index.column returns { values }.
|
|
677
685
|
- Catch errors on every API call; handle 403 and 429.
|
|
678
686
|
|
|
679
687
|
## Commands
|
|
680
|
-
- Run the CLI through the npm scripts below, or as
|
|
688
|
+
- Run the CLI through the npm scripts below, or as npx @pancake-pos/app-cli <command> (login, versions, mcp). Never run npx pos-app: the pos-app package on npm is not Pancake's, and npx downloads it when the project is not installed.
|
|
681
689
|
- npm run dev: try in POS (turn on Development mode on the custom apps management page); POS reloads when code changes.
|
|
682
690
|
- npx eslint .: check the code after every change; do not add eslint-disable to silence rules.
|
|
691
|
+
- npm run typecheck: TypeScript for extensions/ (Preact) and page/ (React).
|
|
683
692
|
- npm run check: validate the manifest and code; fix every error before deploying.
|
|
684
693
|
- npm run deploy: build, validate the manifest, upload a new version (options after --, for example npm run deploy -- --message "...").
|
|
685
|
-
- With the pos-app MCP server: get_target/list_shop_meta/sample_context before writing code, check_app after every change, get_errors after testing in POS, deploy when done (uploads an inactive version; an admin activates it). For a platform bug or a missing feature: draft a report, show it to the user, and call send_feedback only after they agree.
|
|
694
|
+
- With the pos-app MCP server: get_target/list_shop_meta/sample_context before writing code, search_api/get_api before every pos.api call, check_app after every change, get_errors after testing in POS, deploy when done (uploads an inactive version; an admin activates it). For a platform bug or a missing feature: draft a report, show it to the user, and call send_feedback only after they agree.
|
|
686
695
|
````
|
|
687
696
|
|
|
688
697
|
---
|
|
@@ -1240,7 +1249,7 @@ An extension point (target) is a place POS exposes for apps. Each target defines
|
|
|
1240
1249
|
|
|
1241
1250
|
## Naming
|
|
1242
1251
|
|
|
1243
|
-
Targets attached to a POS record are named `<resource>.<location>`, for example `order.details.action` or `purchase.index.selection-action`.
|
|
1252
|
+
Targets attached to a POS record are named `<resource>.<location>`, for example `order.details.action` or `purchase.index.selection-action`. Resources share the same location names, so an app written for orders works the same way for purchases or warehouse transfers. Some locations do not exist for every resource: products have no print templates (`print-action`), and customers only have boxes, actions and columns (see [`customer.*`](#customer)).
|
|
1244
1253
|
|
|
1245
1254
|
| Resource | POS screen | List | Detail form |
|
|
1246
1255
|
|---|---|---|---|
|
|
@@ -1249,6 +1258,7 @@ Targets attached to a POS record are named `<resource>.<location>`, for example
|
|
|
1249
1258
|
| `purchase` | Purchases (goods receipts) | Purchase list | Purchase modal |
|
|
1250
1259
|
| `export` | Exports (goods issues) | Export list | Export modal |
|
|
1251
1260
|
| `warehouse_transfer` | Warehouse transfers | Transfer list | Transfer modal |
|
|
1261
|
+
| `customer` | Customers | Customer list | Customer profile |
|
|
1252
1262
|
|
|
1253
1263
|
## Target list
|
|
1254
1264
|
|
|
@@ -1506,7 +1516,7 @@ type PageData = {
|
|
|
1506
1516
|
|
|
1507
1517
|
- **Iframe pages:** the URL is synced both ways; when the app changes its internal route the POS URL follows, so reloads, the back button and shared links all work. The page connects to POS through the SDK, see [Runtime API › Iframe pages](/developers/apps/en/runtime-api#iframe-pages).
|
|
1508
1518
|
- **Worker UI pages:** the worker has no history. Change the path with `pos.navigate({ page, path, query })`; POS updates the URL and emits `data-change` with the new `path` and `query` without recreating the worker. The browser back button also emits `data-change`.
|
|
1509
|
-
- Build Worker UI pages with `pos-page`, `pos-
|
|
1519
|
+
- Build Worker UI pages with `pos-page`, `pos-filter-bar`, `pos-stat`, `pos-chart`, `pos-tabs`, `pos-table` and the other elements following [Standard screens](/developers/apps/en/components#standard-screens) (see [Components](/developers/apps/en/components#worker-ui-pages)). Set the title and primary button on the POS title bar with `pos.page`, as on iframe pages (see [Runtime API › Worker UI pages](/developers/apps/en/runtime-api#worker-ui-pages)).
|
|
1510
1520
|
|
|
1511
1521
|
```tsx
|
|
1512
1522
|
import { render } from 'preact';
|
|
@@ -1742,7 +1752,7 @@ export default defineExtension('purchase.print-action', async ({ records }) => {
|
|
|
1742
1752
|
|
|
1743
1753
|
## `*.index.column`
|
|
1744
1754
|
|
|
1745
|
-
The app's column in a list, placed before the fixed right column (Status) or at the end of the table. Available for all 5 resources. POS calls the module **once for the records shown** on the page, not once per row.
|
|
1755
|
+
The app's column in a list, placed before the fixed right column (Status) or at the end of the table. Available for all 5 resources and for customers. POS calls the module **once for the records shown** on the page, not once per row.
|
|
1746
1756
|
|
|
1747
1757
|
```ts
|
|
1748
1758
|
type ColumnData<R extends Resource> = { resource: R; records: RefOf<R>[] }; // records on the page, up to 500
|
|
@@ -1822,6 +1832,79 @@ stack.append(field, button);
|
|
|
1822
1832
|
document.body.append(stack);
|
|
1823
1833
|
```
|
|
1824
1834
|
|
|
1835
|
+
## Standard screens
|
|
1836
|
+
|
|
1837
|
+
POS lays elements out like other POS pages; the app only picks the right elements and their order, never colors or spacing.
|
|
1838
|
+
|
|
1839
|
+
- **`app.page` pages:** `pos-page` is the POS page frame. `heading` and `description` go on the title row at the top, the `actions` slot sits on the right next to the primary button (`pos.page.setPrimaryAction`); content is stacked on the gray background.
|
|
1840
|
+
- **Blocks:** a `pos-section` directly in `pos-page` is a white card; a `pos-section` inside another `pos-section` is a flat group; in blocks and modals, `pos-section` is a white card with a thin border. `pos-table` and `pos-chart` placed directly in `pos-page` get a white card automatically.
|
|
1841
|
+
- **Numbers:** `pos-stat` in a 3–5 column `pos-grid`; do not combine `pos-section` and `pos-heading` into metric cards.
|
|
1842
|
+
- **Filters:** put filter inputs in one `pos-filter-bar` at the top of the page or of a section; use `pos-date-range` for periods. For status filters use `pos-tabs` with `badge`: chips with counts like the status bar of POS list pages.
|
|
1843
|
+
- **Buttons:** tool buttons (reload, filter, settings) are icon-only buttons with a `tooltip`; group secondary actions in a `pos-menu-button`. Show statuses in tables with a `format: 'badge'` column; open a row's details in a `pos-drawer` or `pos-modal` with `pos-description-list` and `pos-timeline`.
|
|
1844
|
+
- **Do not** repeat the page title in the content, use emoji or decorative characters in labels (use the `tone` of `pos-stat` and `pos-badge`), nest `pos-section` more than two levels deep, or wrap every element in its own `pos-section`.
|
|
1845
|
+
- Drop all-zero or empty rows from charts and tables; show names instead of ids (look names up through the Open API).
|
|
1846
|
+
|
|
1847
|
+
### Report page
|
|
1848
|
+
|
|
1849
|
+
```tsx
|
|
1850
|
+
<pos-page description="Actual delivery times against the carriers' committed SLA" width="full">
|
|
1851
|
+
<pos-button slot="actions" icon="download" onClick={exportCsv}>Export</pos-button>
|
|
1852
|
+
<pos-filter-bar>
|
|
1853
|
+
<pos-date-range label="Order created" value={range} onChange={(e) => e.detail && setRange(e.detail)} />
|
|
1854
|
+
<pos-button icon="refresh" tooltip="Reload" onClick={load} />
|
|
1855
|
+
</pos-filter-bar>
|
|
1856
|
+
<pos-grid columns={4}>
|
|
1857
|
+
<pos-stat label="On-time rate" value={58.3} format="percent" help="12 delivered orders" />
|
|
1858
|
+
<pos-stat label="On time" value={7} tone="success" selected={state === 'on_time'} onClick={() => setState('on_time')} />
|
|
1859
|
+
<pos-stat label="Late" value={5} tone="critical" selected={state === 'late'} onClick={() => setState('late')} />
|
|
1860
|
+
<pos-stat label="Shipping fees" value={2450000} format="money" />
|
|
1861
|
+
</pos-grid>
|
|
1862
|
+
<pos-section heading="By carrier">
|
|
1863
|
+
<pos-chart type="bar" xKey="carrier" data={byCarrier} series={[{ key: 'rate', label: 'On-time rate (%)' }]} onSelect={(e) => setCarrier(e.detail.x)} />
|
|
1864
|
+
</pos-section>
|
|
1865
|
+
</pos-page>
|
|
1866
|
+
```
|
|
1867
|
+
|
|
1868
|
+
### List page
|
|
1869
|
+
|
|
1870
|
+
```tsx
|
|
1871
|
+
<pos-page width="full">
|
|
1872
|
+
<pos-button slot="actions" icon="upload" onClick={importFile}>Import</pos-button>
|
|
1873
|
+
<pos-menu-button slot="actions" items={[{ id: 'csv', label: 'Export CSV' }]} onSelect={exportAs} />
|
|
1874
|
+
<pos-section>
|
|
1875
|
+
<pos-filter-bar>
|
|
1876
|
+
<pos-text-field label="Search order code" value={search} onChange={(e) => setSearch(e.detail)} />
|
|
1877
|
+
<pos-select label="Warehouse" options={warehouses} value={warehouse} onChange={(e) => setWarehouse(e.detail)} />
|
|
1878
|
+
</pos-filter-bar>
|
|
1879
|
+
<pos-tabs tabs={[{ id: 'all', label: 'All', badge: 169 }, { id: 'late', label: 'Late', badge: 12 }]} selected={status} onChange={(e) => setStatus(e.detail)}>
|
|
1880
|
+
<pos-table
|
|
1881
|
+
columns={[
|
|
1882
|
+
{ key: 'code', title: 'Order', format: 'link' },
|
|
1883
|
+
{ key: 'customer', title: 'Customer', subtitle: 'phone' },
|
|
1884
|
+
{ key: 'state', title: 'SLA', format: 'badge', tones: { Late: 'critical', 'On time': 'success' } },
|
|
1885
|
+
{ key: 'fee', title: 'Shipping fee', format: 'money', align: 'right', sortable: true },
|
|
1886
|
+
]}
|
|
1887
|
+
rows={rows}
|
|
1888
|
+
sort={sort}
|
|
1889
|
+
pagination={{ page, pageSize: 30, total }}
|
|
1890
|
+
onSortChange={(e) => setSort(e.detail)}
|
|
1891
|
+
onPageChange={(e) => setPage(e.detail.page)}
|
|
1892
|
+
onCellClick={(e) => setDetail(e.detail.row)}
|
|
1893
|
+
/>
|
|
1894
|
+
</pos-tabs>
|
|
1895
|
+
</pos-section>
|
|
1896
|
+
</pos-page>
|
|
1897
|
+
```
|
|
1898
|
+
|
|
1899
|
+
```tsx
|
|
1900
|
+
<pos-drawer open={!!detail} heading={detail?.code} onClose={() => setDetail(null)}>
|
|
1901
|
+
<pos-description-list items={[{ label: 'Carrier', value: detail?.carrier }, { label: 'Shipping fee', value: detail?.fee, format: 'money' }]} />
|
|
1902
|
+
<pos-timeline items={detail?.events ?? []} />
|
|
1903
|
+
</pos-drawer>
|
|
1904
|
+
```
|
|
1905
|
+
|
|
1906
|
+
The page's primary button (for example "Sync") is set with `pos.page.setPrimaryAction` and always comes last on the title row. Export files (CSV, Excel) with [`pos.download`](/developers/apps/en/runtime-api#pos-download).
|
|
1907
|
+
|
|
1825
1908
|
## Layout
|
|
1826
1909
|
|
|
1827
1910
|
### pos-stack
|
|
@@ -1838,7 +1921,7 @@ Stacks children vertically or horizontally.
|
|
|
1838
1921
|
|
|
1839
1922
|
### pos-grid
|
|
1840
1923
|
|
|
1841
|
-
A grid of equal columns;
|
|
1924
|
+
A grid of equal columns; cells narrower than 180 px wrap to the next row (one column on narrow screens).
|
|
1842
1925
|
|
|
1843
1926
|
| Property | Type | Default | Description |
|
|
1844
1927
|
|---|---|---|---|
|
|
@@ -1847,14 +1930,14 @@ A grid of equal columns; collapses to one column on narrow screens.
|
|
|
1847
1930
|
|
|
1848
1931
|
### pos-section
|
|
1849
1932
|
|
|
1850
|
-
A titled
|
|
1933
|
+
A titled block. Directly in `pos-page` it is a white card; inside another `pos-section` it is a flat group; in blocks and modals it is a white card with a thin border (see [Standard screens](#standard-screens)).
|
|
1851
1934
|
|
|
1852
1935
|
| Property | Type | Description |
|
|
1853
1936
|
|---|---|---|
|
|
1854
1937
|
| `heading` | `string` | Title |
|
|
1855
1938
|
| `description` | `string` | Short text below the title |
|
|
1856
1939
|
|
|
1857
|
-
Slot `actions`:
|
|
1940
|
+
Slot `actions`: `pos-button`, `pos-link` and `pos-menu-button` shown at the right of the title.
|
|
1858
1941
|
|
|
1859
1942
|
```tsx
|
|
1860
1943
|
<pos-section heading="Customer debt">
|
|
@@ -1883,6 +1966,8 @@ A horizontal rule. No properties.
|
|
|
1883
1966
|
|---|---|---|
|
|
1884
1967
|
| `level` | `1–4` | `3` |
|
|
1885
1968
|
|
|
1969
|
+
Font size: `1` 20 px (same as the page title), `2` 18 px, `3` 16 px (same as section titles), `4` 14 px.
|
|
1970
|
+
|
|
1886
1971
|
### pos-badge
|
|
1887
1972
|
|
|
1888
1973
|
A short status label.
|
|
@@ -1920,6 +2005,40 @@ Displays an amount using the shop's currency and number format.
|
|
|
1920
2005
|
|
|
1921
2006
|
POS loads images through a proxy on the sandbox: image types only, up to 5 MB. Images from undeclared domains are not shown (development mode logs a warning).
|
|
1922
2007
|
|
|
2008
|
+
### pos-description-list
|
|
2009
|
+
|
|
2010
|
+
Label – value pairs like the order info blocks of POS (gray label on the left, value on the right); for modals, panels and detail blocks.
|
|
2011
|
+
|
|
2012
|
+
| Property | Type | Default | Description |
|
|
2013
|
+
|---|---|---|---|
|
|
2014
|
+
| `items` | `Item[]` | `[]` | Up to 50 rows |
|
|
2015
|
+
| `columns` | `1–4` | `1` | Number of columns; cells narrower than 220 px wrap |
|
|
2016
|
+
|
|
2017
|
+
```ts
|
|
2018
|
+
type Item = {
|
|
2019
|
+
label: string;
|
|
2020
|
+
value: string | number | null; // null or empty shows "—"
|
|
2021
|
+
format?: 'text' | 'number' | 'money' | 'percent' | 'date' | 'datetime' | 'badge';
|
|
2022
|
+
tone?: 'default' | 'info' | 'success' | 'warning' | 'critical'; // color when format is badge
|
|
2023
|
+
};
|
|
2024
|
+
```
|
|
2025
|
+
|
|
2026
|
+
```tsx
|
|
2027
|
+
<pos-description-list columns={2} items={[
|
|
2028
|
+
{ label: 'Carrier', value: 'GHN' },
|
|
2029
|
+
{ label: 'Shipping fee', value: 25000, format: 'money' },
|
|
2030
|
+
{ label: 'SLA', value: '2 days late', format: 'badge', tone: 'critical' },
|
|
2031
|
+
]} />
|
|
2032
|
+
```
|
|
2033
|
+
|
|
2034
|
+
### pos-timeline
|
|
2035
|
+
|
|
2036
|
+
A timeline: order journey, change history.
|
|
2037
|
+
|
|
2038
|
+
| Property | Type | Description |
|
|
2039
|
+
|---|---|---|
|
|
2040
|
+
| `items` | `{ title: string; time?: string; description?: string; tone?: Tone }[]` | Up to 100 steps; an ISO 8601 `time` is formatted like POS, other text is shown as is; `tone` is the dot color |
|
|
2041
|
+
|
|
1923
2042
|
## Inputs
|
|
1924
2043
|
|
|
1925
2044
|
Properties shared by every input:
|
|
@@ -1980,6 +2099,22 @@ POS shows what the user types immediately and then sends `onChange` to the app,
|
|
|
1980
2099
|
| `value` | `string \| null` | Date as `YYYY-MM-DD` |
|
|
1981
2100
|
| `min`, `max` | `string` | Selectable range, as `YYYY-MM-DD` |
|
|
1982
2101
|
|
|
2102
|
+
### pos-date-range
|
|
2103
|
+
|
|
2104
|
+
A date range like the period picker of the Statistics pages, with quick choices: Today, Yesterday, Last 7 days, Last 30 days, Start of week to now, Start of month to now, Last month.
|
|
2105
|
+
|
|
2106
|
+
| Property | Type | Default | Description |
|
|
2107
|
+
|---|---|---|---|
|
|
2108
|
+
| `value` | `{ from: string; to: string } \| null` | | Dates as `YYYY-MM-DD` |
|
|
2109
|
+
| `min`, `max` | `string` | | Selectable date limits |
|
|
2110
|
+
| `presets` | `boolean` | `true` | Show the quick choices |
|
|
2111
|
+
|
|
2112
|
+
Plus the common input properties (`label`, `disabled`, `required`, `error`, `help`). Event `onChange`: `event.detail` is `{ from, to }`, or `null` when cleared.
|
|
2113
|
+
|
|
2114
|
+
```tsx
|
|
2115
|
+
<pos-date-range label="Order created" value={range} onChange={(e) => e.detail && setRange(e.detail)} />
|
|
2116
|
+
```
|
|
2117
|
+
|
|
1983
2118
|
## Feedback
|
|
1984
2119
|
|
|
1985
2120
|
### pos-banner
|
|
@@ -2000,6 +2135,34 @@ A prominent message inside the extension.
|
|
|
2000
2135
|
|---|---|---|
|
|
2001
2136
|
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
|
|
2002
2137
|
|
|
2138
|
+
### pos-progress
|
|
2139
|
+
|
|
2140
|
+
A progress bar for rates (on time, target reached).
|
|
2141
|
+
|
|
2142
|
+
| Property | Type | Default | Description |
|
|
2143
|
+
|---|---|---|---|
|
|
2144
|
+
| `value` | `number` | `0` | Percentage 0–100 |
|
|
2145
|
+
| `label` | `string` | | Label on the left, percentage on the right |
|
|
2146
|
+
| `tone` | `'default' \| 'info' \| 'success' \| 'warning' \| 'critical'` | `'default'` | Bar color |
|
|
2147
|
+
| `showValue` | `boolean` | `true` | Show the percentage |
|
|
2148
|
+
|
|
2149
|
+
### pos-empty
|
|
2150
|
+
|
|
2151
|
+
An empty state like POS pages: a small illustration, a title, a description and action buttons.
|
|
2152
|
+
|
|
2153
|
+
| Property | Type | Description |
|
|
2154
|
+
|---|---|---|
|
|
2155
|
+
| `heading` | `string` | Title |
|
|
2156
|
+
| `description` | `string` | Description |
|
|
2157
|
+
|
|
2158
|
+
Slot `actions` for `pos-button` and `pos-link`.
|
|
2159
|
+
|
|
2160
|
+
```tsx
|
|
2161
|
+
<pos-empty heading="No late orders" description="Change the dates to see more">
|
|
2162
|
+
<pos-button slot="actions" onClick={reset}>Reset filters</pos-button>
|
|
2163
|
+
</pos-empty>
|
|
2164
|
+
```
|
|
2165
|
+
|
|
2003
2166
|
## Data and actions
|
|
2004
2167
|
|
|
2005
2168
|
### pos-button
|
|
@@ -2010,12 +2173,41 @@ A prominent message inside the extension.
|
|
|
2010
2173
|
| `size` | `'small' \| 'medium'` | `'medium'` | |
|
|
2011
2174
|
| `loading` | `boolean` | `false` | Shows a busy state and blocks clicks |
|
|
2012
2175
|
| `disabled` | `boolean` | `false` | |
|
|
2176
|
+
| `icon` | see below | | Icon before the text; a button without text is an icon-only button |
|
|
2177
|
+
| `tooltip` | `string` | | Hover text; required for icon-only buttons (also their name for screen readers) |
|
|
2013
2178
|
|
|
2014
2179
|
`onClick` event.
|
|
2015
2180
|
|
|
2181
|
+
Icons (the POS icon set): `plus`, `edit`, `delete`, `download`, `upload`, `refresh`, `filter`, `search`, `settings`, `print`, `copy`, `link`, `external`, `view`, `send`, `check`, `close`, `more`, `calendar`, `info`, `warning`.
|
|
2182
|
+
|
|
2183
|
+
```tsx
|
|
2184
|
+
<pos-button icon="download" onClick={exportCsv}>Export</pos-button>
|
|
2185
|
+
<pos-button icon="refresh" tooltip="Reload" onClick={reload} />
|
|
2186
|
+
```
|
|
2187
|
+
|
|
2188
|
+
### pos-menu-button
|
|
2189
|
+
|
|
2190
|
+
A button that opens a list of actions. Without `label` it is a "⋯" button.
|
|
2191
|
+
|
|
2192
|
+
| Property | Type | Default | Description |
|
|
2193
|
+
|---|---|---|---|
|
|
2194
|
+
| `items` | `{ id: string; label: string; danger?: boolean; disabled?: boolean }[]` | `[]` | Up to 30 items |
|
|
2195
|
+
| `label` | `string` | | Button text |
|
|
2196
|
+
| `icon` | as `pos-button` | `more` without `label` | |
|
|
2197
|
+
| `variant` | `'primary' \| 'secondary' \| 'tertiary'` | `'secondary'` | |
|
|
2198
|
+
| `size` | `'small' \| 'medium'` | `'medium'` | |
|
|
2199
|
+
| `disabled` | `boolean` | `false` | |
|
|
2200
|
+
| `tooltip` | `string` | | Hover text |
|
|
2201
|
+
|
|
2202
|
+
Event `onSelect`: `event.detail` is the `id` of the clicked item.
|
|
2203
|
+
|
|
2204
|
+
```tsx
|
|
2205
|
+
<pos-menu-button slot="actions" items={[{ id: 'csv', label: 'Export CSV' }, { id: 'delete', label: 'Delete', danger: true }]} onSelect={(e) => run(e.detail)} />
|
|
2206
|
+
```
|
|
2207
|
+
|
|
2016
2208
|
### pos-table
|
|
2017
2209
|
|
|
2018
|
-
A data table. Cells only hold values (strings, numbers);
|
|
2210
|
+
A data table like the POS list tables. Cells only hold values (strings, numbers); how they show (format, badge, link, secondary line) is set on the column.
|
|
2019
2211
|
|
|
2020
2212
|
| Property | Type | Default | Description |
|
|
2021
2213
|
|---|---|---|---|
|
|
@@ -2025,6 +2217,10 @@ A data table. Cells only hold values (strings, numbers); POS formats them accord
|
|
|
2025
2217
|
| `loading` | `boolean` | `false` | |
|
|
2026
2218
|
| `pagination` | `{ page: number; pageSize: number; total: number } \| null` | `null` | App-side pagination |
|
|
2027
2219
|
| `emptyText` | `string` | | Content when there are no rows |
|
|
2220
|
+
| `sort` | `{ key: string; order: 'asc' \| 'desc' } \| null` | `null` | Sorted column (the app sorts the data or calls the API again) |
|
|
2221
|
+
| `summary` | `object \| null` | `null` | Totals row at the bottom, values by column `key`, formatted like the column |
|
|
2222
|
+
| `selectable` | `boolean` | `false` | A checkbox at the start of each row |
|
|
2223
|
+
| `selected` | `(string \| number)[]` | `[]` | Keys of the selected rows |
|
|
2028
2224
|
|
|
2029
2225
|
```ts
|
|
2030
2226
|
type Column = {
|
|
@@ -2032,14 +2228,24 @@ type Column = {
|
|
|
2032
2228
|
title: string;
|
|
2033
2229
|
align?: 'left' | 'center' | 'right';
|
|
2034
2230
|
width?: number; // px
|
|
2035
|
-
format?: 'text' | 'number' | 'money' | 'date' | 'datetime';
|
|
2231
|
+
format?: 'text' | 'number' | 'money' | 'percent' | 'date' | 'datetime' | 'badge' | 'link';
|
|
2232
|
+
tones?: Record<string, 'default' | 'info' | 'success' | 'warning' | 'critical'>; // badge color by cell value
|
|
2233
|
+
subtitle?: string; // field shown as a gray secondary line (for example the SKU under the name)
|
|
2234
|
+
sortable?: boolean;
|
|
2235
|
+
fixed?: 'left' | 'right'; // keep the column visible when scrolling sideways
|
|
2236
|
+
ellipsis?: boolean; // cut long text to one line
|
|
2036
2237
|
};
|
|
2037
2238
|
```
|
|
2038
2239
|
|
|
2240
|
+
- `percent`: the number is a percentage (12.5 → 12.5%). `badge`: the cell value becomes a colored label using `tones`. `link`: link-styled text that fires `onCellClick`.
|
|
2241
|
+
|
|
2039
2242
|
Events:
|
|
2040
2243
|
|
|
2041
2244
|
- `onPageChange`: `event.detail` is `{ page, pageSize }`.
|
|
2042
2245
|
- `onRowClick`: `event.detail` is the clicked row.
|
|
2246
|
+
- `onSortChange`: `event.detail` is `{ key, order }`, or `null` when sorting is cleared.
|
|
2247
|
+
- `onSelectionChange`: `event.detail` is the array of selected row keys (rows on other pages stay selected).
|
|
2248
|
+
- `onCellClick`: `event.detail` is `{ key, row }` when a `format: 'link'` cell is clicked.
|
|
2043
2249
|
|
|
2044
2250
|
```tsx
|
|
2045
2251
|
<pos-table
|
|
@@ -2054,6 +2260,25 @@ Events:
|
|
|
2054
2260
|
/>
|
|
2055
2261
|
```
|
|
2056
2262
|
|
|
2263
|
+
```tsx
|
|
2264
|
+
<pos-table
|
|
2265
|
+
columns={[
|
|
2266
|
+
{ key: 'name', title: 'Product', subtitle: 'sku', sortable: true, fixed: 'left' },
|
|
2267
|
+
{ key: 'state', title: 'SLA', format: 'badge', tones: { Late: 'critical', 'On time': 'success' } },
|
|
2268
|
+
{ key: 'code', title: 'Order', format: 'link' },
|
|
2269
|
+
{ key: 'rate', title: 'Rate', format: 'percent', align: 'right' },
|
|
2270
|
+
]}
|
|
2271
|
+
rows={rows}
|
|
2272
|
+
sort={sort}
|
|
2273
|
+
summary={{ name: 'Total', rate: 62.5 }}
|
|
2274
|
+
selectable
|
|
2275
|
+
selected={selected}
|
|
2276
|
+
onSortChange={(e) => setSort(e.detail)}
|
|
2277
|
+
onSelectionChange={(e) => setSelected(e.detail)}
|
|
2278
|
+
onCellClick={(e) => openOrder(e.detail.row)}
|
|
2279
|
+
/>
|
|
2280
|
+
```
|
|
2281
|
+
|
|
2057
2282
|
### pos-modal
|
|
2058
2283
|
|
|
2059
2284
|
A dialog opened from inside an extension (for example from a button in a block).
|
|
@@ -2075,25 +2300,37 @@ Slot `footer` for the dialog buttons. The `onClose` event fires when the user cl
|
|
|
2075
2300
|
|
|
2076
2301
|
Actions with `presentation: "modal"` already get a POS modal; do not wrap them in `pos-modal`.
|
|
2077
2302
|
|
|
2303
|
+
### pos-drawer
|
|
2304
|
+
|
|
2305
|
+
A panel that slides in from the right, like Settings on the Statistics pages: view a row's details without leaving the list.
|
|
2306
|
+
|
|
2307
|
+
| Property | Type | Default | Description |
|
|
2308
|
+
|---|---|---|---|
|
|
2309
|
+
| `open` | `boolean` | | Open state |
|
|
2310
|
+
| `heading` | `string` | | Title |
|
|
2311
|
+
| `width` | `'small' \| 'medium' \| 'large'` | `'medium'` | 400, 560 or 800 px |
|
|
2312
|
+
|
|
2313
|
+
Slot `footer` for the buttons at the bottom. Event `onClose` when the user clicks close or outside.
|
|
2314
|
+
|
|
2078
2315
|
## Worker UI pages
|
|
2079
2316
|
|
|
2080
|
-
The
|
|
2317
|
+
The elements below build `render: "worker"` pages and report screens (see [Extension points › app.page](/developers/apps/en/targets#app-page)); they also work in blocks and modals.
|
|
2081
2318
|
|
|
2082
2319
|
### pos-page
|
|
2083
2320
|
|
|
2084
|
-
Page frame
|
|
2321
|
+
Page frame. On `app.page` pages, `heading` and `description` show on the POS title row (without `heading`, the title set with `pos.page.setTitle` or the extension `title` is used), the `actions` slot sits on the right next to the primary button, and content is stacked on the gray background. In blocks and modals, `pos-page` is a titled block.
|
|
2085
2322
|
|
|
2086
2323
|
| Property | Type | Default | Description |
|
|
2087
2324
|
|---|---|---|---|
|
|
2088
|
-
| `heading` | `string` | |
|
|
2325
|
+
| `heading` | `string` | | Page title |
|
|
2089
2326
|
| `description` | `string` | | Text below the heading |
|
|
2090
|
-
| `width` | `'narrow' \| 'default' \| 'full'` | `'default'` | Content width: 720 px, 1,
|
|
2327
|
+
| `width` | `'narrow' \| 'default' \| 'full'` | `'default'` | Content width: 720 px, up to 1,800 px or the full screen |
|
|
2091
2328
|
|
|
2092
|
-
Slot `actions` for `pos-button` and `pos-
|
|
2329
|
+
Slot `actions` for `pos-button`, `pos-link` and `pos-menu-button`; on `app.page` pages it comes before the primary button.
|
|
2093
2330
|
|
|
2094
2331
|
### pos-tabs
|
|
2095
2332
|
|
|
2096
|
-
A
|
|
2333
|
+
A bar of chips with counts (`badge`), like the status bar of POS list pages. The app keeps the selected tab and renders that tab's content as children.
|
|
2097
2334
|
|
|
2098
2335
|
| Property | Type | Description |
|
|
2099
2336
|
|---|---|---|
|
|
@@ -2104,7 +2341,7 @@ The `onChange` event: `event.detail` is the selected tab's `id`. POS switches th
|
|
|
2104
2341
|
|
|
2105
2342
|
### pos-filter-bar
|
|
2106
2343
|
|
|
2107
|
-
A row of filters above a table or chart. Children: `pos-text-field`, `pos-number-field`, `pos-select`, `pos-checkbox`, `pos-date-picker`, `pos-button`. Inputs are drawn compactly on one row, `label` becomes the hint when there is no `placeholder`, and the row wraps when space runs out.
|
|
2344
|
+
A row of filters above a table or chart. Children: `pos-text-field`, `pos-number-field`, `pos-select`, `pos-checkbox`, `pos-date-picker`, `pos-date-range`, `pos-button`, `pos-menu-button`. Inputs are drawn compactly on one row, `label` becomes the hint when there is no `placeholder`, and the row wraps when space runs out.
|
|
2108
2345
|
|
|
2109
2346
|
```tsx
|
|
2110
2347
|
<pos-filter-bar>
|
|
@@ -2124,12 +2361,18 @@ A chart drawn by POS; the app only passes data. Colors follow the POS palette.
|
|
|
2124
2361
|
| `type` | `'line' \| 'bar' \| 'area' \| 'pie'` | `'line'` | Chart type |
|
|
2125
2362
|
| `data` | `object[]` | `[]` | One object per point, string or number values; up to 500 points and 21 fields |
|
|
2126
2363
|
| `xKey` | `string` | | Field for the horizontal axis (slice names for `pie`) |
|
|
2127
|
-
| `series` | `{ key: string; label: string; format?: 'number' \| 'money' }[]` | `[]` | Up to 10 series; `pie` uses the first one |
|
|
2364
|
+
| `series` | `{ key: string; label: string; format?: 'number' \| 'money' \| 'percent'; type?: 'bar' \| 'line' }[]` | `[]` | Up to 10 series; `pie` uses the first one. A series `type` (`bar`, `line` charts) combines bars and lines |
|
|
2365
|
+
| `selected` | `string \| number \| null` | | Selected horizontal-axis value: other bars and slices fade; line and area charts show a marker |
|
|
2128
2366
|
| `stacked` | `boolean` | `false` | Stack bars/areas |
|
|
2367
|
+
| `horizontal` | `boolean` | `false` | Horizontal bars (`bar` only): good for long labels or many groups; the height grows with the number of groups |
|
|
2129
2368
|
| `height` | `number` | `300` | 120–600 px |
|
|
2130
2369
|
| `loading` | `boolean` | `false` | |
|
|
2131
2370
|
| `emptyText` | `string` | | Text when there is no data |
|
|
2132
2371
|
|
|
2372
|
+
- Event `onSelect`: when the app listens, bars, slices and points are clickable; `event.detail` is `{ x, series }`: `x` is the clicked `xKey` value, `series` the `key` of the clicked bar (`null` for `line`, `area`, `pie`).
|
|
2373
|
+
- `format: 'percent'`: values are percentages; when every series is a percentage the value axis shows "%". All series share one value axis, so only combine series of similar size (orders and late orders, not orders and revenue).
|
|
2374
|
+
- Long horizontal-axis labels are shortened to fit, with the full text on hover; when each group gets too narrow, labels are tilted. Drop all-zero groups and merge small ones into "Other" before passing `data`.
|
|
2375
|
+
|
|
2133
2376
|
```tsx
|
|
2134
2377
|
<pos-chart
|
|
2135
2378
|
type="bar"
|
|
@@ -2139,6 +2382,34 @@ A chart drawn by POS; the app only passes data. Colors follow the POS palette.
|
|
|
2139
2382
|
/>
|
|
2140
2383
|
```
|
|
2141
2384
|
|
|
2385
|
+
### pos-stat
|
|
2386
|
+
|
|
2387
|
+
A metric card for report screens: label, large value, a secondary line and the change from the previous period. Put several cards in a `pos-grid`. On the page background it is a white card; inside a white block (`pos-section`, modal, block) it is a light gray tile.
|
|
2388
|
+
|
|
2389
|
+
| Property | Type | Default | Description |
|
|
2390
|
+
|---|---|---|---|
|
|
2391
|
+
| `label` | `string` | | Metric name |
|
|
2392
|
+
| `value` | `number \| string \| null` | | Numbers are formatted by `format`; strings show as is; `null` shows `—` |
|
|
2393
|
+
| `format` | `'number' \| 'money' \| 'percent'` | `'number'` | `percent`: `value` is a percentage (58.3 → 58.3%) |
|
|
2394
|
+
| `currency` | `string` | the shop's currency | ISO 4217 code, used with `money` |
|
|
2395
|
+
| `tone` | `'default' \| 'info' \| 'success' \| 'warning' \| 'critical'` | `'default'` | Color of the value |
|
|
2396
|
+
| `help` | `string` | | Secondary line below the value, for example "12 delivered orders" |
|
|
2397
|
+
| `change` | `number` | | Change in %, shown with an arrow; up is good (green), down is bad (red) |
|
|
2398
|
+
| `changeLabel` | `string` | | Note next to the change, for example "vs last week" |
|
|
2399
|
+
| `inverse` | `boolean` | `false` | Flip the colors of `change` for metrics where lower is better (late orders, costs) |
|
|
2400
|
+
| `selected` | `boolean` | `false` | Highlighted border, for a card that acts as a filter |
|
|
2401
|
+
| `loading` | `boolean` | `false` | Shows a placeholder instead of the value |
|
|
2402
|
+
|
|
2403
|
+
Event `onClick`: when the app listens, the card is clickable (Enter works too), for example clicking "Late" to filter the table below.
|
|
2404
|
+
|
|
2405
|
+
```tsx
|
|
2406
|
+
<pos-grid columns={4}>
|
|
2407
|
+
<pos-stat label="On-time rate" value={58.3} format="percent" help="12 delivered orders" change={4.2} changeLabel="vs last week" />
|
|
2408
|
+
<pos-stat label="Late" value={5} tone="critical" change={12.5} inverse selected={filter === 'late'} onClick={() => setFilter('late')} />
|
|
2409
|
+
<pos-stat label="Return fees" value={2450000} format="money" />
|
|
2410
|
+
</pos-grid>
|
|
2411
|
+
```
|
|
2412
|
+
|
|
2142
2413
|
---
|
|
2143
2414
|
|
|
2144
2415
|
Source: https://pos.pages.fm/developers/apps/runtime-api.md
|
|
@@ -2171,6 +2442,7 @@ interface Pos {
|
|
|
2171
2442
|
|
|
2172
2443
|
toast: { show(message: string, options?: ToastOptions): void };
|
|
2173
2444
|
ui: { confirm(options: ConfirmOptions): Promise<boolean> };
|
|
2445
|
+
download(file: DownloadFile): Promise<void>;
|
|
2174
2446
|
navigate(to: NavigateTarget): void;
|
|
2175
2447
|
refresh(): void;
|
|
2176
2448
|
close(): void;
|
|
@@ -2185,7 +2457,7 @@ interface Pos {
|
|
|
2185
2457
|
|---|---|---|---|---|---|
|
|
2186
2458
|
| `shop`, `user`, `app`, `data` | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
2187
2459
|
| `api`, `session` | ✓ | ✓ | ✓ | ✓ | ✓ |
|
|
2188
|
-
| `toast`, `ui.confirm` | ✓ | ✓ | ✓ | | ✓ |
|
|
2460
|
+
| `toast`, `ui.confirm`, `download` | ✓ | ✓ | ✓ | | ✓ |
|
|
2189
2461
|
| `navigate` | ✓ | ✓ | ✓ | | ✓ |
|
|
2190
2462
|
| `refresh` | ✓ | ✓ | ✓ | | |
|
|
2191
2463
|
| `close` | | | ✓ | | |
|
|
@@ -2292,6 +2564,25 @@ if (await pos.ui.confirm({ title: 'Push this order to the ERP?' })) {
|
|
|
2292
2564
|
}
|
|
2293
2565
|
```
|
|
2294
2566
|
|
|
2567
|
+
## pos.download
|
|
2568
|
+
|
|
2569
|
+
Downloads a file the app built (CSV, Excel, PDF…) to the user's computer. Code in the worker cannot create download links, so POS downloads it for the app; iframe pages can call it the same way.
|
|
2570
|
+
|
|
2571
|
+
```ts
|
|
2572
|
+
type DownloadFile = {
|
|
2573
|
+
filename: string; // the extension sets the type: .csv, .xlsx, .json, .txt, .pdf
|
|
2574
|
+
content: string | ArrayBuffer | ArrayBufferView; // text, or bytes (for example an Excel file from a library)
|
|
2575
|
+
type?: string; // MIME type, when the file name has none of the extensions above
|
|
2576
|
+
};
|
|
2577
|
+
|
|
2578
|
+
const csv = ['Order,Customer,Total', ...rows.map((r) => [r.id, r.name, r.total].join(','))].join('\n');
|
|
2579
|
+
await pos.download({ filename: 'orders.csv', content: csv });
|
|
2580
|
+
```
|
|
2581
|
+
|
|
2582
|
+
- Up to 10 MB per file. Other types, larger files or a wrong `content` type throw `PosRuntimeError` with `code: 'invalid_download'`.
|
|
2583
|
+
- CSV files get a UTF-8 BOM so Excel reads Vietnamese correctly.
|
|
2584
|
+
- Not available in hooks and `should_render`.
|
|
2585
|
+
|
|
2295
2586
|
## pos.navigate
|
|
2296
2587
|
|
|
2297
2588
|
Navigates to one of the app's pages.
|
|
@@ -2343,6 +2634,8 @@ pos.page.setPrimaryAction({ label: 'Export Excel', loading: false });
|
|
|
2343
2634
|
pos.on('primary-action', exportExcel);
|
|
2344
2635
|
```
|
|
2345
2636
|
|
|
2637
|
+
A `pos-page` `heading`, when set, replaces the `setTitle` title on the title row; `setTitle` still sets the browser tab title.
|
|
2638
|
+
|
|
2346
2639
|
In TypeScript `pos.page` is optional because only pages have it; in a page module use `pos.page!` or `(pos as PosPage).page`.
|
|
2347
2640
|
|
|
2348
2641
|
## Iframe pages
|
|
@@ -2466,6 +2759,7 @@ These groups are only available from a logged-in session in the POS UI; every ki
|
|
|
2466
2759
|
## Allowed endpoints
|
|
2467
2760
|
|
|
2468
2761
|
- Only use endpoints listed in the [Open API docs](/developers/api/en). Internal endpoints used by the POS UI can change at any time and will be blocked for app tokens.
|
|
2762
|
+
- AI tools look endpoints up with the `search_api` and `get_api` tools of the [MCP server](/developers/apps/en/ai-guide#mcp): they list only these endpoints, with parameters, response shapes and the `access.methods` each needs.
|
|
2469
2763
|
- Every path must belong to the current shop: `/shops/{shop_id}/...`.
|
|
2470
2764
|
- If data or an operation you need is missing, request it in the Open API instead of using internal endpoints.
|
|
2471
2765
|
|
|
@@ -2693,6 +2987,7 @@ The rules ship in `@pancake-pos/eslint-plugin`. Element rules are generated from
|
|
|
2693
2987
|
| `pos-*` elements | Elements, properties and events exist in the [catalog](/developers/apps/en/components) (with a suggestion for typos); valid enum values; valid children and slots; text only inside elements that accept text | error |
|
|
2694
2988
|
| Data and security | `pos.api.*` methods are listed in `access.methods`; literal `fetch` URLs and `pos-image` sources belong to `network.allowed_origins` (images may also use `public/`); no hardcoded shop id; no strings that look like API keys, tokens or secrets | error |
|
|
2695
2989
|
| Behavior | Modules `export default` a function; rendered lists have a `key`; input `value` is not set after an `await`; `before_submit` does not chain several API calls | warning |
|
|
2990
|
+
| Standard screens | Follow [Standard screens](/developers/apps/en/components#standard-screens): no emoji or decorative characters in labels; a `pos-section` holding only a heading and a number should be a `pos-stat`; `pos-section` nested at most two levels; icon-only buttons have a `tooltip`; the `pos-page` `heading` does not repeat `pos.page.setTitle` | warning |
|
|
2696
2991
|
| Iframe pages | No `window.top` or `window.parent`; no routes starting with `/_pos/` | error |
|
|
2697
2992
|
|
|
2698
2993
|
Static checks cannot catch dynamic cases such as element names built from variables. In development mode POS also logs warnings to the console at run time.
|
|
@@ -2729,7 +3024,7 @@ dist/
|
|
|
2729
3024
|
|
|
2730
3025
|
## MCP
|
|
2731
3026
|
|
|
2732
|
-
`pos-app mcp` starts an [MCP](https://modelcontextprotocol.io) server (stdio) so AI tools can look up the docs, see the shop's real metadata, check the app, try it, read errors, deploy and report bugs to Pancake without leaving the chat.
|
|
3027
|
+
`pos-app mcp` starts an [MCP](https://modelcontextprotocol.io) server (stdio) so AI tools can look up the docs and Open API endpoints, see the shop's real metadata, check the app, try it, read errors, deploy and report bugs to Pancake without leaving the chat.
|
|
2733
3028
|
|
|
2734
3029
|
```bash
|
|
2735
3030
|
npx @pancake-pos/app-cli mcp --shop 123 # run in the app's project folder, or add --cwd <folder>
|