Young's Mind
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)

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-server preset) — 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/vite plugin (CSS-first @theme tokens)
  • Chat client: hand-rolled SSE parser (see §6 — @ai-sdk/vue has NO useChat)
  • Deploy: systemd pulse-site.service (port 8092) + nginx pulse.youngle.au (LE cert SAN already covers it, basic auth young:Vicky2@13)

3. Pages

RoutePageContent
/DashboardLatest 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
/newsNews feedAggregated headlines: ABC Business, AFR, Google News AU (ASX 200 / ASX Market / Mining & Rare Earths queries). Source badges, relative timestamps, open in new tab
/chatAsk AIStreaming 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 Levels section
  • News: RSS feeds (10-min cache via Nitro defineCachedEventHandler)
  • Chat LLM: OpenCode Zen (https://opencode.ai/zen/v1, compatibility: 'compatible'), model deepseek-v4-flash with mimo-v2.5-free fallback; key from ZEN_API_KEY env

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

  1. @ai-sdk/vue 2.0.226 exports NO useChat (only useCompletion). Chat client = fetch + ReadableStream parse of data: JSON lines, accumulate text-delta.
  2. convertToModelMessages 500s on OpenAI-style {content: string} messages — must send parts array shape.
  3. Nginx SSE: /api/chat location needs proxy_buffering off; proxy_cache off; proxy_read_timeout 300s; proxy_set_header Connection ''; chunked_transfer_encoding off;
  4. Tailwind v4 via @tailwindcss/vite; theme tokens in assets/css/main.css (@theme).
  5. 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 touch server/.
  • Ace (backend): server/ (api routes, caching, security, chat streaming + grounding), build pipeline, systemd unit, nginx config, deployment + verification. Do NOT touch pages/, 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/charts traversal attack returns 400
  • https://pulse.youngle.au/ 200 behind auth; systemd pulse-site active; 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
Copyright © 2026