@controleonline/ui-common 1.2.87 → 1.2.88

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/.github/workflows/integration-source-gate.yml +68 -0
  2. package/.github/workflows/manager-worker.yml +7 -156
  3. package/.github/workflows/publish-npm.yml +73 -27
  4. package/.github/workflows/pull-request-checks.yml +7 -5
  5. package/docs/technical/Device-Detail-Alias-UI-Sync.md +141 -0
  6. package/package.json +13 -8
  7. package/src/react/components/BottomNavigationBar.js +178 -174
  8. package/src/react/components/BottomNavigationBar.styles.js +139 -118
  9. package/src/react/components/DefaultProvider.native.js +427 -421
  10. package/src/react/components/DefaultProvider.web.js +131 -125
  11. package/src/react/components/RemoteCheckoutService.js +3 -3
  12. package/src/react/components/RuntimeFooterMarqueeText.js +88 -36
  13. package/src/react/components/RuntimeInfoFooter.js +76 -43
  14. package/src/react/components/RuntimeInfoFooter.styles.js +22 -14
  15. package/src/react/config/deviceConfigBootstrap.js +3 -3
  16. package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +4 -22
  17. package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +6 -2
  18. package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +4 -0
  19. package/src/react/pages/Devices/detail/DeviceDetailScreen.js +23 -338
  20. package/src/react/pages/Devices/detail/createDeviceDetailContext.js +20 -0
  21. package/src/react/pages/Devices/detail/useDeviceDetailActions.js +11 -36
  22. package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +129 -37
  23. package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +11 -38
  24. package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +9 -28
  25. package/src/react/pages/IntegrationConfigFields.js +1 -0
  26. package/src/react/pages/IntegrationConfigPage.js +101 -45
  27. package/src/react/pages/IntegrationConfigPage.utils.js +21 -13
  28. package/src/react/pages/fetchPeopleConfigs.js +21 -0
  29. package/src/react/pages/integrationsCatalog.js +15 -110
  30. package/src/react/services/Cielo/Cielo.js +9 -9
  31. package/src/react/services/Getnet/Getnet.web.js +25 -0
  32. package/src/react/utils/companyAuthority.js +36 -0
  33. package/src/react/utils/fileUrl.js +14 -5
  34. package/src/react/utils/paymentDevices.js +375 -270
  35. package/src/react/utils/printerDevices.js +2 -2
  36. package/src/react/utils/runtimeFooter.js +178 -224
  37. package/src/react/utils/runtimeFooterText.js +160 -0
  38. package/src/react/utils/runtimeLanguage.js +5 -5
  39. package/src/react/utils/runtimeZoom.js +29 -5
  40. package/src/react/utils/shopFranchises.js +167 -12
  41. package/src/react/utils/storeColumns.js +235 -217
  42. package/src/react/utils/systemErrorMessage.js +2 -1
  43. package/src/store/integration/index.js +43 -0
  44. package/src/tests/browser/manager/device-detail-apply-config-defined.spec.js +31 -0
  45. package/src/tests/browser/manager/device-detail-composition.spec.js +295 -0
  46. package/src/tests/browser/manager/device-detail-copy-config.spec.js +11 -1
  47. package/src/tests/browser/manager/device-detail-pdv-current-company.spec.js +273 -0
  48. package/src/tests/browser/manager/runtime-footer-rotation.spec.js +397 -0
  49. package/src/tests/react/components/DefaultProviderAuthority.test.js +28 -0
  50. package/src/tests/react/components/RuntimeFooterMarqueeText.build.test.js +29 -0
  51. package/src/tests/react/config/deviceConfigBootstrap.test.js +24 -1
  52. package/src/tests/react/pages/applyCurrentDeviceConfig.test.js +85 -0
  53. package/src/tests/react/pages/deviceDetailComposition.test.js +65 -0
  54. package/src/tests/react/pages/deviceDetailHookDependencies.test.mjs +31 -0
  55. package/src/tests/react/pages/deviceDetailLoaderDependencies.test.mjs +28 -0
  56. package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +27 -6
  57. package/src/tests/react/services/Cielo.test.js +2 -2
  58. package/src/tests/react/utils/companyAuthority.test.js +53 -0
  59. package/src/tests/react/utils/formatter.test.mjs +6 -0
  60. package/src/tests/react/utils/paymentDevices.test.js +72 -0
  61. package/src/tests/react/utils/runtimeFooter.test.js +35 -0
  62. package/src/tests/react/utils/runtimeFooterText.test.js +88 -0
  63. package/src/tests/react/utils/runtimeLanguage.test.js +4 -4
  64. package/src/tests/react/utils/runtimeZoom.test.js +20 -0
  65. package/src/tests/react/utils/shopFranchises.test.js +32 -13
  66. package/src/tests/react/utils/translate.test.mjs +6 -6
  67. package/src/utils/formatter.js +7 -3
  68. package/src/utils/translate.js +16 -16
  69. package/src/utils/translatePending.js +6 -6
  70. package/src/utils/translateStorage.js +2 -2
