diff --git a/apps/web/src/lib/title.ts b/apps/web/src/lib/title.ts new file mode 100644 index 0000000..ee06f7c --- /dev/null +++ b/apps/web/src/lib/title.ts @@ -0,0 +1,18 @@ +/** + * Per-route document titles. + * + * A single static title makes browser history and a wall of open tabs useless + * — every entry reads "pig" and nobody can tell the margin view from the + * pipeline. The page name leads, because that is the part a tab strip has room + * to show before truncating. + */ +import { useEffect } from 'react'; + +export function usePageTitle(title?: string): void { + useEffect(() => { + document.title = title ? `${title} · pig` : 'pig — Prime Intellect Growth'; + return () => { + document.title = 'pig — Prime Intellect Growth'; + }; + }, [title]); +} diff --git a/apps/web/src/pages/Accounts.tsx b/apps/web/src/pages/Accounts.tsx index 033ea02..eaa869f 100644 --- a/apps/web/src/pages/Accounts.tsx +++ b/apps/web/src/pages/Accounts.tsx @@ -6,6 +6,7 @@ import { useQuery } from '@tanstack/react-query'; import { Building2 } from 'lucide-react'; import { get, relativeTime } from '@/lib/api'; import { Badge, ConfidenceBadge, EmptyState, Input, Skeleton } from '@/components/ui'; +import { usePageTitle } from '@/lib/title'; interface Account { id: string; @@ -20,6 +21,7 @@ interface Account { } export function Accounts() { + usePageTitle('Accounts'); const [side, setSide] = useState<'all' | 'supply' | 'demand'>('all'); const [query, setQuery] = useState(''); diff --git a/apps/web/src/pages/Capacity.tsx b/apps/web/src/pages/Capacity.tsx index 372ea21..59d574f 100644 --- a/apps/web/src/pages/Capacity.tsx +++ b/apps/web/src/pages/Capacity.tsx @@ -9,6 +9,7 @@ import { useState } from 'react'; import { useMutation, useQuery } from '@tanstack/react-query'; import { Search, Server, Zap } from 'lucide-react'; import { compactNumber, get, money, percent, post, shortDate } from '@/lib/api'; +import { usePageTitle } from '@/lib/title'; import { Badge, Button, @@ -42,6 +43,7 @@ interface AvailabilityRow { type MatchRow = AvailabilityRow & { score: number; rationale: string[] }; export function Capacity() { + usePageTitle('Capacity'); const [tab, setTab] = useState<'available' | 'match'>('available'); return ( diff --git a/apps/web/src/pages/Margin.tsx b/apps/web/src/pages/Margin.tsx index ff5554d..731d0a1 100644 --- a/apps/web/src/pages/Margin.tsx +++ b/apps/web/src/pages/Margin.tsx @@ -8,6 +8,7 @@ import { useQuery } from '@tanstack/react-query'; import { compactNumber, get, money, moneyExact, percent } from '@/lib/api'; import { Card, CardContent, CardHeader, CardTitle, EmptyState, Skeleton, Stat } from '@/components/ui'; +import { usePageTitle } from '@/lib/title'; interface MarginReport { totals: { @@ -36,6 +37,7 @@ interface MarginReport { } export function Margin() { + usePageTitle('Margin'); const { data, isLoading } = useQuery({ queryKey: ['margin'], queryFn: () => get('/api/capacity/margin'), diff --git a/apps/web/src/pages/Overview.tsx b/apps/web/src/pages/Overview.tsx index 3edcb80..cc4a316 100644 --- a/apps/web/src/pages/Overview.tsx +++ b/apps/web/src/pages/Overview.tsx @@ -10,6 +10,7 @@ import { AlertTriangle, ArrowRight, Server, TrendingUp } from 'lucide-react'; import { Link } from 'react-router-dom'; import { compactNumber, get, money, percent, relativeTime } from '@/lib/api'; import { Badge, Card, CardContent, CardHeader, CardTitle, EmptyState, Skeleton, Stat } from '@/components/ui'; +import { usePageTitle } from '@/lib/title'; interface Dashboard { me: { name: string; teams: { team: string; role: string }[] }; @@ -44,6 +45,7 @@ interface Dashboard { } export function Overview() { + usePageTitle('Overview'); const { data, isLoading, error } = useQuery({ queryKey: ['dashboard'], queryFn: () => get('/api/dashboard'), diff --git a/apps/web/src/pages/Pipeline.tsx b/apps/web/src/pages/Pipeline.tsx index 11d0c0b..03eb1c5 100644 --- a/apps/web/src/pages/Pipeline.tsx +++ b/apps/web/src/pages/Pipeline.tsx @@ -10,6 +10,7 @@ import { useMemo, useState } from 'react'; import { useQuery } from '@tanstack/react-query'; import { get, money, relativeTime } from '@/lib/api'; import { Badge, Card, EmptyState, Skeleton } from '@/components/ui'; +import { usePageTitle } from '@/lib/title'; interface DemandDeal { id: string; @@ -126,6 +127,8 @@ function PipelineBoard React.ReactNode; }) { + usePageTitle(title); + const { data, isLoading } = useQuery({ queryKey: [endpoint], queryFn: () => get>(endpoint), diff --git a/apps/web/src/pages/Settings.tsx b/apps/web/src/pages/Settings.tsx index a8397df..4f71afb 100644 --- a/apps/web/src/pages/Settings.tsx +++ b/apps/web/src/pages/Settings.tsx @@ -6,12 +6,13 @@ * devices rather than being a per-browser quirk. */ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { Check, Monitor, Moon, Sun, Terminal } from 'lucide-react'; -import { get, patch } from '@/lib/api'; +import { Check, LogOut, Monitor, Moon, Sun, Terminal } from 'lucide-react'; +import { get, getSupabase, patch } from '@/lib/api'; import { useTheme } from '@/lib/theme'; import { getAccent, THEME_MODES, type ThemeMode } from '@pig/core'; import { Badge, Button, Card, CardContent, CardHeader, CardTitle, Input } from '@/components/ui'; import { useState } from 'react'; +import { usePageTitle } from '@/lib/title'; interface Me { id: string; @@ -23,6 +24,7 @@ interface Me { } export function Settings() { + usePageTitle('Settings'); const { data: me } = useQuery({ queryKey: ['me'], queryFn: () => get('/api/me') }); return ( @@ -34,6 +36,7 @@ export function Settings() { + ); } @@ -236,3 +239,47 @@ claude mcp add pig -- npx -y @pig/mcp`} ); } + +/** + * Signing out. + * + * There was no way to do this at all until now, which is an easy omission to + * make when developing signed in and a genuinely stranded feeling for anyone + * on a shared machine. + * + * Clearing the identity provider's session is enough: PIG holds no session of + * its own, and the auth listener in App.tsx notices and returns to sign-in + * without a reload. + */ +function SessionCard() { + const [busy, setBusy] = useState(false); + + async function signOut() { + setBusy(true); + try { + await getSupabase()?.auth.signOut(); + } finally { + // Belt and braces: if the provider call fails, a reload still lands on + // the sign-in screen rather than leaving a half-signed-out interface. + window.location.href = '/'; + } + } + + return ( + + + Session +

+ Signing out clears this browser only. API keys you have issued keep working — + revoke those separately. +

+
+ + + +
+ ); +}