# Technical documentation — formulys_simpleflow

Evidence date: 2026-10-14.

Source: `git@bitbucket.org:overdrive/formulys_simpleflow.git`, `master` at `07cd01b4ddf9e6ffa8bafbaf12eb5bf77d67e8d7` (2024-06-21, joshgatdew, merge). Default branch `master`. Other remote branch: `origin/url-query-state` at `b5896d3` (not merged; parent is `e11b604`, two commits behind `master`).

The scan-host path `~/projects/sonar_scan/repos/formulys_simpleflow` was not on this machine. Facts below come from a clone of `07cd01b`. Secret values are not stored in git. `example.env` contains only a sample `VITE_bob=cat`.

This inspection did not run `npm install` or the dev server. Parse and call-graph notes were read from source.

## Stack

| Layer | Choice | Evidence |
|---|---|---|
| UI | React 18.2 (`react`, `react-dom`) | `package.json` |
| Bundler | Vite 5 (`vite` `^5.2.11`, `@vitejs/plugin-react` `^4.2.1`) | `package.json`, `vite.config.js` |
| Language | JavaScript, JSX, ESM (`"type": "module"`) | `package.json`. No TypeScript sources. `@types/react` is a devDependency only |
| Routing | `react-router-dom` `^6.22.1`, `BrowserRouter` | `src/main.jsx`, `src/App.jsx` |
| State | Zustand `^4.5.1` | `src/stores/authStore.js`, `src/stores/submissionStore.js` |
| HTTP | axios `^1.6.7` | stores, helpers, profile and user forms |
| Forms | `@formio/react` `^5.3.0-rc.3`, `formiojs` `^4.18.2` | company editor, process cards, submission cards |
| CSS | Bootstrap 4.6.2 from jsDelivr in `index.html`; Bootstrap 5.3.3 imported in `src/main.jsx`; `sass` is a devDependency and no `.scss` file is in the tree | `index.html`, `main.jsx`, `package.json` |
| Icons | Font Awesome 6 (`@fortawesome/react-fontawesome`) and Bootstrap Icons 1.11.3 from jsDelivr | components, `index.html` |
| Tabs | `react-tabs` `^6.0.2` | `Management.jsx` |
| Modals | Bootstrap 4 `data-toggle` / jQuery `$().modal`, plus `react-modal` on the management pages | cards, profile, company, users |
| Mail API | `@sendgrid/client` `^8.1.3` called from the browser | `EmailManagement.jsx` |
| Lint | ESLint 8, `eslint-plugin-react`, react-hooks, react-refresh | `.eslintrc.cjs` |
| CI | Bitbucket Pipelines, image `node:20` | `bitbucket-pipelines.yml` |
| Scan | `sonarsource/sonarqube-scan:2.0.1` | same file |
| Package name | `simflow-react` `0.0.0` private | `package.json` |

`vite.config.js` is the default React plugin config. No alias, proxy, `base`, or `server.port` (port 4000 is only on the `dev` script).

Webpack 5, `webpack-cli`, and `webpack-dev-server` are runtime dependencies. No webpack config file exists. The build script is `vite build`. Provenance: **Implemented** unused dependencies.

Dependencies named `-` (`^0.0.1`) and `save-dev` (`^0.0.1-security`) are also listed. No source file imports them. Provenance: **Implemented** package metadata. How they were added is **Unknown**.

## Repository layout

