diff --git a/LOCAL.md b/LOCAL.md
index ddeea7a..7ffabf2 100644
--- a/LOCAL.md
+++ b/LOCAL.md
@@ -6,13 +6,15 @@ Paper desk. Live Binance last for crypto fills. NSE/Yahoo when the cash session
- **Node.js 22 LTS** — [https://nodejs.org](https://nodejs.org) (include npm)
- Windows, macOS, or Linux
-- Unzip this folder, e.g. `D:\work Dir\MeridianFinal`
+- This operator’s working copy: `D:\work Dir\MeridianFinal-local\MeridianFinal`
No Postgres required. The app uses embedded PGLite unless you set `DATABASE_URL`.
## Quick start (Windows)
-1. Unzip to `D:\work Dir\MeridianFinal`
+Working copy: **`D:\work Dir\MeridianFinal-local\MeridianFinal`**
+
+1. Open that folder
2. Double-click **`run.bat`**
- First run: `npm install` (a few minutes)
- Copies `.env.example` → `.env` if missing
diff --git a/README.md b/README.md
index d997f9d..b3aacc2 100644
--- a/README.md
+++ b/README.md
@@ -1,18 +1,27 @@
# Meridian Final
-Personal multi-asset desk (V1–V4). Paper first. Live Kite stays off.
+**Canonical product.** V2 Greeks + V3 auto desk + V4 meta-label, in one paper desk.
-- **Auto paper** — ₹10L book, farm sleeve (labels + costs) and PnL sleeve (Kelly, armed after meta promote). Binance overnight; NSE cash/F&O only in the cash session. 10× is a return target, not a promise.
+Paper first. Live Kite stays off. Mock book ₹10,00,000.
+
+Older trees are **frozen**. Do not patch V3/V4. Do not use the MeridianV4 repo.
+
+- **Signals / Paper / Paper auto-send** — Signals proposes (Would BUY). Paper waits for Approve / Skip / Size. Paper auto-send fills on its own. Live is locked.
- **Tape** — NSE, FX, COMEX/MCX, every Binance USDT pair
-- **Book** — CSV → Buy / Hold / Sell
-- **Greeks** — gamma scalping reviews
-- **Research** — natural-language names
-- **Advice** — spot / futures / options cards
+- **Book** — CSV cash ledger, separate from the paper clip book. META is n/a until the paper model promotes.
+- **Greeks** — what-if calculator (V2), not the live paper book
+- **Research** — natural-language names, or empty if the query does not match
+- **Advice** — spot / futures / options cards that respect session and promotion
-**Run on your PC:** see [LOCAL.md](LOCAL.md) (`run.bat` on Windows, `run.sh` on macOS/Linux).
+**Run on your PC:** see [LOCAL.md](LOCAL.md) (`run.bat` on Windows, `run.sh` on macOS/Linux). localhost and 127.0.0.1 both sign in.
Test login: `WQ3137` / `Test@password`
-Python engines: `meridian_final/`. Spec: [docs/BUILD_PLAN.md](docs/BUILD_PLAN.md)
+| Doc | |
+|-----|--|
+| Lineage (V1→Final) | [docs/LINEAGE.md](docs/LINEAGE.md) |
+| Build spec | [docs/BUILD_PLAN.md](docs/BUILD_PLAN.md) |
+| UX contract | [docs/UX_BACKLOG.md](docs/UX_BACKLOG.md) |
+| Python math ports | `meridian_final/` |
Kite keys never belong in git. This is personal software. You can lose the whole book. Nothing here is an order.
diff --git a/START_HERE.txt b/START_HERE.txt
index 7e6c967..2210ce9 100644
--- a/START_HERE.txt
+++ b/START_HERE.txt
@@ -3,7 +3,7 @@ MERIDIAN FINAL — run on your PC
Windows:
1. Install Node.js 22 LTS from https://nodejs.org (tick "Add to PATH")
- 2. Unzip this folder to D:\work Dir\MeridianFinal
+ 2. Working copy: D:\work Dir\MeridianFinal-local\MeridianFinal
3. Double-click run.bat
4. Open http://localhost:3000
5. Login WQ3137 / Test@password
diff --git a/docs/BUILD_PLAN.md b/docs/BUILD_PLAN.md
index 1e7091f..11c561e 100644
--- a/docs/BUILD_PLAN.md
+++ b/docs/BUILD_PLAN.md
@@ -1,9 +1,10 @@
# Meridian Final — Build Master Spec
**Last updated:** 23 Aug 2026
-**Repos:** [sanjaymaverick-cmd/MeridianFinal](https://github.com/sanjaymaverick-cmd/MeridianFinal)
-**Lineage:** V1 advisor → V2 Greeks → V3 auto desk → V4 OpenAlgo / meta-label
-**Isolation:** V3 and V4 stay frozen. Final copies math, never patches those trees.
+**Canonical repo:** [sanjaymaverick-cmd/MeridianFinal](https://github.com/sanjaymaverick-cmd/MeridianFinal)
+**Canonical product:** Meridian Final = V2 Greeks + V3 auto desk + V4 meta-label, one paper desk
+**Lineage:** [LINEAGE.md](LINEAGE.md) — V1 advisor → V2 Greeks → V3 auto desk → V4 OpenAlgo / meta-label → **this repo**
+**Isolation:** V3 and V4 stay frozen. Final copies math, never patches those trees. Do not use the MeridianV4 GitHub repo as a working tree.
---
diff --git a/docs/LINEAGE.md b/docs/LINEAGE.md
new file mode 100644
index 0000000..5882bf6
--- /dev/null
+++ b/docs/LINEAGE.md
@@ -0,0 +1,51 @@
+# Meridian Final — lineage
+
+**Canonical product:** Meridian Final
+**Canonical repo:** [sanjaymaverick-cmd/MeridianFinal](https://github.com/sanjaymaverick-cmd/MeridianFinal)
+**As of:** 23 Aug 2026
+
+**Local checkout (operator PC):** `D:\work Dir\MeridianFinal-local\MeridianFinal`
+
+V1–V4 are frozen history. New work ships **only** here. Do not patch the V3 or V4 trees. Do not use [MeridianV4](https://github.com/sanjaymaverick-cmd/MeridianV4) as a working repo.
+
+```
+V1 advisor → V2 Greeks → V3 auto desk → V4 OpenAlgo / meta-label
+ ↓
+ Meridian Final (this repo)
+ TypeScript desk + meridian_final/ Python ports
+```
+
+## What each version contributed
+
+| Version | What it was | What Final kept | Isolation |
+|---------|-------------|-----------------|-----------|
+| **V1** | Five-factor advisor. Book Buy / Hold / Sell. | Scoring on the imported cash book. | Math copied into `meridian_final/scoring.py` + Book UI. |
+| **V2** | Greeks book, gamma scalping. | `/greeks` what-if calculator. Gamma-scalp reviews. Daily PnL = theta. Harvest = ½ Γ (ΔS)². | Copied into `meridian_final/greeks.py`, `gamma_scalp.py`. Not the live paper book. |
+| **V3** | Auto paper desk. Short holds, EOD flatten. Honest 199-row meta set (21.6% win, ₹50k). | Farm sleeve, fill tape, gamma rehedge reviews. | V3 files (`build_meta_labels.py`, `meridian_v3_*`) are **read-only**. Never edit. |
+| **V4** | OpenAlgo strategy host. mlfinlab-style meta. Longer-hold PnL sleeve. Promotion gates. | Decision-engine math. Farm 90s labels vs PnL (hard stop / 2.2R / trail). Promote only on paper fills. | V4 tree frozen. Ports in `meridian_final/decision.py`. OpenAlgo is optional, not the running desk. |
+
+## What Final is (and is not)
+
+**Is:** one personal paper desk. Mock ₹10,00,000. Signals / Paper / Paper auto-send. Live locked. Kite disarmed. Research that answers the query or returns empty. Promotion in English.
+
+**Is not:** a live broker. An OpenAlgo plugin. A continuation of the MeridianV4 GitHub repo. An order.
+
+## Promotion (from V4, tightened)
+
+PnL sleeve stays flat until **all** of: n ≥ 2000, test AUC ≥ 0.55, hit > 52%. Quality holds (≥300s) counted separately from 90s farm time-stops. Synth-only AUC does not promote. Book META is `n/a` until then — never fake 0%.
+
+## Execution path
+
+```
+Research (NL + universe + Grok when signed in)
+ ↓
+Scoring (V1) + Meta (V4 logistic, if promoted)
+ ↓
+Decision engine (gates, size, manage)
+ ↓
+Greeks / gamma reviews (V2)
+ ↓
+OMS: paper desk now → Kite / OpenAlgo later, only if LIVE_OK + static IP
+```
+
+Spec: [BUILD_PLAN.md](BUILD_PLAN.md). UX contract: [UX_BACKLOG.md](UX_BACKLOG.md). Local run: [../LOCAL.md](../LOCAL.md).
diff --git a/docs/UX_BACKLOG.md b/docs/UX_BACKLOG.md
new file mode 100644
index 0000000..54bdf35
--- /dev/null
+++ b/docs/UX_BACKLOG.md
@@ -0,0 +1,27 @@
+# Meridian Final — UX backlog (paper desk)
+
+Shipped on this branch. Constraint: mock capital. Analyzer / Paper only. Kite disarmed. Never “Arm”.
+
+| ID | Item | Where |
+|---|---|---|
+| UX-01 | Identity strip: MOCK · mode · ENGINE ON\|PAUSED · KITE DISARMED. Modes Signals / Paper / Paper auto-send / Live (locked). No `:live` fill suffix. | `desk-shell`, `ux-copy` |
+| UX-02 | Pause ≠ Flatten. Pause keeps exits. Flatten confirms. Resume paper. Duplicate Kill gone. | `desk-shell`, `paper-engine` |
+| UX-03 | Promotion verdict in English with n / AUC / hit pass-fail. Quality vs time-stop share. | `promotion-strip` |
+| UX-04 | Book META is n/a until promote. Factor Buy decoupled. Two ledgers. | `portfolio.ts`, `portfolio.tsx` |
+| UX-05 | localhost and 127.0.0.1 on :3000 and :8080. Guest cannot Pause/Reset/mode. Origin error in English. | `auth/server`, `login`, `desk-shell` |
+| UX-06 | Paper mode pending Approve / Skip / Size (15s auto-skip). Flatten-one on clips. Auto-send is explicit. | `paper-engine`, `auto.tsx` |
+| UX-07 | Signals last scan stays live with Would BUY / Would SELL. No new fills. | `paper-engine` tick |
+| UX-08 | Research returns empty + reason on mismatch. Guest heuristic labelled. Run history. | `research-rank`, `research.tsx` |
+| UX-09 | Last decision sentence. Heat vs cap. Farm hold countdown. PnL idle named. | `auto.tsx` |
+| UX-10 | English fill/scan reasons. Quote source is not the word live. | `ux-copy` |
+| UX-11 | Reset confirms. Not in the header thumb zone next to Pause. | `auto.tsx` |
+| UX-12 | Download is the fit set; hold class labelled; count mismatch noted. | `auto.tsx`, `listSamples` |
+| UX-13 | Weekend/cash-closed advice does not work NSE cash. Unpromoted meta is not a 0.55 gate to work. | `advice.ts` |
+| UX-14 | Mode chips on mobile. Clip cards under md. No forced 720px table. | `desk-shell`, `auto.tsx` |
+| UX-15 | Fresh desk starts Signals + paused. Start paper is labelled. | `emptyEngine`, header |
+| UX-16 | Greeks titled what-if / calculator. Snapshot marks. | `greeks.tsx` |
+| UX-17 | One timestamp column on Command fills. | `index.tsx` |
+| UX-18 | Header is the mode source. Auto chips labelled mirrors. | `desk-shell`, `auto.tsx` |
+| UX-19 | Book Block. Research Watch / Ignore. Greeks Queue paper hedge / Dismiss. | `desk.ts`, pages |
+
+Preserve: Kite off. Kill/Pause in the header. Pause does not flatten. Resume never sneaks to live.
diff --git a/src/components/auto-engine.tsx b/src/components/auto-engine.tsx
index a08976d..b18e516 100644
--- a/src/components/auto-engine.tsx
+++ b/src/components/auto-engine.tsx
@@ -1,6 +1,18 @@
import { useEffect } from "react";
import { useQuery } from "@tanstack/react-query";
-import { getPaperBook, resetPaperBook, setPaperFlags } from "@/lib/server/desk";
+import {
+ getPaperBook,
+ resetPaperBook,
+ setPaperFlags,
+ flattenPaperBook,
+ flattenPaperClip,
+ approvePaperPending,
+ skipPaperPending,
+ setPaperBlock,
+ setPaperWatch,
+ queuePaperHedge,
+ dismissPaperHedge,
+} from "@/lib/server/desk";
import { useDesk } from "@/lib/desk-store";
export function AutoEngine() {
@@ -20,7 +32,6 @@ export function AutoEngine() {
}
export async function setDeskMode(mode: "advisory" | "paper" | "auto") {
- // Optimistic UI — engine used to force mode=auto on every poll; that is fixed.
useDesk.getState().setMode(mode);
try {
const book = await setPaperFlags({ data: { mode } });
@@ -48,3 +59,75 @@ export async function resetDeskPaper() {
console.error("[desk] reset failed", err);
}
}
+
+export async function flattenDeskPaper() {
+ try {
+ const book = await flattenPaperBook();
+ useDesk.getState().hydratePaper(book);
+ } catch (err) {
+ console.error("[desk] flatten failed", err);
+ }
+}
+
+export async function flattenDeskClip(symbol: string) {
+ try {
+ const book = await flattenPaperClip({ data: { symbol } });
+ useDesk.getState().hydratePaper(book);
+ } catch (err) {
+ console.error("[desk] flatten clip failed", err);
+ }
+}
+
+export async function approveDeskPending(id: string, sizePct?: number) {
+ try {
+ const book = await approvePaperPending({ data: { id, sizePct } });
+ useDesk.getState().hydratePaper(book);
+ } catch (err) {
+ console.error("[desk] approve failed", err);
+ }
+}
+
+export async function skipDeskPending(id: string) {
+ try {
+ const book = await skipPaperPending({ data: { id } });
+ useDesk.getState().hydratePaper(book);
+ } catch (err) {
+ console.error("[desk] skip failed", err);
+ }
+}
+
+export async function blockDeskSymbol(symbol: string, blocked: boolean) {
+ try {
+ const book = await setPaperBlock({ data: { symbol, blocked } });
+ useDesk.getState().hydratePaper(book);
+ } catch (err) {
+ console.error("[desk] block failed", err);
+ }
+}
+
+export async function watchDeskSymbol(symbol: string, watch: boolean) {
+ try {
+ const book = await setPaperWatch({ data: { symbol, watch } });
+ useDesk.getState().hydratePaper(book);
+ } catch (err) {
+ console.error("[desk] watch failed", err);
+ }
+}
+
+export async function queueDeskHedge(symbol: string, note: string, from = "greeks") {
+ try {
+ const book = await queuePaperHedge({ data: { symbol, note, from } });
+ useDesk.getState().hydratePaper(book);
+ } catch (err) {
+ console.error("[desk] queue hedge failed", err);
+ }
+}
+
+export async function dismissDeskHedge() {
+ try {
+ const book = await dismissPaperHedge();
+ useDesk.getState().hydratePaper(book);
+ } catch (err) {
+ console.error("[desk] dismiss hedge failed", err);
+ }
+}
diff --git a/src/components/confirm.tsx b/src/components/confirm.tsx
new file mode 100644
index 0000000..feea3ad
--- /dev/null
+++ b/src/components/confirm.tsx
@@ -0,0 +1,38 @@
+import { Button } from "@/components/ui/button";
+
+export function Confirm({
+ title,
+ body,
+ action,
+ danger,
+ onCancel,
+ onOk,
+}: {
+ title: string;
+ body: string;
+ action: string;
+ danger?: boolean;
+ onCancel: () => void;
+ onOk: () => void;
+}) {
+ return (
+
+
+
{title}
+
{body}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/desk-shell.tsx b/src/components/desk-shell.tsx
index a049a05..091ea47 100644
--- a/src/components/desk-shell.tsx
+++ b/src/components/desk-shell.tsx
@@ -1,14 +1,20 @@
-import type { ReactNode } from "react";
+import { useState, type ReactNode } from "react";
import { Link, useRouterState } from "@tanstack/react-router";
import { Activity, BookOpen, Compass, LayoutDashboard, LineChart, Sigma } from "lucide-react";
+import { toast } from "sonner";
import { UserButton } from "@/lib/auth/gates";
import { useCurrentUserState } from "@/lib/auth/use-current-user";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
-import { useDesk } from "@/lib/desk-store";
+import { Confirm } from "@/components/confirm";
+import { useDesk, type DeskMode } from "@/lib/desk-store";
import { cn } from "@/lib/utils";
-import { AutoEngine, setDeskKilled, setDeskMode } from "@/components/auto-engine";
+import { inr } from "@/lib/utils";
+import { PAPER_BUDGET } from "@/lib/meridian/decision";
+import { AutoEngine, setDeskKilled, setDeskMode, flattenDeskPaper } from "@/components/auto-engine";
import { QuotesHydrator } from "@/components/quotes-hydrator";
+import { MODE_LABEL } from "@/lib/ux-copy";
+import { formatIstStamp } from "@/lib/utils";
const NAV = [
{ to: "/", label: "Command", icon: LayoutDashboard },
@@ -19,23 +25,42 @@ const NAV = [
{ to: "/research", label: "Research", icon: Compass },
] as const;
+const MODES: DeskMode[] = ["advisory", "paper", "auto"];
+
export function DeskShell({ children }: { children: ReactNode }) {
const pathname = useRouterState({ select: (s) => s.location.pathname });
const { user, isPending } = useCurrentUserState();
const mode = useDesk((s) => s.mode);
const killed = useDesk((s) => s.killed);
+ const pausedAt = useDesk((s) => s.pausedAt);
+ const [askFlatten, setAskFlatten] = useState(false);
+ const guest = !isPending && !user;
+ const operator = Boolean(user);
+ const engineOn = !killed;
+ const modeLabel = MODE_LABEL[mode];
+
+ function guard(fn: () => void) {
+ if (!operator) {
+ toast.message("Guest view only. Sign in to Pause, Flatten, or change mode.");
+ return;
+ }
+ fn();
+ }
return (
-
+
Meridian
Final
-