From f2dc1775b4d0f5afe0d6469169c8acb2e17fe126 Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Thu, 15 Oct 2026 17:20:05 +0100 Subject: [PATCH 1/9] DEV-1719 Clarify actual server action request verification --- docs/API/core/app-actions-v3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API/core/app-actions-v3.md b/docs/API/core/app-actions-v3.md index 5063e22c..f7b910cf 100644 --- a/docs/API/core/app-actions-v3.md +++ b/docs/API/core/app-actions-v3.md @@ -267,7 +267,7 @@ async function execute(context) {

Never let a setting value leave the action. Never return it, never log it (console.*), never put it in a thrown or returned error message, and never put it in a URL or query string — send it in a request header or body instead. Return values and error messages are stored in the app action logs and returned to the caller of runWithResult(); console output and every request URL are captured in the server runner logs. A provider that only accepts its key in the URL query string can not be called without breaking this rule.

-

Server actions run in a headless browser, so fetch from a server action is a cross-origin browser request. Provider APIs that do not allow cross-origin browser requests (CORS) reject it. Server-side HTTP requests without this restriction are not available yet.

+

Server actions use native fetch in a headless browser runtime. A provider's CORS policy for ordinary browser requests does not establish whether a server action can call it. Test the request in a server action and diagnose the observed HTTP response or network failure before declaring the integration unsupported.

A credential reaches `context.settings` from one of two sources: From 1fc3eae3a57bce98b620ad71ae287c3da83a575f Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Fri, 16 Oct 2026 14:40:25 +0100 Subject: [PATCH 2/9] DEV-1719 Document execution-dependent action results --- docs/API/core/app-actions-v3.md | 51 ++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/docs/API/core/app-actions-v3.md b/docs/API/core/app-actions-v3.md index f7b910cf..aaeda64b 100644 --- a/docs/API/core/app-actions-v3.md +++ b/docs/API/core/app-actions-v3.md @@ -940,32 +940,51 @@ await Fliplet.App.V3.Actions.run('confirm-booking', { ### `runWithResult(nameOrId, payload)` — wait for result -Executes the action and waits for it to complete. Returns the value from the `execute()` function. +Executes the action and waits for it to complete. The result shape depends on where it runs. - **Parameters:** - `nameOrId` (String or Number) — The action name or numeric ID - `payload` (Optional Object) — Data to pass as `context.payload`. -- **Returns:** Promise that resolves with the return value of the `execute()` function. The result is limited to 6MB. +- **Returns:** A Promise resolving to the result described below. Server results are limited to 6MB. + +| Execution | Resolved value | +|-----------|----------------| +| Server | A result object: `{ success: true, data: }` on success. Read the action's value from `result.data`, including `false`, `0`, `null`, and an empty string. | +| Client | The value returned by `execute()` directly. A thrown error rejects the Promise. | +| `any` | Uses the client behavior when the action is bundled locally; otherwise it runs on the server and uses the server result object. | + +Server execution failures can resolve with `{ success: false, error: { message: "..." } }`; they do not necessarily reject the Promise. Returning `{ success: false, error: "..." }` from the action can also produce a failed server result, with the returned value retained in `data`. Check `result.success` before using `result.data`, and handle any application-specific outcome within that value. Request failures, such as an HTTP or network error, can reject the Promise and need a `catch` handler. ```js -// Run and get the result -var result = await Fliplet.App.V3.Actions.runWithResult('confirm-booking', { - entryId: 123, - name: 'Nick' -}); -// result is exactly what execute() returned -// e.g., { success: true } -if (result.success) { - // Booking confirmed +// confirm-booking is configured with environment: 'server'. +// Its execute() function returns { bookingId: 123, confirmed: true }. +try { + var result = await Fliplet.App.V3.Actions.runWithResult('confirm-booking', { + entryId: 123, + name: 'Nick' + }); + + if (!result || result.success !== true) { + throw new Error('The booking action failed.'); + } + + var booking = result.data; + if (booking.confirmed) { + // Booking confirmed; booking.bookingId is 123. + } +} catch (error) { + // Show a failure state. Do not automatically retry a potentially completed write. } -// Run by action ID instead of name -var result = await Fliplet.App.V3.Actions.runWithResult(12345, { - entryId: 123 +// calculate-total is bundled locally with environment: 'client' or 'any'. +// Its execute() function returns a number, so no .data access is needed. +var total = await Fliplet.App.V3.Actions.runWithResult('calculate-total', { + quantity: 2 }); -// result is the return value of execute() ``` +Choose the result handling for the action's execution environment. Do not use `result.data || result`: it loses valid falsy values and can mistake a client action's own `data` property for a server wrapper. An action ID can be used instead of its name in either example. +

Rate limiting: the run action endpoint is limited to 30 requests per minute. Contact the Fliplet team for more details.

Payload size limit: the input payload is serialized to JSON and sent to the execution backend. The effective maximum size is constrained by the transport/infrastructure (commonly the maximum URL/query-string length if the payload is passed as a URL-encoded query parameter). Keep payloads small and pass references (IDs/keys) for large inputs. @@ -1589,7 +1608,7 @@ All error responses follow this format: | `Fliplet.App.V3.Actions.update(id, data)` | `id` (Number), `data` (Object) | `action` (object) | Update a master action | | `Fliplet.App.V3.Actions.remove(id)` | `id` (Number) | void | Delete an action (master + production) | | `Fliplet.App.V3.Actions.run(nameOrId, payload)` | `nameOrId` (String/Number), `payload` (Object) | Promise | Queue action for execution, no return value | -| `Fliplet.App.V3.Actions.runWithResult(nameOrId, payload)` | `nameOrId` (String/Number), `payload` (Object) | Return value of `execute()` | Run action and wait for result | +| `Fliplet.App.V3.Actions.runWithResult(nameOrId, payload)` | `nameOrId` (String/Number), `payload` (Object) | Server result object; direct `execute()` value on client | Run action and wait for result; see result handling above | | `Fliplet.App.V3.Actions.publish(id)` | `id` (Number) | `{ action }` | Publish master to production | | `Fliplet.App.V3.Actions.unpublish(id)` | `id` (Number) | void | Remove from production | | `Fliplet.App.V3.Actions.getLogs(options)` | `{ id, where, limit, offset }` | `{ count, logs }` | Get execution logs | From 5286a701f166074dce51eb6d1d435d82e43708a3 Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Fri, 16 Oct 2026 15:35:29 +0100 Subject: [PATCH 3/9] DEV-1719 Document explicit action caller authorization --- docs/API/core/app-actions-v3.md | 36 +++++++++++++++++++++++++++++++++ docs/API/fliplet-session.md | 23 +++++++++++++++++---- docs/API/v3/auth.md | 2 +- 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/docs/API/core/app-actions-v3.md b/docs/API/core/app-actions-v3.md index aaeda64b..2b4b60f1 100644 --- a/docs/API/core/app-actions-v3.md +++ b/docs/API/core/app-actions-v3.md @@ -391,6 +391,40 @@ async function execute(context) { // Requires dependency: fliplet-notifications ``` +## Authorize callers before accessing private data + +A server action keeps provider credentials off the device; it does not automatically restrict who can receive the action's result. Decide which operations are public and which require an app user. Reading public information need not require login. Reading private account data or performing restricted writes requires an authorization check inside the action, before contacting the provider. A hidden button or a protected screen is not that check. + +For an on-demand action using Data Source login, include `fliplet-session` in `dependencies`. Read the calling session with `Fliplet.Session.run({ url: 'v1/session', cache: false })` and inspect `response.session.entries.dataSource`. Check its `dataSourceId` against the intended login Data Source and require its `id` (the authenticated entry ID). A session existing, or an email matching a row, is not sufficient. Do not accept caller identity or permission claims from `context.payload` or session `client` fields. + +```js +// Action environment: 'server'; dependencies: ['fliplet-session']. +// Replace 123 with the app's intended login Data Source ID. +async function execute(context) { + try { + const response = await Fliplet.Session.run({ url: 'v1/session', cache: false }); + const session = response && response.session; + const entry = session && session.entries && session.entries.dataSource; + + if (!entry || entry.dataSourceId !== 123 || !entry.id) { + return { success: false, error: 'Access denied' }; + } + + // Identity check only. Apply the operation's account/ownership policy + // before adding a private provider request here. + return { success: true, identityVerified: true }; + } catch (error) { + return { success: false, error: 'Unable to verify access' }; + } +} +``` + +This fresh read rejects request failures instead of returning a stored session; see [fresh session reads](../fliplet-session#read-the-session-without-a-stored-session-fallback). It establishes the authenticated entry, not necessarily its current permissions. If access depends on current account status, role or record ownership, read the authoritative records and apply that policy before the provider request. Session entry data can reflect an earlier login; do not treat a snapshot as proof that an account is still active. Add `fliplet-datasources` when using `Fliplet.DataSources` for those checks. + +Scheduled actions have no interactive caller. Give a scheduled service operation its own explicit permitted resources and operations. Never interpret a missing on-demand caller as permission to run the scheduled path. Likewise, keep caller authorization separate from the provider credential: a valid API key does not establish the caller's access. + +Verify both permitted and denied calls: the intended user succeeds, while signed-out users, a login from another Data Source, forged payload identity and failed session reads make no provider request. Verify current-account or ownership restrictions separately when the app requires them. + ## Dependencies If your action code uses Fliplet APIs, you must include the corresponding package in the `dependencies` array. Dependencies can be Fliplet package names or external URLs. @@ -409,6 +443,7 @@ dependencies: [ | Package | Required when using | Description | API Reference | |---------|---------------------|-------------|---------------| +| `fliplet-session` | `Fliplet.Session` | Session API — read the calling session | [Session JS API](../fliplet-session) | | `fliplet-datasources` | `Fliplet.DataSources` | Data Sources API — connect, find, insert, update, remove entries | [Data Sources JS API](https://developers.fliplet.com/API/fliplet-datasources.html) | | `fliplet-media` | `Fliplet.Media` | Media API — upload, retrieve, and manage media files | [Media JS API](https://developers.fliplet.com/API/fliplet-media.html) | | `fliplet-communicate` | `Fliplet.Communicate` | Communication API — send emails, push notifications, SMS | [Communicate JS API](https://developers.fliplet.com/API/fliplet-communicate.html) | @@ -491,6 +526,7 @@ await Fliplet.Media.Files.upload({ folderId: folderId, file: fileBlob }); When writing action code, **match each `Fliplet.*` namespace used in the `execute` function to its corresponding package** and include it in the `dependencies` array: +- Code uses `Fliplet.Session` → add `fliplet-session` - Code uses `Fliplet.DataSources` → add `fliplet-datasources` - Code uses `Fliplet.Communicate` → add `fliplet-communicate` - Code uses `Fliplet.Media` → add `fliplet-media` diff --git a/docs/API/fliplet-session.md b/docs/API/fliplet-session.md index 41f3a2cf..ff841a06 100644 --- a/docs/API/fliplet-session.md +++ b/docs/API/fliplet-session.md @@ -20,16 +20,31 @@ Read, write, and clear the current user session — including cached offline ses // Get the session using the locally cached offline data. // This is fast and work across all network conditions. Fliplet.User.getCachedSession().then(function onCachedSessionRetrieved(session) { - console.log(session); + // Use the session for UI state; do not log authentication fields. }); -// This only works if the app is online as it makes an API request -// to Fliplet servers to return the live data. +// Request the current session. If the request fails, get() can return +// a stored session; use the fresh-read method below for access checks. Fliplet.Session.get().then(function onSessionRetrieved(session) { - console.log(session); + // Use the session for UI state; do not log authentication fields. }); ``` +### Read the session without a stored-session fallback + +Use `Fliplet.Session.run({ url: 'v1/session', cache: false })` when a failed session read must stop an operation. Include `fliplet-session` in an app action's dependencies. The promise resolves to a response object containing `session`; it rejects when the request fails. Treat a missing or malformed `response.session` as a failed access check. + +```js +const response = await Fliplet.Session.run({ url: 'v1/session', cache: false }); +const session = response && response.session; +const login = session && session.entries && session.entries.dataSource; +// For Data Source login, login.dataSourceId identifies the login Data Source +// and login.id identifies its authenticated entry. Check the intended source +// and operation's permissions before granting access. +``` + +`Fliplet.Session.get()` also requests the session, but can fall back to a stored session if the request fails. `Fliplet.User.getCachedSession()` is appropriate for cached UI decisions; neither a cached result nor the existence of a session establishes permission for a private operation. In a server action, use the fresh read above and follow [caller authorization](core/app-actions-v3#authorize-callers-before-accessing-private-data). Do not log the session response or its authentication fields. + ### Get a key from the current session ```js diff --git a/docs/API/v3/auth.md b/docs/API/v3/auth.md index a2476603..b6103679 100644 --- a/docs/API/v3/auth.md +++ b/docs/API/v3/auth.md @@ -219,7 +219,7 @@ async function isLoggedIn() { } ``` -`Fliplet.User.getCachedSession()` is fast and works offline because it reads from local storage. Use it for UI decisions (show/hide content). For server-verified session checks, use `Fliplet.Session.get()` instead (requires network). +`Fliplet.User.getCachedSession()` is fast and works offline because it reads from local storage. Use it for UI decisions (show/hide content). `Fliplet.Session.get()` requests the session but can fall back to a stored session on failure. When an access decision requires a fresh read that fails on request errors, use the [session read without stored-session fallback](../fliplet-session#read-the-session-without-a-stored-session-fallback). ### Get the Logged-In User's Data From b7d024c07d4bc7ff6cb61a2b9dc483ee2f2d3c61 Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Fri, 16 Oct 2026 19:21:28 +0100 Subject: [PATCH 4/9] DEV-1719 Clarify published action schedules and execution logs --- docs/API/core/app-actions-v3.md | 34 ++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/docs/API/core/app-actions-v3.md b/docs/API/core/app-actions-v3.md index 2b4b60f1..9858ab7d 100644 --- a/docs/API/core/app-actions-v3.md +++ b/docs/API/core/app-actions-v3.md @@ -61,9 +61,10 @@ Write and run JavaScript code directly on the server or client to perform automa Actions have two versions: **master** (development) and **production** (live). -- `create()` always creates a **master** action. +- `create()` always creates a **master** action. Creating, updating or restoring a master action does not start its automatic schedule or change the published schedule. - `update()` can only update the **master** action. You can **not** update a production action directly — update the master and republish. -- `publish()` copies the current master action to production. The app itself must be published first. +- Publishing the whole app (`POST /v1/apps/:appId/publish`) copies its V3 actions to production, including apps that contain only actions. This does not require a website URL or a web, iOS or Android build. +- `publish()` copies one master action to production. The app itself must already be published. - `unpublish()` removes the production version. The master version remains. - `remove()` deletes the master action. If a production version exists, it is also deleted. You can **not** delete a production action directly. - **Scheduled actions** (`schedule` trigger) only run the **published (production)** version. @@ -740,7 +741,7 @@ triggers: [ ## Create an action -Use `Fliplet.App.V3.Actions.create()` to create a V3 action. This always creates a **master** (development) version. To run the action in production/live apps, you must also publish it using `publish()`. +Use `Fliplet.App.V3.Actions.create()` to create a V3 action. This always creates a **master** (development) version. To run it in production/live apps, publish the whole app or use `publish()` for an already-published app. ### Parameters @@ -800,7 +801,7 @@ Example timezones: ### Create a scheduled action -A scheduled action requires: `frequency` (cron expression), `triggers: [{ trigger: 'schedule' }]`, and `environment` set to `"server"` or `"any"`. The action only runs in production after being published with `publish()`. +A scheduled action requires `active: true`, `frequency` (cron expression), `triggers: [{ trigger: 'schedule' }]` and `environment` set to `"server"` or `"any"`. Publish the whole app to activate its production schedule, or use `publish()` for one action in an already-published app. Saving the master action alone does not start the schedule. ```js var result = await Fliplet.App.V3.Actions.create({ @@ -825,7 +826,8 @@ var result = await Fliplet.App.V3.Actions.create({ // result — the created action object (returned directly, not wrapped) // result.id — use this ID to publish, update, or delete the action -// IMPORTANT: You must publish the action for the schedule to be active in production: +// Publish the whole app to activate this schedule. +// Alternatively, if the app is already published, publish this action alone: // await Fliplet.App.V3.Actions.publish(result.id); ``` @@ -1158,13 +1160,15 @@ var result = await Fliplet.App.V3.Actions.update(12345, { ## Temporarily deactivate an action -Update an action with `active: false` to disable it. Inactive actions do **not** run on any trigger (manual, schedule, log, or analytics). To reactivate, set `active: true` and republish if needed. +Updating the master action with `active: false` disables the master only. If a production copy exists, republish the action or the whole app to disable that copy too. Until then, its published schedule keeps running. To reactivate it, set `active: true` and republish. `unpublish()` is another way to stop production execution while retaining the master. ```js var result = await Fliplet.App.V3.Actions.update(12345, { active: false }); -// Action is now inactive — it will not run until active is set back to true +// For an already-published action, apply the disabled state to production: +await Fliplet.App.V3.Actions.publish(12345); +// Both versions are inactive after publication succeeds. ``` ## Delete an action @@ -1183,7 +1187,7 @@ await Fliplet.App.V3.Actions.remove(12345); ## Publish an action -Use `Fliplet.App.V3.Actions.publish()` to copy the master action to production. Only published actions run in live apps. The app itself must be published first. +Use `Fliplet.App.V3.Actions.publish()` to copy one master action to production after the app itself has been published. Publishing the whole app also publishes its V3 actions. See [Master vs production lifecycle](#master-vs-production-lifecycle). - **Parameters:** `id` (Number) — The action ID - **Returns:** Promise resolving to `{ action: }` @@ -1193,7 +1197,7 @@ var result = await Fliplet.App.V3.Actions.publish(12345); // result.action — the published production action object ``` -

If the app has not been published, publish() returns an APP_NOT_PUBLISHED error. Publish the app first, then publish the action.

+

If the app has not been published, publish() returns an APP_NOT_PUBLISHED error. Publish the whole app to create its production copy and publish its V3 actions.

## Unpublish an action @@ -1377,7 +1381,7 @@ The restore: - Takes a `pre-restore` snapshot of the current configuration first, so the restore can itself be undone. - Restores every field from the snapshot **except** `masterTaskId` and `productionTaskId` (those describe the action's identity and publish state, not its config). -- Re-applies the cron schedule if the restored configuration has a `frequency` and is `active`. +- Restores the master's schedule configuration without starting it or changing the production schedule. Republish the action or the whole app to apply the restored configuration to production. - Writes an `appAction.v3.restore` audit log carrying both `restoredFromVersionId` and `preRestoreVersionId`. ```js @@ -1449,7 +1453,7 @@ The `data` object contains: | `payload` | Object | Always | The payload that was passed to the action. Empty `{}` for scheduled actions. For log triggers, contains the `{ trigger, log }` object. | | `duration` | Number | Always | Execution time in milliseconds | | `actionVersion` | String | Always | Always `"v3"` | -| `result` | Object | On success (on-demand only) | `{ data: , success: true }`. Not present for successful scheduled runs. | +| `result` | Object | When the server returns a result | The server execution result, including for scheduled runs. On success: `{ data: , success: true }`. | | `error` | Object | On failure only | Error details: `{ name: "ErrorType", message: "description", stack: "..." }` | ### Example: successful on-demand log entry @@ -1486,12 +1490,16 @@ The `data` object contains: "runOn": "server", "taskId": 130057, "duration": 747, - "actionVersion": "v3" + "actionVersion": "v3", + "result": { + "data": { "processedCount": 12 }, + "success": true + } } } ``` -

Successful scheduled log entries do not include a result property, even if the execute() function returns a value.

+Scheduled server execution logs include the returned result. Keep secrets out of return values as well as log messages. ### Example: failed action log entry From 82a9e9f0ed24e24c31590662cf2c1bd93bf875eb Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Fri, 16 Oct 2026 19:44:28 +0100 Subject: [PATCH 5/9] DEV-1719 Document Studio publication for background integrations --- docs/API/core/app-actions-v3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/API/core/app-actions-v3.md b/docs/API/core/app-actions-v3.md index 9858ab7d..29ec9891 100644 --- a/docs/API/core/app-actions-v3.md +++ b/docs/API/core/app-actions-v3.md @@ -63,7 +63,7 @@ Actions have two versions: **master** (development) and **production** (live). - `create()` always creates a **master** action. Creating, updating or restoring a master action does not start its automatic schedule or change the published schedule. - `update()` can only update the **master** action. You can **not** update a production action directly — update the master and republish. -- Publishing the whole app (`POST /v1/apps/:appId/publish`) copies its V3 actions to production, including apps that contain only actions. This does not require a website URL or a web, iOS or Android build. +- Publishing the whole app (`POST /v1/apps/:appId/publish`) copies its V3 actions to production, including apps that contain only actions; no website URL or web, iOS or Android build is required. In Studio V3, choose **Publish** > **Publish project changes**. - `publish()` copies one master action to production. The app itself must already be published. - `unpublish()` removes the production version. The master version remains. - `remove()` deletes the master action. If a production version exists, it is also deleted. You can **not** delete a production action directly. From 505234b637a20cf02cbeeb7aedf882b6f456b48a Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Fri, 16 Oct 2026 23:58:56 +0100 Subject: [PATCH 6/9] DEV-1719 Correct direct action invocation trigger contract --- docs/API/core/app-actions-v3.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/API/core/app-actions-v3.md b/docs/API/core/app-actions-v3.md index 29ec9891..84e50f37 100644 --- a/docs/API/core/app-actions-v3.md +++ b/docs/API/core/app-actions-v3.md @@ -565,7 +565,9 @@ Each trigger in the `triggers` array is an object with these properties: ### `manual` trigger -The `manual` trigger has no `where` clause. It fires when you call `run()` or `runWithResult()`. +The `manual` trigger has no `where` clause. Direct calls to `run()` or `runWithResult()` do not require this trigger. + +Trigger configuration does not authorize callers. Removing `manual`, or using an empty `triggers` array, does not prevent direct invocation. For restricted operations, [authorize callers inside the server action](#authorize-callers-before-accessing-private-data). ```js triggers: [{ trigger: 'manual' }] @@ -833,7 +835,7 @@ var result = await Fliplet.App.V3.Actions.create({ ### Create an on-demand (manual) action -

To run an action on-demand (e.g., triggered by a user), you must include the manual trigger. Without it, run() and runWithResult() cannot execute the action.

+The example includes a `manual` trigger, but direct invocation does not require it. See [the manual trigger and caller authorization](#manual-trigger). ```js var result = await Fliplet.App.V3.Actions.create({ @@ -950,9 +952,9 @@ var result = await Fliplet.App.V3.Actions.create({ ## Run an on-demand action -Actions with a `manual` trigger can be executed using `run()` (fire and forget) or `runWithResult()` (wait for the return value). Pass the action `name` (String) or `id` (Number) as the first parameter. +Use `run()` (fire and forget) or `runWithResult()` (wait for the return value) to invoke an action directly. Pass the action `name` (String) or `id` (Number) as the first parameter. -

Only actions with a manual trigger can be run via run() or runWithResult(). Calling these methods on an action without a manual trigger results in an error. The action must also have active: true.

+The action must have `active: true`. A `manual` trigger is not required, including when `triggers` is empty. App access, the action's execution environment and any authorization checks inside the action still apply. See [the manual trigger and caller authorization](#manual-trigger). ### `run(nameOrId, payload)` — fire and forget From 6d1e57c1fd99965d9ec33ece39e99f558d7aba7a Mon Sep 17 00:00:00 2001 From: Tony Wu Date: Sat, 17 Oct 2026 00:43:20 +0100 Subject: [PATCH 7/9] DEV-1719 Correct bootstrap dependency loading contract --- docs/API/v3/app-bootstrap.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/API/v3/app-bootstrap.md b/docs/API/v3/app-bootstrap.md index 7336a033..836ecb1c 100644 --- a/docs/API/v3/app-bootstrap.md +++ b/docs/API/v3/app-bootstrap.md @@ -15,14 +15,14 @@ Each constraint maps to a concrete platform guarantee: 1. Dependencies resolve through the Fliplet asset pipeline so versioning, caching, and per-environment injection work. 2. Source files are fetched with signed media requests so private apps don't leak their screens. -3. The runtime registers its globals (`Fliplet.ENV`, `Fliplet.Router`, `Fliplet.Session`, etc.) before your framework mounts. +3. `Fliplet().then(...)` waits for runtime initialization before your framework mounts. Optional packages such as `Fliplet.Session` must also be loaded before use. 4. The viewport is locked so native apps don't pinch-zoom or auto-zoom when users focus an input field.

Skip any of these constraints and the app fails at boot, works in dev but breaks in production, or is rejected by the boot-HTML lint.

## 1. Fetch dependencies with `Fliplet.require.lazy` -Any registered Fliplet dependency (Vue, React, Vue Router, etc.) **must** be loaded with `Fliplet.require.lazy(name)`. Never use `