+ {/* Base gradient */}
+
+ {/* Subtle dark veil for contrast */}
+
+ {/* Frosted glass shimmer – IMPORTANT: pointer-events-none so it never blocks clicks */}
+
{/* App content above blur */}
+ 1 Connect
+ 2 Configure
+ 3 Secrets
+ 4 Dashboard
+ 5 Jenkins
+
-
+
} />
} />
diff --git a/client/src/components/ui/Button.tsx b/client/src/components/ui/Button.tsx
index 65d4fcd..49a8a6b 100644
--- a/client/src/components/ui/Button.tsx
+++ b/client/src/components/ui/Button.tsx
@@ -19,6 +19,16 @@ const buttonVariants = cva(
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
+
+ // ✅ Glass (matches Connect page look)
+ glass:
+ "border border-white/30 bg-white/10 text-slate-100 " +
+ "hover:bg-white/20 backdrop-blur-md shadow-glass",
+
+ // optional: a slightly stronger white version for primary actions
+ glassSolid:
+ "border border-white/40 bg-white/20 text-slate-900 " +
+ "hover:bg-white/30 backdrop-blur-md shadow-glass",
},
size: {
default: "h-9 px-4 py-2",
diff --git a/client/src/components/ui/GlassButton.tsx b/client/src/components/ui/GlassButton.tsx
new file mode 100644
index 0000000..8676705
--- /dev/null
+++ b/client/src/components/ui/GlassButton.tsx
@@ -0,0 +1,13 @@
+export function GlassButton({ children, onClick }: any) {
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/client/src/index.css b/client/src/index.css
index 91f20d2..ec89c18 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -1,195 +1,57 @@
+/* 1️⃣ Import Tailwind */
@import "tailwindcss";
-@plugin "tailwindcss-animate";
-@custom-variant dark (&:is(.dark *));
+/* 2️⃣ Import Satoshi font from Fontshare */
+@import url("https://api.fontshare.com/v2/css?f[]=satoshi@400,500,700&display=swap");
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+/* 3️⃣ Define theme variables (font + glass shadow) */
+@theme {
+ --font-sans: "Satoshi", system-ui, -apple-system, BlinkMacSystemFont,
+ "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
-/* src/index.css */
-html, body, #root { height: 100%; }
-body { background: transparent; }
-
-
-/* :root {
- font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
- line-height: 1.5;
- font-weight: 400;
-
- color-scheme: light dark;
- color: rgba(255, 255, 255, 0.87);
- background-color: #242424;
-
- font-synthesis: none;
- text-rendering: optimizeLegibility;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
- --radius: 0.625rem;
- --background: oklch(1 0 0);
- --foreground: oklch(0.13 0.028 261.692);
- --card: oklch(1 0 0);
- --card-foreground: oklch(0.13 0.028 261.692);
- --popover: oklch(1 0 0);
- --popover-foreground: oklch(0.13 0.028 261.692);
- --primary: oklch(0.21 0.034 264.665);
- --primary-foreground: oklch(0.985 0.002 247.839);
- --secondary: oklch(0.967 0.003 264.542);
- --secondary-foreground: oklch(0.21 0.034 264.665);
- --muted: oklch(0.967 0.003 264.542);
- --muted-foreground: oklch(0.551 0.027 264.364);
- --accent: oklch(0.967 0.003 264.542);
- --accent-foreground: oklch(0.21 0.034 264.665);
- --destructive: oklch(0.577 0.245 27.325);
- --border: oklch(0.928 0.006 264.531);
- --input: oklch(0.928 0.006 264.531);
- --ring: oklch(0.707 0.022 261.325);
- --chart-1: oklch(0.646 0.222 41.116);
- --chart-2: oklch(0.6 0.118 184.704);
- --chart-3: oklch(0.398 0.07 227.392);
- --chart-4: oklch(0.828 0.189 84.429);
- --chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(0.985 0.002 247.839);
- --sidebar-foreground: oklch(0.13 0.028 261.692);
- --sidebar-primary: oklch(0.21 0.034 264.665);
- --sidebar-primary-foreground: oklch(0.985 0.002 247.839);
- --sidebar-accent: oklch(0.967 0.003 264.542);
- --sidebar-accent-foreground: oklch(0.21 0.034 264.665);
- --sidebar-border: oklch(0.928 0.006 264.531);
- --sidebar-ring: oklch(0.707 0.022 261.325);
+ --shadow-glass: 0 12px 40px rgba(0, 0, 0, 0.35);
}
-a {
- font-weight: 500;
- color: #646cff;
- text-decoration: inherit;
-}
-a:hover {
- color: #535bf2;
-}
+/* 4️⃣ Base styles that apply globally */
+@layer base {
+ html {
+ font-size: 19px; /* slightly larger base */
+ }
-body {
- margin: 0;
- display: flex;
- place-items: center;
- min-width: 320px;
- min-height: 100vh;
-}
+ body {
+ @apply font-sans antialiased text-slate-100 bg-transparent selection:bg-white/20;
+ text-shadow: 0 1px 2px rgba(0,0,0,0.4);
+ }
-h1 {
- font-size: 3.2em;
- line-height: 1.1;
+ /* ✨ Ensure all inputs, selects, and buttons use white text */
+ input, select, textarea, button {
+ @apply text-slate-100 placeholder:text-slate-400;
+ }
}
-button {
- border-radius: 8px;
- border: 1px solid transparent;
- padding: 0.6em 1.2em;
- font-size: 1em;
- font-weight: 500;
- font-family: inherit;
- background-color: #1a1a1a;
- cursor: pointer;
- transition: border-color 0.25s;
-}
-button:hover {
- border-color: #646cff;
-}
-button:focus,
-button:focus-visible {
- outline: 4px auto -webkit-focus-ring-color;
-}
-@media (prefers-color-scheme: light) {
- :root {
- color: #213547;
- background-color: #ffffff;
- }
- a:hover {
- color: #747bff;
- }
- button {
- background-color: #f9f9f9;
- }
-}
-@theme inline {
- --radius-sm: calc(var(--radius) - 4px);
- --radius-md: calc(var(--radius) - 2px);
- --radius-lg: var(--radius);
- --radius-xl: calc(var(--radius) + 4px);
- --color-background: var(--background);
- --color-foreground: var(--foreground);
- --color-card: var(--card);
- --color-card-foreground: var(--card-foreground);
- --color-popover: var(--popover);
- --color-popover-foreground: var(--popover-foreground);
- --color-primary: var(--primary);
- --color-primary-foreground: var(--primary-foreground);
- --color-secondary: var(--secondary);
- --color-secondary-foreground: var(--secondary-foreground);
- --color-muted: var(--muted);
- --color-muted-foreground: var(--muted-foreground);
- --color-accent: var(--accent);
- --color-accent-foreground: var(--accent-foreground);
- --color-destructive: var(--destructive);
- --color-border: var(--border);
- --color-input: var(--input);
- --color-ring: var(--ring);
- --color-chart-1: var(--chart-1);
- --color-chart-2: var(--chart-2);
- --color-chart-3: var(--chart-3);
- --color-chart-4: var(--chart-4);
- --color-chart-5: var(--chart-5);
- --color-sidebar: var(--sidebar);
- --color-sidebar-foreground: var(--sidebar-foreground);
- --color-sidebar-primary: var(--sidebar-primary);
- --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
- --color-sidebar-accent: var(--sidebar-accent);
- --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
- --color-sidebar-border: var(--sidebar-border);
- --color-sidebar-ring: var(--sidebar-ring);
-}
-.dark {
- --background: oklch(0.13 0.028 261.692);
- --foreground: oklch(0.985 0.002 247.839);
- --card: oklch(0.21 0.034 264.665);
- --card-foreground: oklch(0.985 0.002 247.839);
- --popover: oklch(0.21 0.034 264.665);
- --popover-foreground: oklch(0.985 0.002 247.839);
- --primary: oklch(0.928 0.006 264.531);
- --primary-foreground: oklch(0.21 0.034 264.665);
- --secondary: oklch(0.278 0.033 256.848);
- --secondary-foreground: oklch(0.985 0.002 247.839);
- --muted: oklch(0.278 0.033 256.848);
- --muted-foreground: oklch(0.707 0.022 261.325);
- --accent: oklch(0.278 0.033 256.848);
- --accent-foreground: oklch(0.985 0.002 247.839);
- --destructive: oklch(0.704 0.191 22.216);
- --border: oklch(1 0 0 / 10%);
- --input: oklch(1 0 0 / 15%);
- --ring: oklch(0.551 0.027 264.364);
- --chart-1: oklch(0.488 0.243 264.376);
- --chart-2: oklch(0.696 0.17 162.48);
- --chart-3: oklch(0.769 0.188 70.08);
- --chart-4: oklch(0.627 0.265 303.9);
- --chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.21 0.034 264.665);
- --sidebar-foreground: oklch(0.985 0.002 247.839);
- --sidebar-primary: oklch(0.488 0.243 264.376);
- --sidebar-primary-foreground: oklch(0.985 0.002 247.839);
- --sidebar-accent: oklch(0.278 0.033 256.848);
- --sidebar-accent-foreground: oklch(0.985 0.002 247.839);
- --sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.551 0.027 264.364);
+/* @import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap");
+
+@import "tailwindcss";
+@plugin "tailwindcss-animate";
+
+
+
+/* Global font + smoothing */
+/* html,
+body {
+ @apply font-sans antialiased text-slate-100;
+ font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, Helvetica, Arial, sans-serif;
}
-@layer base {
- * {
- @apply border-border outline-ring/50;
- }
- body {
- @apply bg-background text-foreground;
- }
-} */
+@custom-variant dark (&:is(.dark *));
+
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+html, body, #root { height: 100%; }
+body { background: transparent; } */
diff --git a/client/src/lib/ai.ts b/client/src/lib/ai.ts
new file mode 100644
index 0000000..e69de29
diff --git a/client/src/lib/api.ts b/client/src/lib/api.ts
index 4b52cca..b9a0727 100644
--- a/client/src/lib/api.ts
+++ b/client/src/lib/api.ts
@@ -34,25 +34,25 @@ async function mcp(tool: string, input: Record = {}): Promise
}
export const api = {
- // Pull repos via MCP repo_reader tool (mocked backend). Maps to simple string[] of full_name.
+ // ✅ method syntax (preferred)
async listRepos(): Promise<{ repos: string[] }> {
- const data = await mcp<{
- repositories: { name: string; full_name: string; branches?: string[] }[];
- }>("repo_reader", {});
- const repos = (data?.data?.repositories ?? []).map((r) => r.full_name);
+ const data = await mcp<{ repositories: { full_name: string; branches?: string[] }[] }>(
+ "repo_reader",
+ {}
+ );
+ const repos = (data.repositories ?? []).map(r => r.full_name);
return { repos };
},
- // Derive branches by calling repo_reader again and selecting the repo.
async listBranches(repo: string): Promise<{ branches: string[] }> {
- const data = await mcp<{
- repositories: { name: string; full_name: string; branches?: string[] }[];
- }>("repo_reader", {});
- const item = (data?.data?.repositories ?? []).find((r) => r.full_name === repo);
+ const data = await mcp<{ repositories: { full_name: string; branches?: string[] }[] }>(
+ "repo_reader",
+ {}
+ );
+ const item = (data.repositories ?? []).find(r => r.full_name === repo);
return { branches: item?.branches ?? [] };
},
- // Generate pipeline via MCP pipeline_generator (mock). Assume provider 'aws' for now.
async createPipeline(payload: any) {
const { repo, branch, template = "node_app", options } = payload || {};
const data = await mcp("pipeline_generator", {
@@ -65,21 +65,22 @@ export const api = {
return data;
},
- // List AWS roles via MCP oidc_adapter and map to list of ARNs.
async listAwsRoles(): Promise<{ roles: string[] }> {
const data = await mcp<{ roles?: { name: string; arn: string }[] }>(
"oidc_adapter",
{ provider: "aws" }
);
- const roles = (data.roles ?? []).map((r) => r.arn);
- return { roles };
+ return { roles: (data.roles ?? []).map(r => r.arn) };
},
- // Not implemented on server yet; keep API shape but throw a helpful error.
async openPr(_payload: any) {
throw new Error("openPr is not implemented on the server (no MCP tool)");
},
+ // ... keep the rest of your existing methods like getConnections, getSecretPresence, etc.
+
+
+
// --- Mocked config/secrets endpoints for Secrets/Preflight flow ---
async getConnections(_repo: string): Promise<{
githubAppInstalled: boolean;
diff --git a/client/src/pages/ConfigurePage.tsx b/client/src/pages/ConfigurePage.tsx
index 9f5c070..d833d9e 100644
--- a/client/src/pages/ConfigurePage.tsx
+++ b/client/src/pages/ConfigurePage.tsx
@@ -5,6 +5,7 @@ import { useRepoStore } from "../store/useRepoStore";
import { usePipelineStore } from "../store/usePipelineStore";
import { useChatStore } from "../store/useChatStore";
import { parseChatForPipeline, summarizeChanges } from "@/lib/aiAssist";
+import { GlassButton } from "@/components/ui/GlassButton";
const STAGES = ["build", "test", "deploy"] as const;
@@ -110,16 +111,18 @@ export default function ConfigurePage() {
{/* history */}
-
+
{messages.map((m, i) => (
- {m.text}
-
+ className={`inline-block rounded px-3 py-2 text-sm leading-relaxed whitespace-pre-wrap ${
+ m.role === "user"
+ ? "bg-white/20 text-slate-100"
+ : "bg-white/10 border border-white/20 text-slate-200"
+ }`}
+>
+ {m.text}
+
))}
@@ -131,14 +134,14 @@ export default function ConfigurePage() {
"Node 18; npm ci; npm test; npm run build",
"role arn:aws:iam::123456789012:role/app-ci",
].map((ex) => (
-
+
))}
@@ -156,7 +159,7 @@ export default function ConfigurePage() {
Press ⌘/Ctrl + Enter to send
-
-
+
@@ -222,7 +225,7 @@ export default function ConfigurePage() {
disabled={busy}
value={pipeline.options?.awsRoleArn ?? ""}
onChange={(e) => pipeline.setOption?.("awsRoleArn", e.target.value)}
- className="rounded-md border px-3 py-2"
+ className="rounded-md border px-3 py-2 text-white bg-black"
>
{pipeline.roles?.map((r: any) => (
@@ -235,14 +238,14 @@ export default function ConfigurePage() {
{/* ====== Actions ====== */}
-
-
+
{/* ====== YAML Preview ====== */}
diff --git a/client/src/pages/ConnectPage.tsx b/client/src/pages/ConnectPage.tsx
index 534613d..de2a04d 100644
--- a/client/src/pages/ConnectPage.tsx
+++ b/client/src/pages/ConnectPage.tsx
@@ -1,4 +1,4 @@
-// src/pages/ConnectPage.tsx
+import { GlassButton } from "../components/ui/GlassButton";
import { Button } from "@/components/ui/button";
import { Label } from "@/components/ui/label";
import {
@@ -11,16 +11,16 @@ export default function ConnectPage() {
return (
-
+
Connect your repository
-
+
@@ -61,13 +61,13 @@ export default function ConnectPage() {
-
+