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: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7

- uses: pnpm/action-setup@v4
with:
version: 11
# Version is taken from package.json#packageManager; do not also set `version` here
# or action-setup fails with ERR_PNPM_BAD_PM_VERSION ("Multiple versions of pnpm").
- uses: pnpm/action-setup@v6

- uses: actions/setup-node@v4
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
Expand Down
8 changes: 6 additions & 2 deletions src/app/api/analyze/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,9 @@ describe("POST /api/analyze", () => {
});

it("returns 500 when analyzer throws an error", async () => {
const mockAnalyzeProject = vi.fn().mockRejectedValue(new Error("Parsing failed"));
const mockAnalyzeProject = vi
.fn()
.mockRejectedValue(new Error("Parsing failed"));

mockGetAnalyzer.mockReturnValue({
language: "python",
Expand All @@ -268,7 +270,9 @@ describe("POST /api/analyze", () => {
}),
});

const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const consoleErrorSpy = vi
.spyOn(console, "error")
.mockImplementation(() => {});

const res = await POST(req);
expect(res.status).toBe(500);
Expand Down
5 changes: 3 additions & 2 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,9 @@ html.dark .react-flow__attribution {

/* ---- React Flow Custom Node & Selection overrides ---- */
.react-flow__node {
transition: border-color 0.25s, box-shadow 0.25s !important;
transition:
border-color 0.25s,
box-shadow 0.25s !important;
}

.react-flow__node:hover {
Expand All @@ -278,4 +280,3 @@ html.dark .react-flow__node.selected {
border-color: var(--color-brand-dark) !important;
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.4) !important;
}

8 changes: 4 additions & 4 deletions src/components/layout/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,7 @@ export default function Header({
);

return (
<header
className="sticky top-0 z-50 flex items-center justify-between gap-3 px-4 py-3.5 sm:grid sm:grid-cols-[1fr_auto_1fr] sm:px-6 sm:py-4 border-b border-[#e2e8f0] dark:border-[#232a36] backdrop-blur-xl bg-white/80 dark:bg-[#0b0d12]/80"
>
<header className="sticky top-0 z-50 flex items-center justify-between gap-3 px-4 py-3.5 sm:grid sm:grid-cols-[1fr_auto_1fr] sm:px-6 sm:py-4 border-b border-[#e2e8f0] dark:border-[#232a36] backdrop-blur-xl bg-white/80 dark:bg-[#0b0d12]/80">
{/* ── Left: brand ── */}
<Link
href={`/${lang}`}
Expand Down Expand Up @@ -339,7 +337,9 @@ export default function Header({
: "text-ink-soft hover:bg-slate-50 hover:text-ink dark:text-muted dark:hover:bg-surface-hover dark:hover:text-fg"
}`}
>
<span>{LANGUAGE_NAMES[locale] ?? locale.toUpperCase()}</span>
<span>
{LANGUAGE_NAMES[locale] ?? locale.toUpperCase()}
</span>
{active && <span className="text-[10px]">✓</span>}
</Link>
);
Expand Down
5 changes: 1 addition & 4 deletions src/components/sections/TestimonialForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,7 @@

import { useActionState, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import {
postTestimonial,
type TestimonialFormState,
} from "@/lib/testimonials";
import { postTestimonial, type TestimonialFormState } from "@/lib/testimonials";

const INITIAL: TestimonialFormState = { ok: false };

Expand Down
4 changes: 3 additions & 1 deletion src/components/sections/TestimonialsBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ export default function TestimonialsBoard({
featured ? "lg:col-span-2" : ""
}`}
>
{item.rating ? <Stars value={item.rating} size={featured ? 18 : 15} /> : null}
{item.rating ? (
<Stars value={item.rating} size={featured ? 18 : 15} />
) : null}
<p
className={`leading-[1.65] text-[#0f172a] dark:text-[#e6e9ef] ${
featured ? "text-lg" : "text-[15px]"
Expand Down
15 changes: 11 additions & 4 deletions src/components/ui/CodeWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,16 @@ export default function CodeWorkspace({
} finally {
setIsLoading(false);
}
}, [isLoading, mode, files, language, code, setGraph, setError, setIsLoading]);
}, [
isLoading,
mode,
files,
language,
code,
setGraph,
setError,
setIsLoading,
]);

async function handleSave() {
if (!graph || saving) return;
Expand Down Expand Up @@ -340,8 +349,6 @@ export default function CodeWorkspace({
setFiles(null); // file extensions differ per language
}



function syncScroll(e: React.UIEvent<HTMLTextAreaElement>) {
if (gutterRef.current)
gutterRef.current.scrollTop = e.currentTarget.scrollTop;
Expand Down Expand Up @@ -425,7 +432,7 @@ export default function CodeWorkspace({
<div key={i}>{i + 1}</div>
))}
</div>
<textarea
<textarea
value={code}
onChange={(e) => setCode(e.target.value)}
onScroll={syncScroll}
Expand Down
14 changes: 8 additions & 6 deletions src/components/ui/Diagram.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,9 @@ function DiagramFlow({
const height = bounds.height + padding * 2;

// 2. Locate the viewport element inside React Flow DOM
const viewport = document.querySelector(".react-flow__viewport") as HTMLElement | null;
const viewport = document.querySelector(
".react-flow__viewport",
) as HTMLElement | null;
if (!viewport) return;

// 3. Set background color relative to format and theme
Expand Down Expand Up @@ -564,7 +566,10 @@ function DiagramFlow({

{/* Separator */}
{legend.length > 0 && (
<div className="w-[1px] h-3.5 bg-[#e2e8f0] dark:bg-[#232a36] mx-0.5" aria-hidden="true" />
<div
className="w-[1px] h-3.5 bg-[#e2e8f0] dark:bg-[#232a36] mx-0.5"
aria-hidden="true"
/>
)}

{/* Export Dropdown Selector */}
Expand Down Expand Up @@ -640,10 +645,7 @@ function DiagramFlow({
);
}

export default function Diagram(props: {
graph: Graph;
emptyLabel: string;
}) {
export default function Diagram(props: { graph: Graph; emptyLabel: string }) {
return (
<ReactFlowProvider>
<DiagramFlow {...props} />
Expand Down
7 changes: 5 additions & 2 deletions src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import { drizzle } from "drizzle-orm/libsql";
import * as schema from "./schema";

const url = process.env.TURSO_DATABASE_URL;
if (!url) {
// `next build` imports route modules to collect data; allow it to run without a
// real database. At runtime the URL is still required.
const isBuild = process.env.NEXT_PHASE === "phase-production-build";
if (!url && !isBuild) {
throw new Error("TURSO_DATABASE_URL is not set");
}

const client = createClient({
url,
url: url ?? "file::memory:",
authToken: process.env.TURSO_AUTH_TOKEN,
});

Expand Down
5 changes: 4 additions & 1 deletion src/lib/validation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import {

describe("graphMetaSchema", () => {
test("rejects an empty title", () => {
const result = graphMetaSchema.safeParse({ title: " ", language: "python" });
const result = graphMetaSchema.safeParse({
title: " ",
language: "python",
});
expect(result.success).toBe(false);
});

Expand Down
8 changes: 6 additions & 2 deletions src/lib/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ export const graphMetaSchema = z.object({
// loose objects rather than re-declaring the whole type.
export const graphSchema = z.object({
nodes: z
.array(z.object({ id: z.string(), label: z.string(), type: z.string() }).loose())
.array(
z.object({ id: z.string(), label: z.string(), type: z.string() }).loose(),
)
.max(5000),
edges: z
.array(
z.object({ source: z.string(), target: z.string(), kind: z.string() }).loose(),
z
.object({ source: z.string(), target: z.string(), kind: z.string() })
.loose(),
)
.max(20000),
});
Expand Down