This commit is contained in:
Matheus
2026-09-23 12:29:08 -03:00
commit f5d231ab8f
59 changed files with 16658 additions and 0 deletions
+121
View File
@@ -0,0 +1,121 @@
---
name: vitruvio-adicionar-menu
description: >
Use ONLY when the user explicitly asks to add a menu item or menu entry in a Vitruvio repository.
Triggers: "add menu item", "add to menu", "adicionar ao menu", "criar item de menu",
"add panel to menu", "adicionar painel no menu", or any explicit request to register
something in the vitruvio.json "menu" array.
Do NOT trigger for general panel/process/script creation — menu entries are separate.
---
# Add Vitruvio Menu Item
> All messages shown to the user must be written in Portuguese.
You are adding an entry to the `menu` array in `vitruvio.json`. Menu entries are **never created automatically** — only when the user explicitly requests it. Follow these steps in order.
## 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 — Show the current menu structure
Read `vitruvio.json` and print the menu tree as a readable outline. Example format:
```
menu:
[0] menu-root "Meu Módulo" (MENU)
[0] menu-panel "Meu Painel" (PAINEL → my-panel)
[1] menu-report "Meu Relatório" (RELATORIO → my-report)
```
If the `menu` array is empty or absent, say so.
## Step 3 — Collect item details
Ask the user (in a single message, only ask what is missing):
- **Type** — one of:
- `PAINEL` — links to a panel (`panelKey`)
- `RELATORIO` — links to a report (`reportKey`)
- `PROCESSO` — links to a process (`processKey`)
- `MENU` — a submenu/folder (no artifact link, has `children`)
- **Target artifact key** — the `key` of the panel/report/process to link (skip if type is `MENU`)
- **Name** — the label shown in the menu
- **Key** — unique key for this menu entry (suggest `menu-<artifactKey>` as default)
- **Parent** — root level, or inside which existing `MENU` item? Show the options from Step 2.
- **Order** — integer position within its parent (suggest next available based on existing siblings)
- **Icon** — only relevant for root-level `MENU` items. Vitruvio uses FontAwesome numeric codes (e.g. `61441` = fa-adjust). For children, always `null`.
## Step 4 — Build the JSON entry
### Type: PAINEL
```json
{
"key": "<key>",
"name": "<name>",
"icon": null,
"order": <order>,
"type": "PAINEL",
"panelKey": "<panelKey>",
"children": []
}
```
### Type: RELATORIO
```json
{
"key": "<key>",
"name": "<name>",
"icon": null,
"order": <order>,
"type": "RELATORIO",
"reportKey": "<reportKey>",
"children": []
}
```
### Type: PROCESSO
```json
{
"key": "<key>",
"name": "<name>",
"icon": null,
"order": <order>,
"type": "PROCESSO",
"processKey": "<processKey>",
"children": []
}
```
### Type: MENU (submenu / root folder)
```json
{
"key": "<key>",
"name": "<name>",
"icon": <iconCode or null>,
"order": <order>,
"type": "MENU",
"children": []
}
```
## Step 5 — Insert into vitruvio.json
- Read `vitruvio.json`.
- If `menu` array does not exist, create it as an empty array first.
- If the user chose **root level**: append the entry to the top-level `menu` array.
- If the user chose a **parent item**: find the parent entry by key inside `menu` (search recursively if needed) and append to its `children` array.
- Check that the chosen `key` is not already used anywhere in the menu tree before inserting.
- Write the updated file back preserving formatting (2-space indent).
## Step 6 — Report
Tell the user:
- Added `<type>` entry `<key>` ("Name") at `<location>` with order `<order>`
- Remind them: menu order is relative within siblings — reorder adjacent items if needed
- Remind them: `icon` values are FontAwesome numeric codes; use `null` for leaf items
@@ -0,0 +1,100 @@
# Create Vitruvio Library
> All messages shown to the user must be written in Portuguese.
You are creating a new static file library inside a Vitruvio repository. Libraries are static file bundles (JS, CSS, images) served by the platform as HTTP resources. They are different from scripts — scripts run server-side on Rhino; libraries are served to clients as-is. Follow these steps in order.
## 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 — Collect library details
Ask the user (in a single message, only ask what is missing from their original request):
- **Key (sigla)** — kebab-case unique identifier. Used to reference the library and build its endpoint URL. Stable — changing it breaks any code that references it.
- **Name** — human-readable label shown in the Vitruvio UI.
- **Description** — one sentence about what this library provides (optional).
- **Auth mode** — one of:
- `PUBLIC` — accessible without authentication (default; use for most JS/CSS bundles)
- `STATIC_TOKEN` — token required; use when files must not be publicly accessible
- **Mobile enabled?** — `true` if mobile clients need to load these files; `false` otherwise (default: `false`).
- **What files will this library contain?** — brief description so you can create a useful starting placeholder (e.g. "custom JS utilities for panels", "CSS theme overrides", "image assets").
## Step 3 — Create the directory and placeholder file
```bash
mkdir -p libraries/<key>
```
Create a placeholder file appropriate to what the user described:
- For a **JS library**: `libraries/<key>/<key>.js`
- For a **CSS library**: `libraries/<key>/<key>.css`
- For an **image/mixed library**: `libraries/<key>/README.md` explaining what belongs here
### JS placeholder
```javascript
/**
* Library: <name>
* Key: <key>
* Description: <description>
*
* These files are served as static HTTP resources.
* Access URL: vBibliotecaService.buildEndpointUrl('<key>', '<key>.js')
*/
// Add your client-side JavaScript here.
// This runs in the browser — full ES6+ is supported (unlike server-side Rhino scripts).
```
### CSS placeholder
```css
/**
* Library: <name>
* Key: <key>
* Description: <description>
*
* These files are served as static HTTP resources.
* Access URL: vBibliotecaService.buildEndpointUrl('<key>', '<key>.css')
*/
/* Add your styles here */
```
## Step 4 — Register in vitruvio.json
Read `vitruvio.json`, find or create the `"libraries"` array, and add:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"type": "LOCAL",
"authMode": "<authMode>",
"authToken": null,
"mobileEnabled": <mobileEnabled>,
"files": "libraries/<key>/"
}
```
Omit `"description"` if not provided. Preserve the existing file structure and all other entries.
## Step 5 — Report
Tell the user:
- Directory created: `libraries/<key>/`
- Placeholder file(s) created
- Registered in `vitruvio.json` with key `<key>`
- How to get the serving URL at runtime:
```javascript
var url = vBibliotecaService.buildEndpointUrl('<key>', 'filename.js');
```
- Remind them: files in this directory are served as-is — client-side JS here can use modern ES6+, unlike server-side Rhino scripts
+109
View File
@@ -0,0 +1,109 @@
# Create Vitruvio Endpoint
> All messages shown to the user must be written in Portuguese.
You are creating a new REST endpoint inside a Vitruvio repository. Follow these steps in order.
## 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 — Collect endpoint details
Ask the user (in a single message, only ask what is missing from their original request):
- **Key** — kebab-case unique identifier. Forms the URL path. Changing it later breaks external integrations.
- **Name** — human-readable label shown in the Vitruvio UI.
- **Description** — one sentence about what this endpoint does.
- **HTTP verbs** — which methods to implement: GET, POST, PUT, PATCH, DELETE. Only scaffold the ones actually needed.
- **Auth mode** — one of:
- `PUBLIC` — no authentication (default)
- `STATIC_TOKEN` — token in query param `_x_token_auth` or header `X-WS-TOKEN-AUTH`
- `VITRUVIO_WS_USER_AUTH` — Vitruvio bearer token; `loggedUser` is available in the script
- `HTTP_BASIC_AUTH` — HTTP basic auth; `loggedUser` is available; roles validated by Vitruvio admin
## Step 3 — Create the file
Target path: `endpoints/<key>.js`
URL after deploy:
| authMode | URL |
|---|---|
| `PUBLIC` | `/api/integration/public/<key>` |
| `STATIC_TOKEN` | `/api/integration/tokenauth/<key>` |
| `VITRUVIO_WS_USER_AUTH` | `/api/integration/bearerauth/<key>` |
| `HTTP_BASIC_AUTH` | `/api/integration/bauth/<key>` |
Template (include only the requested verbs):
```javascript
/**
* Nome: <name>
* Sigla: <key>
* Descrição: <description>
* Auth: <authMode>
*/
function WebService() {
// this.onGet = function(params) { ... } ← GET / DELETE: params has .headers and .query
// this.onPost = function(params) { ... } ← POST / PUT / PATCH: params also has .requestBody (string, always JSON.parse before use)
this.onPost = function(params) {
try {
var body = JSON.parse(params.requestBody);
if (!body.id) throw 'Missing required field: id';
// implementation here
return JSON.stringify({ success: true });
} catch (e) {
return JSON.stringify({ error: e.toString() });
}
};
}
module.exports = new WebService();
```
Rules (Rhino ES5 — no exceptions):
- No `let`, `const`, arrow functions, template literals, destructuring, spread, `class`, or `import/export`
- Use `var` everywhere
- Always `JSON.parse(params.requestBody)` before accessing the body — never trust it raw
- Always return strings — `JSON.stringify(obj)`, not raw objects
- Return `null` or nothing for `204 No Content`; return a string for `200 OK`
- Remove unused verb stubs entirely — don't leave placeholder bodies
- Never concatenate user input into SQL strings; use named bind params (`:paramName`)
- Don't hardcode datasource names or tokens — read from `vConfigService` or a DB config table
- Put heavy logic in a separate script loaded via `libService.loadScript`, not inline in the endpoint
## Step 4 — Register in vitruvio.json
Read `vitruvio.json`, find or create the `"endpoints"` array, and add:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"language": "javascript",
"authMode": "<authMode>",
"active": true,
"source": "endpoints/<key>.js"
}
```
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
## Step 5 — Report
Tell the user:
- File created: `endpoints/<key>.js`
- Registered in `vitruvio.json` with key `<key>`
- URL once deployed (based on authMode)
- Which verbs were scaffolded
@@ -0,0 +1,279 @@
# Create Vitruvio Desktop Form
> All messages shown to the user must be written in Portuguese.
Desktop forms are Vaadin 8 forms defined in XML and rendered by the Vitruvio engine. This
skill creates the **desktop** form. There are two variants that share almost all of their
component vocabulary but differ in their root element and how variables flow — see
**Differences: panel vs process** below.
## 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 target
Ask (only what is missing):
- **Panel or process form?** (decides root element / file location — see below)
- **Key** — the panel or process key (the folder name).
- For a **process**, which `formKey`(s) are needed — they must match the `activiti:formKey`
values in `processes/<key>/<key>.bpmn`.
- **What should the form show/do?** — fields and behaviour, so you can scaffold something useful.
## Differences: panel vs process
| | Panel | Process |
|----------------|-------|---------|
| File | `panels/<key>/<key>-desktop.xml` | `processes/<key>/<key>-desktop.xml` |
| Root element | `<panel-form>` | `<forms>` (with `processKey` attribute optional) |
| Namespace | `http://www.davinti.com.br/vitruvio/form/panel` | `http://www.davinti.com.br/vitruvio/form` |
| XSD | `vitruvio-panel-form.xsd` | `vitruvio-form.xsd` |
| Forms per file | exactly one `<form>` | **one `<form formKey>` per BPMN `activiti:formKey`** |
| Variables | none built-in; use `engine.getGlobalVariable` | process variables: `engine.getVariable`/`setVariable`; submitted field `id="X"` in `formKey="A"` → variable `A_X` |
| `<library>` | not used | optional: shared `<complex-component id>` reused via `<component-ref refId>` |
Everything below (components, DBTable, engine API, ES5 rules) is **identical** for both.
## Step 3 — Scaffold the file
### Panel variant — `panels/<key>/<key>-desktop.xml`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/panel https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-panel-form.xsd">
<form formKey="<key>" width="100%" height="100%">
<name><name></name>
<description><description></description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// called once when the panel opens
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%" height="100%">
<!-- add widgets here -->
</VerticalLayout>
</components>
</form>
</panel-form>
```
### Process variant — `processes/<key>/<key>-desktop.xml`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<forms xmlns="http://www.davinti.com.br/vitruvio/form"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-form.xsd">
<!-- Optional: shared components used across multiple forms -->
<library>
<!-- <complex-component id="libShared">...</complex-component> -->
</library>
<!-- One <form> per activiti:formKey in the BPMN -->
<form formKey="formAbertura" width="100%">
<name>Abertura</name>
<description>Abertura do processo</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// called when the form opens
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%">
<TextField id="descricao" type="string" caption="Descrição" width="100%" required="true" />
</VerticalLayout>
</components>
</form>
<form formKey="formExecutar" width="100%">
<name>Executar</name>
<description>Etapa de execução</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// read a process variable set during a previous step
// var valor = engine.getVariable('formAbertura_descricao');
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%">
<TextField id="resultado" type="string" caption="Resultado" width="100%" required="true" />
<ComboBox id="aprovado" type="string" caption="Aprovado?" required="true" allowNullSelection="false">
<entry key="1" value="Sim"/>
<entry key="0" value="Não"/>
</ComboBox>
</VerticalLayout>
</components>
</form>
</forms>
```
## Components (78 desktop components)
Before using a component you are unsure about, read its reference:
`~/.local/share/vitruvio-platform/docs/components/desktop/<ComponentName>.md`
(full list in `docs/components/INDEX.md`).
### Layout
| Component | Common attributes |
|---|---|
| `VerticalLayout` | `spacing`, `margin`, `width`, `height`, `expandRatio` |
| `HorizontalLayout` | same as above |
| `Panel` | `id`, `caption`, `width`, `height`, `margin` |
| `TabLayout` | `id`, `width`, `framed` — contains `<Tab caption="...">` children |
### Widgets
| Component | Key attributes |
|---|---|
| `TextField` | `id`, `type` (`string`/`number`), `caption`, `width`, `required` |
| `NumericField` | `id`, `type`, `caption`, `width`, `visible` |
| `DateField` | `id`, `type` (`date`/`datetime`), `caption`, `resolution` (`DAY`/`MINUTE`) |
| `ComboBox` | `id`, `type`, `caption`, `allowNullSelection` — children: `<entry key="..." value="..."/>` |
| `Label` | `id`, `width`, `contentMode` (`HTML`/`TEXT`) — child: `<value>...</value>` |
| `ButtonWidget` | `id`, `caption`, `style` (`GREEN`/`RED`/`DEFAULT`), `defaultIcon` — child: `<onClickScript>` |
| `RichTextArea` | `id`, `type`, `caption`, `width`, `height` |
| `ImageWidget` | `id`, `width`, `height` — child: `<image><base64 extension="png">...</base64></image>` |
### DBTable (data grid)
```xml
<DBTable id="tbDados" type="string" width="100%" rows="8"
exportXLS="true" showRowCount="true" selectable="true">
<datasource>
<!-- Option A: static query -->
<freeQuery connection-key="vitruvio_producao">
<![CDATA[
SELECT col1, col2
FROM my_table
WHERE param = ${myParam}
]]>
</freeQuery>
<!-- Option B: dynamic query built in JS -->
<sqlBuilderDataSource connection-key="vitruvio_producao" language="JavaScript">
<![CDATA[
function buildSQL(params) {
var sql = 'SELECT col1, col2 FROM my_table WHERE 1=1';
var val = engine.getField('myFilter').getValue();
if (val) {
sql += ' AND col1 = ${val}';
params.put('val', val);
}
return sql;
}
]]>
</sqlBuilderDataSource>
</datasource>
<key-field>CHAVE</key-field>
<columns>
<column name="COL1" caption="Column 1" expand-ratio="1"/>
<column name="COL2" caption="Column 2" expand-ratio="2"/>
<generated name="Action" expand-ratio="0.5">
<scriptColumnGenerator language="JavaScript">
<![CDATA[
function Generator() {
var com = libService.loadScript('vaadinComponents');
this.generate = function(itemId, columnId, item, container) {
var btn = com.buttonIcon('action', function() {
var id = item.getItemProperty('CHAVE').getValue();
// do something
}, 'edit');
return com.horizontalLayout([btn]);
}
}
var script = new Generator();
]]>
</scriptColumnGenerator>
</generated>
</columns>
<bind>
<parameter value-type="number" defaultValue="0" parameterName="myParam" field-ref="otherTable"/>
</bind>
</DBTable>
```
**SQL in datasources:** use `${paramName}` for substitution — NOT `:paramName`. Named
params (`:paramName`) are only for `queries/*.sql` files.
## engine API
```javascript
// Fields
engine.getField('id').getValue()
engine.getField('id').getConvertedValue() // typed value (number, date, etc.)
engine.getField('id').setValue(value)
engine.getField('id').setEnabled(bool)
engine.getField('id').setVisible(bool)
engine.getField('id').setRequired(bool)
engine.getField('id').setCaption('new caption')
engine.getField('id').refresh() // DBTable — re-run its query
// Widgets / layouts
engine.getWidgetController('id').getButton()
engine.getLayout('id').getSelectedTab()
// User
engine.getLoggedUser().getLogin()
engine.getLoggedUser().getNome()
// Global variables (survive tab changes within a session)
engine.setGlobalVariable('key', value)
engine.getGlobalVariable('key')
// Process forms only — process variables:
engine.getVariable('varName')
engine.setVariable('varName', value)
engine.getProcessDefinitionId()
engine.formKey() // current form's formKey
engine.getFormName()
// Open another panel / load a library
var vUI = libService.loadScript('vUI');
vUI.showPanel('panelKey', { param1: value1 });
var lib = libService.loadScript('scriptKey');
```
## Script rules (Rhino ES5)
Desktop form scripts run on **Rhino ES5** — no `let`/`const`, arrow functions, template
literals, destructuring, `class`, `import/export`. Use `var`, string `+` concat,
`JSON.parse`/`JSON.stringify`, `importClass(Packages.some.java.Class)` for Java interop.
(See the repo `CLAUDE.md` "JavaScript — ES5 / Rhino Engine" section.)
> Note: this ES5 rule is for **desktop**. Mobile forms use modern JS on the client — see
> **vitruvio-criar-form-mobile**.
## Step 4 — Manifest
If run **standalone**, set `"forms"."desktop"` to the file path on the existing panel or
process entry in `vitruvio.json`. Full entry creation is handled by **vitruvio-criar-painel**
/ **vitruvio-criar-processo**.
## Step 5 — Report
Tell the user:
- File created/updated and which variant (panel/process).
- For processes: each `<form formKey>` must match an `activiti:formKey` in the BPMN, and
submitted field `id="X"` in `formKey="A"` becomes process variable `A_X`.
- `run()` in `<initScript>` is called every time the form opens.
- `${paramName}` for SQL substitution in datasources; `:paramName` only in named query files.
@@ -0,0 +1,255 @@
# 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.
+92
View File
@@ -0,0 +1,92 @@
# Create Vitruvio Panel (orchestrator)
> All messages shown to the user must be written in Portuguese.
A panel is one or two form files plus a manifest entry:
| Part | Skill that owns it |
|------|--------------------|
| `panels/<key>/<key>-desktop.xml` (web form) | **vitruvio-criar-form-desktop** (panel variant) |
| `panels/<key>/<key>-mobile.xml` (app form, optional) | **vitruvio-criar-form-mobile** (panel variant) |
This skill collects the intent once, drives those skills, and registers the panel in
`vitruvio.json`. Do the file work by following the referenced skills — do not re-derive
their templates here.
## 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 — Collect panel details
Ask the user (in a single message, only ask what is missing):
- **Key** — PascalCase or snake_case unique identifier. Used in `engine.showPanel(key)` and
as the folder name.
- **Name** — human-readable label shown in the Vitruvio menu.
- **Category** — display category, hierarchical with `/` (e.g. `"Comercial"`,
`"Auditoria/Gondola"`).
- **Description** — one sentence (optional).
- **Open in new window?** — `true`/`false`. Default `true`.
- **Show in mobile list?** — `true`/`false`. Default `false`.
- **Needs a mobile form?** — if yes, a `<key>-mobile.xml` is created too. Mobile is a different
schema with explicit data wiring (the mobile skill will ask for variables/lookups/libs).
- **What should the panel do?** — fields/behaviour, so the form scaffold is useful.
## Step 3 — Scaffold
```bash
vitruvio new panel <key> --name "<name>"
```
This creates `panels/<key>/<key>-desktop.xml` and the `vitruvio.json` entry. Then replace the
generated form using the focused skills:
1. **Desktop form** — follow **vitruvio-criar-form-desktop** (panel variant) to write
`panels/<key>/<key>-desktop.xml` from the user's description.
2. **Mobile form (if requested)** — follow **vitruvio-criar-form-mobile** (panel variant) to
write `panels/<key>/<key>-mobile.xml`. `vitruvio new` does not create it.
## Step 4 — Update vitruvio.json entry
`vitruvio new` already added the entry. Full shape:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"category": "<category>",
"displayOrder": 0,
"showInPresentation": false,
"openInNewWindow": true,
"showInMobileList": false,
"displayTimeInSeconds": 0,
"allowedGroups": [],
"allowedUsers": [],
"forms": {
"desktop": "panels/<key>/<key>-desktop.xml"
}
}
```
Fields `vitruvio new` typically leaves at defaults that need updating:
- `"category"` — set to the user's category (default `""`).
- `"description"` — add if provided.
- `"openInNewWindow"` — set to `false` for same-window.
- `"showInMobileList"` — set to `true` for mobile visibility.
- If a mobile form was created, add `"mobile": "panels/<key>/<key>-mobile.xml"` inside `"forms"`
(and set `showInMobileList: true`).
## Step 5 — Report
Tell the user:
- Files created: `<key>-desktop.xml` (and `<key>-mobile.xml` if applicable).
- Registered in `vitruvio.json` with key `<key>`.
- `run()` in `<initScript>` is called every time the panel opens.
- `${paramName}` for SQL substitution in datasource blocks; `:paramName` only in named query files.
- For mobile: only explicitly declared data is available on the device — see vitruvio-criar-form-mobile.
+120
View File
@@ -0,0 +1,120 @@
# Create Vitruvio Patch
> All messages shown to the user must be written in Portuguese.
You are creating a new Liquibase database migration patch inside a Vitruvio repository. Follow these steps in order.
## 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 — Inspect existing patches to determine the next changeset ID
```bash
find patches/ -name "*.xml" | xargs grep -h 'id="[0-9]' 2>/dev/null | grep -oP 'id="\K[0-9]+' | sort -n | tail -1
```
The next ID = last found ID + 1. If no patches exist yet, start at 1.
Also check whether oracle and postgresql subdirectories already exist:
```bash
ls patches/
```
## Step 3 — Collect migration details
Ask the user (in a single message, only ask what is missing):
- **What this migration does** — describe the change (e.g. "add column STATUS to table PEDIDO", "create table AUDIT_LOG", "insert config rows").
- **Author** — Vitruvio username (e.g. `joao.felix`). Use `git config user.name` if unsure.
- **Module key** — used in the filename (e.g. `GO`, `checklist`, `faturamento`). Default: the repo's `metadata.key` from `vitruvio.json`.
## Step 4 — Create the patch files
**Both oracle/ and postgresql/ files must always be created and kept in sync.**
Filename convention: `{YYYYMMDDHHmm}_{MODULE_KEY}.xml` (e.g. `202506011430_checklist.xml`). Use the current date and time.
```bash
mkdir -p patches/oracle patches/postgresql
```
### Absolute rules
- **Append-only.** Never edit or delete existing `<changeSet>` entries — modifying a checksum that Liquibase already recorded breaks deployment.
- **Unique numeric IDs.** Each `<changeSet id="...">` must have a unique ID within the repo. Increment from the last found.
- **Always use `<preConditions onFail="MARK_RAN">`** — every changeset must be idempotent and safe to re-run on any DB state.
- **Oracle ≠ PostgreSQL.** Write each file for its target DB — data types, sequences, and quoting differ. Never copy-paste blindly.
- **One logical change per changeset** — don't batch unrelated changes into a single `<changeSet>`.
### File skeleton
```xml
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="<author>" id="<next-id>" objectQuotingStrategy="LEGACY">
<preConditions onError="WARN" onFail="MARK_RAN" onSqlOutput="IGNORE">
<!-- guard appropriate for the operation — see examples below -->
</preConditions>
<sql endDelimiter=";" splitStatements="true" stripComments="false">
-- SQL for this DB dialect
</sql>
</changeSet>
</databaseChangeLog>
```
### preConditions reference
| Operation | Guard to use |
|-----------|-------------|
| CREATE TABLE | `<not><tableExists tableName="MY_TABLE"/></not>` |
| ADD COLUMN | `<not><columnExists tableName="MY_TABLE" columnName="MY_COL"/></not>` |
| CREATE INDEX | `<not><indexExists indexName="IDX_NAME"/></not>` |
| ADD CONSTRAINT / FK | `<not><foreignKeyConstraintExists foreignKeyName="FK_NAME"/></not>` |
| INSERT row (by PK) | `<sqlCheck expectedResult="0">SELECT COUNT(*) FROM MY_TABLE WHERE ID = 1</sqlCheck>` |
| DROP TABLE | `<tableExists tableName="MY_TABLE"/>` |
| DROP COLUMN | `<columnExists tableName="MY_TABLE" columnName="MY_COL"/>` |
### Oracle vs PostgreSQL differences to watch
| | Oracle | PostgreSQL |
|---|---|---|
| Auto-increment | Separate `CREATE SEQUENCE` + trigger or `DEFAULT seq.NEXTVAL` | `SERIAL` or `GENERATED ALWAYS AS IDENTITY` |
| String type | `VARCHAR2(n)` | `VARCHAR(n)` |
| Boolean | `NUMBER(1)` | `BOOLEAN` |
| Date/time | `DATE`, `TIMESTAMP` | `DATE`, `TIMESTAMP` |
| Current timestamp | `SYSDATE` | `CURRENT_TIMESTAMP` |
| Quoting | `LEGACY` strategy (unquoted) | Same |
## Step 5 — Check vitruvio.json patches registration
The patches directory only needs to be registered once. Check if it is already there:
```bash
grep -A2 '"patches"' vitruvio.json
```
If not registered, add to `vitruvio.json`:
```json
"patches": "patches/"
```
## Step 6 — Report
Tell the user:
- Files created: `patches/oracle/<filename>.xml` and `patches/postgresql/<filename>.xml`
- Changeset IDs used
- Summary of what each changeset does
- Reminder: never edit existing changesets once committed — add new ones instead
@@ -0,0 +1,216 @@
# Create Vitruvio Process BPMN
> All messages shown to the user must be written in Portuguese.
You are creating the **BPMN workflow file** of a Vitruvio process. This skill is focused
on the `.bpmn` file only — the desktop form is handled by **vitruvio-criar-form-desktop**
(process variant) and the mobile form by **vitruvio-criar-form-mobile** (process variant).
## 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 — Collect flow details
Ask the user (in a single message, only ask what is missing):
- **Key** — snake_case or camelCase unique identifier. Becomes the BPMN process ID and the
folder name. Must match the `key` used for the process in `vitruvio.json`.
- **Name** — human-readable label.
- **Who can start it?** — group key(s) allowed to open the process (e.g. `gestao`,
`admin`). Used in `activiti:candidateStarterGroups`.
- **Steps** — the human tasks (user tasks), and which group handles each. A simple linear
flow is enough to start.
- **Decisions / branches?** — any exclusive gateways with conditions.
- **Automatic steps?** — any script tasks running between user tasks.
## Step 3 — Write the BPMN file: `processes/<key>/<key>.bpmn`
### Critical rules
- The `<bpmn2:process id="...">` value is the canonical process identity. The importer
reads it from the BPMN, not from vitruvio.json. **It must match the `key`.**
- Every node must appear in a `<bpmn2:laneSet>` / `<bpmn2:lane>` **and** in the
`<bpmndi:BPMNDi>` section — Vitruvio renders the diagram.
- Each `activiti:formKey` on the start event and user tasks must match a
`<form formKey="...">` in the desktop form XML (and mobile form, if present).
- Process variables from submitted forms are auto-named `{formKey}_{fieldId}`
(e.g. `formAbertura_status`). Gateway conditions reference them.
- Gateway conditions use `#{variable == 'value'}` (JUEL expression language).
- Script tasks call `vScriptService.loadScript('scriptKey', 'javascript')`, **not**
`libService`.
### Minimal skeleton (start → user task → end, single lane)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:activiti="http://activiti.org/bpmn"
id="sample-diagram"
targetNamespace="http://bpmn.io/schema/bpmn"
exporter="bpmn-js (https://demo.bpmn.io)"
exporterVersion="8.2.0"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:collaboration id="Collaboration_<key>">
<bpmn2:participant id="processo_<key>" name="<Name>" processRef="<key>" />
</bpmn2:collaboration>
<bpmn2:process id="<key>" name="<Name>" isExecutable="true"
activiti:candidateStarterGroups="<starterGroup>">
<bpmn2:laneSet>
<bpmn2:lane id="lane_execucao" name="Execução">
<bpmn2:flowNodeRef>inicio</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>task_executar</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>fim</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:laneSet>
<!-- Start event: activiti:initiator stores the login of who opened the process -->
<bpmn2:startEvent id="inicio" name="Início"
activiti:formKey="formAbertura"
activiti:initiator="iniciador">
<bpmn2:outgoing>flow_inicio_task</bpmn2:outgoing>
</bpmn2:startEvent>
<!-- User task: candidateGroups controls who sees it in their inbox -->
<bpmn2:userTask id="task_executar" name="Executar"
activiti:formKey="formExecutar"
activiti:candidateGroups="${vStringUtils.validateRoles(gr_executores)}">
<bpmn2:incoming>flow_inicio_task</bpmn2:incoming>
<bpmn2:outgoing>flow_task_fim</bpmn2:outgoing>
</bpmn2:userTask>
<!-- Script task example (omit if not needed):
<bpmn2:scriptTask id="script_processar" name="Processar" scriptFormat="javascript">
<bpmn2:incoming>flow_task_script</bpmn2:incoming>
<bpmn2:outgoing>flow_script_fim</bpmn2:outgoing>
<bpmn2:script>var f = vScriptService.loadScript('meu_script', 'javascript');
f(execution);</bpmn2:script>
</bpmn2:scriptTask>
-->
<!-- Exclusive gateway example (omit if not needed):
<bpmn2:exclusiveGateway id="gw_decisao" name="Aprovado?">
<bpmn2:incoming>flow_task_gw</bpmn2:incoming>
<bpmn2:outgoing>flow_sim</bpmn2:outgoing>
<bpmn2:outgoing>flow_nao</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="flow_sim" name="Sim" sourceRef="gw_decisao" targetRef="fim">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">#{formExecutar_aprovado == '1'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_nao" name="Não" sourceRef="gw_decisao" targetRef="task_executar">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">#{formExecutar_aprovado == '0'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
-->
<bpmn2:endEvent id="fim" name="Fim">
<bpmn2:incoming>flow_task_fim</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="flow_inicio_task" sourceRef="inicio" targetRef="task_executar" />
<bpmn2:sequenceFlow id="flow_task_fim" sourceRef="task_executar" targetRef="fim" />
</bpmn2:process>
<!-- BPMNDi: visual layout — required for the diagram to render -->
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_<key>">
<bpmndi:BPMNShape id="Participant_di" bpmnElement="processo_<key>" isHorizontal="true">
<dc:Bounds x="100" y="80" width="750" height="180" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="lane_execucao_di" bpmnElement="lane_execucao" isHorizontal="true">
<dc:Bounds x="130" y="80" width="720" height="180" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="inicio_di" bpmnElement="inicio">
<dc:Bounds x="192" y="152" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="195" y="195" width="30" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="task_executar_di" bpmnElement="task_executar">
<dc:Bounds x="310" y="130" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="fim_di" bpmnElement="fim">
<dc:Bounds x="492" y="152" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="497" y="195" width="19" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="flow_inicio_task_di" bpmnElement="flow_inicio_task">
<di:waypoint x="228" y="170" />
<di:waypoint x="310" y="170" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="flow_task_fim_di" bpmnElement="flow_task_fim">
<di:waypoint x="410" y="170" />
<di:waypoint x="492" y="170" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
```
### Multi-lane pattern (when tasks belong to different roles)
Add each lane inside `<bpmn2:laneSet>`, list the node IDs inside each lane, and adjust
the BPMNDi bounds:
```xml
<bpmn2:laneSet>
<bpmn2:lane id="lane_gestao" name="Gestão">
<bpmn2:flowNodeRef>inicio</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>fim</bpmn2:flowNodeRef>
</bpmn2:lane>
<bpmn2:lane id="lane_execucao" name="Execução">
<bpmn2:flowNodeRef>task_executar</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:laneSet>
```
### Script task — script side
```javascript
// In <bpmn2:script> inside a scriptTask:
var f = vScriptService.loadScript('meu_script', 'javascript');
f(execution);
// In the script file itself (pattern: process/task script — see vitruvio-criar-script):
(function(execution) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
var status = execution.getVariable('formAbertura_status');
// ...
})(execution)
```
## Step 4 — Manifest
If this skill is run **standalone** (the process already exists in `vitruvio.json`),
ensure its entry has `"bpmn": "processes/<key>/<key>.bpmn"`. Do not create or modify
the rest of the entry here — full registration is handled by **vitruvio-criar-processo**.
## Step 5 — Report
Tell the user:
- File created/updated: `processes/<key>/<key>.bpmn`
- The process identity is the `<bpmn2:process id>` — it must match the key.
- Each `activiti:formKey` must have a matching `<form formKey="...">` in the form XML
(create/update it with **vitruvio-criar-form-desktop** / **vitruvio-criar-form-mobile**).
- Submitted field `id="X"` in `formKey="formAbertura"` becomes variable `formAbertura_X`.
- For complex flows, recommend editing the BPMN in bpmn.io or Camunda Modeler before deploying.
@@ -0,0 +1,87 @@
# Create Vitruvio Process (orchestrator)
> All messages shown to the user must be written in Portuguese.
A process is made of up to three artifacts plus a manifest entry:
| Part | Skill that owns it |
|------|--------------------|
| `processes/<key>/<key>.bpmn` (workflow) | **vitruvio-criar-processo-bpmn** |
| `processes/<key>/<key>-desktop.xml` (web form) | **vitruvio-criar-form-desktop** (process variant) |
| `processes/<key>/<key>-mobile.xml` (app form, optional) | **vitruvio-criar-form-mobile** (process variant) |
This skill collects the intent once, drives those skills, and registers the process in
`vitruvio.json`. Do the file work by following the referenced skills — do not re-derive
their templates here.
## 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 — Collect process details
Ask the user (in a single message, only ask what is missing):
- **Key** — snake_case or camelCase unique identifier. Becomes the BPMN process ID and the
folder name.
- **Name** — human-readable label.
- **Description** — one sentence (optional).
- **Who can start it?** — group key(s) for `activiti:candidateStarterGroups`.
- **Steps** — the human tasks and which group handles each. A simple linear flow is enough.
- **Needs a script task?** — automatic steps running a script between user tasks.
- **Needs a mobile form?** — if yes, a `<key>-mobile.xml` is created too. Remember mobile is a
different schema with explicit data wiring — variables, lookups and libs must be named
up front (the mobile skill will ask).
## Step 3 — Scaffold
```bash
vitruvio new process <key> --name "<name>"
```
This creates `processes/<key>/<key>.bpmn`, `processes/<key>/<key>-desktop.xml`, and the
`vitruvio.json` entry. Then replace the generated files using the focused skills:
1. **BPMN** — follow **vitruvio-criar-processo-bpmn** to write `processes/<key>/<key>.bpmn`
from the user's steps/branches.
2. **Desktop form** — follow **vitruvio-criar-form-desktop** (process variant) to write
`processes/<key>/<key>-desktop.xml`, one `<form formKey>` per `activiti:formKey` in the BPMN.
3. **Mobile form (if requested)** — follow **vitruvio-criar-form-mobile** (process variant) to
write `processes/<key>/<key>-mobile.xml`. `vitruvio new` does not create it.
## Step 4 — Update vitruvio.json entry
`vitruvio new` already added the entry. Full shape:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"bpmn": "processes/<key>/<key>.bpmn",
"forms": {
"desktop": "processes/<key>/<key>-desktop.xml"
},
"schedules": []
}
```
- Add `"description"` if the user provided one — `vitruvio new` does not set it.
- If a mobile form was created, add `"mobile": "processes/<key>/<key>-mobile.xml"` inside `"forms"`.
- Add `schedules` only if the process runs on a timer (cron/simple interval).
## Step 5 — Report
Tell the user:
- Files created: `<key>.bpmn`, `<key>-desktop.xml` (and `<key>-mobile.xml` if applicable).
- Registered in `vitruvio.json` with key `<key>`.
- The process identity is the `<bpmn2:process id>` — it must match the key.
- Each `activiti:formKey` in the BPMN must have a matching `<form formKey="...">` in **every**
form file (desktop and mobile).
- Submitted field `id="X"` in `formKey="formAbertura"` becomes process variable `formAbertura_X`
(desktop auto-injects these; mobile must declare/fetch them — see vitruvio-criar-form-mobile).
- For complex flows, recommend editing the BPMN in bpmn.io or Camunda Modeler before deploying.
+63
View File
@@ -0,0 +1,63 @@
# Create Vitruvio Query
> All messages shown to the user must be written in Portuguese.
You are creating a new named SQL query inside a Vitruvio repository. Follow these steps in order.
## 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 — Collect query details
Ask the user (in a single message, only ask what is missing from their original request):
- **Key** — kebab-case unique identifier. Used to reference this query in reports, DBTable components, and scripts.
- **Name** — human-readable label shown in the Vitruvio UI.
- **SQL** — the query itself, or enough context to write it.
- **Connection** — datasource name (default: `vitruvio_producao`). Ask only if the user mentions a specific datasource.
## Step 3 — Create the file
Target path: `queries/<key>.sql`
Rules:
- One `SELECT` per file. No multiple statements, no DDL, no INSERT/UPDATE/DELETE.
- Named bind parameters use `:paramName` syntax — never concatenate user input into SQL.
- Write ANSI SQL where possible. If DB-specific syntax is unavoidable, note it in a comment.
- Keep Oracle and PostgreSQL compatibility in mind — avoid syntax that only works in one.
```sql
SELECT col1,
col2
FROM my_table
WHERE active = 1
AND id = :id
ORDER BY col1
```
## Step 4 — Register in vitruvio.json
Read `vitruvio.json`, find or create the `"queries"` array, and add:
```json
{
"key": "<key>",
"name": "<name>",
"source": "queries/<key>.sql",
"connection": "<connection>"
}
```
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
## Step 5 — Report
Tell the user:
- File created: `queries/<key>.sql`
- Registered in `vitruvio.json` with key `<key>`
- How it can be used: as a datasource in a report, in a DBTable component, or loaded in a script via `db.executeNamedQuery('<key>', params)`
@@ -0,0 +1,118 @@
# Create Vitruvio Report
> All messages shown to the user must be written in Portuguese.
You are registering a new report inside a Vitruvio repository. Follow these steps in order.
## 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 — Identify the report type
Ask the user which type applies (if not already clear from the request):
| Type | When to use |
|------|-------------|
| **MODELO_ESTATICO** | A Jasper report whose layout is fully designed in a `.jrxml` file (Jaspersoft Studio). The dev provides or will provide the `.jrxml`. |
| **DINAMICO_QUERY_SQL** | A Vitruvio-managed report where data comes from a named query and columns/layout are configured in `vitruvio.json`. The `.jrxml` is still needed but is simpler — Vitruvio drives the structure. |
## Step 3 — Collect details
Ask the user (in a single message, only ask what is missing):
**Both types:**
- **Key** — kebab-case or snake_case unique identifier.
- **Name** — human-readable label shown in the UI.
- **Category** — display category (e.g. `"Compras"`, `"Auditoria/Gondola"`).
- **Owner** — Vitruvio username of the responsible person.
- **Orientation** — `RETRATO` (portrait) or `PAISAGEM` (landscape). Default: `RETRATO`.
- **Allowed groups / users** — who can access this report (can be empty arrays).
- **Has parameters?** — does the user fill in parameters before running it? If yes, a params form is needed.
**DINAMICO_QUERY_SQL only:**
- **Query key** — the named query that feeds the report (must be registered in `vitruvio.json`).
- **Columns** — list of columns: name (DB column), label, alignment (`LEFT`/`CENTER`/`RIGHT`), width (px), aggregation (`null`, `SUM`, `COUNT`, etc.).
## Step 4 — Create the files
### MODELO_ESTATICO
Files live flat in `reports/`:
- `reports/<key>.jrxml` — **do not generate this file**; tell the user to place the Jaspersoft-designed template here. Must target **JasperReports 6.21.2** — do not save with a newer version.
- `reports/<key>-params.xml` — only if the report has parameters (follows the same Vaadin XML form schema as panels).
### DINAMICO_QUERY_SQL
Files live in a subdirectory:
- `reports/<key>/template.jrxml` — **do not generate this file**; tell the user to place the template here.
- `reports/<key>/params.xml` — only if the report has parameters.
## Step 5 — Register in vitruvio.json
Read `vitruvio.json`, find or create the `"reports"` array, and add the entry.
### MODELO_ESTATICO entry
```json
{
"key": "<key>",
"name": "<name>",
"type": "MODELO_ESTATICO",
"category": "<category>",
"owner": "<owner>",
"template": "reports/<key>.jrxml",
"parameterForm": "reports/<key>-params.xml",
"orientation": "RETRATO",
"allowedGroups": [],
"allowedUsers": [],
"columns": [],
"schedules": []
}
```
Omit `"parameterForm"` if no params form.
### DINAMICO_QUERY_SQL entry
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"type": "DINAMICO_QUERY_SQL",
"category": "<category>",
"owner": "<owner>",
"template": "reports/<key>/template.jrxml",
"parameterForm": "reports/<key>/params.xml",
"query": "<query-key>",
"orientation": "RETRATO",
"allowedGroups": [],
"allowedUsers": [],
"columns": [
{
"name": "COLUMN_NAME",
"label": "Column Label",
"align": "LEFT",
"width": 100,
"aggregation": null
}
]
}
```
Omit `"parameterForm"` if no params form.
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
## Step 6 — Report
Tell the user:
- Entry registered in `vitruvio.json` with key `<key>` and type `<type>`
- For MODELO_ESTATICO: remind them to place the `.jrxml` at `reports/<key>.jrxml`, designed in Jaspersoft Studio 6.21.2
- For DINAMICO_QUERY_SQL: remind them to place the template at `reports/<key>/template.jrxml`
- If a params form is needed: what file to create and that it follows the same Vaadin XML schema as panels
+97
View File
@@ -0,0 +1,97 @@
# Create Vitruvio Script
> All messages shown to the user must be written in Portuguese.
You are creating a new script inside a Vitruvio repository. Follow these steps in order.
## 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 — Collect script details
Ask the user (in a single message, only ask what is missing from their original request):
- **Key (sigla)** — unique identifier used in `libService.loadScript('key')`. Snake_case. Must be unique across all scripts in the repo. If the user already provided a name, suggest a key derived from it.
- **Name** — human-readable label shown in the Vitruvio UI.
- **Pattern** — which of the two patterns applies:
- **Library** — reusable module, loaded by other scripts/endpoints/panels via `libService.loadScript`. Wrap in `({...})`.
- **Process/task script** — runs directly from a process task or scheduler. Top-level execution, no export.
- **Description** — one sentence about what this script does (optional, but ask if not provided).
- **Domain** — `USUARIO` (user-level, default) or `SISTEMA` (system-level).
## Step 3 — Create the file
Target path: `scripts/<key>.js`
### Library template
```javascript
/**
* Nome: <name>
* Sigla: <key>
* Descrição: <description>
*/
({
// example function — replace with actual implementation
run: function(params) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
// implementation here
return {};
}
})
```
### Process / task script template
```javascript
/**
* Nome: <name>
* Sigla: <key>
* Descrição: <description>
*/
(function(execution) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
// implementation here
})(execution)
```
Rules (Rhino ES5 — no exceptions):
- No `let`, `const`, arrow functions, template literals, destructuring, spread, `class`, or `import/export`
- Use `var` everywhere
- No `require`, `process`, `window`, or Node/browser globals
- String concatenation with `+`, not template literals
- `JSON.parse` / `JSON.stringify` for serialization
## Step 4 — Register in vitruvio.json
Read `vitruvio.json`, find or create the `"scripts"` array, and add:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"language": "javascript",
"domain": "<USUARIO|SISTEMA>",
"source": "scripts/<key>.js"
}
```
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
## Step 5 — Report
Tell the user:
- File created: `scripts/<key>.js`
- Registered in `vitruvio.json` with key `<key>`
- How to load it from another script or endpoint: `var lib = libService.loadScript('<key>');`
@@ -0,0 +1,121 @@
---
name: vitruvio-adicionar-menu
description: >
Use ONLY when the user explicitly asks to add a menu item or menu entry in a Vitruvio repository.
Triggers: "add menu item", "add to menu", "adicionar ao menu", "criar item de menu",
"add panel to menu", "adicionar painel no menu", or any explicit request to register
something in the vitruvio.json "menu" array.
Do NOT trigger for general panel/process/script creation — menu entries are separate.
---
# Add Vitruvio Menu Item
> All messages shown to the user must be written in Portuguese.
You are adding an entry to the `menu` array in `vitruvio.json`. Menu entries are **never created automatically** — only when the user explicitly requests it. Follow these steps in order.
## 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 — Show the current menu structure
Read `vitruvio.json` and print the menu tree as a readable outline. Example format:
```
menu:
[0] menu-root "Meu Módulo" (MENU)
[0] menu-panel "Meu Painel" (PAINEL → my-panel)
[1] menu-report "Meu Relatório" (RELATORIO → my-report)
```
If the `menu` array is empty or absent, say so.
## Step 3 — Collect item details
Ask the user (in a single message, only ask what is missing):
- **Type** — one of:
- `PAINEL` — links to a panel (`panelKey`)
- `RELATORIO` — links to a report (`reportKey`)
- `PROCESSO` — links to a process (`processKey`)
- `MENU` — a submenu/folder (no artifact link, has `children`)
- **Target artifact key** — the `key` of the panel/report/process to link (skip if type is `MENU`)
- **Name** — the label shown in the menu
- **Key** — unique key for this menu entry (suggest `menu-<artifactKey>` as default)
- **Parent** — root level, or inside which existing `MENU` item? Show the options from Step 2.
- **Order** — integer position within its parent (suggest next available based on existing siblings)
- **Icon** — only relevant for root-level `MENU` items. Vitruvio uses FontAwesome numeric codes (e.g. `61441` = fa-adjust). For children, always `null`.
## Step 4 — Build the JSON entry
### Type: PAINEL
```json
{
"key": "<key>",
"name": "<name>",
"icon": null,
"order": <order>,
"type": "PAINEL",
"panelKey": "<panelKey>",
"children": []
}
```
### Type: RELATORIO
```json
{
"key": "<key>",
"name": "<name>",
"icon": null,
"order": <order>,
"type": "RELATORIO",
"reportKey": "<reportKey>",
"children": []
}
```
### Type: PROCESSO
```json
{
"key": "<key>",
"name": "<name>",
"icon": null,
"order": <order>,
"type": "PROCESSO",
"processKey": "<processKey>",
"children": []
}
```
### Type: MENU (submenu / root folder)
```json
{
"key": "<key>",
"name": "<name>",
"icon": <iconCode or null>,
"order": <order>,
"type": "MENU",
"children": []
}
```
## Step 5 — Insert into vitruvio.json
- Read `vitruvio.json`.
- If `menu` array does not exist, create it as an empty array first.
- If the user chose **root level**: append the entry to the top-level `menu` array.
- If the user chose a **parent item**: find the parent entry by key inside `menu` (search recursively if needed) and append to its `children` array.
- Check that the chosen `key` is not already used anywhere in the menu tree before inserting.
- Write the updated file back preserving formatting (2-space indent).
## Step 6 — Report
Tell the user:
- Added `<type>` entry `<key>` ("Name") at `<location>` with order `<order>`
- Remind them: menu order is relative within siblings — reorder adjacent items if needed
- Remind them: `icon` values are FontAwesome numeric codes; use `null` for leaf items
@@ -0,0 +1,96 @@
---
name: vitruvio-atualizar-manifesto
description: >
Use when the user wants to update, rename, or remove an existing artifact registration
in vitruvio.json. Triggers: "update the category/description/authMode of", "change the name of",
"rename this panel/script/endpoint", "remove this artifact", "unregister", "atualizar manifesto",
"mudar categoria", "remover painel", or any request to modify fields of an already-registered artifact.
Do NOT trigger for creating new artifacts (use vitruvio-criar-* skills) or adding menu entries (use vitruvio-adicionar-menu).
---
# Update Vitruvio Manifest Entry
> All messages shown to the user must be written in Portuguese.
You are modifying an existing entry in `vitruvio.json`. No new files — only manifest changes (and optionally file moves/deletions). Follow these steps in order.
## 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 — Identify operation and target
Ask the user (in a single message, only ask what is missing):
- **Operation** — one of:
- **A — Update fields**: change metadata on an existing entry (category, description, authMode, allowedGroups, displayOrder, etc.)
- **B — Remove entry**: unregister an artifact from `vitruvio.json`
- **C — Rename key**: change the artifact's key (breaking change — see Operation C below)
- **Target artifact** — the key of the artifact to modify, and its section (panel, script, endpoint, query, report, library, process).
Read `vitruvio.json` and show the user the current entry before proceeding.
---
## Operation A — Update fields
Ask what fields to change and their new values. Then edit `vitruvio.json` with the updated values, preserving all other fields exactly.
### Editable fields by artifact type
**Panel:** `name`, `description`, `category`, `displayOrder`, `showInPresentation`, `openInNewWindow`, `showInMobileList`, `displayTimeInSeconds`, `allowedGroups`, `allowedUsers`, `forms.mobile`, `forms.mobileAlternative`, `defaultState`, `thumbnail`
**Script:** `name`, `description`, `domain` (`USUARIO` / `SISTEMA`)
**Endpoint:** `name`, `description`, `authMode` (`PUBLIC`, `STATIC_TOKEN`, `VITRUVIO_WS_USER_AUTH`, `HTTP_BASIC_AUTH`), `active`
**Query:** `name`, `connection`
**Report:** `name`, `description`, `category`, `owner`, `orientation`, `parameterForm`, `allowedGroups`, `allowedUsers`
**Library:** `name`, `authMode`, `mobileEnabled`
**Process:** `name`, `description`, `forms.mobile`, `forms.mobileAlternative`
After editing, report each field that changed (old value → new value).
---
## Operation B — Remove entry
1. Show the full current entry from `vitruvio.json` to the user.
2. Ask for explicit confirmation before proceeding — do not remove without a yes.
3. Remove the entry from its section array in `vitruvio.json`. If the section array becomes empty, remove the array key entirely.
4. Ask the user: **"Do you also want to delete the artifact's files from disk?"** — list which files/directories would be deleted. Do not delete anything without an explicit yes.
5. If yes, delete the files.
6. Report: entry removed from `vitruvio.json`; files deleted if confirmed.
---
## Operation C — Rename key
> **Warning to show the user before proceeding:**
> Renaming a key is a breaking change. Any code that references the old key — `engine.showPanel('<old-key>')`, `libService.loadScript('<old-key>')`, menu entries, process variables, external integrations — will break. Search the repo for the old key before confirming.
1. Show the warning above and ask the user to confirm they understand.
2. Ask for the new key (kebab-case). Check it is not already used in the same section of `vitruvio.json`.
3. Update the `key` field in `vitruvio.json`.
4. Update any `source`, `bpmn`, `template`, `parameterForm`, `files`, or `forms.*` paths inside the same entry that include the old key in their path.
5. Rename the artifact's directory or file on disk:
- `panels/<old-key>/` → `panels/<new-key>/`
- `scripts/<old-key>.js` → `scripts/<new-key>.js`
- `endpoints/<old-key>.js` → `endpoints/<new-key>.js`
- `queries/<old-key>.sql` → `queries/<new-key>.sql`
- `reports/<old-key>/` → `reports/<new-key>/`
- `libraries/<old-key>/` → `libraries/<new-key>/`
- `processes/<old-key>/` → `processes/<new-key>/`
6. Check if a menu entry references the old key (`panelKey`, `reportKey`, `processKey`) and update it too.
7. Report: what was renamed in `vitruvio.json` and on disk. Remind the user to search the codebase for any remaining hardcoded references to the old key:
```bash
grep -r "<old-key>" --include="*.xml" --include="*.js" --include="*.json" .
```
@@ -0,0 +1,109 @@
---
name: vitruvio-criar-biblioteca
description: >
Use when the user wants to create a new static file library in a Vitruvio repository.
Triggers: "create library", "new library", "criar biblioteca", "nova biblioteca", "add library",
"static files", "JS library", "CSS library", or any request to scaffold a libraries/<key>/ directory
and register it in vitruvio.json.
---
# Create Vitruvio Library
> All messages shown to the user must be written in Portuguese.
You are creating a new static file library inside a Vitruvio repository. Libraries are static file bundles (JS, CSS, images) served by the platform as HTTP resources. They are different from scripts — scripts run server-side on Rhino; libraries are served to clients as-is. Follow these steps in order.
## 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 — Collect library details
Ask the user (in a single message, only ask what is missing from their original request):
- **Key (sigla)** — kebab-case unique identifier. Used to reference the library and build its endpoint URL. Stable — changing it breaks any code that references it.
- **Name** — human-readable label shown in the Vitruvio UI.
- **Description** — one sentence about what this library provides (optional).
- **Auth mode** — one of:
- `PUBLIC` — accessible without authentication (default; use for most JS/CSS bundles)
- `STATIC_TOKEN` — token required; use when files must not be publicly accessible
- **Mobile enabled?** — `true` if mobile clients need to load these files; `false` otherwise (default: `false`).
- **What files will this library contain?** — brief description so you can create a useful starting placeholder (e.g. "custom JS utilities for panels", "CSS theme overrides", "image assets").
## Step 3 — Scaffold and create files
```bash
vitruvio new library <key> --name "<name>"
```
This creates the `libraries/<key>/` directory and registers it in `vitruvio.json` with `authMode: "PUBLIC"` and `mobileEnabled: false`. Then create a placeholder file appropriate to what the user described:
- For a **JS library**: `libraries/<key>/<key>.js`
- For a **CSS library**: `libraries/<key>/<key>.css`
- For an **image/mixed library**: `libraries/<key>/README.md` explaining what belongs here
### JS placeholder
```javascript
/**
* Library: <name>
* Key: <key>
* Description: <description>
*
* These files are served as static HTTP resources.
* Access URL: vBibliotecaService.buildEndpointUrl('<key>', '<key>.js')
*/
// Add your client-side JavaScript here.
// This runs in the browser — full ES6+ is supported (unlike server-side Rhino scripts).
```
### CSS placeholder
```css
/**
* Library: <name>
* Key: <key>
* Description: <description>
*
* These files are served as static HTTP resources.
* Access URL: vBibliotecaService.buildEndpointUrl('<key>', '<key>.css')
*/
/* Add your styles here */
```
## Step 4 — Update vitruvio.json entry
`vitruvio new` already added the library entry. The full entry shape is:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"type": "LOCAL",
"authMode": "PUBLIC",
"authToken": null,
"mobileEnabled": false,
"files": "libraries/<key>/"
}
```
Update only what differs: set `"authMode"` if not `"PUBLIC"`; set `"mobileEnabled": true` if mobile clients need this library; add `"description"` if provided.
## Step 5 — Report
Tell the user:
- Directory created: `libraries/<key>/`
- Placeholder file(s) created
- Registered in `vitruvio.json` with key `<key>`
- How to get the serving URL at runtime:
```javascript
var url = vBibliotecaService.buildEndpointUrl('<key>', 'filename.js');
```
- Remind them: files in this directory are served as-is — client-side JS here can use modern ES6+, unlike server-side Rhino scripts
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,90 @@
/**
* Nome: Parâmetro de indicadores
* Sigla: dashboard_ia
* Descrição: Biblioteca compartilhada dos dashboards de indicadores da Diretoria: preferências de usuário (tema), CSS do topbar e toolbar, helpers de tema.
*/
(function() {
var PREF_NS = 'preferences';
function loadPreferences(login) {
try {
var raw = vConfigService.getUserConfigAsString(String(login), PREF_NS);
if (raw != null && String(raw) !== '') {
return JSON.parse(String(raw));
}
} catch (e) {}
return { dark_mode: true };
}
function savePreferences(login, prefs) {
try {
vConfigService.saveUserConfig(String(login), PREF_NS, JSON.stringify(prefs));
} catch (e) {}
}
function getTheme(engine) {
return String(engine.getGlobalVariable('theme') || 'dark');
}
function isLight(engine) {
return getTheme(engine) === 'light';
}
function applyTheme(themeName, page, btnTheme) {
page.getJavaScript().execute(
themeName === 'light'
? "document.body.classList.add('theme-light-app');"
: "document.body.classList.remove('theme-light-app');"
);
if (btnTheme != null) {
btnTheme.setCaption(themeName === 'dark' ? '☽' : '☀');
}
}
function addSharedCss(page) {
var style =
".dash-topbar { background:#0f1923 !important; border-bottom:1px solid #243447; } " +
".dash-topbar .v-button { background:transparent !important; border:1px solid transparent !important; color:#556677 !important; border-radius:4px !important; transition:none !important; } " +
".dash-topbar .v-button .v-button-caption { color:#556677 !important; } " +
".dash-topbar .v-button.dash-nav-active { background:#1a2733 !important; border-color:#4a9edd !important; } " +
".dash-topbar .v-button.dash-nav-active .v-button-caption { color:#4a9edd !important; font-weight:bold !important; } " +
".dash-topbar .v-caption { color:#8899aa !important; font-size:10px !important; } " +
".dash-topbar input.v-textfield, .dash-topbar .v-datefield-textfield, .dash-topbar input.v-filterselect-input { background:#1a2733 !important; color:#ccc !important; border-color:#243447 !important; } " +
".dash-topbar .v-datefield-button { background:#1a2733 !important; border-color:#243447 !important; } " +
".dash-topbar .v-select-select { background:#1a2733 !important; color:#ccc !important; border-color:#243447 !important; } " +
"body.theme-light-app .dash-topbar { background:#f8fafc !important; border-bottom:1px solid #e2e8f0; } " +
"body.theme-light-app .dash-topbar .v-button { color:#475569 !important; } " +
"body.theme-light-app .dash-topbar .v-button .v-button-caption { color:#475569 !important; } " +
"body.theme-light-app .dash-topbar .v-button.dash-nav-active { background:#dbeafe !important; border-color:#2563eb !important; color:#2563eb !important; } " +
"body.theme-light-app .dash-topbar .v-button.dash-nav-active .v-button-caption { color:#2563eb !important; } " +
"body.theme-light-app .dash-topbar input.v-textfield, body.theme-light-app .dash-topbar .v-datefield-textfield, body.theme-light-app .dash-topbar input.v-filterselect-input { background:#ffffff !important; color:#334155 !important; border-color:#e2e8f0 !important; } " +
"body.theme-light-app .dash-topbar .v-datefield-button { background:#ffffff !important; border-color:#e2e8f0 !important; } " +
"body.theme-light-app .dash-topbar .v-select-select { background:#ffffff !important; color:#334155 !important; border-color:#e2e8f0 !important; } " +
".theme-light.dash-wrapper { background:#f0f2f5; } " +
".theme-light .dash-kpi { background:#ffffff; box-shadow:0 1px 3px rgba(0,0,0,.08); } " +
".theme-light .dash-kpi-label { color:#64748b; } " +
".theme-light .dash-kpi-value { color:#1e293b; } " +
".theme-light .dash-chart-box { background:#ffffff; box-shadow:0 1px 3px rgba(0,0,0,.08); } " +
".theme-light .dash-chart-title { color:#64748b; } " +
".mov-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:8px; } " +
".mov-filter-input { background:#1a2733; border:1px solid #243447; color:#ccc; padding:5px 10px; border-radius:4px; font-size:11px; flex:1; outline:none; font-family:Arial; } " +
".mov-filter-input:focus { border-color:#4a9edd; } " +
".mov-export-btn { background:#1a2733; border:1px solid #243447; color:#8899aa; padding:5px 12px; border-radius:4px; font-size:11px; cursor:pointer; white-space:nowrap; font-family:Arial; } " +
".mov-export-btn:hover { border-color:#4a9edd; color:#4a9edd; } " +
".theme-light .mov-filter-input { background:#ffffff; border-color:#e2e8f0; color:#334155; } " +
".theme-light .mov-filter-input:focus { border-color:#2563eb; } " +
".theme-light .mov-export-btn { background:#ffffff; border-color:#e2e8f0; color:#64748b; } " +
".theme-light .mov-export-btn:hover { border-color:#2563eb; color:#2563eb; } ";
page.getStyles().add(style);
}
return {
loadPreferences: loadPreferences,
savePreferences: savePreferences,
getTheme: getTheme,
isLight: isLight,
applyTheme: applyTheme,
addSharedCss: addSharedCss
};
})()
@@ -0,0 +1,784 @@
---
name: vitruvio-criar-dashboard-mobile
description: >
Use when creating the MOBILE version of an existing Vitruvio indicator dashboard panel — a
DesktopPanel shell (`<key>-mobile.xml`) that delegates to `<key>-desktop.xml`, plus the mobile
detection/adaptation added to the desktop form: collapsible filter sidebar, stacked cards,
dark/light theme toggle, and developer mobile preview. Triggers: "criar mobile do dashboard",
"adicionar versão mobile ao indicador", "mobile preview do painel indicador",
"CriarMobileIndicador". Called by vitruvio-criar-indicador-dashboard for the mobile part; can
also be invoked directly with the target `<key>-desktop.xml` path. For the desktop part use
vitruvio-criar-dashboard-desktop.
---
# Criar Mobile para Painel Indicador
> Todas as mensagens ao usuário devem ser em Português.
Você está criando a versão mobile de um painel Vitruvio existente. O padrão usado neste repositório é o **DesktopPanel delegado**: o `<key>-mobile.xml` é um shell mínimo que delega toda a renderização ao `<key>-desktop.xml`, que detecta o contexto mobile e adapta o layout sozinho.
Receba o caminho do `<key>-desktop.xml` alvo como argumento (ex: `panels/meu-painel/meu-painel-desktop.xml`).
---
## LEITURA OBRIGATÓRIA AO INICIAR — CONTEXT.md
**Esta é a primeira coisa a fazer, antes de abrir qualquer outro arquivo.**
Derive o diretório do painel a partir do argumento e leia:
```
panels/<panel-key>/CONTEXT.md
```
**Se o CONTEXT.md existir:**
- Leia-o completo primeiro. Ele contém os IDs reais dos campos, layouts, funções JS e o estado atual do painel — tudo que você precisa para criar o mobile corretamente sem reler o XML inteiro.
- Use a seção **"IDs de layout e campo"** para preencher `forceLayoutsRender` e `forceFieldsRender` no `<key>-mobile.xml`.
- Use a seção **"Funções JavaScript principais"** para entender o que já existe e evitar duplicar.
- Informe ao usuário: _"Li o contexto do painel. Vou criar o mobile com base nele."_
- Ao final, **atualize o CONTEXT.md**: marque a versão mobile como "concluída", adicione o caminho do `<key>-mobile.xml` e registre a modificação no histórico.
**Se o CONTEXT.md não existir:**
- Informe ao usuário que não há arquivo de contexto para este painel.
- Leia o `<key>-desktop.xml` completo para extrair os IDs necessários.
- Ao final, crie o CONTEXT.md completo (veja o formato na Fase 8 da skill **vitruvio-criar-dashboard-desktop**).
---
## Princípios obrigatórios — leia antes de qualquer coisa
### Nunca invente — pergunte quando tiver dúvida
- Se não conhecer um componente, atributo ou comportamento da plataforma: **pergunte ao usuário** antes de inventar. Uma pergunta custa menos do que um bug em produção.
- Baseie-se sempre no que já existe: leia o painel alvo e outros painéis deste repositório para entender o padrão adotado. O que já funciona aqui é a referência.
- Não suponha assinaturas de serviços, nomes de atributos ou comportamentos de componentes que não estejam evidentes no código existente.
### Se o painel tem filtros acima do conteúdo — mova-os para uma sidebar colapsável
Se o painel alvo tem filtros (combos, campos de texto, datas) posicionados **acima** do conteúdo principal (em linha no topo), **não deixe assim no mobile**. Filtros acima ocupam espaço valioso e poluem a tela.
O padrão deste repositório é a **sidebar de filtros colapsável** — os dois painéis de referência
canônicos estão empacotados junto com a skill desktop, leia-os antes de aplicar o padrão:
- `.claude/skills/vitruvio-criar-dashboard-desktop/dashboard-contrato-desktop.xml`
- `.claude/skills/vitruvio-criar-dashboard-desktop/dashboard-tecnicos-desktop.xml`
O painel usa a global `dashLib` (carregada pelo `run()` desktop via `libService.loadScript('dashboard_ia')`)
para tema/preferências — veja `.claude/skills/vitruvio-criar-dashboard-desktop/dashboard_ia.js` para
a API disponível. **É material de consulta apenas: nunca crie, copie ou registre um
`scripts/dashboard_ia.js` no repositório de destino.**
O padrão é:
**Layout estrutural (XML)**:
```
rootLayout (VerticalLayout, 100% x 100%)
├── topBar (HorizontalLayout) — botão "◄ Filtros" + navegação + spacer + tema
└── mainArea (HorizontalLayout, expandRatio=1)
├── filterBar (VerticalLayout, width=220px) — os filtros em coluna
└── contentPanel (VerticalLayout, expandRatio=1) — o conteúdo principal
```
**Botão toggle no topBar**:
```xml
<ButtonWidget id="btnToggleFiltros" caption="◄ Filtros">
<onClickScript language="JavaScript">
<![CDATA[
function run() {
var fb = engine.getLayout('filterBar');
if (!fb) return;
var filterLayout = fb.getRootComposition();
var estaVisivel = filterLayout.isVisible();
filterLayout.setVisible(!estaVisivel);
var btn = engine.getWidgetController('btnToggleFiltros').getButton();
btn.setCaption(estaVisivel ? '► Filtros' : '◄ Filtros');
}
]]>
</onClickScript>
</ButtonWidget>
```
**No mobile**: o `filterBar` começa invisível (escondido no bloco `_mobileRender` do `run()`). O mobile tem seu próprio botão de filtros dentro da `filterBar` (`btnPreviewFiltros`) que só aparece no mobile — ele aciona o mesmo toggle, mantendo consistência sem adicionar nova lógica.
**Botão de tema dentro da filterBar (obrigatório no mobile)**: adicione um `ButtonWidget id="btnThemeMob"` no fundo do `filterBar` (após o spacer Label), com `visible="false"`. No bloco `_mobileRender` do `run()`, torne-o visível. Ele deve chamar a mesma função `doToggleTheme` que o `btnTheme` do topBar — defina essa função no ScriptWidget InitScript, registre-a com `engine.setGlobalVariable('doToggleTheme', doToggleTheme)` no `init()`, e tenha ambos os botões chamando-a via `engine.getGlobalVariable('doToggleTheme')`. A função atualiza a caption de AMBOS os botões de tema (`btnTheme` e `btnThemeMob`), o CSS do filterBar e o HTML gerado.
```xml
<!-- dentro do filterBar, após o spacer Label -->
<ButtonWidget id="btnThemeMob" caption="&#x263D;" description="Alternar tema" visible="false" width="100%">
<onClickScript language="JavaScript">
<![CDATA[
function run() {
var fn = engine.getGlobalVariable('doToggleTheme');
if (fn) fn();
}
]]>
</onClickScript>
</ButtonWidget>
```
```javascript
// No bloco _mobileRender do run():
var _btnThemeMob = engine.getWidgetController('btnThemeMob');
if (_btnThemeMob) { _btnThemeMob.getButton().setVisible(true); }
// Oculta btnTheme do topBar — no mobile apenas btnThemeMob é visível
var _btnThemeTb = engine.getWidgetController('btnTheme');
if (_btnThemeTb) { _btnThemeTb.getButton().setVisible(false); }
// doToggleTheme no ScriptWidget InitScript — atualiza os dois botões:
function doToggleTheme() {
var dashLib = engine.getGlobalVariable('dashLib');
var login = engine.getGlobalVariable('userLogin');
var current = String(engine.getGlobalVariable('theme') || 'dark');
var next = current === 'dark' ? 'light' : 'dark';
engine.setGlobalVariable('theme', next);
try {
var ui = Packages.com.vaadin.ui.UI.getCurrent();
if (dashLib && ui) { dashLib.applyTheme(next, ui.getPage(), null); }
if (dashLib && login) { dashLib.savePreferences(login, { dark_mode: next === 'dark' }); }
} catch(e) {}
var bT = engine.getWidgetController('btnTheme');
if (bT) { bT.getButton().setCaption(next === 'dark' ? '☽' : '☀'); }
var bM = engine.getWidgetController('btnThemeMob');
if (bM) { bM.getButton().setCaption(next === 'dark' ? '☽' : '☀'); }
var _fb = engine.getLayout('filterBar');
if (_fb) {
var fbComp = _fb.getRootComposition();
if (next === 'light') { fbComp.removeStyleName('dash-filterbar-dark'); fbComp.addStyleName('dash-filterbar-light'); }
else { fbComp.removeStyleName('dash-filterbar-light'); fbComp.addStyleName('dash-filterbar-dark'); }
}
try {
Packages.com.vaadin.ui.UI.getCurrent().getPage().getJavaScript().execute(
"var el=document.getElementById('meu-painel-main');" +
"if(el){if('" + next + "'==='light')el.classList.add('theme-light');else el.classList.remove('theme-light');}"
);
} catch(e2) {}
var fn = engine.getGlobalVariable('renderView');
if (fn) fn();
}
// No init():
engine.setGlobalVariable('doToggleTheme', doToggleTheme);
```
**Os campos de filtro dentro da filterBar são os do painel novo** — não copie os campos do `dashboard-contratos`. Leia o painel alvo, identifique seus filtros originais (cmbAno, cmbMes, txtCliente, etc.) e coloque-os dentro do `filterBar` como `VerticalLayout` com `spacing="true" margin="true"`.
**`forceFieldsRender` no `<key>-mobile.xml`**: inclua todos os campos de filtro da sidebar. Sem isso, `engine.getField('cmbAno')` falha no WebView mobile.
**Campo de pesquisa/busca do desktop**: se o painel desktop tem um campo de texto para pesquisa (ex: filtrar por cliente, número, palavra-chave), ele **obrigatoriamente deve aparecer no mobile também**, dentro da filterBar. Nunca omita a pesquisa no mobile — o usuário mobile precisa dela tanto quanto o desktop. Se a pesquisa estiver inline acima da tabela no desktop, mova-a para dentro da `filterBar` no mobile (ou mantenha nos dois lugares).
---
### Siga o padrão do repositório
Este repositório já tem painéis funcionando. Antes de criar algo novo:
- Leia pelo menos um `<key>-desktop.xml` existente deste repo para entender como estão estruturados os componentes, como são chamados os serviços, como é o estilo visual
- Reutilize classes CSS já definidas (ex: `dash-kpi`, `dash-kpi-sm`, `mov-card`, `mobile-topbar`) em vez de criar novas
- Reutilize padrões de `run()` / `init()` / `renderHtml()` que já existem no painel alvo
### Botões e campos padronizados
- **Botões de ação** (`ButtonWidget`): use `style="DEFAULT"` para ações neutras, `style="GREEN"` para confirmar/salvar, `style="RED"` para cancelar/excluir. Nunca crie botões com HTML customizado quando um `ButtonWidget` padrão serve.
- **Campos de filtro**: `ComboBox` para listas fechadas, `TextField` para texto livre, `DateField` para datas. Sempre com `id` único e `caption` em português.
- **Ícones**: use os já presentes no painel alvo. Se precisar de novo ícone, use os codepoints já usados no repositório (ex: `&#x1F4F1;` para mobile, `&#x2715;` para fechar) — não invente.
- **Labels de valor monetário**: sempre use a função de formatação já existente no painel (ex: `formatBRL(valor)`) — não crie nova.
### JavaScript — ES5 Rhino somente
Nunca use ES6+. Veja o que é proibido e o que usar no lugar:
```javascript
// PROIBIDO
const x = 1; // use: var x = 1;
let y = 2; // use: var y = 2;
() => {}; // use: function() {}
`texto ${var}`; // use: 'texto ' + var
const { a } = obj; // use: var a = obj.a;
class Foo {} // use: function Foo() {}
async/await // use: callbacks
import/export // não disponível
for (const x of arr) // use: for (var i = 0; i < arr.length; i++)
```
---
---
## Passo 1 — Confirmar repositório e ler o painel alvo
```bash
ls vitruvio.json 2>/dev/null && echo "OK" || echo "NOT_A_VITRUVIO_REPO"
```
Se `NOT_A_VITRUVIO_REPO`, pare e peça para o usuário entrar no diretório correto.
Leia o `<key>-desktop.xml` alvo **completo** antes de escrever qualquer linha. Leia também pelo menos um outro painel do repositório para entender os padrões já adotados. Só depois prossiga.
Leia o `<key>-desktop.xml` alvo para entender:
- O `formKey` do painel
- Os layouts declarados (`id` de cada `VerticalLayout`, `HorizontalLayout`, `Panel`, etc.)
- Os campos de filtro (`ComboBox`, `TextField`, etc.)
- A estrutura do `initScript` / `run()` / `init()` do ScriptWidget
- O que é renderizado em HTML (ScriptWidget que chama `renderHtml`)
Leia o `vitruvio.json` para identificar a entrada do painel (`key`, `forms`).
---
## Passo 2 — Criar o `<key>-mobile.xml`
Crie `panels/<key>/<key>-mobile.xml`. Ele é um shell que delega ao desktop:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
<form formKey="<key>-mobile">
<name><NOME DO PAINEL></name>
<description><DESCRIÇÃO></description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// _mobileRender é setado automaticamente pelo DesktopPanel antes
// do painel desktop inicializar — o desktop detecta e adapta o layout.
}
]]>
</initScript>
<components>
<VerticalLayout width="100%" height="100%">
<DesktopPanel id="<camelCaseId>" panelKey="<key>"
layoutId="rootLayout" external="true"
forceLayoutsRender="<lista de layout ids separados por vírgula>"
forceFieldsRender="<lista de field ids separados por vírgula>" height="100%" />
</VerticalLayout>
</components>
</form>
</panel-form>
```
**`forceLayoutsRender`**: liste todos os layouts do desktop que precisam existir no DOM móvel (ex: `topBar, filterBar, mainArea, contentPanel, panelRoot`). Leia o XML do desktop para identificá-los.
**`forceFieldsRender`**: liste os campos de filtro que o script usa (ex: `cmbAno, cmbMes, cmbOrdem`). Sem eles, `engine.getField('cmbAno')` falha no mobile.
**`height="100%"` no `VerticalLayout` e no `DesktopPanel`**: os dois níveis do shell usam `100%` — a altura real vem do container mobile que envolve este `<key>-mobile.xml`. Um valor fixo grande (ex: `3000px`/`700px`) já foi usado aqui para forçar os layouts filhos a calcularem porcentagem, mas isso quebrava a tela (espaço em branco/corte); com `100%` nos dois níveis do shell a altura se propaga corretamente do container pai, sem precisar desse artifício.
---
## Passo 3 — Atualizar vitruvio.json
No `vitruvio.json`, na entrada do painel:
- Adicione `"mobile": "panels/<key>/<key>-mobile.xml"` dentro de `"forms"`
- Defina `"showInMobileList": true`
```json
{
"key": "<key>",
"forms": {
"desktop": "panels/<key>/<key>-desktop.xml",
"mobile": "panels/<key>/<key>-mobile.xml"
},
"showInMobileList": true
}
```
---
## Passo 4 — Adaptar o `<key>-desktop.xml`
Esta é a parte principal. O desktop precisa detectar o contexto mobile e adaptar o HTML gerado.
### 4.1 — Bloco de inicialização mobile no `run()`
Dentro do `run()` (initScript), após o bloco de CSS principal, adicione a detecção mobile:
```javascript
if (engine.isGlobalVariableSet('_mobileRender')) {
engine.setGlobalVariable('isMobile', true);
var _rootLayout = engine.getLayout('rootLayout');
if (_rootLayout) {
var _rootLayoutComp = _rootLayout.getRootComposition();
// 700px é só o placeholder inicial — o server não sabe a altura da tela do
// cliente. O script abaixo troca por um valor proporcional a window.screen.height
// assim que a página carrega, ANTES do colapso de renderHtml (ver seção 4.6).
_rootLayoutComp.setHeight("700px");
_rootLayoutComp.addStyleName('mobile-root-frame');
page.getJavaScript().execute(
"(function(){" +
"var el=document.querySelector('.mobile-root-frame');" +
"if(!el){return;}" +
"var sh=window.screen.height;" +
"var byMargin=sh-220;" +
"var byRatio=Math.round(sh*0.80);" +
"var h=Math.max(320,Math.min(byMargin,byRatio));" +
"el.style.height=h+'px';" +
"})();"
);
}
// Aplica estilo de topbar mobile ao topBar se existir
var _topBar = engine.getLayout('topBar');
if (_topBar) { _topBar.getRootComposition().addStyleName('mobile-topbar'); }
// Esconde filterBar no mobile (filtros ficam em outro fluxo ou botão)
var _filterBar = engine.getLayout('filterBar');
if (_filterBar) { _filterBar.getRootComposition().setVisible(false); }
// Exibe btnThemeMob (dentro da filterBar) e oculta btnTheme (topBar)
// Sem isso o usuário vê dois botões de tema simultaneamente no mobile
var _btnThemeMobR = engine.getWidgetController('btnThemeMob');
if (_btnThemeMobR) { _btnThemeMobR.getButton().setVisible(true); }
var _btnThemeTbR = engine.getWidgetController('btnTheme');
if (_btnThemeTbR) { _btnThemeTbR.getButton().setVisible(false); }
// CSS específico mobile adicionado via page.getStyles().add()
page.getStyles().add(
// Tabelas/grids mobile: altura fixa com scroll interno
".mobile-view .mov-scroll { height:520px !important; overflow-y:auto !important;" +
" -webkit-overflow-scrolling:touch !important; overscroll-behavior:contain; touch-action:pan-y; } "
// Adicione aqui outros CSS mobile específicos deste painel
);
}
```
> **Se o painel alvo tem o assistente de IA "Pietro"** (sidebar `chatBar`, veja a seção opcional
> 7.11 da skill **vitruvio-criar-dashboard-desktop**): no mesmo bloco `_mobileRender`, oculte
> `btnToggleChat` e mostre `btnChatFabMobile` — mesmo tratamento dado a `btnTheme`/`btnThemeMob`
> acima. Replique também em `enterMobilePreview`/`exitMobilePreview` (Passo 5.3). Se o painel não
> tiver esses IDs, ignore — é um add-on opcional, não parte obrigatória do padrão.
### 4.2 — Variável `mobileClass` no HTML gerado
Em toda função que gera HTML (ex: `desenharDashboard`, `renderView`, etc.), adicione:
```javascript
var mobileClass = (engine.isGlobalVariableSet('isMobile') || engine.isGlobalVariableSet('_mobileRender')) ? ' mobile-view' : '';
var html = '<div class="dash-wrapper' + mobileClass + '" id="meu-painel-main">';
```
Isso permite que todo o CSS mobile use seletores `.mobile-view .classe` sem afetar o desktop.
### 4.3 — Layouts horizontais → cards verticais no mobile
**Regra de ouro**: no mobile NUNCA coloque duas informações lado a lado. Tudo em coluna única.
**Desktop** (layout horizontal):
```javascript
html += '<div style="display:flex; gap:16px;">';
html += '<div>R$ 100.000</div>';
html += '<div>R$ 50.000</div>';
html += '</div>';
```
**Mobile** (detectar e converter para vertical):
```javascript
if (mobileClass) {
html += '<div style="display:flex; flex-direction:column; gap:8px;">';
html += '<div class="mob-card"><span class="mob-label">Locação</span><span class="mob-value">R$ 100.000</span></div>';
html += '<div class="mob-card"><span class="mob-label">Serviço</span><span class="mob-value">R$ 50.000</span></div>';
html += '</div>';
} else {
// layout desktop original
}
```
### 4.4 — Tabelas (grids) no mobile → cards empilhados
No desktop: `<table>` com colunas. No mobile: cada linha vira um card com os campos empilhados:
```javascript
if (mobileClass) {
html += '<div class="mov-scroll"><div class="mov-cards">';
for (var i = 0; i < linhas.length; i++) {
var l = linhas[i];
html += '<div class="mov-card">';
html += '<div class="mov-card-top">';
html += ' <span class="mov-card-cliente">' + l.nome + '</span>';
html += '</div>';
html += '<div class="mov-card-meta">' + l.codigo + ' &middot; ' + l.tipo + '</div>';
html += '<div class="mov-card-values">';
html += ' <div class="mov-card-val"><span class="mov-card-lbl">Valor</span><span>' + formatBRL(l.valor) + '</span></div>';
html += ' <div class="mov-card-val"><span class="mov-card-lbl">Status</span><span>' + l.status + '</span></div>';
html += '</div>';
html += '</div>';
}
html += '</div></div>';
} else {
// tabela desktop
}
```
### 4.5 — CSS mobile obrigatório
No `addCss()` (ou no CSS principal adicionado em `run()`), inclua os seletores mobile:
> **CRÍTICO:** Todo CSS de scroll mobile **deve estar em `addCss()`** — nunca apenas no bloco `_mobileRender`. Se ficar só em `_mobileRender`, o preview mobile do desenvolvedor não funciona (porque `isMobile=true` mas `_mobileRender` não está setado).
```javascript
// ── Scroll containers no mobile — OBRIGATÓRIO em addCss() ──
".mobile-view .mov-scroll { max-height:520px !important; overflow-y:auto !important; -webkit-overflow-scrolling:touch !important; overscroll-behavior:contain; touch-action:pan-y; } " +
".mobile-view .tec-mob-scroll { max-height:420px !important; overflow-y:auto !important; -webkit-overflow-scrolling:touch !important; overscroll-behavior:contain; touch-action:pan-y; } " +
".mobile-view .hbar-scroll { max-height:280px !important; overflow-y:auto !important; -webkit-overflow-scrolling:touch !important; } " +
// Tamanhos de fonte mobile — NUNCA menor que o desktop, sempre maior
".mobile-view .dash-kpi-sm .dash-kpi-label { font-size:13px !important; } " +
".mobile-view .dash-kpi-sm .dash-kpi-value { font-size:17px !important; } " +
// Cards de movimentação/dados mobile
".mov-card { background:#1a2733; border-radius:6px; padding:12px 16px; margin-bottom:8px; } " +
".mov-card-cliente { font-weight:bold; color:#fff; font-size:13px; } " +
".mov-card-meta { font-size:11px; color:#8899aa; margin-bottom:8px; } " +
".mov-card-values { display:flex; flex-wrap:wrap; gap:6px; } " +
".mov-card-val { flex:1; min-width:80px; background:#0f1923; border-radius:4px; padding:6px 8px; } " +
".mov-card-lbl { display:block; font-size:10px; color:#8899aa; margin-bottom:2px; } " +
// Mobile: fontes maiores nos cards
".mobile-view .mov-card-cliente { font-size:15px !important; } " +
".mobile-view .mov-card-meta { font-size:13px !important; } " +
".mobile-view .mov-card-lbl { font-size:12px !important; } " +
```
**Classes de scroll padrão:**
| Classe | Altura | Uso |
|---|---|---|
| `.mov-scroll` | 520px | Container da lista de movimentação (cards ou tabela) |
| `.tec-mob-scroll` | 420px | Container de lista de cards de técnicos/ranking |
| `.hbar-scroll` | max 280px | Container de gráfico de barras horizontais (svgHBar) |
**Padrão de envolvimento obrigatório no mobile:**
```javascript
// ── Movimentação: barra de pesquisa FORA do scroll, cards DENTRO ──
html += '<div class="mob-search-bar">...</div>'; // fica fixo no topo
html += '<div class="mov-scroll">'; // scroll interno
for (var i = 0; i < linhas.length; i++) {
html += '<div class="mov-card">...</div>';
}
if (linhas.length === 0) html += '<div style="padding:28px;text-align:center;color:#556677;">Sem dados</div>';
html += '</div>'; // fecha mov-scroll
// ── Lista de técnicos/ranking: header fora, cards dentro ──
html += '<div class="dash-chart-box">...header com botões de sort...</div>';
html += '<div class="tec-mob-scroll">';
html += '<div id="tec-mob-list" data-sc="total" data-sd="-1">';
for (var i = 0; i < tecArr.length; i++) {
html += '<div class="tec-mob-card" ...>...</div>';
}
html += '</div>'; // fecha tec-mob-list
html += '</div>'; // fecha tec-mob-scroll
// ── Gráficos svgHBar: sempre dentro de hbar-scroll ──
html += '<div class="dash-chart-box">';
html += '<div class="dash-chart-title" style="font-size:13px;">Título</div>';
html += '<div class="hbar-scroll">' + svgHBar(dados, '#4a9edd') + '</div>';
html += '</div>';
```
**Tamanhos mínimos de fonte mobile:**
| Tipo de dado | Desktop | Mobile mínimo |
|---|---|---|
| Label/rótulo | 10–12px | 12–13px |
| Valor principal | 13–16px | 16–18px |
| Título de seção | 14–16px | 18–20px |
| Texto de detalhe | 11–12px | 13–14px |
### 4.6 — renderHtml: tratar ScriptWidget para mobile e preview
**Padrão real (confirmado nos dois painéis de referência):** `renderHtml` distingue **preview de
dev** (`isMobile` setado, `_mobileRender` não) do resto — só o preview usa altura natural via
`base.setHeight(null)`; mobile real (`_mobileRender`) e desktop normal sempre usam `setSizeFull()`.
Não existe nenhuma técnica de "colapsar um elemento de `700px`" — isso é uma versão obsoleta. Depois
do render, o único ajuste feito por JS é liberar `overflow:visible` nos containers pais intermediários
até encontrar o `.mobile-root-frame` (cuja altura já foi fixada uma única vez no bloco `_mobileRender`
do `run()`, via `window.screen.height` — ver seção 4.1), para que o scroll externo não "arraste" a
`topBar` junto:
```javascript
function renderHtml(html) {
var comp = components.html(html);
// isPreviewMode: preview de dev (isMobile=true SEM _mobileRender). Mobile WebView real
// (_mobileRender=true) e desktop normal usam setSizeFull() — só o preview precisa de altura
// natural para o Panel (contentPanel) enxergar o overflow real e rolar.
var isPreviewMode = engine.isGlobalVariableSet('isMobile') && !engine.isGlobalVariableSet('_mobileRender');
if (isPreviewMode) {
try { base.setHeight(null); } catch(e) {}
comp.setWidth("100%");
} else {
comp.setSizeFull();
}
base.removeAllComponents();
base.addComponent(comp);
if (!isPreviewMode) { base.setExpandRatio(comp, 1.0); }
Packages.com.vaadin.ui.UI.getCurrent().getPage().getJavaScript().execute(
"setTimeout(function(){" +
"if(typeof meuPainelApplyTheme==='function')meuPainelApplyTheme();" +
// NÃO redimensionar .mobile-root-frame para caber o conteúdo aqui — sua altura é fixa
// (definida uma única vez no _mobileRender via window.screen.height). Só libera
// overflow:visible nos pais intermediários para o scroll externo funcionar sem
// "arrastar" a topBar junto.
"var el=document.getElementById('meu-painel-main');" +
"if(el){" +
"var p=el,n=0;" +
"while(p&&n<30){p=p.parentElement;n++;" +
"if(!p){break;}" +
"if(p.classList&&p.classList.contains('mobile-root-frame')){break;}" +
"var cs=window.getComputedStyle(p);" +
"if(cs&&(cs.overflowY==='auto'||cs.overflowY==='scroll'||cs.overflowY==='hidden')){p.style.overflowY='visible';}" +
"if(cs&&(cs.overflow==='auto'||cs.overflow==='scroll'||cs.overflow==='hidden')){p.style.overflow='visible';}" +
"}" +
"}" +
"},400);"
);
}
```
---
## Passo 5 — Sistema de Preview Mobile (para grupo vi_developer)
Adicione ao `<key>-desktop.xml` um botão de preview que simula o mobile diretamente no desktop. Isso permite testar sem abrir o WebView mobile.
### 5.1 — Botão no topBar (XML)
```xml
<ButtonWidget id="btnDevPreview" caption="&#x1F4F1;"
description="Preview Mobile (apenas desenvolvedores)" visible="false">
<onClickScript>
function run() { var fn = engine.getGlobalVariable('enterMobilePreview'); if (fn) fn(); }
</onClickScript>
</ButtonWidget>
<ButtonWidget id="btnExitPreview" caption="&#x2715; Sair Preview"
description="Voltar ao modo desktop" visible="false">
<onClickScript>
function run() { var fn = engine.getGlobalVariable('exitMobilePreview'); if (fn) fn(); }
</onClickScript>
</ButtonWidget>
```
### 5.2 — CSS do preview
Adicione ao CSS principal:
```javascript
".preview-mobile-frame { margin:0 auto !important; border:2px solid #4a9edd !important;" +
" border-radius:12px !important; box-shadow:0 0 40px rgba(74,158,221,0.25) !important; } " +
".preview-exit-btn { position:fixed !important; top:10px !important; right:12px !important;" +
" z-index:99999 !important; background:#e74c3c !important; border:none !important;" +
" border-radius:6px !important; padding:7px 16px !important; font-weight:bold !important;" +
" box-shadow:0 2px 12px rgba(0,0,0,0.4) !important; cursor:pointer !important; } " +
".preview-exit-btn .v-button-caption { color:#fff !important; font-size:12px !important; } "
```
### 5.3 — Funções enterMobilePreview / exitMobilePreview
**Padrão real (confirmado nos dois painéis de referência):** o preview usa altura **natural**, não
fixa — `base.setHeight(null)` faz o `ScriptWidget` crescer ao tamanho do conteúdo, e o `Panel`
(`contentPanel`) que o envolve enxerga o overflow real e rola sozinho. Não há valor fixo (nem
`700px` nem `3000px`) nem colapso via JS aqui — essa técnica é só para o `_mobileRender` real
(placeholder `700px` recalculado por `window.screen.height`, seção 4.1), que existe porque ali sim
há uma tela de celular real para medir. No preview, que roda no navegador desktop do dev, altura
natural é suficiente e mais simples.
Adicione no ScriptWidget, antes do `init()`:
```javascript
function enterMobilePreview() {
engine.setGlobalVariable('isMobile', true);
// base com height null → cresce ao tamanho do conteúdo → Panel enxerga overflow e rola
try { base.setHeight(null); } catch(e) {}
var _rl = engine.getLayout('rootLayout');
if (_rl) {
var _rlComp = _rl.getRootComposition();
_rlComp.setWidth("375px");
_rlComp.addStyleName('preview-mobile-frame');
}
var _tBar = engine.getLayout('topBar');
if (_tBar) _tBar.getRootComposition().addStyleName('mobile-topbar');
// Salva e esconde filterBar
var _fBar = engine.getLayout('filterBar');
if (_fBar) {
var _fBarComp = _fBar.getRootComposition();
engine.setGlobalVariable('_prevFilterBarVisible', _fBarComp.isVisible());
_fBarComp.setVisible(false);
}
var _btnDev = engine.getWidgetController('btnDevPreview');
if (_btnDev) _btnDev.getButton().setVisible(false);
var _btnExit = engine.getWidgetController('btnExitPreview');
if (_btnExit) {
_btnExit.getButton().setVisible(true);
_btnExit.getButton().addStyleName('preview-exit-btn');
}
// Exibe btnThemeMob e oculta btnTheme — igual ao _mobileRender real
var _btnThMob = engine.getWidgetController('btnThemeMob');
if (_btnThMob) _btnThMob.getButton().setVisible(true);
var _btnThTb = engine.getWidgetController('btnTheme');
if (_btnThTb) _btnThTb.getButton().setVisible(false);
renderView(); // re-renderiza com mobile-view ativo
}
function exitMobilePreview() {
engine.unsetGlobalVariable('isMobile');
// Restaura base para height 100% (modo desktop normal)
try { base.setHeight("100%"); } catch(e) {}
var _rl = engine.getLayout('rootLayout');
if (_rl) {
var _rlComp = _rl.getRootComposition();
_rlComp.setWidth("100%");
_rlComp.removeStyleName('preview-mobile-frame');
}
var _tBar = engine.getLayout('topBar');
if (_tBar) _tBar.getRootComposition().removeStyleName('mobile-topbar');
// Restaura filterBar — usa loose equality (== ) para coerção de Java Boolean
var _fBar = engine.getLayout('filterBar');
if (_fBar) {
var _wasVisible = engine.getGlobalVariable('_prevFilterBarVisible');
_fBar.getRootComposition().setVisible(_wasVisible == true);
engine.unsetGlobalVariable('_prevFilterBarVisible');
}
var _btnExit = engine.getWidgetController('btnExitPreview');
if (_btnExit) {
_btnExit.getButton().removeStyleName('preview-exit-btn');
_btnExit.getButton().setVisible(false);
}
// Restaura btnTheme do topBar e oculta btnThemeMob ao sair do preview
var _btnThMobExit = engine.getWidgetController('btnThemeMob');
if (_btnThMobExit) _btnThMobExit.getButton().setVisible(false);
var _btnThTbExit = engine.getWidgetController('btnTheme');
if (_btnThTbExit) _btnThTbExit.getButton().setVisible(true);
// Reexibe botão preview se o usuário for developer
var _isDev = engine.getGlobalVariable('isDeveloper');
var _btnDev = engine.getWidgetController('btnDevPreview');
if (_btnDev) _btnDev.getButton().setVisible(!!_isDev);
renderView();
}
```
### 5.4 — Checar grupo vi_developer e exibir botão no init()
No `init(mapa)` ou no `run()`, após a inicialização principal:
```javascript
// Verifica acesso de desenvolvedor (vi_developer)
try {
var _dbLib = libService.loadScript('db');
var _banco = new _dbLib(_dbLib.VITRUVIO_DATASOURCE);
var _devRow = _banco.queryRow(
"SELECT COUNT(*) AS CNT FROM nauth.usuario u " +
"INNER JOIN nauth.usuario_grupo ug ON u.usuario_id = ug.usuario_fk " +
"INNER JOIN nauth.grupo g ON ug.grupo_fk = g.grupo_id " +
"WHERE u.login = '" + engine.getLoggedUser().getLogin() + "' " +
"AND g.sigla = 'vi_developer'"
);
if (_devRow && String(_devRow.CNT) !== '0') {
engine.setGlobalVariable('isDeveloper', true);
var _btnDev = engine.getWidgetController('btnDevPreview');
if (_btnDev) _btnDev.getButton().setVisible(true);
}
} catch(e) {}
// Registra funções de preview como variáveis globais (acessíveis pelo XML dos botões)
engine.setGlobalVariable('enterMobilePreview', enterMobilePreview);
engine.setGlobalVariable('exitMobilePreview', exitMobilePreview);
```
---
## Passo 6 — Armadilhas conhecidas (não repita estes erros)
### Java Boolean vs JS boolean
`engine.getGlobalVariable()` retorna `Java Boolean`, não JS primitive:
```javascript
// ERRADO — Java Boolean.FALSE !== JS false (tipos diferentes, strict equality falha)
if (_wasVisible !== false) { ... }
// CERTO — loose equality funciona com Java Boolean
if (_wasVisible == true) { ... }
// OU
if (!!_wasVisible) { ... }
```
### setExpandRatio antes de addComponent
```javascript
// ERRADO — comp não é filho de base ainda, Vaadin lança IllegalArgumentException
comp.setSizeFull();
base.setExpandRatio(comp, 1.0); // ← ERRO: comp não está em base
base.addComponent(comp);
// CERTO — setExpandRatio sempre APÓS addComponent
comp.setSizeFull();
base.removeAllComponents();
base.addComponent(comp);
base.setExpandRatio(comp, 1.0); // ← OK: comp já é filho
```
### base.setHeight(null) no preview — por que funciona
`enterMobilePreview` põe `base` (o container do `ScriptWidget`) em altura natural com `base.setHeight(null)` — sem isso, o container mantém a altura da viewport inteira e o `Panel` (`contentPanel`) não enxerga o conteúdo real para rolar. No `_mobileRender` real esse mesmo `base.setHeight(null)` **não** é usado — lá o `renderHtml` sempre chama `comp.setSizeFull()`, porque quem controla a altura do frame é o cálculo de `window.screen.height` no `rootLayout` (seção 4.1), não o `base`.
### inline style sobrescreve class CSS
Se o HTML gerado tem `style="font-size:12px"` no elemento, CSS de classe não sobrescreve (exceto com `!important`). Mude o valor diretamente na geração do HTML para contexto mobile:
```javascript
var fontSize = mobileClass ? '14px' : '12px';
html += '<div style="font-size:' + fontSize + ';color:#8899aa;">Texto</div>';
```
### Redimensionar o frame mobile a cada render — não faça isso
Uma versão antiga recalculava a altura do `.mobile-root-frame` para `el.getBoundingClientRect().bottom` a cada `renderHtml`. Isso fazia o frame crescer para caber TODO o conteúdo (não só o viewport), transformando `topBar` + conteúdo num único bloco comprido que a página externa rolava inteiro — "arrastando" a `topBar` junto. A altura do `.mobile-root-frame` é definida **uma única vez** no bloco `_mobileRender` do `run()` (via `window.screen.height`, seção 4.1) e deve permanecer fixa; `renderHtml` só libera `overflow:visible` nos pais intermediários (seção 4.6), nunca redimensiona o frame em si.
---
## Passo 7 — Checklist de entrega
Antes de declarar pronto, confirme:
- [ ] Leu o `<key>-desktop.xml` alvo e ao menos um outro painel do repositório antes de escrever qualquer coisa
- [ ] Tudo que foi feito tem referência no código existente do repositório — nada inventado
- [ ] Não usou nenhuma feature ES6+ (sem `const`, `let`, arrow functions, template literals, destructuring, `class`, `async/await`)
- [ ] Botões usam `style` padrão (`DEFAULT`, `GREEN`, `RED`) — sem HTML customizado para botões
- [ ] Reutilizou funções de formatação já existentes no painel (ex: `formatBRL`) — sem duplicar
- [ ] Reutilizou classes CSS já existentes — sem criar novas desnecessariamente
- [ ] `<key>-mobile.xml` criado com `DesktopPanel` apontando para o `panelKey` correto
- [ ] `forceLayoutsRender` lista todos os layouts usados pelo desktop
- [ ] `forceFieldsRender` lista todos os campos de filtro acessados via `engine.getField()`
- [ ] `vitruvio.json`: `"mobile"` adicionado em `"forms"` e `"showInMobileList": true`
- [ ] `run()` do desktop detecta `_mobileRender` e adapta rootLayout + filterBar + CSS
- [ ] No `_mobileRender` real: `rootLayout` recebe `setHeight("700px")` + `addStyleName('mobile-root-frame')`, seguido de `page.getJavaScript().execute(...)` calculando a altura final a partir de `window.screen.height` (clamp entre 320px e `min(screen.height-220, screen.height*0.80)`) — o `700px` é só o placeholder inicial, não a altura final do mobile real
- [ ] HTML gerado usa `mobileClass` e layouts verticais no mobile
- [ ] Tabelas/grids do desktop viram cards empilhados no mobile
- [ ] Fontes mobile respeitam os tamanhos mínimos (labels ≥ 12px, valores ≥ 16px)
- [ ] `renderHtml` distingue preview (`isMobile` sem `_mobileRender`) de mobile real/desktop: só o preview usa `base.setHeight(null)` + `comp.setWidth("100%")`; os outros dois usam `comp.setSizeFull()` + `base.setExpandRatio(comp, 1.0)`
- [ ] `enterMobilePreview` usa `base.setHeight(null)` (altura natural) — não copia o `700px` fixo do `_mobileRender` real, que só faz sentido para tela de celular
- [ ] `exitMobilePreview` restaura `base.setHeight("100%")`
- [ ] JS pós-render (setTimeout 400ms mínimo) só libera `overflow:visible` nos pais até `.mobile-root-frame` — nunca redimensiona o frame em si
- [ ] `setExpandRatio` é chamado APÓS `addComponent`
- [ ] Funções `enterMobilePreview` / `exitMobilePreview` registradas como global vars
- [ ] Botão `btnDevPreview` visível somente para `vi_developer`
- [ ] `btnThemeMob` adicionado como **primeiro filho** do `filterBar`, `visible="false"` no XML, visível no bloco `_mobileRender`
- [ ] No bloco `_mobileRender` e em `enterMobilePreview`: `btnTheme` (topBar) oculto via `setVisible(false)` — evita dois botões de tema simultâneos
- [ ] No `exitMobilePreview`: `btnThemeMob` oculto e `btnTheme` restaurado via `setVisible(true)`
- [ ] `doToggleTheme` definido no ScriptWidget, registrado no `init()`, chamado por `btnTheme` e `btnThemeMob`
- [ ] `doToggleTheme` atualiza caption de ambos os botões de tema
- [ ] Comparação de Java Boolean usa `==` (loose equality), nunca `===`
- [ ] CSS de scroll mobile (`.mov-scroll`, `.tec-mob-scroll`, `.hbar-scroll`) está em `addCss()` — **não apenas** no bloco `_mobileRender`
- [ ] Lista de cards de movimentação mobile envolvida em `<div class="mov-scroll">`
- [ ] Lista de técnicos/ranking mobile envolvida em `<div class="tec-mob-scroll">`
- [ ] Gráficos svgHBar no mobile envolvidos em `<div class="hbar-scroll">`
---
## Referência rápida de classes CSS mobile
| Classe | Uso |
|---|---|
| `.mobile-view` | Aplicada ao `id` raiz do HTML gerado quando em modo mobile |
| `.mobile-topbar` | Estilo do topBar no mobile (botões menores, compactos) |
| `.preview-mobile-frame` | Borda azul que indica o frame de preview 375px |
| `.preview-exit-btn` | Botão "✕ Sair Preview" flutuante fixed no canto superior direito |
| `.mov-scroll` | Container da lista de movimentação (cresce com o conteúdo, scroll acima de 520px) |
| `.tec-mob-scroll` | Container de lista de técnicos/ranking (cresce com o conteúdo, scroll acima de 420px) |
| `.hbar-scroll` | Container de gráfico svgHBar (max 280px + scroll) |
| `.mov-cards` | Container de cards empilhados (substitui a table no mobile) |
| `.mov-card` | Card individual de cada linha da tabela |
| `.mov-card-cliente` | Nome/título principal do card (font-size ≥ 15px mobile) |
| `.mov-card-meta` | Informações secundárias (contrato, tipo, período) |
| `.mov-card-values` | Flex wrap com os valores numéricos do card |
| `.mov-card-val` | Célula individual de valor dentro do card |
| `.mov-card-lbl` | Rótulo do valor (font-size ≥ 12px mobile) |
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
<form formKey="dashboard-contratos-mobile">
<name>Dashboard de Contratos</name>
<description>Dashboard de Contratos</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// _mobileRender é setado automaticamente pelo DesktopPanel antes
// do painel desktop inicializar — o desktop detecta e adapta o layout.
}
]]>
</initScript>
<components>
<VerticalLayout width="100%" height="100%">
<DesktopPanel id="dashContratos" panelKey="dashboard-contratos"
layoutId="rootLayout" external="true" height="100%"
forceLayoutsRender="topBar, filterBar, mainArea, contentPanel, panelRoot"
forceFieldsRender="cmbAno, cmbMes, cmbOrdem" />
</VerticalLayout>
</components>
</form>
</panel-form>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
<form formKey="dashboard-tecnicos-mobile">
<name>Dashboard Técnicos</name>
<description>Dashboard de produtividade dos técnicos com ranking, SLA e detalhamento por OS.</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// _mobileRender é setado automaticamente pelo DesktopPanel antes
// do painel desktop inicializar — o desktop detecta e adapta o layout.
}
]]>
</initScript>
<components>
<VerticalLayout width="100%" height="100%">
<DesktopPanel id="dashTecnicosMobile" panelKey="dashboard_tecnicos"
layoutId="rootLayout" external="true" height="100%"
forceLayoutsRender="topBar, mainArea, filterBar, contentPanel, panelRoot"
forceFieldsRender="dtIni, dtFim, nmfSla, cmbExecucao" />
</VerticalLayout>
</components>
</form>
</panel-form>
@@ -0,0 +1,90 @@
/**
* Nome: Parâmetro de indicadores
* Sigla: dashboard_ia
* Descrição: Biblioteca compartilhada dos dashboards de indicadores da Diretoria: preferências de usuário (tema), CSS do topbar e toolbar, helpers de tema.
*/
(function() {
var PREF_NS = 'preferences';
function loadPreferences(login) {
try {
var raw = vConfigService.getUserConfigAsString(String(login), PREF_NS);
if (raw != null && String(raw) !== '') {
return JSON.parse(String(raw));
}
} catch (e) {}
return { dark_mode: true };
}
function savePreferences(login, prefs) {
try {
vConfigService.saveUserConfig(String(login), PREF_NS, JSON.stringify(prefs));
} catch (e) {}
}
function getTheme(engine) {
return String(engine.getGlobalVariable('theme') || 'dark');
}
function isLight(engine) {
return getTheme(engine) === 'light';
}
function applyTheme(themeName, page, btnTheme) {
page.getJavaScript().execute(
themeName === 'light'
? "document.body.classList.add('theme-light-app');"
: "document.body.classList.remove('theme-light-app');"
);
if (btnTheme != null) {
btnTheme.setCaption(themeName === 'dark' ? '☽' : '☀');
}
}
function addSharedCss(page) {
var style =
".dash-topbar { background:#0f1923 !important; border-bottom:1px solid #243447; } " +
".dash-topbar .v-button { background:transparent !important; border:1px solid transparent !important; color:#556677 !important; border-radius:4px !important; transition:none !important; } " +
".dash-topbar .v-button .v-button-caption { color:#556677 !important; } " +
".dash-topbar .v-button.dash-nav-active { background:#1a2733 !important; border-color:#4a9edd !important; } " +
".dash-topbar .v-button.dash-nav-active .v-button-caption { color:#4a9edd !important; font-weight:bold !important; } " +
".dash-topbar .v-caption { color:#8899aa !important; font-size:10px !important; } " +
".dash-topbar input.v-textfield, .dash-topbar .v-datefield-textfield, .dash-topbar input.v-filterselect-input { background:#1a2733 !important; color:#ccc !important; border-color:#243447 !important; } " +
".dash-topbar .v-datefield-button { background:#1a2733 !important; border-color:#243447 !important; } " +
".dash-topbar .v-select-select { background:#1a2733 !important; color:#ccc !important; border-color:#243447 !important; } " +
"body.theme-light-app .dash-topbar { background:#f8fafc !important; border-bottom:1px solid #e2e8f0; } " +
"body.theme-light-app .dash-topbar .v-button { color:#475569 !important; } " +
"body.theme-light-app .dash-topbar .v-button .v-button-caption { color:#475569 !important; } " +
"body.theme-light-app .dash-topbar .v-button.dash-nav-active { background:#dbeafe !important; border-color:#2563eb !important; color:#2563eb !important; } " +
"body.theme-light-app .dash-topbar .v-button.dash-nav-active .v-button-caption { color:#2563eb !important; } " +
"body.theme-light-app .dash-topbar input.v-textfield, body.theme-light-app .dash-topbar .v-datefield-textfield, body.theme-light-app .dash-topbar input.v-filterselect-input { background:#ffffff !important; color:#334155 !important; border-color:#e2e8f0 !important; } " +
"body.theme-light-app .dash-topbar .v-datefield-button { background:#ffffff !important; border-color:#e2e8f0 !important; } " +
"body.theme-light-app .dash-topbar .v-select-select { background:#ffffff !important; color:#334155 !important; border-color:#e2e8f0 !important; } " +
".theme-light.dash-wrapper { background:#f0f2f5; } " +
".theme-light .dash-kpi { background:#ffffff; box-shadow:0 1px 3px rgba(0,0,0,.08); } " +
".theme-light .dash-kpi-label { color:#64748b; } " +
".theme-light .dash-kpi-value { color:#1e293b; } " +
".theme-light .dash-chart-box { background:#ffffff; box-shadow:0 1px 3px rgba(0,0,0,.08); } " +
".theme-light .dash-chart-title { color:#64748b; } " +
".mov-toolbar { display:flex; align-items:center; gap:8px; margin-bottom:8px; } " +
".mov-filter-input { background:#1a2733; border:1px solid #243447; color:#ccc; padding:5px 10px; border-radius:4px; font-size:11px; flex:1; outline:none; font-family:Arial; } " +
".mov-filter-input:focus { border-color:#4a9edd; } " +
".mov-export-btn { background:#1a2733; border:1px solid #243447; color:#8899aa; padding:5px 12px; border-radius:4px; font-size:11px; cursor:pointer; white-space:nowrap; font-family:Arial; } " +
".mov-export-btn:hover { border-color:#4a9edd; color:#4a9edd; } " +
".theme-light .mov-filter-input { background:#ffffff; border-color:#e2e8f0; color:#334155; } " +
".theme-light .mov-filter-input:focus { border-color:#2563eb; } " +
".theme-light .mov-export-btn { background:#ffffff; border-color:#e2e8f0; color:#64748b; } " +
".theme-light .mov-export-btn:hover { border-color:#2563eb; color:#2563eb; } ";
page.getStyles().add(style);
}
return {
loadPreferences: loadPreferences,
savePreferences: savePreferences,
getTheme: getTheme,
isLight: isLight,
applyTheme: applyTheme,
addSharedCss: addSharedCss
};
})()
@@ -0,0 +1,123 @@
---
name: vitruvio-criar-endpoint
description: >
Use when the user wants to create a new REST endpoint in a Vitruvio repository.
Triggers: "create endpoint", "new endpoint", "criar endpoint", "novo endpoint", "add endpoint",
"REST", "WebService", "integração", or any request to scaffold an endpoints/*.js file.
---
# Create Vitruvio Endpoint
> All messages shown to the user must be written in Portuguese.
You are creating a new REST endpoint inside a Vitruvio repository. Follow these steps in order.
## 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 — Collect endpoint details
Ask the user (in a single message, only ask what is missing from their original request):
- **Key** — kebab-case unique identifier. Forms the URL path. Changing it later breaks external integrations.
- **Name** — human-readable label shown in the Vitruvio UI.
- **Description** — one sentence about what this endpoint does.
- **HTTP verbs** — which methods to implement: GET, POST, PUT, PATCH, DELETE. Only scaffold the ones actually needed.
- **Auth mode** — one of:
- `PUBLIC` — no authentication (default)
- `STATIC_TOKEN` — token in query param `_x_token_auth` or header `X-WS-TOKEN-AUTH`
- `VITRUVIO_WS_USER_AUTH` — Vitruvio bearer token; `loggedUser` is available in the script
- `HTTP_BASIC_AUTH` — HTTP basic auth; `loggedUser` is available; roles validated by Vitruvio admin
## Step 3 — Scaffold and create file
```bash
vitruvio new endpoint <key> --name "<name>"
```
This creates `endpoints/<key>.js` and registers it in `vitruvio.json` with `authMode: "PUBLIC"` and `active: true`. Replace the generated file with the template below. If authMode is not PUBLIC, update that field in `vitruvio.json`.
Target path: `endpoints/<key>.js`
URL after deploy:
| authMode | URL |
|---|---|
| `PUBLIC` | `/api/integration/public/<key>` |
| `STATIC_TOKEN` | `/api/integration/tokenauth/<key>` |
| `VITRUVIO_WS_USER_AUTH` | `/api/integration/bearerauth/<key>` |
| `HTTP_BASIC_AUTH` | `/api/integration/bauth/<key>` |
Template (include only the requested verbs):
```javascript
/**
* Nome: <name>
* Sigla: <key>
* Descrição: <description>
* Auth: <authMode>
*/
function WebService() {
// this.onGet = function(params) { ... } ← GET / DELETE: params has .headers and .query
// this.onPost = function(params) { ... } ← POST / PUT / PATCH: params also has .requestBody (string, always JSON.parse before use)
this.onPost = function(params) {
try {
var body = JSON.parse(params.requestBody);
if (!body.id) throw 'Missing required field: id';
// implementation here
return JSON.stringify({ success: true });
} catch (e) {
return JSON.stringify({ error: e.toString() });
}
};
}
module.exports = new WebService();
```
Rules (Rhino ES5 — no exceptions):
- No `let`, `const`, arrow functions, template literals, destructuring, spread, `class`, or `import/export`
- Use `var` everywhere
- Always `JSON.parse(params.requestBody)` before accessing the body — never trust it raw
- Always return strings — `JSON.stringify(obj)`, not raw objects
- Return `null` or nothing for `204 No Content`; return a string for `200 OK`
- Remove unused verb stubs entirely — don't leave placeholder bodies
- Never concatenate user input into SQL strings; use named bind params (`:paramName`)
- Don't hardcode datasource names or tokens — read from `vConfigService` or a DB config table
- Put heavy logic in a separate script loaded via `libService.loadScript`, not inline in the endpoint
## Step 4 — Update vitruvio.json entry
`vitruvio new` already added the endpoint entry. The full entry shape is:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"language": "javascript",
"authMode": "PUBLIC",
"active": true,
"source": "endpoints/<key>.js"
}
```
Update only what differs: set `"authMode"` to the correct value if not `"PUBLIC"`; add `"description"` if provided.
## Step 5 — Report
Tell the user:
- File created: `endpoints/<key>.js`
- Registered in `vitruvio.json` with key `<key>`
- URL once deployed (based on authMode)
- Which verbs were scaffolded
@@ -0,0 +1,290 @@
---
name: vitruvio-criar-form-desktop
description: >
Use when the user wants to create or edit the DESKTOP (web) XML form of a Vitruvio panel
or process — the Vaadin form rendered on desktop. Triggers: "create desktop form",
"criar formulário desktop", "form xml", "desktop form xml", "tela desktop",
"add a field to the desktop form", "process desktop form". This is the single home for
desktop form knowledge; vitruvio-criar-painel and vitruvio-criar-processo call it for
their form part. For the mobile form use vitruvio-criar-form-mobile.
---
# Create Vitruvio Desktop Form
> All messages shown to the user must be written in Portuguese.
Desktop forms are Vaadin 8 forms defined in XML and rendered by the Vitruvio engine. This
skill creates the **desktop** form. There are two variants that share almost all of their
component vocabulary but differ in their root element and how variables flow — see
**Differences: panel vs process** below.
## 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 target
Ask (only what is missing):
- **Panel or process form?** (decides root element / file location — see below)
- **Key** — the panel or process key (the folder name).
- For a **process**, which `formKey`(s) are needed — they must match the `activiti:formKey`
values in `processes/<key>/<key>.bpmn`.
- **What should the form show/do?** — fields and behaviour, so you can scaffold something useful.
## Differences: panel vs process
| | Panel | Process |
|----------------|-------|---------|
| File | `panels/<key>/<key>-desktop.xml` | `processes/<key>/<key>-desktop.xml` |
| Root element | `<panel-form>` | `<forms>` (with `processKey` attribute optional) |
| Namespace | `http://www.davinti.com.br/vitruvio/form/panel` | `http://www.davinti.com.br/vitruvio/form` |
| XSD | `vitruvio-panel-form.xsd` | `vitruvio-form.xsd` |
| Forms per file | exactly one `<form>` | **one `<form formKey>` per BPMN `activiti:formKey`** |
| Variables | none built-in; use `engine.getGlobalVariable` | process variables: `engine.getVariable`/`setVariable`; submitted field `id="X"` in `formKey="A"` → variable `A_X` |
| `<library>` | not used | optional: shared `<complex-component id>` reused via `<component-ref refId>` |
Everything below (components, DBTable, engine API, ES5 rules) is **identical** for both.
## Step 3 — Scaffold the file
### Panel variant — `panels/<key>/<key>-desktop.xml`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/panel https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-panel-form.xsd">
<form formKey="<key>" width="100%" height="100%">
<name><name></name>
<description><description></description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// called once when the panel opens
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%" height="100%">
<!-- add widgets here -->
</VerticalLayout>
</components>
</form>
</panel-form>
```
### Process variant — `processes/<key>/<key>-desktop.xml`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<forms xmlns="http://www.davinti.com.br/vitruvio/form"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-form.xsd">
<!-- Optional: shared components used across multiple forms -->
<library>
<!-- <complex-component id="libShared">...</complex-component> -->
</library>
<!-- One <form> per activiti:formKey in the BPMN -->
<form formKey="formAbertura" width="100%">
<name>Abertura</name>
<description>Abertura do processo</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// called when the form opens
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%">
<TextField id="descricao" type="string" caption="Descrição" width="100%" required="true" />
</VerticalLayout>
</components>
</form>
<form formKey="formExecutar" width="100%">
<name>Executar</name>
<description>Etapa de execução</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// read a process variable set during a previous step
// var valor = engine.getVariable('formAbertura_descricao');
}
]]>
</initScript>
<components>
<VerticalLayout spacing="true" margin="true" width="100%">
<TextField id="resultado" type="string" caption="Resultado" width="100%" required="true" />
<ComboBox id="aprovado" type="string" caption="Aprovado?" required="true" allowNullSelection="false">
<entry key="1" value="Sim"/>
<entry key="0" value="Não"/>
</ComboBox>
</VerticalLayout>
</components>
</form>
</forms>
```
## Components (78 desktop components)
Before using a component you are unsure about, read its reference:
`~/.local/share/vitruvio-platform/docs/components/desktop/<ComponentName>.md`
(full list in `docs/components/INDEX.md`).
### Layout
| Component | Common attributes |
|---|---|
| `VerticalLayout` | `spacing`, `margin`, `width`, `height`, `expandRatio` |
| `HorizontalLayout` | same as above |
| `Panel` | `id`, `caption`, `width`, `height`, `margin` |
| `TabLayout` | `id`, `width`, `framed` — contains `<Tab caption="...">` children |
### Widgets
| Component | Key attributes |
|---|---|
| `TextField` | `id`, `type` (`string`/`number`), `caption`, `width`, `required` |
| `NumericField` | `id`, `type`, `caption`, `width`, `visible` |
| `DateField` | `id`, `type` (`date`/`datetime`), `caption`, `resolution` (`DAY`/`MINUTE`) |
| `ComboBox` | `id`, `type`, `caption`, `allowNullSelection` — children: `<entry key="..." value="..."/>` |
| `Label` | `id`, `width`, `contentMode` (`HTML`/`TEXT`) — child: `<value>...</value>` |
| `ButtonWidget` | `id`, `caption`, `style` (`GREEN`/`RED`/`DEFAULT`), `defaultIcon` — child: `<onClickScript>` |
| `RichTextArea` | `id`, `type`, `caption`, `width`, `height` |
| `ImageWidget` | `id`, `width`, `height` — child: `<image><base64 extension="png">...</base64></image>` |
### DBTable (data grid)
```xml
<DBTable id="tbDados" type="string" width="100%" rows="8"
exportXLS="true" showRowCount="true" selectable="true">
<datasource>
<!-- Option A: static query -->
<freeQuery connection-key="vitruvio_producao">
<![CDATA[
SELECT col1, col2
FROM my_table
WHERE param = ${myParam}
]]>
</freeQuery>
<!-- Option B: dynamic query built in JS -->
<sqlBuilderDataSource connection-key="vitruvio_producao" language="JavaScript">
<![CDATA[
function buildSQL(params) {
var sql = 'SELECT col1, col2 FROM my_table WHERE 1=1';
var val = engine.getField('myFilter').getValue();
if (val) {
sql += ' AND col1 = ${val}';
params.put('val', val);
}
return sql;
}
]]>
</sqlBuilderDataSource>
</datasource>
<key-field>CHAVE</key-field>
<columns>
<column name="COL1" caption="Column 1" expand-ratio="1"/>
<column name="COL2" caption="Column 2" expand-ratio="2"/>
<generated name="Action" expand-ratio="0.5">
<scriptColumnGenerator language="JavaScript">
<![CDATA[
function Generator() {
var com = libService.loadScript('vaadinComponents');
this.generate = function(itemId, columnId, item, container) {
var btn = com.buttonIcon('action', function() {
var id = item.getItemProperty('CHAVE').getValue();
// do something
}, 'edit');
return com.horizontalLayout([btn]);
}
}
var script = new Generator();
]]>
</scriptColumnGenerator>
</generated>
</columns>
<bind>
<parameter value-type="number" defaultValue="0" parameterName="myParam" field-ref="otherTable"/>
</bind>
</DBTable>
```
**SQL in datasources:** use `${paramName}` for substitution — NOT `:paramName`. Named
params (`:paramName`) are only for `queries/*.sql` files.
## engine API
```javascript
// Fields
engine.getField('id').getValue()
engine.getField('id').getConvertedValue() // typed value (number, date, etc.)
engine.getField('id').setValue(value)
engine.getField('id').setEnabled(bool)
engine.getField('id').setVisible(bool)
engine.getField('id').setRequired(bool)
engine.getField('id').setCaption('new caption')
engine.getField('id').refresh() // DBTable — re-run its query
// Widgets / layouts
engine.getWidgetController('id').getButton()
engine.getLayout('id').getSelectedTab()
// User
engine.getLoggedUser().getLogin()
engine.getLoggedUser().getNome()
// Global variables (survive tab changes within a session)
engine.setGlobalVariable('key', value)
engine.getGlobalVariable('key')
// Process forms only — process variables:
engine.getVariable('varName')
engine.setVariable('varName', value)
engine.getProcessDefinitionId()
engine.formKey() // current form's formKey
engine.getFormName()
// Open another panel / load a library
var vUI = libService.loadScript('vUI');
vUI.showPanel('panelKey', { param1: value1 });
var lib = libService.loadScript('scriptKey');
```
## Script rules (Rhino ES5)
Desktop form scripts run on **Rhino ES5** — no `let`/`const`, arrow functions, template
literals, destructuring, `class`, `import/export`. Use `var`, string `+` concat,
`JSON.parse`/`JSON.stringify`, `importClass(Packages.some.java.Class)` for Java interop.
(See the repo `CLAUDE.md` "JavaScript — ES5 / Rhino Engine" section.)
> Note: this ES5 rule is for **desktop**. Mobile forms use modern JS on the client — see
> **vitruvio-criar-form-mobile**.
## Step 4 — Manifest
If run **standalone**, set `"forms"."desktop"` to the file path on the existing panel or
process entry in `vitruvio.json`. Full entry creation is handled by **vitruvio-criar-painel**
/ **vitruvio-criar-processo**.
## Step 5 — Report
Tell the user:
- File created/updated and which variant (panel/process).
- For processes: each `<form formKey>` must match an `activiti:formKey` in the BPMN, and
submitted field `id="X"` in `formKey="A"` becomes process variable `A_X`.
- `run()` in `<initScript>` is called every time the form opens.
- `${paramName}` for SQL substitution in datasources; `:paramName` only in named query files.
@@ -0,0 +1,267 @@
---
name: vitruvio-criar-form-mobile
description: >
Use when the user wants to create or edit the MOBILE form of a Vitruvio panel or process
(the React-Native form rendered in the mobile app). Triggers: "create mobile form",
"criar formulário mobile", "mobile panel", "painel mobile", "criar painel mobile",
"process mobile form", "formulário mobile do processo", "add mobile form", "mobile form xml",
"bridge mobile". This is the single home for mobile form knowledge; vitruvio-criar-painel
and vitruvio-criar-processo call it for their mobile part. For the desktop form use
vitruvio-criar-form-desktop.
---
# 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.
@@ -0,0 +1,100 @@
---
name: vitruvio-criar-painel
description: >
Use when the user wants to create a new panel (tela / form) in a Vitruvio repository.
Triggers: "create panel", "new panel", "criar painel", "novo painel", "add panel",
or any request to scaffold a <key>-desktop.xml inside a panels/ folder.
---
# Create Vitruvio Panel (orchestrator)
> All messages shown to the user must be written in Portuguese.
A panel is one or two form files plus a manifest entry:
| Part | Skill that owns it |
|------|--------------------|
| `panels/<key>/<key>-desktop.xml` (web form) | **vitruvio-criar-form-desktop** (panel variant) |
| `panels/<key>/<key>-mobile.xml` (app form, optional) | **vitruvio-criar-form-mobile** (panel variant) |
This skill collects the intent once, drives those skills, and registers the panel in
`vitruvio.json`. Do the file work by following the referenced skills — do not re-derive
their templates here.
## 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 — Collect panel details
Ask the user (in a single message, only ask what is missing):
- **Key** — PascalCase or snake_case unique identifier. Used in `engine.showPanel(key)` and
as the folder name.
- **Name** — human-readable label shown in the Vitruvio menu.
- **Category** — display category, hierarchical with `/` (e.g. `"Comercial"`,
`"Auditoria/Gondola"`).
- **Description** — one sentence (optional).
- **Open in new window?** — `true`/`false`. Default `true`.
- **Show in mobile list?** — `true`/`false`. Default `false`.
- **Needs a mobile form?** — if yes, a `<key>-mobile.xml` is created too. Mobile is a different
schema with explicit data wiring (the mobile skill will ask for variables/lookups/libs).
- **What should the panel do?** — fields/behaviour, so the form scaffold is useful.
## Step 3 — Scaffold
```bash
vitruvio new panel <key> --name "<name>"
```
This creates `panels/<key>/<key>-desktop.xml` and the `vitruvio.json` entry. Then replace the
generated form using the focused skills:
1. **Desktop form** — follow **vitruvio-criar-form-desktop** (panel variant) to write
`panels/<key>/<key>-desktop.xml` from the user's description.
2. **Mobile form (if requested)** — follow **vitruvio-criar-form-mobile** (panel variant) to
write `panels/<key>/<key>-mobile.xml`. `vitruvio new` does not create it.
## Step 4 — Update vitruvio.json entry
`vitruvio new` already added the entry. Full shape:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"category": "<category>",
"displayOrder": 0,
"showInPresentation": false,
"openInNewWindow": true,
"showInMobileList": false,
"displayTimeInSeconds": 0,
"allowedGroups": [],
"allowedUsers": [],
"forms": {
"desktop": "panels/<key>/<key>-desktop.xml"
}
}
```
Fields `vitruvio new` typically leaves at defaults that need updating:
- `"category"` — set to the user's category (default `""`).
- `"description"` — add if provided.
- `"openInNewWindow"` — set to `false` for same-window.
- `"showInMobileList"` — set to `true` for mobile visibility.
- If a mobile form was created, add `"mobile": "panels/<key>/<key>-mobile.xml"` inside `"forms"`
(and set `showInMobileList: true`).
## Step 5 — Report
Tell the user:
- Files created: `<key>-desktop.xml` (and `<key>-mobile.xml` if applicable).
- Registered in `vitruvio.json` with key `<key>`.
- `run()` in `<initScript>` is called every time the panel opens.
- `${paramName}` for SQL substitution in datasource blocks; `:paramName` only in named query files.
- For mobile: only explicitly declared data is available on the device — see vitruvio-criar-form-mobile.
@@ -0,0 +1,132 @@
---
name: vitruvio-criar-patch
description: >
Use when the user wants to create a new Liquibase database migration patch in a Vitruvio repository.
Triggers: "create patch", "new patch", "criar patch", "novo patch", "database migration",
"migração de banco", "changeset", "liquibase", or any request to scaffold oracle/postgresql patch XML files.
---
# Create Vitruvio Patch
> All messages shown to the user must be written in Portuguese.
You are creating a new Liquibase database migration patch inside a Vitruvio repository. Follow these steps in order.
## 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 — Inspect existing patches
`vitruvio new patch` auto-detects the next changeset ID by scanning all existing XML files — you don't need to find it manually. For situational awareness you can check:
```bash
find patches/ -name "*.xml" | xargs grep -h 'id="[0-9]' 2>/dev/null | grep -oP 'id="\K[0-9]+' | sort -n | tail -1
```
Next ID = last found + 1. If no patches exist yet, starts at 1. The CLI also creates `patches/oracle/` and `patches/postgresql/` subdirectories if they don't exist yet.
## Step 3 — Collect migration details
Ask the user (in a single message, only ask what is missing):
- **What this migration does** — describe the change (e.g. "add column STATUS to table PEDIDO", "create table AUDIT_LOG", "insert config rows").
- **Author** — Vitruvio username (e.g. `joao.felix`). Use `git config user.name` if unsure.
- **Module key** — used in the filename (e.g. `GO`, `checklist`, `faturamento`). Default: the repo's `metadata.key` from `vitruvio.json`.
## Step 4 — Create the patch files
**Both oracle/ and postgresql/ files must always be created and kept in sync.**
Filename convention: `{YYYYMMDDHHmm}_{MODULE_KEY}.xml` (e.g. `202506011430_checklist.xml`).
Use `vitruvio new patch` to scaffold — it handles the filename, ID assignment, and directory creation automatically:
```bash
vitruvio new patch <module-key>
```
Find the created files, then replace the SQL placeholder in both with the actual migration for each DB dialect, and set `author` to the correct Vitruvio username:
```bash
ls -t patches/oracle/ | head -1
```
### Absolute rules
- **Append-only.** Never edit or delete existing `<changeSet>` entries — modifying a checksum that Liquibase already recorded breaks deployment.
- **Unique numeric IDs.** Each `<changeSet id="...">` must have a unique ID within the repo. Increment from the last found.
- **Always use `<preConditions onFail="MARK_RAN">`** — every changeset must be idempotent and safe to re-run on any DB state.
- **Oracle ≠ PostgreSQL.** Write each file for its target DB — data types, sequences, and quoting differ. Never copy-paste blindly.
- **One logical change per changeset** — don't batch unrelated changes into a single `<changeSet>`.
### File skeleton
```xml
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="<author>" id="<next-id>" objectQuotingStrategy="LEGACY">
<preConditions onError="WARN" onFail="MARK_RAN" onSqlOutput="IGNORE">
<!-- guard appropriate for the operation — see examples below -->
</preConditions>
<sql endDelimiter=";" splitStatements="true" stripComments="false">
-- SQL for this DB dialect
</sql>
</changeSet>
</databaseChangeLog>
```
### preConditions reference
| Operation | Guard to use |
|-----------|-------------|
| CREATE TABLE | `<not><tableExists tableName="MY_TABLE"/></not>` |
| ADD COLUMN | `<not><columnExists tableName="MY_TABLE" columnName="MY_COL"/></not>` |
| CREATE INDEX | `<not><indexExists indexName="IDX_NAME"/></not>` |
| ADD CONSTRAINT / FK | `<not><foreignKeyConstraintExists foreignKeyName="FK_NAME"/></not>` |
| INSERT row (by PK) | `<sqlCheck expectedResult="0">SELECT COUNT(*) FROM MY_TABLE WHERE ID = 1</sqlCheck>` |
| DROP TABLE | `<tableExists tableName="MY_TABLE"/>` |
| DROP COLUMN | `<columnExists tableName="MY_TABLE" columnName="MY_COL"/>` |
### Oracle vs PostgreSQL differences to watch
| | Oracle | PostgreSQL |
|---|---|---|
| Auto-increment | Separate `CREATE SEQUENCE` + trigger or `DEFAULT seq.NEXTVAL` | `SERIAL` or `GENERATED ALWAYS AS IDENTITY` |
| String type | `VARCHAR2(n)` | `VARCHAR(n)` |
| Boolean | `NUMBER(1)` | `BOOLEAN` |
| Date/time | `DATE`, `TIMESTAMP` | `DATE`, `TIMESTAMP` |
| Current timestamp | `SYSDATE` | `CURRENT_TIMESTAMP` |
| Quoting | `LEGACY` strategy (unquoted) | Same |
## Step 5 — Check vitruvio.json patches registration
The patches directory only needs to be registered once. Check if it is already there:
```bash
grep -A2 '"patches"' vitruvio.json
```
If not registered, add to `vitruvio.json`:
```json
"patches": "patches/"
```
## Step 6 — Report
Tell the user:
- Files created: `patches/oracle/<filename>.xml` and `patches/postgresql/<filename>.xml`
- Changeset IDs used
- Summary of what each changeset does
- Reminder: never edit existing changesets once committed — add new ones instead
@@ -0,0 +1,227 @@
---
name: vitruvio-criar-processo-bpmn
description: >
Use when the user wants to create or edit only the BPMN workflow file of a Vitruvio
process (the Activiti flow: lanes, tasks, gateways, sequence flows), without touching
the forms. Triggers: "create bpmn", "criar bpmn", "novo bpmn", "fluxo do processo",
"workflow do processo", "só o bpmn", "process flow", "edit the bpmn".
For the full process (BPMN + forms + manifest) use vitruvio-criar-processo, which calls
this skill for the BPMN part.
---
# Create Vitruvio Process BPMN
> All messages shown to the user must be written in Portuguese.
You are creating the **BPMN workflow file** of a Vitruvio process. This skill is focused
on the `.bpmn` file only — the desktop form is handled by **vitruvio-criar-form-desktop**
(process variant) and the mobile form by **vitruvio-criar-form-mobile** (process variant).
## 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 — Collect flow details
Ask the user (in a single message, only ask what is missing):
- **Key** — snake_case or camelCase unique identifier. Becomes the BPMN process ID and the
folder name. Must match the `key` used for the process in `vitruvio.json`.
- **Name** — human-readable label.
- **Who can start it?** — group key(s) allowed to open the process (e.g. `gestao`,
`admin`). Used in `activiti:candidateStarterGroups`.
- **Steps** — the human tasks (user tasks), and which group handles each. A simple linear
flow is enough to start.
- **Decisions / branches?** — any exclusive gateways with conditions.
- **Automatic steps?** — any script tasks running between user tasks.
## Step 3 — Write the BPMN file: `processes/<key>/<key>.bpmn`
### Critical rules
- The `<bpmn2:process id="...">` value is the canonical process identity. The importer
reads it from the BPMN, not from vitruvio.json. **It must match the `key`.**
- Every node must appear in a `<bpmn2:laneSet>` / `<bpmn2:lane>` **and** in the
`<bpmndi:BPMNDi>` section — Vitruvio renders the diagram.
- Each `activiti:formKey` on the start event and user tasks must match a
`<form formKey="...">` in the desktop form XML (and mobile form, if present).
- Process variables from submitted forms are auto-named `{formKey}_{fieldId}`
(e.g. `formAbertura_status`). Gateway conditions reference them.
- Gateway conditions use `#{variable == 'value'}` (JUEL expression language).
- Script tasks call `vScriptService.loadScript('scriptKey', 'javascript')`, **not**
`libService`.
### Minimal skeleton (start → user task → end, single lane)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<bpmn2:definitions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:bpmn2="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:dc="http://www.omg.org/spec/DD/20100524/DC"
xmlns:di="http://www.omg.org/spec/DD/20100524/DI"
xmlns:activiti="http://activiti.org/bpmn"
id="sample-diagram"
targetNamespace="http://bpmn.io/schema/bpmn"
exporter="bpmn-js (https://demo.bpmn.io)"
exporterVersion="8.2.0"
xsi:schemaLocation="http://www.omg.org/spec/BPMN/20100524/MODEL BPMN20.xsd">
<bpmn2:collaboration id="Collaboration_<key>">
<bpmn2:participant id="processo_<key>" name="<Name>" processRef="<key>" />
</bpmn2:collaboration>
<bpmn2:process id="<key>" name="<Name>" isExecutable="true"
activiti:candidateStarterGroups="<starterGroup>">
<bpmn2:laneSet>
<bpmn2:lane id="lane_execucao" name="Execução">
<bpmn2:flowNodeRef>inicio</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>task_executar</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>fim</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:laneSet>
<!-- Start event: activiti:initiator stores the login of who opened the process -->
<bpmn2:startEvent id="inicio" name="Início"
activiti:formKey="formAbertura"
activiti:initiator="iniciador">
<bpmn2:outgoing>flow_inicio_task</bpmn2:outgoing>
</bpmn2:startEvent>
<!-- User task: candidateGroups controls who sees it in their inbox -->
<bpmn2:userTask id="task_executar" name="Executar"
activiti:formKey="formExecutar"
activiti:candidateGroups="${vStringUtils.validateRoles(gr_executores)}">
<bpmn2:incoming>flow_inicio_task</bpmn2:incoming>
<bpmn2:outgoing>flow_task_fim</bpmn2:outgoing>
</bpmn2:userTask>
<!-- Script task example (omit if not needed):
<bpmn2:scriptTask id="script_processar" name="Processar" scriptFormat="javascript">
<bpmn2:incoming>flow_task_script</bpmn2:incoming>
<bpmn2:outgoing>flow_script_fim</bpmn2:outgoing>
<bpmn2:script>var f = vScriptService.loadScript('meu_script', 'javascript');
f(execution);</bpmn2:script>
</bpmn2:scriptTask>
-->
<!-- Exclusive gateway example (omit if not needed):
<bpmn2:exclusiveGateway id="gw_decisao" name="Aprovado?">
<bpmn2:incoming>flow_task_gw</bpmn2:incoming>
<bpmn2:outgoing>flow_sim</bpmn2:outgoing>
<bpmn2:outgoing>flow_nao</bpmn2:outgoing>
</bpmn2:exclusiveGateway>
<bpmn2:sequenceFlow id="flow_sim" name="Sim" sourceRef="gw_decisao" targetRef="fim">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">#{formExecutar_aprovado == '1'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
<bpmn2:sequenceFlow id="flow_nao" name="Não" sourceRef="gw_decisao" targetRef="task_executar">
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">#{formExecutar_aprovado == '0'}</bpmn2:conditionExpression>
</bpmn2:sequenceFlow>
-->
<bpmn2:endEvent id="fim" name="Fim">
<bpmn2:incoming>flow_task_fim</bpmn2:incoming>
</bpmn2:endEvent>
<bpmn2:sequenceFlow id="flow_inicio_task" sourceRef="inicio" targetRef="task_executar" />
<bpmn2:sequenceFlow id="flow_task_fim" sourceRef="task_executar" targetRef="fim" />
</bpmn2:process>
<!-- BPMNDi: visual layout — required for the diagram to render -->
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_<key>">
<bpmndi:BPMNShape id="Participant_di" bpmnElement="processo_<key>" isHorizontal="true">
<dc:Bounds x="100" y="80" width="750" height="180" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="lane_execucao_di" bpmnElement="lane_execucao" isHorizontal="true">
<dc:Bounds x="130" y="80" width="720" height="180" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="inicio_di" bpmnElement="inicio">
<dc:Bounds x="192" y="152" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="195" y="195" width="30" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="task_executar_di" bpmnElement="task_executar">
<dc:Bounds x="310" y="130" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="fim_di" bpmnElement="fim">
<dc:Bounds x="492" y="152" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="497" y="195" width="19" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="flow_inicio_task_di" bpmnElement="flow_inicio_task">
<di:waypoint x="228" y="170" />
<di:waypoint x="310" y="170" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="flow_task_fim_di" bpmnElement="flow_task_fim">
<di:waypoint x="410" y="170" />
<di:waypoint x="492" y="170" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn2:definitions>
```
### Multi-lane pattern (when tasks belong to different roles)
Add each lane inside `<bpmn2:laneSet>`, list the node IDs inside each lane, and adjust
the BPMNDi bounds:
```xml
<bpmn2:laneSet>
<bpmn2:lane id="lane_gestao" name="Gestão">
<bpmn2:flowNodeRef>inicio</bpmn2:flowNodeRef>
<bpmn2:flowNodeRef>fim</bpmn2:flowNodeRef>
</bpmn2:lane>
<bpmn2:lane id="lane_execucao" name="Execução">
<bpmn2:flowNodeRef>task_executar</bpmn2:flowNodeRef>
</bpmn2:lane>
</bpmn2:laneSet>
```
### Script task — script side
```javascript
// In <bpmn2:script> inside a scriptTask:
var f = vScriptService.loadScript('meu_script', 'javascript');
f(execution);
// In the script file itself (pattern: process/task script — see vitruvio-criar-script):
(function(execution) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
var status = execution.getVariable('formAbertura_status');
// ...
})(execution)
```
## Step 4 — Manifest
If this skill is run **standalone** (the process already exists in `vitruvio.json`),
ensure its entry has `"bpmn": "processes/<key>/<key>.bpmn"`. Do not create or modify
the rest of the entry here — full registration is handled by **vitruvio-criar-processo**.
## Step 5 — Report
Tell the user:
- File created/updated: `processes/<key>/<key>.bpmn`
- The process identity is the `<bpmn2:process id>` — it must match the key.
- Each `activiti:formKey` must have a matching `<form formKey="...">` in the form XML
(create/update it with **vitruvio-criar-form-desktop** / **vitruvio-criar-form-mobile**).
- Submitted field `id="X"` in `formKey="formAbertura"` becomes variable `formAbertura_X`.
- For complex flows, recommend editing the BPMN in bpmn.io or Camunda Modeler before deploying.
@@ -0,0 +1,95 @@
---
name: vitruvio-criar-processo
description: >
Use when the user wants to create a new Activiti workflow process in a Vitruvio repository.
Triggers: "create process", "new process", "criar processo", "novo processo", "add process",
"workflow", "BPMN", or any request to scaffold a BPMN file or process form XML.
---
# Create Vitruvio Process (orchestrator)
> All messages shown to the user must be written in Portuguese.
A process is made of up to three artifacts plus a manifest entry:
| Part | Skill that owns it |
|------|--------------------|
| `processes/<key>/<key>.bpmn` (workflow) | **vitruvio-criar-processo-bpmn** |
| `processes/<key>/<key>-desktop.xml` (web form) | **vitruvio-criar-form-desktop** (process variant) |
| `processes/<key>/<key>-mobile.xml` (app form, optional) | **vitruvio-criar-form-mobile** (process variant) |
This skill collects the intent once, drives those skills, and registers the process in
`vitruvio.json`. Do the file work by following the referenced skills — do not re-derive
their templates here.
## 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 — Collect process details
Ask the user (in a single message, only ask what is missing):
- **Key** — snake_case or camelCase unique identifier. Becomes the BPMN process ID and the
folder name.
- **Name** — human-readable label.
- **Description** — one sentence (optional).
- **Who can start it?** — group key(s) for `activiti:candidateStarterGroups`.
- **Steps** — the human tasks and which group handles each. A simple linear flow is enough.
- **Needs a script task?** — automatic steps running a script between user tasks.
- **Needs a mobile form?** — if yes, a `<key>-mobile.xml` is created too. Remember mobile is a
different schema with explicit data wiring — variables, lookups and libs must be named
up front (the mobile skill will ask).
## Step 3 — Scaffold
```bash
vitruvio new process <key> --name "<name>"
```
This creates `processes/<key>/<key>.bpmn`, `processes/<key>/<key>-desktop.xml`, and the
`vitruvio.json` entry. Then replace the generated files using the focused skills:
1. **BPMN** — follow **vitruvio-criar-processo-bpmn** to write `processes/<key>/<key>.bpmn`
from the user's steps/branches.
2. **Desktop form** — follow **vitruvio-criar-form-desktop** (process variant) to write
`processes/<key>/<key>-desktop.xml`, one `<form formKey>` per `activiti:formKey` in the BPMN.
3. **Mobile form (if requested)** — follow **vitruvio-criar-form-mobile** (process variant) to
write `processes/<key>/<key>-mobile.xml`. `vitruvio new` does not create it.
## Step 4 — Update vitruvio.json entry
`vitruvio new` already added the entry. Full shape:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"bpmn": "processes/<key>/<key>.bpmn",
"forms": {
"desktop": "processes/<key>/<key>-desktop.xml"
},
"schedules": []
}
```
- Add `"description"` if the user provided one — `vitruvio new` does not set it.
- If a mobile form was created, add `"mobile": "processes/<key>/<key>-mobile.xml"` inside `"forms"`.
- Add `schedules` only if the process runs on a timer (cron/simple interval).
## Step 5 — Report
Tell the user:
- Files created: `<key>.bpmn`, `<key>-desktop.xml` (and `<key>-mobile.xml` if applicable).
- Registered in `vitruvio.json` with key `<key>`.
- The process identity is the `<bpmn2:process id>` — it must match the key.
- Each `activiti:formKey` in the BPMN must have a matching `<form formKey="...">` in **every**
form file (desktop and mobile).
- Submitted field `id="X"` in `formKey="formAbertura"` becomes process variable `formAbertura_X`
(desktop auto-injects these; mobile must declare/fetch them — see vitruvio-criar-form-mobile).
- For complex flows, recommend editing the BPMN in bpmn.io or Camunda Modeler before deploying.
@@ -0,0 +1,77 @@
---
name: vitruvio-criar-query
description: >
Use when the user wants to create a new named SQL query in a Vitruvio repository.
Triggers: "create query", "new query", "criar query", "nova query", "add query", "named query",
or any request to scaffold a queries/*.sql file and register it in vitruvio.json.
---
# Create Vitruvio Query
> All messages shown to the user must be written in Portuguese.
You are creating a new named SQL query inside a Vitruvio repository. Follow these steps in order.
## 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 — Collect query details
Ask the user (in a single message, only ask what is missing from their original request):
- **Key** — kebab-case unique identifier. Used to reference this query in reports, DBTable components, and scripts.
- **Name** — human-readable label shown in the Vitruvio UI.
- **SQL** — the query itself, or enough context to write it.
- **Connection** — datasource name (default: `vitruvio_producao`). Ask only if the user mentions a specific datasource.
## Step 3 — Scaffold and create file
```bash
vitruvio new query <key> --name "<name>"
```
This creates `queries/<key>.sql` and registers it in `vitruvio.json` with `connection: "vitruvio_producao"`. Replace the generated SQL with the actual query. If a different datasource is needed, update `"connection"` in `vitruvio.json`.
Target path: `queries/<key>.sql`
Rules:
- One `SELECT` per file. No multiple statements, no DDL, no INSERT/UPDATE/DELETE.
- Named bind parameters use `:paramName` syntax — never concatenate user input into SQL.
- Write ANSI SQL where possible. If DB-specific syntax is unavoidable, note it in a comment.
- Keep Oracle and PostgreSQL compatibility in mind — avoid syntax that only works in one.
```sql
SELECT col1,
col2
FROM my_table
WHERE active = 1
AND id = :id
ORDER BY col1
```
## Step 4 — Update vitruvio.json entry
`vitruvio new` already added the query entry. The full entry shape is:
```json
{
"key": "<key>",
"name": "<name>",
"source": "queries/<key>.sql",
"connection": "vitruvio_producao"
}
```
Update `"connection"` only if the user specified a datasource other than `"vitruvio_producao"`.
## Step 5 — Report
Tell the user:
- File created: `queries/<key>.sql`
- Registered in `vitruvio.json` with key `<key>`
- How it can be used: as a datasource in a report, in a DBTable component, or loaded in a script via `db.executeNamedQuery('<key>', params)`
@@ -0,0 +1,126 @@
---
name: vitruvio-criar-relatorio
description: >
Use when the user wants to register a new report in a Vitruvio repository.
Triggers: "create report", "new report", "criar relatório", "novo relatório", "add report",
MODELO_ESTATICO, DINAMICO_QUERY_SQL, "jrxml", or any request to scaffold a report entry.
---
# Create Vitruvio Report
> All messages shown to the user must be written in Portuguese.
You are registering a new report inside a Vitruvio repository. Follow these steps in order.
## 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 — Identify the report type
Ask the user which type applies (if not already clear from the request):
| Type | When to use |
|------|-------------|
| **MODELO_ESTATICO** | A Jasper report whose layout is fully designed in a `.jrxml` file (Jaspersoft Studio). The dev provides or will provide the `.jrxml`. |
| **DINAMICO_QUERY_SQL** | A Vitruvio-managed report where data comes from a named query and columns/layout are configured in `vitruvio.json`. The `.jrxml` is still needed but is simpler — Vitruvio drives the structure. |
## Step 3 — Collect details
Ask the user (in a single message, only ask what is missing):
**Both types:**
- **Key** — kebab-case or snake_case unique identifier.
- **Name** — human-readable label shown in the UI.
- **Category** — display category (e.g. `"Compras"`, `"Auditoria/Gondola"`).
- **Owner** — Vitruvio username of the responsible person.
- **Orientation** — `RETRATO` (portrait) or `PAISAGEM` (landscape). Default: `RETRATO`.
- **Allowed groups / users** — who can access this report (can be empty arrays).
- **Has parameters?** — does the user fill in parameters before running it? If yes, a params form is needed.
**DINAMICO_QUERY_SQL only:**
- **Query key** — the named query that feeds the report (must be registered in `vitruvio.json`).
- **Columns** — list of columns: name (DB column), label, alignment (`LEFT`/`CENTER`/`RIGHT`), width (px), aggregation (`null`, `SUM`, `COUNT`, etc.).
## Step 4 — Create the files
### MODELO_ESTATICO
Files live flat in `reports/`:
- `reports/<key>.jrxml` — **do not generate this file**; tell the user to place the Jaspersoft-designed template here. Must target **JasperReports 6.21.2** — do not save with a newer version.
- `reports/<key>-params.xml` — only if the report has parameters (follows the same Vaadin XML form schema as panels).
### DINAMICO_QUERY_SQL
Files live in a subdirectory:
- `reports/<key>/template.jrxml` — **do not generate this file**; tell the user to place the template here.
- `reports/<key>/params.xml` — only if the report has parameters.
## Step 5 — Register in vitruvio.json
Read `vitruvio.json`, find or create the `"reports"` array, and add the entry.
### MODELO_ESTATICO entry
```json
{
"key": "<key>",
"name": "<name>",
"type": "MODELO_ESTATICO",
"category": "<category>",
"owner": "<owner>",
"template": "reports/<key>.jrxml",
"parameterForm": "reports/<key>-params.xml",
"orientation": "RETRATO",
"allowedGroups": [],
"allowedUsers": [],
"columns": [],
"schedules": []
}
```
Omit `"parameterForm"` if no params form.
### DINAMICO_QUERY_SQL entry
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"type": "DINAMICO_QUERY_SQL",
"category": "<category>",
"owner": "<owner>",
"template": "reports/<key>/template.jrxml",
"parameterForm": "reports/<key>/params.xml",
"query": "<query-key>",
"orientation": "RETRATO",
"allowedGroups": [],
"allowedUsers": [],
"columns": [
{
"name": "COLUMN_NAME",
"label": "Column Label",
"align": "LEFT",
"width": 100,
"aggregation": null
}
]
}
```
Omit `"parameterForm"` if no params form.
Preserve the existing file structure and all other entries. Write the updated `vitruvio.json` back.
## Step 6 — Report
Tell the user:
- Entry registered in `vitruvio.json` with key `<key>` and type `<type>`
- For MODELO_ESTATICO: remind them to place the `.jrxml` at `reports/<key>.jrxml`, designed in Jaspersoft Studio 6.21.2
- For DINAMICO_QUERY_SQL: remind them to place the template at `reports/<key>/template.jrxml`
- If a params form is needed: what file to create and that it follows the same Vaadin XML schema as panels
@@ -0,0 +1,111 @@
---
name: vitruvio-criar-script
description: >
Use when the user wants to create a new JavaScript script in a Vitruvio repository.
Triggers: "create script", "new script", "criar script", "novo script", "add script",
or any request to scaffold a scripts/*.js file and register it in vitruvio.json.
---
# Create Vitruvio Script
> All messages shown to the user must be written in Portuguese.
You are creating a new script inside a Vitruvio repository. Follow these steps in order.
## 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 — Collect script details
Ask the user (in a single message, only ask what is missing from their original request):
- **Key (sigla)** — unique identifier used in `libService.loadScript('key')`. Snake_case. Must be unique across all scripts in the repo. If the user already provided a name, suggest a key derived from it.
- **Name** — human-readable label shown in the Vitruvio UI.
- **Pattern** — which of the two patterns applies:
- **Library** — reusable module, loaded by other scripts/endpoints/panels via `libService.loadScript`. Wrap in `({...})`.
- **Process/task script** — runs directly from a process task or scheduler. Top-level execution, no export.
- **Description** — one sentence about what this script does (optional, but ask if not provided).
- **Domain** — `USUARIO` (user-level, default) or `SISTEMA` (system-level).
## Step 3 — Scaffold and create file
```bash
vitruvio new script <key> --name "<name>"
```
This creates `scripts/<key>.js` and registers it in `vitruvio.json` with `domain: "USUARIO"`. Replace the generated file with the appropriate template below. If the domain should be `SISTEMA`, update that field in `vitruvio.json`.
Target path: `scripts/<key>.js`
### Library template
```javascript
/**
* Nome: <name>
* Sigla: <key>
* Descrição: <description>
*/
({
// example function — replace with actual implementation
run: function(params) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
// implementation here
return {};
}
})
```
### Process / task script template
```javascript
/**
* Nome: <name>
* Sigla: <key>
* Descrição: <description>
*/
(function(execution) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
// implementation here
})(execution)
```
Rules (Rhino ES5 — no exceptions):
- No `let`, `const`, arrow functions, template literals, destructuring, spread, `class`, or `import/export`
- Use `var` everywhere
- No `require`, `process`, `window`, or Node/browser globals
- String concatenation with `+`, not template literals
- `JSON.parse` / `JSON.stringify` for serialization
## Step 4 — Update vitruvio.json entry
`vitruvio new` already added the script entry. The full entry shape is:
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description>",
"language": "javascript",
"domain": "USUARIO",
"source": "scripts/<key>.js"
}
```
Update only what differs: change `"domain"` to `"SISTEMA"` if needed; add `"description"` if provided.
## Step 5 — Report
Tell the user:
- File created: `scripts/<key>.js`
- Registered in `vitruvio.json` with key `<key>`
- How to load it from another script or endpoint: `var lib = libService.loadScript('<key>');`
@@ -0,0 +1,178 @@
---
name: vitruvio-registrar-artefato
description: >
Use when the user has an existing file they want to add to a Vitruvio repository.
Triggers: "I have an existing file", "move this to panels/", "register this artifact",
"add this already existing", "tenho um arquivo pronto", "mover para o repo",
or any request where the source file already exists and needs to be placed in the
correct repo directory and registered in vitruvio.json.
Do NOT use vitruvio-criar-* skills for this — those scaffold new files and would overwrite the existing one.
---
# Register Existing Artifact
> All messages shown to the user must be written in Portuguese.
You are wiring an already-existing file into a Vitruvio repository. No scaffolding — the file exists and must be placed correctly then registered in `vitruvio.json`. Follow these steps in order.
## 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 — Collect details
Ask the user (in a single message, only ask what is missing from their original request):
- **Source file path** — where the file currently lives (absolute or relative).
- **Artifact type** — one of: `panel`, `script`, `endpoint`, `query`, `report`, `library`, `process`.
- **Key** — kebab-case unique identifier for this artifact. Must be unique within its section in `vitruvio.json`. Changing it later is a breaking change.
- **Name** — human-readable label shown in the Vitruvio UI.
- Type-specific fields (only ask what is relevant):
- Panel: `category` (e.g. `"Base de Conhecimento"`), `openInNewWindow` (default `true`), `showInMobileList` (default `false`), mobile form path if one also exists.
- Script: `domain` — `USUARIO` (default) or `SISTEMA`.
- Endpoint: `authMode` — `PUBLIC` (default), `STATIC_TOKEN`, `VITRUVIO_WS_USER_AUTH`, or `HTTP_BASIC_AUTH`.
- Query: `connection` — datasource name (default `vitruvio_producao`).
- Report: `type` — `MODELO_ESTATICO` or `DINAMICO_QUERY_SQL`; `query` key; `template` path; `parameterForm` path (optional).
- Library: `authMode` — `PUBLIC` (default); `mobileEnabled` (default `false`).
- Process: path to the BPMN file and desktop form XML if they are separate files.
## Step 3 — Place the file(s)
Create the destination directory if it does not exist, then move the source file to the correct location. **Never call `vitruvio new`** — it would scaffold and overwrite the existing file.
| Artifact | Destination | Notes |
|----------|-------------|-------|
| `panel` | `panels/<key>/<key>-desktop.xml` | Rename the source desktop form to `<key>-desktop.xml`. If a mobile form also exists, place it at `panels/<key>/<key>-mobile.xml`. |
| `script` | `scripts/<key>.js` | |
| `endpoint` | `endpoints/<key>.js` | |
| `query` | `queries/<key>.sql` | |
| `report` | `reports/<key>/` | Move jrxml and optional params.xml into this directory. |
| `library` | `libraries/<key>/` | Move all files into this directory. |
| `process` | `processes/<key>/` | Move the .bpmn and form XML(s) into this directory. |
```bash
# Example: panel
mkdir -p panels/<key>
mv /path/to/source.xml panels/<key>/<key>-desktop.xml
```
## Step 4 — Add entry to vitruvio.json
Read `vitruvio.json`. If the relevant section array does not exist, create it. Append the entry for the artifact type. Do **not** remove or modify any existing entries.
### Panel
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description or omit>",
"category": "<category>",
"displayOrder": 10,
"showInPresentation": false,
"openInNewWindow": true,
"showInMobileList": false,
"displayTimeInSeconds": 0,
"allowedGroups": [],
"allowedUsers": [],
"forms": {
"desktop": "panels/<key>/<key>-desktop.xml",
"mobile": null,
"mobileAlternative": null
},
"defaultState": null,
"thumbnail": null
}
```
### Script
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description or omit>",
"language": "javascript",
"domain": "USUARIO",
"source": "scripts/<key>.js"
}
```
### Endpoint
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description or omit>",
"language": "javascript",
"authMode": "PUBLIC",
"active": true,
"source": "endpoints/<key>.js"
}
```
### Query
```json
{
"key": "<key>",
"name": "<name>",
"connection": "vitruvio_producao",
"source": "queries/<key>.sql"
}
```
### Report
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description or omit>",
"type": "DINAMICO_QUERY_SQL",
"category": "<category or omit>",
"owner": null,
"template": "reports/<key>/template.jrxml",
"parameterForm": "reports/<key>/params.xml",
"query": "<query-key>",
"orientation": "RETRATO",
"allowedGroups": [],
"allowedUsers": []
}
```
### Library
```json
{
"key": "<key>",
"name": "<name>",
"type": "LOCAL",
"authMode": "PUBLIC",
"authToken": null,
"mobileEnabled": false,
"files": "libraries/<key>/"
}
```
### Process
```json
{
"key": "<key>",
"name": "<name>",
"description": "<description or omit>",
"bpmn": "processes/<key>/<key>.bpmn",
"forms": {
"desktop": "processes/<key>/<key>-desktop.xml",
"mobile": null,
"mobileAlternative": null
}
}
```
## Step 5 — Report
Tell the user:
- Where the file was placed
- Registered in `vitruvio.json` under section `<type>` with key `<key>`
- Which fields were left at defaults and may need updating (e.g. `category`, `allowedGroups`, `description`)
- Reminder: keys are stable identifiers — changing them later is a breaking change
+4
View File
@@ -0,0 +1,4 @@
node_modules/
tests/.env
docs/
.mcp.json
+1
View File
@@ -0,0 +1 @@
@davinti:registry=https://git.davinti.com.br/api/packages/davinTI/npm/
+416
View File
@@ -0,0 +1,416 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://git.davinti.com.br/davinTI/vitruvio/raw/branch/master/vitruvio-ui/src/main/resources/vitruvio-manifest-schema.json",
"title": "vitruvio.json",
"description": "Vitruvio Git Content Repository Manifest",
"type": "object",
"required": ["version", "metadata"],
"additionalProperties": false,
"properties": {
"$schema": { "type": "string" },
"_comment": { "type": "string" },
"_comment_version": { "type": "string" },
"version": {
"type": "string",
"description": "Manifest version, e.g. \"1.0.0\""
},
"metadata": {
"type": "object",
"required": ["key"],
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"key": { "type": "string", "description": "Unique identifier for this content package" },
"standardProduct": { "type": "boolean" }
}
},
"panels": { "type": "array", "items": { "$ref": "#/definitions/panel" } },
"processes": { "type": "array", "items": { "$ref": "#/definitions/process" } },
"scripts": { "type": "array", "items": { "$ref": "#/definitions/script" } },
"endpoints": { "type": "array", "items": { "$ref": "#/definitions/endpoint" } },
"queries": { "type": "array", "items": { "$ref": "#/definitions/query" } },
"reports": { "type": "array", "items": { "$ref": "#/definitions/report" } },
"libraries": { "type": "array", "items": { "$ref": "#/definitions/library" } },
"groups": { "type": "array", "items": { "$ref": "#/definitions/group" } },
"properties": { "type": "array", "items": { "$ref": "#/definitions/property" } },
"menu": { "type": "array", "items": { "$ref": "#/definitions/menuItem" } },
"permissions": { "type": "array", "items": { "$ref": "#/definitions/permission" } },
"patches": {
"type": "string",
"description": "Relative directory path containing Liquibase changesets, e.g. \"patches/\""
}
},
"definitions": {
"panel": {
"type": "object",
"required": ["key", "name"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"description": { "type": ["string", "null"] },
"category": { "type": "string", "description": "Hierarchical path, e.g. \"Comercial/Vendas\"" },
"displayOrder": { "type": "integer", "default": 0 },
"showInPresentation": { "type": "boolean", "default": false },
"openInNewWindow": { "type": "boolean", "default": false },
"showInMobileList": { "type": "boolean", "default": false },
"displayTimeInSeconds": { "type": "integer", "default": 0 },
"allowedGroups": { "type": "array", "items": { "type": "string" } },
"allowedUsers": { "type": "array", "items": { "type": "string" } },
"forms": {
"type": "object",
"additionalProperties": false,
"properties": {
"desktop": { "type": ["string", "null"], "description": "Relative path to desktop form XML" },
"mobile": { "type": ["string", "null"], "description": "Relative path to mobile form XML" },
"mobileAlternative": { "type": ["string", "null"], "description": "Relative path to alternative mobile form XML" }
}
},
"defaultState": { "type": ["string", "null"], "description": "Relative path to default state JSON" },
"thumbnail": { "type": ["string", "null"], "description": "Relative path to thumbnail image" }
}
},
"script": {
"type": "object",
"required": ["key", "name", "source", "language", "domain"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"description": { "type": ["string", "null"] },
"language": { "type": "string", "enum": ["javascript", "JavaScript", "groovy", "Groovy"] },
"domain": { "type": "string", "enum": ["USUARIO", "SISTEMA"] },
"source": { "type": "string", "description": "Relative path to the script file" },
"documentation": { "type": ["string", "null"], "description": "Relative path to optional documentation file" },
"schedules": { "type": "array", "items": { "$ref": "#/definitions/scriptSchedule" } }
}
},
"scriptSchedule": {
"type": "object",
"required": ["uuid", "nome", "triggerInfo"],
"additionalProperties": false,
"properties": {
"uuid": { "type": "string", "format": "uuid" },
"nome": { "type": "string" },
"descricao": { "type": ["string", "null"] },
"proprietario": { "type": ["string", "null"] },
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
"scheduleScriptParameters": { "type": ["string", "null"] },
"controleNotificacao": { "type": "boolean", "default": false },
"configuracaoNotificacao": { "type": ["object", "null"] },
"triggerInfo": { "$ref": "#/definitions/triggerInfo" },
"statusPolicy": { "$ref": "#/definitions/scheduleSyncPolicy" }
}
},
"endpoint": {
"type": "object",
"required": ["key", "name", "source", "language", "authMode"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"description": { "type": ["string", "null"] },
"language": { "type": "string", "enum": ["javascript", "JavaScript", "groovy", "Groovy"] },
"authMode": { "type": "string", "enum": ["PUBLIC", "STATIC_TOKEN", "VITRUVIO_WS_USER_AUTH", "HTTP_BASIC_AUTH"] },
"active": { "type": "boolean", "default": true },
"source": { "type": "string", "description": "Relative path to the endpoint script file" }
}
},
"query": {
"type": "object",
"required": ["key", "name", "source"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"connection": { "type": ["string", "null"], "description": "Database connection key" },
"source": { "type": "string", "description": "Relative path to the SQL file" }
}
},
"report": {
"type": "object",
"required": ["key", "name", "type", "category"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"description": { "type": ["string", "null"] },
"type": { "type": "string", "enum": ["MODELO_ESTATICO", "DINAMICO_QUERY_SQL"] },
"category": { "type": "string", "description": "Hierarchical category path" },
"owner": { "type": ["string", "null"] },
"template": { "type": ["string", "null"], "description": "Relative path to Jasper template" },
"parameterForm": { "type": ["string", "null"], "description": "Relative path to parameter form XML" },
"query": { "type": ["string", "null"], "description": "Key of the query used by this report" },
"orientation": { "type": "string", "enum": ["RETRATO", "PAISAGEM"] },
"allowedGroups": { "type": "array", "items": { "type": "string" } },
"allowedUsers": { "type": "array", "items": { "type": "string" } },
"columns": { "type": "array", "items": { "$ref": "#/definitions/reportColumn" } },
"schedules": { "type": "array", "items": { "$ref": "#/definitions/reportSchedule" } }
}
},
"reportColumn": {
"type": "object",
"additionalProperties": false,
"properties": {
"name": { "type": "string" },
"label": { "type": "string" },
"align": { "type": "string", "enum": ["LEFT", "CENTER", "RIGHT"] },
"width": { "type": "integer" },
"aggregation": { "type": ["string", "null"], "description": "e.g. \"SUM\", \"COUNT\", \"AVG\"" },
"groupBy": { "type": "boolean", "default": false },
"groupByType": { "type": ["string", "null"], "description": "e.g. \"VALUE_IN_HEADER\", \"VALUE_IN_HEADER_WITH_HEADERS\"" },
"pattern": { "type": ["string", "null"] },
"separadorDecimal": { "type": ["string", "null"] },
"separadorGrupo": { "type": ["string", "null"] }
}
},
"reportSchedule": {
"type": "object",
"required": ["uuid", "nome", "triggerInfo"],
"additionalProperties": false,
"properties": {
"uuid": { "type": "string", "format": "uuid" },
"nome": { "type": "string" },
"descricao": { "type": ["string", "null"] },
"proprietario": { "type": ["string", "null"] },
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
"triggerInfo": { "$ref": "#/definitions/triggerInfo" },
"usuarios": { "type": "array", "items": { "type": "string" } },
"emails": { "type": "array", "items": { "type": "string" } },
"grupos": { "type": "array", "items": { "type": "string" } },
"formatos": { "type": "array", "items": { "type": "integer" } },
"emailHTML": { "type": ["string", "null"] },
"emailAssunto": { "type": ["string", "null"] },
"preExec": { "type": ["string", "null"] },
"posExec": { "type": ["string", "null"] },
"embedHtml": { "type": "boolean", "default": false },
"embedRecipients": { "type": "boolean", "default": false },
"comprimirAnexosZip": { "type": "boolean", "default": false },
"ignorarRelatorioSemDados": { "type": "boolean", "default": false },
"enviarRelatorioPorEmail": { "type": "boolean", "default": false },
"salvarBibliotecaArquivos": { "type": "boolean", "default": false },
"biblioteca": { "type": ["string", "null"] },
"nomeArquivoPrefixo": { "type": ["string", "null"] },
"usarDataComoSufixoComFormato": { "type": ["string", "null"] },
"usarDataComoSufixoComFormatoAoAnexar": { "type": ["string", "null"] },
"sobrescreverArquivoNaBiblioteca": { "type": "boolean", "default": false },
"subdiretorio": { "type": ["string", "null"] },
"jsonReportParam": { "type": ["string", "null"] }
}
},
"library": {
"type": "object",
"required": ["key", "name", "type", "authMode", "files"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"type": { "type": "string", "enum": ["LOCAL"] },
"authMode": { "type": "string", "enum": ["PUBLIC", "MOBILE", "STATIC_TOKEN"] },
"authToken": { "type": ["string", "null"], "description": "Required when authMode is STATIC_TOKEN" },
"mobileEnabled": { "type": "boolean", "default": false },
"files": { "type": "string", "description": "Relative path to directory containing library files" }
}
},
"group": {
"type": "object",
"required": ["key", "name"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"description": { "type": ["string", "null"] }
}
},
"property": {
"type": "object",
"required": ["key", "type"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"displayName": { "type": "string" },
"description": { "type": ["string", "null"] },
"type": { "type": "string", "enum": ["STRING", "INTEGER", "BOOLEAN", "DATE"] },
"size": { "type": ["integer", "null"] },
"precision": { "type": ["integer", "null"] },
"format": { "type": ["string", "null"] },
"required": { "type": "boolean", "default": false },
"password": { "type": "boolean", "default": false },
"predefinedValues": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"descricao": { "type": "string" },
"ordem": { "type": "integer", "default": 0 }
}
}
}
}
},
"menuItem": {
"type": "object",
"required": ["key", "name", "type"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"icon": { "type": ["integer", "null"] },
"order": { "type": "integer", "default": 0 },
"type": { "type": "string", "enum": ["MENU", "PAINEL", "RELATORIO", "PROCESSO", "SISTEMA"] },
"panelKey": { "type": ["string", "null"], "description": "References panel.key (when type is PANEL)" },
"reportKey": { "type": ["string", "null"], "description": "References report.key (when type is REPORT)" },
"processKey": { "type": ["string", "null"], "description": "References process.key (when type is PROCESS)" },
"children": { "type": "array", "items": { "$ref": "#/definitions/menuItem" } }
}
},
"permission": {
"type": "object",
"required": ["processKey", "group"],
"additionalProperties": false,
"properties": {
"processKey": { "type": "string", "description": "References process.key" },
"group": { "type": "string", "description": "References group.key" },
"read": { "type": "boolean", "default": false },
"writeStages": { "type": "boolean", "default": false },
"cancel": { "type": "boolean", "default": false },
"delete": { "type": "boolean", "default": false },
"stageStatus": { "type": "boolean", "default": false }
}
},
"process": {
"type": "object",
"required": ["key", "name", "bpmn"],
"additionalProperties": false,
"properties": {
"key": { "type": "string" },
"name": { "type": "string" },
"description": { "type": ["string", "null"] },
"bpmn": { "type": "string", "description": "Relative path to the BPMN XML file" },
"forms": {
"type": "object",
"additionalProperties": false,
"properties": {
"desktop": { "type": ["string", "null"] },
"mobile": { "type": ["string", "null"] },
"mobileAlternative": { "type": ["string", "null"] }
}
},
"schedules": { "type": "array", "items": { "$ref": "#/definitions/processSchedule" } }
}
},
"processSchedule": {
"type": "object",
"required": ["uuid", "nome", "triggerInfo"],
"additionalProperties": false,
"properties": {
"uuid": { "type": "string", "format": "uuid" },
"nome": { "type": "string" },
"descricao": { "type": ["string", "null"] },
"proprietario": { "type": ["string", "null"] },
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
"triggerInfo": { "$ref": "#/definitions/triggerInfo" },
"type": { "type": ["string", "null"], "enum": ["SIMPLE", "OWNER_LOOP", null] },
"controleNotificacao": { "type": "boolean", "default": false },
"configuracaoNotificacao": { "type": ["string", "null"] },
"metadata": { "$ref": "#/definitions/scheduleNotificationMetadata" },
"usuarios": { "type": "array", "items": { "type": "string" } },
"grupos": { "type": "array", "items": { "type": "string" } },
"emails": { "type": "array", "items": { "type": "string" } },
"scheduleParams": { "type": ["string", "null"] },
"customParams": { "type": ["string", "null"] }
}
},
"scheduleNotificationMetadata": {
"type": "object",
"additionalProperties": false,
"properties": {
"assuntoEmail": { "type": ["string", "null"] },
"mensagemEmail": { "type": ["string", "null"] },
"checkEmail": { "type": ["boolean", "null"] },
"checkPush": { "type": ["boolean", "null"] },
"usuariosNotificados": { "type": ["string", "null"] },
"gruposNotificados": { "type": ["string", "null"] },
"emailsAuxiliares": { "type": ["string", "null"] }
}
},
"triggerInfo": {
"oneOf": [
{ "$ref": "#/definitions/simpleTriggerInfo" },
{ "$ref": "#/definitions/calendarTriggerInfo" }
]
},
"simpleTriggerInfo": {
"type": "object",
"required": ["type", "repeatEvery", "repeat"],
"additionalProperties": false,
"properties": {
"type": { "type": "string", "const": "SimpleTriggerInfo" },
"triggerName": { "type": ["string", "null"] },
"triggerDescription": { "type": ["string", "null"] },
"repeat": { "type": "string", "enum": ["SECONDS", "MINUTES", "HOURS", "DAYS", "WEEKS"] },
"repeatEvery": { "type": "integer", "minimum": 1 },
"repeatCount": { "type": ["integer", "null"] },
"startDate": { "type": ["string", "null"], "format": "date-time" },
"endDate": { "type": ["string", "null"], "format": "date-time" },
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
"minute": { "type": "integer", "minimum": 0, "maximum": 59 },
"hour": { "type": "integer", "minimum": 0, "maximum": 23 },
"dayOfWeek": { "type": "integer", "minimum": 1, "maximum": 7 }
}
},
"calendarTriggerInfo": {
"type": "object",
"required": ["type"],
"additionalProperties": false,
"properties": {
"type": { "type": "string", "const": "CalendarTriggerInfo" },
"triggerName": { "type": ["string", "null"] },
"triggerDescription": { "type": ["string", "null"] },
"startDate": { "type": ["string", "null"], "format": "date-time" },
"endDate": { "type": ["string", "null"], "format": "date-time" },
"execFeriado": { "$ref": "#/definitions/scheduleExecFeriado" },
"months": { "type": "array", "items": { "type": "integer", "minimum": 1, "maximum": 12 }, "description": "Empty = every month" },
"weekDays": { "type": "array", "items": { "type": "integer", "minimum": 1, "maximum": 7 }, "description": "1=Sunday ... 7=Saturday. Mutually exclusive with monthDaysExpression" },
"monthDaysExpression": { "type": ["string", "null"], "description": "e.g. \"1,15\" or \"1-5\" or \"*\". Mutually exclusive with weekDays" },
"hourExpression": { "type": ["string", "null"], "description": "e.g. \"8,12,18\" or \"8-17\" or \"*\"" },
"minutesExpression": { "type": ["string", "null"], "description": "e.g. \"0,30\" or \"*\"" },
"secondsExpression": { "type": ["string", "null"], "description": "e.g. \"0\"" }
}
},
"scheduleExecFeriado": {
"type": "string",
"enum": ["SEMPRE_EXECUTAR_FERIADO", "NUNCA_EXECUTAR_FERIADO", "DEFINIR_EXECUTAR_FERIADO"]
},
"scheduleSyncPolicy": {
"type": "string",
"description": "Controls the schedule's paused/active state on git sync. Omitted = KEEP_PREVIOUS: restores whatever pause/active state the schedule already had (new schedules are still imported paused). ALWAYS_ACTIVE / ALWAYS_PAUSED force that state on every sync regardless of prior state.",
"enum": ["KEEP_PREVIOUS", "ALWAYS_ACTIVE", "ALWAYS_PAUSED"]
}
}
}
+531
View File
@@ -0,0 +1,531 @@
# Vitruvio Development Guidelines
## What is Vitruvio
Vitruvio is a Java 21 / Spring / Vaadin 7 multi-tenant BPM platform. Developers extend it by creating **content repositories** — git repos that contain panels, processes, scripts, queries, reports, endpoints, and more. Vitruvio reads these repos and renders their content at runtime. This file is one such repository.
Every repository must have a `vitruvio.json` manifest at its root that declares all artifacts. That manifest is the source of truth for what the platform will register.
---
## Repository Structure
Artifacts live under their own top-level directories, matching the paths declared in `vitruvio.json`:
```
vitruvio.json
panels/
<panel-key>/
<panel-key>-desktop.xml
<panel-key>-mobile.xml # optional
default-state.json
thumbnail.png
processes/
<process-key>/
<process-key>.bpmn
<process-key>-desktop.xml
<process-key>-mobile.xml # optional
scripts/
<script-key>.js
<script-key>.md # optional documentation
endpoints/
<endpoint-key>.js
queries/
<query-key>.sql
reports/
<report-key>/
template.jrxml
params.xml
libraries/
<library-key>/
patches/
```
All `key` values in `vitruvio.json` must be **kebab-case**, unique within their artifact type, and stable — changing a key is a breaking change.
---
## vitruvio.json Manifest
`version` and `metadata.key` are the only required fields. All other sections are optional arrays.
### metadata
```json
{
"name": "Human-readable module name",
"key": "module-key", // kebab-case, unique across tenants
"standardProduct": false // true only for widespread modules
}
```
### Artifact sections
Each section is an array. Omit the section entirely if there are no items — don't leave empty arrays unless needed.
Key rules per artifact:
- **panels** — UI screens built from XML forms. `forms.desktop` is the primary form path; mobile is optional.
- **processes** — BPMN workflows. Can declare `schedules` (cron or simple interval triggers). Are composed from a BPMN file for Activiti, a xml file for the web form and one or two xml files for the mobile forms.
- **scripts** — Reusable JS logic. `domain` is either `"SISTEMA"` (system-level) or `"USUARIO"` (user-level). Will generally be USUARIO. For non-trivial logic, see Unit Testing below.
- **endpoints** — REST endpoints. `authMode` is one of `"PUBLIC"`, `"STATIC_TOKEN"`, `"VITRUVIO_WS_USER_AUTH"`, or `"HTTP_BASIC_AUTH"`. Default to `"PUBLIC"`. `active: true` to enable. For non-trivial logic, see Unit Testing below.
- **queries** — Named SQL queries. `connection` references a configured datasource. Use `"vitruvio_producao"` for the default datasource.
- **reports** — Jasper reports. Require a query key, a `.jrxml` template, and optionally a parameter form.
- **libraries** — Static file bundles (JS/CSS/images). `type: "LOCAL"` for files in this repo.
- **groups** — User groups this module manages or depends on.
- **properties** — User-configurable key/value settings.
- **menu** — Navigation tree. See the Menu section below for structure and type values.
- **permissions** — Process-level access control per group. Mostly unused, generally defined in the bpmn file for the process.
- **patches** — Path to a directory containing per-database Liquibase changelogs. Has `oracle/` and `postgresql/` subdirectories, each with its own XML changelog file.
---
## XML Forms (Panels & Processes)
Forms are XML files validated against Vitruvio's XSD schema. Vaadin components are declared in XML and rendered by the platform's Java presenter layer — there is no manual Java UI code in content repos (except if imported in scripts using the Rhino engine).
**Form files are named `<key>-desktop.xml` / `<key>-mobile.xml` and the BPMN `<key>.bpmn`** (artifact key + suffix), so each form is searchable by its key rather than dozens of identical `form-desktop.xml` tabs.
**Desktop vs mobile forms are different schemas.** Desktop (`<key>-desktop.xml`) uses the `<panel-form>` / `<forms>` schemas, the 78-component desktop set, and Rhino ES5 scripts with auto-injected process variables. Mobile (`<key>-mobile.xml`) uses the `<mobile-forms>` schema (namespace `.../vitruvio/mobile-form`), only the 18 mobile components, **modern React-Native JS on the client** (Promises) with **server-side `<ServerSide><Bridge>` blocks in Rhino ES5** for any lib/DB/service access, and **explicitly declared data** (`<Autoload>` variables, `<QueryDataSource>`, bridges via `vCommunicationService.executeOnServer`). Use the focused skills — `vitruvio-criar-processo-bpmn`, `vitruvio-criar-form-desktop`, `vitruvio-criar-form-mobile` (driven by the `vitruvio-criar-painel` / `vitruvio-criar-processo` orchestrators) — and read `docs/components/mobile/` before building a mobile form.
General rules:
- **Always save form/XML files as UTF-8 *without* a BOM.** A leading byte-order mark (`EF BB BF`) — or any whitespace/content before the `<?xml` declaration — makes the platform's XML parser fail with `Content is not allowed in prolog` when the form is opened, even though the file looks fine in an editor. When writing or editing `.xml`/`.bpmn` files, never prepend a BOM or blank line; the declaration must be the very first bytes. Run `vitruvio validate` (which checks for this) before committing.
- Always validate your XML against the XSD before committing. Malformed forms fail on open.
- Keep forms focused. One screen = one form. Split complex layouts into sub-forms or components if the schema allows.
- Use `<script>` blocks with `<![CDATA[...]]>` for inline JavaScript on components (button actions, lifecycle hooks, field validators). For non-trivial logic, delegate to a named script file rather than writing large CDATA blocks inline or repetitive code and import it using `libService.loadScript("lib_name")`.
- `displayOrder` on panels controls the order they appear in listings. Use gaps (10, 20, 30…) to allow future insertions without reshuffling.
---
## JavaScript — ES5 / Rhino Engine
All JavaScript in this platform runs on **Mozilla Rhino**, an ES5 interpreter embedded in the JVM. Modern JS features are not available.
### What you CANNOT use
```javascript
// NO — ES6+ is not supported
const x = 1;
let y = 2;
const fn = () => {};
`template ${literal}`;
const { a, b } = obj;
const [first, ...rest] = arr;
class Foo {}
async function bar() {}
await something();
import x from 'y';
export default x;
for (const item of iterable) {}
Promise.resolve();
Array.from(x);
Object.assign({}, a, b);
```
### What you MUST use instead
```javascript
// YES — ES5 style
var x = 1;
var y = 2;
var fn = function() {};
"template " + variable;
var a = obj.a; var b = obj.b;
// loop with index
for (var i = 0; i < arr.length; i++) {}
// prototype-based, not class
function Foo() {}
Foo.prototype.bar = function() {};
// callbacks, not async/await
doSomething(function(result) { ... });
```
### Additional Rhino caveats
- `JSON.parse` and `JSON.stringify` are available.
- `typeof`, `instanceof`, standard `Array`/`Object`/`String` methods from ES5 work fine.
- No browser globals (`window`, `document`, `fetch`, `XMLHttpRequest`).
- No Node.js globals (`require`, `process`, `Buffer`, `__dirname`).
- Rhino exposes Java interop — avoid it in business scripts unless you know what you're doing.
- Always declare variables with `var`. Undeclared variables become globals and cause hard-to-trace bugs.
- When comparing values that originate from Java (DB query results, form field values, process variables), use `==` / `!=` — Java types do not strict-equal JavaScript primitives. Use `===` / `!==` only when you control both sides and know they are pure JS values.
- `Array.prototype` iteration methods `forEach`, `map`, `filter`, `reduce`, `some`, `every` are available — they are ES5 and work fine in Rhino.
---
## Unit Testing
Tests run under **Jest on Node**, not Rhino — this is the one place in the repo where modern JS
(`const`, arrow functions, destructuring) is fine, because `*.test.js` files never execute on the
platform. The production code they test still must be ES5/Rhino-safe.
**Scope: `scripts/*.js` and `endpoints/*.js` only.** Inline `<script>` CDATA in panel/process XML and
`.sql` queries have no module boundary to hook Jest into and are not unit tested this way.
**When to write a test.** Only for non-trivial logic — a function with a branch, a loop, or a call
into `db`/`http`/a platform service. A script or endpoint that's a straight passthrough doesn't need
one. **Any bug fix always gets a regression test**, written test-first:
1. Write a test asserting the correct behavior.
2. Run it and confirm it **fails** (proves it actually catches the bug).
3. Fix the root cause.
4. Run it and confirm it **passes**.
5. Run the full suite (`npm test`) to check for regressions elsewhere.
Only add the dependency-injection seam below to scripts/endpoints that actually get a test — don't
restructure trivial code preemptively for testability it doesn't need.
### Making a script testable
Wrap the logic in a constructor accepting an optional `deps` object, falling back to the real
platform globals. Guard the CommonJS export with `typeof module` — Rhino's script-loading context
(`libService.loadScript`) never defines `module`, Node always does. See `scripts/exemplo.js` /
`scripts/exemplo.test.js` for the full example.
```javascript
function MinhaLib(deps) {
var banco = deps ? deps.banco : new db(db.VITRUVIO_DATASOURCE);
this.metodo = function() { ... };
}
if (typeof module !== 'undefined') module.exports = MinhaLib;
```
### Making an endpoint testable
Endpoints have a fixed production contract: the platform's Rhino endpoint loader expects
`module.exports` to already be an instance with `onGet`/`onPost`/etc, not a constructor — and Rhino
*does* define `module` there, so the `typeof module` guard doesn't distinguish it from Node. Instead,
guard on `process`, which only exists in Node:
```javascript
function WebService(deps) {
if (deps) {
this.db = deps.db;
this.webApi = deps.webApi;
} else {
this.db = libService.loadScript('db');
this.webApi = libService.loadScript('minha-lib');
}
this.onPost = function(params, headers, res) { ... };
}
if (typeof process !== "undefined" && process.env.NODE_ENV === "test") {
module.exports = WebService;
} else {
module.exports = new WebService();
}
```
When there's more than one dependency, swap the **whole `deps` object** in one `if`/`else` block
rather than per-field fallback (`deps.x || real`). A test that forgets to mock one dependency then
gets `undefined` and fails loudly, instead of silently hitting a real service.
### Mocking
Prefer the shared helpers over hand-rolled stubs:
- `@davinti/vitruvio-core-libs/db/mock` → `MockDb`, `createQueryResult` for anything using `db`.
- `@davinti/vitruvio-test-utils` → `createMockServletResponse`, `createMockLibService` for endpoint
responses and `libService.loadScript` lookups.
- The shared Jest preset (`jest.config.js` → `preset: '@davinti/vitruvio-test-utils'`) already stubs
`println`, `java`, `libService`, `vLogger` as globals — `libService.loadScript` returns `null` for
anything not explicitly mocked, by design, so an unmocked lib dependency fails loudly rather than
silently.
For a platform service with no shared mock yet (most of them — only `db` has one so far), stub the
global inline in the test file:
```javascript
beforeEach(() => {
global.vEmailService = { enviarEmail: jest.fn() };
});
```
If the same stub keeps getting copy-pasted across test files, that's a signal to request it be added
to `@davinti/vitruvio-test-utils` upstream — don't duplicate it locally across many test files.
### Running
`npm test` must pass before scripts/endpoints work is considered done — same standing as
`vitruvio validate` for XML forms.
---
## E2E Testing
E2E tests run against a real Vitruvio instance spun up by `vitruvio test:start`. Use them for
**process workflows** — happy paths that cross multiple artifacts (BPMN → script → DB write) and are
too integrated to unit test meaningfully. Don't E2E test things that unit tests already cover
(pure script logic, single endpoint calls with mocked deps).
### Setup
Nenhuma configuração é necessária — `vitruvio test:start` funciona direto. Um usuário de
teste é gerado automaticamente a cada execução (veja `resolveE2ECredentials` em
`@davinti/vitruvio-test-utils`).
Se quiser usar um usuário fixo em vez do gerado automaticamente:
```bash
cp tests/.env.example tests/.env
# preencha TEST_USER e TEST_PASSWORD — nunca use admin
```
`tests/.env` é gitignored.
### Running
```bash
vitruvio test:start # spin up Vitruvio + Postgres + gitea-mock (Docker)
vitruvio test:sync # push current working tree and import into Vitruvio
npm run test:e2e # run tests in tests/*.e2e.js
vitruvio test:stop # tear down containers and volumes
```
`test:sync` pushes uncommitted changes — you don't need to commit before testing. Re-run it whenever
you change a process, form, script, or vitruvio.json.
### Writing a test
Tests live in `tests/*.e2e.js`. Use `VitruvioClient` from `@davinti/vitruvio-test-utils`:
```javascript
'use strict';
var { VitruvioClient } = require('@davinti/vitruvio-test-utils');
var client = new VitruvioClient();
beforeAll(async function() {
await client.login(process.env.TEST_USER, process.env.TEST_PASSWORD);
});
test('meu-processo: completa tarefa de aprovação', async function() {
var instance = await client.startProcess('meu-processo-key');
var task = await client.waitForTask('Task_aprovacao', { processInstanceId: instance.processInstanceId });
var data = await client.getTaskData(task.id);
await client.completeTask(task.id, Object.assign({}, data, { aprovado: true }));
});
```
**`global-setup.js` runs before every test suite** — it pings Vitruvio and seeds the test user. Your
test file just needs `beforeAll` for `client.login`.
### Prerequisites for a process to be testable
Two things must be true or `startProcess` returns 404:
1. **`forms.mobile` declared in `vitruvio.json`** — the REST API only exposes processes with a mobile
form entry. Point it at the desktop XML if there's no real mobile form:
```json
"forms": { "desktop": "processes/meu-processo/meu-processo-desktop.xml", "mobile": "processes/meu-processo/meu-processo-desktop.xml" }
```
2. **`candidateStarterGroups` must exist in nauth with `tag = 'vi_task_group'`** — Vitruvio filters
starter groups by that tag. Groups imported from `vitruvio.json` get it automatically. If you add a
new group just for a process, pass it in `global-setup.js` so `createTestUser` seeds it:
```javascript
await createTestUser(client, { login: login, password: password, groups: ['meu-grupo'] });
```
If `candidateStarterGroups` is absent from the BPMN, any authenticated user can start — fine for
tests but usually wrong for production.
### `VitruvioClient` API
| Method | What it does |
|--------|-------------|
| `login(user, pass)` | Authenticates and stores the JWT |
| `startProcess(key, data?)` | Starts a process instance; returns `{ processInstanceId, ... }` |
| `getTasks()` | Returns all tasks visible to the logged-in user |
| `waitForTask(taskKey, opts?)` | Polls until a task with that `taskDefinitionKey` appears; `opts`: `{ processInstanceId, timeout, interval }` |
| `waitForProcessEnd(instanceId, opts?)` | Polls until no tasks remain for the given process instance |
| `getTaskData(id)` | Returns the task's current form data |
| `completeTask(id, data?)` | Completes the task with the given form data |
| `callEndpoint(key, opts?)` | Calls a public integration endpoint |
`waitForTask` and `waitForProcessEnd` default to a 30 s timeout with 2 s polling.
### DB seed utilities
Available from `@davinti/vitruvio-test-utils` for use in `global-setup.js`:
**`deleteTestProcessInstances(pg, processKey)`** — deletes all instances of a process key from the
test DB (Activiti runtime + history + all Vitruvio child rows). Call it at the top of the setup
function so each run starts clean and `waitForTask` can't match a task from a previous run:
```javascript
await deleteTestProcessInstances(client, 'meu-processo-key');
await createTestUser(client, { login: login, password: password, groups: ['vi_user'] });
```
**`seedConexao(pg, options)`** — upserts a `conexao` row so scripts using `new db('my-key')` or
queries with `connection: 'my-key'` can resolve the datasource. Idempotent — safe to call on every
test run. Required fields: `siglaId`, `host`, `instancia`. Optional: `nome`, `porta` (default 5432),
`usuario`, `senha`, `plataforma` (default 2 = PostgreSQL), `aliases` (array of alias strings):
```javascript
await seedConexao(client, {
siglaId: 'minha-conexao',
host: process.env.TEST_DB_HOST || 'localhost',
porta: parseInt(process.env.TEST_DB_PORT || '15432'),
instancia: process.env.TEST_DB_NAME || 'vitruvio',
usuario: process.env.TEST_DB_USER || 'postgres',
senha: process.env.TEST_DB_PASSWORD || 'postgres',
});
```
---
## Vitruvio Platform Services
Injected objects available in script and endpoint contexts (e.g. `vEmailService`, `vProcessService`, `libService`, `vFileService`, `vLoginService`…).
**Before calling any service method, read its doc file:**
```
~/.local/share/vitruvio-platform/docs/services/<ServiceName>.md
```
Every injected service has its own file there — method signatures, parameters, return types, and examples. Do not guess method names or signatures; always check the file first.
---
## Developer Libraries
These are widely-used libraries already available in the platform environment:
| Library | Purpose |
| ------- | ------------------------------------------------------------------------------------- |
| `http` | HTTP request utilities (GET, POST, etc.) |
| `db` | Database query helpers — prefer named queries from `queries/` over raw SQL in scripts |
> Also not full list, there are more usable libs.
### `db` usage notes
- `db.query()` always returns a result wrapper object, **never `null`** — even with zero rows, you get a wrapper whose `.each()` simply doesn't invoke the callback. Don't null-check it, just call `.each()` directly:
```javascript
var rows = banco.query("SELECT ...", {});
rows.each(function(row) { ... });
```
`db.queryRow()` is the one that returns `null` when there are no rows — that one you do need to null-check.
- Values from `db.queryRow()` and `db.query()` row properties are Java objects. Use `==` / `!=` when comparing them to JavaScript primitives (follows from the Java interop rule above).
- The default datasource connection key is `"vitruvio_producao"`. Use it for `connection-key` in `sqlBuilderDataSource`/`freeQuery`, for `connection` in `vitruvio.json` queries, and when constructing `new db('vitruvio_producao')` in scripts. The string `"default"` does not resolve to anything and will cause errors.
---
## Menu
The `menu` section of `vitruvio.json` defines the navigation tree shown to users. It is a flat-ish array — nesting is done via `children` on `MENU` items.
```json
"menu": [
{
"key": "my-panel-item",
"name": "My Panel",
"order": 1,
"type": "PAINEL",
"panelKey": "my-panel-key",
"children": []
},
{
"key": "my-group",
"name": "My Group",
"icon": 61946,
"order": 2,
"type": "MENU",
"children": [
{
"key": "my-group/child-panel",
"name": "Child Panel",
"icon": 62030,
"order": 0,
"type": "PAINEL",
"panelKey": "child-panel-key",
"children": []
}
]
}
]
```
### Field reference
| Field | Required | Notes |
|-------|----------|-------|
| `key` | yes | Unique identifier. For children, use `Parent/Child` path convention |
| `name` | yes | Label shown in the UI |
| `type` | yes | `MENU` (group/submenu), `PAINEL` (panel), `RELATORIO` (report), `PROCESSO` (process) |
| `order` | yes | Display order within its level |
| `icon` | no | Numeric codepoint for the item icon |
| `panelKey` | for PAINEL | Must match the `key` of a registered panel |
| `children` | yes | Array of child items. Always include, even as `[]` on leaf items |
### Rules
- `MENU` items are containers only — they do not link to anything themselves, only their `children` do.
- Keys must be unique across the whole menu tree. Use the `Parent/Child` path convention for nested items to avoid collisions.
- `order` controls sorting within a level; gaps (0, 10, 20…) allow future insertions.
- **Never generate menu entries unless the user explicitly asks for it.** Creating a panel, process, or other artifact does not imply adding it to the menu.
---
## Global Platform Reference
A shared directory exists outside this repo with platform-wide resources. Always check it before writing new code.
| OS | Path |
|----|------|
| Linux | `~/.local/share/vitruvio-platform/` |
| Windows | `%LOCALAPPDATA%\vitruvio-platform\` |
### What's where and when to use it
| Folder | Contents | When to look here |
|--------|----------|-------------------|
| `libs/` | Core JS library files (`db`, `http`, `messages`, `vaadinComponents`, etc.) | Before implementing something that a platform lib likely already handles |
| `docs/services/` | One `.md` per injected Java service — method signatures, parameters, return types. Named by injection variable (e.g. `vProcessInstanceService.md`, `vEmailService.md`). | When you need to call a platform service and don't know what methods it exposes |
| `docs/java/` | Full exported JavaDocs (HTML). Package structure: `br/`, `com/`, `org/`. | When you need to `importClass(Packages....)` a Java class and want to know its API |
| `docs/components/desktop/` | 78 Vaadin component reference markdowns — attributes, events, usage examples. See `docs/components/INDEX.md` for the full list. | When working with any component and unsure of its attributes or behaviour. Read `docs/components/desktop/<ComponentName>.md` before using a component you're not certain about. |
| `docs/components/mobile/` | 18 mobile component reference markdowns. | When building mobile forms. |
| `examples/panels/` | Example panel XML forms | Before writing a panel from scratch |
| `examples/processes/` | Example BPMN + process XML forms | Before writing a process from scratch |
| `examples/scripts/` | Example library and task scripts | Before writing a script from scratch |
| `examples/endpoints/` | Example REST endpoints | Before writing an endpoint from scratch |
| `examples/reports/` | Example Jasper report configs | Before writing a report from scratch |
> Examples show how things generally work in the platform — treat them as functional reference, not necessarily best-practice code.
### Services vs. libs — key distinction
- **Services** (`docs/services/`) are Java objects injected directly into every script context. Call them by their variable name — no loading needed. Names follow the `vClassName` convention (e.g. `vProcessInstanceService`, `vEmailService`, `vConfigService`). Exceptions: `libService`, `engine`, `execution`.
- **Libs** (`libs/`) are JS files loaded on demand with `libService.loadScript('key')`. The `db` lib is the primary example — it wraps `ConexaoService` into a friendlier JS API.
---
## General Conventions
- **Named queries versus inline SQL.** Put SQL in `queries/*.sql` and reference by key if they will be reused only. Keeps SQL auditable and reusable across scripts/reports.
- **Group keys are shared contracts.** If a `group-key` is referenced across permission rules and menu items, treat it as a public API — dont change the key.
- **Patches are append-only.** `patches/` has `oracle/` and `postgresql/` subdirectories, each with its own Liquibase XML changelog. Never modify existing changesets — only append new ones. Keep both files in sync.
- **No BOM, ever.** All text artifacts (XML forms, BPMN, scripts, queries, reports) must be plain UTF-8 with no byte-order mark. A BOM breaks XML parsing at runtime and is invisible in most editors — `vitruvio validate` now flags it, but don't introduce it in the first place.
---
## Git Commits
Applies to every commit in this repo. Follow **Conventional Commits**
(`type(scope): summary`, e.g. `feat`, `fix`, `refactor`, `docs`, `chore`), in Brazilian Portuguese or
English. The body must give context on **what changed, why, and how** — not just restate the diff —
and reference the ticket number when one exists.
```
feat(compras): adiciona comprador específico na solicitação de compra
Permite abrir a solicitação para um comprador específico e filtra os
produtos por comprador no painel de precificação.
Ref: ticket 23917
```
## README
Check `README.md` whenever the repo looks near-complete or complete (most artifacts registered,
feature work wrapping up). If it's missing or still the `projeto-base` placeholder, warn the dev and
ask them to describe what the module does — then write/update the README covering: what it manages,
how it works, the most important files and how they interact, and any caveats worth flagging to a
future reader. Only write it once the dev has confirmed the description; don't invent one.
Vendored
+72
View File
@@ -0,0 +1,72 @@
pipeline {
agent {
docker {
image 'node:22-alpine'
// Mount the host Docker socket so docker compose works inside the container
args '-u root -v /var/run/docker.sock:/var/run/docker.sock'
}
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Install') {
steps {
withCredentials([string(credentialsId: 'gitea-pat', variable: 'GITEA_PAT')]) {
sh '''
apk add --no-cache docker-cli docker-cli-compose
npm config set @davinti:registry https://git.davinti.com.br/api/packages/davinTI/npm/
npm config set //git.davinti.com.br/api/packages/davinTI/npm/:_authToken ${GITEA_PAT}
npm ci
'''
}
}
}
stage('Test') {
steps {
sh 'npm test -- --passWithNoTests'
}
}
stage('E2E') {
steps {
withCredentials([
usernamePassword(credentialsId: 'gitea-docker-creds', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')
]) {
sh '''
echo "$DOCKER_PASS" | docker login git.davinti.com.br -u "$DOCKER_USER" --password-stdin
docker compose -f tests/docker-compose.test.yml up -d
for i in $(seq 1 36); do
wget -q -O /dev/null http://localhost:18080/rest/public/test && break
echo "Aguardando Vitruvio... ($i/36)"
sleep 5
done
npm run test:e2e
'''
}
}
post {
always {
sh 'docker compose -f tests/docker-compose.test.yml down -v || true'
}
}
}
}
post {
always {
cleanWs()
}
success {
echo 'Sucesso ✅'
}
failure {
echo 'Build failed.'
}
}
}
+290
View File
@@ -0,0 +1,290 @@
# Repositório de Conteúdo Vitruvio
Um repositório de conteúdo Vitruvio é um repositório Git que declara artefatos de UI (painéis, processos, scripts, relatórios, etc.) através de um único arquivo de manifesto: `vitruvio.json`.
## Arquivos obrigatórios
| Arquivo | Obrigatório | Descrição |
|---------|-------------|-----------|
| `vitruvio.json` | Sim | Manifesto que declara todos os artefatos |
Todo o restante (formulários, scripts, BPMNs, etc.) é referenciado via caminhos relativos dentro do manifesto e pode ser organizado livremente.
---
## vitruvio.json
Localizado na **raiz** do repositório. O sistema falhará ao importar o repositório se este arquivo estiver ausente ou inválido.
### Campos raiz
| Campo | Tipo | Obrigatório | Descrição |
|-------|------|-------------|-----------|
| `version` | string | **Sim** | Versão semântica deste manifesto, ex.: `"1.0.0"` |
| `metadata` | object | **Sim** | Identidade do módulo (ver abaixo) |
| `panels` | array | Não | Painéis de UI |
| `processes` | array | Não | Processos BPMN |
| `scripts` | array | Não | Scripts compartilhados |
| `endpoints` | array | Não | Endpoints REST |
| `queries` | array | Não | Queries SQL nomeadas |
| `reports` | array | Não | Relatórios (estáticos ou dinâmicos) |
| `libraries` | array | Não | Bibliotecas de arquivos estáticos |
| `groups` | array | Não | Grupos de usuários para controle de acesso |
| `properties` | array | Não | Propriedades configuráveis do módulo |
| `menu` | array | Não | Árvore de navegação do menu |
| `permissions` | array | Não | Permissões de grupo por processo |
| `patches` | string | Não | Caminho para o diretório de changesets do Liquibase, ex.: `"patches/"` |
### metadata
```json
{
"name": "My Module",
"key": "my-module",
"standardProduct": false
}
```
| Campo | Obrigatório | Descrição |
|-------|-------------|-----------|
| `key` | **Sim** | Identificador único do módulo. Deve ser único em toda a plataforma. |
| `name` | Não | Nome de exibição legível |
| `standardProduct` | Não | Marca como produto padrão da plataforma (padrão: `false`) |
---
## Referência de artefatos
### panels
Cada entrada de painel corresponde a uma tela de UI.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único do painel |
| `name` | string | Nome de exibição |
| `description` | string | Descrição curta |
| `category` | string | Caminho de categoria hierárquica, ex.: `"Comercial/Vendas"` |
| `displayOrder` | int | Ordem de exibição dentro da categoria |
| `showInPresentation` | boolean | Exibir no modo apresentação/kiosk |
| `openInNewWindow` | boolean | Abrir em uma nova janela do navegador |
| `showInMobileList` | boolean | Exibir na lista mobile |
| `displayTimeInSeconds` | int | Tempo de rotação automática (0 = desativado) |
| `allowedGroups` | string[] | Chaves dos grupos com permissão de visualizar este painel |
| `allowedUsers` | string[] | Logins dos usuários com permissão de visualizar este painel |
| `forms.desktop` | string | Caminho relativo para o XML do formulário desktop |
| `forms.mobile` | string | Caminho relativo para o XML do formulário mobile |
| `forms.mobileAlternative` | string | Caminho relativo para o XML do formulário mobile alternativo |
| `defaultState` | string | Caminho relativo para um arquivo JSON com o estado padrão do painel |
| `thumbnail` | string | Caminho relativo para uma imagem de miniatura |
---
### processes
Fluxos de trabalho baseados em BPMN.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único do processo |
| `name` | string | Nome de exibição |
| `description` | string | Descrição curta |
| `bpmn` | string | Caminho relativo para o arquivo XML do BPMN |
| `forms.desktop` | string | Caminho relativo para o XML do formulário desktop |
| `forms.mobile` | string | Caminho relativo para o XML do formulário mobile |
| `forms.mobileAlternative` | string | Caminho relativo para o XML do formulário mobile alternativo |
| `schedules` | array | Agendamentos de disparo automático (ver abaixo) |
**Tipos de gatilho para agendamento:**
```json
{ "type": "cron", "expression": "0 0 0 * * ?" }
{ "type": "simple", "intervalMs": 60000, "repeatCount": -1 }
```
`repeatCount: -1` significa repetir indefinidamente.
---
### scripts
Scripts server-side reutilizáveis.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único do script |
| `name` | string | Nome de exibição |
| `description` | string | Descrição curta |
| `language` | string | Linguagem do script, ex.: `"javascript"` |
| `domain` | string | Classificação: `"NEGOCIO"` ou `"SISTEMA"` |
| `source` | string | Caminho relativo para o arquivo do script |
| `documentation` | string | Caminho relativo para o arquivo de documentação opcional |
---
### endpoints
Endpoints HTTP expostos.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único do endpoint |
| `name` | string | Nome de exibição |
| `description` | string | Descrição curta |
| `language` | string | Linguagem do script, ex.: `"javascript"` |
| `authMode` | string | `"PUBLIC"`, `"MOBILE"` ou `"STATIC_TOKEN"` |
| `active` | boolean | Se o endpoint está ativo |
| `source` | string | Caminho relativo para o arquivo do script do endpoint |
---
### queries
Queries SQL nomeadas que podem ser referenciadas por relatórios.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único da query |
| `name` | string | Nome de exibição |
| `connection` | string | Chave da conexão com o banco de dados |
| `source` | string | Caminho relativo para o arquivo `.sql` |
---
### reports
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único do relatório |
| `name` | string | Nome de exibição |
| `description` | string | Descrição curta |
| `type` | string | `"MODELO_ESTATICO"` ou `"DINAMICO_QUERY_SQL"` |
| `category` | string | Caminho de categoria hierárquica |
| `owner` | string | Chave do grupo proprietário do relatório |
| `template` | string | Caminho relativo para o template `.jrxml` |
| `parameterForm` | string | Caminho relativo para o XML do formulário de parâmetros |
| `query` | string | Referencia uma `QueryManifestEntry.key` |
| `orientation` | string | `"RETRATO"` ou `"PAISAGEM"` |
| `allowedGroups` | string[] | Chaves dos grupos com permissão de executar este relatório |
| `allowedUsers` | string[] | Logins dos usuários com permissão de executar este relatório |
| `columns` | array | Definições de colunas (label, alinhamento, largura, agregação) |
**Valores de agregação de coluna:** `"SUM"`, `"COUNT"`, `"AVG"` ou `null`.
---
### libraries
Pacotes de arquivos estáticos servidos para o front-end.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único da biblioteca |
| `name` | string | Nome de exibição |
| `type` | string | `"LOCAL"` |
| `authMode` | string | `"PUBLIC"`, `"MOBILE"` ou `"STATIC_TOKEN"` |
| `authToken` | string | Obrigatório quando `authMode` for `"STATIC_TOKEN"` |
| `mobileEnabled` | boolean | Se a biblioteca é servida para clientes mobile |
| `files` | string | Caminho relativo para o diretório contendo os arquivos da biblioteca |
---
### groups
Grupos de usuários utilizados para controle de acesso em painéis, relatórios e permissões.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único do grupo |
| `name` | string | Nome de exibição |
| `description` | string | Descrição curta |
| `tag` | string | Tag opcional para filtragem |
---
### properties
Propriedades configuráveis no nível do módulo, editáveis em tempo de execução.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Chave da propriedade utilizada no código |
| `displayName` | string | Label legível |
| `description` | string | Descrição curta |
| `type` | string | `"STRING"`, `"INTEGER"`, `"BOOLEAN"`, `"DATE"`, etc. |
| `size` | int | Tamanho máximo em caracteres (para STRING) |
| `precision` | int | Precisão decimal (para tipos numéricos) |
| `format` | string | Máscara de formato opcional |
| `required` | boolean | Se um valor deve obrigatoriamente ser definido |
| `password` | boolean | Se o valor deve ser mascarado na UI |
| `predefinedValues` | array | Valores permitidos: `{ "key": "...", "descricao": "...", "ordem": 1 }` |
---
### menu
Árvore de navegação hierárquica. Itens podem ser aninhados usando `children`.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `key` | string | Identificador único do item de menu |
| `name` | string | Label de exibição |
| `icon` | int | Código do ícone (definido pela plataforma) |
| `order` | int | Ordem de exibição entre os irmãos |
| `type` | string | `"PANEL"`, `"REPORT"`, `"PROCESS"` ou `"GROUP"` |
| `panelKey` | string | Referencia uma `PanelManifestEntry.key` (quando type for `"PANEL"`) |
| `reportKey` | string | Referencia uma `ReportManifestEntry.key` (quando type for `"REPORT"`) |
| `processKey` | string | Referencia uma `ProcessManifestEntry.key` (quando type for `"PROCESS"`) |
| `children` | array | Itens de menu aninhados (para o tipo `"GROUP"`) |
---
### permissions
Define o que um grupo pode fazer dentro de um processo.
| Campo | Tipo | Descrição |
|-------|------|-----------|
| `processKey` | string | Referencia uma `ProcessManifestEntry.key` |
| `group` | string | Referencia uma `GroupManifestEntry.key` |
| `read` | boolean | Pode visualizar instâncias do processo |
| `writeStages` | boolean | Pode avançar/concluir etapas |
| `cancel` | boolean | Pode cancelar instâncias |
| `delete` | boolean | Pode excluir instâncias |
| `stageStatus` | boolean | Pode alterar o status de etapas |
---
## Estrutura de diretórios sugerida
```
repo-root/
├── vitruvio.json
├── panels/
│ └── my-panel/
│ ├── my-panel-desktop.xml
│ ├── my-panel-mobile.xml
│ ├── default-state.json
│ └── thumbnail.png
├── processes/
│ └── my-process/
│ ├── my-process.bpmn
│ ├── my-process-desktop.xml
│ └── my-process-mobile.xml
├── scripts/
│ └── my-script.js
├── endpoints/
│ └── my-endpoint.js
├── queries/
│ └── my-query.sql
├── reports/
│ └── my-report/
│ ├── template.jrxml
│ └── params.xml
├── libraries/
│ └── my-library/
└── patches/
└── changelog.xml
```
Todos os caminhos no `vitruvio.json` devem ser **relativos à raiz do repositório**.
+252
View File
@@ -0,0 +1,252 @@
# Endpoints
Endpoints are REST WebServices written in ES5 JavaScript, executed on the Rhino engine. Each file must export a single `WebService` instance. Vitruvio maps incoming HTTP requests to the corresponding handler method.
---
## vitruvio.json Registration
```json
{
"key": "my-endpoint",
"name": "My Endpoint",
"description": "What this endpoint does.",
"language": "javascript",
"authMode": "PUBLIC",
"active": true,
"source": "endpoints/my-endpoint.js"
}
```
- `key` — kebab-case, stable. Changing it breaks any external integrations pointing to the URL.
- `authMode` — one of `"PUBLIC"`, `"STATIC_TOKEN"`, `"VITRUVIO_WS_USER_AUTH"`, `"HTTP_BASIC_AUTH"`. Default to `"PUBLIC"`.
- `active` — must be `true` for the endpoint to be reachable. Set to `false` to disable without removing.
### URL structure
The `authMode` determines the URL segment used to reach the endpoint:
| authMode | URL |
|---|---|
| `PUBLIC` | `/api/integration/public/{key}` |
| `STATIC_TOKEN` | `/api/integration/tokenauth/{key}` |
| `VITRUVIO_WS_USER_AUTH` | `/api/integration/bearerauth/{key}` |
| `HTTP_BASIC_AUTH` | `/api/integration/bauth/{key}` |
### STATIC_TOKEN authentication
The token can be sent in either of two ways:
- Query parameter: `_x_token_auth=<token>`
- Request header: `X-WS-TOKEN-AUTH: <token>`
---
## File Structure
```javascript
function WebService() {
this.onGet = function(params) { ... }
this.onPost = function(params) { ... }
this.onPut = function(params) { ... }
this.onPatch = function(params) { ... }
this.onDelete = function(params) { ... }
}
module.exports = new WebService();
```
- All methods are **optional**. Only implement the verbs your endpoint actually handles.
- Remove unused verb stubs entirely — don't leave placeholder `return JSON.stringify({ prop: value })` bodies in production code.
- `module.exports` must be `new WebService()` — Vitruvio instantiates and reads from this exported object.
- If a caller sends a verb that is not implemented in the script, Vitruvio throws `EndpointMethodNotImplementedException` — it is not a silent 404.
---
## The `params` Object
Each handler receives a single `params` argument:
```javascript
// GET / DELETE
params = {
headers: {}, // request headers as key/value
query: {} // query string params as key/value e.g. params.query["hub.mode"]
}
// POST / PUT / PATCH
params = {
requestBody: '', // raw request body string — always JSON.parse() before use
headers: {},
query: {}
}
```
**Always parse the body explicitly:**
```javascript
this.onPost = function(params) {
var body = JSON.parse(params.requestBody);
// use body.*
}
```
---
## Return Values
| Return value | HTTP response |
|---|---|
| A non-empty string | `200 OK` with that string as body |
| `null`, no return, or empty string `""` | `204 No Content` |
| `JSON.stringify(obj)` | `200 OK` with JSON body — set Content-Type accordingly |
Always return strings. Returning a raw object will not serialize correctly.
```javascript
// Correct
return JSON.stringify({ status: 'ok', data: result });
// Wrong — object will not be serialized properly
return { status: 'ok' };
```
---
## Error Handling
Wrap handler logic in `try/catch` and return a JSON error body. Don't let uncaught exceptions bubble up.
```javascript
this.onPost = function(params) {
try {
var body = JSON.parse(params.requestBody);
if (!body.id) throw "Missing required field: id";
// ...
return JSON.stringify({ success: true });
} catch (e) {
return JSON.stringify({ error: e.toString() });
}
};
```
- Use `e.toString()` when serializing caught exceptions — raw exception objects may not stringify correctly under Rhino.
- Validate required fields early and throw descriptive messages.
---
## DELETE Convention
By convention, DELETE requests should not receive a body and should not return data. If nothing is returned, Vitruvio responds with `204 No Content`.
```javascript
this.onDelete = function(params) {
// perform deletion
// no return needed — 204 is implied
}
```
---
## Loading Scripts and Libraries
Use `libService.loadScript(key)` to load scripts registered in `vitruvio.json`. The `key` is the script's `key` field in the manifest.
```javascript
// Load inside the handler — safe, always fresh
this.onPost = function(params) {
var lib = libService.loadScript('my-lib');
lib.doSomething();
}
// Load in the constructor — shared across all calls to this endpoint instance
function WebService() {
var lib = libService.loadScript('my-lib');
this.onPost = function(params) {
lib.doSomething();
}
}
```
**Prefer constructor-level loading** when the same library is used in multiple handlers — avoids redundant loads. Use handler-level loading when the script key is dynamic or determined at runtime.
---
## Database Access
Load the `db` library and instantiate with a datasource name:
```javascript
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE); // default Vitruvio datasource
// or
var banco = new db('named-datasource');
```
Common operations:
```javascript
// Single row
var row = banco.queryRow("SELECT NAME FROM TB_CONFIG WHERE ID = :id", { id: 1 });
// Multiple rows
var rows = banco.query("SELECT * FROM TB_ITEMS WHERE ACTIVE = 1");
// Update / Insert
banco.update("UPDATE TB_ITEMS SET STATUS = :status WHERE ID = :id", {
status: 'DONE',
id: itemId
});
```
- Prefer named queries from `queries/*.sql` for reusable or complex SQL. Use inline SQL only for simple, endpoint-specific queries.
- Use named bind parameters (`:paramName`) — never concatenate user input into SQL strings.
---
## Logged User (Authenticated Modes)
For `VITRUVIO_WS_USER_AUTH` and `HTTP_BASIC_AUTH`, Vitruvio resolves the caller's identity and injects a `loggedUser` object into the script context. This gives the endpoint access to who is making the call.
`HTTP_BASIC_AUTH` additionally validates that the authenticated user belongs to the roles configured for that endpoint in Vitruvio's admin.
---
## Common Patterns
### Webhook with verification (e.g. Meta/WhatsApp)
```javascript
this.onGet = function(params) {
var mode = params.query["hub.mode"];
var token = params.query["hub.verify_token"];
var challenge = params.query["hub.challenge"];
if (mode === "subscribe" && token === EXPECTED_TOKEN) {
return challenge;
}
return JSON.stringify({ error: "Forbidden" });
};
```
### Delegating to a script
```javascript
this.onPost = function(params) {
try {
var body = JSON.parse(params.requestBody);
var lib = libService.loadScript('my-processing-script');
var result = lib.run(body);
return JSON.stringify(result);
} catch (e) {
return JSON.stringify({ error: e.toString() });
}
};
```
---
## What NOT to do
- Don't leave placeholder bodies (`return JSON.stringify({ prop: value })`) in unused verb stubs — remove the whole method instead.
- Don't return raw exception objects — always call `.toString()` or wrap in a message.
- Don't hardcode datasource names or tokens in the script — read them from `vConfigService` or from a DB config table.
- Don't put heavy business logic directly in the endpoint file — delegate to a script via `libService.loadScript`.
+3
View File
@@ -0,0 +1,3 @@
module.exports = {
preset: '@davinti/vitruvio-test-utils',
};
+7
View File
@@ -0,0 +1,7 @@
'use strict';
module.exports = {
testMatch: ['<rootDir>/tests/**/*.e2e.js'],
globalSetup: './tests/global-setup.js',
testTimeout: 60000,
};
+3876
View File
File diff suppressed because it is too large Load Diff
+17
View File
@@ -0,0 +1,17 @@
{
"name": "projeto-base",
"private": true,
"scripts": {
"test": "jest",
"test:e2e": "jest --config jest.e2e.config.js",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@davinti/vitruvio-core-libs": "latest",
"@davinti/vitruvio-test-utils": "latest",
"@types/jest": "^29.5.14",
"jest": "^29",
"pg": "^8.0.0",
"typescript": "^5.0.0"
}
}
+673
View File
@@ -0,0 +1,673 @@
# Panels
Panels are UI screens defined entirely in XML. Vitruvio parses the XML against its XSD schema and renders Vaadin 7 components through a Java presenter layer — no Java UI code lives here.
---
## vitruvio.json Registration
```json
{
"key": "my-panel",
"name": "My Panel",
"description": "Short description.",
"category": "Category/Subcategory",
"displayOrder": 10,
"showInPresentation": false,
"openInNewWindow": false,
"showInMobileList": false,
"displayTimeInSeconds": 0,
"allowedGroups": ["group-key"],
"allowedUsers": [],
"forms": {
"desktop": "panels/my-panel/my-panel-desktop.xml",
"mobile": "panels/my-panel/my-panel-mobile.xml"
}
}
```
- `displayOrder` — use gaps (10, 20, 30…) to allow future insertions.
- `forms.mobile` is optional. Omit if there is no mobile form.
- `allowedGroups` and `allowedUsers` are additive — a user in any allowed group or listed directly gets access.
---
## Desktop Form — File Structure
```xml
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/panel
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-panel-form.xsd">
<form formKey="uniqueFormKey" width="100%" height="100%">
<name>Human-readable title</name>
<description>Short description.</description>
<initScript language="JavaScript">
<![CDATA[
function run() {
// called when the panel loads
}
]]>
</initScript>
<components>
<!-- root layout component -->
</components>
</form>
</panel-form>
```
- `formKey` must be unique within the panel. By convention it matches the panel key.
- `<initScript>` runs once when the panel is opened. The `run()` function is the entry point.
- Everything in `<components>` is the Vaadin component tree.
---
## Mobile Form — File Structure
Mobile forms use a different namespace and XSD, and support a `<ServerSide>` block for server-executed bridge functions.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<panel-form xmlns="http://www.davinti.com.br/vitruvio/form/mobile/panel"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.davinti.com.br/vitruvio/form/mobile/panel
https://bitbucket.org/davinTI/vitruvio-xds/raw/master/vitruvio-mobile-panel-form.xsd">
<form formKey="uniqueFormKey">
<name>Title</name>
<description></description>
<initScript language="JavaScript">
<![CDATA[
function run() { ... }
]]>
</initScript>
<ServerSide>
<DataSources>
<SQLBuilderScriptDataSource id="dsName" connection-key="datasource-key"
language="JavaScript" workOnline="true">
<![CDATA[
function buildSQL(requestParams, sql_params) {
return "SELECT ...";
}
]]>
</SQLBuilderScriptDataSource>
</DataSources>
<Bridges>
<Bridge id="myBridge" language="JavaScript">
<![CDATA[
function execute(params) {
// runs on the server — full access to libService, db, etc.
return { result: ... };
}
]]>
</Bridge>
</Bridges>
</ServerSide>
<components>
<!-- mobile component tree -->
</components>
</form>
</panel-form>
```
### Calling bridges from mobile client scripts
```javascript
vCommunicationService.executeOnServer('myBridge', params).then(function(result) {
// handle result
}).catch(function(error) {
console.log('Error: ' + error);
});
```
### Mobile timers
```javascript
engine.startTimer('timerName', function() {
// runs every intervalMs
}, intervalMs);
engine.stopTimer('timerName');
```
---
## The `engine` API (Desktop)
`engine` is injected into all script contexts inside a panel.
### Fields
```javascript
var field = engine.getField('fieldId');
field.getValue(); // returns null when empty; for DB-backed fields returns a Java type — use == not === when comparing
field.setValue(value);
field.clear();
field.setEnabled(true/false);
field.setVisible(true/false);
field.setRequired(true/false);
field.focus();
field.refresh(); // DB components — re-runs the datasource query
```
### Layouts
```javascript
var layout = engine.getLayout('layoutId');
layout.getRootComposition().setVisible(true/false);
layout.getRootComposition().setCaption('New caption');
layout.getRootComposition().removeAllComponents();
layout.getRootComposition().addComponent(component);
layout.getRootComposition().setScrollTop(100000); // scroll to bottom
// WindowLayout only
layout.showWindow();
layout.closeWindow();
```
### User & Session
```javascript
var user = engine.getLoggedUser();
user.getLogin(); // username string
user.getNome(); // display name (desktop)
```
### Global variables — sharing state across components
```javascript
// Set in initScript or any handler
engine.setGlobalVariable('myHelper', function() { ... });
engine.setGlobalVariable('myData', { key: 'value' });
// Read anywhere in the form
engine.getGlobalVariable('myHelper')();
var data = engine.getGlobalVariable('myData');
// Clean up
engine.unsetGlobalVariable('myVar');
```
Use global variables to expose reusable functions (e.g. `limparCampos`, `carregarDados`) and shared state across components and events.
### Form validation
```javascript
if (engine.isValid()) {
// all required fields are filled
}
```
### Repeating timers (desktop polling)
```javascript
var timer = engine.registerRepeatingTimer(3000, function() {
// do something every 3s
// return true to keep running, false/undefined to stop
});
engine.setGlobalVariable('myTimer', timer);
// Later, to stop it:
engine.getGlobalVariable('myTimer').setEnabled(false);
engine.unsetGlobalVariable('myTimer');
```
---
## Script Blocks and Events
### initScript
Runs when the panel loads. Define helper functions here and expose them via `setGlobalVariable` so other components can call them.
```xml
<initScript language="JavaScript">
<![CDATA[
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
function carregarDados() { ... }
function run() {
engine.setGlobalVariable('carregarDados', carregarDados);
carregarDados();
}
]]>
</initScript>
```
### Button click
```xml
<ButtonWidget id="btnSalvar" caption="Salvar" defaultIcon="SAVE" style="GREEN">
<onClickScript language="JavaScript">
<![CDATA[
function run() {
// handle click
}
]]>
</onClickScript>
</ButtonWidget>
```
### Field value change
```xml
<TextField id="txfPesquisa" type="string" caption="Pesquisar" immediate="true">
<events>
<valueChange>
<script language="JavaScript">
<![CDATA[
function run() {
engine.getField('myTable').refresh();
}
]]>
</script>
</valueChange>
</events>
</TextField>
```
`immediate="true"` is required for `valueChange` to fire on every keystroke.
---
## Layout Components
### VerticalLayout / HorizontalLayout
```xml
<VerticalLayout width="100%" height="100%" spacing="true" margin="true"
id="myLayout" align="TOP_LEFT" expandRatio="1" visible="true"
backgroundColor="#f8b916">
```
**Sizing fields inside `layouts`:** always prefer `width="100%"` combined with `expandRatio` instead of fixed pixel widths. `expandRatio` controls the proportional share of the available space each child receives.
```xml
<HorizontalLayout width="100%" spacing="true">
<NumericField id="nmfId" width="100%" expandRatio="1" ... />
<TextField id="txfNome" width="100%" expandRatio="3" ... />
<ButtonWidget id="btnAcao" width="100%" expandRatio="1" ... />
</HorizontalLayout>
```
### Panel
A styled container with an optional caption and background color.
```xml
<Panel width="100%" height="100%" caption="Section title" backgroundColor="#f0f0f0" margin="true">
<VerticalLayout>...</VerticalLayout>
</Panel>
```
### CrudPanel
The standard page chrome: header bar + section content area.
> Validators will reject it as a root element. Always wrap it in a Layout element like `VerticalLayout`, `HorizontalLayout`, `TabLayout`, or `AbsoluteLayout` as the root component inside `<components>`.
```xml
<VerticalLayout width="100%" height="100%">
<CrudPanel width="100%" height="100%" expandRatio="1">
<Header>
<Caption>Page Title</Caption>
<SubCaption>Subtitle text</SubCaption>
</Header>
<Section caption="Tab label" subCaption="..." showHeader="false" width="100%" height="100%">
<VerticalLayout width="100%" height="100%">
<!-- content — Section requires a layout component (VerticalLayout, HorizontalLayout, etc.) as its direct child, not bare components -->
</VerticalLayout>
</Section>
</CrudPanel>
</VerticalLayout>
```
### TabLayout
```xml
<TabLayout width="100%">
<Tab caption="Tab 1">
<!-- content -->
</Tab>
<Tab caption="Tab 2">
<!-- content -->
</Tab>
</TabLayout>
```
### ScrollPanel
```xml
<ScrollPanel id="scrollArea" width="100%" height="100%">
<VerticalLayout id="content" width="100%">
<!-- scrollable content -->
</VerticalLayout>
</ScrollPanel>
```
### WindowLayout (modal)
```xml
<WindowLayout id="myModal" windowHeight="40%" windowWidth="40%"
windowResizable="false" windowClosable="false" windowModal="true">
<VerticalLayout width="100%" height="100%" margin="true">
<!-- modal content -->
</VerticalLayout>
</WindowLayout>
```
Open/close via `engine.getLayout('myModal').showWindow()` / `.closeWindow()`.
---
## Input Components
### Common attributes
| Attribute | Purpose |
| ------------- | ---------------------------------------------- |
| `id` | Required for `engine.getField()` access |
| `caption` | Label above the component |
| `width` | CSS width (`100%`, `200px`) |
| `expandRatio` | Flex grow ratio within parent layout |
| `visible` | `true`/`false` |
| `required` | Marks field as required for `engine.isValid()` |
| `enabled` | `true`/`false` |
| `description` | Tooltip text |
| `immediate` | `true` to fire events on every change |
| `maxLength` | Max character count (TextField, TextArea) |
### TextField / TextArea / NumericField / DateField
```xml
<TextField id="txfNome" type="string" caption="Nome" width="100%" required="true" maxLength="100" />
<TextArea id="txaDescricao" type="string" caption="Descrição" width="100%" />
<NumericField id="nmfCodigo" type="number" caption="Código" width="100%" enabled="false" />
<DateField id="dtInicio" type="date" caption="Data Início" resolution="DAY" width="100%" />
```
### Label (HTML content)
```xml
<Label id="lblInfo" contentMode="HTML" align="MIDDLE_CENTER">
<value>
<![CDATA[
<b style="font-size: 16px;">HTML content here</b>
]]>
</value>
</Label>
```
### ButtonWidget
```xml
<ButtonWidget id="btnAcao" caption="Label" defaultIcon="SAVE"
style="GREEN" width="100%" height="30px" expandRatio="1" align="MIDDLE_RIGHT"
keyCode="ENTER">
```
`style` values: `BLUE`, `RED`, `GREEN`, `GRAY`
`defaultIcon` values: `SAVE`, `ADD`, `EDIT`, `REMOVE`, `TRASH`, `SEARCH`, `BACK`, `SEND`, `ARROW_UP`, `ARROW_DOWN`
---
## DB Components
### DBTable
The primary data grid. Fetches data from a SQL datasource.
```xml
<DBTable id="dbtItems" type="number" immediate="true" width="100%"
height="100%" expandRatio="1" caption="Items"
showRowCount="true" drawRefreshButton="true" selectable="false"
rows="10" multivalue="false" exportXLS="true" useGridComponent="true">
<datasource>
<sqlBuilderDataSource connection-key="vitruvio" language="JavaScript">
<![CDATA[
function buildSQL(params) {
// params is a Map<String, Object> — put values here and reference with ${name} in SQL.
// Read form fields via engine (guard with if(engine) — datasource may run before form init).
var search = engine ? engine.getField('txfPesquisa').getValue() : null;
var searchValue = (search && String(search) != '') ? '%' + String(search) + '%' : '%';
params.put('search', searchValue);
return "SELECT * FROM MY_TABLE WHERE upper(NAME) LIKE upper(${search}) ORDER BY NAME";
}
]]>
</sqlBuilderDataSource>
</datasource>
<key-field>ID</key-field>
<columns>
<column name="ID" caption="Código" expand-ratio=".1" />
<column name="NAME" caption="Nome" expand-ratio=".6" />
<generated name="edit" caption="Editar" expand-ratio=".1">
<!-- see Generated Columns below -->
</generated>
</columns>
<bind>
<parameter value-type="string" defaultValue="" parameterName="parFilter" field-ref="txfFilter" />
</bind>
<events>
<valueChange>
<script language="JavaScript">
<![CDATA[
function run() {
var id = engine.getField('dbtItems').getValue();
// react to row selection
}
]]>
</script>
</valueChange>
</events>
<styleGenerator>
<scriptGenerator language="JavaScript">
<![CDATA[
function getStyle() {
return 'background-light-gray';
}
]]>
</scriptGenerator>
</styleGenerator>
</DBTable>
```
**Datasource variants:**
- `<sqlBuilderDataSource>` — dynamic SQL built in JavaScript; `engine` is available inside
- `<freeQuery connection-key="...">` — static SQL with `${paramName}` tokens bound via `<bind>`
- `<form-datasource ref="dsId" />` — reference a `<DataSources>` definition (mobile)
**Parameter syntax — important distinction:**
- `sqlBuilderDataSource` and `freeQuery`: use `${paramName}` in SQL, bound via `params.put()` or `<bind>`
- `db` library scripts (inline SQL): use `:paramName` syntax instead
**Note:** In `sqlBuilderDataSource`, always guard with `if (engine)` before calling `engine.getField(...)` because the datasource may be evaluated before the form is fully initialised.
### Generated Columns (action buttons in table rows)
```javascript
var vc = libService.loadScript('vaadinComponents');
function Generator() {
this.generate = function(itemId, columnId, item, container) {
var btn = vc.buttonIcon('Editar', function() {
var nome = item.getItemProperty('NAME').getValue();
engine.getField('txfNome').setValue(nome);
}, 'edit');
return btn;
}
}
var script = new Generator();
```
For delete with confirmation dialog:
```javascript
var vc = libService.loadScript('vaadinComponents');
importClass(Packages.br.com.davinti.base.vaadin.components.layout.ConfirmationBox);
function Generator() {
this.generate = function(itemId, columnId, item, container) {
var btn = vc.buttonIcon('Deletar', function() {
var listener = new ConfirmationBox.ConfirmationBoxListener() {
dialogEnd: function(context, action) {
if (action == ConfirmationBox.Action.YES) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
banco.update("DELETE FROM MY_TABLE WHERE ID = :id", { id: Number(itemId) });
engine.getField('dbtItems').refresh();
}
}
};
ConfirmationBox.show(
ConfirmationBox.DialogIcon.WARNING,
'Confirmar exclusão',
'Deseja excluir o item ' + item.getItemProperty('NAME').getValue() + '?',
listener,
ConfirmationBox.ACTION_YES_CANCEL
);
}, 'trash');
return btn;
}
}
var script = new Generator();
```
### DBTwinColSelect (multi-select from DB)
Multi-select widget populated from a query.
**Reading selected values:** `getValue()` on a `multivalue="true"` DBTwinColSelect returns a Java `Collection`, not a JS array. Use the Java iterator API:
```javascript
var selected = engine.getField('dbtwItems').getValue();
if (selected != null) {
var iter = selected.iterator();
while (iter.hasNext()) {
var item = String(iter.next());
// process item
}
}
```
**Setting values programmatically:** use a Java `ArrayList`:
```javascript
var itens = new java.util.ArrayList();
banco.query(sql, params).each(function(row) {
itens.add(java.lang.Long.valueOf(row.ID));
});
engine.getField('dbtwItems').setValue(itens);
```
### DBComboBox
Single-select dropdown from a query. Use `.getValue()` / `.setValue(val)` / `.clear()`.
---
## Feedback to the User
### MessageBox (desktop)
```javascript
importClass(Packages.br.com.davinti.base.vaadin.components.layout.MessageBox);
MessageBox.show(MessageBox.BoxType.SUCESS, 'Sucesso', 'Saved.');
MessageBox.show(MessageBox.BoxType.ERROR, 'Erro', 'Something failed.');
MessageBox.show(MessageBox.BoxType.WARNING, 'Atenção', 'Please check...');
```
Note: `BoxType.SUCESS` has no second S — that's the platform's spelling.
### Tray notification (non-blocking)
```javascript
var messages = libService.loadScript('messages');
var n = messages.notification;
n.show({
caption: 'Saved',
msg: 'Data saved successfully.',
delay: 3,
type: n.type.tray
});
```
### MessageBox (mobile)
The mobile API is different:
```javascript
MessageBox.show('Title', 'Message text');
MessageBox.showLoading('Loading...');
MessageBox.hideLoading();
MessageBox.confirm('Message', 'Title', [
{ text: 'Sim', handler: function() { ... } },
{ text: 'Não', handler: function() { ... } }
]);
```
---
## Loading Scripts and Libraries
```javascript
// In initScript (runs once, available to all handlers via globalVariable)
var libIA = libService.loadScript('IA_lib');
engine.setGlobalVariable('libIA', libIA);
// Inside a button or event handler
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
```
The `vaadinComponents` lib is used heavily in generated columns to create dynamic Vaadin components programmatically (buttons, layouts, labels, etc.).
---
## Database in Panels
Same as in endpoints — see the root CLAUDE.md for `db` usage notes. Key extras for panels:
```javascript
banco.getSequenceNextVal('MY_SEQUENCE'); // get next ID before insert
banco.transaction(function() {
this.update(sql1, params1);
this.update(sql2, params2);
});
banco.isOracle(); // true if Oracle — use for DB-dialect differences (SYSDATE vs CURRENT_TIMESTAMP, etc.)
```
---
## Field ID Naming Conventions
Prefix field IDs by component type for readability:
| Prefix | Component |
| ------------- | --------------- |
| `txf` | TextField |
| `txa` | TextArea |
| `ncf` / `nmf` | NumericField |
| `dbt` | DBTable |
| `dbb` | DBComboBox |
| `dbtw` | DBTwinColSelect |
| `btn` | ButtonWidget |
| `chk` | CheckBox |
| `lbl` | Label |
| `dt` | DateField |
| `cmb` | ComboBox |
---
## Common Patterns
### Show/hide navigation between views
Use multiple layouts within the same form and toggle visibility:
```javascript
engine.getLayout('layoutForm').getRootComposition().setVisible(true);
engine.getLayout('layoutList').getRootComposition().setVisible(false);
engine.getLayout('sectionHeader').getRootComposition().setCaption('New Caption');
```
### CRUD in a single form
The recurring pattern across panels:
1. **List view** (`layoutHome`) — DBTable + search field + "Add" button
2. **Form view** (`layoutAddItem`) — fields + Save + Back buttons, initially `visible="false"`
3. **initScript** exposes a `limparCampos` global that clears all form fields
4. **Edit** button in DBTable populates fields and switches to form view
5. **Save** checks `engine.isValid()`, upserts based on whether a key field has a value, then returns to list
6. **Back** clears fields and returns to list
### Reading system config
```javascript
var valor = vConfigService.getSystemConfigAsString('CONFIG_KEY');
```
### Getting the logged user in a script
```javascript
engine.getLoggedUser().getLogin() // desktop
engine.getLoggedUser().getNome() // desktop display name
engine.getLoggedUser().getName() // mobile display name
```
+54
View File
@@ -0,0 +1,54 @@
# Patches
Liquibase database migration changelogs. Two subdirectories — one per supported database:
```
patches/
oracle/
<timestamp>_<KEY>.xml
postgresql/
<timestamp>_<KEY>.xml
```
The filename convention is `{YYYYMMDDHHmm}_{MODULE_KEY}.xml` (e.g. `202604081221_GO.xml`).
Both files must always be kept in sync — every changeset must appear in both.
## Rules
- **Append-only.** Never edit or delete existing `<changeSet>` entries. Liquibase tracks executed changesets by `id` + `author`; modifying them breaks the checksum and will fail deployment. You can change conditions and order tho.
- **Unique IDs.** Each `<changeSet id="...">` must have a unique numeric ID within the file. Increment from the last existing one.
- **Use preConditions.** Wrap DDL in `<preConditions onFail="MARK_RAN">` guards so the changelog is idempotent and safe to re-run.
- **Oracle vs PostgreSQL syntax differs.** Sequences, data types, and quoting rules are different — write each file for its target DB. Don't copy-paste blindly between them.
## File skeleton
```xml
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="your.name" id="401" objectQuotingStrategy="LEGACY">
<preConditions onError="WARN" onFail="MARK_RAN" onSqlOutput="IGNORE">
<not>
<tableExists tableName="my_table" />
</not>
</preConditions>
<sql endDelimiter=";" splitStatements="true" stripComments="false">
CREATE TABLE my_table ( ... );
</sql>
</changeSet>
</databaseChangeLog>
```
## vitruvio.json registration
```json
"patches": [
{ "path": "patches" }
]
```
+17
View File
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd">
<changeSet author="nome.usuario" id="id_unico_changeset">
<preConditions onError="WARN" onFail="MARK_RAN" onSqlOutput="IGNORE">
<not>
<tableExists tableName="go_setor" />
</not>
</preConditions>
<sql endDelimiter=";" splitStatements="true" stripComments="false">CREATE TABLE tabela ()</sql>
</changeSet>
</databaseChangeLog>
+166
View File
@@ -0,0 +1,166 @@
# Processes
BPMN workflows powered by Activiti. Each process lives in its own subdirectory.
```
processes/
<process-key>/
<process-key>.bpmn
<process-key>-desktop.xml
<process-key>-mobile.xml # optional when process involves mobile tasks.
<process-key>-mobile-alt.xml # optional when the client uses two versions of the mobile with different APIs. User should point the need for that one out.
```
The XML form rules, components, engine API, and JS constraints are the same as panels — see `panels/CLAUDE.md`. This file covers only the differences.
---
## vitruvio.json registration
```json
"processes": [
{
"key": "my-process",
"name": "My Process",
"bpmn": "processes/my-process/my-process.bpmn",
"forms": {
"desktop": "processes/my-process/my-process-desktop.xml",
"mobile": null,
"mobileAlternative": null
},
"schedules": [
{
"name": "Daily trigger",
"trigger": { "type": "cron", "expression": "0 0 0 * * ?" }
}
]
}
]
```
`schedules` is optional. Trigger types: `"cron"` (Quartz expression) or `"simple"` (`intervalMs` + `repeatCount: -1` for indefinite).
---
## Form XML — key differences from panels
The root element is `<forms>` (not `<form>`), with a `processKey` attribute matching the BPMN process id:
```xml
<forms xmlns="http://www.davinti.com.br/vitruvio/form"
xmlns:xsi="..."
xsi:schemaLocation="..."
processKey="my-process">
<library>
<!-- reusable complex-component definitions -->
</library>
<form formKey="formAbertura" width="100%">
<!-- components for the start event / opening task -->
</form>
<form formKey="formAprovacao" width="100%">
<!-- components for approval task -->
</form>
</forms>
```
### `<library>`
Define reusable `<complex-component>` blocks here and reference them by id in task forms. Avoids repeating the same layout across multiple task forms.
### `<form formKey="...">`
Each `<form>` corresponds to one BPMN user task (via `activiti:formKey`). The keys must match exactly. One process can have many task forms in the same XML file.
---
## BPMN — key attributes
### User tasks
```xml
<bpmn2:userTask id="Task_Approve" name="Approve"
activiti:formKey="formAprovacao"
activiti:candidateGroups="group-key"
activiti:assignee="${someVar}">
```
- `activiti:formKey` — must match a `<form formKey="...">` in the desktop/mobile XML.
- `activiti:candidateGroups` — group key(s) that can claim this task.
- `activiti:assignee` — specific user expression (optional, overrides candidate groups).
### Start event
```xml
<bpmn2:startEvent id="start" activiti:formKey="formAbertura" activiti:initiator="owner">
```
`activiti:initiator="owner"` stores the login of whoever opened the process into the `owner` process variable.
### Sequence flow conditions
Conditions reference process variables using JUEL syntax:
```xml
<bpmn2:conditionExpression xsi:type="bpmn2:tFormalExpression">
#{formAbertura_aprovado=='1'}
</bpmn2:conditionExpression>
```
Process variables are set by the form engine automatically following the convention `{formKey}_{fieldId}` when a task is completed. They can also be set explicitly in script tasks via the `execution` object.
### Script tasks
```xml
<bpmn2:scriptTask id="myScript" name="Do something" scriptFormat="javascript">
<bpmn2:script>
var lib = vScriptService.loadScript('my-lib', 'javascript');
var obj = new lib.MyClass();
obj.doWork(execution);
</bpmn2:script>
</bpmn2:scriptTask>
```
- Use `vScriptService.loadScript(key, 'javascript')` — **not** `libService.loadScript()`.
- `execution` is the Activiti execution context. Use it to read/write process variables:
```javascript
execution.getVariable('myVar');
execution.setVariable('myVar', value);
```
### Task listeners
Listeners run on task lifecycle events (`create`, `complete`, `assignment`):
```xml
<activiti:taskListener class="org.activiti.engine.impl.bpmn.listener.ScriptTaskListener" event="create">
<activiti:field name="language"><activiti:string>javascript</activiti:string></activiti:field>
<activiti:field name="script">
<activiti:string>
var lib = vScriptService.loadScript('my-lib', 'javascript');
lib.onTaskCreate(task);
</activiti:string>
</activiti:field>
</activiti:taskListener>
```
`task` is available in listeners (not `execution`). Use `task.getExecution()` if you need the execution context.
---
## Engine extras in process forms
These are available in addition to the standard panel engine API:
| Method | Description |
| ------------------------- | ---------------------------------------------------------------------- |
| `engine.formKey()` | Returns the `formKey` of the currently active task form |
| `engine.isTaskComplete()` | Returns `true` if the task has already been completed (read-only view) |
Use `engine.formKey()` to share `initScript` logic across multiple task forms with different initialization paths:
```javascript
function run() {
if (engine.formKey() == 'formAbertura') {
// init for opening form
} else if (engine.formKey() == 'formAprovacao') {
// init for approval form
}
}
```
+25
View File
@@ -0,0 +1,25 @@
# Queries
Named SQL queries registered in `vitruvio.json`. Plain `.sql` files — one query per file.
Used as datasources for reports, DBTable components, and referenced by key in scripts via the `db` lib.
## Rules
- One `SELECT` per file. No multiple statements, no DDL.
- Named bind parameters use `:paramName` syntax (same as inline SQL in scripts).
- Write ANSI SQL where possible. If DB-specific syntax is unavoidable, keep both Oracle and PostgreSQL in mind — or note the limitation.
- Queries should be read-only. For INSERT/UPDATE/DELETE, use inline SQL in scripts.
## vitruvio.json registration
```json
"queries": [
{
"key": "my-query",
"name": "My Query",
"source": "queries/my-query.sql",
"connection": "vitruvio_producao"
}
]
```
+33
View File
@@ -0,0 +1,33 @@
# Reports
Jasper Reports templates. Each report lives directly under `reports/` (flat — no subdirectory per report).
## Files per report
| File | Required | Purpose |
|------|----------|---------|
| `<key>.jrxml` | yes | Jasper report template |
| `<key>-params.xml` | no | Vitruvio form shown to the user before running the report (parameter input) |
The `-params.xml` file follows the same XML/XSD form schema as panel forms — same components, same engine API, same JS rules.
## Version constraint
Reports must be compatible with **JasperReports 6.21.2**. Use **Jaspersoft Studio 6.21.2** to design them — do not save with a newer version, as the generated XML will reference a newer schema and fail to compile on the server.
## vitruvio.json registration
```json
"reports": [
{
"key": "my-report",
"name": "My Report",
"queryKey": "my-query",
"template": "reports/my-report.jrxml",
"paramsForm": "reports/my-report-params.xml"
}
]
```
- `queryKey` references a key from `queries/` — the report's main datasource.
- `paramsForm` is optional; omit if the report takes no parameters.
+66
View File
@@ -0,0 +1,66 @@
# Scripts
Reusable JS logic loaded via `libService.loadScript('script-key')`. All scripts run on **Mozilla Rhino (ES5)** — the same engine and constraints as endpoints and panel inline scripts. See root CLAUDE.md for the full ES5 rules.
## What's NOT available in scripts
- No `engine` object (that's panel/process-only).
- No `params` request object (that's endpoint-only).
- No browser or Node globals (`window`, `require`, `process`, etc.).
Services like `vProcessInstanceService`, `vConfigService`, and libs like `db`, `http` are available and injected the same way as everywhere else.
## Two patterns
### Process / task scripts — top-level execution
Scripts triggered directly by a process task or scheduler. Code runs at the top level, no export needed.
```javascript
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
var result = banco.queryForList("SELECT ...", {});
// do work...
```
### Library scripts — object literal export
Reusable libs meant to be loaded by other scripts, endpoints, or panels. Wrap everything in a `({...})` object literal — this is the value returned by `libService.loadScript()`.
```javascript
({
versao: 1.0,
nome: 'My Library',
myFunction: function(params) {
var db = libService.loadScript('db');
var banco = new db(db.VITRUVIO_DATASOURCE);
// ...
return result;
}
})
```
The caller then uses it as:
```javascript
var myLib = libService.loadScript('my-lib');
myLib.myFunction({ foo: 'bar' });
```
## vitruvio.json registration
```json
"scripts": [
{
"key": "my-lib",
"name": "My Library",
"path": "scripts/my-lib.js",
"domain": "USUARIO"
}
]
```
`domain` is either `"SISTEMA"` (system-level) or `"USUARIO"` (user-level). Default to `"USUARIO"`.
+33
View File
@@ -0,0 +1,33 @@
# Credenciais do usuário de teste — opcional, copie para .env e preencha
# se quiser usar um usuário fixo. Se deixado em branco, um usuário de teste
# é gerado automaticamente a cada execução (veja resolveE2ECredentials).
# O arquivo .env está no .gitignore e NUNCA deve ser commitado
TEST_USER=
TEST_PASSWORD=
# URL do Vitruvio em execução. Padrão: porta do docker-compose de testes.
# Aponte para sua instância local se ela já estiver no ar.
TEST_VITRUVIO_URL=http://localhost:18080
# Conexão que o global-setup usa para criar o usuário de teste no banco.
TEST_DB_HOST=localhost
TEST_DB_PORT=15432
TEST_DB_USER=postgres
TEST_DB_PASSWORD=postgres
TEST_DB_NAME=vitruvio
# Para apontar o container Vitruvio a um banco externo em vez do postgres
# do docker-compose, preencha as variáveis abaixo. Use host.docker.internal
# para referenciar o localhost do host de dentro do container.
# Suba apenas o Vitruvio e o gitea-mock com:
# vitruvio test:start --no-db
#
# TYPE_DATABASE=postgresql
# DRIVER_CLASS_NAME=org.postgresql.Driver
# URL_VITRUVIO=jdbc:postgresql://host.docker.internal:5432/meu_banco
# URL_NAUTH=jdbc:postgresql://host.docker.internal:5432/meu_banco
# USERNAME_VITRUVIO=meu_usuario
# PASSWORD_VITRUVIO=minha_senha
# USERNAME_NAUTH=meu_usuario
# PASSWORD_NAUTH=minha_senha
+76
View File
@@ -0,0 +1,76 @@
services:
postgres:
image: postgres:15
environment:
POSTGRES_DB: vitruvio
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
TZ: America/Sao_Paulo
volumes:
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "15432:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres -d vitruvio"]
interval: 5s
timeout: 5s
retries: 10
start_period: 10s
vitruvio:
image: git.davinti.com.br/davinti/vitruvio-test:latest
depends_on:
postgres:
condition: service_healthy
volumes:
- git-repos:/repos
ports:
- "18080:8080"
environment:
TYPE_DATABASE: ${TYPE_DATABASE:-postgresql}
DRIVER_CLASS_NAME: ${DRIVER_CLASS_NAME:-org.postgresql.Driver}
URL_VITRUVIO: ${URL_VITRUVIO:-jdbc:postgresql://postgres:5432/vitruvio}
URL_NAUTH: ${URL_NAUTH:-jdbc:postgresql://postgres:5432/vitruvio}
USERNAME_VITRUVIO: ${USERNAME_VITRUVIO:-postgres}
PASSWORD_VITRUVIO: ${PASSWORD_VITRUVIO:-postgres}
USERNAME_NAUTH: ${USERNAME_NAUTH:-postgres}
PASSWORD_NAUTH: ${PASSWORD_NAUTH:-postgres}
VITRUVIO_QUARTZ_ENABLED: "false"
VITRUVIO_PATH: ROOT
HOST_JMX_PORT: 19020
TZ: America/Sao_Paulo
DB_POOL_INITIAL_SIZE: 1
DB_POOL_MAX_IDLE: 2
DB_POOL_MAX_TOTAL: 10
DB_POOL_MIN_IDLE: 1
EMAIL_HOST: localhost
EMAIL_PORT: "25"
EMAIL_DEFAULT_FROM: test@test.local
EMAIL_DEFAULT_FROM_NAME: Vitruvio Test
EMAIL_USERNAME: test
EMAIL_PASSWORD: test
EMAIL_USE_TLS: "false"
gitea-mock:
image: alpine/git
entrypoint:
- sh
- -c
- |
git config --global --add safe.directory /project &&
git config --global --add safe.directory /project/.git &&
mkdir -p /repos &&
git clone --bare /project /repos/project.git &&
sleep infinity
volumes:
- ..:/project:ro
- git-repos:/repos
healthcheck:
test: ["CMD-SHELL", "test -f /repos/project.git/HEAD"]
interval: 3s
timeout: 3s
retries: 10
start_period: 15s
volumes:
git-repos:
+3
View File
@@ -0,0 +1,3 @@
'use strict';
module.exports = require('@davinti/vitruvio-test-utils').globalSetup;
+1
View File
@@ -0,0 +1 @@
CREATE SCHEMA IF NOT EXISTS nauth;
+24
View File
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES5",
"lib": ["ES5"],
"module": "None",
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": false,
"noImplicitAny": false,
"skipLibCheck": true
},
"include": [
"scripts/**/*.js",
"endpoints/**/*.js",
"panels/**/*.js",
"processes/**/*.js",
"queries/**/*.js",
"reports/**/*.js",
"libraries/**/*.js",
"types/**/*.d.ts"
],
"exclude": ["node_modules", "tests"]
}
+85
View File
@@ -0,0 +1,85 @@
// Ambient declarations for the Rhino (ES5) script execution environment.
// These globals are injected by Vitruvio — they are NOT available in Node.js.
// The Jest stubs in vitruvio-test-utils/setup.js mirror these for unit tests.
// Java interop (Rhino built-ins)
declare var java: any;
declare var Packages: any;
// Script output variable — set this at the end of a script to return a value
declare var result: string;
// Print to the script console
declare function println(s: any): void;
declare var vLogger: {
info(msg: string): void;
warn(msg: string): void;
error(msg: string): void;
debug(msg: string): void;
};
declare var libService: {
loadScript(name: string): any;
};
declare var vProcessInstanceService: {
criarInstancia(user: string, processKey: string): any;
adicionarAnexo(processInstanceId: string, descricao: string, arquivo: any, file: any): void;
salvarFormularioCompletandoTarefa(taskId: string, data: any): void;
salvarFormulario(taskId: string, data: any): void;
completarTarefa(taskId: string): void;
cancelarProcesso(instanceId: number): void;
cancelarProcessoDefinitivamente(instanceId: number): void;
deletarProcesso(instanceId: number): void;
reativarProcesso(instanceId: number): void;
getInstanceByBPMNProcessInstanceId(bpmnId: string): any;
getInstanceById(id: number): any;
getInstanceStatusByBPMNProcessInstanceId(bpmnId: string): any;
updateInstanceDescription(instanceId: number, description: string): void;
updateInstanceDueDate(instanceId: number, date: any): void;
updateInstancePriority(instanceId: number, priority: number): void;
atualizarStatus(instanceId: number, status: string): void;
definirMarcador(instanceId: number, marker: string): void;
definirEmpresa(instanceId: number, companyId: number): void;
setTaskAssignee(taskId: string, userId: number): void;
claimTask(taskId: string, userId: number): void;
};
declare var vFileService: {
createTemporaryFile(): any;
createTemporaryDirectory(): any;
writeTemporaryFile(content: any): any;
persistFile(file: any, metadata: any): any;
updateFile(fileId: number, file: any): any;
getFile(fileId: number): any;
getMetadataById(fileId: number): any;
createStream(fileId: number): any;
deleteFile(fileId: number): void;
openSession(): any;
commit(session: any): void;
rollback(session: any): void;
};
declare var vEmailService: {
send(to: string, subject: string, body: string): void;
sendError(subject: string, body: string): void;
sendToUser(userId: number, subject: string, body: string): void;
};
declare var vConfigService: {
getSystemConfigAsString(key: string): string | null;
getSystemConfigAsLong(key: string): number | null;
getSystemConfigAsDouble(key: string): number | null;
getSystemConfigAsBoolean(key: string): boolean | null;
setSystemConfig(key: string, value: any): void;
getUserConfig(userId: number, key: string): string | null;
setUserConfig(userId: number, key: string, value: any): void;
getInstanceConfig(instanceId: number, key: string): string | null;
setInstanceConfig(instanceId: number, key: string, value: any): void;
};
declare var vPanelService: {
getPanel(key: string): any;
getPanelData(key: string, params: any): any;
};
+36
View File
@@ -0,0 +1,36 @@
{
"_comment": "vitruvio.json — Arquivo de manifesto para um repositório de conteúdo Vitruvio.",
"_comment_version": "'version' e 'metadata.key' são OBRIGATÓRIOS. Todas as outras seções são opcionais.",
"$schema": "./.vitruvio/vitruvio-manifest-schema.json",
"version": "1.0.0",
"metadata": {
"name": "Meu Módulo",
"key": "my-module",
"standardProduct": false
},
"panels": [
{
"key": "my-panel",
"name": "Meu Painel",
"description": "Descrição curta do que este painel faz.",
"category": "Categoria/Subcategoria",
"displayOrder": 1,
"showInPresentation": false,
"openInNewWindow": false,
"showInMobileList": false,
"displayTimeInSeconds": 0,
"allowedGroups": [
"group-key-1"
],
"allowedUsers": [],
"forms": {
"desktop": "panels/my-panel/my-panel-desktop.xml",
"mobile": "panels/my-panel/my-panel-mobile.xml",
"mobileAlternative": null
},
"defaultState": "panels/my-panel/default-state.json",
"thumbnail": "panels/my-panel/thumbnail.png"
}
],
"patches": "patches/"
}