@@ -0,0 +1,68 @@
1
+ name: Integration source gate
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [dev, staging, master]
6
+ types: [opened, synchronize, reopened, edited, ready_for_review]
7
+
8
+ permissions:
9
+ contents: read
10
+ pull-requests: read
11
+
12
+ jobs:
13
+ source-policy:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Enforce task or frozen RC source
17
+ env:
18
+ HEAD_REF: ${{ github.head_ref }}
19
+ BASE_REF: ${{ github.base_ref }}
20
+ run: |
21
+ set -eu
22
+ if [ "$BASE_REF" = "dev" ]; then
23
+ echo "$HEAD_REF" | grep -Eq '^task-[1-9][0-9]*$' || {
24
+ echo "::error::dev only accepts task-<numeric issue>."
25
+ exit 1
26
+ }
27
+ exit 0
28
+ fi
29
+ echo "$HEAD_REF" | grep -Eq '^rc/[0-9]+\.[0-9]+\.[0-9]+-rc\.[1-9][0-9]*$' || {
30
+ echo "::error::$BASE_REF only accepts a frozen rc/X.Y.Z-rc.N source."
31
+ exit 1
32
+ }
33
+
34
+ - uses: actions/checkout@v4
35
+ if: github.base_ref != 'dev'
36
+ with:
37
+ ref: ${{ github.head_ref }}
38
+
39
+ - name: Validate frozen RC manifest
40
+ if: github.base_ref != 'dev'
41
+ env:
42
+ HEAD_REF: ${{ github.head_ref }}
43
+ run: |
44
+ set -eu
45
+ test -f .release/rc-manifest.json || {
46
+ echo "::error::Missing .release/rc-manifest.json"
47
+ exit 1
48
+ }
49
+ node --input-type=module <<'NODE'
50
+ import fs from 'node:fs';
51
+ const m = JSON.parse(fs.readFileSync('.release/rc-manifest.json', 'utf8'));
52
+ const branch = process.env.HEAD_REF;
53
+ const match = /^rc\/(\d+\.\d+\.\d+)-rc\.([1-9][0-9]*)$/.exec(branch);
54
+ if (!match) throw new Error('invalid RC branch');
55
+ if (m.branch !== branch || m.version !== match[1] || Number(m.rc) !== Number(match[2])) throw new Error('manifest identity mismatch');
56
+ if (m.frozen !== true) throw new Error('manifest is not frozen');
57
+ if (!Array.isArray(m.tasks) || m.tasks.length < 1 || m.tasks.length > 5) throw new Error('RC must contain 1..5 tasks');
58
+ const tasks = m.tasks.map(Number);
59
+ if (tasks.some((id) => !Number.isInteger(id) || id < 1)) throw new Error('invalid task id');
60
+ if (new Set(tasks).size !== tasks.length) throw new Error('duplicate RC tasks');
61
+ if (!/^[0-9a-f]{40}$/i.test(String(m.baseMaster || ''))) throw new Error('invalid baseMaster');
62
+ const repos = m.repositories || {};
63
+ if (!Object.keys(repos).length) throw new Error('empty repositories manifest');
64
+ for (const [repo, sha] of Object.entries(repos)) {
65
+ if (!/^ControleOnline\/[A-Za-z0-9_.-]+$/.test(repo) || !/^[0-9a-f]{40}$/i.test(String(sha))) throw new Error('invalid repository manifest entry');
66
+ }
67
+ console.log('Frozen RC manifest valid:', branch, tasks);
68
+ NODE
@@ -1,168 +1,19 @@
1
1
  name: Manager Worker
2
2
 
3
3
  on:
4
- push:
5
- branches: [master, dev, staging]
4
+ workflow_dispatch:
5
+
6
+ # PAUSA CTO ControleOnline/agents-mcp#201 (2026-08-29)
7
+ # Trigger original desativado: push em master, dev, staging.
6
8
 
7
9
  permissions:
8
- contents: write
10
+ contents: read
9
11
  issues: write
10
12
  pull-requests: write
11
13
 
12
- # Orquestrador: Manager resolve/cria issue e decide quais workers invocar.
13
- # Padrão de branch da org = master (não usar main).
14
- # Em master: bump patch + tag vX.Y.Z → dispara publish-npm.yml (Trusted Publishing).
15
14
  jobs:
