diff --git a/package-lock.json b/package-lock.json
index 715fd0e..4d4796b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -48,6 +48,7 @@
"eslint": "^9.39.4",
"eslint-config-next": "^16.2.6",
"husky": "^9.1.7",
+ "kill-port": "^2.0.1",
"lint-staged": "^15.2.10",
"postcss": "^8.5.15",
"prettier": "^3.3.3",
@@ -117,7 +118,6 @@
"integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.7",
"@babel/generator": "^7.29.7",
@@ -2081,7 +2081,6 @@
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.6.tgz",
"integrity": "sha512-kIU8SLQkYWGp3pVKiYzA5OSaNF5EE03P/R8zEmmrG6XwOg5oBjXyQVVIauQ0dgau4zYhpZEhJrvIYt6oM+zZZA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@octokit/auth-token": "^5.0.0",
"@octokit/graphql": "^8.2.2",
@@ -2310,7 +2309,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.1.tgz",
"integrity": "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q==",
"license": "Apache-2.0",
- "peer": true,
"engines": {
"node": ">=8.0.0"
}
@@ -2423,7 +2421,6 @@
"resolved": "https://registry.npmjs.org/@opentelemetry/core/-/core-2.7.1.tgz",
"integrity": "sha512-QAqIj32AtK6+pEVNG7EOVxHdE06RP+FM5qpiEJ4RtDcFIqKUZHYhl7/7UY5efhwmwNAg7j8QbJVBLxMerc0+gw==",
"license": "Apache-2.0",
- "peer": true,
"dependencies": {
"@opentelemetry/semantic-conventions": "^1.29.0"
},
@@ -4616,7 +4613,6 @@
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.105.4.tgz",
"integrity": "sha512-cEnx+k49knU+qdIP7rXwR6fqEXPHZs+74xFK1R0S8MgQ7v9tbePVdGxvO03n3bPympMdJWVLadARBfU4TgNHCQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@supabase/auth-js": "2.105.4",
"@supabase/functions-js": "2.105.4",
@@ -4970,7 +4966,6 @@
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.15.6.tgz",
"integrity": "sha512-NoaMtzhxOrubeL/7UZuNTrejB4MPAJ0RpxZqXQf2qXuVlTPuG6Y8p4u9dKRaue4yjmC7ZhzVO2/Yyyn25znrPQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/node": "*",
"pg-protocol": "*",
@@ -4999,7 +4994,6 @@
"integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.2.2"
@@ -5011,7 +5005,6 @@
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"peerDependencies": {
"@types/react": "^18.0.0"
}
@@ -5096,7 +5089,6 @@
"integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.60.0",
"@typescript-eslint/types": "8.60.0",
@@ -5608,7 +5600,6 @@
"resolved": "https://registry.npmjs.org/@upstash/redis/-/redis-1.38.0.tgz",
"integrity": "sha512-wu+dZBptlLy0+MCUEoHmzrY/TnmgDey3+c7EbIGwrLqAvkP8yi5MWZHYGIFtAygmL4Bkz2TdFu+eU0vFPncIcg==",
"license": "MIT",
- "peer": true,
"dependencies": {
"uncrypto": "^0.1.3"
}
@@ -5859,7 +5850,6 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -6397,7 +6387,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.10.12",
"caniuse-lite": "^1.0.30001782",
@@ -7736,7 +7725,6 @@
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -7922,7 +7910,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -8821,6 +8808,13 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-them-args": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/get-them-args/-/get-them-args-1.3.2.tgz",
+ "integrity": "sha512-LRn8Jlk+DwZE4GTlDbT3Hikd1wSHgLMme/+7ddlqKd7ldwR6LjJgTVWzBnR01wnYGe4KgrXjg287RaI22UHmAw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/get-tsconfig": {
"version": "4.14.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz",
@@ -9912,7 +9906,6 @@
"integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -10024,6 +10017,20 @@
"json-buffer": "3.0.1"
}
},
+ "node_modules/kill-port": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/kill-port/-/kill-port-2.0.1.tgz",
+ "integrity": "sha512-e0SVOV5jFo0mx8r7bS29maVWp17qGqLBZ5ricNSajON6//kmb7qqqNnml4twNE8Dtj97UQD+gNFOaipS/q1zzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-them-args": "1.3.2",
+ "shell-exec": "1.0.2"
+ },
+ "bin": {
+ "kill-port": "cli.js"
+ }
+ },
"node_modules/language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@@ -10691,7 +10698,6 @@
"resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz",
"integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@next/env": "16.2.6",
"@swc/helpers": "0.5.15",
@@ -11263,7 +11269,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.12",
"picocolors": "^1.1.1",
@@ -11434,7 +11439,6 @@
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.9.tgz",
"integrity": "sha512-GD3qdB0x1z9xgFI6cdRD6xu2Sp2WCOEoe3mtnyB5Ee0XrrL5Pe+e4CCnJrRMnL1zYtRDZmQQVbvOttLnKDLnaw==",
"license": "Unlicense",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -11498,7 +11502,6 @@
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -11667,7 +11670,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -11680,7 +11682,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -12324,6 +12325,13 @@
"node": ">=8"
}
},
+ "node_modules/shell-exec": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/shell-exec/-/shell-exec-1.0.2.tgz",
+ "integrity": "sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/side-channel": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
@@ -13140,7 +13148,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -13794,7 +13801,6 @@
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -14043,7 +14049,6 @@
"integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -14593,7 +14598,6 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -14607,7 +14611,6 @@
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@types/chai": "^5.2.2",
"@vitest/expect": "3.2.4",
@@ -15070,7 +15073,6 @@
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/src/app/(app)/app-navbar.tsx b/src/app/(app)/app-navbar.tsx
new file mode 100644
index 0000000..fdc0db5
--- /dev/null
+++ b/src/app/(app)/app-navbar.tsx
@@ -0,0 +1,39 @@
+import Link from 'next/link';
+import { Anchor } from 'lucide-react';
+import { CommandPalette } from '@/components/command-palette';
+import { LogoutButton } from './logout-button';
+
+const NAV_LINKS = [
+ { label: 'Dashboard', href: '/dashboard' },
+ { label: 'Issues', href: '/issues' },
+ { label: 'My PRs', href: '/my-prs' },
+ { label: 'Leaderboard', href: '/leaderboard' },
+];
+
+export function AppNavbar({ handle }: { handle: string | null }) {
+ return (
+
+
+
+
+
MergeShip
+
+
+
+
+
+
+
+
+ {handle &&
{handle}}
+
+
+
+ );
+}
diff --git a/src/app/(app)/app-shell.css b/src/app/(app)/app-shell.css
new file mode 100644
index 0000000..0bec694
--- /dev/null
+++ b/src/app/(app)/app-shell.css
@@ -0,0 +1,376 @@
+/* Authenticated app — split-panel shell matching landing hero */
+
+.app-shell {
+ --ms-cream: #f2f0eb;
+ --ms-ink: #111110;
+ --ms-muted: #b8b3aa;
+ --ms-muted-dark: #8a877e;
+ --ms-green: #16a34a;
+ --ms-border: #2a2a28;
+ --ms-border-subtle: rgba(242, 240, 235, 0.08);
+ --ms-card: #1a1a18;
+ --ms-card-hover: #222220;
+
+ min-height: 100%;
+ background: var(--ms-ink);
+ color: var(--ms-cream);
+ font-family: var(--font-dm-sans), 'DM Sans', sans-serif;
+ font-weight: 300;
+ font-size: 16px;
+ line-height: 1.55;
+ -webkit-font-smoothing: antialiased;
+}
+
+.app-shell ::selection {
+ background: rgba(22, 163, 74, 0.35);
+ color: var(--ms-cream);
+}
+
+.app-shell::-webkit-scrollbar {
+ width: 6px;
+}
+.app-shell::-webkit-scrollbar-track {
+ background: var(--ms-ink);
+}
+.app-shell::-webkit-scrollbar-thumb {
+ background: #3a3a36;
+ border-radius: 0;
+}
+.app-shell::-webkit-scrollbar-thumb:hover {
+ background: #4a4a46;
+}
+
+/* Page layout */
+.app-page {
+ padding: 48px 56px 64px;
+ max-width: 1280px;
+}
+
+@media (max-width: 768px) {
+ .app-page {
+ padding: 32px 24px 48px;
+ }
+}
+
+.app-page-header {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+ margin-bottom: 48px;
+ padding-bottom: 24px;
+ border-bottom: 1px solid var(--ms-border);
+}
+
+@media (min-width: 768px) {
+ .app-page-header {
+ flex-direction: row;
+ align-items: flex-end;
+ justify-content: space-between;
+ }
+}
+
+.app-eyebrow {
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 11px;
+ font-weight: 400;
+ text-transform: uppercase;
+ letter-spacing: 0.12em;
+ color: var(--ms-muted-dark);
+ margin-bottom: 16px;
+}
+
+.app-title {
+ font-family: var(--font-dm-serif), 'DM Serif Display', serif;
+ font-size: clamp(2.25rem, 4vw, 4.5rem);
+ font-weight: 400;
+ line-height: 0.95;
+ letter-spacing: -0.04em;
+ color: var(--ms-cream);
+}
+
+.app-title-sm {
+ font-family: var(--font-dm-serif), 'DM Serif Display', serif;
+ font-size: clamp(1.75rem, 3vw, 2.5rem);
+ font-weight: 400;
+ line-height: 1;
+ letter-spacing: -0.03em;
+ color: var(--ms-cream);
+}
+
+.app-lead {
+ font-size: 1.05rem;
+ line-height: 1.55;
+ color: var(--ms-muted);
+ max-width: 560px;
+}
+
+.app-body {
+ color: var(--ms-muted);
+ font-size: 0.95rem;
+ line-height: 1.55;
+}
+
+.app-section-label {
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 10px;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+ color: var(--ms-muted-dark);
+ margin-bottom: 12px;
+}
+
+.app-card {
+ background: var(--ms-card);
+ border: 1px solid var(--ms-border);
+ transition:
+ border-color 0.2s ease,
+ background 0.2s ease;
+}
+
+.app-card:hover {
+ border-color: #3a3a36;
+ background: var(--ms-card-hover);
+}
+
+.app-divider {
+ border-color: var(--ms-border);
+}
+
+.app-footer {
+ margin-top: 96px;
+ padding-top: 24px;
+ border-top: 1px solid var(--ms-border);
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 16px;
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ color: var(--ms-muted-dark);
+}
+
+.app-footer a {
+ color: var(--ms-muted-dark);
+ text-decoration: none;
+ transition: color 0.15s ease;
+}
+
+.app-footer a:hover {
+ color: var(--ms-cream);
+}
+
+/* Split navbar — cream left / black right, aligned with sidebar + main */
+.app-navbar {
+ display: flex;
+ height: 64px;
+ z-index: 50;
+}
+
+.app-navbar-left {
+ display: flex;
+ width: 16rem;
+ shrink: 0;
+ align-items: center;
+ gap: 28px;
+ background: var(--ms-cream);
+ color: var(--ms-ink);
+ padding: 0 20px 0 24px;
+ border-bottom: 1px solid #ddd9d0;
+}
+
+.app-navbar-right {
+ display: flex;
+ flex: 1;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 20px;
+ background: var(--ms-ink);
+ color: var(--ms-cream);
+ padding: 0 24px 0 32px;
+ border-bottom: 1px solid var(--ms-border);
+}
+
+.app-navbar-logo {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ text-decoration: none;
+ color: var(--ms-ink);
+ flex-shrink: 0;
+}
+
+.app-navbar-wordmark {
+ font-family: var(--font-dm-serif), 'DM Serif Display', serif;
+ font-size: 1.25rem;
+ letter-spacing: -0.01em;
+ line-height: 1;
+}
+
+.app-navbar-links {
+ align-items: center;
+ gap: 28px;
+ min-width: 0;
+}
+
+.app-navbar-link {
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 0.7rem;
+ font-weight: 400;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ms-ink);
+ text-decoration: none;
+ position: relative;
+ padding: 4px 0;
+ white-space: nowrap;
+ transition: color 0.15s ease;
+}
+
+.app-navbar-link::after {
+ content: '';
+ position: absolute;
+ left: 0;
+ bottom: 0;
+ height: 1px;
+ width: 0;
+ background: var(--ms-ink);
+ transition: width 220ms ease;
+}
+
+.app-navbar-link:hover::after {
+ width: 100%;
+}
+
+.app-navbar-user {
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 0.72rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ color: var(--ms-muted);
+ max-width: 140px;
+}
+
+.app-navbar-btn {
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 0.72rem;
+ font-weight: 400;
+ text-transform: uppercase;
+ letter-spacing: 0.1em;
+ padding: 11px 18px;
+ border: 1px solid var(--ms-cream);
+ background: transparent;
+ color: var(--ms-cream);
+ cursor: pointer;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ transition:
+ background 0.2s ease,
+ color 0.2s ease;
+ white-space: nowrap;
+}
+
+.app-navbar-btn:hover {
+ background: var(--ms-cream);
+ color: var(--ms-ink);
+}
+
+/* Sidebar */
+.app-sidebar {
+ background: var(--ms-cream);
+ color: var(--ms-ink);
+ border-right: 1px solid #ddd9d0;
+}
+
+.app-sidebar-divider {
+ height: 1px;
+ background: #ddd9d0;
+}
+
+.app-nav-section {
+ padding: 0 12px 10px;
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 9px;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+ color: var(--ms-green);
+}
+
+.app-nav-item {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ padding: 7px 10px 7px 8px;
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 11px;
+ font-weight: 400;
+ letter-spacing: 0.07em;
+ text-transform: uppercase;
+ text-decoration: none;
+ color: var(--ms-ink);
+ border-left: 2px solid transparent;
+ transition:
+ background 0.15s ease,
+ color 0.15s ease,
+ border-color 0.15s ease;
+}
+
+.app-nav-item svg {
+ width: 14px;
+ height: 14px;
+ flex-shrink: 0;
+ color: #9b9790;
+ transition: color 0.15s ease;
+}
+
+.app-nav-item:hover:not(.app-nav-item--active) {
+ background: rgba(17, 17, 16, 0.05);
+ color: var(--ms-ink);
+}
+
+.app-nav-item:hover:not(.app-nav-item--active) svg {
+ color: #6b6860;
+}
+
+.app-nav-item--active {
+ background: var(--ms-ink);
+ color: var(--ms-cream);
+ border-left-color: var(--ms-green);
+}
+
+.app-nav-item--active svg {
+ color: var(--ms-green);
+}
+
+.app-sidebar-action {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ gap: 8px;
+ padding: 7px 10px;
+ font-family: var(--font-dm-mono), 'DM Mono', monospace;
+ font-size: 11px;
+ font-weight: 400;
+ letter-spacing: 0.07em;
+ text-transform: uppercase;
+ color: #6b6860;
+ background: none;
+ border: none;
+ cursor: pointer;
+ text-align: left;
+ transition: color 0.15s ease;
+}
+
+.app-sidebar-action:hover {
+ color: var(--ms-ink);
+}
+
+.app-sidebar-action svg {
+ width: 13px;
+ height: 13px;
+ flex-shrink: 0;
+}
diff --git a/src/app/(app)/dashboard/loading.tsx b/src/app/(app)/dashboard/loading.tsx
index a0ee2b6..9067afd 100644
--- a/src/app/(app)/dashboard/loading.tsx
+++ b/src/app/(app)/dashboard/loading.tsx
@@ -1,6 +1,6 @@
export default function DashboardLoading() {
return (
-
+
diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx
index 85645bf..c8fde3a 100644
--- a/src/app/(app)/dashboard/page.tsx
+++ b/src/app/(app)/dashboard/page.tsx
@@ -60,56 +60,53 @@ export default async function DashboardPage() {
- {/* Main Columns */}
-
- {/* Left Column */}
-
-
}>
-
-
+ {/* Stats Row */}
+
}>
+
+
-
}>
-
-
-
+ {/* Main Columns */}
+
+ {/* Left Column */}
+
+
}>
+
+
- {/* Right Column */}
-
- }>
-
-
- }>
-
-
-
+
}>
+
+
- {/* Footer */}
-
+ {/* Right Column */}
+
+ }>
+
+
+ }>
+
+
+
+
+
);
}
function NotConfigured() {
return (
-
+
-
Dashboard not configured
-
Auth isn't wired on this deployment yet.
+
Dashboard not configured
+
Auth isn't wired on this deployment yet.
);
diff --git a/src/app/(app)/dashboard/sync-button.tsx b/src/app/(app)/dashboard/sync-button.tsx
index d885789..16cb2de 100644
--- a/src/app/(app)/dashboard/sync-button.tsx
+++ b/src/app/(app)/dashboard/sync-button.tsx
@@ -87,14 +87,18 @@ export function SyncButton({ lastSyncedAt }: Props) {
-
+
{formatSyncedAt(localSyncedAt)}
diff --git a/src/app/(app)/error.tsx b/src/app/(app)/error.tsx
index dafd0ad..77fc30e 100644
--- a/src/app/(app)/error.tsx
+++ b/src/app/(app)/error.tsx
@@ -2,21 +2,16 @@
export default function Error({ reset }: { reset: () => void }) {
return (
-
-
-
- System Error
-
-
-
Something went wrong.
-
-
- An unexpected error occurred while loading this page.
-
-
+
+
+
System Error
+
Something went wrong.
+
An unexpected error occurred while loading this page.
diff --git a/src/app/(app)/help-inbox/loading.tsx b/src/app/(app)/help-inbox/loading.tsx
index e6ab74d..bb8f164 100644
--- a/src/app/(app)/help-inbox/loading.tsx
+++ b/src/app/(app)/help-inbox/loading.tsx
@@ -1,6 +1,6 @@
export default function HelpInboxPageLoading() {
return (
-
+
diff --git a/src/app/(app)/help-inbox/page.tsx b/src/app/(app)/help-inbox/page.tsx
index 73cbdd3..76eabaf 100644
--- a/src/app/(app)/help-inbox/page.tsx
+++ b/src/app/(app)/help-inbox/page.tsx
@@ -56,7 +56,7 @@ export default async function HelpInboxPage() {
const sb = await getServerSupabase();
if (!sb) {
return (
-
+
);
@@ -70,7 +70,7 @@ export default async function HelpInboxPage() {
const service = getServiceSupabase();
if (!service) {
return (
-
+
Service role not configured.
);
@@ -159,7 +159,7 @@ export default async function HelpInboxPage() {
}
return (
-
+
Help inbox
diff --git a/src/app/(app)/issues/loading.tsx b/src/app/(app)/issues/loading.tsx
index 4e37d73..b6fffc3 100644
--- a/src/app/(app)/issues/loading.tsx
+++ b/src/app/(app)/issues/loading.tsx
@@ -1,6 +1,6 @@
export default function IssuesPageLoading() {
return (
-
+
diff --git a/src/app/(app)/issues/page.tsx b/src/app/(app)/issues/page.tsx
index eda5d2c..bb40ce8 100644
--- a/src/app/(app)/issues/page.tsx
+++ b/src/app/(app)/issues/page.tsx
@@ -20,7 +20,9 @@ export default async function IssuesPage({ searchParams }: { searchParams: Searc
const sb = await getServerSupabase();
if (!sb)
return (
- Not configured
+
);
const {
@@ -111,24 +113,22 @@ export default async function IssuesPage({ searchParams }: { searchParams: Searc
const repoOptions: RepoOption[] = repoResult.ok ? repoResult.data : [];
return (
-
-
-
-
- 02 / ISSUES
-
- Browse Issues
-
-
- {linkedRecs.length > 0 && (
-
- )}
-
-
-
+
+
+
+
02 / Issues
+
Browse Issues
+
+
+
+ {linkedRecs.length > 0 && (
+
+ )}
+
+
);
}
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx
index 6fcfa9e..4839021 100644
--- a/src/app/(app)/layout.tsx
+++ b/src/app/(app)/layout.tsx
@@ -1,4 +1,3 @@
-import Link from 'next/link';
import { redirect } from 'next/navigation';
import { getServerSupabase } from '@/lib/supabase/server';
import { getServiceSupabase } from '@/lib/supabase/service';
@@ -7,6 +6,8 @@ import { LogoutButton } from './logout-button';
import { CommandPalette } from '@/components/command-palette';
import { isUserMaintainer } from '@/lib/maintainer/detect';
import { ThemeToggle } from './theme-toggle';
+import { AppNavbar } from './app-navbar';
+import './app-shell.css';
export default async function AppLayout({ children }: { children: React.ReactNode }) {
const sb = await getServerSupabase();
@@ -39,48 +40,58 @@ export default async function AppLayout({ children }: { children: React.ReactNod
}
return (
-
- {/* Sidebar */}
-