docs(technical.profile): stub Profile SPA module (frontend-only, deploy, default-landing)

Profile had no technical.* module. Documents: frontend-only SPA (hiveiq-profile-frontend
:5184, no backend/DB — composes auth/mgmt/messaging), PROFILE_VERSION sha-pinning +
single-instance recreate, the profile.bcos.cloud->www.bcos.cloud root-redirect red herring,
and that Profile is the sole is_default module (browser landing page). Inbox unread badge
cross-links [profile.inbox] rather than duplicating. Captured from the 2026-07-17 prod deploy.
This commit is contained in:
2026-07-18 08:35:07 -04:00
parent a8ccab8d7b
commit 8960b03ec6
2 changed files with 108 additions and 0 deletions
@@ -0,0 +1,45 @@
---
module: technical.profile
title: Profile — user account & settings SPA
tab: Gotchas
order: 40
audience: dev
---
# Profile gotchas & diagnostics
## `profile.bcos.cloud/` root returns 302 → `www.bcos.cloud` — this is normal
A bare/direct hit on the root **redirects to the marketing site** (`www.bcos.cloud`). It's
intentional nginx routing — `00-redirect.conf` bounces `report` / `profile` / `logs` roots to
marketing. **Do not read this 302 as an outage or a broken deploy.** The real access path is the
HiveIQ Browser loading the module URL *with auth context* post-login, not an anonymous root hit.
To verify the SPA is actually healthy, check the container, not the public root:
- `docker ps | grep profile-frontend` → healthy, on the expected `:<sha>`.
- `curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:5184/` → **200** (container serves fine).
- Confirm the deployed build by grepping the served bundle inside the container, e.g.
`docker exec hiveiq-profile-frontend grep -rl '<marker>' /app/dist` — a `200` alone proves nothing
(an SPA returns `200` + `index.html` for any path).
## It is frontend-only — the bug is almost always in a backend
Profile owns no data. Symptom-to-owner:
- Inbox empty / unread count wrong / messages missing → **hiveops-messaging** (8086). See [profile.inbox].
- Preferences not saving (theme, `inboxPollSeconds`, notifications) → **hiveops-auth** (prefs are auth-owned).
- Password/MFA/backup-code issues → **hiveops-auth**. Institution dropdown empty → **hiveops-mgmt**.
## Deploy is single-instance (no blue/green)
Recreating `hiveiq-profile-frontend` briefly drops `profile.bcos.cloud`. Pin
**`PROFILE_VERSION=<sha>`** in the services `.env` (immutable tag); unset = mutable `:latest`. Keep a
rollback reference before recreating — capture the current image ID
(`docker inspect hiveiq-profile-frontend --format '{{.Image}}'`), since the previous `:latest` tag is
mutable and may not cleanly re-address the old build.
## The Inbox unread badge lives in the SPA docs, not here
The sidebar unread-badge feature (nav badge on the Inbox item, `#ef4444`, `inboxPollSeconds` poll,
capped `99+`, silent-on-failure) is documented in **[profile.inbox]** (Claude tab: "Sidebar unread
badge", added 2026-07-16). The count comes from `GET /api/messaging/messages/unread/count`
(hiveops-messaging, cached per userId). Don't duplicate it here.
@@ -0,0 +1,63 @@
---
module: technical.profile
title: Profile — user account & settings SPA
tab: Overview
order: 10
audience: dev
---
# Profile (`hiveops-profile`)
> **DRAFT · internal.** Written 2026-07-17 from `hiveops-profile` source + the prod deploy on that
> date. Verify against code before relying on specifics.
A **frontend-only SPA** — the user's account/settings surface (`profile.bcos.cloud`). It has **no
dedicated backend and no database of its own**; it composes existing services. If data misbehaves,
the Profile app is almost never the cause — check the owning backend. See [technical.profile] Gotchas.
## Responsibility
Served by container **`hiveiq-profile-frontend`** on port **5184** (nginx serving `/app/dist`). Built
with Vite; `VITE_AUTH_URL` (`auth.bcos.cloud`) and `VITE_API_URL` (`api.bcos.cloud`) are baked at
build time. It aggregates:
| Surface | Owning service | Notes |
|---|---|---|
| Inbox / messages / unread count | **hiveops-messaging** (8086, `hiveiq_messaging`) | see [profile.inbox] |
| User preferences (incl. `inboxPollSeconds`, display, appearance, notifications) | **hiveops-auth** | prefs are auth-owned, not profile |
| Security (password, MFA/TOTP, backup codes) | **hiveops-auth** | see [profile.security] |
| Institution dropdown (admin compose) | **hiveops-mgmt** `GET /api/v1/msp/institutions` | MSP_ADMIN/BCOS_ADMIN only |
Feature docs live in the SPA modules: [profile.inbox] · [profile.notifications] · [profile.security]
· [profile.display] · [profile.appearance] · [profile.profile]. **Do not add message/pref tables or
endpoints to profile** — messaging and auth own that data. See [platform.service-ownership].
## It is the default landing page
Profile is the **sole module flagged `is_default`** in mgmt `app_modules` (every other module is
`false`). The HiveIQ Browser's `getHomeUrl()` (`getDefaultModule()` → the first `isDefault` module)
therefore resolves to Profile, so **logged-in users land on the Profile page after login** — which is
why the Inbox unread badge is the first thing a user can see. Exceptions: `analyticsOnly` whitelabel
tenants land on Analytics, and a user must have Profile in their effective module set (it is
`is_core`, so normally everyone does). See [technical.browser] and [technical.mgmt] (module
allocation, `AppModuleService`).
## Build & deploy
- **Image:** `registry.bcos.cloud/hiveiq-profile-frontend`. Compose service `hiveiq-profile-frontend`
in `instances/services/compose/frontends.yml` (merged into the services project via `include`).
- **Version pin:** `image: ${REGISTRY_URL}/hiveiq-profile-frontend:${PROFILE_VERSION:-latest}`. Set
**`PROFILE_VERSION=<sha>`** in the services `.env` — an immutable tag per the prod convention (the
git short-sha of the release-to-main commit, e.g. `63712fb`). Left unset it silently resolves to
mutable `:latest`; the prod deploy on 2026-07-17 pinned it off `:latest` for the first time.
- **Deploy:** on the services VM (`173.231.195.250`), `~/hiveiq/hiveops-openmetal/hiveops/instances/services`:
```bash
# set PROFILE_VERSION=<sha> in .env, then:
docker compose pull hiveiq-profile-frontend
docker compose up -d hiveiq-profile-frontend
```
- **Single instance — NOT blue/green.** A recreate is a few seconds of `profile.bcos.cloud`
unavailability. Serialize with any other operator touching the shared services `.env`/tree.
See [technical.profile] Gotchas for the `profile.bcos.cloud → www.bcos.cloud` root-redirect red herring
and the "it's frontend-only, check the backend" rule.