16
15
  manager:
17
- name: Manager — resolve + plano de workers
18
- runs-on: ubuntu-latest
19
- outputs:
20
- issue_number: ${{ steps.mgr.outputs.issue_number }}
21
- created: ${{ steps.mgr.outputs.created }}
22
- run_qa: ${{ steps.mgr.outputs.run_qa }}
23
- run_security: ${{ steps.mgr.outputs.run_security }}
24
- run_docs: ${{ steps.mgr.outputs.run_docs }}
25
- run_gates: ${{ steps.mgr.outputs.run_gates }}
26
- base_branch: ${{ steps.mgr.outputs.base_branch }}
27
- steps:
28
- - uses: actions/checkout@v4
29
- with:
30
- fetch-depth: 0
31
-
32
- - name: Manager subworker
33
- id: mgr
34
- uses: ./.github/actions/workers/manager
35
- with:
36
- gh_token: ${{ secrets.GH_TOKEN }}
37
- branch: ${{ github.ref_name }}
38
-
39
- # Workers invocados pelo plano do Manager (paralelo quando ambos true)
40
- qa:
41
- name: QA (invocado pelo Manager)
42
- needs: manager
43
- if: needs.manager.outputs.run_qa == 'true'
44
- runs-on: ubuntu-latest
45
- steps:
46
- - uses: actions/checkout@v4
47
- - uses: ./.github/actions/workers/qa
48
- with:
49
- gh_token: ${{ secrets.GH_TOKEN }}
50
- issue_number: ${{ needs.manager.outputs.issue_number }}
51
- base_branch: ${{ needs.manager.outputs.base_branch }}
52
-
53
- security:
54
- name: Security (invocado pelo Manager)
55
- needs: manager
56
- if: needs.manager.outputs.run_security == 'true'
57
- runs-on: ubuntu-latest
58
- steps:
59
- - uses: actions/checkout@v4
60
- - uses: ./.github/actions/workers/security
61
- with:
62
- gh_token: ${{ secrets.GH_TOKEN }}
63
- issue_number: ${{ needs.manager.outputs.issue_number }}
64
- base_branch: ${{ needs.manager.outputs.base_branch }}
65
-
66
- technical-documenter:
67
- name: Technical Documenter (invocado pelo Manager)
68
- needs: manager
69
- if: needs.manager.outputs.run_docs == 'true'
70
- runs-on: ubuntu-latest
71
- steps:
72
- - uses: actions/checkout@v4
73
- - uses: ./.github/actions/workers/technical-documenter
74
- with:
75
- gh_token: ${{ secrets.GH_TOKEN }}
76
- issue_number: ${{ needs.manager.outputs.issue_number }}
77
- base_branch: ${{ needs.manager.outputs.base_branch }}
78
-
79
- # master: se faltar gate do quarteto → tags + workers faltantes
80
- master-gates:
81
- name: Master gates (quarteto)
82
- needs: manager
83
- if: needs.manager.outputs.run_gates == 'true'
84
- runs-on: ubuntu-latest
85
- outputs:
86
- need_qa: ${{ steps.check.outputs.need_qa }}
87
- need_security: ${{ steps.check.outputs.need_security }}
88
- need_docs: ${{ steps.check.outputs.need_docs }}
89
- steps:
90
- - uses: actions/checkout@v4
91
- - name: Inspect quarteto e tags
92
- id: check
93
- env:
94
- GH_TOKEN: ${{ secrets.GH_TOKEN }}
95
- ISSUE: ${{ needs.manager.outputs.issue_number }}
96
- REPO: ${{ github.repository }}
97
- run: |
98
- set -euo pipefail
99
- LABELS=$(gh issue view "$ISSUE" --repo "$REPO" --json labels --jq '[.labels[].name] | join(",")')
100
- echo "Labels: $LABELS"
101
-
102
- need_qa=false
103
- need_security=false
104
- need_docs=false
105
-
106
- echo "$LABELS" | grep -q 'qa:accepted' || need_qa=true
107
- echo "$LABELS" | grep -q 'security:accepted' || need_security=true
108
- echo "$LABELS" | grep -q 'agent:technical-documenter:done' || need_docs=true
109
-
110
- echo "need_qa=$need_qa" >> "$GITHUB_OUTPUT"
111
- echo "need_security=$need_security" >> "$GITHUB_OUTPUT"
112
- echo "need_docs=$need_docs" >> "$GITHUB_OUTPUT"
113
-
114
- [ "$need_qa" = true ] && gh issue edit "$ISSUE" --repo "$REPO" --add-label "agent:qa" || true
115
- [ "$need_security" = true ] && gh issue edit "$ISSUE" --repo "$REPO" --add-label "agent:security" || true
116
- [ "$need_docs" = true ] && gh issue edit "$ISSUE" --repo "$REPO" --add-label "agent:technical-documenter" || true
117
-
118
- gh issue comment "$ISSUE" --repo "$REPO" --body \
119
- "Master gates (Manager): need_qa=$need_qa need_security=$need_security need_docs=$need_docs"
120
-
121
- master-qa:
122
- name: QA (master gap via Manager)
123
- needs: [manager, master-gates]
124
- if: needs.master-gates.outputs.need_qa == 'true'
16
+ name: Manager — paused
125
17
  runs-on: ubuntu-latest
