@pancake-pos/app-cli 2026.1.5 → 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.
@@ -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
- │ └─ push-erp.ts
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
@@ -259,21 +261,21 @@ npx @pancake-pos/app-cli login --shop 123 --token <token>
259
261
  Then run:
260
262
 
261
263
  ```bash
262
- npx pos-app dev # watch and build, serve at http://localhost:5174
264
+ npm run dev # watch and build, serve at http://localhost:5174
263
265
  ```
264
266
 
265
267
  On the same management page, click **Development mode** and enter `http://localhost:5174` (see [Manage apps › Development mode](/developers/apps/en/manage-apps#development-mode)). While it is on, POS loads the app from `localhost` **in your browser only**; everyone else in the shop keeps using the active version. When you edit code, POS reloads the extensions without reloading the page. Use Chrome or Edge: Safari may block an `https` page from loading `http://localhost`.
266
268
 
267
- In development mode, `pos.api` can call the Open API only after the app has been uploaded at least once (`npx pos-app deploy`, no activation needed), and it uses the `access` of the latest uploaded version. Deploy again after changing `access`. After each such deploy, reload the POS page so POS gets a new token.
269
+ In development mode, `pos.api` can call the Open API only after the app has been uploaded at least once (`npm run deploy`, no activation needed), and it uses the `access` of the latest uploaded version. Deploy again after changing `access`. After each such deploy, reload the POS page so POS gets a new token.
268
270
 
269
271
  Development mode runs against the real data of the open shop, so try a test shop first. To see the app as a staff member with narrow permissions, deploy and activate it on a test shop and sign in with that account.
270
272
 
271
- While you write code, an editor with the ESLint extension shows problems as you type. Run `npx pos-app check` to validate the manifest as well.
273
+ While you write code, an editor with the ESLint extension shows problems as you type. Run `npm run check` to validate the manifest as well.
272
274
 
273
275
  ## 4. Build and upload
274
276
 
275
277
  ```bash
276
- npx pos-app deploy
278
+ npm run deploy
277
279
  ```
278
280
 
279
281
  This command:
@@ -420,7 +422,7 @@ The `pos-app` command (and the MCP server) needs a token to upload apps to the s
420
422
 
421
423
  Development mode lets you try the code you are writing inside real POS without affecting anyone else.
422
424
 
423
- 1. On your machine, run `npx pos-app dev` in the app folder (served at `http://localhost:5174` by default).
425
+ 1. On your machine, run `npm run dev` in the app folder (served at `http://localhost:5174` by default).
424
426
  2. Click **Development mode**, enter the **Dev server address** and click **Enable development mode**.
425
427
 
426
428
  ![The Development mode dialog](/static/developers/apps/manage/en/dev-mode.webp)
@@ -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. Write the code, then `check_app` until it reports no errors.
540
- 4. `dev_start`, try the app in POS, then `get_errors` to fix runtime errors.
541
- 5. `deploy` and ask an admin to activate the new version.
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,8 +656,9 @@ 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.
654
- - [ ] `npx pos-app check` reports no errors.
655
- - [ ] Tested with `npx pos-app dev` (an account with the Settings permission), then deployed to a test shop and tried with a narrow-permission staff account.
659
+ - [ ] Worker UI screens follow [Standard screens](/developers/apps/en/components#standard-screens): no emoji, no repeated title, numbers in `pos-stat`.
660
+ - [ ] `npm run check` reports no errors.
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
 
657
663
  ## Sample AGENTS.md
658
664
 
@@ -665,23 +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
- - page/: iframe page (React + Vite), gets `pos` from `connect()` in `@pancake-pos/ui-extensions/page`; routes starting with /_pos/ are reserved for POS.
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
- - npx pos-app dev: try in POS (turn on Development mode on the custom apps management page); POS reloads when code changes.
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.
689
+ - npm run dev: try in POS (turn on Development mode on the custom apps management page); POS reloads when code changes.
681
690
  - npx eslint .: check the code after every change; do not add eslint-disable to silence rules.
682
- - npx pos-app check: validate the manifest and code; fix every error before deploying.
683
- - npx pos-app deploy: build, validate the manifest, upload a new version.
684
- - 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.
691
+ - npm run typecheck: TypeScript for extensions/ (Preact) and page/ (React).
692
+ - npm run check: validate the manifest and code; fix every error before deploying.
693
+ - npm run deploy: build, validate the manifest, upload a new version (options after --, for example npm run deploy -- --message "...").
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.
685
695
  ````
686
696
 
687
697
  ---
@@ -1239,7 +1249,7 @@ An extension point (target) is a place POS exposes for apps. Each target defines
1239
1249
 
1240
1250
  ## Naming
1241
1251
 
1242
- Targets attached to a POS record are named `<resource>.<location>`, for example `order.details.action` or `purchase.index.selection-action`. Every resource offers the same six locations, so an app written for orders works the same way for purchases or warehouse transfers.
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)).
1243
1253
 
1244
1254
  | Resource | POS screen | List | Detail form |
1245
1255
  |---|---|---|---|
@@ -1248,6 +1258,7 @@ Targets attached to a POS record are named `<resource>.<location>`, for example
1248
1258
  | `purchase` | Purchases (goods receipts) | Purchase list | Purchase modal |
1249
1259
  | `export` | Exports (goods issues) | Export list | Export modal |
1250
1260
  | `warehouse_transfer` | Warehouse transfers | Transfer list | Transfer modal |
1261
+ | `customer` | Customers | Customer list | Customer profile |
1251
1262
 
1252
1263
  ## Target list
1253
1264
 
@@ -1505,7 +1516,7 @@ type PageData = {
1505
1516
 
1506
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).
1507
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`.
1508
- - Build Worker UI pages with `pos-page`, `pos-tabs`, `pos-filter-bar`, `pos-chart` and the other elements (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)).
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)).
1509
1520
 
1510
1521
  ```tsx
1511
1522
  import { render } from 'preact';
@@ -1741,7 +1752,7 @@ export default defineExtension('purchase.print-action', async ({ records }) => {
1741
1752
 
1742
1753
  ## `*.index.column`
1743
1754
 
1744
- 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.
1745
1756
 
1746
1757
  ```ts
1747
1758
  type ColumnData<R extends Resource> = { resource: R; records: RefOf<R>[] }; // records on the page, up to 500
@@ -1821,6 +1832,79 @@ stack.append(field, button);
1821
1832
  document.body.append(stack);
1822
1833
  ```
1823
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
+
1824
1908
  ## Layout
1825
1909
 
1826
1910
  ### pos-stack
@@ -1837,7 +1921,7 @@ Stacks children vertically or horizontally.
1837
1921
 
1838
1922
  ### pos-grid
1839
1923
 
1840
- A grid of equal columns; collapses to one column on narrow screens.
1924
+ A grid of equal columns; cells narrower than 180 px wrap to the next row (one column on narrow screens).
1841
1925
 
1842
1926
  | Property | Type | Default | Description |
1843
1927
  |---|---|---|---|
@@ -1846,14 +1930,14 @@ A grid of equal columns; collapses to one column on narrow screens.
1846
1930
 
1847
1931
  ### pos-section
1848
1932
 
1849
- A titled container, typically the outer shell of a block.
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)).
1850
1934
 
1851
1935
  | Property | Type | Description |
1852
1936
  |---|---|---|
1853
1937
  | `heading` | `string` | Title |
1854
1938
  | `description` | `string` | Short text below the title |
1855
1939
 
1856
- Slot `actions`: buttons shown at the right of the title.
1940
+ Slot `actions`: `pos-button`, `pos-link` and `pos-menu-button` shown at the right of the title.
1857
1941
 
1858
1942
  ```tsx
1859
1943
  <pos-section heading="Customer debt">
@@ -1882,6 +1966,8 @@ A horizontal rule. No properties.
1882
1966
  |---|---|---|
1883
1967
  | `level` | `1–4` | `3` |
1884
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
+
1885
1971
  ### pos-badge
1886
1972
 
1887
1973
  A short status label.
@@ -1919,6 +2005,40 @@ Displays an amount using the shop's currency and number format.
1919
2005
 
1920
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).
1921
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
+
1922
2042
  ## Inputs
1923
2043
 
1924
2044
  Properties shared by every input:
@@ -1979,6 +2099,22 @@ POS shows what the user types immediately and then sends `onChange` to the app,
1979
2099
  | `value` | `string \| null` | Date as `YYYY-MM-DD` |
1980
2100
  | `min`, `max` | `string` | Selectable range, as `YYYY-MM-DD` |
1981
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
+
1982
2118
  ## Feedback
1983
2119
 
1984
2120
  ### pos-banner
@@ -1999,6 +2135,34 @@ A prominent message inside the extension.
1999
2135
  |---|---|---|
2000
2136
  | `size` | `'small' \| 'medium' \| 'large'` | `'medium'` |
2001
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
+
2002
2166
  ## Data and actions
2003
2167
 
2004
2168
  ### pos-button
@@ -2009,12 +2173,41 @@ A prominent message inside the extension.
2009
2173
  | `size` | `'small' \| 'medium'` | `'medium'` | |
2010
2174
  | `loading` | `boolean` | `false` | Shows a busy state and blocks clicks |
2011
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) |
2012
2178
 
2013
2179
  `onClick` event.
2014
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
+
2015
2208
  ### pos-table
2016
2209
 
2017
- A data table. Cells only hold values (strings, numbers); POS formats them according to the column `format`.
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.
2018
2211
 
2019
2212
  | Property | Type | Default | Description |
2020
2213
  |---|---|---|---|
@@ -2024,6 +2217,10 @@ A data table. Cells only hold values (strings, numbers); POS formats them accord
2024
2217
  | `loading` | `boolean` | `false` | |
2025
2218
  | `pagination` | `{ page: number; pageSize: number; total: number } \| null` | `null` | App-side pagination |
2026
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 |
2027
2224
 
2028
2225
  ```ts
2029
2226
  type Column = {
@@ -2031,14 +2228,24 @@ type Column = {
2031
2228
  title: string;
2032
2229
  align?: 'left' | 'center' | 'right';
2033
2230
  width?: number; // px
2034
- 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
2035
2237
  };
2036
2238
  ```
2037
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
+
2038
2242
  Events:
2039
2243
 
2040
2244
  - `onPageChange`: `event.detail` is `{ page, pageSize }`.
2041
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.
2042
2249
 
2043
2250
  ```tsx
2044
2251
  <pos-table
@@ -2053,6 +2260,25 @@ Events:
2053
2260
  />
2054
2261
  ```
2055
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
+
2056
2282
  ### pos-modal
2057
2283
 
2058
2284
  A dialog opened from inside an extension (for example from a button in a block).
@@ -2074,25 +2300,37 @@ Slot `footer` for the dialog buttons. The `onClose` event fires when the user cl
2074
2300
 
2075
2301
  Actions with `presentation: "modal"` already get a POS modal; do not wrap them in `pos-modal`.
2076
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
+
2077
2315
  ## Worker UI pages
2078
2316
 
2079
- The four elements below build `render: "worker"` pages (see [Extension points › app.page](/developers/apps/en/targets#app-page)); they also work in blocks and modals.
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.
2080
2318
 
2081
2319
  ### pos-page
2082
2320
 
2083
- Page frame: heading, description, action buttons and content stacked vertically. The title bar at the top still belongs to POS (set it with `pos.page.setTitle`).
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.
2084
2322
 
2085
2323
  | Property | Type | Default | Description |
2086
2324
  |---|---|---|---|
2087
- | `heading` | `string` | | Heading inside the page |
2325
+ | `heading` | `string` | | Page title |
2088
2326
  | `description` | `string` | | Text below the heading |
2089
- | `width` | `'narrow' \| 'default' \| 'full'` | `'default'` | Content width: 720 px, 1,200 px or the full screen |
2327
+ | `width` | `'narrow' \| 'default' \| 'full'` | `'default'` | Content width: 720 px, up to 1,800 px or the full screen |
2090
2328
 
2091
- Slot `actions` for `pos-button` and `pos-link` at the right of the heading.
2329
+ Slot `actions` for `pos-button`, `pos-link` and `pos-menu-button`; on `app.page` pages it comes before the primary button.
2092
2330
 
2093
2331
  ### pos-tabs
2094
2332
 
2095
- A tab bar. The app keeps the selected tab and renders that tab's content as children.
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.
2096
2334
 
2097
2335
  | Property | Type | Description |
2098
2336
  |---|---|---|
@@ -2103,7 +2341,7 @@ The `onChange` event: `event.detail` is the selected tab's `id`. POS switches th
2103
2341
 
2104
2342
  ### pos-filter-bar
2105
2343
 
2106
- 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.
2107
2345
 
2108
2346
  ```tsx
2109
2347
  <pos-filter-bar>
@@ -2123,12 +2361,18 @@ A chart drawn by POS; the app only passes data. Colors follow the POS palette.
2123
2361
  | `type` | `'line' \| 'bar' \| 'area' \| 'pie'` | `'line'` | Chart type |
2124
2362
  | `data` | `object[]` | `[]` | One object per point, string or number values; up to 500 points and 21 fields |
2125
2363
  | `xKey` | `string` | | Field for the horizontal axis (slice names for `pie`) |
2126
- | `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 |
2127
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 |
2128
2368
  | `height` | `number` | `300` | 120–600 px |
2129
2369
  | `loading` | `boolean` | `false` | |
2130
2370
  | `emptyText` | `string` | | Text when there is no data |
2131
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
+
2132
2376
  ```tsx
2133
2377
  <pos-chart
2134
2378
  type="bar"
@@ -2138,6 +2382,34 @@ A chart drawn by POS; the app only passes data. Colors follow the POS palette.
2138
2382
  />
2139
2383
  ```
2140
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
+
2141
2413
  ---
2142
2414
 
2143
2415
  Source: https://pos.pages.fm/developers/apps/runtime-api.md
@@ -2170,6 +2442,7 @@ interface Pos {
2170
2442
 
2171
2443
  toast: { show(message: string, options?: ToastOptions): void };
2172
2444
  ui: { confirm(options: ConfirmOptions): Promise<boolean> };
2445
+ download(file: DownloadFile): Promise<void>;
2173
2446
  navigate(to: NavigateTarget): void;
2174
2447
  refresh(): void;
2175
2448
  close(): void;
@@ -2184,7 +2457,7 @@ interface Pos {
2184
2457
  |---|---|---|---|---|---|
2185
2458
  | `shop`, `user`, `app`, `data` | ✓ | ✓ | ✓ | ✓ | ✓ |
2186
2459
  | `api`, `session` | ✓ | ✓ | ✓ | ✓ | ✓ |
2187
- | `toast`, `ui.confirm` | ✓ | ✓ | ✓ | | ✓ |
2460
+ | `toast`, `ui.confirm`, `download` | ✓ | ✓ | ✓ | | ✓ |
2188
2461
  | `navigate` | ✓ | ✓ | ✓ | | ✓ |
2189
2462
  | `refresh` | ✓ | ✓ | ✓ | | |
2190
2463
  | `close` | | | ✓ | | |
@@ -2291,6 +2564,25 @@ if (await pos.ui.confirm({ title: 'Push this order to the ERP?' })) {
2291
2564
  }
2292
2565
  ```
2293
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
+
2294
2586
  ## pos.navigate
2295
2587
 
2296
2588
  Navigates to one of the app's pages.
@@ -2342,6 +2634,8 @@ pos.page.setPrimaryAction({ label: 'Export Excel', loading: false });
2342
2634
  pos.on('primary-action', exportExcel);
2343
2635
  ```
2344
2636
 
2637
+ A `pos-page` `heading`, when set, replaces the `setTitle` title on the title row; `setTitle` still sets the browser tab title.
2638
+
2345
2639
  In TypeScript `pos.page` is optional because only pages have it; in a page module use `pos.page!` or `(pos as PosPage).page`.
2346
2640
 
2347
2641
  ## Iframe pages
@@ -2465,6 +2759,7 @@ These groups are only available from a logged-in session in the POS UI; every ki
2465
2759
  ## Allowed endpoints
2466
2760
 
2467
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.
2468
2763
  - Every path must belong to the current shop: `/shops/{shop_id}/...`.
2469
2764
  - If data or an operation you need is missing, request it in the Open API instead of using internal endpoints.
2470
2765
 
@@ -2662,7 +2957,13 @@ const pos = await connect({ timeout: 10_000 });
2662
2957
 
2663
2958
  Every command accepts `--json` for machine-readable output (CI, scripts, the MCP server).
2664
2959
 
2665
- The `pos-app` command is available once `@pancake-pos/app-cli` is installed, so `npx pos-app …` works inside the project folder. Outside a project, call it by package name: `npx @pancake-pos/app-cli <command>`. The `pos-app` package on npm is not published by Pancake.
2960
+ How to run the CLI:
2961
+
2962
+ - **In a project** created by `create-pos-app`: `npm run dev`, `npm run check`, `npm run build`, `npm run deploy`. Put options after `--`, for example `npm run deploy -- --message "Fix the debt box"`.
2963
+ - **Other commands, or from anywhere:** `npx @pancake-pos/app-cli <command>`, for example `npx @pancake-pos/app-cli versions`. In an installed project this uses the project's own version.
2964
+ - **Short command everywhere:** install globally with `npm i -g @pancake-pos/app-cli`, then run `pos-app <command>`.
2965
+
2966
+ Do not run `npx pos-app`: the `pos-app` package on npm is not published by Pancake. Outside an installed project, npx downloads and runs that package.
2666
2967
 
2667
2968
  Activation and rollback happen on the [custom apps page](/developers/apps/en/manage-apps) in POS, so an admin always approves permissions before an app runs.
2668
2969
 
@@ -2686,6 +2987,7 @@ The rules ship in `@pancake-pos/eslint-plugin`. Element rules are generated from
2686
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 |
2687
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 |
2688
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 |
2689
2991
  | Iframe pages | No `window.top` or `window.parent`; no routes starting with `/_pos/` | error |
2690
2992
 
2691
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.
@@ -2722,7 +3024,7 @@ dist/
2722
3024
 
2723
3025
  ## MCP
2724
3026
 
2725
- `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.
2726
3028
 
2727
3029
  ```bash
2728
3030
  npx @pancake-pos/app-cli mcp --shop 123 # run in the app's project folder, or add --cwd <folder>