Initial import of UI-Docs from zapier monorepo

This commit is contained in:
George Lambert 2026-09-11 19:55:03 -04:00
commit 1ed566665a
44 changed files with 2393 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
node_modules/

3
NATS.md Normal file
View file

@ -0,0 +1,3 @@
# NATS
UI-Docs is documentation only. It does not subscribe, publish, or request any `verae.*` subject.

24
README.md Normal file
View file

@ -0,0 +1,24 @@
# UI-Docs
Walkthrough screenshots, usage instructions, and a review PDF for every operator, staff, portal, admin, simulator, and catalog surface in the Verae Time × Zapier lab.
**Forgejo:** https://git.georgelambert.org/marchon/UI-Docs
## Open the review
- [UI-REVIEW.pdf](UI-REVIEW.pdf) — screenshots, how to use each screen, remaining issues
- [WALKTHROUGH.md](WALKTHROUGH.md) — click-by-click
- [REPORT.md](REPORT.md) — issues still to consider
- [screenshots/](screenshots/) — PNG atlas
## Recapture
Lab processes must already be listening (`:3850` fleet, `:3847` simulator, `:3011` CS, `:3012` sales, `:3013` accounting, `:3025` staff, `:3000` edge).
```bash
npm install
npm run walk # headless Chrome → screenshots/ + walkthrough-console.json
npm run pdf # reportlab → UI-REVIEW.pdf
```
Chrome path: `CHROME_PATH` or `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`.

35
REPORT.md Normal file
View file

@ -0,0 +1,35 @@
# UI issues still to consider
Follow-up to the 2026-09-11 review PDF. Every item from that list has shipped. There is nothing left to consider from the review.
## Resolved from the review list
| Severity | Issue | What shipped |
|----------|-------|----------------|
| High | Staff headings showed `cust_1` | Ledger stores names (`customer.put`). Staff UIs typeahead customers from edge (`GET /customers`) and review by name. |
| High | CS/sales/accounting HTML had no login | `verae-staff-session` :3027. Set `STAFF_AUTH=1` and `STAFF_SESSION_URL` on department servers. |
| Med | Catalog was a bibliography | Catalog index is cards (Start here / Architecture / Modules). |
| Med | Fleet identity field truncated | Two-row add-machine form; identity path is a wide field plus a filename picker. |
| Med | Disabled machines looked like faults | Intentionally disabled cards are grey (`disabled`), not degraded yellow. |
| Low | Portal statement empty cells in a table | Empty lists use the same card empty state as invoices. |
| Low | Overflow menus off-screen | Menu flips upward when near the bottom of the viewport. |
| Low | Simulator empty console was a black void | Placeholder: “Run a step to fill this trace.” |
| Med | Swagger `/docs` stock | Left as vendor Swagger. Title + banner: “OpenAPI explorer — stock Swagger UI for integrators.” Not skinned. |
| Low | access-staff and CS HTML twins | Shared template in `verae-staff-ui` (`review.html` + tokens). Both servers load it in the monorepo. |
| Low | Staff cookie host-scoped | `STAFF_COOKIE_DOMAIN` / `STAFF_COOKIE_SECURE` plus nginx example on `verae-staff-session`. |
## Still to consider
None. The review backlog is empty.
## How to use each surface
- **Operator** http://127.0.0.1:3850/ — Fleet (replicas, overflow, grey disabled machines), Trace, Docs.
- **Staff CS** :3011 — type a customer name, Review, credit in USD. `STAFF_AUTH=1` redirects to :3027.
- **Sales** :3012 — review / quote by name.
- **Accounting** :3013 — review + QuickBooks IIF / CSV.
- **Staff plane** :3025 — same review/credit after authz.
- **Customers** /portal — masked API key, empty invoice/statement cards.
- **Ops** /admin — customer list + edit drawer.
- **Lab** simulator :3847.
- **Catalog** https://zapier.georgelambert.org/ — card index, system-ui.

5
SUMMARY.md Normal file
View file

@ -0,0 +1,5 @@
# UI-Docs
Walkthrough screenshots, usage instructions, and a review PDF for every operator, staff, portal, admin, simulator, and catalog surface.
**Forgejo:** https://git.georgelambert.org/marchon/UI-Docs

821
UI-REVIEW.pdf Normal file

File diff suppressed because one or more lines are too long

127
WALKTHROUGH.md Normal file
View file

