Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion apps/web/app/terminal/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { getWSClient } from '@/lib/ws-client';
import { useSession } from '@/lib/auth';

export default function TerminalPage() {
const { showLeftRail, showRightRail } = useTerminalStore();
const { showLeftRail, showRightRail, setShowLeftRail, setShowRightRail } = useTerminalStore();
const { user, loading: sessionLoading } = useSession();
const ingestMT5 = useMT5Store((s) => s.ingestEvent);
const refreshAccounts = useMT5Store((s) => s.refreshAccounts);
Expand All @@ -28,6 +28,17 @@ export default function TerminalPage() {
else if (!user.emailVerified) window.location.href = '/verify';
}, [sessionLoading, user]);

// Mobile: the rails are fixed-width (48px + 340px) and would crush the flex-1 chart to a sliver
// on a phone. Default both closed under lg (<1024px) so the chart gets the full viewport; the
// rails still open as overlays (drawer-style) via the top-bar/settings toggles.
useEffect(() => {
if (typeof window === 'undefined' || window.innerWidth >= 1024) return;
setShowLeftRail(false);
setShowRightRail(false);
// once, on first mount — a user reopening a rail on mobile must stick.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

// Docs deep link: /terminal?pulse=<base64url(code)> loads the snippet into the Script dock
// (one-shot, then stripped from the URL so refresh doesn't re-apply it).
useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/features/terminal/left-rail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function LeftRail() {

return (
<TooltipProvider>
<aside className="flex w-12 shrink-0 flex-col items-center gap-1 border-r border-border bg-surface/80 py-3">
<aside className="absolute inset-y-0 left-0 z-30 flex w-12 shrink-0 flex-col items-center gap-1 overflow-y-auto border-r border-border bg-surface/95 py-3 lg:static lg:z-auto lg:overflow-visible lg:bg-surface/80">
{TOOLS.map((tool, i) => {
const showBreak = GROUP_BREAKS.has(tool.group ?? '') && TOOLS[i - 1]?.group !== tool.group;
return (
Expand Down
2 changes: 1 addition & 1 deletion apps/web/features/terminal/right-rail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function RightRail() {
const useBrokerTrade = user?.role === 'admin' && activePane.symbol.startsWith('KITE:');

return (
<aside className="flex w-[340px] shrink-0 flex-col border-l border-border bg-surface/85">
<aside className="absolute inset-y-0 right-0 z-30 flex w-[min(340px,88vw)] shrink-0 flex-col border-l border-border bg-surface/95 shadow-xl backdrop-blur lg:static lg:z-auto lg:w-[340px] lg:bg-surface/85 lg:shadow-none lg:backdrop-blur-0">
<Tabs value={rightRailTab} onValueChange={setRightRailTab} className="flex h-full flex-col">
<TabsList className="mx-2 mt-3 grid w-auto grid-cols-7 self-stretch">
<TabsTrigger value="trade" aria-label="Trade" title="Trade" className="h-8 px-0">
Expand Down
19 changes: 13 additions & 6 deletions docs/STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ increment per session, tick the box there AND log here.

| Date | Item | Commits | Evidence |
|---|---|---|---|
| 2026-07-17 | **PROD RESCUE #3 — VM `supercharts2`→`supercharts3` (GCP mining false-flag recurred, site was down).** `supercharts2` TERMINATED + un-startable ("Compute Engine detected suspicious activity" — 3rd time, the Binance-WS + build-CPU heuristic). **Data preserved:** snapshotted `supercharts2-disk` → `supercharts-rescue-0717` FIRST → new disk from snapshot → new `e2-standard-2` `supercharts3` (asia-south1-a, tag `sc`, firewall `sc-web` 80/443) → deleted flagged VM to free the static IP → created with `--address=35.200.208.191`. Boot auto-resurrected pm2 (supercharts-api+web) + Caddy. **⚠️ DEPLOY TARGET IS NOW `supercharts3`.** Durable fix still pending = move off GCP (recurs every few days). | (host op; no code change) | **LIVE:** `curl https://supercharting.com` → **200**; deployed HEAD `65f0adb` == local HEAD; `/api/broker/automation` → **401** (Pro gate); DB intact via snapshot (13 users, 130 watchlist syms, 1 broker_conn, 1 egress + ip_assignment); Binance backfill running post-boot. |
| 2026-07-14 | **GW-7 docs capstone — "Automate a SuperTrend flip on Zerodha" guide (the FINAL-DELIVERY user-facing surface that ties GW-1..GW-7 together).** New public page `/docs/automation` teaching the owner (and any Pro user) how to ARM a SuperTrend position-flip on ANY connected Kite instrument (stock / option / future / MCX): BUY → go long (close any short first), SELL → flip short — routed through the audited, IP-whitelisted broker pipeline. **Pure content module** `apps/web/features/docs/automation-guide.ts` (no React/IO): `AUTOMATION_STRATEGY_SCRIPT` (a runnable `ta.supertrend` flip recipe that emits clean BUY+SELL flip marks + draws the line), `AUTOMATION_DEFAULTS` (the SINGLE documented source of the atr10·mult3·qty1·MIS·cap5·telegram defaults), `FLIP_TABLE` (flat/opposite/already-there semantics = the real flip-planner), `ARM_STEPS` (go-Pro+connect → whitelist IP → open instrument → arm → let-it-flip/disarm), `SAFETY_RAILS` (position-flip-never-stack · per-day cap · dd-breaker kill-switch · audit-before-broker · fill notes · reconnect nudge). Rendered by `apps/web/app/docs/automation/page.tsx` — syntax-highlighted script + Run-in-terminal deep link + flip table + numbered arm steps + safety-rail grid + honest-costs note (Kite order APIs free; ₹500/mo Kite data add-on to Zerodha; SuperCharts buys/redistributes no data). Wired into the docs **sidebar** (Guides), **sitemap**, and **hub card** (Bot icon). **Drift-guarded so the docs can't lie:** `tests/docs-automation.test.ts` runs the strategy through the REAL interpreter (asserts ≥1 buy AND ≥1 sell flip mark over the proven SuperTrend-flip zigzag + the drawn line) AND pins `AUTOMATION_DEFAULTS` field-for-field against BOTH `defaultArmForm()` (`apps/web/lib/automation-arm.ts`) and `buildSupertrendAutomation()` (`apps/api/src/broker/supertrend-automation.ts`: atr/mult in the indicator spec, `crosses_above/below` flip conditions, qty/product/cap/telegram in the `brokerOrder`). Change a default in the arm surface → this test fails. Additive **docs-only** — places no order; the live alert engine / Telegram / MT5 config untouched. **The OWNER arms live.** | `0346afb` (**pushed to main; VM `supercharts2` synced `git pull --ff-only` 892f736→0346afb, no lockfile change, web rebuilt, `pm2 restart supercharts-web` ONLY — docs-only web change; API/alert-engine (pid 6732, 2h uptime) never bounced**) | **735/735** (`pnpm vitest run --no-file-parallelism`; +5 new `tests/docs-automation.test.ts`: strategy script runs & emits both buy+sell flip marks + a plot · script input defaults (ATR length 10 / multiplier 3) == `AUTOMATION_DEFAULTS` · `AUTOMATION_DEFAULTS` == `defaultArmForm()` field-for-field · same defaults drive `buildSupertrendAutomation` (spec inputs atr10·mult3, buy crosses_above / sell crosses_below, brokerOrder qty1·MIS·cap5·telegram) · content completeness — ≥3 arm steps + ≥3 safety rails + a BUY & SELL flip row). api+web typecheck clean; **web production build clean** (`/docs/automation` prerendered static, fresh BUILD_ID). Browser-verified on **local** `/docs/automation`: full render — hero + "how the flip works" callout + highlighted PulseScript script + Run-in-terminal deep link + 2-row flip table + 5 numbered arm steps + 6-card safety grid, sidebar "Automate on Zerodha" active, **0 console errors**. **Prod (supercharts2) after web restart:** `curl https://supercharting.com` → **200**; `/docs/automation` → **200** (title "Automate a SuperTrend flip on your Kite instrument" + "position-flip market order" present); `sitemap.xml` includes `docs/automation`; `/api/health` ok, binance **connected** (9 subs) + kite **connected**; anon `GET /api/broker/automation` → **401** (requirePro gate intact). Live alert engine untouched (API never restarted). |
| 2026-07-14 | **GW-7 polish (b) — order-fill Telegram notifications (the owner SEES money move when an armed flip actually trades).** When the alert-order executor places (or the broker rejects) a live flip order, DM the owner. **Pure, tested** `apps/api/src/broker/order-fill-note.ts` (no DB / network / broker): `formatOrderFillNote` (🟢/🔴 + "Opened long"/"Flipped to short" + `EXCHANGE:SYMBOL` + `SIDE qty (PRODUCT)` + broker label + the broker order ids + optional /terminal link, HTML), `formatOrderRejectNote` (⚠️ + the broker's **verbatim** message, HTML-escaped so `< > &` can't break Telegram parse mode), and `buildAutomationNote(outcome, ctx)` → body-or-**null** (notifies ONLY on `placed` + `broker_rejected`; `noop`/`skipped`/positions-failed/executor-failed stay silent — no money moved, and the reconnect nudge + breaker Telegram + arm UI already cover those states, so no double-spam). Wired into the executor as an **optional injected `AlertOrderNotifier`** (`resolveBot`/`send`/`appUrl`): `execute` now splits `runFlip` (the audited flip core, unchanged) from a fire-and-forget `notifyFill` that **NEVER throws and never changes the outcome** — a wedged Telegram can't undo or hide a live order. The alert engine passes the `notify` field **only when the alert opted into Telegram delivery**, so a fill note rides the user's existing choice (never a surprise message); `main.ts` wires the notifier with a 3-tier bot resolve (alert's chosen bot id → first enabled `telegram_bots` → legacy `telegram_configs` singleton) + `sendTelegramMessage` parseMode HTML + `NEXT_PUBLIC_APP_URL`. Additive: legacy 48/144 MA-cross alerts + MT5 + the alert engine's own signal delivery untouched (no `brokerOrder` → the executor never runs). **Places NO order — every path stub-proven; the OWNER arms live.** | `33bf758` (**pushed to main; VM `supercharts2` synced `git pull --ff-only` 0c81840→33bf758, no lockfile/web change, `pm2 restart supercharts-api --update-env` ONLY — API-only behavioral increment; web (pid 4206, 3h uptime) never bounced**) | **730/730** (`pnpm vitest run --no-file-parallelism`; +12 new: `tests/broker-order-fill-note.test.ts` 6 — open→"Opened long"/flip→"Flipped to short"+🔴, app-link only when appUrl set, reject note escapes `<bad> & wrong`→`&lt;bad&gt; &amp;`, buildAutomationNote placed/rejected→note & noop/skipped/positions-failed/executor-failed→null; +6 in `tests/broker-alert-order-executor.test.ts`: placed+telegram-on→1 note w/ chosen botId honored, broker-rejected→verbatim reject note, notify-omitted→no note, noop/kill-switch→no note, send-throws→outcome still `placed`, disabled-bot→no note). api+web typecheck clean. **Prod (supercharts2) after API restart:** `curl https://supercharting.com` → **200**; `/api/health` ok, binance **connected** (9 subs) + kite **connected**; anon `GET /api/broker/automation/reconnect-status` + `GET /api/broker/automation` → **401** (requirePro gate live). API booted clean (Server listening :4000, mt5 bridge :7878; the only log lines are the anon-probe 401s). Live alert engine reloaded clean (48/144 MA-cross untouched — no `brokerOrder`). |
| 2026-07-14 | **GW-7 polish (a) — daily Kite-token reconnect nudge (the one operational gap that silently kills an armed automation).** Kite Connect access tokens invalidate once per IST morning (~06:00), so an ARMED SuperTrend flip stops trading until the owner reconnects and mints a fresh token. This surfaces + reminds. **Pure, tested** `apps/api/src/broker/reconnect-nudge.ts` (no DB / network / broker): `istTokenResetBoundary` + `istClock` (IST is a fixed +5:30, no DST → deterministic), `isTokenStale` (lastLogin < most-recent reset), `computeReconnectNudges` (active + ≥1 armed automation + stale → candidate), `formatReconnectNudge` (HTML Telegram body, broker name + armed count + optional /terminal link), and `runReconnectNudge` (composes over injected `loadArmedConnections`/`resolveBot`/`send` — **never throws, never places an order**; a wedged Telegram or missing bot → a `skipped` entry). New read route **`GET /api/broker/automation/reconnect-status`** (`requirePro`) returns the caller's `{ connected, armedAutomationCount, lastLoginAt, stale, needsReconnect, message }` so the arm UI can show a reconnect banner. **Env-gated scheduler** in `main.ts` behind `BROKER_RECONNECT_NUDGE=1` (default **OFF** — self-protecting gate like the email-verification flag; the loop ships it dormant so it can never surprise-message anyone): once per IST day at `BROKER_RECONNECT_NUDGE_HOUR` (default 9) it DMs stale+armed users via their first enabled Telegram bot (3-tier resolve → legacy singleton), deduped by IST day index, cleaned up on close. Additive: live 48/144 MA-cross alerts + MT5 + the alert engine untouched. | `0c81840` (**pushed to main; VM `supercharts2` synced `git pull --ff-only` c21a581→0c81840, no lockfile/web change, `pm2 restart supercharts-api --update-env` ONLY — API-only behavioral increment; web untouched**) | **718/718** (`pnpm vitest run --no-file-parallelism`; +17 new `tests/broker-reconnect-nudge.test.ts`: IST boundary after/before 06:00 · istClock hour + IST-midnight day roll · isTokenStale null/before/after · computeReconnectNudges active+armed+stale / skips pending / skips unarmed / skips fresh / requireArmed:false · formatReconnectNudge plural+link / singular+no-link · runReconnectNudge sends only stale+armed-with-bot & skips no-bot & never touches fresh + send-failure→skipped-not-thrown + disabled-bot→skipped; +2 route tests in `broker-automation-routes.test.ts`: anon→401 / fresh→no-reconnect / aged-3d token→needsReconnect+message; no-connection→connected:false). api+web typecheck clean. **Prod (supercharts2) after API restart:** `curl https://supercharting.com` → **200**; `/api/health` ok, binance **connected** (9 subs) + kite **connected**; anon `GET /api/broker/automation/reconnect-status` → **401** (requirePro gate live, route deployed). Scheduler dormant (flag unset on VM → nothing sends). Live alert engine reloaded clean on restart (48/144 MA-cross untouched). |
Expand Down Expand Up @@ -156,12 +157,18 @@ autocomplete) then IND-1..2. SCANNER (SCAN-1..4) ✅ · DOCS (DOCS-1..3) ✅ ·

## Questions for owner

- ✅ **PROD BACK UP + GW-7 DEPLOYED** — the VM is `supercharts2` (asia-south1-a), RUNNING; this
session confirmed `curl https://supercharting.com` → **200**, `/api/health` ok, binance+kite
**connected**. GW-7 core (`48c4276`) + builder (`c05f811`) are on the VM. **⚠️ DEPLOY TARGET IS
`supercharts2`** (the old `supercharts` VM is gone — see the PROD RESCUE row). The GCP mining
false-flag will likely recur; the durable fix is a non-GCP VPS (`infra/deploy/vm-bootstrap.sh`
one-commands any fresh Ubuntu) — needs the owner's provider account.
- ✅ **PROD BACK UP (3rd rescue 2026-07-17) — VM is now `supercharts3`** (asia-south1-a), RUNNING;
this session confirmed `curl https://supercharting.com` → **200**, `/api/broker/automation` →
**401** (Pro gate live), deployed HEAD = local HEAD `65f0adb`, DB intact (13 users, 130 watchlist
syms, 1 broker_conn, egress/ip_assignments preserved), pm2 supercharts-api+web online, Binance
backfill running. **⚠️ DEPLOY TARGET IS NOW `supercharts3`** — every `gcloud compute ssh`/deploy
uses `supercharts3` (supercharts2 was deleted after its disk was snapshotted; see rescue row).
**🔴 GCP crypto-mining FALSE-FLAG has now recurred 3× (supercharts→2→3).** Each rescue is
~15min of whack-a-mole and WILL recur. **Durable fix = move off GCP to a non-GCP VPS**
(Hetzner/DO/Contabo won't crypto-flag Binance-WS); `infra/deploy/vm-bootstrap.sh` one-commands any
fresh Ubuntu, DB migrates via the snapshot — needs the owner to provision a box. Rescue recipe if
it flags again: snapshot `supercharts3-disk` → new disk from snapshot → new `e2-standard-2` tag
`sc` → delete flagged VM (frees IP) → create with `--address=35.200.208.191`.
- ⚠️ **Deploy convention for zero-runtime-delta increments:** the GW-7 builder (`c05f811`) is a pure
module NOTHING imports at runtime yet, so this run synced the VM with `git pull --ff-only` and did
**NOT** `pm2 restart` — bouncing the live 48/144-alert engine + Binance WS for no runtime change is
Expand Down
Loading