```text
bitbucket-pipelines.yml
example.env
index.html
package.json
package-lock.json
public/vite.svg
README.md                          Vite template text
sonar-project.properties
vite.config.js
.eslintrc.cjs
.gitignore
src/main.jsx                       Bootstrap import, BrowserRouter, StrictMode
src/App.jsx                        Routes and profile modal
src/App.css                        Background image
src/index.css
src/assets/logo.png                Login and fallback nav logo
src/assets/background.png          Referenced from App.css
src/assets/react.svg               Not imported
src/lib/constants.js               FORMIO_BASE_URL
src/lib/helpers.js                 fetchSubmissions, formatLabel, compareDates
src/stores/authStore.js
src/stores/submissionStore.js
src/components/cards/BusinessProcessCard.jsx
src/components/cards/DashboardProcessCard.jsx
src/components/common/ComingSoon.jsx
src/components/common/Header.jsx
src/components/common/LoadingSpinner.jsx
src/components/common/Modal.jsx    Used only by the unused jason page
src/components/common/Navigation.jsx
src/components/common/Page.jsx
src/components/forms/UpdateUserForm.jsx
src/components/forms/UpdateUserAccountForm.jsx
src/pages/LoginForm.jsx
src/pages/Dashboard.jsx
src/pages/ProcessHub.jsx
src/pages/TaskList.jsx
src/pages/Management.jsx
src/pages/CompanyManagement.jsx
src/pages/UserManagement.jsx
src/pages/UserManagement.jason.jsx Not imported
src/pages/EmailManagement.jsx
src/pages/ReportingPage.jsx
src/pages/Settings.jsx
src/pages/Messages.jsx
src/pages/Notifications.jsx
```

`formatLabel` in `helpers.js` is exported and not imported anywhere else. Provenance: **Implemented** unused export.

`sonarqube_scan.bat` was listed when this onboarding task was created. It is not in the clone.

## Application structure

`index.html` mounts `#root`, sets the title "SimFlow Process Hub", and loads CDN CSS and, after the module tag, jQuery slim, Popper, and Bootstrap 4. Classic scripts run as the parser reaches them. The module script is deferred, so it runs after those classic scripts. `$` is therefore expected to exist before React calls `$().modal`. Provenance: **Inferred** from HTML script order. Not runtime-tested.

`main.jsx` also executes `import * as bootstrap from 'bootstrap'` (the npm v5 package). Nothing in `src/` references that namespace after the import. Modal code uses the global `$` from the CDN.

Zustand stores are module singletons. They are not given to React context. Components call the hooks directly.

`Page` renders `Navigation`, `Header`, and `children`. `/login` skips `Page`.

## Form.io integration

Constant: `FORMIO_BASE_URL = "https://rzkiqpzlxxtfqjl.form.io"` in `src/lib/constants.js`.

`UpdateUserForm.jsx` repeats the host as a string literal instead of the constant. The path is the same host.

| Call | Method | Path | Caller |
|---|---|---|---|
| Login | POST | `/simflowlogin/submission` | `authStore.login` |
| Current user | GET | `/current` | `authStore.autoLogin` |
| One company | GET | `/simflowclientdetail/submission/{user.company._id}` | `authStore.fetchCompanyDetails` |
| Company list | GET | `/simflowclientdetail/submission/?limit=500` plus optional `data.businessName=` | company page, user form |
| Company form | Form.io `<Form src>` | `/simflowclientdetail` or `/simflowclientdetail/submission/{id}` | `CompanyManagement.jsx` |
| User list | GET | `/simflowclientusers/submission/?limit=500` plus optional company filter | `UserManagement.jsx` |
| One user | GET | `/simflowclientusers/submission/{id}` | `UpdateUserAccountForm.jsx` |
| Create user | POST | `/simflowclientusers` | same |
| Update user | PUT | `/simflowclientusers/submission/{id}` | profile form and user form |
| Roles | GET | `/simflowuserroles/submission/?limit=500` | user form |
| Process submissions | GET | `{businessProcessForm.formUrl}/submission/?limit=500` | `fetchSubmissions` |
| Blank process form | Form.io `<Form src>` | `businessProcessForm.formUrl` | `BusinessProcessCard.jsx` |
| Existing submission | Form.io `<Form src>` | `{formUrl}/submission/{_id}` | `DashboardProcessCard.jsx` |

Headers on axios calls: `Content-Type: application/json` and `x-jwt-token`. Login sends an empty token header.

`fetchSubmissions` returns `undefined` when the token is missing or the request throws. Callers that then `.filter` the result will throw. Provenance: **Implemented**.

The Form.io project is not defined in git. Silicon Ark documents the same host for `pestelite_html` with different form paths (`pestelite*`). This repo's paths are `simflow*`. Shared-project intent is **Unknown / Requires Confirmation**.

This front end has no reference to `formulys_simpleflow_backend`, MongoDB, Socket.io, or the role names `tenantUser`, `tenantAdmin`, and `platformAdmin`.

## Auth