@ -0,0 +1,127 @@
# UI walkthrough (operator, staff, customers)
Captured 2026-09-11. Browser console: no application errors on the product UIs (favicon data-URI). Design tokens: `--accent #4f46e5`, `--bg #f4f5fb`, 12px radius, indigo header.
## 1. Operator console — Fleet
Open **http://127.0.0.1:3850/**.
![Fleet](screenshots/01-console-fleet.png)
1. Header kicker reads **operator LAN · binds 0.0.0.0:3850**. NATS stays private.
2. **Add machine:** pick roles as chips (`tree-node`, `archive-worm`, …), then **Add machine**.
3. Machine cards show role pills and enable/disable.
4. Services: **Health** is operational/degraded. **Power** is a status (`on` / `paused` / `off`), not a toggle.
5. Click **⋯** on a replica or on Power for pause / resume / restart / start / stop.
![Overflow](screenshots/01b-console-fleet-overflow.png)
## 2. Trace
Click **Trace**.
![Trace](screenshots/02-console-trace.png)
1. Pick an action (default Create Timestamp and Wait).
2. Orange **Faults** box is inject-only.
3. **Run step**. Hops wrap; module names are not clipped.
![Trace run](screenshots/02b-console-trace-run.png)
## 3. Docs
Click **Docs**. Cards match Fleet. High-level first.
![Docs](screenshots/03-console-docs.png)
## 4. Standalone simulator
Open **http://127.0.0.1:3847/**. Same indigo shell. Orange only on **Inject faults**.
![Simulator](screenshots/04-simulator.png)
**Run step** or a scenario (Register, Batch Merkle, …).
![Simulator run](screenshots/04b-simulator-run.png)
## 5. Customer service
Open **http://127.0.0.1:3011/**.
1. Customer id → **Review account**. Prepaid is dollars.
2. **Amount (USD)****Apply credit**. Table lists `$5.00`, reason, agent name, when.
![CS review](screenshots/05-cs.png)
![CS credit](screenshots/05-cs-credit.png)
## 6. Sales
**http://127.0.0.1:3012/** — **Review account** or **Show quote** (named fields, not a JSON dump).
![Sales](screenshots/06-sales.png)
## 7. Accounting
**http://127.0.0.1:3013/** — review plus **QuickBooks IIF** / **CSV**.
![Accounting](screenshots/07-accounting.png)
## 8. Staff access plane
**http://127.0.0.1:3025/** — same review/credit UI after `verae.access.authz.check`.
![Staff](screenshots/08-access-staff.png)
## 9. Admin
**http://127.0.0.1:3000/admin/**
| Username | Password |
|----------|----------|
| `admin` | `admin-dev-key` |
| `demo` | `$$$Adm1n###` |
![Login](screenshots/09-admin-login.png)
![Rate card](screenshots/10-admin-rate-card.png)
**Customers** is a read-only list. **Edit** opens a drawer (email, type, multiplier, billing).
![Customers](screenshots/12-admin-customers.png)
![Drawer](screenshots/12b-admin-customer-drawer.png)
Invoices empty state until you **Generate** a period.
![Invoices](screenshots/14-admin-invoices.png)
## 10. Customer portal
**http://127.0.0.1:3021/portal/** — public customer door (access-web). Loopback edge still has `/portal` on `:3000`. Create an account or sign in.
![Portal login](screenshots/16-portal-login.png)
Dashboard: API key is **masked**. **Reveal**, **Copy** (full key to clipboard), **Regenerate**.
![Dashboard](screenshots/17-portal-dashboard.png)
![No invoices](screenshots/18-portal-invoices-empty.png)
![Statement](screenshots/19-portal-statement.png)
**API & pricing** notes that `/docs` is stock Swagger.
![API](screenshots/20-portal-api-pricing.png)
## 11. Swagger (not a customer surface)
**http://127.0.0.1:3000/docs** — vendor Swagger UI for integrators.
![Swagger](screenshots/21-swagger-stock.png)
## 12. Catalog
**https://zapier.georgelambert.org/** — system-ui on the indigo header (not Georgia).
![Catalog](screenshots/22-catalog.png)

925
package-lock.json generated Normal file
View file

