pulse-portal
Audience
PLANA staff. The BOS frontend codebase. Customers using BOS read /bos/ instead.
pulse-portal is the BOS frontend — the Vue 3 SPA at my.planapulse.ai/{workspace-slug}. It is the screen customers actually open to use PLANA Pulse.
Stack
| Property | Value |
|---|---|
| Repo | git.planapulse.com/plana-pulse/pulse-portal |
| Language | JavaScript ESM (no TypeScript) |
| Framework | Vue 3.5 with Vite (SPA mode; NOT Nuxt) |
| Router | None — in-memory view switching via useAppState |
| State | Pinia |
| Streaming | Native EventSource for SSE |
| Functional utilities | Ramda |
| Image base | Built static, served by nginx-unprivileged |
| Namespace | pulse-account (prod) or bos-demo (dev) |
| Deployment | bos-portal |
| Domain | my.planapulse.ai/{slug} (catch-all routes here) |
What it serves
The eight views described in BOS user manual:
- Dashboard (KPIs + cashflow + alerts summary + quick links)
- Chat (4 agents, SSE streaming)
- Alerts (master/detail)
- Banking (PSD2 accounts + transactions)
- Billing (plan + invoices)
- Execution log (tool call audit)
- Integrations (catalog with status)
- Settings (workspace / team / API keys / alert thresholds)
How it differs from pulse-account
pulse-account | pulse-portal (BOS) | |
|---|---|---|
| Framework | Nuxt 3 SSR | Vue 3 + Vite SPA |
| Routing | Nuxt file-based | In-memory tab switching |
| URL | my.planapulse.ai SPA paths | my.planapulse.ai/{workspace-slug} |
| Purpose | Customer account portal | Workspace operating screen |
The two coexist on my.planapulse.ai; the Envoy Gateway routes between them based on path. See Architecture → Envoy Gateway.
Auth
pulse-portal does not maintain its own session. It reads the pa_token cookie set by pulse-account-api and includes it in every /api/* request. If the cookie is missing or expired, the API returns 401 and the frontend redirects to login.
SSE streaming
The Chat panel uses native EventSource:
const es = new EventSource(`/api/agents/${agentId}/chat/stream?...`)
es.addEventListener('text_delta', e => { ... })
es.addEventListener('tool_call', e => { ... })
es.addEventListener('done', () => es.close())pulse-account-api proxies the EventSource to ai-agents via reply.hijack(). See ai-agents for the downstream.
Configuration
| Env var (build-time) | Purpose |
|---|---|
VITE_API_BASE | /api (relative) |
VITE_SSE_BASE | Same |
VITE_MATOMO_SITE_ID | Matomo tracking |
There are no runtime secrets in the frontend pod. All sensitive operations go through the API.
Known surface that does NOT belong to BOS
Some code paths in pulse-portal are no longer used and should not be relied on (also documented in BOS → Known issues):
useFinanceAgent.js— superseded by genericuseAgent.js- BottomPanel
LOG,TERMINAL,PROBLEMStabs — hardcoded demo data; not wired to real backends - Settings → "Domain" tab — empty placeholder
- StatusBar
Excel ⚠indicator — static, not dynamic - AppSidebar "New dashboard" / "New session" buttons — no handlers
- Hardcoded "Techno OOD" in AppSidebar footer
- Hardcoded "CM" avatar initials in TitleBar
- Hardcoded "Apr 2026" period badge in TabBar
These are cleanup tasks. Do not document them as features in /bos/.
Mobile
pulse-portal is mobile-responsive but not a native app. A native companion (Ionic Vue + Capacitor 7) is on the roadmap — see BOS → Mobile status.
Deployment
| Path | What |
|---|---|
| Build | Vite → dist/ (static) |
| Image | nginx-unprivileged serving the static dist |
| Deploy | Currently kubectl set image from CI (migration to Flux pending) |
| Source of truth | infra/k8s/pulse-account/bos-portal.yaml |
Where to read more
- BOS user manual — what
pulse-portalactually does for end users - pulse-account — the SSR account portal it sits next to
- pulse-account-api — the backend it talks to
- ai-agents — the agent runtime behind chat
- Architecture → Envoy Gateway — the routing that lands on
pulse-portal