Files
jogos_matheus/.claude/commands/vitruvio-criar-form-mobile.md
2026-09-23 12:29:08 -03:00

256 lines
11 KiB
Markdown

# Create Vitruvio Mobile Form
> All messages shown to the user must be written in Portuguese.
Mobile forms are **not** desktop forms with a different skin. They use a different schema,
a smaller component set, a different JavaScript runtime, and an explicit server↔client data
contract. Read this whole skill before scaffolding — the mistakes here are not the desktop
mistakes.
## The four things that make mobile different
1. **Different schema.** Root is `<mobile-forms>`, namespace
`http://www.davinti.com.br/vitruvio/mobile-form`, XSD `vitruvio-mobile-form.xsd`.
2. **Only 18 components** (vs 78 desktop). Do **not** assume a desktop widget exists on
mobile. The full list:
`CheckBox, ComboBox, GoogleMapsField, HorizontalLayout, ImageLibraryByFieldValue,
ImageWidget, Label, MaskedField, MoneyField, NumericField, OptionGroup,
ProgressBarWidget, RatingStars, SignaturePadField, TabLayout, TextField, Toggle,
VerticalLayout` (plus structural `SubForm`/`ItemList`). Read
`~/.local/share/vitruvio-platform/docs/components/mobile/<Component>.md` before using one.
3. **Two JavaScript runtimes.**
- **Client-side** (`initScript`, `discoveryScript`, validators, component event scripts):
**modern React-Native JS** — arrow functions, Promises, `.then()/.catch()` are fine and
expected. Most APIs are **async** and return Promises.
- **Server-side** (`<ServerSide><Bridge>` `execute(...)` bodies): **Rhino ES5**, same rules
as scripts/desktop. This is the only place with access to platform libs and services
(`libService`, `runtimeService`, db, etc.).
4. **Data is explicit.** Nothing is auto-injected the way desktop process variables are. Every
piece of server data the form needs must be declared — via an `<Autoload>` variable, a
`<QueryDataSource>` (synced to the device, works offline), or fetched on demand from a
named `<Bridge>` with `vCommunicationService.executeOnServer(...)`.
## Step 1 — Confirm you are inside a Vitruvio repo
```bash
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
```
If `NOT_A_VITRUVIO_REPO`, stop and tell the user to `cd` into the correct repo.
## Step 2 — Determine variant and gather the data contract
Ask (only what is missing):
- **Panel or process mobile form?** (see **Differences: panel vs process** below)
- **Key** — the panel or process key (folder name).
- For a **process**: which `formKey`(s) — they must match the `activiti:formKey` in
`processes/<key>/<key>.bpmn`.
- **Which variables does the form need?** Because nothing is auto-injected, ask explicitly:
- process variables to read (each becomes an `<Autoload>` `<variable>` and/or a Bridge call)
- reference/lookup data (each becomes a `<QueryDataSource>` backed by a `queries/*.sql`)
- any server-only logic / libs needed (each becomes a `<Bridge>`)
- **Offline?** Whether the data sources must be available without connectivity (affects
`autoSyncOnInit` / `autoSyncOnDiscovery`).
## Differences: panel vs process
The schema, component set, ServerSide/Bridge mechanics and JS runtimes below are **identical**
for both. Only these differ:
| | Panel mobile form | Process mobile form |
|----------------|-------------------|---------------------|
| File | `panels/<key>/<key>-mobile.xml` | `processes/<key>/<key>-mobile.xml` |
| Root attribute | `<mobile-forms>` (no `processKey`) | `<mobile-forms processKey="<key>">` |
| Forms per file | one `<form>` | **one `<form formKey>` per BPMN `activiti:formKey`** (must match) |
| Manifest | set `forms.mobile` **and** `showInMobileList: true` on the panel entry | set `forms.mobile` (and optionally `forms.mobileAlternative`) on the process entry |
| Variables | use `engine.getGlobalVariable` / Autoload | process variables are fetched server-side via a Bridge (`runtimeService.getVariable`) and/or declared in `<Autoload>` |
> Filename: name files by the **artifact key + suffix** — `<key>-mobile.xml` (and
> `<key>-desktop.xml`, `<key>.bpmn`). This keeps every form searchable by its key instead of
> dozens of identical `form-mobile.xml` tabs. Older content used `form-mobile.xml` /
> `form_web_mobile.xml`; the real path is whatever `forms.mobile` points to, so legacy files
> still work — but new scaffolds use `<key>-mobile.xml`.
## Step 3 — Scaffold the file
### Skeleton (process variant shown; for a panel drop `processKey` and use a single `<form>`)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<mobile-forms processKey="<key>"
xmlns="http://www.davinti.com.br/vitruvio/mobile-form"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/mobile-form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-form.xsd">
<form formKey="formColeta">
<name>Coleta</name>
<description>Etapa de coleta no app</description>
<!-- Runs when the form opens. CLIENT-SIDE: modern JS, async APIs return Promises. -->
<initScript language="JavaScript">
<![CDATA[
function run() {
// Autoloaded variables land in the global scope:
engine.getField('nomeLoja').setValue(engine.getGlobalVariable('nomeLoja'));
}
]]>
</initScript>
<!-- Optional: pull server data when the task is discovered (before the user opens it). -->
<discoveryScript language="JavaScript">
<![CDATA[
function run() {
var params = { id: execution.getProcessInstanceId() };
vCommunicationService.executeOnServer('bridgeNumeroCarga', params).then(result => {
var n = parseInt(result, 10);
execution.setVariable('numeroCarga', n).then(ok => {}).catch(err => {
console.log('Erro ao setar numeroCarga localmente', err);
});
}).catch(err => {
console.log('Erro ao coletar numeroCarga no server', err);
});
}
]]>
</discoveryScript>
<!-- Block completing the task when a rule is not met. -->
<validators>
<ScriptValidator execution="COMPLETE" language="JavaScript" id="validadorComplete">
<![CDATA[
function Validator() {
var msg;
this.getMessage = function() { return msg; };
this.isValid = function() {
if (engine.getField('confirma').getValue() == 'Sim') { return true; }
msg = 'Confirme a execução antes de finalizar.';
return false;
};
}
var validator = new Validator();
]]>
</ScriptValidator>
</validators>
<components>
<VerticalLayout spacing="true" margin="true" width="100%">
<TextField id="nomeLoja" type="string" caption="Loja" readOnly="true" />
<OptionGroup id="confirma" type="string" caption="Executou?" required="true">
<entry key="Sim" value="Sim" />
<entry key="Nao" value="Não" />
</OptionGroup>
<SignaturePadField id="assinatura" caption="Assinatura" />
</VerticalLayout>
</components>
<!-- Explicitly declared variables auto-injected into the form's scope on load. -->
<Autoload>
<variables autoInjectionScope="ENGINE_GLOBAL_SCOPE" autoPersist="true">
<variable>nomeLoja</variable>
<variable>numeroCarga</variable>
</variables>
</Autoload>
<!-- Everything the server provides to this form. -->
<ServerSide>
<!-- Named queries (queries/*.sql) synced to the device for offline lookups. -->
<DataSources>
<QueryDataSource key="qry_produtos_carga" autoSyncOnInit="true" autoSyncOnDiscovery="true" refreshInSeconds="60" />
</DataSources>
<!-- Server-side functions. Rhino ES5. Full access to libService / runtimeService / db.
Called from the client via vCommunicationService.executeOnServer('id', params). -->
<Bridges>
<Bridge language="JavaScript" id="bridgeNumeroCarga">
<![CDATA[
function execute(params) {
var numeroCarga = runtimeService.getVariable(params.id, 'numeroCarga');
return numeroCarga ? numeroCarga : -1;
}
]]>
</Bridge>
</Bridges>
</ServerSide>
</form>
</mobile-forms>
```
## How libs and process variables reach the mobile form
The mobile app cannot call `libService.loadScript(...)` or read process variables directly —
those live on the server. The pattern is always **declare a Bridge, call it from the client**:
```xml
<!-- SERVER-SIDE (Rhino ES5): a lib used to build a barcode image -->
<Bridge language="JavaScript" id="imagemCodBarras">
<![CDATA[
function execute(codbarras) {
var generator = libService.loadScript('barcode-gen');
return ',' + generator.generateEAN13BarcodeImageAsBase64({ value: codbarras });
}
]]>
</Bridge>
```
```javascript
// CLIENT-SIDE (modern JS): call the bridge, use the Promise result
vCommunicationService.executeOnServer('imagemCodBarras', codigoBarras).then(base64 => {
engine.getField('codigoImagem').setValue(base64);
}).catch(err => console.log('Erro no bridge imagemCodBarras', err));
```
Rules of thumb:
- Anything needing a **platform lib, the database, or a platform service** → put it in a
**Bridge** (server, ES5) and call it with `vCommunicationService.executeOnServer`.
- **Reference/lookup tables** the form reads repeatedly → a **`QueryDataSource`** backed by a
`queries/*.sql` (works offline once synced).
- **Process variables** the form needs → either declare them in `<Autoload>` or fetch them
in a Bridge via `runtimeService.getVariable(processInstanceId, 'varName')` and store locally
with `execution.setVariable(...)`.
- Client-side `execution.getVariable(...)` / `execution.setVariable(...)` are **async** and
return Promises — use `.then()`.
## List-based entry: SubForm + ItemList
For "add many items" screens (collect a list of rows), use a `SubForm` with an `<ItemList>`:
```xml
<SubForm formKey="executarAcao">
<name>Executar AÇÃO</name>
<initScript language="JavaScript">
<![CDATA[ function run(apply) { if (apply) { apply(); } } ]]>
</initScript>
<ItemList addItemButtonCaption="Gravar na Lista" caption="Itens">
<property id="OBSERVACAO" caption="Observação" />
</ItemList>
<components>
<VerticalLayout width="100%" spacing="true" margin="true">
<!-- fields captured per item -->
</VerticalLayout>
</components>
</SubForm>
```
Validate list completeness with `engine.getSubFormListSizeByStatus(function(size){ ... }, "all")`
inside a `COMPLETE` validator.
## Step 4 — Manifest
If run **standalone**, update the matching entry in `vitruvio.json`:
- **Panel**: set `forms.mobile = "panels/<key>/<key>-mobile.xml"` and `showInMobileList: true`.
- **Process**: set `forms.mobile = "processes/<key>/<key>-mobile.xml"`.
Full entry creation is handled by **vitruvio-criar-painel** / **vitruvio-criar-processo**.
## Step 5 — Report
Tell the user:
- File created/updated and variant (panel/process).
- Which variables/queries/bridges were declared, and that **only declared data is available**
on the device — anything else must be added as an Autoload variable, QueryDataSource, or Bridge.
- Reminder: client scripts are modern JS (Promises); Bridge bodies are server-side Rhino ES5.
- For processes: each `<form formKey>` must match an `activiti:formKey` in the BPMN.
- Suggest reading `docs/components/mobile/` and an example
(`~/.local/share/vitruvio-platform/examples/processes/*/form_web_mobile.xml`) for richer screens.