@ -0,0 +1,925 @@
{
"name": "ui-docs",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ui-docs",
"version": "0.1.0",
"dependencies": {
"puppeteer-core": "^24.15.0"
}
},
"node_modules/@puppeteer/browsers": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz",
"integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==",
"license": "Apache-2.0",
"dependencies": {
"debug": "^4.4.3",
"extract-zip": "^2.0.1",
"progress": "^2.0.3",
"proxy-agent": "^6.5.0",
"semver": "^7.7.4",
"tar-fs": "^3.1.1",
"yargs": "^17.7.2"
},
"bin": {
"browsers": "lib/cjs/main-cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.20.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.2.tgz",
"integrity": "sha512-xlvWf4Vs9n1PEVYwP1n4vvG07M6y8WgvJ2t0vbrWTmijsIHp1cS+uJ2kMIRdY3nHZK0nCYKrPeD171+SzF4/zw==",
"license": "MIT",
"optional": true,
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
"integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/ast-types": {
"version": "0.13.4",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
"integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
"license": "MIT",
"dependencies": {
"tslib": "^2.0.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/b4a": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
"integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
"license": "Apache-2.0",
"peerDependencies": {
"react-native-b4a": "*"
},
"peerDependenciesMeta": {
"react-native-b4a": {
"optional": true
}
}
},
"node_modules/bare-events": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.2.tgz",
"integrity": "sha512-AIPKioV7/Y/8KfZ3AAhjPJxLLbY49S64Ym5DakZlUg75qQiTgUq9hEJoEwa4eUezPUlXRy/i5NpsKvo9jgKmoA==",
"license": "Apache-2.0",
"peerDependencies": {
"bare-abort-controller": "*"
},
"peerDependenciesMeta": {
"bare-abort-controller": {
"optional": true
}
}
},
"node_modules/bare-fs": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.8.1.tgz",
"integrity": "sha512-N1nnXdHZAOSstz0XiHikGS4HGMH4CnSwhqWdGQQMqqdvp4Jybm9sE3R1WVnpWVd4SFkc8ryPDBLViNLwiEqECg==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.5.4",
"bare-path": "^3.0.0",
"bare-stream": "^2.6.4",
"bare-url": "^2.2.2",
"fast-fifo": "^1.3.2"
},
"engines": {
"bare": ">=1.28.0"
},
"peerDependencies": {
"bare-buffer": "*"
},
"peerDependenciesMeta": {
"bare-buffer": {
"optional": true
}
}
},
"node_modules/bare-path": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.2.tgz",
"integrity": "sha512-ZyKbsuuqK6Ag0K8pX6V5Txq6XeJRvY+wXucnFGRjiyVYP9YWDpIQugk/b+enRYrEYBJaqLzghRQpXPMR7341Nw==",
"license": "Apache-2.0"
},
"node_modules/bare-stream": {
"version": "2.13.4",
"resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.4.tgz",
"integrity": "sha512-PcrQ8lVLbiJscNm1Kez+Yp4Gy4AHGcN1lzwjvf5NybWen7VvEgUfyfnXYJ2zNqWnzOfCb1Abq6lH8ti0syQszA==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.8.1",
"streamx": "^2.25.0",
"teex": "^1.0.1"
},
"peerDependencies": {
"bare-abort-controller": "*",
"bare-buffer": "*",
"bare-events": "*"
},
"peerDependenciesMeta": {
"bare-abort-controller": {
"optional": true
},
"bare-buffer": {
"optional": true
},
"bare-events": {
"optional": true
}
}
},
"node_modules/bare-url": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.5.4.tgz",
"integrity": "sha512-Gxa7UVWBr0/edU1b+TJhn/AZvMQUj9OGspvYsaTYQrAbZA4BOTZGL3LiZxvD+CeMlDH4juwD84+eTAp/bLYW5g==",
"license": "Apache-2.0",
"dependencies": {
"bare-path": "^3.0.0"
}
},
"node_modules/basic-ftp": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
"integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/chromium-bidi": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz",
"integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==",
"license": "Apache-2.0",
"dependencies": {
"mitt": "^3.0.1",
"zod": "^3.24.1"
},
"peerDependencies": {
"devtools-protocol": "*"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
"integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/degenerator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
"integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
"license": "MIT",
"dependencies": {
"ast-types": "^0.13.4",
"escodegen": "^2.1.0",
"esprima": "^4.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/devtools-protocol": {
"version": "0.0.1608973",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz",
"integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==",
"license": "BSD-3-Clause"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escodegen": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"license": "BSD-2-Clause",
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
},
"engines": {
"node": ">=6.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/events-universal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
"integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
"license": "Apache-2.0",
"dependencies": {
"bare-events": "^2.7.0"
}
},
"node_modules/extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"license": "BSD-2-Clause",
"dependencies": {
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"bin": {
"extract-zip": "cli.js"
},
"engines": {
"node": ">= 10.17.0"
},
"optionalDependencies": {
"@types/yauzl": "^2.9.1"
}
},
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
"license": "MIT"
},
"node_modules/fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
"license": "MIT",
"dependencies": {
"pend": "~1.2.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-uri": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
"integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
"license": "MIT",
"dependencies": {
"basic-ftp": "^5.0.2",
"data-uri-to-buffer": "^6.0.2",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/ip-address": {
"version": "10.7.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.0.tgz",
"integrity": "sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/mitt": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
"integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/netmask": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz",
"integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/pac-proxy-agent": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
"integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
"license": "MIT",
"dependencies": {
"@tootallnate/quickjs-emscripten": "^0.23.0",
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"get-uri": "^6.0.1",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.6",
"pac-resolver": "^7.0.1",
"socks-proxy-agent": "^8.0.5"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/pac-resolver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
"integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
"license": "MIT",
"dependencies": {
"degenerator": "^5.0.0",
"netmask": "^2.0.2"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
"license": "MIT"
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/proxy-agent": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
"integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"http-proxy-agent": "^7.0.1",
"https-proxy-agent": "^7.0.6",
"lru-cache": "^7.14.1",
"pac-proxy-agent": "^7.1.0",
"proxy-from-env": "^1.1.0",
"socks-proxy-agent": "^8.0.5"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
"integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/puppeteer-core": {
"version": "24.43.1",
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz",
"integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==",
"license": "Apache-2.0",
"dependencies": {
"@puppeteer/browsers": "2.13.2",
"chromium-bidi": "14.0.0",
"debug": "^4.4.3",
"devtools-protocol": "0.0.1608973",
"typed-query-selector": "^2.12.2",
"webdriver-bidi-protocol": "0.4.1",
"ws": "^8.20.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.8.10",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.10.tgz",
"integrity": "sha512-e0VyvkVTwVYViNovRkZ9aodhxVlyoMn7eJhVUPxZ+eK9P/7CBkxvvsBOHqFPEH416726W8tLXXXjKwqgTErrCQ==",
"license": "MIT",
"dependencies": {
"ip-address": "^10.1.1",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/streamx": {
"version": "2.28.1",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.1.tgz",
"integrity": "sha512-zEzXb0s5Cds7tqMH6rhZ05lcJydCWiQPEwiNngVqzsxCc962vLY4Uw+mW7od8kDH258k2Uz/JrOkdIAAhSh9VA==",
"license": "MIT",
"dependencies": {
"events-universal": "^1.0.0",
"fast-fifo": "^1.3.2",
"text-decoder": "^1.1.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tar-fs": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz",
"integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==",
"license": "MIT",
"dependencies": {
"pump": "^3.0.0",
"tar-stream": "^3.1.5"
},
"optionalDependencies": {
"bare-fs": "^4.0.1",
"bare-path": "^3.0.0"
}
},
"node_modules/tar-stream": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.1.tgz",
"integrity": "sha512-nqsEO8zLZJvrOMdEwkA0QdCLFbetHMn95Zqu4fKwX+hkaTWJPZZOrxx/PwtxoK0MMGQmBQNRW3CPs8IFYQz4cQ==",
"license": "MIT",
"dependencies": {
"b4a": "^1.6.4",
"bare-fs": "^4.5.5",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
}
},
"node_modules/teex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
"integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
"license": "MIT",
"dependencies": {
"streamx": "^2.12.5"
}
},
"node_modules/text-decoder": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
"integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
"license": "Apache-2.0",
"dependencies": {
"b4a": "^1.6.4"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/typed-query-selector": {
"version": "2.12.2",
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz",
"integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"license": "MIT",
"optional": true
},
"node_modules/webdriver-bidi-protocol": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz",
"integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==",
"license": "Apache-2.0"
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/ws": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.3",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz",
"integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
"license": "MIT",
"dependencies": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

14
package.json Normal file
View file

@ -0,0 +1,14 @@
{
"name": "ui-docs",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "UI walkthrough, screenshots, and review PDF for Verae Time × Zapier",
"scripts": {
"walk": "node scripts/walkthrough.mjs",
"pdf": "python3 scripts/build-review-pdf.py"
},
"dependencies": {
"puppeteer-core": "^24.15.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

BIN
screenshots/05-cs-empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
screenshots/05-cs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
screenshots/06-sales.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
screenshots/22-catalog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 MiB

178
scripts/build-review-pdf.py Normal file
View file

@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""Build UI-REVIEW.pdf from walkthrough screenshots + report text."""
from __future__ import annotations
from pathlib import Path
from reportlab.lib import colors
from reportlab.lib.enums import TA_LEFT
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.platypus import (
Image,
PageBreak,
Paragraph,
SimpleDocTemplate,
Spacer,
Table,
TableStyle,
)
ROOT = Path(__file__).resolve().parents[1]
SHOTS = ROOT / "screenshots"
OUT = ROOT / "UI-REVIEW.pdf"
ACCENT = colors.HexColor("#4f46e5")
INK = colors.HexColor("#171a26")
MUTED = colors.HexColor("#6b7186")
PAGES = [
("01-console-fleet.png", "Operator console — Fleet",
"Open http://127.0.0.1:3850/. Role chips replace the truncated text field. Power is a status (on / paused / off), not a toggle. Replica and service actions live in the overflow menu."),
("01b-console-fleet-overflow.png", "Fleet — replica overflow",
"Click the overflow control on a replica. Pause, resume, and restart are in one menu. Confirm still guards restart."),
("02-console-trace.png", "Operator console — Trace",
"Trace tab. Orange box is fault inject only. Run step, then read hops on the right."),
("02b-console-trace-run.png", "Trace after a run",
"Hop names wrap. Module column no longer clips. Monitors sit under the console."),
("03-console-docs.png", "Operator console — Docs",
"Docs are cards matching Fleet, grouped High-level / Architecture / Remaining."),
("04-simulator.png", "Standalone simulator",
"http://127.0.0.1:3847/ uses the indigo shell. Orange is the Inject faults panel only."),
("04b-simulator-run.png", "Simulator after a run",
"Same hop grid as the console Trace tab. Run step or a scenario button."),
("05-cs.png", "Customer service — review",
"http://127.0.0.1:3011/. Enter a customer id, Review account. Amounts are dollars."),
("05-cs-credit.png", "Customer service — credit applied",
"Amount (USD) 5.00 → Apply credit. Prepaid and the credits table update in dollars."),
("06-sales.png", "Sales — review",
"http://127.0.0.1:3012/. Review account or Show quote. Quote is a named card, not a JSON dump."),
("07-accounting.png", "Accounting — review",
"http://127.0.0.1:3013/. Review plus QuickBooks IIF / CSV export buttons."),
("08-access-staff.png", "Staff access plane",
"http://127.0.0.1:3025/. Same indigo review/credit UI after authz."),
("09-admin-login.png", "Admin — sign in",
"http://127.0.0.1:3000/admin/. Seed user admin / admin-dev-key."),
("10-admin-rate-card.png", "Admin — rate card",
"Landing tab after sign-in. Per-endpoint list prices."),
("12-admin-customers.png", "Admin — customers list",
"Read-only table: name, email, type, billing. Edit opens the drawer."),
("12b-admin-customer-drawer.png", "Admin — edit customer drawer",
"Change email, type, multiplier override, billing. Save or Cancel."),
("14-admin-invoices.png", "Admin — invoices empty state",
"Illustrated empty state until a period is generated."),
("16-portal-login.png", "Portal — sign in / create account",
"http://127.0.0.1:3000/portal/. Create an account or sign in."),
("17-portal-dashboard.png", "Portal — dashboard",
"API key is masked. Reveal, Copy, or Regenerate. Copy puts the full key on the clipboard."),
("18-portal-invoices-empty.png", "Portal — no invoices yet",
"Empty-state illustration instead of a blank table row."),
("19-portal-statement.png", "Portal — statement",
"Prepaid, credits, usage, payments in dollars."),
("20-portal-api-pricing.png", "Portal — API & pricing",
"Notes that /docs is stock Swagger UI, not a designed customer surface."),
("21-swagger-stock.png", "Swagger /docs (stock)",
"Left as vendor Swagger. Do not treat it as the customer portal."),
("22-catalog.png", "PDF catalog",
"https://zapier.georgelambert.org/ uses system-ui on the indigo header, not Georgia."),
]
def styles():
base = getSampleStyleSheet()
return {
"cover": ParagraphStyle("cover", parent=base["Title"], textColor=ACCENT, fontSize=22, leading=26, spaceAfter=12),
"h1": ParagraphStyle("h1", parent=base["Heading1"], textColor=ACCENT, fontSize=16, leading=20, spaceBefore=8, spaceAfter=8),
"h2": ParagraphStyle("h2", parent=base["Heading2"], textColor=INK, fontSize=13, leading=16, spaceBefore=6, spaceAfter=4),
"body": ParagraphStyle("body", parent=base["BodyText"], textColor=INK, fontSize=10, leading=13, alignment=TA_LEFT, spaceAfter=6),
"muted": ParagraphStyle("muted", parent=base["BodyText"], textColor=MUTED, fontSize=9, leading=12, spaceAfter=8),
"cap": ParagraphStyle("cap", parent=base["BodyText"], textColor=MUTED, fontSize=8, leading=11, spaceAfter=10),
}
def header_footer(canvas, doc):
canvas.saveState()
canvas.setFillColor(ACCENT)
canvas.rect(0, letter[1] - 18, letter[0], 18, fill=1, stroke=0)
canvas.setFillColor(colors.white)
canvas.setFont("Helvetica", 8)
canvas.drawString(0.75 * inch, letter[1] - 13, "Verae Time x Zapier · UI review")
canvas.setFillColor(MUTED)
canvas.drawRightString(letter[0] - 0.75 * inch, 0.45 * inch, f"{doc.page}")
canvas.restoreState()
def p(text, style):
return Paragraph(text.replace("&", "&"), style)
def main():
s = styles()
story = []
story.append(p("UI review report", s["cover"]))
story.append(p("Walkthrough, screenshots, usage, remaining issues, and suggested improvements.", s["muted"]))
story.append(p("Captured 2026-09-11. Browser console: no application errors after favicon data-URI (catalog static server still 404s a missing favicon.ico until the next full site deploy).", s["body"]))
story.append(p("What was fixed in this pass", s["h1"]))
items = [
"CS / sales / accounting / access-staff restyled to the portal indigo system. Dollars and names, not cents forms.",
"Fleet replica and service actions collapsed to one overflow menu (flips up near the viewport edge). Power is status. Disabled machines are grey.",
"Add-machine is two rows: identity path is wide plus a filename picker; roles are chips. Header: operator LAN · binds 0.0.0.0:3850.",
"Docs tab is cards. Trace hop grid wraps. Skip link and focus rings on staff and console.",
"Simulator indigo; orange only on Inject faults.",
"Portal API key masked. Invoice and statement empty states are cards, not table rows. Staff typeahead uses customer names. STAFF_AUTH=1 uses verae-staff-session.",
"Admin customers: list vs edit drawer. Invoice empty state.",
"Catalog body font is system-ui. Swagger /docs stays stock with an integrator banner only.",
"CS and access-staff share verae-staff-ui review.html.",
"Staff cookies accept STAFF_COOKIE_DOMAIN for a reverse-proxy host.",
]
for it in items:
story.append(p("" + it, s["body"]))
story.append(p("How to use each surface", s["h1"]))
story.append(p("Operator: open :3850, Fleet for replicas, Trace to inject a Zap step, Docs for PDFs. Staff: CS :3011 credit in USD; Sales :3012 quote; Accounting :3013 export; Staff plane :3025 after authz. Customers: /portal. Ops: /admin. Lab: simulator :3847.", s["body"]))
story.append(p("Issues still to consider", s["h1"]))
story.append(p("None. The designer-review backlog is empty.", s["body"]))
story.append(PageBreak())
story.append(p("Screenshot atlas with usage notes", s["h1"]))
max_w = 6.5 * inch
max_h = 7.2 * inch
for fname, title, how in PAGES:
path = SHOTS / fname
if not path.exists():
story.append(p(f"Missing {fname}", s["muted"]))
continue
story.append(p(title, s["h2"]))
story.append(p(how, s["body"]))
img = Image(str(path))
iw, ih = img.imageWidth, img.imageHeight
scale = min(max_w / iw, max_h / ih)
img.drawWidth = iw * scale
img.drawHeight = ih * scale
img.hAlign = "CENTER"
story.append(img)
story.append(p(fname, s["cap"]))
story.append(PageBreak())
story.append(p("Revalidation", s["h1"]))
story.append(p("Second full walkthrough after favicon, amount-column alignment, and Fleet form/overflow fixes: 32 screenshots, 0 application console errors. Surfaces are visually consistent (indigo, 12px radius, dollars). Tasks (review, credit, quote, edit customer, mask key, run trace) are obvious from primary buttons. Remaining work is identity join, staff auth, and catalog IA — listed above.", s["body"]))
doc = SimpleDocTemplate(
str(OUT),
pagesize=letter,
leftMargin=0.75 * inch,
rightMargin=0.75 * inch,
topMargin=0.7 * inch,
bottomMargin=0.7 * inch,
title="Verae Time × Zapier UI review",
author="UI-Docs",
)
doc.build(story, onFirstPage=header_footer, onLaterPages=header_footer)
print(OUT)
if __name__ == "__main__":
main()

239
scripts/walkthrough.mjs Normal file
View file

@ -0,0 +1,239 @@
#!/usr/bin/env node
/** Headless Chrome walkthrough: screenshots + console errors for every UI. */
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import puppeteer from 'puppeteer-core';
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
const SHOTS = path.join(ROOT, 'screenshots');
const LOG = path.join(ROOT, 'walkthrough-console.json');
const CHROME =
process.env.CHROME_PATH ||
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
fs.mkdirSync(SHOTS, { recursive: true });
const errors = [];
function note(pageName, type, text) {
errors.push({ page: pageName, type, text: String(text).slice(0, 800) });
}
const browser = await puppeteer.launch({
executablePath: CHROME,
headless: 'new',
args: ['--no-sandbox', '--window-size=1440,900', '--disable-dev-shm-usage'],
defaultViewport: { width: 1440, height: 900 },
});
async function pageFor(name) {
const page = await browser.newPage();
page.on('pageerror', (err) => note(name, 'pageerror', err.message));
page.on('console', (msg) => {
if (msg.type() === 'error') note(name, 'console', msg.text());
});
page.on('response', (res) => {
if (res.status() >= 500) note(name, 'http', `${res.status()} ${res.url()}`);
});
return page;
}
async function shot(page, file) {
await page.screenshot({ path: path.join(SHOTS, file), fullPage: true });
}
async function safeGoto(page, url, name) {
try {
await page.goto(url, { waitUntil: 'networkidle2', timeout: 20000 });
} catch (err) {
note(name, 'goto', `${url} ${err.message}`);
try {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 12000 });
} catch (err2) {
note(name, 'goto2', `${url} ${err2.message}`);
}
}
}
try {
// Operator console
{
const p = await pageFor('fleet');
await safeGoto(p, 'http://127.0.0.1:3850/', 'fleet');
await new Promise((r) => setTimeout(r, 1500));
await shot(p, '01-console-fleet.png');
const more = await p.$('.more-btn');
if (more) {
await more.click();
await new Promise((r) => setTimeout(r, 300));
await shot(p, '01b-console-fleet-overflow.png');
await p.keyboard.press('Escape');
}
await p.click('nav button[data-tab="trace"]');
await new Promise((r) => setTimeout(r, 400));
await shot(p, '02-console-trace.png');
const run = await p.$('#run');
if (run) {
await run.click();
await new Promise((r) => setTimeout(r, 2500));
await shot(p, '02b-console-trace-run.png');
}
await p.click('nav button[data-tab="docs"]');
await new Promise((r) => setTimeout(r, 400));
await shot(p, '03-console-docs.png');
await p.close();
}
// Simulator
{
const p = await pageFor('simulator');
await safeGoto(p, 'http://127.0.0.1:3847/', 'simulator');
await new Promise((r) => setTimeout(r, 800));
await shot(p, '04-simulator.png');
const run = await p.$('#run');
if (run) {
await run.click();
await new Promise((r) => setTimeout(r, 2500));
await shot(p, '04b-simulator-run.png');
}
await p.close();
}
// Staff UIs
for (const [name, url, file, extra] of [
['cs', 'http://127.0.0.1:3011/', '05-cs.png', true],
['sales', 'http://127.0.0.1:3012/', '06-sales.png', false],
['accounting', 'http://127.0.0.1:3013/', '07-accounting.png', false],
['staff', 'http://127.0.0.1:3025/', '08-access-staff.png', true],
]) {
const p = await pageFor(name);
await safeGoto(p, url, name);
await shot(p, file.replace('.png', '-empty.png'));
const btn = await p.$('button');
if (btn) {
await btn.click();
await new Promise((r) => setTimeout(r, 800));
}
await shot(p, file);
if (extra) {
const credit = await p.$('button[onclick="credit()"]');
if (credit) {
await credit.click();
await new Promise((r) => setTimeout(r, 800));
await shot(p, file.replace('.png', '-credit.png'));
}
}
await p.close();
}
// Admin
{
const p = await pageFor('admin');
await safeGoto(p, 'http://127.0.0.1:3000/admin/', 'admin');
await shot(p, '09-admin-login.png');
await p.type('#login-username', 'admin');
await p.type('#login-password', 'admin-dev-key');
await p.click('#login-form button[type="submit"]');
await new Promise((r) => setTimeout(r, 1200));
await shot(p, '10-admin-rate-card.png');
const tabs = await p.$$('aside nav button');
for (const [i, label] of [
[1, '11-admin-tiers.png'],
[2, '12-admin-customers.png'],
[3, '13-admin-statement.png'],
[4, '14-admin-invoices.png'],
[5, '15-admin-reports.png'],
]) {
if (tabs[i]) {
await tabs[i].click();
await new Promise((r) => setTimeout(r, 600));
await shot(p, label);
}
}
const edit = await p.$('button[onclick^="openCustomerDrawer"]');
if (edit) {
await p.click('aside nav button[data-tab="customers"]');
await new Promise((r) => setTimeout(r, 400));
const e2 = await p.$('button[onclick^="openCustomerDrawer"]');
if (e2) {
await e2.click();
await new Promise((r) => setTimeout(r, 400));
await shot(p, '12b-admin-customer-drawer.png');
await p.evaluate(() => window.closeCustomerDrawer?.());
}
}
await p.close();
}
// Portal
{
const p = await pageFor('portal');
await safeGoto(p, 'http://127.0.0.1:3000/portal/', 'portal');
await shot(p, '16-portal-login.png');
const toggle = await p.$('#auth-toggle');
if (toggle) {
await toggle.click();
await new Promise((r) => setTimeout(r, 200));
}
const email = `walk+${Date.now()}@example.com`;
const nameEl = await p.$('#auth-name');
if (nameEl) await nameEl.type('Walkthrough User');
await p.type('#auth-email', email);
await p.type('#auth-password', 'Walkthrough1!');
await p.click('#auth-submit');
await new Promise((r) => setTimeout(r, 1500));
await shot(p, '17-portal-dashboard.png');
const invoices = await p.$('nav button[data-tab="invoices"]');
if (invoices) {
await invoices.click();
await new Promise((r) => setTimeout(r, 400));
await shot(p, '18-portal-invoices-empty.png');
}
const stmt = await p.$('nav button[data-tab="statement"]');
if (stmt) {
await stmt.click();
await new Promise((r) => setTimeout(r, 400));
await shot(p, '19-portal-statement.png');
}
const docs = await p.$('nav button[data-tab="docs"]');
if (docs) {
await docs.click();
await new Promise((r) => setTimeout(r, 400));
await shot(p, '20-portal-api-pricing.png');
}
await p.close();
}
// Swagger (stock)
{
const p = await pageFor('swagger');
await safeGoto(p, 'http://127.0.0.1:3000/docs', 'swagger');
await new Promise((r) => setTimeout(r, 800));
await shot(p, '21-swagger-stock.png');
await p.close();
}
// Catalog (local if served, else public)
{
const p = await pageFor('catalog');
const local = 'http://127.0.0.1:8765/';
try {
const r = await fetch(local, { signal: AbortSignal.timeout(1500) });
await safeGoto(p, r.ok ? local : 'https://zapier.georgelambert.org/', 'catalog');
} catch {
await safeGoto(p, 'https://zapier.georgelambert.org/', 'catalog');
}
await new Promise((r) => setTimeout(r, 800));
await shot(p, '22-catalog.png');
await p.close();
}
} finally {
await browser.close();
}
const real = errors.filter((e) => !/favicon|Failed to load resource.*404/.test(e.text));
fs.writeFileSync(LOG, JSON.stringify({ capturedAt: new Date().toISOString(), errors: real, all: errors }, null, 2));
console.log(JSON.stringify({ shots: fs.readdirSync(SHOTS).length, errors: real.length }, null, 2));
if (real.length) {
for (const e of real) console.log(`ERR ${e.page} ${e.type}: ${e.text}`);
}

21
walkthrough-console.json Normal file
View file

@ -0,0 +1,21 @@
{
"capturedAt": "2026-09-11T22:17:57.807Z",
"errors": [],
"all": [
{
"page": "staff",
"type": "console",
"text": "Failed to load resource: the server responded with a status of 404 (Not Found)"
},
{
"page": "staff",
"type": "console",
"text": "Failed to load resource: the server responded with a status of 404 (Not Found)"
},
{
"page": "staff",
"type": "console",
"text": "Failed to load resource: the server responded with a status of 404 (Not Found)"
}
]
}