1. JWT from the Form.io login response header, key `token` in `localStorage`.
2. Restore via `GET /current` on the login page when the key exists.
3. Logout removes the key and clears `user` and `company` in the Zustand store.
4. No cookie, no Authorization Bearer scheme, no CSRF token, no route `loader` guard.

The redirect effect lives in `App` and depends only on `user`. It does not read the token itself.

## SendGrid

`EmailManagement.jsx` runs only as a child of the Emails tab (Simflow Admin).

```text
import.meta.env.VITE_REACT_SENDGRID_API_KEY
client.setApiKey(apiKey)
GET /v3/suppression/bounces
GET /v3/suppression/blocks
qs: from_email="joshua@overdrive.co.za", limit 10
```

The key name is a `VITE_` variable, which Vite inlines into the client bundle. The request then leaves the browser. Provenance: **Implemented**. A comment names `VITE_SENDGRID_API_KEY`; that name is not read.

`example.env` does not document the key. `.env` is gitignored (`.gitignore` line `.env`). Whether a key exists in Bitbucket, on an FTP host, or in a developer's ignored file is **Unknown / Requires Confirmation**.

`setLoading(false)` is in a `finally` attached to the async function, not to the SendGrid promises, so loading ends before `setBounces` / `setBlocks`.

## Pipeline and deploy

`bitbucket-pipelines.yml`:

| Item | Value |
|---|---|
| Image | `node:20` |
| Clone | `depth: full` |
| Branch | `master` only |
| Step 1 | `npm install`, `CI=false npm run build`, artifacts `dist/**` |
| Step 2 | `apt-get install ncftp`, `ncftpput -R` of `dist/*` |
| FTP variables | `FTP_USERNAME_QA`, `FTP_PASSWORD_QA`, `FTP_HOST_QA`, `FTP_SITE_ROOT_QA` |
| Echo | `$FTP_HOST` and `$FTP_SITE_ROOT` (different names from the upload) |
| Step 3 | `sonarsource/sonarqube-scan:2.0.1` |
| Sonar variables | `SONAR_HOST_URL`, `SONAR_TOKEN` |
| Sonar cache | `~/.sonar` |
| Project key | `overdrive_formulys_simpleflow_09e31949-1f76-42c8-9767-83697088df34` |

The YAML comment says the install step is "required to install zip". The script does not install or invoke zip.

History on 2024-04-15 (Lloyd) moves the deploy from earlier script experiments to `ncftpput`. Commit messages include "revert to git ftp" and "reveret gitgtp". The file at the tip does not call git-ftp.

No Dockerfile, serverless manifest, Terraform, or web-server config is in the repo. The deployed artefact is the static Vite `dist` directory. The FTP document root and the public URL are **Unknown / Requires Confirmation**.

`CI=false` is set for the build. Create React App treats `CI=true` as a warning-as-error switch. This project uses Vite. Whether the flag changes Vite's behaviour was not checked. Provenance of the flag: **Implemented** in the YAML.

## Local scripts

| Script | Command |
|---|---|
| `npm start` | `vite` |
| `npm run dev` | `vite --port 4000` |
| `npm run build` | `vite build` |
| `npm run lint` | `eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0` |
| `npm run preview` | `vite preview` |

ESLint extends `eslint:recommended`, `plugin:react/recommended`, `plugin:react/jsx-runtime`, and `plugin:react-hooks/recommended`. `react/jsx-no-target-blank` is off. `react-refresh/only-export-components` is warn. Ignored: `dist`, `.eslintrc.cjs`. Environment: browser, es2020.

There is no test script, no test runner, and no `*.test.*` / `*.spec.*` file.

## Environment

| Name | Where it is read | In `example.env`? |
|---|---|---|
| `VITE_REACT_SENDGRID_API_KEY` | `EmailManagement.jsx` | No |
| `VITE_bob` | Nowhere in `src/` | Yes, value `cat`, comment says to copy the file to `.env` |
| `SONAR_HOST_URL`, `SONAR_TOKEN` | pipeline | No. Not application env |
| `FTP_USERNAME_QA`, `FTP_PASSWORD_QA`, `FTP_HOST_QA`, `FTP_SITE_ROOT_QA` | pipeline | No |