126
18
  steps:
127
- - uses: actions/checkout@v4
128
- - uses: ./.github/actions/workers/qa
129
- with:
130
- gh_token: ${{ secrets.GH_TOKEN }}
131
- issue_number: ${{ needs.manager.outputs.issue_number }}
132
- base_branch: master
133
-
134
- master-security:
135
- name: Security (master gap via Manager)
136
- needs: [manager, master-gates]
137
- if: needs.master-gates.outputs.need_security == 'true'
138
- runs-on: ubuntu-latest
139
- steps:
140
- - uses: actions/checkout@v4
141
- - uses: ./.github/actions/workers/security
142
- with:
143
- gh_token: ${{ secrets.GH_TOKEN }}
144
- issue_number: ${{ needs.manager.outputs.issue_number }}
145
- base_branch: master
146
-
147
- # Em push para master: bump patch + cria tag vX.Y.Z (dispara publish-npm.yml)
148
- # Ignora commits de release ([skip bump]) para evitar loop.
149
- version-tag:
150
- name: Bump version + tag
151
- if: github.ref == 'refs/heads/master' && !contains(github.event.head_commit.message, '[skip bump]')
152
- runs-on: ubuntu-latest
153
- steps:
154
- - uses: actions/checkout@v4
155
- with:
156
- fetch-depth: 0
157
- token: ${{ secrets.GH_TOKEN }}
158
-
159
- - uses: actions/setup-node@v4
160
- with:
161
- node-version: '24'
162
-
163
- - name: Bump patch + tag
164
- run: |
165
- git config user.name "github-actions[bot]"
166
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
167
- npm version patch -m "chore(release): %s [skip bump]"
168
- git push origin HEAD:master --follow-tags
19
+ - run: echo "Manager Worker paused by ControleOnline/agents-mcp#201"
@@ -2,44 +2,90 @@ name: Publish Package to npmjs
2
2
 
3
3
  on:
4
4
  push:
5
- tags:
6
- - 'v*'
7
5
 
8
6
  permissions:
7
+ contents: write
9
8
  id-token: write
10
- contents: read
9
+
10
+ concurrency:
11
+ group: ${{ github.repository }}-npm-release
12
+ cancel-in-progress: false
11
13
 
12
14
  jobs:
13
15
  publish:
