Merge feat/reskin-tangled: reskin + Tangled morphism fixes

Author: Aaron Steven White
Commit ee055a221d369f0718f71c38bc7651728b181274
Structural diff unavailable

These commits were pushed via plain git push, so no pre-parsed schemas are available. Install git-remote-cospan and re-push via panproto:// to see scope-level changes, breaking change detection, and semantic diffs.

brew install panproto/tap/git-remote-cospan
37 files changed +1184 -745
@@ -1,33 +1,112 @@
11 @import "tailwindcss";
2+@import url('https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=DM+Mono:wght@300;400;500&display=swap');
23 
34 @theme {
4-	--color-surface-0: oklch(0.13 0.01 260);
5-	--color-surface-1: oklch(0.16 0.01 260);
6-	--color-surface-2: oklch(0.20 0.01 260);
7-	--color-border: oklch(0.28 0.01 260);
8-	--color-accent: oklch(0.65 0.15 250);
9-	--color-accent-hover: oklch(0.70 0.15 250);
10-	--color-text-primary: oklch(0.93 0.01 260);
11-	--color-text-secondary: oklch(0.65 0.01 260);
12-	--color-breaking: oklch(0.65 0.20 25);
13-	--color-compatible: oklch(0.65 0.17 145);
14-	--color-conflict: oklch(0.70 0.18 55);
15-	--color-lens: oklch(0.65 0.15 280);
16-	--font-sans: "Inter Variable", system-ui, sans-serif;
17-	--font-mono: "JetBrains Mono", "Fira Code", monospace;
5+	/* ── Palette: deep indigo-black with cold precision ── */
6+	--color-void: #07080f;
7+	--color-ground: #0c0d17;
8+	--color-surface: #12131f;
9+	--color-raised: #1a1b2a;
10+	--color-elevated: #222336;
11+	--color-line: #2a2b40;
12+	--color-line-bright: #3d3e5a;
13+
14+	/* Text: high contrast hierarchy */
15+	--color-ink: #e8e9f0;
16+	--color-caption: #8b8da3;
17+	--color-ghost: #4e5068;
18+
19+	/* Accent: a single electric blue, used sparingly */
20+	--color-focus: #5b7ff5;
21+	--color-focus-bright: #7b9aff;
22+	--color-focus-dim: #3a5ad1;
23+
24+	/* Semantic */
25+	--color-ok: #3dd68c;
26+	--color-err: #f55b6a;
27+	--color-warn: #f5c542;
28+	--color-info: #5bb8f5;
29+
30+	/* Legacy aliases */
31+	--color-breaking: #f55b6a;
32+	--color-compatible: #3dd68c;
33+	--color-conflict: #f5c542;
34+	--color-lens: #5bb8f5;
35+	--color-accent: #5b7ff5;
36+	--color-accent-hover: #7b9aff;
37+	--color-success: #3dd68c;
38+	--color-danger: #f55b6a;
39+	--color-warning: #f5c542;
40+
41+	/* Backwards compat tokens */
42+	--color-bg: #07080f;
43+	--color-surface-0: #0c0d17;
44+	--color-surface-1: #12131f;
45+	--color-surface-2: #1a1b2a;
46+	--color-border: #2a2b40;
47+	--color-border-hover: #3d3e5a;
48+	--color-text-primary: #e8e9f0;
49+	--color-text-secondary: #8b8da3;
50+	--color-text-muted: #4e5068;
51+
52+	/* Typography */
53+	--font-sans: 'DM Sans', system-ui, -apple-system, sans-serif;
54+	--font-mono: 'DM Mono', 'JetBrains Mono', 'SF Mono', monospace;
55+}
56+
57+/* ── Base ── */
58+html {
59+	-webkit-font-smoothing: antialiased;
60+	-moz-osx-font-smoothing: grayscale;
61+	font-feature-settings: 'ss01', 'ss02', 'cv01';
1862 }
1963 
2064 body {
21-	background-color: var(--color-surface-0);
22-	color: var(--color-text-primary);
65+	background: var(--color-void);
66+	color: var(--color-ink);
2367 	font-family: var(--font-sans);
68+	font-size: 14px;
69+	line-height: 1.65;
70+}
71+
72+::selection {
73+	background: oklch(0.45 0.12 260 / 0.4);
2474 }
2575 
26-/* Hide scrollbar for horizontal scroll areas while keeping scroll behavior */
27-.scrollbar-none {
28-	-ms-overflow-style: none;
29-	scrollbar-width: none;
76+/* ── Grid texture: the mathematical substrate ── */
77+.grid-texture {
78+	background-image:
79+		linear-gradient(to right, oklch(0.20 0.02 270 / 0.08) 1px, transparent 1px),
80+		linear-gradient(to bottom, oklch(0.20 0.02 270 / 0.08) 1px, transparent 1px);
81+	background-size: 40px 40px;
3082 }
31-.scrollbar-none::-webkit-scrollbar {
32-	display: none;
83+
84+.dot-grid {
85+	background-image: radial-gradient(oklch(0.35 0.02 270 / 0.25) 1px, transparent 1px);
86+	background-size: 20px 20px;
3387 }
88+
89+/* ── Glow effects ── */
90+.glow-focus {
91+	box-shadow: 0 0 0 1px var(--color-focus-dim), 0 0 20px oklch(0.45 0.12 260 / 0.15);
92+}
93+
94+/* ── Scrollbar ── */
95+.scrollbar-none { -ms-overflow-style: none; scrollbar-width: none; }
96+.scrollbar-none::-webkit-scrollbar { display: none; }
97+
98+::-webkit-scrollbar { width: 6px; }
99+::-webkit-scrollbar-track { background: transparent; }
100+::-webkit-scrollbar-thumb { background: var(--color-line); border-radius: 3px; }
101+::-webkit-scrollbar-thumb:hover { background: var(--color-line-bright); }
102+
103+/* ── Focus ring ── */
104+:focus-visible {
105+	outline: 1.5px solid var(--color-focus);
106+	outline-offset: 2px;
107+}
108+
109+/* ── Graph paper (legacy util) ── */
110+.graph-paper { background-image: radial-gradient(circle, oklch(0.30 0.008 270) 0.5px, transparent 0.5px); background-size: 24px 24px; }
111+.noise-overlay { position: relative; }
112+.noise-overlay::after { content: ''; position: absolute; inset: 0; opacity: 0.025; pointer-events: none; background: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }
@@ -1,5 +1,12 @@
11 import { xrpcQuery } from './client.js';
22 
3+export interface AlgebraicChecks {
4+	gatTypeCheck: string | null;
5+	equationVerification: string | null;
6+	lensLawCheck: string | null;
7+	breakingChangeCheck: string | null;
8+}
9+
310 export interface RefUpdate {
411 	rkey: string;
512 	repo: string;
@@ -9,8 +16,10 @@ export interface RefUpdate {
916 	protocol: string;
1017 	commitCount: number;
1118 	migrationId: string | null;
19+	lensId: string | null;
1220 	lensQuality: number | null;
1321 	breakingChangeCount: number;
22+	algebraicChecks: AlgebraicChecks | null;
1423 	committerDid: string;
1524 	committerHandle: string | null;
1625 	createdAt: string;
@@ -6,8 +6,16 @@ export interface Repo {
66 	description: string | null;
77 	protocol: string;
88 	starCount: number;
9+	forkCount: number;
910 	openIssueCount: number;
1011 	openMrCount: number;
12+	source: string;
13+	sourceUri: string | null;
14+	sourceRepo: string | null;
15+	nodeDid: string;
16+	nodeUrl: string;
17+	defaultBranch: string;
18+	visibility: string;
1119 	createdAt: string;
1220 }
1321 
@@ -23,6 +31,7 @@ interface RawRepoListResponse {
2331 
2432 export async function listRepos(params?: {
2533 	did?: string;
34+	source?: string;
2635 	limit?: number;
2736 	cursor?: string;
2837 }): Promise<RepoListResponse> {
@@ -9,19 +9,18 @@
99 
1010 <a
1111 	href="{basePath}/issues/{issue.rkey}"
12-	class="block rounded-lg border border-border bg-surface-1 p-4 transition-colors hover:border-accent"
12+	class="block rounded-lg border border-border bg-surface-1 p-4 transition-all hover:border-border-hover"
1313 >
1414 	<div class="flex items-start gap-3">
1515 		<div class="mt-0.5">
1616 			<StateBadge state={issue.state} />
1717 		</div>
1818 		<div class="min-w-0 flex-1">
19-			<h3 class="font-medium text-text-primary">{issue.title}</h3>
20-			<div class="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-secondary">
19+			<h3 class="font-semibold text-text-primary">{issue.title}</h3>
20+			<div class="mt-1.5 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-text-muted">
2121 				<span>#{issue.rkey}</span>
22-				<span>opened {timeAgo(issue.createdAt)}</span>
2322 				{#if issue.creatorHandle}
24-					<span>by {issue.creatorHandle}</span>
23+					<span>opened by {issue.creatorHandle}</span>
2524 				{/if}
2625 				{#if issue.commentCount > 0}
2726 					<span class="flex items-center gap-1">
@@ -31,6 +30,7 @@
3130 						{issue.commentCount}
3231 					</span>
3332 				{/if}
33+				<span>{timeAgo(issue.createdAt)}</span>
3434 			</div>
3535 			{#if issue.labels.length > 0}
3636 				<div class="mt-2 flex flex-wrap gap-1">
@@ -1,228 +1,67 @@
11 <script lang="ts">
2-	import { goto } from '$app/navigation';
32 	import { page } from '$app/stores';
4-	import { debounce } from '$lib/utils/debounce.js';
53 	import UserMenu from '$lib/components/auth/UserMenu.svelte';
6-	import NotificationBell from '$lib/components/shared/NotificationBell.svelte';
74 
85 	let { user }: { user?: { authenticated: boolean; did: string; handle: string; displayName?: string; avatar?: string } | null } = $props();
96 
10-	let searchValue = $state('');
11-	let mobileSearchValue = $state('');
12-	let searchInputRef = $state<HTMLInputElement | null>(null);
13-	let mobileMenuOpen = $state(false);
7+	let currentPath = $derived($page.url.pathname);
148 
15-	// Close mobile menu on navigation
16-	$effect(() => {
17-		$page.url.pathname;
18-		mobileMenuOpen = false;
19-	});
9+	const navLinks = [
10+		{ href: '/', label: 'Explore' },
11+		{ href: '/feed', label: 'Feed' },
12+		{ href: '/search', label: 'Search' },
13+	] as const;
2014 
21-	const submitSearch = debounce((q: string) => {
22-		if (q.trim()) {
23-			goto(`/search?q=${encodeURIComponent(q.trim())}`);
24-		}
25-	}, 300);
26-
27-	function handleSearchKeydown(event: KeyboardEvent) {
28-		if (event.key === 'Enter' && searchValue.trim()) {
29-			goto(`/search?q=${encodeURIComponent(searchValue.trim())}`);
30-		}
31-	}
32-
33-	function handleMobileSearchKeydown(event: KeyboardEvent) {
34-		if (event.key === 'Enter' && mobileSearchValue.trim()) {
35-			goto(`/search?q=${encodeURIComponent(mobileSearchValue.trim())}`);
36-			mobileMenuOpen = false;
37-		}
38-	}
39-
40-	function handleGlobalKeydown(event: KeyboardEvent) {
41-		const target = event.target as HTMLElement;
42-		if (
43-			target.tagName === 'INPUT' ||
44-			target.tagName === 'TEXTAREA' ||
45-			target.isContentEditable
46-		) {
47-			return;
48-		}
49-
50-		if (event.key === '/') {
51-			event.preventDefault();
52-			searchInputRef?.focus();
53-		}
54-
55-		if (event.key === 'Escape' && mobileMenuOpen) {
56-			mobileMenuOpen = false;
57-		}
58-	}
59-
60-	function toggleMobileMenu() {
61-		mobileMenuOpen = !mobileMenuOpen;
15+	function isActive(href: string): boolean {
16+		if (href === '/') return currentPath === '/';
17+		return currentPath.startsWith(href);
6218 	}
6319 </script>
6420 
65-<svelte:window onkeydown={handleGlobalKeydown} />
66-
67-<header class="border-b border-border bg-surface-1">
68-	<nav class="mx-auto flex h-12 max-w-6xl items-center justify-between px-4">
69-		<div class="flex items-center gap-6">
70-			<a href="/" class="flex items-center gap-2 text-lg font-semibold tracking-tight text-text-primary">
71-				<img src="/logo-dark.svg" alt="" class="h-6 w-6" />
72-				<span class="hidden xs:inline">Cospan</span>
73-			</a>
74-			<div class="hidden items-center gap-4 md:flex">
75-				<a href="/" class="text-sm text-text-secondary hover:text-text-primary transition-colors">
76-					Explore
77-				</a>
78-				<a href="/feed" class="text-sm text-text-secondary hover:text-text-primary transition-colors">
79-					Feed
80-				</a>
81-				<a href="/orgs" class="text-sm text-text-secondary hover:text-text-primary transition-colors">
82-					Orgs
83-				</a>
84-				<a href="/search" class="text-sm text-text-secondary hover:text-text-primary transition-colors">
85-					Search
21+<header class="sticky top-0 z-50 border-b border-line/60 bg-void/80 backdrop-blur-xl">
22+	<nav class="mx-auto flex h-14 max-w-[1200px] items-center justify-between px-6">
23+		<!-- Wordmark -->
24+		<a href="/" class="group flex items-center gap-2 font-mono text-[15px] font-medium tracking-tight text-ink">
25+			<!-- Cospan mark: three vertices, the apex brighter -->
26+			<svg class="h-[18px] w-[18px]" viewBox="0 0 18 18" fill="none">
27+				<circle cx="3" cy="14" r="1.8" fill="currentColor" opacity="0.3"/>
28+				<circle cx="9" cy="3" r="2" fill="currentColor" opacity="0.7"/>
29+				<circle cx="15" cy="14" r="1.8" fill="currentColor" opacity="0.3"/>
30+				<line x1="3" y1="14" x2="9" y2="3" stroke="currentColor" stroke-width="0.8" opacity="0.2"/>
31+				<line x1="15" y1="14" x2="9" y2="3" stroke="currentColor" stroke-width="0.8" opacity="0.2"/>
32+			</svg>
33+			cospan
34+		</a>
35+
36+		<!-- Nav -->
37+		<div class="flex items-center gap-1">
38+			{#each navLinks as link}
39+				<a
40+					href={link.href}
41+					class="relative rounded-md px-3.5 py-1.5 text-[13px] font-medium transition-all duration-150
42+						{isActive(link.href)
43+							? 'bg-raised text-ink'
44+							: 'text-ghost hover:text-caption hover:bg-surface'}"
45+				>
46+					{link.label}
8647 				</a>
87-			</div>
48+			{/each}
8849 		</div>
89-		<div class="flex items-center gap-3">
90-			<!-- Desktop search -->
91-			<div class="relative hidden md:block">
92-				<svg
93-					class="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-text-secondary"
94-					fill="none"
95-					viewBox="0 0 24 24"
96-					stroke="currentColor"
97-					stroke-width="2"
98-				>
99-					<path
100-						stroke-linecap="round"
101-						stroke-linejoin="round"
102-						d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
103-					/>
104-				</svg>
105-				<input
106-					bind:this={searchInputRef}
107-					type="text"
108-					bind:value={searchValue}
109-					oninput={() => submitSearch(searchValue)}
110-					onkeydown={handleSearchKeydown}
111-					placeholder="Search... (/)"
112-					class="w-48 rounded-md border border-border bg-surface-0 py-1 pl-8 pr-3 text-xs text-text-primary placeholder:text-text-secondary focus:border-accent focus:outline-none"
113-				/>
114-			</div>
115-			<NotificationBell />
50+
51+		<!-- Right -->
52+		<div class="flex items-center gap-2">
11653 			{#if user?.authenticated}
11754 				<a
11855 					href="/new"
119-					class="hidden rounded-md border border-border bg-surface-1 p-1.5 text-text-secondary transition-colors hover:border-accent hover:text-text-primary sm:block"
120-					title="New repository"
56+					class="flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-[12px] font-medium text-caption transition-all hover:border-line-bright hover:text-ink"
12157 				>
122-					<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
58+					<svg class="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
12359 						<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
12460 					</svg>
61+					New
12562 				</a>
12663 			{/if}
127-			<div class="hidden sm:block">
128-				<UserMenu {user} />
129-			</div>
130-
131-			<!-- Mobile hamburger -->
132-			<button
133-				onclick={toggleMobileMenu}
134-				class="rounded-md p-1.5 text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary md:hidden"
135-				aria-label="Toggle menu"
136-			>
137-				{#if mobileMenuOpen}
138-					<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
139-						<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
140-					</svg>
141-				{:else}
142-					<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
143-						<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
144-					</svg>
145-				{/if}
146-			</button>
64+			<UserMenu {user} />
14765 		</div>
14866 	</nav>
149-
150-	<!-- Mobile menu -->
151-	{#if mobileMenuOpen}
152-		<div class="border-t border-border bg-surface-1 px-4 py-3 md:hidden">
153-			<!-- Mobile search -->
154-			<div class="mb-3">
155-				<div class="relative">
156-					<svg
157-						class="pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-text-secondary"
158-						fill="none"
159-						viewBox="0 0 24 24"
160-						stroke="currentColor"
161-						stroke-width="2"
162-					>
163-						<path
164-							stroke-linecap="round"
165-							stroke-linejoin="round"
166-							d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
167-						/>
168-					</svg>
169-					<input
170-						type="text"
171-						bind:value={mobileSearchValue}
172-						onkeydown={handleMobileSearchKeydown}
173-						placeholder="Search repositories..."
174-						class="w-full rounded-md border border-border bg-surface-0 py-2 pl-8 pr-3 text-sm text-text-primary placeholder:text-text-secondary focus:border-accent focus:outline-none"
175-					/>
176-				</div>
177-			</div>
178-
179-			<!-- Mobile nav links -->
180-			<div class="space-y-1">
181-				<a href="/" class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary">
182-					Explore
183-				</a>
184-				<a href="/feed" class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary">
185-					Feed
186-				</a>
187-				<a href="/orgs" class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary">
188-					Orgs
189-				</a>
190-				<a href="/search" class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary">
191-					Search
192-				</a>
193-			</div>
194-
195-			{#if user?.authenticated}
196-				<div class="mt-3 border-t border-border pt-3 space-y-1">
197-					<a
198-						href="/new"
199-						class="flex items-center gap-2 rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary"
200-					>
201-						<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
202-							<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
203-						</svg>
204-						New repository
205-					</a>
206-					<a
207-						href="/{user.did}"
208-						class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary"
209-					>
210-						Your profile
211-					</a>
212-					<a
213-						href="/settings"
214-						class="block rounded-md px-3 py-2 text-sm text-text-secondary transition-colors hover:bg-surface-2 hover:text-text-primary"
215-					>
216-						Settings
217-					</a>
218-				</div>
219-			{:else}
220-				<div class="mt-3 border-t border-border pt-3">
221-					<div class="sm:hidden">
222-						<UserMenu {user} />
223-					</div>
224-				</div>
225-			{/if}
226-		</div>
227-	{/if}
22867 </header>
cospan · schematic version control on atproto built on AT Protocol