`@rollup/plugin-replace` and `dotenv` are devDependencies. `vite.config.js` imports neither. Vite loads `.env` for `VITE_` names on its own. That default was not runtime-tested.

## Defects visible in source

These are observations, not a decision to change the product.

| Id | Where | What |
|---|---|---|
| D-1 | `src/App.jsx` line 7, since `5952e5d` (2024-05-09) | `import Messages from "./pages/Messages";1` does not parse. `npm run build` on `07cd01b` cannot succeed |
| D-2 | `DashboardProcessCard.jsx` `handleSubmissionClose` | Calls `getAllSubmissions` but the component neither imports the store hook nor declares the function. The same call remains on `url-query-state` |
| D-3 | `submissionStore.js` vs `ProcessHub.jsx` and `Dashboard.jsx` | Simflow Admin bypass exists on the hub and the recent column, not in `getAllSubmissions`, which feeds the dashboard grid and the task list |
| D-4 | `TaskList.jsx` filter | `.toLowerCase()` on `simflowCardName` is not guarded. The dashboard filter is guarded |
| D-5 | `CompanyManagement.jsx` search | `contactPersonEmail.toLowerCase()` is not guarded |
| D-6 | `UserManagement.jsx` search | `company.data.businessName` is not guarded |
| D-7 | `Dashboard.jsx` | `arr.length = 15` pads as well as truncates |
| D-8 | `TaskList.jsx`, `EmailManagement.jsx` | `setLoading(false)` runs before the async work they started has finished |
| D-9 | `UpdateUserAccountForm.jsx` | `getUserAcc` reads `allRoles` state in the same function turn as `setAllRoles` |
| D-10 | `UpdateUserForm.jsx` | Profile PUT spreads the entire `user` object and always sends `password` |
| D-11 | `EmailManagement.jsx` | SendGrid secret is a `VITE_` variable, so a configured key ships in the JS bundle |
| D-12 | `bitbucket-pipelines.yml` | Success echo uses `FTP_HOST` and `FTP_SITE_ROOT`; upload uses the `_QA` names |
| D-13 | `UserManagement.jason.jsx` | Dead copy of the pre-`b843b82` screen. `Modal.jsx` is only reachable from it |

`BusinessProcessCard` calls `document.querySelector(".null.has-success").remove()` on submit. If that node is absent, `remove` throws before the modal closes. Provenance: **Inferred** from the unguarded call.

## Security characteristics (from the code, not a pentest)

- The only credential the browser stores is the Form.io JWT in `localStorage`.
- Role checks are client-side string compares. A user who can open the route still downloads the management modules; the tabs are conditional renders. Enforcement depends on Form.io accepting or rejecting the JWT. That server policy is not in git.
- The SendGrid client runs in the page. Anyone who can load the built Emails bundle can read a `VITE_` key from the JS if one was present at build time.
- The suppression query pins a single sender address in source.
- CDN scripts (jQuery, Bootstrap, Form.io CSS, Bootstrap Icons) are loaded from jsDelivr and `cdn.form.io`. Bootstrap, jQuery, and Popper tags include `integrity`. The Form.io CSS link and the Bootstrap Icons link do not.
- No captcha, rate limit, or security header is configured in this repo. Static hosting headers would live on the FTP server. **Unknown**.

## Testing and quality gates

- Local: `npm run lint` is defined. It was not run for this document.
- CI: no test step. SonarQube runs only after the FTP step, and only if the build step succeeds.
- Sonar properties set a project key and nothing else.
- No coverage threshold, no Cypress/Playwright/PHPUnit (this is not a PHP app), no seed data.

## Related Silicon Ark documents

| Path | Relationship as evidenced |
|---|---|
| `formulys_simpleflow_backend/` | Separate Bitbucket repo. Scaffold with Express, Socket.io, and Mongoose. Not called by this UI. Role names differ. Link is the shared slug words only |
| `pestelite_html/` | Documents the same Form.io host `rzkiqpzlxxtfqjl.form.io` and different form paths |
| `intersect_lambda/` | Mentions `simpleflowhub.app` as a mail sender and an unused URL. This repo does not reference that host |

None of those documents previously described this front end. Whether the products are one programme is **Unknown / Requires Confirmation**.