16
+ if: ${{ github.repository_owner == 'ControleOnline' && (github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v')) }}
14
17
  runs-on: ubuntu-latest
15
- permissions:
16
- id-token: write
17
- contents: read
18
18
  steps:
19
19
  - uses: actions/checkout@v4
20
-
20
+ with:
21
+ ref: ${{ github.ref }}
22
+ fetch-depth: 0
23
+ - name: Validate package metadata and create version tag
24
+ id: release
25
+ env:
26
+ REF_TYPE: ${{ github.ref_type }}
27
+ REF_NAME: ${{ github.ref_name }}
28
+ EXPECTED_REPOSITORY: ${{ github.repository }}
29
+ run: |
30
+ node <<'NODE'
31
+ const fs = require('node:fs');
32
+ const cp = require('node:child_process');
33
+ const pkg = require('./package.json');
34
+ const expectedUrl = `https://github.com/${process.env.EXPECTED_REPOSITORY}`;
35
+ if (!/^\d+\.\d+\.\d+$/.test(String(pkg.version))) throw new Error(`Expected stable X.Y.Z, got ${pkg.version}`);
36
+ if (pkg.repository?.url !== expectedUrl) throw new Error(`repository.url must be ${expectedUrl}; got ${pkg.repository?.url}`);
37
+ const sourceSha = cp.execFileSync('git', ['rev-parse', 'HEAD'], {encoding:'utf8'}).trim();
38
+ let tag = '';
39
+ if (process.env.REF_TYPE === 'tag') {
40
+ tag = process.env.REF_NAME;
41
+ if (tag !== `v${pkg.version}`) throw new Error(`Tag ${tag} does not match package version ${pkg.version}`);
42
+ } else {
43
+ let previous = '';
44
+ try { previous = JSON.parse(cp.execFileSync('git', ['show', `${sourceSha}^:package.json`], {encoding:'utf8'})).version || ''; } catch {}
45
+ if (previous === pkg.version) { fs.appendFileSync(process.env.GITHUB_OUTPUT, 'release_tag=\n'); process.exit(0); }
46
+ const parts = String(pkg.version).split('.').map(Number);
47
+ if (previous) {
48
+ const old = String(previous).split('.').map(Number);
49
+ let cmp = 0; for (let i=0;i<3;i++) { if ((parts[i]||0)!==(old[i]||0)) { cmp=(parts[i]||0)>(old[i]||0)?1:-1; break; } }
50
+ if (cmp <= 0) throw new Error(`Version ${pkg.version} must be greater than first-parent version ${previous}`);
51
+ }
52
+ tag = `v${pkg.version}`;
53
+ const existing = cp.spawnSync('git', ['ls-remote', '--exit-code', '--refs', 'origin', `refs/tags/${tag}`], {encoding:'utf8'});
54
+ if (existing.status === 0) {
55
+ cp.execFileSync('git', ['fetch', 'origin', `refs/tags/${tag}:refs/tags/${tag}`], {stdio:'inherit'});
56
+ const tagged = cp.execFileSync('git', ['rev-parse', `${tag}^{commit}`], {encoding:'utf8'}).trim();
57
+ if (tagged !== sourceSha) throw new Error(`Immutable tag ${tag} already points at ${tagged}, not ${sourceSha}`);
58
+ } else {
59
+ cp.execFileSync('git', ['config', 'user.name', 'github-actions[bot]']);
60
+ cp.execFileSync('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com']);
61
+ cp.execFileSync('git', ['tag', '-a', tag, sourceSha, '-m', `Release ${pkg.name}@${pkg.version}`], {stdio:'inherit'});
62
+ cp.execFileSync('git', ['push', 'origin', `refs/tags/${tag}`], {stdio:'inherit'});
63
+ }
64
+ }
65
+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `release_tag=${tag}\n`);
66
+ console.log(`Validated ${pkg.name}@${pkg.version} from ${sourceSha}; release tag ${tag}`);
67
+ NODE
21
68
  - uses: actions/setup-node@v4
69
+ if: steps.release.outputs.release_tag != ''
22
70
  with:
23
- node-version: '24'
24
-
71
+ node-version: 24
25
72
  - name: Ensure npm 11.5.1+ (OIDC support)
73
+ if: steps.release.outputs.release_tag != ''
26
74
  run: |
27
- npm install -g npm@latest
28
- echo "node=$(node -v) npm=$(npm -v)"
29
-
30
- - name: Prepare registry (OIDC only — no classic token)
31
- run: |
32
- unset NODE_AUTH_TOKEN NPM_TOKEN || true
33
- rm -f .npmrc "$HOME/.npmrc" || true
34
- npm config delete //registry.npmjs.org/:_authToken 2>/dev/null || true
35
- npm config set registry https://registry.npmjs.org/
36
- echo "NODE_AUTH_TOKEN=${NODE_AUTH_TOKEN-<unset>}"
37
- echo "ACTIONS_ID_TOKEN_REQUEST_URL=${ACTIONS_ID_TOKEN_REQUEST_URL:+set}"
38
- echo "ACTIONS_ID_TOKEN_REQUEST_TOKEN=${ACTIONS_ID_TOKEN_REQUEST_TOKEN:+set}"
39
- npm config list 2>/dev/null | grep -E "registry|auth|token" || true
40
- cat "$HOME/.npmrc" 2>/dev/null || echo "(no ~/.npmrc)"
41
-
42
- - name: Publish to npmjs (Trusted Publishing / OIDC)
75
+ npm install --global npm@latest
76
+ npm --version
77
+ - name: Skip if version is already on npm
78
+ id: registry
79
+ if: steps.release.outputs.release_tag != ''
43
80
  run: |
44
- unset NODE_AUTH_TOKEN NPM_TOKEN || true
45
- npm publish --access public --loglevel verbose
81
+ PACKAGE_NAME=$(node -p "require('./package.json').name")
82
+ PACKAGE_VERSION=$(node -p "require('./package.json').version")
83
+ if npm view "$PACKAGE_NAME@$PACKAGE_VERSION" version >/dev/null 2>&1; then
84
+ echo 'published=true' >> "$GITHUB_OUTPUT"
85
+ echo "$PACKAGE_NAME@$PACKAGE_VERSION is already published; skipping duplicate publish"
86
+ else
87
+ echo 'published=false' >> "$GITHUB_OUTPUT"
88
+ fi
89
+ - name: Publish to npmjs with trusted publishing
90
+ if: steps.release.outputs.release_tag != '' && steps.registry.outputs.published != 'true'
91
+ run: npm publish --access public --loglevel verbose
@@ -12,8 +12,10 @@ jobs:
12
12
  steps:
13
13
  - uses: actions/checkout@v5
14
14
 
15
- - uses: actions/setup-node@v4
16
- with:
17
- node-version: '20.x'
18
-
19
- - run: npm test
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: '20.x'
18
+
19
+ - run: npm install --ignore-scripts --no-package-lock --no-audit --no-fund
20
+
21
+ - run: npm test
@@ -0,0 +1,141 @@
1
+ # Device Detail — sincronização do alias (nome) na UI após save
2
+
3
+ Documentação técnica da correção de feedback visual ao editar o **nome (alias)** do device na tela `/device-detail` (Manager).
4
+
5
+ **Issue de origem:** [app-community#382](https://github.com/ControleOnline/app-community/issues/382).
6
+
7
+ ## Problema de negócio
8
+
9
+ Na tela de detalhe de dispositivo (`/device-detail`):
10
+
11
+ 1. O usuário edita o **nome do device** e grava.
12
+ 2. A alteração **é persistida** no backend (API `devices`).
13
+ 3. A UI **não** refletia o novo nome até um **refresh** (F5) da página.
14
+
15
+ O feedback esperado é imediato: após save com sucesso, o valor exibido no header deve ser o novo alias **sem** reload.
16
+
17
+ ## Papel no produto (`APP_TYPE`)
18
+
19
+ | Visão | O que este fluxo faz | O que **não** faz |
20
+ | --- | --- | --- |
21
+ | **MANAGER** | Rota principal: lista Dispositivos → detalhe → editar alias → save; header atualiza na hora | Não altera permissões, tipos de device nem DeviceConfig de PDV/DISPLAY |
22
+ | **ADMIN** | Pode reutilizar a mesma página compartilhada de `ui-common` quando a visão importa `DeviceDetailPage` | Não é o alvo prioritário do smoke Manager |
23
+ | **POS / PPC / SHOP / DELIVERY / CRM** | Não expõem esta tela de edição de alias de device | Não devem assumir ownership da tela de device-detail |
24
+
25
+ Fronteiras: módulo compartilhado `ui-common`; rota consumida pelo **Manager** via `ui-manager`.
26
+
27
+ ## Onde vive o código
28
+
29
+ | Peça | Caminho |
30
+ | --- | --- |
31
+ | Shell da página | `ui-common` → `src/react/pages/DeviceDetailPage.js` (orquestra hooks/seções; ≤ 500 linhas após modularização) |
32
+ | Header (alias edit/save) | `src/react/pages/Devices/detail/DeviceDetailHeader.js` |
33
+ | Ações (inclui `saveAlias`) | `src/react/pages/Devices/detail/useDeviceDetailActions.js` |
34
+ | Estado local + ref de skip | `src/react/pages/Devices/detail/useDeviceDetailStateA.js` (ou estado co-localizado nos hooks de detail) |
35
+ | Merge store pós-save | `src/react/utils/deviceAliasSync.js` → `buildDeviceAliasStoreUpdates` |
36
+ | Smoke browser | `src/tests/browser/manager/device-detail-alias-save.spec.js` |
37
+ | Unit | `src/tests/react/utils/deviceAliasSync.test.js` |
38
+
39
+ Rota Manager: `ui-manager` importa `DeviceDetailPage` de `@controleonline/ui-common`.
40
+
41
+ ## Causa raiz (técnica)
42
+
43
+ Havia descompasso entre:
44
+
45
+ 1. **Persistência** (`deviceActions.save({ id, alias })`) — ok no backend.
46
+ 2. **Estado local** (`alias` / `aliasInput` no header).
47
+ 3. **Store runtime** (`device` e `device_config`), de onde `initialAlias` é derivado:
48
+
49
+ ```text
50
+ initialAlias = currentDevice?.alias
51
+ || currentDeviceConfig?.device?.alias
52
+ || currentDevice?.device
53
+ ```
54
+
55
+ Um `useEffect` sincroniza o estado local a partir de `initialAlias` quando **não** está em modo edição:
56
+
57
+ ```text
58
+ useEffect(() => {
59
+ if (editingAlias) return;
60
+ if (skipAliasSyncFromStoreRef.current) {
61
+ skipAliasSyncFromStoreRef.current = false;
62
+ return;
63
+ }
64
+ setAlias(initialAlias || '');
65
+ setAliasInput(initialAlias || '');
66
+ }, [editingAlias, initialAlias]);
67
+ ```
68
+
69
+ Se o save atualizava só o backend (ou só o state local) **sem** atualizar o store, na saída do modo edição o efeito reaplicava o `initialAlias` antigo → a UI “voltava” ao nome anterior até um reload completo recarregar o device do servidor.
70
+
71
+ ## Correção canônica
72
+
73
+ Após `deviceActions.save` com sucesso:
74
+
75
+ 1. Calcular o alias definitivo (`savedDevice?.alias || trimmed`).
76
+ 2. Chamar `buildDeviceAliasStoreUpdates({ deviceId, nextAlias, runtimeDevice, runtimeDeviceConfig, savedDevice, normalizeEntityId })`.
77
+ 3. Persistir no store:
78
+ - `deviceActions.setItem(mergedDevice)`
79
+ - `deviceConfigActions.setItem(nextDeviceConfig)` quando houver config aninhada com `device.alias`
80
+ 4. Armar `skipAliasSyncFromStoreRef.current = true` **antes** de atualizar o state local (evita race com o `useEffect` no mesmo ciclo).
81
+ 5. `setAlias(nextAlias)`, `setAliasInput(nextAlias)`, `setEditingAlias(false)`.
82
+
83
+ ### `buildDeviceAliasStoreUpdates`
84
+
85
+ Responsável por manter **device** e **device_config.device** coerentes com o alias gravado, para que `initialAlias` derivado do store não “snap back” para o valor antigo.
86
+
87
+ - Mescla o device runtime (mesmo id) com o payload salvo e força `alias`.
88
+ - Se existir `runtimeDeviceConfig`, clona e atualiza `device.alias` aninhado.
89
+
90
+ ## Fluxo (Mermaid)
91
+
92
+ ```mermaid
93
+ sequenceDiagram
94
+ participant U as Usuário (Manager)
95
+ participant H as DeviceDetailHeader
96
+ participant A as useDeviceDetailActions.saveAlias
97
+ participant API as deviceActions.save
98
+ participant S as Store (device + device_config)
99
+ participant E as useEffect (sync initialAlias)
100
+
101
+ U->>H: Edita alias + grava
102
+ H->>A: onSaveAlias
103
+ A->>API: save({ id, alias })
104
+ API-->>A: savedDevice
105
+ A->>S: setItem(mergedDevice / nextDeviceConfig)
106
+ A->>A: skipAliasSyncFromStoreRef = true
107
+ A->>H: setAlias + setAliasInput + exit edit
108
+ Note over E: effect roda com editingAlias=false
109
+ E->>E: vê skip=true → consome flag e não sobrescreve
110
+ H-->>U: Header mostra novo nome (sem F5)
111
+ ```
112
+
113
+ ## Critérios de aceite (referência)
114
+
115
+ - Em `/device-detail`, após editar o nome e gravar com sucesso, o nome no header passa a ser o novo valor **sem** refresh.
116
+ - Sem regressão em outros campos editáveis da mesma tela nem na listagem de dispositivos.
117
+ - Persistência no backend permanece correta.
118
+ - Evidência: smoke `device-detail-alias-save.spec.js` (edit → PUT → texto do header com `testID` `device-alias-text` / `device-alias-input` / `device-alias-save`).
119
+
120
+ ## testIDs (smoke)
121
+
122
+ | testID | Uso |
123
+ | --- | --- |
124
+ | `device-alias-text` | Texto do alias no header (modo leitura) |
125
+ | `device-alias-input` | Input em modo edição |
126
+ | `device-alias-edit` | Botão para entrar em edição |
127
+ | `device-alias-save` | Botão de confirmar save |
128
+
129
+ ## Fora de escopo desta página
130
+
131
+ - Redesign da tela device-detail.
132
+ - Novas funcionalidades de device além do feedback pós-save do alias.
133
+ - Contratos de API além do `save` de device já existente.
134
+ - Configuração de PDV/DISPLAY/PRINT (ver [Smoke helpers — device-configuracao](https://github.com/ControleOnline/app-community/wiki/Smoke-Helpers-Device-Configuracao)).
135
+
136
+ ## Links cruzados
137
+
138
+ - Issue: [app-community#382](https://github.com/ControleOnline/app-community/issues/382)
139
+ - Wiki do app: [Home](https://github.com/ControleOnline/app-community/wiki) · ponte [ui-common](https://github.com/ControleOnline/app-community/wiki/ui-common)
140
+ - Smoke de listagem/navegação relacionada: `ui-common/src/tests/browser/manager/devices-current.spec.js`
141
+ - Code quality / limite ≤ 500 linhas: [agents-mcp code-quality](https://github.com/ControleOnline/agents-mcp/blob/master/skills/shared/code-quality.md)
package/package.json CHANGED
@@ -1,30 +1,35 @@
1
1
  {
2
2
  "name": "@controleonline/ui-common",
3
- "version": "1.2.87",
3
+ "version": "1.2.88",
4
4
  "author": "ControleOnline",
5
5
  "description": "ControleOnline Common Quasar UI Component",
6
6
  "license": "MIT",
7
7
  "main": "src/react/index.js",
8
8
  "scripts": {
9
- "test": "npm run test:receita-federal && npm run test:imports-status && npm run test:runtime-language && npm run test:runtime-footer && npm run test:runtime-footer-marquee && npm run test:invoice-presentation && npm run test:system-error-message && npm run test:bottom-navigation-toolbar && npm run test:copy-device-configs && npm run test:print-routing",
9
+ "test": "npm run test:receita-federal && npm run test:imports-status && npm run test:runtime-language && npm run test:runtime-footer && npm run test:runtime-footer-marquee && npm run test:invoice-presentation && npm run test:system-error-message && npm run test:bottom-navigation-toolbar && npm run test:copy-device-configs && npm run test:print-routing && npm run test:device-detail-dependencies",
10
10
  "test:receita-federal": "node --test src/tests/react/pages/receitaFederalIntegrationContract.test.mjs",
11
- "test:imports-status": "mkdir -p node_modules/@controleonline && ln -sfn ../.. node_modules/@controleonline/ui-common && node --test src/tests/react/utils/importStatus.test.js",
11
+ "test:imports-status": "mkdir -p node_modules/@controleonline && rm -rf node_modules/@controleonline/ui-common && ln -s ../.. node_modules/@controleonline/ui-common && node --test src/tests/react/utils/importStatus.test.js",
12
12
  "test:runtime-language": "node --test src/tests/react/utils/runtimeLanguage.test.js",
13
- "test:runtime-footer": "node --test src/tests/react/utils/runtimeFooter.test.js",
13
+ "test:runtime-footer": "node --test src/tests/react/utils/runtimeFooter.test.js src/tests/react/utils/runtimeFooterText.test.js",
14
14
  "test:invoice-presentation": "node --test src/tests/react/utils/invoicePresentation.test.mjs",
15
- "test:system-error-message": "mkdir -p node_modules/@controleonline && ln -sfn ../.. node_modules/@controleonline/ui-common && node --test src/tests/react/utils/systemErrorMessage.test.js",
15
+ "test:system-error-message": "mkdir -p node_modules/@controleonline && rm -rf node_modules/@controleonline/ui-common && ln -s ../.. node_modules/@controleonline/ui-common && node --test src/tests/react/utils/systemErrorMessage.test.js",
16
16
  "test:coverage": "c8 --reporter=clover --report-dir coverage npm test",
17
17
  "test:bottom-navigation-toolbar": "node --test src/tests/react/utils/bottomNavigationToolbar.test.js",
18
18
  "test:copy-device-configs": "node --test src/tests/react/utils/copyDeviceConfigs.test.mjs",
19
19
  "test:print-routing": "node --test src/tests/react/utils/printRouting.test.mjs src/tests/react/utils/printSpoolUtils.test.mjs",
20
- "test:runtime-footer-marquee": "node --test src/tests/react/components/RuntimeFooterMarqueeText.test.js"
20
+ "test:runtime-footer-marquee": "node --test src/tests/react/components/RuntimeFooterMarqueeText.test.js src/tests/react/components/RuntimeFooterMarqueeText.build.test.js",
21
+ "test:device-detail-dependencies": "node --test src/tests/react/pages/deviceDetailLoaderDependencies.test.mjs"
21
22
  },
22
23
  "devDependencies": {
23
- "c8": "^10.1.3"
24
+ "c8": "^10.1.3",
25
+ "react": "^19.1.0",
26
+ "react-test-renderer": "^19.1.0",
27
+ "@babel/core": "^7.26.0",
28
+ "babel-preset-expo": "~54.0.10"
24
29
  },
25
30
  "repository": {
26
31
  "type": "git",
27
- "url": "https://github.com/ControleOnline/ui-common.git"
32
+ "url": "https://github.com/ControleOnline/ui-common"
28
33
  },
29
34
  "bugs": "",
30
35
  "homepage": "https://www.controleonline.com/devs/plugins",