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
10 changes: 6 additions & 4 deletions apps/cli/src/tui/statusFreshness.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,22 +207,24 @@ describe('computeStatusFreshness — stale payload', () => {

it('badges stale once age exceeds the threshold', () => {
const now = 1_700_000_000_000;
const ageMs = TRADE_STALE_AFTER_MS + 42_000; // well past the threshold
const out = computeStatusFreshness({
...BASE,
dataLoading: false,
lastTickMs: now - (TRADE_STALE_AFTER_MS + 42_000), // 52s past last tick
lastTickMs: now - ageMs,
nowMs: now,
});
const sec = Math.round(ageMs / 1000);
expect(out.isStale).toBe(true);
expect(out.staleSec).toBe(52);
expect(out.freshnessStr).toContain('stale 52s');
expect(out.staleSec).toBe(sec);
expect(out.freshnessStr).toContain(`stale ${sec}s`);
});

it('renders real (stale) PnL so the operator can still see last-known numbers', () => {
const now = 1_700_000_000_000;
const out = computeStatusFreshness({
dataLoading: false,
lastTickMs: now - 30_000,
lastTickMs: now - (TRADE_STALE_AFTER_MS + 5_000), // clearly past the stale threshold
nowMs: now,
realizedPnl: 7.0,
realizedPnlPct: 1.1,
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/app/api/auth/signup/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ export async function POST(req: NextRequest) {
);
}

if (!data.user) return Response.json({ error: 'no user returned' }, { status: 500 });

const payload = {
user: { id: data.user.id, email: data.user.email },
session: data.session && {
Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/app/console/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Image from 'next/image';
import { createServerSupabase } from '@/lib/supabase';
import { ConsoleClient } from './console-client';
import { RenewalBanner } from '@/components/renewal-banner';
import { SignOutForm } from '@/components/sign-out-form';

export const dynamic = 'force-dynamic';

Expand All @@ -26,9 +27,7 @@ export default async function ConsolePage() {
<Link href="/store" className="text-zinc-400 hover:text-zinc-200">Store</Link>
<Link href="/settings" className="text-zinc-400 hover:text-zinc-200">Settings</Link>
<span className="text-zinc-400">{user.email}</span>
<form action="/api/auth/logout" method="POST">
<button className="text-zinc-500 hover:text-zinc-300">Sign out</button>
</form>
<SignOutForm className="text-zinc-500 hover:text-zinc-300" />
</div>
</nav>
<RenewalBanner userId={user.id} />
Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TradingChart } from './trading-chart';
import { DashboardSummary } from './dashboard-summary';
import { GrowthProjection } from './growth-projection';
import { RenewalBanner } from '@/components/renewal-banner';
import { SignOutForm } from '@/components/sign-out-form';

export const dynamic = 'force-dynamic';

Expand All @@ -29,9 +30,7 @@ export default async function DashboardPage() {
<Link href="/console" className="text-sm text-orange-300 hover:text-orange-200">Console →</Link>
<Link href="/settings" className="text-sm text-zinc-400 hover:text-zinc-200">Settings</Link>
<span className="text-sm text-zinc-400">{user.email}</span>
<form action="/api/auth/logout" method="POST">
<button className="text-sm text-zinc-500 hover:text-zinc-300 transition">Sign out</button>
</form>
<SignOutForm className="text-sm text-zinc-500 hover:text-zinc-300 transition" />
</div>
</nav>

Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/app/dashboard/projections/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { redirect } from 'next/navigation';
import Link from 'next/link';
import Image from 'next/image';
import { ProjectionsClient } from './projections-client';
import { SignOutForm } from '@/components/sign-out-form';

export const dynamic = 'force-dynamic';

Expand All @@ -25,9 +26,7 @@ export default async function ProjectionsPage() {
<Link href="/console" className="text-sm text-orange-300 hover:text-orange-200">Console →</Link>
<Link href="/settings" className="text-sm text-zinc-400 hover:text-zinc-200">Settings</Link>
<span className="text-sm text-zinc-400">{user.email}</span>
<form action="/api/auth/logout" method="POST">
<button className="text-sm text-zinc-500 hover:text-zinc-300 transition">Sign out</button>
</form>
<SignOutForm className="text-sm text-zinc-500 hover:text-zinc-300 transition" />
</div>
</nav>

Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
UniswapLogo, ZeroExLogo, OneInchLogo, JupiterLogo, PumpFunLogo,
UniswapV4Logo, PancakeSwapLogo, RaydiumLogo, OrcaLogo, AerodromeLogo,
} from './_components/brand-logos';
import { SignOutForm } from '@/components/sign-out-form';

export const dynamic = 'force-dynamic';

Expand Down Expand Up @@ -127,9 +128,7 @@ export default async function LandingPage() {
<Link href="/console" className="text-sm text-orange-300 hover:text-orange-200 transition">Console</Link>
<Link href="/settings" className="text-sm text-zinc-400 hover:text-zinc-200 transition">Settings</Link>
<span className="text-sm text-zinc-500">{user.email}</span>
<form action="/api/auth/logout" method="POST">
<button className="text-sm text-zinc-500 hover:text-zinc-300 transition">Sign out</button>
</form>
<SignOutForm className="text-sm text-zinc-500 hover:text-zinc-300 transition" />
</>
) : (
<>
Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/app/settings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Link from 'next/link';
import Image from 'next/image';
import { createServerSupabase } from '@/lib/supabase';
import { SettingsClient } from './settings-client';
import { SignOutForm } from '@/components/sign-out-form';

export const dynamic = 'force-dynamic';

Expand All @@ -24,9 +25,7 @@ export default async function SettingsPage() {
</div>
<div className="flex items-center gap-4 text-sm">
<span className="text-zinc-400">{user.email}</span>
<form action="/api/auth/logout" method="POST">
<button className="text-zinc-500 hover:text-zinc-300">Sign out</button>
</form>
<SignOutForm className="text-zinc-500 hover:text-zinc-300" />
</div>
</nav>
<SettingsClient />
Expand Down
5 changes: 2 additions & 3 deletions apps/web/src/app/store/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { PLUGIN_CATALOG, type CatalogEntry } from '@b1dz/core';
import { createServerSupabase } from '@/lib/supabase';
import { coinpayConfigured } from '@/lib/coinpay-client';
import { InstallButton } from './install-button';
import { SignOutForm } from '@/components/sign-out-form';

export const metadata: Metadata = {
title: 'b1dz Store — Plugin Marketplace',
Expand Down Expand Up @@ -78,9 +79,7 @@ export default async function StorePage() {
<Link href="/dashboard" className="text-sm text-zinc-400 hover:text-zinc-200 transition">Dashboard</Link>
<Link href="/settings" className="text-sm text-zinc-400 hover:text-zinc-200 transition">Settings</Link>
<span className="text-sm text-zinc-500">{user.email}</span>
<form action="/api/auth/logout" method="POST">
<button className="text-sm text-zinc-500 hover:text-zinc-300 transition">Sign out</button>
</form>
<SignOutForm className="text-sm text-zinc-500 hover:text-zinc-300 transition" />
</>
) : (
<>
Expand Down
23 changes: 23 additions & 0 deletions apps/web/src/components/sign-out-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client';

/**
* Sign-out button that clears this browser's b1dz client-side cache before the
* server logout. Logout is a server form POST (clears the session cookie), which
* can't touch localStorage — so without this, cached dashboard state (e.g.
* b1dz:source-state:*) would linger for the next account on a shared browser.
* Defense-in-depth on top of the per-user cache keying.
*/
export function SignOutForm({ className }: { className?: string }) {
const clearClientCache = () => {
try {
for (const key of Object.keys(window.localStorage)) {
if (key.startsWith('b1dz:')) window.localStorage.removeItem(key);
}
} catch { /* private mode / quota */ }
};
return (
<form action="/api/auth/logout" method="POST" onSubmit={clearClientCache}>
<button className={className}>Sign out</button>
</form>
);
}
6 changes: 4 additions & 2 deletions packages/core/src/runtime-cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ describe('stripLiveSourceState', () => {
},
],
},
// Heavy/noisy tick data should still be stripped from DB fallback.
// Heavy/noisy tick data (prices, rawLog) is stripped from the DB fallback.
prices: [{ exchange: 'kraken', pair: 'BTC-USD', bid: 101, ask: 102 }],
rawLog: [{ at: 'now', text: 'noise' }],
// tradeState is intentionally KEPT: closedTrades is persistent history the
// UI needs even when the runtime cache has expired.
tradeState: { closedTrades: [{ pair: 'BTC-USD' }] },
};

Expand All @@ -42,6 +44,6 @@ describe('stripLiveSourceState', () => {
expect(stripped.tradeStatus).toEqual(payload.tradeStatus);
expect(stripped.prices).toBeUndefined();
expect(stripped.rawLog).toBeUndefined();
expect(stripped.tradeState).toBeUndefined();
expect(stripped.tradeState).toEqual(payload.tradeState);
});
});
1 change: 1 addition & 0 deletions packages/core/src/runtime-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const LIVE_SOURCE_STATE_FIELDS = new Set([
'activityLog',
'openOrders',
'opportunities',
'prices',
'rawLog',
'recentTrades',
'signals',
Expand Down
4 changes: 2 additions & 2 deletions packages/source-dealdash/src/strategy/decide.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ function ctxBase(over: Partial<DecisionContext>): DecisionContext {
}

const book = (r: { decisions: Decision[] }, id: number) =>
r.decisions.find(d => d.kind === 'book' && d.auctionId === id);
r.decisions.find((d): d is Extract<Decision, { kind: 'book' }> => d.kind === 'book' && d.auctionId === id);
const cancel = (r: { decisions: Decision[] }, id: number) =>
r.decisions.find(d => d.kind === 'cancel' && d.auctionId === id);
r.decisions.find((d): d is Extract<Decision, { kind: 'cancel' }> => d.kind === 'cancel' && d.auctionId === id);

// ---------- balance state transitions ----------

Expand Down
Loading