Pulse Website — Product Spec
Pulse Website — Product Spec
Version: 1.0 · Owner: Min · Status: Approved by Young (2026-08-05)
Delegated: Nova = frontend design & build · Ace = backend build & deploy
Reference build: /home/ubuntu/pulse-site (Min's MVP — live, verified, to be upgraded)
1. Purpose
Pulse (https://pulse.youngle.au) is Young's personal website to check news and the daily market brief. It replaces the old parked "coming soon" page. Dark, modern, shadcn-dashboard aesthetic with the SolOS brand mint green (#16A34A) and brain logo.
2. Stack (locked)
- Framework: Nuxt 4 (Nitro server,
node-serverpreset) —https://github.com/nuxt/nuxt,https://nuxt.com/docs - AI: Vercel AI SDK (
ai+@ai-sdk/openai), streaming to OpenCode Zen - Styling: Tailwind CSS v4 via
@tailwindcss/viteplugin (CSS-first@themetokens) - Chat client: hand-rolled SSE parser (see §6 —
@ai-sdk/vuehas NOuseChat) - Deploy: systemd
pulse-site.service(port 8092) + nginxpulse.youngle.au(LE cert SAN already covers it, basic authyoung:Vicky2@13)
3. Pages
| Route | Page | Content |
|---|---|---|
/ | Dashboard | Latest daily brief, parsed into cards in priority order: Portfolio table → Watchlist cards (WEB/DRO/SEMI) → Global → Sector moves → 📈 Interactive TradingView chart (MA50 + volume indicators; stock list sidebar + chart two-column layout on desktop, horizontal scroll on mobile; timeframe chips D/W/1M/60; widget embeds s3.tradingview.com/tv.js, dark theme, ASX symbols, client-only) → Notes. Date dropdown browses full brief archive |
/news | News feed | Aggregated headlines: ABC Business, AFR, Google News AU (ASX 200 / ASX Market / Mining & Rare Earths queries). Source badges, relative timestamps, open in new tab |
/chat | Ask AI | Streaming chat with Min persona, grounded in the latest vault brief |
4. Data sources
- Briefs: vault
Daily Briefings/*.md(server-side fs read; live per request OK at personal traffic — no watcher) - Charts: vault
Daily Briefings/charts/*.png - Watchlist levels: vault
market-watch.mdx→## Watchlist Levelssection - News: RSS feeds (10-min cache via Nitro
defineCachedEventHandler) - Chat LLM: OpenCode Zen (
https://opencode.ai/zen/v1,compatibility: 'compatible'), modeldeepseek-v4-flashwithmimo-v2.5-freefallback; key fromZEN_API_KEYenv
5. API contract (server routes)
GET /api/brief[?date=YYYY-MM-DD]
{ "brief": { "date", "title", "meta", "sections": [
{ "heading", "key", "kind": "text|table|list|charts", "content": [], "headers": [], "rows": [], "images": [] } ] },
"dates": ["2026-08-05", ...], "latest": "2026-08-05" }
GET /api/news
[{ "source", "title", "link", "desc", "pubDate" }, ...] // ≤50, newest first
GET /api/charts?file=semi_chart.png
Binary PNG. Must reject traversal (resolve() + startsWith guard).
POST /api/chat
Body — AI SDK UI messages: { "messages": [{ "role": "user", "parts": [{ "type": "text", "text": "..." }] }] }
Response — AI SDK UI stream (SSE data: lines with {"type":"text-delta","delta":"..."} events). System prompt = Min persona + latest brief + watchlist levels injected.
6. Critical engineering constraints
@ai-sdk/vue2.0.226 exports NOuseChat(onlyuseCompletion). Chat client =fetch+ ReadableStream parse ofdata:JSON lines, accumulatetext-delta.convertToModelMessages500s on OpenAI-style{content: string}messages — must sendpartsarray shape.- Nginx SSE:
/api/chatlocation needsproxy_buffering off; proxy_cache off; proxy_read_timeout 300s; proxy_set_header Connection ''; chunked_transfer_encoding off; - Tailwind v4 via
@tailwindcss/vite; theme tokens inassets/css/main.css(@theme). - RSS: one dead feed must not kill the page (
Promise.allSettled).
7. Ownership split
- Nova (frontend):
pages/,components/,assets/css/main.css,app.vue,public/(logos/favicon). Owns the shadcn-style design system, dashboard layout, news UI, chat UI. Do NOT touchserver/. - Ace (backend):
server/(api routes, caching, security, chat streaming + grounding), build pipeline, systemd unit, nginx config, deployment + verification. Do NOT touchpages/,components/,assets/unless fixing an integration bug (then coordinate).
8. Acceptance criteria
- Dashboard renders latest brief: portfolio table first, watchlist cards, sectors, charts (clickable → TradingView), notes
- Date archive dropdown works
- News page: ≥1 feed live, 50-item cap, refresh works, dead feed doesn't break page
- Chat: streams token-by-token; "What did LIN do today?" → answers from the brief (LIN -10.06% to $0.697, support broken)
-
/api/chartstraversal attack returns 400 -
https://pulse.youngle.au/200 behind auth; systemdpulse-siteactive; nginx reloaded - RAM: second Nitro ~200MB acceptable
9. Deploy reference
cd /home/ubuntu/pulse-site
ZEN_KEY=$(python3 -c "import json;print(json.load(open('/home/ubuntu/.solos/config/credential_pools.json'))['opencode-zen']['keys'][0]['key'])")
ZEN_API_KEY="$ZEN_KEY" npx nuxi build
sudo cp pulse-site.service /etc/systemd/system/ && sudo systemctl daemon-reload && sudo systemctl enable --now pulse-site
sudo cp pulse-nginx.conf /etc/nginx/sites-available/pulse.youngle.au && sudo nginx -t && sudo systemctl restart nginx
10. Non-goals (v1)
- No user accounts, no auth UI (nginx basic auth only)
- No historical chart data beyond vault PNGs
- No mobile apps
Pulse Public/Private Split + Eve's Corner — Spec
Author: Mira (business + governance) · Co-sign: Min (implementation) · Status: In build Date: 2026-08-09
Pulse V2 — Product Spec (Google Finance-style)
Version: 2.0 · Owner: Min · Status: Approved by Young (2026-08-06) Delegated: Nova = frontend design & build · Ace = backend build & deploy Baseline: V1 live at https://pulse.youngle.au (/home/ubuntu/pulse-site) — upgrade, don't break. V1 spec: PULSE-SPEC.md.

