- Four moves from nothing to a live site published from your pod.
- Everything happens in the app — no curl required.
+ Four moves from nothing to a live site published from your pod. Everything happens in the
+ app — no curl required.
{seeded ? (
<>
{" "}
@@ -433,8 +413,7 @@ function StartHere({
return (
- You've got a pod and a session. From here it's repo →
- pages → push.
+ You've got a pod and a session. From here it's repo → pages → push.
{steps.map((s) => (
@@ -464,15 +443,10 @@ function StepCard({
>
{n}
-
+
{title}
-
- {body}
-
+ {body}
{cta.label}
@@ -495,10 +469,7 @@ function Section({
}) {
return (
-
+
{title}
{children}
@@ -506,13 +477,7 @@ function Section({
);
}
-function Stat({
- label,
- value,
-}: {
- label: string;
- value: React.ReactNode;
-}) {
+function Stat({ label, value }: { label: string; value: React.ReactNode }) {
return (
{status}
@@ -609,8 +570,7 @@ function DemoCard({
featured
? {
borderColor: "var(--accent)",
- background:
- "color-mix(in srgb, var(--accent) 12%, var(--paper-soft))",
+ background: "color-mix(in srgb, var(--accent) 12%, var(--paper-soft))",
}
: undefined
}
@@ -659,11 +619,7 @@ function DemoCard({
-
+
{demo.blurb}
diff --git a/src/app/people/[owner]/page.tsx b/src/app/people/[owner]/page.tsx
index e33152d..dbe068a 100644
--- a/src/app/people/[owner]/page.tsx
+++ b/src/app/people/[owner]/page.tsx
@@ -5,11 +5,7 @@ import ProfileView from "../profile-view";
export const dynamic = "force-dynamic";
-export default async function PersonPage({
- params,
-}: {
- params: Promise<{ owner: string }>;
-}) {
+export default async function PersonPage({ params }: { params: Promise<{ owner: string }> }) {
const { owner } = await params;
const repos = listRepos();
const ownerRepo = repos.find((r) => r.owner === owner);
diff --git a/src/app/people/page.tsx b/src/app/people/page.tsx
index bc8ef2a..8535e01 100644
--- a/src/app/people/page.tsx
+++ b/src/app/people/page.tsx
@@ -1,5 +1,5 @@
-import Link from "next/link";
import { Button, Input } from "@mind-studio/ui";
+import Link from "next/link";
import { listRepos, type Repo } from "@/lib/registry/repos";
import ProfileView from "./profile-view";
@@ -43,9 +43,8 @@ export default async function PeoplePage({
Profiles, read from pods.
- The bridge doesn't store names, bios, or avatars. Every field on a
- profile page is fetched live from the WebID URL and rendered as-is. If
- a field isn't in the pod, the page says so.
+ The bridge doesn't store names, bios, or avatars. Every field on a profile page is fetched
+ live from the WebID URL and rendered as-is. If a field isn't in the pod, the page says so.
@@ -68,10 +67,7 @@ export default async function PeoplePage({
>
/people/{p.owner}
-
+
@{p.owner}
- Paste any public WebID URL. The bridge will dereference it and render
- whatever it finds — including profiles hosted on other Solid servers
- that have never heard of this bridge.
+ Paste any public WebID URL. The bridge will dereference it and render whatever it finds —
+ including profiles hosted on other Solid servers that have never heard of this bridge.
- Tip: this works for any WebID whose document is public-read — the
- bridge dereferences the URL with no auth.
+ Tip: this works for any WebID whose document is public-read — the bridge dereferences the
+ URL with no auth.
@@ -127,19 +122,10 @@ function uniqueByWebId(repos: Repo[]): Repo[] {
return out.sort((a, b) => a.owner.localeCompare(b.owner));
}
-function Section({
- title,
- children,
-}: {
- title: React.ReactNode;
- children: React.ReactNode;
-}) {
+function Section({ title, children }: { title: React.ReactNode; children: React.ReactNode }) {
return (
-
+
{title}
{children}
diff --git a/src/app/people/profile-view.tsx b/src/app/people/profile-view.tsx
index fc9e1ab..d786836 100644
--- a/src/app/people/profile-view.tsx
+++ b/src/app/people/profile-view.tsx
@@ -1,6 +1,6 @@
import Link from "next/link";
-import { fetchProfile, listContainer } from "@/lib/solid/profile";
import { listRepos, type Repo } from "@/lib/registry/repos";
+import { fetchProfile, listContainer } from "@/lib/solid/profile";
function initials(name: string | null, fallback: string): string {
const source = (name && name.trim().length > 0 ? name : fallback).trim();
@@ -62,12 +62,7 @@ export default async function ProfileView({ webId }: { webId: string }) {
try {
profile = await fetchProfile(webId);
} catch (err) {
- return (
-
- );
+ return ;
}
const repos = listRepos();
@@ -92,12 +87,9 @@ export default async function ProfileView({ webId }: { webId: string }) {
const ownerSlug = ownerSlugForWebId(webId, repos);
const fallbackHandle = ownerSlug ?? lastSegment(stripFragment(webId));
- const displayName =
- profile.name ?? (profile.nick ? `@${profile.nick}` : `@${fallbackHandle}`);
+ const displayName = profile.name ?? (profile.nick ? `@${profile.nick}` : `@${fallbackHandle}`);
const initialsBadge = initials(profile.name, profile.nick ?? fallbackHandle);
- const issuerHost = profile.oidcIssuer
- ? hostOnly(profile.oidcIssuer)
- : hostOnly(profile.document);
+ const issuerHost = profile.oidcIssuer ? hostOnly(profile.oidcIssuer) : hostOnly(profile.document);
return (
@@ -121,9 +113,7 @@ export default async function ProfileView({ webId }: { webId: string }) {
)}
-
- Profile{ownerSlug ? ` · ${ownerSlug}` : ""}
-
+
Profile{ownerSlug ? ` · ${ownerSlug}` : ""}
-
+
{originHostPath(profile.webId)}
{profile.oidcIssuer ? (
-
+
{originHostPath(profile.oidcIssuer)}
) : (
@@ -183,12 +163,7 @@ export default async function ProfileView({ webId }: { webId: string }) {
{profile.homepage ? (
-
+
{originHostPath(profile.homepage)}
) : (
@@ -243,9 +218,7 @@ export default async function ProfileView({ webId }: { webId: string }) {
className="inline-flex items-baseline gap-2 rounded-[var(--radius-chip)] border border-[color:var(--ink-trace)] bg-[color:var(--paper-soft)] px-3 py-1.5 text-xs transition-colors hover:border-[color:var(--accent)] hover:text-[color:var(--accent)]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
-
- {r.owner}/
-
+ {r.owner}/
{r.name}
{r.visibility === "private" ? (
@@ -272,10 +245,7 @@ export default async function ProfileView({ webId }: { webId: string }) {
const href = linkForKnown(w, repos);
const slug = ownerSlugForWebId(w, repos);
return (
-
+
{slug ? `@${slug}` : "look up →"}
@@ -296,9 +266,8 @@ export default async function ProfileView({ webId }: { webId: string }) {
- Cross-referenced: which repos the bridge thinks this person owns vs.
- which ones their pod itself advertises in{" "}
- codespaces/. The pod is authoritative.
+ Cross-referenced: which repos the bridge thinks this person owns vs. which ones their pod
+ itself advertises in codespaces/. The pod is authoritative.
r.name)}
@@ -312,18 +281,12 @@ export default async function ProfileView({ webId }: { webId: string }) {
{sitesContainer === null ? (
- (no public/sites/ container, or not
- readable)
+ (no public/sites/ container, or not readable)
) : sitesFromPod.length === 0 ? (
-
- (container is empty)
-
+ (container is empty)
) : (
-
+
{sitesFromPod.map((name) => (
· {" "}
@@ -345,10 +308,9 @@ export default async function ProfileView({ webId }: { webId: string }) {
- Verbatim Turtle returned by{" "}
- GET {profile.document} with{" "}
- Accept: text/turtle. Nothing on this page
- is sourced from anywhere else.
+ Verbatim Turtle returned by GET {profile.document} with{" "}
+ Accept: text/turtle. Nothing on this page is sourced from
+ anywhere else.
{profile.rawTurtle.trim() || "(empty)"}
@@ -359,20 +321,14 @@ export default async function ProfileView({ webId }: { webId: string }) {
className="text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
- // this profile was fetched server-side, refreshed on every page load.
- nothing is cached, nothing is stored on the bridge.
+ // this profile was fetched server-side, refreshed on every page load. nothing is cached,
+ nothing is stored on the bridge.
);
}
-function DataRow({
- label,
- children,
-}: {
- label: string;
- children: React.ReactNode;
-}) {
+function DataRow({ label, children }: { label: string; children: React.ReactNode }) {
return (
<>
@@ -384,26 +340,13 @@ function DataRow({
}
function Missing({ children }: { children: React.ReactNode }) {
- return (
-
- no {children} in profile
-
- );
+ return no {children} in profile ;
}
-function Section({
- title,
- children,
-}: {
- title: React.ReactNode;
- children: React.ReactNode;
-}) {
+function Section({ title, children }: { title: React.ReactNode; children: React.ReactNode }) {
return (
-
+
{title}
{children}
@@ -455,10 +398,7 @@ function RepoCrossRef({
const inBridge = bridgeRepos.includes(name);
const inPod = podRepos.includes(name);
return (
-
+
{name}
{inBridge ? "✓" : "—"}
{inPod ? "✓" : "—"}
@@ -470,13 +410,7 @@ function RepoCrossRef({
);
}
-function ErrorPanel({
- webId,
- message,
-}: {
- webId: string;
- message: string;
-}) {
+function ErrorPanel({ webId, message }: { webId: string; message: string }) {
return (
@@ -499,8 +433,7 @@ function ErrorPanel({
- The bridge dereferenced{" "}
- {webId} server-side and the
+ The bridge dereferenced {webId} server-side and the
request failed. Profiles must be publicly readable to render here.
{message}
@@ -510,8 +443,7 @@ function ErrorPanel({
className="mt-8 text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
- // the seeded alice and mind ACLs allow public read.
- external WebIDs need the same.
+ // the seeded alice and mind ACLs allow public read. external WebIDs need the same.
);
diff --git a/src/app/profile/ai-providers/manage.tsx b/src/app/profile/ai-providers/manage.tsx
index 223f80f..17489b4 100644
--- a/src/app/profile/ai-providers/manage.tsx
+++ b/src/app/profile/ai-providers/manage.tsx
@@ -1,31 +1,28 @@
"use client";
-import { useMemo, useState } from "react";
-import { useRouter } from "next/navigation";
import {
- Button,
- Input,
- Select,
- SelectTrigger,
- SelectValue,
- SelectContent,
- SelectItem,
AlertDialog,
- AlertDialogTrigger,
+ AlertDialogAction,
+ AlertDialogCancel,
AlertDialogContent,
- AlertDialogHeader,
+ AlertDialogDescription,
AlertDialogFooter,
+ AlertDialogHeader,
AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogCancel,
- AlertDialogAction,
+ AlertDialogTrigger,
+ Button,
buttonVariants,
+ Input,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@mind-studio/ui";
+import { useRouter } from "next/navigation";
+import { useMemo, useState } from "react";
+import type { ProviderName, ProviderSpec } from "@/lib/ai-providers/providers";
import { authedFetch } from "@/lib/auth/csrf-client";
-import type {
- ProviderSpec,
- ProviderName,
-} from "@/lib/ai-providers/providers";
type Configured = {
provider: ProviderName;
@@ -53,15 +50,12 @@ export function AiProvidersManager({
-
+
Keys
- One key per provider. Adding a key replaces any existing key for
- that provider; only the last 4 characters are shown after save.
+ One key per provider. Adding a key replaces any existing key for that provider; only the
+ last 4 characters are shown after save.
{providers.map((p) => (
@@ -91,9 +85,7 @@ function DefaultSelector({
pref: Pref;
}) {
const router = useRouter();
- const [provider, setProvider] = useState
(
- pref.provider ?? "",
- );
+ const [provider, setProvider] = useState(pref.provider ?? "");
const [model, setModel] = useState(pref.model ?? "");
const [custom, setCustom] = useState(false);
const [busy, setBusy] = useState(false);
@@ -114,9 +106,7 @@ function DefaultSelector({
setSaved(false);
try {
const body =
- provider === ""
- ? { provider: null, model: null }
- : { provider, model: model.trim() };
+ provider === "" ? { provider: null, model: null } : { provider, model: model.trim() };
const res = await authedFetch("/api/profile/ai/pref", {
method: "PUT",
body: JSON.stringify(body),
@@ -136,16 +126,13 @@ function DefaultSelector({
return (
-
+
Default model
- Applies to every repo you own. The coder uses this provider + model
- for every issue it picks up. Leave blank to fall back to the
- bridge-default MIND_AGENT_MODEL.
+ Applies to every repo you own. The coder uses this provider + model for every issue it picks
+ up. Leave blank to fall back to the bridge-default{" "}
+ MIND_AGENT_MODEL.
@@ -281,13 +265,7 @@ function DefaultSelector({
// Per-provider key card
// -----------------------------------------------------------------------
-function ProviderCard({
- spec,
- configured,
-}: {
- spec: ProviderSpec;
- configured: Configured | null;
-}) {
+function ProviderCard({ spec, configured }: { spec: ProviderSpec; configured: Configured | null }) {
const router = useRouter();
const [open, setOpen] = useState(false);
const [apiKey, setApiKey] = useState("");
@@ -356,11 +334,7 @@ function ProviderCard({
{configured ? (
-
+
configured · {configured.hint}
) : (
@@ -463,14 +437,12 @@ function ProviderCard({
Remove your {spec.label} key?
- Remove your {spec.label} key? The coder will fall back to the
- bridge default until you add a new one.
+ Remove your {spec.label} key? The coder will fall back to the bridge default
+ until you add a new one.
-
- Cancel
-
+ Cancel
)}
- {error ? (
- {error}
- ) : null}
+ {error ? {error}
: null}
);
}
diff --git a/src/app/profile/ai-providers/page.tsx b/src/app/profile/ai-providers/page.tsx
index 6cdbd8a..ade9c42 100644
--- a/src/app/profile/ai-providers/page.tsx
+++ b/src/app/profile/ai-providers/page.tsx
@@ -1,11 +1,8 @@
import Link from "next/link";
import { redirect } from "next/navigation";
-import { readSession } from "@/lib/auth/session";
-import {
- listConfiguredProviders,
- getUserAiPref,
-} from "@/lib/ai-providers/store";
import { PROVIDERS } from "@/lib/ai-providers/providers";
+import { getUserAiPref, listConfiguredProviders } from "@/lib/ai-providers/store";
+import { readSession } from "@/lib/auth/session";
import { AiProvidersManager } from "./manage";
export const dynamic = "force-dynamic";
@@ -31,19 +28,14 @@ export default async function AiProvidersPage() {
AI providers
- Bring your own keys. The coder agent uses your selected provider
- and model whenever it runs on a repo you own. Keys are encrypted
- at rest with the same AES-256-GCM key that protects your Solid
- refresh tokens, and they never leave this bridge.
+ Bring your own keys. The coder agent uses your selected provider and model whenever it runs
+ on a repo you own. Keys are encrypted at rest with the same AES-256-GCM key that protects
+ your Solid refresh tokens, and they never leave this bridge.
-
+
);
}
diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx
index 95b0832..67de068 100644
--- a/src/app/profile/page.tsx
+++ b/src/app/profile/page.tsx
@@ -1,13 +1,10 @@
import Link from "next/link";
import { redirect } from "next/navigation";
-import { readSession } from "@/lib/auth/session";
-import { listActivityForWebId, type ActivityItem } from "@/lib/registry/activity";
-import {
- listConfiguredProviders,
- getUserAiPref,
-} from "@/lib/ai-providers/store";
-import { getProvider } from "@/lib/ai-providers/providers";
import { RelativeTime } from "@/components/relative-time";
+import { getProvider } from "@/lib/ai-providers/providers";
+import { getUserAiPref, listConfiguredProviders } from "@/lib/ai-providers/store";
+import { readSession } from "@/lib/auth/session";
+import { type ActivityItem, listActivityForWebId } from "@/lib/registry/activity";
import ProfileView from "../people/profile-view";
import { ProfileSettings } from "./profile-settings";
@@ -57,15 +54,12 @@ export default async function ProfilePage() {
-
+
Recent activity
- Issues you've filed, pulls you've opened, agent runs on
- your repos. Latest first.
+ Issues you've filed, pulls you've opened, agent runs on your repos. Latest
+ first.
{activity.length === 0 ? (
@@ -73,8 +67,7 @@ export default async function ProfilePage() {
className="rounded border border-dashed border-[color:var(--ink-trace)] px-4 py-6 text-center text-sm italic text-[color:var(--ink-faint)]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
- No activity yet. File an issue or push a repo to see it land
- here.
+ No activity yet. File an issue or push a repo to see it land here.
) : (
@@ -100,10 +93,7 @@ export default async function ProfilePage() {
/>
-
+
{a.title}
@@ -126,10 +116,7 @@ export default async function ProfilePage() {
-
+
AI providers
- The coder agent uses these on issues you file or comment on in
- your own repos. Keys are encrypted at rest; only the last 4
- characters are visible after save.
+ The coder agent uses these on issues you file or comment on in your own repos. Keys are
+ encrypted at rest; only the last 4 characters are visible after save.
{aiPrefProvider && aiPref.model ? (
@@ -152,9 +138,7 @@ export default async function ProfilePage() {
style={{ fontFamily: "var(--font-mono-src)" }}
>
default ·
-
- {aiPrefProvider.label}
-
+
{aiPrefProvider.label}
/
{aiPref.model}
@@ -182,10 +166,7 @@ export default async function ProfilePage() {
-
+
Settings
diff --git a/src/app/profile/profile-settings.tsx b/src/app/profile/profile-settings.tsx
index 9c1019a..592eca9 100644
--- a/src/app/profile/profile-settings.tsx
+++ b/src/app/profile/profile-settings.tsx
@@ -1,8 +1,8 @@
"use client";
-import { useEffect, useState } from "react";
-import { useRouter } from "next/navigation";
import { Button, useMindTheme } from "@mind-studio/ui";
+import { useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
import { useBrand } from "@/components/theme-shell";
import { authedFetch } from "@/lib/auth/csrf-client";
@@ -116,8 +116,8 @@ export function ProfileSettings() {
Sign out
- Drops the bridge's session cookie. Your pod stays connected
- for the OIDC client (revoke that from{" "}
+ Drops the bridge's session cookie. Your pod stays connected for the OIDC client
+ (revoke that from{" "}
connected pods
diff --git a/src/app/repos/[owner]/[repo]/blob/[...path]/page.tsx b/src/app/repos/[owner]/[repo]/blob/[...path]/page.tsx
index f749374..b224c4f 100644
--- a/src/app/repos/[owner]/[repo]/blob/[...path]/page.tsx
+++ b/src/app/repos/[owner]/[repo]/blob/[...path]/page.tsx
@@ -1,10 +1,10 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
-import { repoPath } from "@/lib/git/backend";
-import { listBranches, readBlob } from "@/lib/git/objects";
import { Breadcrumbs } from "@/app/repos/[owner]/[repo]/tree/[[...path]]/page";
import { BranchPicker } from "@/components/branch-picker";
+import { repoPath } from "@/lib/git/backend";
+import { listBranches, readBlob } from "@/lib/git/objects";
+import { getRepo } from "@/lib/registry/repos";
import { RepoTabs } from "../../repo-tabs";
export const dynamic = "force-dynamic";
@@ -27,8 +27,7 @@ export default async function BlobPage({ params, searchParams }: PageProps) {
// Accept either a known branch name or a hex commit SHA (7-40 chars);
// readBlob/git itself validates the final ref via cat-file.
const ref =
- sp.ref &&
- (branches.includes(sp.ref) || /^[0-9a-f]{7,40}$/i.test(sp.ref))
+ sp.ref && (branches.includes(sp.ref) || /^[0-9a-f]{7,40}$/i.test(sp.ref))
? sp.ref
: repo.defaultBranch;
const refQuery = ref === repo.defaultBranch ? null : ref;
@@ -45,10 +44,7 @@ export default async function BlobPage({ params, searchParams }: PageProps) {
-
+
Source
@@ -62,11 +58,7 @@ export default async function BlobPage({ params, searchParams }: PageProps) {
{branches.length <= 1 ? ` · ${ref}` : ""}
{branches.length > 1 ? (
-
+
) : null}
diff --git a/src/app/repos/[owner]/[repo]/issues/[number]/agent-run-card.tsx b/src/app/repos/[owner]/[repo]/issues/[number]/agent-run-card.tsx
index efe1045..1a18380 100644
--- a/src/app/repos/[owner]/[repo]/issues/[number]/agent-run-card.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/[number]/agent-run-card.tsx
@@ -29,10 +29,9 @@ export function AgentRunCard({ run: initial }: { run: AgentRun }) {
async function tick() {
try {
- const res = await fetch(
- `/api/agent-runs/${run.id}/log?since=${sinceRef.current}`,
- { cache: "no-store" },
- );
+ const res = await fetch(`/api/agent-runs/${run.id}/log?since=${sinceRef.current}`, {
+ cache: "no-store",
+ });
if (!res.ok) return;
const body = (await res.json()) as {
content: string;
@@ -75,12 +74,7 @@ export function AgentRunCard({ run: initial }: { run: AgentRun }) {
}
}, [log]);
- const tone =
- run.status === "ok"
- ? "ok"
- : run.status === "running"
- ? undefined
- : "bad";
+ const tone = run.status === "ok" ? "ok" : run.status === "running" ? undefined : "bad";
return (
@@ -105,12 +99,7 @@ export function AgentRunCard({ run: initial }: { run: AgentRun }) {
{run.status === "running" ? (
-
+
) : run.errorMessage ? (
error: {run.errorMessage}
@@ -180,7 +169,12 @@ function LiveLog({
boxShadow: "0 0 8px color-mix(in srgb, var(--accent) 80%, transparent)",
}}
/>
-
+
coder
@@ -190,7 +184,8 @@ function LiveLog({
className="mc-term-live-dot inline-block h-1.5 w-1.5 rounded-full"
style={{
background: "var(--accent)",
- boxShadow: "0 0 8px var(--accent), 0 0 14px color-mix(in srgb, var(--accent) 55%, transparent)",
+ boxShadow:
+ "0 0 8px var(--accent), 0 0 14px color-mix(in srgb, var(--accent) 55%, transparent)",
}}
/>
live
@@ -198,9 +193,7 @@ function LiveLog({
·
{lineCount.toLocaleString()} ln
·
-
- {formatBytes(logSize)}
-
+ {formatBytes(logSize)}
@@ -340,9 +332,7 @@ function CompletedSummary({ summary }: { summary: string }) {
return (
{parsed.headline ? (
-
- {parsed.headline}
-
+
{parsed.headline}
) : null}
{parsed.files.length > 0 ? (
@@ -372,7 +362,9 @@ function CompletedSummary({ summary }: { summary: string }) {
style={{ fontFamily: "var(--font-mono-src)" }}
>
- ›
+
+ ›
+
opencode output
@@ -383,8 +375,7 @@ function CompletedSummary({ summary }: { summary: string }) {
className="mc-terminal relative border-t border-[color:var(--ink-trace)]"
style={{
background: "#06080a",
- boxShadow:
- "0 0 18px color-mix(in srgb, var(--accent) 10%, transparent) inset",
+ boxShadow: "0 0 18px color-mix(in srgb, var(--accent) 10%, transparent) inset",
}}
>
{hasDraft && !busy ? (
-
setBody("")}
- >
+ setBody("")}>
Clear
) : null}
diff --git a/src/app/repos/[owner]/[repo]/issues/[number]/issue-actions.tsx b/src/app/repos/[owner]/[repo]/issues/[number]/issue-actions.tsx
index 60b7717..f7ce121 100644
--- a/src/app/repos/[owner]/[repo]/issues/[number]/issue-actions.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/[number]/issue-actions.tsx
@@ -1,9 +1,9 @@
"use client";
+import { Button } from "@mind-studio/ui";
import { useRouter } from "next/navigation";
import { useState } from "react";
-import { authedFetch } from "@/lib/auth/csrf-client";
import { SignInWall } from "@/components/sign-in-wall";
-import { Button } from "@mind-studio/ui";
+import { authedFetch } from "@/lib/auth/csrf-client";
/**
* Issue-page actions:
@@ -85,13 +85,7 @@ export function IssueActions({
return (
-
+
{busy === "toggle" ? "…" : status === "open" ? "Close issue" : "Reopen issue"}
{status === "open" ? (
@@ -107,11 +101,7 @@ export function IssueActions({
: "Re-fire the coder on this issue (auto-fires on create + comment)"
}
>
- {busy === "run"
- ? "Dispatching…"
- : hasOpenRun
- ? "Coder running…"
- : "Re-run coder"}
+ {busy === "run" ? "Dispatching…" : hasOpenRun ? "Coder running…" : "Re-run coder"}
) : null}
diff --git a/src/app/repos/[owner]/[repo]/issues/[number]/page.tsx b/src/app/repos/[owner]/[repo]/issues/[number]/page.tsx
index 3249329..c88cb3d 100644
--- a/src/app/repos/[owner]/[repo]/issues/[number]/page.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/[number]/page.tsx
@@ -1,13 +1,13 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
-import { repoPath } from "@/lib/git/backend";
-import { readGitTracker } from "@/lib/tracker/read";
-import type { Tracker, TrackerIssue } from "@/lib/tracker/read";
+import { Avatar, deriveLabel } from "@/components/avatar";
import { RelativeTime } from "@/components/relative-time";
+import { repoPath } from "@/lib/git/backend";
import { renderMarkdown } from "@/lib/markdown";
+import { getRepo } from "@/lib/registry/repos";
+import type { Tracker, TrackerIssue } from "@/lib/tracker/read";
+import { readGitTracker } from "@/lib/tracker/read";
import { RepoTabs } from "../../repo-tabs";
-import { Avatar, deriveLabel } from "@/components/avatar";
export const dynamic = "force-dynamic";
@@ -28,18 +28,13 @@ export default async function IssueDetailPage({ params }: PageProps) {
const issue = tracker.issues.find((i) => i.number === number);
if (!issue) notFound();
- const bodyHtml = issue.description?.trim()
- ? renderMarkdown(issue.description)
- : null;
- const epic = issue.epicSlug
- ? tracker.epics.find((e) => e.slug === issue.epicSlug)
- : undefined;
+ const bodyHtml = issue.description?.trim() ? renderMarkdown(issue.description) : null;
+ const epic = issue.epicSlug ? tracker.epics.find((e) => e.slug === issue.epicSlug) : undefined;
const createdTs = issue.created ? Date.parse(issue.created) : NaN;
const modifiedTs = issue.modified ? Date.parse(issue.modified) : NaN;
// The fold emits day-resolution xsd:date values; a relative time off a
// bare date reads as "21h ago" for an issue minted a minute ago.
- const dateOnly = (v: string | undefined) =>
- v && /^\d{4}-\d{2}-\d{2}$/.test(v) ? v : null;
+ const dateOnly = (v: string | undefined) => (v && /^\d{4}-\d{2}-\d{2}$/.test(v) ? v : null);
const createdDate = dateOnly(issue.created);
const modifiedDate = dateOnly(issue.modified);
@@ -55,8 +50,7 @@ export default async function IssueDetailPage({ params }: PageProps) {
className="display break-words text-2xl sm:text-3xl"
style={{ fontFamily: "var(--font-display)" }}
>
-
#{issue.number} {" "}
- {issue.title}
+
#{issue.number} {issue.title}
{issue.stateLabel ?? (issue.open ? "open" : "closed")}
@@ -121,14 +115,9 @@ export default async function IssueDetailPage({ params }: PageProps) {
{bodyHtml ? (
-
+
) : (
-
- (no description)
-
+
(no description)
)}
@@ -137,9 +126,8 @@ export default async function IssueDetailPage({ params }: PageProps) {
className="mt-6 text-[11px] uppercase tracking-[0.18em] text-[color:var(--ink-faint)]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
- Read-only · folded from this repo's{" "}
- .mind tracker. Edit by authoring events under{" "}
- .mind/issues/ and pushing.
+ Read-only · folded from this repo's .mind tracker. Edit by
+ authoring events under .mind/issues/ and pushing.
);
diff --git a/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/collaborative-draft.tsx b/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/collaborative-draft.tsx
index 7ec8c8f..565de9f 100644
--- a/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/collaborative-draft.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/collaborative-draft.tsx
@@ -1,39 +1,39 @@
"use client";
-import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { useRouter } from "next/navigation";
-import Link from "next/link";
-import { useEditor, EditorContent, type Editor } from "@tiptap/react";
-import StarterKit from "@tiptap/starter-kit";
-import Collaboration from "@tiptap/extension-collaboration";
-import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
-import TaskList from "@tiptap/extension-task-list";
-import TaskItem from "@tiptap/extension-task-item";
-import Placeholder from "@tiptap/extension-placeholder";
-import { Markdown } from "tiptap-markdown";
import {
Button,
Input,
- Textarea,
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
Tabs,
TabsList,
TabsTrigger,
+ Textarea,
ToggleGroup,
ToggleGroupItem,
- Select,
- SelectTrigger,
- SelectValue,
- SelectContent,
- SelectItem,
} from "@mind-studio/ui";
+import Collaboration from "@tiptap/extension-collaboration";
+import CollaborationCursor from "@tiptap/extension-collaboration-cursor";
+import Placeholder from "@tiptap/extension-placeholder";
+import TaskItem from "@tiptap/extension-task-item";
+import TaskList from "@tiptap/extension-task-list";
+import { type Editor, EditorContent, useEditor } from "@tiptap/react";
+import StarterKit from "@tiptap/starter-kit";
+import Link from "next/link";
+import { useRouter } from "next/navigation";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { Markdown } from "tiptap-markdown";
import { authedFetch } from "@/lib/auth/csrf-client";
+import { colorForClient, draftRoomName } from "@/lib/collab/config";
import {
createDraftDoc,
- readDraftMeta,
type DraftDoc,
type DraftMeta,
+ readDraftMeta,
} from "@/lib/collab/draft-doc";
-import { colorForClient, draftRoomName } from "@/lib/collab/config";
import { suggestKind } from "@/lib/collab/suggest-kind";
export type CategoryOption = { id: string; label: string };
@@ -60,10 +60,7 @@ const PRIORITIES = ["urgent", "high", "normal", "low"];
*/
export function CollaborativeDraft(props: CollaborativeDraftProps) {
const { owner, repo, draftId, collab } = props;
- const roomName = useMemo(
- () => draftRoomName(owner, repo, draftId),
- [owner, repo, draftId],
- );
+ const roomName = useMemo(() => draftRoomName(owner, repo, draftId), [owner, repo, draftId]);
const [draft, setDraft] = useState(null);
useEffect(() => {
@@ -231,8 +228,7 @@ function DraftEditor({
setSubmitting(true);
const current = readDraftMeta(draft.meta);
const title = current.title.trim();
- const body =
- view === "markdown" ? mdDraft : editor.storage.markdown.getMarkdown();
+ const body = view === "markdown" ? mdDraft : editor.storage.markdown.getMarkdown();
try {
let dest: string;
if (current.kind === "epic") {
@@ -276,8 +272,7 @@ function DraftEditor({
}
}
- const labelClass =
- "text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]";
+ const labelClass = "text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]";
const mono = { fontFamily: "var(--font-mono-src)" };
const isEpic = meta.kind === "epic";
const canCreate = isOwner && meta.title.trim().length > 0 && !submitting;
@@ -450,8 +445,8 @@ function DraftEditor({
) : (
- An epic is a goal that groups issues — it just needs a title and a goal
- narrative below. Add issues to it afterwards.
+ An epic is a goal that groups issues — it just needs a title and a goal narrative below.
+ Add issues to it afterwards.
)}
@@ -511,11 +506,7 @@ function DraftEditor({
- {submitting
- ? "Creating…"
- : isEpic
- ? "Create epic"
- : "Create issue"}
+ {submitting ? "Creating…" : isEpic ? "Create epic" : "Create issue"}
{!isOwner ? (
@@ -523,13 +514,11 @@ function DraftEditor({
) : (
- commits a .mind {isEpic ? "epic" : "issue"} · folded & pushed to the repo
+ commits a .mind {isEpic ? "epic" : "issue"} · folded & pushed to the
+ repo
)}
-
+
cancel
@@ -565,9 +554,7 @@ function EditorToolbar({ editor }: { editor: Editor | null }) {
style={{
fontFamily: "var(--font-mono-src)",
color: active ? "var(--accent-deep)" : "var(--ink-soft)",
- background: active
- ? "color-mix(in srgb, var(--accent) 16%, transparent)"
- : "transparent",
+ background: active ? "color-mix(in srgb, var(--accent) 16%, transparent)" : "transparent",
...style,
}}
>
@@ -575,9 +562,7 @@ function EditorToolbar({ editor }: { editor: Editor | null }) {
);
- const Sep = () => (
-
- );
+ const Sep = () => ;
return (
diff --git a/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/draft-loader.tsx b/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/draft-loader.tsx
index b4dece9..ac1ff98 100644
--- a/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/draft-loader.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/draft-loader.tsx
@@ -12,9 +12,7 @@ const CollaborativeDraft = dynamic(
() => import("./collaborative-draft").then((m) => m.CollaborativeDraft),
{
ssr: false,
- loading: () => (
-
Loading the composer…
- ),
+ loading: () =>
Loading the composer…
,
},
);
diff --git a/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/page.tsx b/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/page.tsx
index 6fc4a46..1c4dc34 100644
--- a/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/page.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/draft/[draftId]/page.tsx
@@ -1,11 +1,11 @@
import Link from "next/link";
import { notFound, redirect } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
-import { repoPath } from "@/lib/git/backend";
-import { readGitTracker } from "@/lib/tracker/read";
import { readSession } from "@/lib/auth/session";
-import { getUserByWebId } from "@/lib/registry/users";
import { nameFromWebId } from "@/lib/collab/config";
+import { repoPath } from "@/lib/git/backend";
+import { getRepo } from "@/lib/registry/repos";
+import { getUserByWebId } from "@/lib/registry/users";
+import { readGitTracker } from "@/lib/tracker/read";
import { RepoTabs } from "../../../repo-tabs";
import { DraftLoader } from "./draft-loader";
@@ -29,7 +29,9 @@ export default async function NewDraftPage({ params }: PageProps) {
// owner can commit). Bounce to sign-in, preserving the draft URL.
const session = await readSession();
if (!session) {
- redirect(`/login?next=${encodeURIComponent(`/repos/${owner}/${name}/issues/draft/${draftId}`)}`);
+ redirect(
+ `/login?next=${encodeURIComponent(`/repos/${owner}/${name}/issues/draft/${draftId}`)}`,
+ );
}
const tracker = await readGitTracker(repoPath(repo.owner, repo.name), owner, name);
@@ -41,8 +43,7 @@ export default async function NewDraftPage({ params }: PageProps) {
const epics = tracker.epics.map((e) => ({ slug: e.slug, title: e.title }));
const isOwner = session.webId === repo.ownerWebId;
- const displayName =
- getUserByWebId(session.webId)?.ownerSlug ?? nameFromWebId(session.webId);
+ const displayName = getUserByWebId(session.webId)?.ownerSlug ?? nameFromWebId(session.webId);
return (
@@ -51,10 +52,7 @@ export default async function NewDraftPage({ params }: PageProps) {
← issues
-
+
New draft
diff --git a/src/app/repos/[owner]/[repo]/issues/new/new-issue-form.tsx b/src/app/repos/[owner]/[repo]/issues/new/new-issue-form.tsx
index 206d3fb..b43993d 100644
--- a/src/app/repos/[owner]/[repo]/issues/new/new-issue-form.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/new/new-issue-form.tsx
@@ -1,18 +1,18 @@
"use client";
-import { useRouter } from "next/navigation";
-import { useState } from "react";
-import { authedFetch } from "@/lib/auth/csrf-client";
import {
Button,
Input,
- Textarea,
Select,
- SelectTrigger,
- SelectValue,
SelectContent,
SelectItem,
+ SelectTrigger,
+ SelectValue,
+ Textarea,
} from "@mind-studio/ui";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { authedFetch } from "@/lib/auth/csrf-client";
export type CategoryOption = { id: string; label: string };
export type EpicOption = { slug: string; title: string };
@@ -67,8 +67,7 @@ export function NewIssueForm({ owner, repo, categories, epics }: Props) {
}
}
- const labelClass =
- "text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]";
+ const labelClass = "text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]";
const mono = { fontFamily: "var(--font-mono-src)" };
return (
diff --git a/src/app/repos/[owner]/[repo]/issues/new/page.tsx b/src/app/repos/[owner]/[repo]/issues/new/page.tsx
index 561156a..9fd708d 100644
--- a/src/app/repos/[owner]/[repo]/issues/new/page.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/new/page.tsx
@@ -1,7 +1,7 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
import { repoPath } from "@/lib/git/backend";
+import { getRepo } from "@/lib/registry/repos";
import { readGitTracker } from "@/lib/tracker/read";
import { RepoTabs } from "../../repo-tabs";
import { NewIssueForm } from "./new-issue-form";
@@ -32,22 +32,14 @@ export default async function NewIssuePage({ params }: PageProps) {
← issues
-
+
New issue
-
+
);
diff --git a/src/app/repos/[owner]/[repo]/issues/page.tsx b/src/app/repos/[owner]/[repo]/issues/page.tsx
index 39e8c16..f7875e3 100644
--- a/src/app/repos/[owner]/[repo]/issues/page.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/page.tsx
@@ -1,11 +1,11 @@
import { randomUUID } from "node:crypto";
import Link from "next/link";
import { notFound } from "next/navigation";
+import { RelativeTime } from "@/components/relative-time";
import { getRepo } from "@/lib/registry/repos";
+import type { Tracker, TrackerIssue } from "@/lib/tracker/read";
import { groupByEpic } from "@/lib/tracker/read";
import { readRepoTracker } from "@/lib/tracker/source";
-import type { Tracker, TrackerIssue } from "@/lib/tracker/read";
-import { RelativeTime } from "@/components/relative-time";
import { RepoTabs } from "../repo-tabs";
export const dynamic = "force-dynamic";
@@ -17,10 +17,7 @@ type PageProps = {
searchParams: Promise<{ status?: string }>;
};
-export default async function IssuesListPage({
- params,
- searchParams,
-}: PageProps) {
+export default async function IssuesListPage({ params, searchParams }: PageProps) {
const { owner, repo: name } = await params;
const { status: statusParam } = await searchParams;
const repo = getRepo(owner, name);
@@ -39,16 +36,11 @@ export default async function IssuesListPage({
-
+
Issues
- {repo.proposalsEnabled ? (
-
- ) : null}
+ {repo.proposalsEnabled ?
: null}
{tracker !== null ?
: null}
@@ -124,8 +116,7 @@ function TrackerBoard({
) : (
{groups.map((group) => {
- const key =
- group.kind === "epic" ? `epic:${group.epic.slug}` : "general";
+ const key = group.kind === "epic" ? `epic:${group.epic.slug}` : "general";
return (
@@ -155,11 +146,7 @@ function TrackerBoard({
{group.issues.map((issue, i) => (
0
- ? "border-t border-[color:var(--ink-trace)]"
- : undefined
- }
+ className={i > 0 ? "border-t border-[color:var(--ink-trace)]" : undefined}
>
@@ -188,20 +175,14 @@ function EpicHeader({
}) {
return (
-
+
{number !== undefined ? (
Epic {number} ·
) : null}
{title}
{epicStatus ? (
-
+
{epicStatus}
) : null}
@@ -273,32 +254,20 @@ function FilterBar({
);
}
-function IssueRow({
- owner,
- repo,
- issue,
-}: {
- owner: string;
- repo: string;
- issue: TrackerIssue;
-}) {
+function IssueRow({ owner, repo, issue }: { owner: string; repo: string; issue: TrackerIssue }) {
const isBlocked = issue.stateId === "Blocked" || issue.blockedBy.length > 0;
const excerpt = makeExcerpt(issue.description ?? "");
const modifiedTs = issue.modified ? Date.parse(issue.modified) : NaN;
// The fold emits day-resolution xsd:date values; rendering those as a
// relative time reads as "21h ago" for an issue minted a minute ago.
const modifiedDateOnly =
- issue.modified && /^\d{4}-\d{2}-\d{2}$/.test(issue.modified)
- ? issue.modified
- : null;
+ issue.modified && /^\d{4}-\d{2}-\d{2}$/.test(issue.modified) ? issue.modified : null;
return (
@@ -328,9 +297,7 @@ function IssueRow({
updated
) : null}
- {issue.blocks.length > 0 ? (
- blocks {issue.blocks.length}
- ) : null}
+ {issue.blocks.length > 0 ? blocks {issue.blocks.length} : null}
{issue.blockedBy.length > 0 ? (
blocked by {issue.blockedBy.length}
@@ -340,8 +307,7 @@ function IssueRow({
- #{issue.number} {" "}
- {issue.title}
+ #{issue.number} {issue.title}
{excerpt ? (
.mind tracker in this repo.
- This dashboard renders the repo's .mind{" "}
- tracker straight from the pushed git history. Author issues as markdown
- folders under .mind/issues/, run{" "}
+ This dashboard renders the repo's .mind tracker straight
+ from the pushed git history. Author issues as markdown folders under{" "}
+ .mind/issues/, run{" "}
npm run tracker:build, and push{" "}
- .mind/build/{tracker,epics,state}.ttl{" "}
- — they'll appear here, grouped by epic.
+ .mind/build/{tracker,epics,state}.ttl — they'll
+ appear here, grouped by epic.
);
diff --git a/src/app/repos/[owner]/[repo]/issues/propose/page.tsx b/src/app/repos/[owner]/[repo]/issues/propose/page.tsx
index d551710..55f7f58 100644
--- a/src/app/repos/[owner]/[repo]/issues/propose/page.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/propose/page.tsx
@@ -1,7 +1,7 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
import { readSession } from "@/lib/auth/session";
+import { getRepo } from "@/lib/registry/repos";
import { ProposeForm } from "./propose-form";
export const dynamic = "force-dynamic";
@@ -22,35 +22,31 @@ export default async function ProposeIssuePage({ params }: PageProps) {
← {owner}/{name} · Issues
-
+
Propose an issue
- Anyone can suggest work for {owner}/{name}.
- Your proposal goes to the owner's pod inbox for review — it isn't
- added to the tracker until the owner accepts it.
+ Anyone can suggest work for{" "}
+
+ {owner}/{name}
+
+ . Your proposal goes to the owner's pod inbox for review — it isn't added to the
+ tracker until the owner accepts it.
{repo.proposalsEnabled ? (
-
+
) : (
-
+
Proposals are closed.
- The owner of {owner}/{name} isn't
- accepting issue proposals right now.
+ The owner of{" "}
+
+ {owner}/{name}
+ {" "}
+ isn't accepting issue proposals right now.
)}
diff --git a/src/app/repos/[owner]/[repo]/issues/propose/propose-form.tsx b/src/app/repos/[owner]/[repo]/issues/propose/propose-form.tsx
index 9313234..d89031d 100644
--- a/src/app/repos/[owner]/[repo]/issues/propose/propose-form.tsx
+++ b/src/app/repos/[owner]/[repo]/issues/propose/propose-form.tsx
@@ -1,7 +1,7 @@
"use client";
-import { useState } from "react";
import { Button, Input, Textarea } from "@mind-studio/ui";
+import { useState } from "react";
type Props = {
owner: string;
@@ -48,23 +48,18 @@ export function ProposeForm({ owner, repo, proposerWebId }: Props) {
}
}
- const labelClass =
- "text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]";
+ const labelClass = "text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]";
const mono = { fontFamily: "var(--font-mono-src)" };
if (done) {
return (
-
+
Proposal submitted.
- It landed in {owner}'s pod inbox.
- The owner will review it and, if accepted, it becomes a tracked issue.
- Thanks for the suggestion.
+ It landed in {owner}'s pod inbox. The owner will review
+ it and, if accepted, it becomes a tracked issue. Thanks for the suggestion.
);
diff --git a/src/app/repos/[owner]/[repo]/nav-tabs.tsx b/src/app/repos/[owner]/[repo]/nav-tabs.tsx
index 442f61b..11172fc 100644
--- a/src/app/repos/[owner]/[repo]/nav-tabs.tsx
+++ b/src/app/repos/[owner]/[repo]/nav-tabs.tsx
@@ -26,9 +26,7 @@ export function NavTabs({ tabs }: { tabs: Tab[] }) {
<>
{tab.label}
{typeof tab.count === "number" ? (
-
+
{tab.count}
) : null}
@@ -41,13 +39,7 @@ export function NavTabs({ tabs }: { tabs: Tab[] }) {
);
if (tab.external) {
return (
-
+
{content}
);
diff --git a/src/app/repos/[owner]/[repo]/packages/page.tsx b/src/app/repos/[owner]/[repo]/packages/page.tsx
index effed9a..924481a 100644
--- a/src/app/repos/[owner]/[repo]/packages/page.tsx
+++ b/src/app/repos/[owner]/[repo]/packages/page.tsx
@@ -1,16 +1,16 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
+import { CopyButton } from "@/components/copy-button";
+import { RelativeTime } from "@/components/relative-time";
+import { readSession } from "@/lib/auth/session";
+import { formatBytes } from "@/lib/format";
import {
- listPackages,
isDigestRef,
+ listPackages,
type PackageRecord,
type PackageType,
} from "@/lib/packages/store";
-import { readSession } from "@/lib/auth/session";
-import { RelativeTime } from "@/components/relative-time";
-import { CopyButton } from "@/components/copy-button";
-import { formatBytes } from "@/lib/format";
+import { getRepo } from "@/lib/registry/repos";
import { RepoTabs } from "../repo-tabs";
export const dynamic = "force-dynamic";
@@ -60,10 +60,7 @@ export default async function PackagesPage({ params }: PageProps) {
← {owner}/{name}
-
+
Packages
{!locked && groups.length > 0 ? (
@@ -71,8 +68,7 @@ export default async function PackagesPage({ params }: PageProps) {
className="mt-2 text-[11px] uppercase tracking-[0.18em] text-[color:var(--ink-faint)]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
- {groups.length} {groups.length === 1 ? "package" : "packages"} · bytes
- live in the pod
+ {groups.length} {groups.length === 1 ? "package" : "packages"} · bytes live in the pod
) : null}
@@ -90,12 +86,7 @@ export default async function PackagesPage({ params }: PageProps) {
{section.items.map((g) => (
-
+
))}
@@ -260,9 +251,9 @@ function PackagesEmptyState({ owner, name }: { owner: string; name: string }) {
Nothing published yet.
- Publish npm packages, container images, or generic files to this repo
- and they show up here — the bytes are stored in the owner's pod,
- addressed by digest. Auth reuses this repo's{" "}
+ Publish npm packages, container images, or generic files to this repo and they show up here
+ — the bytes are stored in the owner's pod, addressed by digest. Auth reuses this
+ repo's{" "}
push tokens
diff --git a/src/app/repos/[owner]/[repo]/page.tsx b/src/app/repos/[owner]/[repo]/page.tsx
index 740f00c..7bf5912 100644
--- a/src/app/repos/[owner]/[repo]/page.tsx
+++ b/src/app/repos/[owner]/[repo]/page.tsx
@@ -1,29 +1,26 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo, getPagesConfig } from "@/lib/registry/repos";
-import { listPushTokens } from "@/lib/registry/tokens";
-import {
- listRunsForRepo,
- type WorkflowRun,
-} from "@/lib/registry/runs";
-import { isOrg } from "@/lib/registry/owners";
+import { CopyButton } from "@/components/copy-button";
+import { RelativeTime } from "@/components/relative-time";
+import { formatDuration } from "@/lib/format";
import { repoPath } from "@/lib/git/backend";
import {
+ type CommitSummary,
findReadme,
hasAnyCommits,
listBranches,
listRecentCommits,
listTree,
- type CommitSummary,
type TreeEntry,
} from "@/lib/git/objects";
import { renderMarkdown } from "@/lib/markdown";
-import { RelativeTime } from "@/components/relative-time";
-import { CopyButton } from "@/components/copy-button";
-import { formatDuration } from "@/lib/format";
-import { TokenManager } from "./token-manager";
-import { RerunButton } from "./rerun-button";
+import { isOrg } from "@/lib/registry/owners";
+import { getPagesConfig, getRepo } from "@/lib/registry/repos";
+import { listRunsForRepo, type WorkflowRun } from "@/lib/registry/runs";
+import { listPushTokens } from "@/lib/registry/tokens";
import { RepoTabs } from "./repo-tabs";
+import { RerunButton } from "./rerun-button";
+import { TokenManager } from "./token-manager";
export const dynamic = "force-dynamic";
@@ -54,13 +51,9 @@ export default async function RepoDetailPage({ params }: PageProps) {
// Fetch 7 so we can show the latest commit in the file-listing "ribbon"
// AND still have up-to-6 distinct entries in the Recent commits section
// below.
- const recentCommits = hasCommits
- ? await listRecentCommits(bare, repo.defaultBranch, 7)
- : [];
+ const recentCommits = hasCommits ? await listRecentCommits(bare, repo.defaultBranch, 7) : [];
const earlierCommits = recentCommits.slice(1);
- const rootEntries = hasCommits
- ? await listTree(bare, repo.defaultBranch, "")
- : [];
+ const rootEntries = hasCommits ? await listTree(bare, repo.defaultBranch, "") : [];
const latestCommit = recentCommits[0] ?? null;
// Pull the 5 most recent runs: index 0 is the "latest build" panel,
@@ -148,16 +141,10 @@ export default async function RepoDetailPage({ params }: PageProps) {
-
+
-
+
- {pages?.enabled && pages.targetContainer
- ? "not published"
- : "off"}
+ {pages?.enabled && pages.targetContainer ? "not published" : "off"}
)
}
@@ -218,18 +203,17 @@ export default async function RepoDetailPage({ params }: PageProps) {
- {pages.lastPublishedAt ? (
-
- ) : (
- "never"
- )}
+ {pages.lastPublishedAt ? : "never"}
>
) : (
Not enabled. Configure with{" "}
- PUT /api/repos/{repo.owner}/{repo.name}/pages.
+
+ PUT /api/repos/{repo.owner}/{repo.name}/pages
+
+ .
)}
@@ -263,11 +247,7 @@ export default async function RepoDetailPage({ params }: PageProps) {
-
+
@@ -277,11 +257,7 @@ export default async function RepoDetailPage({ params }: PageProps) {
);
}
-function PublishStatusBanner({
- pages,
-}: {
- pages: ReturnType
| null;
-}) {
+function PublishStatusBanner({ pages }: { pages: ReturnType | null }) {
if (!pages || !pages.enabled) return null;
if (pages.lastPublishStatus !== "failed" && pages.lastPublishStatus !== "needs-reauth") {
return null;
@@ -306,12 +282,18 @@ function PublishStatusBanner({
{action}
{pages.lastPublishError ? (
-
+
{pages.lastPublishError}
) : null}
{pages.lastPublishAttempt ? (
-
+
attempt ·
) : null}
@@ -409,7 +391,8 @@ function renderBuildStatus(run: WorkflowRun, owner: string, name: string) {
: run.status === "running" || run.status === "queued"
? undefined
: "bad";
- const symbol = run.status === "success" ? "✓" : run.status === "failed" || run.status === "error" ? "✗" : "·";
+ const symbol =
+ run.status === "success" ? "✓" : run.status === "failed" || run.status === "error" ? "✗" : "·";
const trailing =
run.status === "success"
? formatDuration(run.startedAt, run.finishedAt)
@@ -436,16 +419,10 @@ function renderPagesStatus(
publishedUrl: string | null,
) {
if (!pages?.enabled || !pages.targetContainer) {
- return (
- pages off
- );
+ return pages off ;
}
if (!publishedUrl) {
- return (
-
- pages on · not published
-
- );
+ return pages on · not published ;
}
return (
<>
@@ -459,22 +436,14 @@ function renderPagesStatus(
rel="noreferrer"
className="text-[color:var(--ink-faint)] hover:text-[color:var(--accent)]"
>
- {pages.lastPublishedAt ? (
-
- ) : (
- "open ↗"
- )}
+ {pages.lastPublishedAt ? : "open ↗"}
) : null}
>
);
}
-function renderRunsCount(
- count: number,
- owner: string,
- name: string,
-) {
+function renderRunsCount(count: number, owner: string, name: string) {
if (count === 0) return null;
const display = count >= 50 ? "50+" : count.toString();
return (
@@ -545,24 +514,18 @@ function EmptyReadme({
if (hasCommits) {
return (
-
+
No README yet.
- Add a README.md to the default branch
- and it'll render here.
+ Add a README.md to the default branch and it'll render
+ here.
);
}
- const tokenizedCloneUrl = cloneUrl.replace(
- /^(https?:\/\/)/,
- "$1USER:@",
- );
+ const tokenizedCloneUrl = cloneUrl.replace(/^(https?:\/\/)/, "$1USER:@");
const newRepo = `echo "# ${name}" >> README.md
git init
@@ -579,10 +542,7 @@ git push -u origin ${defaultBranch}`;
return (
-
+
Nothing pushed yet.
@@ -591,37 +551,27 @@ git push -u origin ${defaultBranch}`;
Push tokens · Mint first token .
- Copy the token, then paste it where{" "}
- <TOKEN> appears below.
+ Copy the token, then paste it where <TOKEN> appears
+ below.
Run the commands in your terminal.
- The token goes into your local .git/config,
- which is convenient and easy to revoke later (sidebar → Push tokens). If
- you prefer your OS keychain instead, drop the{" "}
- USER:<TOKEN>@ prefix and let git
- prompt — but make sure your credential helper isn't holding a stale
- credential for this host.
+ The token goes into your local .git/config, which is
+ convenient and easy to revoke later (sidebar → Push tokens). If you prefer your OS
+ keychain instead, drop the USER:<TOKEN>@ prefix and let
+ git prompt — but make sure your credential helper isn't holding a stale credential
+ for this host.
-
+
);
}
-function FirstStepsBlock({
- title,
- snippet,
-}: {
- title: string;
- snippet: string;
-}) {
+function FirstStepsBlock({ title, snippet }: { title: string; snippet: string }) {
return (
@@ -698,11 +648,7 @@ function FileListing({
{entries.map((entry) => (
-
+
))}
@@ -834,9 +780,7 @@ function CommitRow({
return (
- {commit.subject || (
- (no subject)
- )}
+ {commit.subject || (no subject) }
-
+
Latest build
-
+
{previousRuns.length > 0 ? (
<>
@@ -1015,14 +952,11 @@ function PreviousRunRow({
{run.status}
{isBad && run.exitCode !== null ? (
-
- exit {run.exitCode}
-
+
exit {run.exitCode}
) : null}
- ·{" "}
- {formatDuration(run.startedAt, run.finishedAt)}
+ · {formatDuration(run.startedAt, run.finishedAt)}
);
@@ -1033,17 +967,13 @@ function NoBuildsHint() {
// workflows
-
+
Run code before you publish.
- Add a .mind/workflow.yml to the default
- branch. On push, the bridge checks out your repo into a sandboxed{" "}
- node:22-alpine container, runs your{" "}
- run: steps, then publishes the result. See{" "}
+ Add a .mind/workflow.yml to the default branch. On push, the
+ bridge checks out your repo into a sandboxed node:22-alpine{" "}
+ container, runs your run: steps, then publishes the result. See{" "}
how it works
{" "}
@@ -1103,9 +1033,7 @@ function LatestBuild({
{run.errorMessage ? (
<>
Error
-
- {run.errorMessage}
-
+ {run.errorMessage}
>
) : null}
@@ -1158,13 +1086,7 @@ function SidebarSection({
);
}
-function SidebarFact({
- label,
- children,
-}: {
- label: string;
- children: React.ReactNode;
-}) {
+function SidebarFact({ label, children }: { label: string; children: React.ReactNode }) {
return (
-
+
Proposals
- Issue proposals submitted to this repo's pod inbox. Accept one to
- mint a .mind issue at{" "}
- todo, or dismiss it.
+ Issue proposals submitted to this repo's pod inbox. Accept one to mint a{" "}
+ .mind issue at todo, or dismiss
+ it.
{!hasTracker ? (
- No .mind tracker in this repo yet —
- proposals can be reviewed and dismissed, but{" "}
- Accept needs{" "}
- .mind/issues/tracker.config.md on the
- default branch.
+ No .mind tracker in this repo yet — proposals can be reviewed
+ and dismissed, but Accept needs{" "}
+ .mind/issues/tracker.config.md on the default branch.
) : null}
@@ -83,10 +78,7 @@ export default async function ProposalsPage({ params }: PageProps) {
) : proposals.length === 0 ? (
-
+
Inbox empty.
diff --git a/src/app/repos/[owner]/[repo]/proposals/proposal-actions.tsx b/src/app/repos/[owner]/[repo]/proposals/proposal-actions.tsx
index 98acb99..f90a547 100644
--- a/src/app/repos/[owner]/[repo]/proposals/proposal-actions.tsx
+++ b/src/app/repos/[owner]/[repo]/proposals/proposal-actions.tsx
@@ -1,16 +1,16 @@
"use client";
-import { useRouter } from "next/navigation";
-import { useState } from "react";
-import { authedFetch } from "@/lib/auth/csrf-client";
import {
Button,
Select,
- SelectTrigger,
- SelectValue,
SelectContent,
SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@mind-studio/ui";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { authedFetch } from "@/lib/auth/csrf-client";
type Props = {
owner: string;
@@ -23,13 +23,7 @@ type Props = {
const PRIORITIES = ["urgent", "high", "normal", "low"];
-export function ProposalActions({
- owner,
- repo,
- id,
- categories,
- canAccept = true,
-}: Props) {
+export function ProposalActions({ owner, repo, id, categories, canAccept = true }: Props) {
const router = useRouter();
const [type, setType] = useState(categories[0]?.id ?? "feature");
const [priority, setPriority] = useState("normal");
@@ -40,10 +34,10 @@ export function ProposalActions({
setError(null);
setBusy("accept");
try {
- const res = await authedFetch(
- `/api/repos/${owner}/${repo}/inbox/${id}/accept`,
- { method: "POST", body: JSON.stringify({ type, priority }) },
- );
+ const res = await authedFetch(`/api/repos/${owner}/${repo}/inbox/${id}/accept`, {
+ method: "POST",
+ body: JSON.stringify({ type, priority }),
+ });
if (!res.ok) {
const data = (await res.json().catch(() => ({}))) as { error?: string };
throw new Error(data.error ?? `request failed: ${res.status}`);
@@ -75,8 +69,7 @@ export function ProposalActions({
}
}
- const labelClass =
- "text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]";
+ const labelClass = "text-[10px] uppercase tracking-[0.22em] text-[color:var(--ink-faint)]";
const mono = { fontFamily: "var(--font-mono-src)" };
return (
diff --git a/src/app/repos/[owner]/[repo]/pulls/[number]/page.tsx b/src/app/repos/[owner]/[repo]/pulls/[number]/page.tsx
index e41bc48..09fa80e 100644
--- a/src/app/repos/[owner]/[repo]/pulls/[number]/page.tsx
+++ b/src/app/repos/[owner]/[repo]/pulls/[number]/page.tsx
@@ -1,21 +1,16 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
-import { getPullRequest } from "@/lib/registry/pulls";
-import { countComments, getIssueById } from "@/lib/registry/issues";
-import { repoPath } from "@/lib/git/backend";
-import {
- commitsAhead,
- diffFiles,
- diffStat,
- unifiedDiff,
-} from "@/lib/git/diff";
+import { DiffView } from "@/components/diff-view";
import { RelativeTime } from "@/components/relative-time";
+import { repoPath } from "@/lib/git/backend";
+import { commitsAhead, diffFiles, diffStat, unifiedDiff } from "@/lib/git/diff";
import { renderMarkdown } from "@/lib/markdown";
-import { DiffView } from "@/components/diff-view";
-import { PullActions } from "./pull-actions";
-import { PrPreviewCard } from "./pr-preview-card";
+import { countComments, getIssueById } from "@/lib/registry/issues";
+import { getPullRequest } from "@/lib/registry/pulls";
+import { getRepo } from "@/lib/registry/repos";
import { RepoTabs } from "../../repo-tabs";
+import { PrPreviewCard } from "./pr-preview-card";
+import { PullActions } from "./pull-actions";
export const dynamic = "force-dynamic";
@@ -34,13 +29,8 @@ export default async function PullDetailPage({ params }: PageProps) {
const bare = repoPath(repo.owner, repo.name);
const baseRef =
- pull.status === "merged" && pull.mergeSha
- ? `${pull.mergeSha}^1`
- : pull.targetBranch;
- const headRef =
- pull.status === "merged" && pull.mergeSha
- ? pull.mergeSha
- : pull.sourceBranch;
+ pull.status === "merged" && pull.mergeSha ? `${pull.mergeSha}^1` : pull.targetBranch;
+ const headRef = pull.status === "merged" && pull.mergeSha ? pull.mergeSha : pull.sourceBranch;
// Diff data is best-effort: if the source branch has been deleted
// post-merge, we silently fall back to empty arrays so the page still
@@ -61,9 +51,7 @@ export default async function PullDetailPage({ params }: PageProps) {
const bodyHtml = pull.body.trim() ? renderMarkdown(pull.body) : null;
const linkedIssue = pull.issueId ? getIssueById(pull.issueId) : null;
- const linkedIssueComments = linkedIssue
- ? countComments(linkedIssue.id)
- : 0;
+ const linkedIssueComments = linkedIssue ? countComments(linkedIssue.id) : 0;
return (
@@ -78,13 +66,9 @@ export default async function PullDetailPage({ params }: PageProps) {
className="display min-w-0 break-words text-2xl sm:text-3xl"
style={{ fontFamily: "var(--font-display)", overflowWrap: "anywhere" }}
>
- #{pull.number} {" "}
- {pull.title}
+ #{pull.number} {pull.title}
-
+
{pull.status}
@@ -124,16 +108,10 @@ export default async function PullDetailPage({ params }: PageProps) {
<>
{" "}
· closes{" "}
-
+
#{linkedIssue.number}
{" "}
-
+
{linkedIssue.status}
>
@@ -154,14 +132,9 @@ export default async function PullDetailPage({ params }: PageProps) {
{bodyHtml ? (
-
+
) : (
-
- (no description)
-
+
(no description)
)}
@@ -265,9 +238,7 @@ export default async function PullDetailPage({ params }: PageProps) {
>
{f.status}
- {f.oldPath && f.oldPath !== f.newPath
- ? `${f.oldPath} → ${f.newPath}`
- : f.newPath}
+ {f.oldPath && f.oldPath !== f.newPath ? `${f.oldPath} → ${f.newPath}` : f.newPath}
+{f.insertions} {" "}
diff --git a/src/app/repos/[owner]/[repo]/pulls/[number]/pr-preview-card.tsx b/src/app/repos/[owner]/[repo]/pulls/[number]/pr-preview-card.tsx
index 26b928e..6ec628d 100644
--- a/src/app/repos/[owner]/[repo]/pulls/[number]/pr-preview-card.tsx
+++ b/src/app/repos/[owner]/[repo]/pulls/[number]/pr-preview-card.tsx
@@ -1,9 +1,9 @@
"use client";
+import { Button } from "@mind-studio/ui";
import { useEffect, useRef, useState } from "react";
-import type { PreviewStatus } from "@/lib/registry/pulls";
import { authedFetch } from "@/lib/auth/csrf-client";
-import { Button } from "@mind-studio/ui";
+import type { PreviewStatus } from "@/lib/registry/pulls";
const POLL_INTERVAL_MS = 1500;
const ANSI_ESCAPE = /\x1b\[[0-9;]*m/g; // eslint-disable-line no-control-regex
@@ -107,18 +107,9 @@ export function PrPreviewCard({
}
}
- const tone =
- state.status === "ready"
- ? "ok"
- : state.status === "failed"
- ? "bad"
- : undefined;
+ const tone = state.status === "ready" ? "ok" : state.status === "failed" ? "bad" : undefined;
const buildLabel =
- state.status === "building"
- ? "Building…"
- : state.status
- ? "Rebuild preview"
- : "Build preview";
+ state.status === "building" ? "Building…" : state.status ? "Rebuild preview" : "Build preview";
return (
@@ -138,12 +129,7 @@ export function PrPreviewCard({
{state.status === "ready" && state.url ? (
-
+
Open preview ↗
) : null}
diff --git a/src/app/repos/[owner]/[repo]/pulls/[number]/pull-actions.tsx b/src/app/repos/[owner]/[repo]/pulls/[number]/pull-actions.tsx
index 3a64520..ae6a638 100644
--- a/src/app/repos/[owner]/[repo]/pulls/[number]/pull-actions.tsx
+++ b/src/app/repos/[owner]/[repo]/pulls/[number]/pull-actions.tsx
@@ -1,9 +1,9 @@
"use client";
+import { Button } from "@mind-studio/ui";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { authedFetch } from "@/lib/auth/csrf-client";
-import { Button } from "@mind-studio/ui";
/**
* Merge/close buttons for an open PR. Both fire server actions through
@@ -26,10 +26,9 @@ export function PullActions({
setBusy(action);
setError(null);
try {
- const res = await authedFetch(
- `/api/repos/${owner}/${repo}/pulls/${number}/${action}`,
- { method: "POST" },
- );
+ const res = await authedFetch(`/api/repos/${owner}/${repo}/pulls/${number}/${action}`, {
+ method: "POST",
+ });
if (!res.ok) {
const body = (await res.json().catch(() => ({}))) as { error?: string };
setError(body.error ?? `${action} failed (HTTP ${res.status})`);
@@ -46,12 +45,7 @@ export function PullActions({
return (
- fire("merge")}
- disabled={busy !== null}
- >
+ fire("merge")} disabled={busy !== null}>
{busy === "merge" ? "Merging…" : "Merge pull request"}
- {error ? (
-
{error}
- ) : null}
+ {error ?
{error}
: null}
);
}
diff --git a/src/app/repos/[owner]/[repo]/pulls/page.tsx b/src/app/repos/[owner]/[repo]/pulls/page.tsx
index 65736d5..6b2d1e7 100644
--- a/src/app/repos/[owner]/[repo]/pulls/page.tsx
+++ b/src/app/repos/[owner]/[repo]/pulls/page.tsx
@@ -1,12 +1,8 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
-import {
- countPullRequestsByStatus,
- listPullRequests,
- type PullStatus,
-} from "@/lib/registry/pulls";
import { RelativeTime } from "@/components/relative-time";
+import { countPullRequestsByStatus, listPullRequests, type PullStatus } from "@/lib/registry/pulls";
+import { getRepo } from "@/lib/registry/repos";
import { RepoTabs } from "../repo-tabs";
export const dynamic = "force-dynamic";
@@ -25,9 +21,7 @@ export default async function PullsPage({ params, searchParams }: PageProps) {
if (!repo) notFound();
const filter = (
- VALID_FILTERS.includes(sp.status as (typeof VALID_FILTERS)[number])
- ? sp.status
- : "open"
+ VALID_FILTERS.includes(sp.status as (typeof VALID_FILTERS)[number]) ? sp.status : "open"
) as PullStatus | "all";
const pulls = listPullRequests(repo.id, filter);
@@ -41,10 +35,7 @@ export default async function PullsPage({ params, searchParams }: PageProps) {
-
+
Pull requests
@@ -65,11 +56,7 @@ export default async function PullsPage({ params, searchParams }: PageProps) {
{pulls.map((p, i) => {
const isOpen = p.status === "open";
const tone =
- p.status === "merged"
- ? "ok"
- : p.status === "closed"
- ? undefined
- : undefined;
+ p.status === "merged" ? "ok" : p.status === "closed" ? undefined : undefined;
const stampStyle =
p.status === "closed"
? {
@@ -82,34 +69,22 @@ export default async function PullsPage({ params, searchParams }: PageProps) {
return (
0
- ? "border-t border-[color:var(--ink-trace)]"
- : undefined
- }
+ className={i > 0 ? "border-t border-[color:var(--ink-trace)]" : undefined}
>
-
+
{p.status}
-
- #{p.number}
- {" "}
+ #{p.number} {" "}
{p.title}
{excerpt ? (
@@ -130,23 +105,15 @@ export default async function PullsPage({ params, searchParams }: PageProps) {
style={{ fontFamily: "var(--font-mono-src)" }}
>
-
- {p.sourceBranch}
- {" "}
- →{" "}
-
- {p.targetBranch}
-
+ {p.sourceBranch} →{" "}
+ {p.targetBranch}
opened
-
+
→
@@ -208,17 +175,13 @@ function FilterBar({
background: isCurrent
? "color-mix(in srgb, var(--accent) 14%, transparent)"
: "transparent",
- color: isCurrent
- ? "var(--accent-deep)"
- : "var(--ink-soft)",
+ color: isCurrent ? "var(--accent-deep)" : "var(--ink-soft)",
}}
>
{item.label}{" "}
{item.count}
@@ -254,8 +217,7 @@ function EmptyState({
Nothing {filter === "all" ? "here yet" : `${filter}`}.
- When a pull request is {filter === "all" ? "opened" : filter}, it
- will show up here.
+ When a pull request is {filter === "all" ? "opened" : filter}, it will show up here.
);
@@ -269,14 +231,12 @@ function EmptyState({
No open pull requests.
- The coder agent opens
- a draft pull request automatically when it pushes a branch in
- response to an issue — the PR then targets{" "}
+ The coder agent opens a draft pull request
+ automatically when it pushes a branch in response to an issue — the PR then targets{" "}
{defaultBranch}.
- You can also push your own branch to this bridge from the command
- line; see{" "}
+ You can also push your own branch to this bridge from the command line; see{" "}
the repo page
{" "}
@@ -287,16 +247,15 @@ function EmptyState({
style={{ fontFamily: "var(--font-mono-src)" }}
>
- open —
- unmerged, source branch still alive
+ open — unmerged, source branch still
+ alive
- merged —
- fast-forwarded onto {defaultBranch}
+ merged — fast-forwarded onto{" "}
+ {defaultBranch}
- closed —
- rejected without merging
+ closed — rejected without merging
diff --git a/src/app/repos/[owner]/[repo]/repo-tabs.tsx b/src/app/repos/[owner]/[repo]/repo-tabs.tsx
index 3a4f756..92fe308 100644
--- a/src/app/repos/[owner]/[repo]/repo-tabs.tsx
+++ b/src/app/repos/[owner]/[repo]/repo-tabs.tsx
@@ -1,19 +1,12 @@
import "server-only";
-import { getRepo, getPagesConfig } from "@/lib/registry/repos";
-import { readRepoTracker } from "@/lib/tracker/source";
-import { countOpenPullRequests } from "@/lib/registry/pulls";
-import { listPackages } from "@/lib/packages/store";
import { readSession } from "@/lib/auth/session";
+import { listPackages } from "@/lib/packages/store";
+import { countOpenPullRequests } from "@/lib/registry/pulls";
+import { getPagesConfig, getRepo } from "@/lib/registry/repos";
+import { readRepoTracker } from "@/lib/tracker/source";
import { NavTabs } from "./nav-tabs";
-type ActiveKey =
- | "code"
- | "issues"
- | "pulls"
- | "runs"
- | "packages"
- | "proposals"
- | "settings";
+type ActiveKey = "code" | "issues" | "pulls" | "runs" | "packages" | "proposals" | "settings";
export async function RepoTabs({
owner,
@@ -33,16 +26,12 @@ export async function RepoTabs({
// Open-issue badge reflects the repo's .mind tracker (the same source the
// /issues board renders, pod-first), so the tab count matches the board.
const tracker = await readRepoTracker(repo, owner, name);
- const openIssueCount = tracker
- ? tracker.issues.filter((i) => i.open).length
- : 0;
+ const openIssueCount = tracker ? tracker.issues.filter((i) => i.open).length : 0;
const openPullCount = countOpenPullRequests(repo.id);
// Distinct published artifacts, counting each (type, name) once — an OCI
// image indexed by both tag and digest is one package, not two.
- const packageCount = new Set(
- listPackages(repo.id).map((p) => `${p.type}:${p.name}`),
- ).size;
+ const packageCount = new Set(listPackages(repo.id).map((p) => `${p.type}:${p.name}`)).size;
const pages = getPagesConfig(repo.id);
// "Live site" only once a publish actually landed — enabled-but-unpublished
diff --git a/src/app/repos/[owner]/[repo]/rerun-button.tsx b/src/app/repos/[owner]/[repo]/rerun-button.tsx
index e1f3b54..3cc350f 100644
--- a/src/app/repos/[owner]/[repo]/rerun-button.tsx
+++ b/src/app/repos/[owner]/[repo]/rerun-button.tsx
@@ -1,21 +1,21 @@
"use client";
-import { useState } from "react";
-import { useRouter } from "next/navigation";
-import { authedFetch } from "@/lib/auth/csrf-client";
import {
- Button,
- buttonVariants,
AlertDialog,
- AlertDialogTrigger,
+ AlertDialogAction,
+ AlertDialogCancel,
AlertDialogContent,
- AlertDialogHeader,
+ AlertDialogDescription,
AlertDialogFooter,
+ AlertDialogHeader,
AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogCancel,
- AlertDialogAction,
+ AlertDialogTrigger,
+ Button,
+ buttonVariants,
} from "@mind-studio/ui";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { authedFetch } from "@/lib/auth/csrf-client";
/**
* Manual workflow re-run trigger. Fires-and-forgets via the runs API,
@@ -65,9 +65,7 @@ export function RerunButton({ owner, repo }: { owner: string; repo: string }) {
-
- Cancel
-
+ Cancel
- {error ? (
- {error}
- ) : null}
+ {error ? {error} : null}
);
}
diff --git a/src/app/repos/[owner]/[repo]/runs/[id]/page.tsx b/src/app/repos/[owner]/[repo]/runs/[id]/page.tsx
index 36c5f1f..04685ab 100644
--- a/src/app/repos/[owner]/[repo]/runs/[id]/page.tsx
+++ b/src/app/repos/[owner]/[repo]/runs/[id]/page.tsx
@@ -26,9 +26,7 @@ export default async function RunDetailPage({ params }: PageProps) {
? undefined
: "bad";
const dur =
- run.finishedAt != null
- ? `${((run.finishedAt - run.startedAt) / 1000).toFixed(1)}s`
- : "—";
+ run.finishedAt != null ? `${((run.finishedAt - run.startedAt) / 1000).toFixed(1)}s` : "—";
return (
@@ -37,10 +35,7 @@ export default async function RunDetailPage({ params }: PageProps) {
← all runs
-
+
Run #{run.id}
Status
- {run.status}
+
+ {run.status}
+
{run.exitCode !== null ? (
-
+
Log
{run.logTail ? (
@@ -92,12 +86,10 @@ export default async function RunDetailPage({ params }: PageProps) {
className="mt-3 rounded border border-[color:var(--ink-trace)] bg-[color:var(--paper-sunk)] px-4 py-3 text-[0.8125rem] leading-[1.55] overflow-x-auto whitespace-pre max-w-full"
style={{ fontFamily: "var(--font-mono-src)", WebkitOverflowScrolling: "touch" }}
>
-{run.logTail}
+ {run.logTail}
) : (
-
- No log captured.
-
+ No log captured.
)}
);
diff --git a/src/app/repos/[owner]/[repo]/runs/page.tsx b/src/app/repos/[owner]/[repo]/runs/page.tsx
index 16cbd8e..3b495bf 100644
--- a/src/app/repos/[owner]/[repo]/runs/page.tsx
+++ b/src/app/repos/[owner]/[repo]/runs/page.tsx
@@ -1,9 +1,9 @@
import Link from "next/link";
import { notFound } from "next/navigation";
-import { getRepo } from "@/lib/registry/repos";
-import { listRunsForRepo, type WorkflowRun } from "@/lib/registry/runs";
import { RelativeTime } from "@/components/relative-time";
import { formatDuration } from "@/lib/format";
+import { getRepo } from "@/lib/registry/repos";
+import { listRunsForRepo, type WorkflowRun } from "@/lib/registry/runs";
import { RepoTabs } from "../repo-tabs";
export const dynamic = "force-dynamic";
@@ -25,10 +25,7 @@ export default async function RunsPage({ params }: PageProps) {
← {owner}/{name}
-
+
All runs
{runs.length > 0 ? (
@@ -36,7 +33,8 @@ export default async function RunsPage({ params }: PageProps) {
className="mt-2 text-[11px] uppercase tracking-[0.18em] text-[color:var(--ink-faint)]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
- {runs.length >= 50 ? "latest 50" : `${runs.length} ${runs.length === 1 ? "run" : "runs"}`} · newest first
+ {runs.length >= 50 ? "latest 50" : `${runs.length} ${runs.length === 1 ? "run" : "runs"}`}{" "}
+ · newest first
) : null}
@@ -72,9 +70,8 @@ function RunsEmptyState() {
A push only records a run when the repo contains a{" "}
- .mind/workflow.yml at its root. Without
- one, the bridge accepts the push, publishes Pages if configured, and
- stays quiet here.
+ .mind/workflow.yml at its root. Without one, the bridge accepts
+ the push, publishes Pages if configured, and stays quiet here.
See{" "}
@@ -87,15 +84,7 @@ function RunsEmptyState() {
);
}
-function RunCard({
- run,
- owner,
- name,
-}: {
- run: WorkflowRun;
- owner: string;
- name: string;
-}) {
+function RunCard({ run, owner, name }: { run: WorkflowRun; owner: string; name: string }) {
const failureSummary = summarizeFailure(run);
const isBad = run.status === "failed" || run.status === "error";
return (
@@ -133,8 +122,7 @@ function RunCard({
className="text-[11px] uppercase tracking-[0.18em] text-[color:var(--ink-faint)]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
- ·{" "}
- {formatDuration(run.startedAt, run.finishedAt)}
+ · {formatDuration(run.startedAt, run.finishedAt)}
- Every git push needs a token.
- Tokens are scoped to this repo and shown in plaintext exactly
- once at creation. Lose it and you mint a new one.
+ Every git push needs a token. Tokens are scoped to this
+ repo and shown in plaintext exactly once at creation. Lose it and you mint a new one.
@@ -161,9 +156,7 @@ export default async function RepoSettingsPage({ params }: PageProps) {
{env.coderImage}
Coder timeout
-
- {Math.round(env.coderTimeoutMs / 1000)}s per run
-
+
{Math.round(env.coderTimeoutMs / 1000)}s per run
Provider + model are owned by{" "}
@@ -180,9 +173,7 @@ export default async function RepoSettingsPage({ params }: PageProps) {
Roster
{roles.length === 0 ? (
-
- No roles registered.
-
+ No roles registered.
) : (
{roles.map((role) => (
@@ -201,20 +192,14 @@ export default async function RepoSettingsPage({ params }: PageProps) {
driver · {role.driver ?? defaultDriver ?? "—"}
-
- {role.summary}
-
+ {role.summary}
fires on{" "}
{role.triggers
- .map((t) =>
- t.on === "issue.labeled"
- ? `issue.labeled(${t.label})`
- : t.on,
- )
+ .map((t) => (t.on === "issue.labeled" ? `issue.labeled(${t.label})` : t.on))
.join(" · ")}
@@ -222,9 +207,8 @@ export default async function RepoSettingsPage({ params }: PageProps) {
)}
- Roster is defined in code (
- src/lib/agents/bootstrap.ts) and
- applies to every repo. Per-repo overrides are not implemented.
+ Roster is defined in code (src/lib/agents/bootstrap.ts)
+ and applies to every repo. Per-repo overrides are not implemented.
@@ -232,9 +216,7 @@ export default async function RepoSettingsPage({ params }: PageProps) {
Mode
-
- MIND_RUNNER={runnerMode}
-
+ MIND_RUNNER={runnerMode}
{runnerMode === "auto"
? "docker if reachable, native otherwise"
@@ -249,9 +231,9 @@ export default async function RepoSettingsPage({ params }: PageProps) {
- Workflow steps from .mind/workflow.yml{" "}
- run on push. The mode is process-global and set via the{" "}
- MIND_RUNNER environment variable.
+ Workflow steps from .mind/workflow.yml run on push. The
+ mode is process-global and set via the MIND_RUNNER{" "}
+ environment variable.
@@ -307,10 +289,7 @@ function Section({
return (
{mark}
-
+
{title}
@@ -339,7 +318,10 @@ function SignInWall({ owner, name }: { owner: string; name: string }) {
Repo settings are owner-only. Connect the WebID that owns{" "}
- {owner}/{name} to see and change them.
+
+ {owner}/{name}
+ {" "}
+ to see and change them.
configure this.
- Settings, tokens, Pages config, and deletion are restricted to the
- WebID that owns the repo.
+ Settings, tokens, Pages config, and deletion are restricted to the WebID that owns the repo.
You are signed in as
-
+
{viewerWebId}
Owner WebID
-
+
{ownerWebId}
diff --git a/src/app/repos/[owner]/[repo]/settings/settings-forms.tsx b/src/app/repos/[owner]/[repo]/settings/settings-forms.tsx
index 8ca2727..2a89de9 100644
--- a/src/app/repos/[owner]/[repo]/settings/settings-forms.tsx
+++ b/src/app/repos/[owner]/[repo]/settings/settings-forms.tsx
@@ -1,8 +1,8 @@
"use client";
+import { Button, Input } from "@mind-studio/ui";
import { useRouter } from "next/navigation";
import { useState } from "react";
-import { Button, Input } from "@mind-studio/ui";
import { authedFetch } from "@/lib/auth/csrf-client";
// -----------------------------------------------------------------------
@@ -69,10 +69,7 @@ export function GeneralForm({
}
return (
-
);
}
@@ -248,10 +239,7 @@ export function PagesForm({
-
+
-
+
);
}
@@ -352,10 +334,9 @@ export function DangerZone({ owner, name }: { owner: string; name: string }) {
Delete this repository
- Drops the registry rows (repo, pages config, tokens, runs, issues,
- pulls, agent runs) and removes the bare git repo from disk. The
- published site already on the pod is not deleted —
- you own that container. Cannot be undone.
+ Drops the registry rows (repo, pages config, tokens, runs, issues, pulls, agent runs) and
+ removes the bare git repo from disk. The published site already on the pod is{" "}
+ not deleted — you own that container. Cannot be undone.
Type {expected} to confirm:
@@ -382,9 +363,7 @@ export function DangerZone({ owner, name }: { owner: string; name: string }) {
{busy ? "dropping…" : "Delete repository"}
- {error ? (
- {error}
- ) : null}
+ {error ? {error}
: null}
);
}
@@ -412,9 +391,7 @@ function Field({
{children}
{hint ? (
-
- {hint}
-
+ {hint}
) : null}
);
@@ -453,9 +430,7 @@ function FormFooter({
{saved ? "✓ saved" : "no changes"}
) : null}
- {error ? (
-
{error}
- ) : null}
+ {error ?
{error} : null}
);
}
diff --git a/src/app/repos/[owner]/[repo]/token-manager.tsx b/src/app/repos/[owner]/[repo]/token-manager.tsx
index 3005b2d..6cc2a36 100644
--- a/src/app/repos/[owner]/[repo]/token-manager.tsx
+++ b/src/app/repos/[owner]/[repo]/token-manager.tsx
@@ -1,20 +1,20 @@
"use client";
-import { useState } from "react";
import {
- Button,
- Input,
AlertDialog,
- AlertDialogTrigger,
+ AlertDialogAction,
+ AlertDialogCancel,
AlertDialogContent,
- AlertDialogHeader,
+ AlertDialogDescription,
AlertDialogFooter,
+ AlertDialogHeader,
AlertDialogTitle,
- AlertDialogDescription,
- AlertDialogCancel,
- AlertDialogAction,
+ AlertDialogTrigger,
+ Button,
buttonVariants,
+ Input,
} from "@mind-studio/ui";
+import { useState } from "react";
import { authedFetch } from "@/lib/auth/csrf-client";
type TokenSummary = {
@@ -58,10 +58,7 @@ export function TokenManager({
createdAt: number;
token: string;
};
- setTokens((prev) => [
- { id: data.id, label: data.label, createdAt: data.createdAt },
- ...prev,
- ]);
+ setTokens((prev) => [{ id: data.id, label: data.label, createdAt: data.createdAt }, ...prev]);
setPlaintext(data.token);
setLabel("");
} catch (e) {
@@ -75,10 +72,9 @@ export function TokenManager({
setBusy(true);
setError(null);
try {
- const res = await authedFetch(
- `/api/repos/${owner}/${repo}/tokens/${id}`,
- { method: "DELETE" },
- );
+ const res = await authedFetch(`/api/repos/${owner}/${repo}/tokens/${id}`, {
+ method: "DELETE",
+ });
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error ?? `request failed (${res.status})`);
@@ -115,9 +111,7 @@ export function TokenManager({
- {error ? (
- {error}
- ) : null}
+ {error ? {error}
: null}
{plaintext ? (
@@ -129,10 +123,7 @@ export function TokenManager({
{plaintext}
- Push with:{" "}
-
- git push {pushUrl(plaintext, owner, repo)}
-
+ Push with: git push {pushUrl(plaintext, owner, repo)}
) : null}
@@ -144,7 +135,9 @@ export function TokenManager({
{tokens.map((t) => (
-
{t.label || (no label) }
+
+ {t.label || (no label) }
+
-
- Cancel
-
+ Cancel
- This repo has no commits yet. Push something to{" "}
- {ref} and refresh.
+ This repo has no commits yet. Push something to {ref} and
+ refresh.
@@ -136,9 +131,7 @@ function TreeListing({
className="text-[color:var(--ink-faint)] text-[11px]"
style={{ fontFamily: "var(--font-mono-src)" }}
>
- {entry.type === "blob" && entry.size !== null
- ? formatBytes(entry.size)
- : ""}
+ {entry.type === "blob" && entry.size !== null ? formatBytes(entry.size) : ""}
@@ -174,19 +167,12 @@ function PageShell({
-
+
Source
{branches.length > 1 ? (
-
+
) : (
("recent");
const deferredFilter = useDeferredValue(filter);
@@ -49,17 +39,13 @@ export function RepoList({
const matched = useMemo(() => {
if (!normalized) return rows;
- return rows.filter((r) =>
- `${r.owner}/${r.name}`.toLowerCase().includes(normalized),
- );
+ return rows.filter((r) => `${r.owner}/${r.name}`.toLowerCase().includes(normalized));
}, [rows, normalized]);
const sorted = useMemo(() => {
const copy = [...matched];
if (sort === "alpha") {
- copy.sort((a, b) =>
- `${a.owner}/${a.name}`.localeCompare(`${b.owner}/${b.name}`),
- );
+ copy.sort((a, b) => `${a.owner}/${a.name}`.localeCompare(`${b.owner}/${b.name}`));
} else {
copy.sort((a, b) => b.activityAt - a.activityAt);
}
@@ -142,13 +128,10 @@ export function RepoList({
style={{ fontFamily: "var(--font-mono-src)" }}
>
- {sorted.length} of {rows.length}{" "}
- {rows.length === 1 ? "repo" : "repos"}
+ {sorted.length} of {rows.length} {rows.length === 1 ? "repo" : "repos"}
·
-
- {totalLive} live on pages
-
+ {totalLive} live on pages
{normalized ? (
<>
·
@@ -203,10 +186,7 @@ function GroupedList({ rows, sort }: { rows: RepoRowData[]; sort: SortMode }) {
{liveCount > 0 ? (
<>
{" "}
- ·{" "}
-
- {liveCount} live
-
+ · {liveCount} live
>
) : null}
@@ -224,16 +204,8 @@ function GroupedList({ rows, sort }: { rows: RepoRowData[]; sort: SortMode }) {
);
}
-function RepoCard({
- row,
- highlight,
-}: {
- row: RepoRowData;
- highlight?: string;
-}) {
- const cardStyle = row.pagesLive
- ? { borderLeft: "1px solid var(--accent)" }
- : undefined;
+function RepoCard({ row, highlight }: { row: RepoRowData; highlight?: string }) {
+ const cardStyle = row.pagesLive ? { borderLeft: "1px solid var(--accent)" } : undefined;
return (
-
-
+
+
@@ -358,11 +323,7 @@ function BuildStatus({ run }: { run: RepoRowData["latestRun"] }) {
? undefined
: "bad";
const symbol =
- run.status === "success"
- ? "✓"
- : run.status === "failed" || run.status === "error"
- ? "✗"
- : "·";
+ run.status === "success" ? "✓" : run.status === "failed" || run.status === "error" ? "✗" : "·";
const right =
run.status === "success"
? formatDuration(run.startedAt, run.finishedAt)
@@ -384,10 +345,7 @@ function BuildStatus({ run }: { run: RepoRowData["latestRun"] }) {
function VisibilityBadge({ value }: { value: string }) {
return (
-
+
{value}
);
@@ -420,10 +378,7 @@ function SortChip({
function InitialEmptyState({ signedIn }: { signedIn: boolean }) {
return (
-
+
No repos yet.
@@ -432,8 +387,7 @@ function InitialEmptyState({ signedIn }: { signedIn: boolean }) {
Create your first repo
{" "}
- from the form, or run{" "}
- npm run seed:demo to populate two
+ from the form, or run npm run seed:demo to populate two
example sites. The{" "}
quickstart on the landing page
@@ -445,9 +399,8 @@ function InitialEmptyState({ signedIn }: { signedIn: boolean }) {
Sign in
{" "}
- to create one from the dashboard, or run{" "}
- npm run seed:demo to populate two
- example sites.
+ to create one from the dashboard, or run npm run seed:demo{" "}
+ to populate two example sites.
>
)}
@@ -466,20 +419,12 @@ function FilterEmptyState({
}) {
return (
-
+
No matches for {filter} .
Try a shorter substring, or{" "}
-
+
clear the filter
.{" "}
diff --git a/src/app/repos/new/new-repo-form.tsx b/src/app/repos/new/new-repo-form.tsx
index d6da1bf..822954c 100644
--- a/src/app/repos/new/new-repo-form.tsx
+++ b/src/app/repos/new/new-repo-form.tsx
@@ -1,17 +1,17 @@
"use client";
-import { useRouter } from "next/navigation";
-import { useState } from "react";
-import { authedFetch } from "@/lib/auth/csrf-client";
import {
Button,
Input,
Select,
- SelectTrigger,
- SelectValue,
SelectContent,
SelectItem,
+ SelectTrigger,
+ SelectValue,
} from "@mind-studio/ui";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
+import { authedFetch } from "@/lib/auth/csrf-client";
type Props = {
owner: string;
@@ -65,10 +65,7 @@ export function NewRepoForm({ owner, ownerWebId, ownerPodRoot }: Props) {
>
Owner
-
+
{owner}
- Letters, digits, . _ - · must start
- with a letter or digit · max 64 chars
+ Letters, digits, . _ - · must start with a letter
+ or digit · max 64 chars
@@ -135,9 +132,7 @@ export function NewRepoForm({ owner, ownerWebId, ownerPodRoot }: Props) {
- setVisibility(value as "public" | "private")
- }
+ onValueChange={(value) => setVisibility(value as "public" | "private")}
disabled={submitting}
>
@@ -145,9 +140,7 @@ export function NewRepoForm({ owner, ownerWebId, ownerPodRoot }: Props) {
public
-
- private (push token also required to clone)
-
+ private (push token also required to clone)
@@ -178,8 +171,7 @@ export function NewRepoForm({ owner, ownerWebId, ownerPodRoot }: Props) {
style={{
borderColor: "var(--status-bad)",
color: "var(--status-bad)",
- background:
- "color-mix(in srgb, var(--status-bad) 8%, transparent)",
+ background: "color-mix(in srgb, var(--status-bad) 8%, transparent)",
}}
>
{error}
@@ -187,10 +179,7 @@ export function NewRepoForm({ owner, ownerWebId, ownerPodRoot }: Props) {
) : null}
-
+
{submitting ? "Creating…" : "Create repo"}
- Your session is signed in but we don't have an owner slug or
- pod root on file yet. Finish signing up to register your pod with
- this bridge.
+ Your session is signed in but we don't have an owner slug or pod root on file yet.
+ Finish signing up to register your pod with this bridge.
@@ -63,11 +62,7 @@ export default async function NewRepoPage() {
return (
-
+
);
}
@@ -88,11 +83,9 @@ function Shell({ children }: { children: React.ReactNode }) {
New repo .
- Creates a bare Git repository on this bridge and writes a Turtle
- description into your pod under{" "}
- /codespaces/{"{name}"}/index.ttl. You
- can enable Mind Pages and mint push tokens from the repo's
- detail page afterwards.
+ Creates a bare Git repository on this bridge and writes a Turtle description into your pod
+ under /codespaces/{"{name}"}/index.ttl. You can enable Mind
+ Pages and mint push tokens from the repo's detail page afterwards.
diff --git a/src/app/repos/page.tsx b/src/app/repos/page.tsx
index c5fa9e4..5507ac1 100644
--- a/src/app/repos/page.tsx
+++ b/src/app/repos/page.tsx
@@ -1,11 +1,8 @@
import Link from "next/link";
-import {
- listRepos,
- getPagesConfig,
-} from "@/lib/registry/repos";
-import { getLatestRunForRepo } from "@/lib/registry/runs";
-import { isOrg } from "@/lib/registry/owners";
import { readSession } from "@/lib/auth/session";
+import { isOrg } from "@/lib/registry/owners";
+import { getPagesConfig, listRepos } from "@/lib/registry/repos";
+import { getLatestRunForRepo } from "@/lib/registry/runs";
import { RepoList, type RepoRowData } from "./_components/repo-list";
export const dynamic = "force-dynamic";
@@ -85,9 +82,8 @@ export default async function ReposPage() {
)}
- Everything registered with this bridge. Each row is a real bare Git
- repository on disk plus, optionally, a Mind Pages target on the
- owner's Solid Pod.
+ Everything registered with this bridge. Each row is a real bare Git repository on disk plus,
+ optionally, a Mind Pages target on the owner's Solid Pod.
diff --git a/src/app/signup/signup-form.tsx b/src/app/signup/signup-form.tsx
index eb490c5..656d289 100644
--- a/src/app/signup/signup-form.tsx
+++ b/src/app/signup/signup-form.tsx
@@ -1,8 +1,8 @@
"use client";
-import { useState } from "react";
-import { useRouter } from "next/navigation";
import { Button, Input } from "@mind-studio/ui";
+import { useRouter } from "next/navigation";
+import { useState } from "react";
export function SignupForm() {
const router = useRouter();
@@ -53,9 +53,7 @@ export function SignupForm() {
/>
-
- Password (≥8 chars)
-
+ Password (≥8 chars)
QUOTAS.maxPackageBlobBytes) {
abortUpload(target.uuid);
- return quota(
- new QuotaExceededError("maxPackageBlobBytes", QUOTAS.maxPackageBlobBytes, size),
- );
+ return quota(new QuotaExceededError("maxPackageBlobBytes", QUOTAS.maxPackageBlobBytes, size));
}
return new NextResponse(null, {
status: 202,
diff --git a/src/components/auth-cta-server.tsx b/src/components/auth-cta-server.tsx
index e58fc58..c23165c 100644
--- a/src/components/auth-cta-server.tsx
+++ b/src/components/auth-cta-server.tsx
@@ -1,8 +1,8 @@
import "server-only";
import { readSession } from "@/lib/auth/session";
-import { displayNameForWebId } from "@/lib/solid/web-id";
-import { getUserByWebId } from "@/lib/registry/users";
import { listRepos } from "@/lib/registry/repos";
+import { getUserByWebId } from "@/lib/registry/users";
+import { displayNameForWebId } from "@/lib/solid/web-id";
import { AuthCta } from "./auth-cta";
function initialsForName(source: string): string {
diff --git a/src/components/auth-cta.tsx b/src/components/auth-cta.tsx
index cf615da..2ee375c 100644
--- a/src/components/auth-cta.tsx
+++ b/src/components/auth-cta.tsx
@@ -1,10 +1,10 @@
"use client";
-import { useEffect, useLayoutEffect, useRef, useState } from "react";
-import { createPortal } from "react-dom";
+import { Button } from "@mind-studio/ui";
import Link from "next/link";
import { useRouter } from "next/navigation";
-import { Button } from "@mind-studio/ui";
+import { useEffect, useLayoutEffect, useRef, useState } from "react";
+import { createPortal } from "react-dom";
import { authedFetch } from "@/lib/auth/csrf-client";
type SignedInProps = {
@@ -125,10 +125,7 @@ function UserMenu({ session }: { session: SignedInProps }) {
}}
>
-
+
{session.displayName}
` for switching the displayed branch on a tree/blob
diff --git a/src/components/copy-button.tsx b/src/components/copy-button.tsx
index 4b41172..a6a9e75 100644
--- a/src/components/copy-button.tsx
+++ b/src/components/copy-button.tsx
@@ -1,7 +1,7 @@
"use client";
-import { useState } from "react";
import { Button } from "@mind-studio/ui";
+import { useState } from "react";
/**
* One-shot clipboard copy with a short success indicator. Falls back
diff --git a/src/components/diff-view.tsx b/src/components/diff-view.tsx
index 685a65d..1e22aa9 100644
--- a/src/components/diff-view.tsx
+++ b/src/components/diff-view.tsx
@@ -9,13 +9,7 @@
* inline classes so the dark-theme overrides in globals.css can flip
* them via `[data-theme='dark']` selectors if/when wanted.
*/
-export function DiffView({
- patch,
- truncated,
-}: {
- patch: string;
- truncated: boolean;
-}) {
+export function DiffView({ patch, truncated }: { patch: string; truncated: boolean }) {
const lines = patch.split("\n");
// Drop the trailing empty line from a final newline so we don't
// render a phantom row at the bottom.
@@ -29,10 +23,7 @@ export function DiffView({
{lines.map((line, i) => {
const kind = classify(line);
return (
-
+
{line || " "}
);
diff --git a/src/components/feedback-launcher.tsx b/src/components/feedback-launcher.tsx
index 3965a89..71db11f 100644
--- a/src/components/feedback-launcher.tsx
+++ b/src/components/feedback-launcher.tsx
@@ -12,11 +12,8 @@ import { FeedbackWidget } from "@mind-studio/core/feedback";
* build-time inlined via `NEXT_PUBLIC_FEEDBACK_INBOX`.
*/
const feedbackInbox =
- process.env.NEXT_PUBLIC_FEEDBACK_INBOX ??
- "http://localhost:3011/alice/codespaces-feedback/";
+ process.env.NEXT_PUBLIC_FEEDBACK_INBOX ?? "http://localhost:3011/alice/codespaces-feedback/";
export function FeedbackLauncher() {
- return (
-
- );
+ return
;
}
diff --git a/src/components/main-nav.tsx b/src/components/main-nav.tsx
index 3a19a2c..6ac7666 100644
--- a/src/components/main-nav.tsx
+++ b/src/components/main-nav.tsx
@@ -1,8 +1,8 @@
"use client";
+import { Button } from "@mind-studio/ui";
import Link from "next/link";
import { usePathname } from "next/navigation";
-import { Button } from "@mind-studio/ui";
type Item = {
href: string;
diff --git a/src/components/matrix-rain.tsx b/src/components/matrix-rain.tsx
index 7c88982..efc0b0d 100644
--- a/src/components/matrix-rain.tsx
+++ b/src/components/matrix-rain.tsx
@@ -3,21 +3,81 @@
import { useEffect, useRef } from "react";
const STREAM = [
- "git push", "git pull", "git fetch", "git clone", "git commit", "git rebase",
- "main", "HEAD", "origin", "refs/heads", "refs/tags", "branch",
- "pod", "WebID", "Solid", "OIDC", "DPoP", "Bearer",
- "ttl", "turtle", "rdf", "ldp", "vcard", "foaf", "dcterms", "xsd",
- ".ttl", ".html", ".css", ".js", "index.html", "card#me",
- "/alice/", "/huhn511/", "/public/", "/sites/", "/codespaces/", "/profile/",
- "alice/bakery", "alice/notes", "alice/marked-blog", "alice/tailwind-site",
- "alice/about", "alice/built-site", "huhn511/hello", "mind/compass",
- ".mind/workflow.yml", "node:22-alpine", "docker run --rm",
- "200 OK", "201 Created", "204 No Content", "401", "403", "404", "409",
- "GET", "POST", "PUT", "DELETE", "PATCH",
- "smart-http", "post-receive", "bare repo", "push-token",
- "Mind Codespaces", "Solid Git Bridge",
- "sha256", "0xdeadbeef", "0xcafe", "0xfeed",
- "codespaces", "registry", "publisher", "coder",
+ "git push",
+ "git pull",
+ "git fetch",
+ "git clone",
+ "git commit",
+ "git rebase",
+ "main",
+ "HEAD",
+ "origin",
+ "refs/heads",
+ "refs/tags",
+ "branch",
+ "pod",
+ "WebID",
+ "Solid",
+ "OIDC",
+ "DPoP",
+ "Bearer",
+ "ttl",
+ "turtle",
+ "rdf",
+ "ldp",
+ "vcard",
+ "foaf",
+ "dcterms",
+ "xsd",
+ ".ttl",
+ ".html",
+ ".css",
+ ".js",
+ "index.html",
+ "card#me",
+ "/alice/",
+ "/huhn511/",
+ "/public/",
+ "/sites/",
+ "/codespaces/",
+ "/profile/",
+ "alice/bakery",
+ "alice/notes",
+ "alice/marked-blog",
+ "alice/tailwind-site",
+ "alice/about",
+ "alice/built-site",
+ "huhn511/hello",
+ "mind/compass",
+ ".mind/workflow.yml",
+ "node:22-alpine",
+ "docker run --rm",
+ "200 OK",
+ "201 Created",
+ "204 No Content",
+ "401",
+ "403",
+ "404",
+ "409",
+ "GET",
+ "POST",
+ "PUT",
+ "DELETE",
+ "PATCH",
+ "smart-http",
+ "post-receive",
+ "bare repo",
+ "push-token",
+ "Mind Codespaces",
+ "Solid Git Bridge",
+ "sha256",
+ "0xdeadbeef",
+ "0xcafe",
+ "0xfeed",
+ "codespaces",
+ "registry",
+ "publisher",
+ "coder",
];
const HEAD_COLOR = "rgba(220, 255, 225, ";
diff --git a/src/components/relative-time.tsx b/src/components/relative-time.tsx
index 9b7eefa..317fb5e 100644
--- a/src/components/relative-time.tsx
+++ b/src/components/relative-time.tsx
@@ -19,12 +19,7 @@ export function RelativeTime({
}) {
const iso = new Date(ts).toISOString();
return (
-
+
{formatRelativeTime(ts)}
);
diff --git a/src/components/sign-in-wall.tsx b/src/components/sign-in-wall.tsx
index 4fe78a2..28c62b5 100644
--- a/src/components/sign-in-wall.tsx
+++ b/src/components/sign-in-wall.tsx
@@ -1,5 +1,5 @@
-import Link from "next/link";
import { Button } from "@mind-studio/ui";
+import Link from "next/link";
/**
* Polite "you need to sign in to do this" card. Used in two places:
@@ -30,9 +30,7 @@ export function SignInWall({
// /login takes `?returnTo=` (validated server-side; cross-origin
// values are dropped). Pass the same value to /signup and /connect — they
// currently ignore it, but the param is harmless and we can wire it up later.
- const safe = next && next.startsWith("/") && !next.startsWith("//")
- ? next
- : null;
+ const safe = next && next.startsWith("/") && !next.startsWith("//") ? next : null;
const qs = safe ? `?returnTo=${encodeURIComponent(safe)}` : "";
const loginHref = `/login${qs}`;
const signupHref = `/signup${qs}`;
@@ -54,9 +52,8 @@ export function SignInWall({
You need a session to {action} .
- The bridge never sees your pod password — sign-in happens against
- your own pod, and the bridge only stores the resulting refresh
- token (encrypted at rest).
+ The bridge never sees your pod password — sign-in happens against your own pod, and the
+ bridge only stores the resulting refresh token (encrypted at rest).
diff --git a/src/components/theme-shell.tsx b/src/components/theme-shell.tsx
index cd21305..b2c42cd 100644
--- a/src/components/theme-shell.tsx
+++ b/src/components/theme-shell.tsx
@@ -1,15 +1,8 @@
"use client";
-import {
- createContext,
- useCallback,
- useContext,
- useEffect,
- useMemo,
- useState,
-} from "react";
import { ThemeProvider } from "@mind-studio/ui";
import { mind } from "@mind-studio/ui/themes";
+import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
import { neo } from "@/lib/theme/neo";
export type Brand = "mind" | "neo";
diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx
index 52b69af..357501a 100644
--- a/src/components/theme-toggle.tsx
+++ b/src/components/theme-toggle.tsx
@@ -1,6 +1,5 @@
"use client";
-import { useEffect, useState } from "react";
import {
Button,
DropdownMenu,
@@ -12,6 +11,7 @@ import {
DropdownMenuTrigger,
useMindTheme,
} from "@mind-studio/ui";
+import { useEffect, useState } from "react";
import { useBrand } from "@/components/theme-shell";
/**
@@ -50,10 +50,7 @@ export function ThemeToggle() {
Appearance
- setMode(v)}
- >
+ setMode(v)}>
Light
diff --git a/src/lib/agents/bootstrap.ts b/src/lib/agents/bootstrap.ts
index 898922c..7fdc830 100644
--- a/src/lib/agents/bootstrap.ts
+++ b/src/lib/agents/bootstrap.ts
@@ -1,14 +1,9 @@
import "server-only";
-import {
- registerDriver,
- registerRole,
- listRoles,
- setDefaultDriver,
-} from "@/lib/agents/registry";
-import { echoDriver } from "@/lib/agents/drivers/echo";
-import { openrouterDriver } from "@/lib/agents/drivers/openrouter";
import { coderDriver } from "@/lib/agents/drivers/coder";
import { codexDriver } from "@/lib/agents/drivers/codex";
+import { echoDriver } from "@/lib/agents/drivers/echo";
+import { openrouterDriver } from "@/lib/agents/drivers/openrouter";
+import { listRoles, registerDriver, registerRole, setDefaultDriver } from "@/lib/agents/registry";
/**
* Wire the available drivers + the demo roster. Idempotent — safe to
@@ -49,9 +44,7 @@ export function ensureAgentsBootstrap(): void {
// to POST /api/agents/dispatch so it runs side-by-side with `coder`
// without double-firing on the same issue event.
registerDriver(codexDriver);
- console.log(
- `[agents] codex driver active (runtime=${process.env.MIND_CODEX_RUNTIME ?? "host"})`,
- );
+ console.log(`[agents] codex driver active (runtime=${process.env.MIND_CODEX_RUNTIME ?? "host"})`);
if (process.env.OPENROUTER_API_KEY) {
registerDriver(openrouterDriver);
diff --git a/src/lib/agents/dispatch.ts b/src/lib/agents/dispatch.ts
index bb89adb..7ddb5a6 100644
--- a/src/lib/agents/dispatch.ts
+++ b/src/lib/agents/dispatch.ts
@@ -1,27 +1,19 @@
import "server-only";
import * as path from "node:path";
+import { getDefaultDriverName, getDriver, rolesForEvent } from "@/lib/agents/registry";
import type { AgentEvent, DriverResult, Role } from "@/lib/agents/types";
-import {
- getDefaultDriverName,
- getDriver,
- rolesForEvent,
-} from "@/lib/agents/registry";
-import {
- createAgentRun,
- finishAgentRun,
-} from "@/lib/registry/agent-runs";
-import { getRepo } from "@/lib/registry/repos";
-import { getIssueByNumber } from "@/lib/registry/issues";
-import { buildPreviewForPull } from "@/lib/pages/preview";
import { Metrics } from "@/lib/metrics";
+import { buildPreviewForPull } from "@/lib/pages/preview";
+import { createAgentRun, finishAgentRun } from "@/lib/registry/agent-runs";
+import { getIssueByNumber } from "@/lib/registry/issues";
+import { getRepo } from "@/lib/registry/repos";
/**
* Where streamed per-run log files live. The registry stores just the
* filename (`{runId}.log`); the directory can be relocated via
* AGENT_LOGS_DIR without rewriting any rows.
*/
-export const AGENT_LOGS_DIR =
- process.env.AGENT_LOGS_DIR ?? path.join(process.cwd(), ".agent-logs");
+export const AGENT_LOGS_DIR = process.env.AGENT_LOGS_DIR ?? path.join(process.cwd(), ".agent-logs");
/**
* Render a prompt for a role given the event. v0 is intentionally
@@ -80,10 +72,7 @@ export async function dispatch(
const repo = getRepo(event.repoOwner, event.repoName);
const issueNumber = "issueNumber" in event ? event.issueNumber : null;
- const issue =
- repo && issueNumber !== null
- ? getIssueByNumber(repo.id, issueNumber)
- : null;
+ const issue = repo && issueNumber !== null ? getIssueByNumber(repo.id, issueNumber) : null;
for (const role of roles) {
const driverName = opts.driver ?? role.driver ?? getDefaultDriverName();
@@ -124,8 +113,7 @@ export async function dispatch(
driver: driverName,
})
: null;
- const logPath =
- run?.logPath ? path.join(AGENT_LOGS_DIR, run.logPath) : null;
+ const logPath = run?.logPath ? path.join(AGENT_LOGS_DIR, run.logPath) : null;
let result: DriverResult;
try {
@@ -158,9 +146,7 @@ export async function dispatch(
// so the result is viewable before merge. Single chokepoint → covers every
// driver. SHA-guarded inside buildPreviewForPull, so re-runs are cheap.
if (repo && result.status === "ok") {
- const data = result.data as
- | { mode?: string; pullNumber?: number }
- | undefined;
+ const data = result.data as { mode?: string; pullNumber?: number } | undefined;
if (data?.mode === "pr" && typeof data.pullNumber === "number") {
const pn = data.pullNumber;
void buildPreviewForPull(repo.id, pn).catch((e) =>
diff --git a/src/lib/agents/drivers/coder.ts b/src/lib/agents/drivers/coder.ts
index a4ac75a..18f5b8f 100644
--- a/src/lib/agents/drivers/coder.ts
+++ b/src/lib/agents/drivers/coder.ts
@@ -1,34 +1,29 @@
import "server-only";
-import { spawn, type ChildProcess } from "node:child_process";
-import * as fs from "node:fs/promises";
+import { type ChildProcess, spawn } from "node:child_process";
import { createWriteStream, type WriteStream } from "node:fs";
+import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
+import { AGENT_LOGS_DIR } from "@/lib/agents/dispatch";
import type { AgentEvent, Driver } from "@/lib/agents/types";
-import { getRepo, type Repo } from "@/lib/registry/repos";
+import { formatOpencodeModel, getProvider, PROVIDERS } from "@/lib/ai-providers/providers";
+import { resolveCoderConfig } from "@/lib/ai-providers/store";
+import { debit, getBalance, ledgerEnabled, llmPrice } from "@/lib/ledger/client";
+import { gateEnvFallback } from "@/lib/ledger/policy";
import {
addComment,
getIssueByNumber,
- listComments,
- setCommentPodUrl,
type Issue,
type IssueComment,
+ listComments,
+ setCommentPodUrl,
} from "@/lib/registry/issues";
-import { commentUrl, writeCommentToPod } from "@/lib/solid/issues";
-import { writePullToPod } from "@/lib/solid/pulls";
import { upsertPullRequest } from "@/lib/registry/pulls";
-import { validateName } from "@/lib/registry/repos";
-import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
+import { getRepo, type Repo, validateName } from "@/lib/registry/repos";
import { ensureContainer, setPublicReadAcl } from "@/lib/solid/containers";
-import { resolveCoderConfig } from "@/lib/ai-providers/store";
-import { ledgerEnabled, getBalance, debit, llmPrice } from "@/lib/ledger/client";
-import { gateEnvFallback } from "@/lib/ledger/policy";
-import { AGENT_LOGS_DIR } from "@/lib/agents/dispatch";
-import {
- PROVIDERS,
- getProvider,
- formatOpencodeModel,
-} from "@/lib/ai-providers/providers";
+import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
+import { commentUrl, writeCommentToPod } from "@/lib/solid/issues";
+import { writePullToPod } from "@/lib/solid/pulls";
/**
* Coder driver. Spawns opencode in a docker container against a clone
@@ -77,8 +72,7 @@ import {
const DEFAULT_IMAGE = "mind-codespaces/coder:latest";
const DEFAULT_TIMEOUT_S = 600;
-const GIT_DATA_DIR =
- process.env.GIT_DATA_DIR ?? path.join(process.cwd(), ".git-data/repos");
+const GIT_DATA_DIR = process.env.GIT_DATA_DIR ?? path.join(process.cwd(), ".git-data/repos");
const WORK_ROOT = process.env.MIND_CODER_WORKROOT ?? os.tmpdir();
const AGENT_COMMENT_REL = ".mind/agent-comment.md";
const AGENT_SCREENSHOTS_REL = ".mind/screenshots";
@@ -228,11 +222,7 @@ export const coderDriver: Driver = {
const chargeRun = async () => {
if (!meterRun || charged) return;
charged = true;
- const res = await debit(
- repo.ownerWebId,
- llmPrice(),
- `builder:coder#${issueNumber}`,
- );
+ const res = await debit(repo.ownerWebId, llmPrice(), `builder:coder#${issueNumber}`);
if (res.ok) log(`[coder] metered ${llmPrice()} MIND (balance ${res.balance})`);
else log(`[coder] meter debit failed (status ${res.status}) — not charging`);
};
@@ -282,11 +272,9 @@ export const coderDriver: Driver = {
]);
const branchExists = branchProbe.exit === 0;
if (branchExists) {
- const co = await sh(
- "git",
- ["-C", workDir, "checkout", "-B", branch, `origin/${branch}`],
- { logStream },
- );
+ const co = await sh("git", ["-C", workDir, "checkout", "-B", branch, `origin/${branch}`], {
+ logStream,
+ });
if (co.exit !== 0) {
return errorResult(
`checkout of existing ${branch} failed (exit ${co.exit})`,
@@ -335,14 +323,7 @@ export const coderDriver: Driver = {
// bridge's process env at exec time, so the key never appears in
// `ps auxe`. We also forward MIND_AI_PROVIDER so the entrypoint
// knows which provider block to leave enabled in auth.json.
- const dockerArgs = [
- "run",
- "--rm",
- "-v",
- `${workDir}:/work`,
- "--env",
- "MIND_AI_PROVIDER",
- ];
+ const dockerArgs = ["run", "--rm", "-v", `${workDir}:/work`, "--env", "MIND_AI_PROVIDER"];
for (const envName of providerSpec.containerEnvNames) {
dockerArgs.push("--env", envName);
}
@@ -455,9 +436,7 @@ export const coderDriver: Driver = {
log(
`[coder] changed files (${changed.length}): ${changed.join(", ")}` +
(wantsComment ? " [+agent-comment.md]" : "") +
- (wantsScreenshots
- ? ` [+${screenshotFiles.length} screenshot(s)]`
- : ""),
+ (wantsScreenshots ? ` [+${screenshotFiles.length} screenshot(s)]` : ""),
);
// Mirror screenshots to host storage FIRST so a pod upload failure
@@ -556,9 +535,7 @@ export const coderDriver: Driver = {
await fs
.rm(path.join(workDir, AGENT_SCREENSHOTS_REL), { recursive: true, force: true })
.catch(() => {});
- await fs
- .rm(path.join(workDir, AGENT_COMMENT_REL), { force: true })
- .catch(() => {});
+ await fs.rm(path.join(workDir, AGENT_COMMENT_REL), { force: true }).catch(() => {});
// `branch` and `branchExists` were declared up-front (before the
// opencode run) so we could resume on top of a prior attempt. The
@@ -591,16 +568,12 @@ export const coderDriver: Driver = {
for (const [label, args] of steps) {
const r = await sh("git", args, { logStream });
if (r.exit !== 0) {
- return errorResult(
- `git ${label} failed (exit ${r.exit})`,
- r.stderr.slice(-800),
- );
+ return errorResult(`git ${label} failed (exit ${r.exit})`, r.stderr.slice(-800));
}
}
log(`[coder] pushed branch ${branch} to ${repoOwner}/${repoName}`);
- const sourceSha = (await sh("git", ["-C", workDir, "rev-parse", "HEAD"]))
- .stdout.trim();
+ const sourceSha = (await sh("git", ["-C", workDir, "rev-parse", "HEAD"])).stdout.trim();
const prBody = [
`Generated by the coder via opencode (model \`${orModel}\`).`,
"",
@@ -708,13 +681,8 @@ function renderTaskPrompt(input: {
"",
`--- Conversation so far (${input.comments.length} comment(s)) ---`,
...input.comments.map((c, i) => {
- const who =
- c.agentRunId !== null ? "coder (you, earlier)" : c.authorWebId;
- return [
- `[${i + 1}] ${who}:`,
- c.body.trim(),
- "",
- ].join("\n");
+ const who = c.agentRunId !== null ? "coder (you, earlier)" : c.authorWebId;
+ return [`[${i + 1}] ${who}:`, c.body.trim(), ""].join("\n");
}),
"--- End of conversation ---",
"",
@@ -786,7 +754,7 @@ function renderTaskPrompt(input: {
"If you do take screenshots:",
" - Take 1–3 that show the result of your change. Use",
" `browser_take_screenshot` with",
- " `filename: \"/work/.mind/screenshots/.png\"`.",
+ ' `filename: "/work/.mind/screenshots/.png"`.',
" - Stop after 3 screenshots — do not keep iterating in the browser.",
"",
"Screenshots in `.mind/screenshots/` are uploaded to the pod and",
@@ -891,8 +859,7 @@ async function uploadScreenshots(input: {
const root = input.repo.ownerPodRoot.endsWith("/")
? input.repo.ownerPodRoot
: `${input.repo.ownerPodRoot}/`;
- const runSeg =
- input.runId !== null ? `run-${input.runId}` : `run-${Date.now()}`;
+ const runSeg = input.runId !== null ? `run-${input.runId}` : `run-${Date.now()}`;
const containers = [
`${root}codespaces/`,
`${root}codespaces/${input.repo.name}/`,
@@ -928,9 +895,7 @@ async function uploadScreenshots(input: {
body: bytes,
});
if (!res.ok) {
- input.log(
- `[coder] screenshot PUT ${name} failed: ${res.status} ${res.statusText}`,
- );
+ input.log(`[coder] screenshot PUT ${name} failed: ${res.status} ${res.statusText}`);
continue;
}
uploaded.push({ name, url });
@@ -950,11 +915,7 @@ async function uploadScreenshots(input: {
* empty string when there are none so callers can `.filter(Boolean)`. */
function renderScreenshotsSection(uploaded: UploadedShot[]): string {
if (uploaded.length === 0) return "";
- return [
- "### Screenshots",
- "",
- ...uploaded.map((s) => ``),
- ].join("\n");
+ return ["### Screenshots", "", ...uploaded.map((s) => ``)].join("\n");
}
function guessImageMime(name: string): string {
@@ -1060,9 +1021,7 @@ function unquotePath(p: string): string {
return p;
}
-async function openLogStream(
- logPath: string | null,
-): Promise {
+async function openLogStream(logPath: string | null): Promise {
if (!logPath) return null;
await fs.mkdir(path.dirname(logPath), { recursive: true });
return createWriteStream(logPath, { flags: "a" });
@@ -1102,7 +1061,7 @@ function sh(
child.on("error", reject);
child.on("close", (code) => {
resolve({
- exit: killed ? 124 : code ?? 0,
+ exit: killed ? 124 : (code ?? 0),
stdout,
stderr,
});
diff --git a/src/lib/agents/drivers/codex.ts b/src/lib/agents/drivers/codex.ts
index 510259d..c379536 100644
--- a/src/lib/agents/drivers/codex.ts
+++ b/src/lib/agents/drivers/codex.ts
@@ -1,23 +1,23 @@
import "server-only";
-import { spawn, type ChildProcess } from "node:child_process";
-import * as fs from "node:fs/promises";
+import { type ChildProcess, spawn } from "node:child_process";
import { createWriteStream, type WriteStream } from "node:fs";
+import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
+import { STATIC_EXPORT_RULES } from "@/lib/agents/prompt-fragments";
import type { AgentEvent, Driver } from "@/lib/agents/types";
-import { getRepo, validateName, type Repo } from "@/lib/registry/repos";
import {
addComment,
getIssueByNumber,
- listComments,
- setCommentPodUrl,
type Issue,
type IssueComment,
+ listComments,
+ setCommentPodUrl,
} from "@/lib/registry/issues";
+import { upsertPullRequest } from "@/lib/registry/pulls";
+import { getRepo, type Repo, validateName } from "@/lib/registry/repos";
import { commentUrl, writeCommentToPod } from "@/lib/solid/issues";
import { writePullToPod } from "@/lib/solid/pulls";
-import { upsertPullRequest } from "@/lib/registry/pulls";
-import { STATIC_EXPORT_RULES } from "@/lib/agents/prompt-fragments";
/**
* Codex driver (PoC). A sibling of the `coder` (opencode) driver that
@@ -59,8 +59,7 @@ import { STATIC_EXPORT_RULES } from "@/lib/agents/prompt-fragments";
const DEFAULT_IMAGE = "mind-codespaces/codex:latest";
const DEFAULT_TIMEOUT_S = 600;
-const GIT_DATA_DIR =
- process.env.GIT_DATA_DIR ?? path.join(process.cwd(), ".git-data/repos");
+const GIT_DATA_DIR = process.env.GIT_DATA_DIR ?? path.join(process.cwd(), ".git-data/repos");
const WORK_ROOT = process.env.MIND_CODER_WORKROOT ?? os.tmpdir();
const AGENT_COMMENT_REL = ".mind/agent-comment.md";
const AGENT_WEBID = "mind:agent:codex";
@@ -209,11 +208,9 @@ export const codexDriver: Driver = {
]);
const branchExists = branchProbe.exit === 0;
if (branchExists) {
- const co = await sh(
- "git",
- ["-C", workDir, "checkout", "-B", branch, `origin/${branch}`],
- { logStream },
- );
+ const co = await sh("git", ["-C", workDir, "checkout", "-B", branch, `origin/${branch}`], {
+ logStream,
+ });
if (co.exit !== 0) {
return errorResult(
`checkout of existing ${branch} failed (exit ${co.exit})`,
@@ -307,22 +304,16 @@ export const codexDriver: Driver = {
}
const elapsed = ((Date.now() - t0) / 1000).toFixed(1);
log(`[codex] exit=${cx.exit} (${elapsed}s)`);
- const cxTail = (cx.stdout + (cx.stderr ? `\n[stderr]\n${cx.stderr}` : "")).slice(
- -2000,
+ const cxTail = (cx.stdout + (cx.stderr ? `\n[stderr]\n${cx.stderr}` : "")).slice(-2000);
+ summaryLines.push(
+ `codex exit=${cx.exit} (runtime=${runtime}, model=${modelLabel}, ${elapsed}s)`,
);
- summaryLines.push(`codex exit=${cx.exit} (runtime=${runtime}, model=${modelLabel}, ${elapsed}s)`);
// 4. Inspect what codex produced.
// - no changes at all → error (model went silent / refused)
// - only .mind/agent-comment.md → comment-only (ASK) path
// - code changes → PR path
- const status = await sh("git", [
- "-C",
- workDir,
- "status",
- "--porcelain",
- "-uall",
- ]);
+ const status = await sh("git", ["-C", workDir, "status", "--porcelain", "-uall"]);
const dirty = status.stdout.trimEnd();
const commentBody = await readAgentCommentFile(workDir);
const wantsComment = commentBody !== null;
@@ -383,9 +374,7 @@ export const codexDriver: Driver = {
await fs
.rm(path.join(workDir, ".playwright-mcp"), { recursive: true, force: true })
.catch(() => {});
- await fs
- .rm(path.join(workDir, AGENT_COMMENT_REL), { force: true })
- .catch(() => {});
+ await fs.rm(path.join(workDir, AGENT_COMMENT_REL), { force: true }).catch(() => {});
const steps: Array<[string, string[]]> = [
["config", ["-C", workDir, "config", "user.email", AGENT_WEBID]],
@@ -413,16 +402,12 @@ export const codexDriver: Driver = {
for (const [label, args] of steps) {
const r = await sh("git", args, { logStream });
if (r.exit !== 0) {
- return errorResult(
- `git ${label} failed (exit ${r.exit})`,
- r.stderr.slice(-800),
- );
+ return errorResult(`git ${label} failed (exit ${r.exit})`, r.stderr.slice(-800));
}
}
log(`[codex] pushed branch ${branch} to ${repoOwner}/${repoName}`);
- const sourceSha = (await sh("git", ["-C", workDir, "rev-parse", "HEAD"]))
- .stdout.trim();
+ const sourceSha = (await sh("git", ["-C", workDir, "rev-parse", "HEAD"])).stdout.trim();
const prBody = [
`Generated by the codex driver (\`codex exec\`, runtime \`${runtime}\`, model \`${modelLabel}\`).`,
"",
@@ -485,9 +470,7 @@ export const codexDriver: Driver = {
status: "error",
summary: [
`codex driver crashed: ${err instanceof Error ? err.message : String(err)}`,
- summaryLines.length > 0
- ? `Progress before crash: ${summaryLines.join("; ")}`
- : "",
+ summaryLines.length > 0 ? `Progress before crash: ${summaryLines.join("; ")}` : "",
]
.filter(Boolean)
.join("\n"),
@@ -522,8 +505,7 @@ function renderTaskPrompt(input: {
"",
`--- Conversation so far (${input.comments.length} comment(s)) ---`,
...input.comments.map((c, i) => {
- const who =
- c.agentRunId !== null ? "codex (you, earlier)" : c.authorWebId;
+ const who = c.agentRunId !== null ? "codex (you, earlier)" : c.authorWebId;
return [`[${i + 1}] ${who}:`, c.body.trim(), ""].join("\n");
}),
"--- End of conversation ---",
@@ -665,9 +647,7 @@ function unquotePath(p: string): string {
return p;
}
-async function openLogStream(
- logPath: string | null,
-): Promise {
+async function openLogStream(logPath: string | null): Promise {
if (!logPath) return null;
await fs.mkdir(path.dirname(logPath), { recursive: true });
return createWriteStream(logPath, { flags: "a" });
@@ -705,7 +685,7 @@ function sh(
});
child.on("error", reject);
child.on("close", (code) => {
- resolve({ exit: killed ? 124 : code ?? 0, stdout, stderr });
+ resolve({ exit: killed ? 124 : (code ?? 0), stdout, stderr });
});
if (opts.timeoutMs) {
const t = setTimeout(() => {
diff --git a/src/lib/agents/drivers/echo.ts b/src/lib/agents/drivers/echo.ts
index 7447bf2..cfb012f 100644
--- a/src/lib/agents/drivers/echo.ts
+++ b/src/lib/agents/drivers/echo.ts
@@ -12,8 +12,7 @@ export const echoDriver: Driver = {
return "Records the role + event without calling any model. Useful for testing the dispatch path.";
},
async run(ctx) {
- const head =
- ctx.prompt.length > 280 ? `${ctx.prompt.slice(0, 280)}…` : ctx.prompt;
+ const head = ctx.prompt.length > 280 ? `${ctx.prompt.slice(0, 280)}…` : ctx.prompt;
return {
status: "ok",
summary: `echo[${ctx.role.name}] event=${ctx.event.type} :: ${head}`,
diff --git a/src/lib/agents/drivers/openrouter.ts b/src/lib/agents/drivers/openrouter.ts
index 96d424f..ce4703c 100644
--- a/src/lib/agents/drivers/openrouter.ts
+++ b/src/lib/agents/drivers/openrouter.ts
@@ -119,8 +119,7 @@ function describeError(json: OpenRouterResponse, status: number): string {
const err = json.error;
const parts: string[] = [];
if (err?.code) parts.push(`code=${err.code}`);
- if (err?.metadata?.provider_name)
- parts.push(`provider=${err.metadata.provider_name}`);
+ if (err?.metadata?.provider_name) parts.push(`provider=${err.metadata.provider_name}`);
if (err?.metadata?.retry_after_seconds)
parts.push(`retry_after=${err.metadata.retry_after_seconds}s`);
const head = err?.message ?? `${status} request failed`;
@@ -154,10 +153,9 @@ async function callOpenRouter(
const remaining = MAX_RETRY_BUDGET_S - budgetUsed;
if (remaining <= 0) break;
- const hinted =
- result.json.error?.metadata?.retry_after_seconds ?? null;
+ const hinted = result.json.error?.metadata?.retry_after_seconds ?? null;
// Exponential backoff: 1, 2, 4 s + 0..1s jitter; cap at remaining budget.
- const computed = Math.pow(2, attempt) + Math.random();
+ const computed = 2 ** attempt + Math.random();
const sleepS = Math.min(hinted ? hinted + 1 : computed, remaining);
console.log(
`[agents] openrouter retry: status=${result.status} attempt=${attempt + 1}/${MAX_RETRIES} sleep=${sleepS.toFixed(1)}s`,
@@ -166,9 +164,7 @@ async function callOpenRouter(
budgetUsed += sleepS;
}
- throw new Error(
- `OpenRouter: ${describeError(lastErr!.json, lastErr!.status)}`,
- );
+ throw new Error(`OpenRouter: ${describeError(lastErr!.json, lastErr!.status)}`);
}
export const openrouterDriver: Driver = {
diff --git a/src/lib/agents/registry.ts b/src/lib/agents/registry.ts
index 04a4804..0dd314e 100644
--- a/src/lib/agents/registry.ts
+++ b/src/lib/agents/registry.ts
@@ -76,9 +76,7 @@ export function getDriver(name: string): Driver | undefined {
* `issue.labeled` trigger).
*/
export function rolesForEvent(event: AgentEvent): Role[] {
- return listRoles().filter((role) =>
- role.triggers.some((t) => triggerMatches(t, event)),
- );
+ return listRoles().filter((role) => role.triggers.some((t) => triggerMatches(t, event)));
}
function triggerMatches(trigger: Trigger, event: AgentEvent): boolean {
diff --git a/src/lib/ai-providers/providers.ts b/src/lib/ai-providers/providers.ts
index c8a604d..84a0caa 100644
--- a/src/lib/ai-providers/providers.ts
+++ b/src/lib/ai-providers/providers.ts
@@ -61,8 +61,16 @@ export const PROVIDERS: ProviderSpec[] = [
// Free tier first — these are what runs out of the box with no
// OpenRouter credit balance. Curated against the live `:free` list
// and filtered to models that declare tool-use support.
- { id: "qwen/qwen3-coder:free", label: "Qwen3 Coder", note: "Qwen · free · coder-tuned (default)" },
- { id: "deepseek/deepseek-v4-flash:free", label: "DeepSeek V4 Flash", note: "DeepSeek · free" },
+ {
+ id: "qwen/qwen3-coder:free",
+ label: "Qwen3 Coder",
+ note: "Qwen · free · coder-tuned (default)",
+ },
+ {
+ id: "deepseek/deepseek-v4-flash:free",
+ label: "DeepSeek V4 Flash",
+ note: "DeepSeek · free",
+ },
{ id: "meta-llama/llama-3.3-70b-instruct:free", label: "Llama 3.3 70B", note: "Meta · free" },
{ id: "z-ai/glm-4.5-air:free", label: "GLM 4.5 Air", note: "Z.AI · free" },
// Paid — pick these when your OpenRouter key has a budget set.
@@ -87,14 +95,17 @@ export const PROVIDERS: ProviderSpec[] = [
{ id: "gemini-2.5-pro", label: "Gemini 2.5 Pro", note: "paid · top quality" },
{ id: "gemini-2.5-flash", label: "Gemini 2.5 Flash", note: "fast · cheap" },
{ id: "gemini-2.0-flash", label: "Gemini 2.0 Flash", note: "free tier" },
- { id: "gemini-2.0-flash-thinking-exp", label: "Gemini 2.0 Flash Thinking", note: "experimental" },
+ {
+ id: "gemini-2.0-flash-thinking-exp",
+ label: "Gemini 2.0 Flash Thinking",
+ note: "experimental",
+ },
],
},
{
name: "anthropic",
label: "Anthropic",
- blurb:
- "Direct Anthropic key for Claude. Skip the OpenRouter margin if you already have one.",
+ blurb: "Direct Anthropic key for Claude. Skip the OpenRouter margin if you already have one.",
keysUrl: "https://console.anthropic.com/settings/keys",
opencodeAuthKey: "anthropic",
opencodeModelPrefix: "anthropic",
@@ -131,19 +142,13 @@ export function getProvider(name: string): ProviderSpec | null {
}
export function isProviderName(value: unknown): value is ProviderName {
- return (
- typeof value === "string" &&
- PROVIDERS.some((p) => p.name === value)
- );
+ return typeof value === "string" && PROVIDERS.some((p) => p.name === value);
}
/**
* The full `-m` argument for opencode. e.g. "openrouter/google/gemini-2.5-pro"
* or "google/gemini-2.5-pro".
*/
-export function formatOpencodeModel(
- provider: ProviderSpec,
- modelId: string,
-): string {
+export function formatOpencodeModel(provider: ProviderSpec, modelId: string): string {
return `${provider.opencodeModelPrefix}/${modelId}`;
}
diff --git a/src/lib/ai-providers/store.ts b/src/lib/ai-providers/store.ts
index d35b5a4..995049e 100644
--- a/src/lib/ai-providers/store.ts
+++ b/src/lib/ai-providers/store.ts
@@ -1,13 +1,13 @@
import "server-only";
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
-import { getDb } from "@/lib/registry/db";
-import { getEnv } from "@/lib/env";
import {
- PROVIDERS,
getProvider,
isProviderName,
+ PROVIDERS,
type ProviderName,
} from "@/lib/ai-providers/providers";
+import { getEnv } from "@/lib/env";
+import { getDb } from "@/lib/registry/db";
/**
* Encrypted vault for per-user provider API keys + a single-row prefs
@@ -25,10 +25,7 @@ function encrypt(plaintext: string): string {
const key = getEnv().identityEncryptionKey;
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key, iv);
- const enc = Buffer.concat([
- cipher.update(plaintext, "utf-8"),
- cipher.final(),
- ]);
+ const enc = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
const tag = cipher.getAuthTag();
return `${CIPHER_VERSION}:${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
}
@@ -41,15 +38,9 @@ function decrypt(stored: string): string | null {
const iv = Buffer.from(parts[1], "base64");
const tag = Buffer.from(parts[2], "base64");
const data = Buffer.from(parts[3], "base64");
- const decipher = createDecipheriv(
- "aes-256-gcm",
- getEnv().identityEncryptionKey,
- iv,
- );
+ const decipher = createDecipheriv("aes-256-gcm", getEnv().identityEncryptionKey, iv);
decipher.setAuthTag(tag);
- return Buffer.concat([decipher.update(data), decipher.final()]).toString(
- "utf-8",
- );
+ return Buffer.concat([decipher.update(data), decipher.final()]).toString("utf-8");
} catch (e) {
console.warn("[ai-providers] decrypt failed:", e);
return null;
@@ -95,14 +86,12 @@ export function setUserApiKey(
return { provider, hint, createdAt: now, updatedAt: now };
}
-export function deleteUserApiKey(
- webId: string,
- provider: ProviderName,
-): void {
+export function deleteUserApiKey(webId: string, provider: ProviderName): void {
const db = getDb();
- db.prepare(
- "DELETE FROM user_ai_providers WHERE web_id = ? AND provider = ?",
- ).run(webId, provider);
+ db.prepare("DELETE FROM user_ai_providers WHERE web_id = ? AND provider = ?").run(
+ webId,
+ provider,
+ );
// If the deleted provider was the user's selected default, blank the
// pref so the coder falls back to env. We could keep it and let the
// resolver detect "selected provider missing key", but blanking is
@@ -141,14 +130,9 @@ export function listConfiguredProviders(webId: string): ConfiguredProvider[] {
/** Decrypts and returns the plaintext key. Server-only; never sent over
* the wire. The coder driver calls this just before spawning docker. */
-export function getDecryptedApiKey(
- webId: string,
- provider: ProviderName,
-): string | null {
+export function getDecryptedApiKey(webId: string, provider: ProviderName): string | null {
const row = getDb()
- .prepare(
- "SELECT api_key_enc FROM user_ai_providers WHERE web_id = ? AND provider = ?",
- )
+ .prepare("SELECT api_key_enc FROM user_ai_providers WHERE web_id = ? AND provider = ?")
.get(webId, provider) as { api_key_enc: string } | undefined;
if (!row) return null;
return decrypt(row.api_key_enc);
@@ -166,9 +150,7 @@ export type UserAiPref = {
export function getUserAiPref(webId: string): UserAiPref {
const row = getDb()
- .prepare(
- "SELECT provider, model, updated_at FROM user_ai_prefs WHERE web_id = ?",
- )
+ .prepare("SELECT provider, model, updated_at FROM user_ai_prefs WHERE web_id = ?")
.get(webId) as
| { provider: string | null; model: string | null; updated_at: number }
| undefined;
diff --git a/src/lib/auth/csrf-client.ts b/src/lib/auth/csrf-client.ts
index 3008e7c..80d29a7 100644
--- a/src/lib/auth/csrf-client.ts
+++ b/src/lib/auth/csrf-client.ts
@@ -21,10 +21,7 @@ export function csrfHeader(): Record {
* the CSRF header for state-changing requests. Use for all POST/PATCH/
* PUT/DELETE calls from client components.
*/
-export async function authedFetch(
- url: string,
- init: RequestInit = {},
-): Promise {
+export async function authedFetch(url: string, init: RequestInit = {}): Promise {
const method = (init.method ?? "GET").toUpperCase();
const headers = new Headers(init.headers);
if (method !== "GET" && method !== "HEAD") {
diff --git a/src/lib/auth/session.ts b/src/lib/auth/session.ts
index 0b7f3a0..dce0ea1 100644
--- a/src/lib/auth/session.ts
+++ b/src/lib/auth/session.ts
@@ -4,7 +4,7 @@ import { cookies, headers } from "next/headers";
import { NextResponse } from "next/server";
import { getEnv } from "@/lib/env";
import type { Repo } from "@/lib/registry/repos";
-import { resolveMemberRole, ROLE_RANK, type MemberRole } from "@/lib/solid/members";
+import { type MemberRole, ROLE_RANK, resolveMemberRole } from "@/lib/solid/members";
/**
* Session cookie format:
@@ -179,10 +179,7 @@ function failure(
code: AuthFailure["body"]["code"],
message: string,
): NextResponse {
- return NextResponse.json(
- { error: message, code },
- { status },
- );
+ return NextResponse.json({ error: message, code }, { status });
}
export type AuthOk = { webId: string };
@@ -296,11 +293,7 @@ export async function requireMember(
if (role && ROLE_RANK[role] >= ROLE_RANK[minRole]) return r;
return {
ok: false,
- response: failure(
- 403,
- "FORBIDDEN",
- `requires '${minRole}' membership on this repo`,
- ),
+ response: failure(403, "FORBIDDEN", `requires '${minRole}' membership on this repo`),
};
}
diff --git a/src/lib/bootstrap.ts b/src/lib/bootstrap.ts
index f637b25..bff809d 100644
--- a/src/lib/bootstrap.ts
+++ b/src/lib/bootstrap.ts
@@ -1,8 +1,8 @@
import "server-only";
import { getEnv } from "@/lib/env";
+import { log } from "@/lib/log";
import { startReconciler } from "@/lib/pages/reconciler";
import { reapStuckRuns } from "@/lib/registry/runs";
-import { log } from "@/lib/log";
/**
* Cross-cutting server bootstrap. Idempotent (safe to call from any
diff --git a/src/lib/collab/config.ts b/src/lib/collab/config.ts
index fedf802..94ae2a9 100644
--- a/src/lib/collab/config.ts
+++ b/src/lib/collab/config.ts
@@ -14,19 +14,14 @@
* content-agnostic and the `mc:issue-draft:*` room namespace keeps the two
* apps' docs from colliding, so no codespaces-specific relay image is needed.
*/
-export const collabRelayUrl =
- process.env.NEXT_PUBLIC_COLLAB_RELAY_URL ?? "ws://localhost:3012";
+export const collabRelayUrl = process.env.NEXT_PUBLIC_COLLAB_RELAY_URL ?? "ws://localhost:3012";
/**
* The relay room id for one draft. Namespaced by app + repo so it never
* collides with whiteboard rooms (or another repo's drafts) on a shared relay.
* `WebsocketProvider` appends this to the relay base as the WS path.
*/
-export function draftRoomName(
- owner: string,
- repo: string,
- draftId: string,
-): string {
+export function draftRoomName(owner: string, repo: string, draftId: string): string {
return `mc:issue-draft:${owner}/${repo}/${draftId}`;
}
@@ -60,9 +55,7 @@ export function nameFromWebId(webId: string | null | undefined): string {
try {
const noFragment = webId.split("#")[0];
const segments = noFragment.split("/").filter(Boolean);
- const meaningful = segments.filter(
- (s) => s !== "profile" && s !== "card" && !s.includes(":"),
- );
+ const meaningful = segments.filter((s) => s !== "profile" && s !== "card" && !s.includes(":"));
return meaningful[0] ?? "Guest";
} catch {
return "Guest";
diff --git a/src/lib/collab/draft-doc.ts b/src/lib/collab/draft-doc.ts
index 9e6d0ce..cb1fda8 100644
--- a/src/lib/collab/draft-doc.ts
+++ b/src/lib/collab/draft-doc.ts
@@ -1,9 +1,9 @@
"use client";
-import * as Y from "yjs";
-import { WebsocketProvider } from "y-websocket";
import { IndexeddbPersistence } from "y-indexeddb";
import type { Awareness } from "y-protocols/awareness";
+import { WebsocketProvider } from "y-websocket";
+import * as Y from "yjs";
import { collabRelayUrl } from "./config";
/**
@@ -71,10 +71,7 @@ export type DraftDoc = {
* document, persisted only to this browser's IndexedDB until it's committed to
* `.mind`.
*/
-export function createDraftDoc(
- roomName: string,
- opts: { collab?: boolean } = {},
-): DraftDoc {
+export function createDraftDoc(roomName: string, opts: { collab?: boolean } = {}): DraftDoc {
const collab = opts.collab ?? true;
const doc = new Y.Doc();
@@ -115,7 +112,7 @@ export function createDraftDoc(
export function readDraftMeta(meta: Y.Map): DraftMeta {
const get = (k: K): DraftMeta[K] => {
const v = meta.get(k);
- return (typeof v === "string" ? (v as DraftMeta[K]) : DEFAULT_META[k]);
+ return typeof v === "string" ? (v as DraftMeta[K]) : DEFAULT_META[k];
};
return {
title: get("title"),
diff --git a/src/lib/env.ts b/src/lib/env.ts
index 22561b4..42cdb82 100644
--- a/src/lib/env.ts
+++ b/src/lib/env.ts
@@ -1,6 +1,6 @@
import "server-only";
import { randomBytes } from "node:crypto";
-import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
+import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
/**
@@ -40,8 +40,8 @@ export type BridgeEnv = {
allowSeededFallback: boolean;
// Secrets
- sessionSecret: Buffer; // HMAC-SHA256 key for the session cookie
- hookSecret: string; // shared secret in post-receive hook scripts
+ sessionSecret: Buffer; // HMAC-SHA256 key for the session cookie
+ hookSecret: string; // shared secret in post-receive hook scripts
identityEncryptionKey: Buffer; // 32-byte AES-256-GCM key for refresh-token encryption
// Agents / coder
@@ -60,9 +60,7 @@ export type BridgeEnv = {
};
function looksLikeLoopback(url: string): boolean {
- return /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|::1|\[::1\])(:|\/|$)/.test(
- url,
- );
+ return /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|::1|\[::1\])(:|\/|$)/.test(url);
}
function readSecretFile(path: string): Record | null {
@@ -137,9 +135,7 @@ export function getEnv(): BridgeEnv {
const nodeEnvRaw = process.env.NODE_ENV ?? "development";
const nodeEnv: BridgeEnv["nodeEnv"] =
- nodeEnvRaw === "production" || nodeEnvRaw === "test"
- ? nodeEnvRaw
- : "development";
+ nodeEnvRaw === "production" || nodeEnvRaw === "test" ? nodeEnvRaw : "development";
const isProd = nodeEnv === "production";
// Next.js evaluates server-side modules during `next build` (static
@@ -151,18 +147,14 @@ export function getEnv(): BridgeEnv {
const isBuildPhase = process.env.NEXT_PHASE === "phase-production-build";
const enforceProd = isProd && !isBuildPhase;
- const bridgePublicUrl =
- process.env.BRIDGE_PUBLIC_URL ?? "http://localhost:3010";
- const bridgeInternalUrl =
- process.env.BRIDGE_INTERNAL_URL ?? "http://127.0.0.1:3010";
+ const bridgePublicUrl = process.env.BRIDGE_PUBLIC_URL ?? "http://localhost:3010";
+ const bridgeInternalUrl = process.env.BRIDGE_INTERNAL_URL ?? "http://127.0.0.1:3010";
const podBaseUrl = process.env.POD_BASE_URL ?? "http://localhost:3011/";
const postReceiveCallbackUrl =
process.env.POST_RECEIVE_CALLBACK_URL ??
`${bridgeInternalUrl.replace(/\/$/, "")}/api/git/internal/post-receive`;
- const gitDataDir = resolve(
- process.env.GIT_DATA_DIR ?? join(process.cwd(), ".git-data", "repos"),
- );
+ const gitDataDir = resolve(process.env.GIT_DATA_DIR ?? join(process.cwd(), ".git-data", "repos"));
const registryDataDir = resolve(
process.env.REGISTRY_DATA_DIR ?? join(process.cwd(), ".registry-data"),
);
@@ -172,10 +164,8 @@ export function getEnv(): BridgeEnv {
const allowSeededFallback = process.env.ALLOW_SEEDED_FALLBACK === "1";
const openrouterApiKey = process.env.OPENROUTER_API_KEY?.trim() || null;
- const agentModel =
- process.env.MIND_AGENT_MODEL?.trim() || "anthropic/claude-3.5-sonnet";
- const coderImage =
- process.env.MIND_CODER_IMAGE?.trim() || "mind-codespaces/coder:latest";
+ const agentModel = process.env.MIND_AGENT_MODEL?.trim() || "anthropic/claude-3.5-sonnet";
+ const coderImage = process.env.MIND_CODER_IMAGE?.trim() || "mind-codespaces/coder:latest";
const coderTimeoutMs = Number(process.env.MIND_CODER_TIMEOUT ?? 600) * 1000;
const coderWorkroot = process.env.MIND_CODER_WORKROOT ?? null;
const runnerRaw = (process.env.MIND_RUNNER ?? "auto").toLowerCase();
diff --git a/src/lib/git/backend.ts b/src/lib/git/backend.ts
index 4c02994..8d9c3bb 100644
--- a/src/lib/git/backend.ts
+++ b/src/lib/git/backend.ts
@@ -1,10 +1,10 @@
import "server-only";
import { spawn } from "node:child_process";
import { existsSync, mkdirSync } from "node:fs";
-import { writeFile, chmod, readdir, rm } from "node:fs/promises";
+import { chmod, readdir, rm, writeFile } from "node:fs/promises";
import { join, resolve, sep } from "node:path";
-import { validateName } from "@/lib/registry/repos";
import { getEnv } from "@/lib/env";
+import { validateName } from "@/lib/registry/repos";
/**
* Resolve the bare-repo storage root. Lazy: never call getEnv() at
@@ -30,9 +30,7 @@ export function repoPath(owner: string, name: string): string {
const path = resolve(root, owner, `${name}.git`);
const rootWithSep = root.endsWith(sep) ? root : root + sep;
if (!path.startsWith(rootWithSep)) {
- throw new Error(
- `repoPath resolved outside gitDataDir (owner=${owner} name=${name})`,
- );
+ throw new Error(`repoPath resolved outside gitDataDir (owner=${owner} name=${name})`);
}
return path;
}
@@ -81,17 +79,10 @@ export async function reinstallAllHooks(): Promise<{ count: number }> {
if (!repoEnt.isDirectory() || !repoEnt.name.endsWith(".git")) continue;
const repoName = repoEnt.name.replace(/\.git$/, "");
try {
- await installPostReceiveHook(
- join(ownerDir, repoEnt.name),
- ownerEnt.name,
- repoName,
- );
+ await installPostReceiveHook(join(ownerDir, repoEnt.name), ownerEnt.name, repoName);
count += 1;
} catch (e) {
- console.warn(
- `[backend] failed to reinstall hook for ${ownerEnt.name}/${repoName}:`,
- e,
- );
+ console.warn(`[backend] failed to reinstall hook for ${ownerEnt.name}/${repoName}:`, e);
}
}
}
@@ -167,10 +158,7 @@ function runGit(args: string[]): Promise {
const diskSizeCache = new Map();
const DISK_SIZE_TTL_MS = 60 * 1000;
-export function getRepoDiskBytes(
- owner: string,
- name: string,
-): Promise {
+export function getRepoDiskBytes(owner: string, name: string): Promise {
const key = `${owner}/${name}`;
const now = Date.now();
const cached = diskSizeCache.get(key);
@@ -206,17 +194,13 @@ export function readBranchHead(
branch: string,
): Promise {
if (!/^[A-Za-z0-9._/-]+$/.test(branch)) {
- return Promise.reject(
- new Error(`refusing unsafe branch name ${JSON.stringify(branch)}`),
- );
+ return Promise.reject(new Error(`refusing unsafe branch name ${JSON.stringify(branch)}`));
}
const repo = repoPath(owner, name);
return new Promise((resolveFn) => {
- const child = spawn(
- "git",
- ["-C", repo, "rev-parse", "--verify", `refs/heads/${branch}`],
- { stdio: ["ignore", "pipe", "pipe"] },
- );
+ const child = spawn("git", ["-C", repo, "rev-parse", "--verify", `refs/heads/${branch}`], {
+ stdio: ["ignore", "pipe", "pipe"],
+ });
let stdout = "";
child.stdout.on("data", (d) => (stdout += d.toString()));
child.on("error", () => resolveFn(null));
diff --git a/src/lib/git/diff.ts b/src/lib/git/diff.ts
index 4b4ee0a..3e0c789 100644
Binary files a/src/lib/git/diff.ts and b/src/lib/git/diff.ts differ
diff --git a/src/lib/git/http-cgi.ts b/src/lib/git/http-cgi.ts
index 6b7a900..9e5ff78 100644
--- a/src/lib/git/http-cgi.ts
+++ b/src/lib/git/http-cgi.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
+import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
import { Readable } from "node:stream";
import { getGitDataDir } from "@/lib/git/backend";
@@ -43,10 +43,7 @@ const REQUEST_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes per Smart-HTTP request
* on the response stream (vs the previous code path, which produced
* a truncated success — a git client would see a partial success).
*/
-export async function runGitHttpBackend(
- req: Request,
- pathInfo: string,
-): Promise {
+export async function runGitHttpBackend(req: Request, pathInfo: string): Promise {
installShutdownHook();
const url = new URL(req.url);
@@ -191,9 +188,7 @@ export async function runGitHttpBackend(
pendingClose = true;
if (bodyController) bodyController.close();
if (!headersParsed) {
- rejectResponse(
- new Error("git http-backend exited without emitting CGI headers"),
- );
+ rejectResponse(new Error("git http-backend exited without emitting CGI headers"));
}
});
@@ -212,16 +207,12 @@ export async function runGitHttpBackend(
liveChildren.delete(child);
if (code !== 0) {
if (!headersParsed) {
- rejectResponse(
- new Error(`git http-backend exited with code ${code} before any output`),
- );
+ rejectResponse(new Error(`git http-backend exited with code ${code} before any output`));
} else if (bodyController) {
// Headers already emitted, then the CGI died mid-body. The
// git client would otherwise see a truncated success and trust
// the bytes — surface the failure as a stream error instead.
- bodyController.error(
- new Error(`git http-backend exited with code ${code} mid-body`),
- );
+ bodyController.error(new Error(`git http-backend exited with code ${code} mid-body`));
}
}
});
diff --git a/src/lib/git/merge.ts b/src/lib/git/merge.ts
index 68d1154..a283659 100644
--- a/src/lib/git/merge.ts
+++ b/src/lib/git/merge.ts
@@ -36,10 +36,7 @@ export async function mergeBranches(
target: string,
message: string,
author: MergeAuthor,
-): Promise<
- | { ok: true; mergeSha: string }
- | { ok: false; conflict: boolean; message: string }
-> {
+): Promise<{ ok: true; mergeSha: string } | { ok: false; conflict: boolean; message: string }> {
// Empty-target fast-path. When the target branch doesn't exist on
// the bare yet (fresh repo whose first commit is the agent's own,
// before anyone has pushed `main`), `checkout target` below fails
@@ -71,16 +68,9 @@ export async function mergeBranches(
};
}
const sourceSha = sourceProbe.stdout.trim();
- const seedDir = await fs.mkdtemp(
- path.join(os.tmpdir(), "mind-pr-seed-"),
- );
+ const seedDir = await fs.mkdtemp(path.join(os.tmpdir(), "mind-pr-seed-"));
try {
- const clone = await sh("git", [
- "clone",
- "--no-checkout",
- bareRepoPath,
- seedDir,
- ]);
+ const clone = await sh("git", ["clone", "--no-checkout", bareRepoPath, seedDir]);
if (clone.exit !== 0) {
return {
ok: false,
@@ -108,9 +98,7 @@ export async function mergeBranches(
}
}
- const workDir = await fs.mkdtemp(
- path.join(os.tmpdir(), "mind-pr-merge-"),
- );
+ const workDir = await fs.mkdtemp(path.join(os.tmpdir(), "mind-pr-merge-"));
try {
const clone = await sh("git", ["clone", bareRepoPath, workDir]);
if (clone.exit !== 0) {
diff --git a/src/lib/git/objects.ts b/src/lib/git/objects.ts
index 1637278..e4d3376 100644
--- a/src/lib/git/objects.ts
+++ b/src/lib/git/objects.ts
@@ -57,12 +57,7 @@ export async function findReadme(
/** True if the repo has at least one commit reachable from HEAD. */
export async function hasAnyCommits(bareRepoPath: string): Promise {
- const { code } = await runGit(bareRepoPath, [
- "rev-parse",
- "--verify",
- "--quiet",
- "HEAD",
- ]);
+ const { code } = await runGit(bareRepoPath, ["rev-parse", "--verify", "--quiet", "HEAD"]);
return code === 0;
}
@@ -146,11 +141,7 @@ export async function listTree(
path: string,
): Promise {
const target = path ? `${ref}:${path}` : ref;
- const { stdout, code, stderr } = await runGit(bareRepoPath, [
- "ls-tree",
- "--long",
- target,
- ]);
+ const { stdout, code, stderr } = await runGit(bareRepoPath, ["ls-tree", "--long", target]);
if (code !== 0) {
// `bad revision` on empty repos, or path doesn't exist on this ref.
if (/Not a valid object name|bad revision|exists on disk/i.test(stderr)) {
@@ -195,19 +186,11 @@ export async function readBlob(
path: string,
): Promise {
// First, get the blob's full size and verify it exists / is a blob.
- const sizeProbe = await runGit(bareRepoPath, [
- "cat-file",
- "-s",
- `${ref}:${path}`,
- ]);
+ const sizeProbe = await runGit(bareRepoPath, ["cat-file", "-s", `${ref}:${path}`]);
if (sizeProbe.code !== 0) return null;
const totalSize = Number.parseInt(sizeProbe.stdout.trim(), 10);
- const typeProbe = await runGit(bareRepoPath, [
- "cat-file",
- "-t",
- `${ref}:${path}`,
- ]);
+ const typeProbe = await runGit(bareRepoPath, ["cat-file", "-t", `${ref}:${path}`]);
if (typeProbe.code !== 0 || typeProbe.stdout.trim() !== "blob") {
return null;
}
@@ -245,19 +228,12 @@ function runGit(bareRepoPath: string, args: string[]): Promise {
let stderr = "";
child.stdout.on("data", (d) => (stdout += d.toString()));
child.stderr.on("data", (d) => (stderr += d.toString()));
- child.on("error", (err) =>
- resolveFn({ stdout, stderr: stderr + err.message, code: -1 }),
- );
- child.on("close", (code) =>
- resolveFn({ stdout, stderr, code: code ?? -1 }),
- );
+ child.on("error", (err) => resolveFn({ stdout, stderr: stderr + err.message, code: -1 }));
+ child.on("close", (code) => resolveFn({ stdout, stderr, code: code ?? -1 }));
});
}
-function runGitBytes(
- bareRepoPath: string,
- args: string[],
-): Promise {
+function runGitBytes(bareRepoPath: string, args: string[]): Promise {
return new Promise((resolveFn) => {
const child = spawn("git", [`--git-dir=${bareRepoPath}`, ...args]);
const chunks: Buffer[] = [];
diff --git a/src/lib/http/json.ts b/src/lib/http/json.ts
index 4072ca3..a750b78 100644
--- a/src/lib/http/json.ts
+++ b/src/lib/http/json.ts
@@ -26,10 +26,7 @@ const MAX_RESPONSE_BYTES = (() => {
return Number.isFinite(n) && n > 0 ? n : 5 * 1024 * 1024;
})();
-export function jsonResponse(
- data: unknown,
- init: ResponseInit = {},
-): NextResponse {
+export function jsonResponse(data: unknown, init: ResponseInit = {}): NextResponse {
const body = JSON.stringify(data);
const bytes = Buffer.byteLength(body, "utf-8");
if (bytes > MAX_RESPONSE_BYTES) {
diff --git a/src/lib/ledger/client.ts b/src/lib/ledger/client.ts
index f68c438..f4f0067 100644
--- a/src/lib/ledger/client.ts
+++ b/src/lib/ledger/client.ts
@@ -65,11 +65,7 @@ export type DebitResult =
* the run has happened, so the caller logs and continues rather than failing
* the user after the fact.
*/
-export async function debit(
- webId: string,
- amount: number,
- memo: string,
-): Promise {
+export async function debit(webId: string, amount: number, memo: string): Promise {
const cfg = ledgerConfig();
if (!cfg) return { ok: false, status: 0, balance: null };
try {
diff --git a/src/lib/ledger/policy.ts b/src/lib/ledger/policy.ts
index eee0861..2349341 100644
--- a/src/lib/ledger/policy.ts
+++ b/src/lib/ledger/policy.ts
@@ -6,9 +6,7 @@
* unmetered).
*/
-export type FallbackGate =
- | { kind: "allow"; meter: boolean }
- | { kind: "blocked"; balance: number };
+export type FallbackGate = { kind: "allow"; meter: boolean } | { kind: "blocked"; balance: number };
/**
* Decide whether an env-fallback (company-key) coder run may proceed, and
diff --git a/src/lib/metrics.ts b/src/lib/metrics.ts
index 504f14f..0f432f8 100644
--- a/src/lib/metrics.ts
+++ b/src/lib/metrics.ts
@@ -30,16 +30,10 @@ function serialiseLabels(labels?: Labels): string {
if (!labels) return "";
const keys = Object.keys(labels).sort();
if (keys.length === 0) return "";
- return keys
- .map((k) => `${k}="${String(labels[k]).replace(/[\\"\n]/g, "_")}"`)
- .join(",");
+ return keys.map((k) => `${k}="${String(labels[k]).replace(/[\\"\n]/g, "_")}"`).join(",");
}
-function ensureEntry(
- name: string,
- type: "counter" | "gauge",
- help: string,
-): MetricEntry {
+function ensureEntry(name: string, type: "counter" | "gauge", help: string): MetricEntry {
const existing = registry.get(name);
if (existing) return existing;
const entry: MetricEntry = { type, help, series: new Map() };
@@ -47,12 +41,7 @@ function ensureEntry(
return entry;
}
-export function incrementCounter(
- name: string,
- help: string,
- labels?: Labels,
- value = 1,
-): void {
+export function incrementCounter(name: string, help: string, labels?: Labels, value = 1): void {
const entry = ensureEntry(name, "counter", help);
const key = serialiseLabels(labels);
entry.series.set(key, (entry.series.get(key) ?? 0) + value);
@@ -94,7 +83,11 @@ export function renderExposition(): string {
// Convenience wrappers for the named series the bridge tracks.
export const Metrics = {
- gitPush(owner: string, repo: string, result: "success" | "auth-failed" | "rate-limited" | "quota-exceeded" | "error"): void {
+ gitPush(
+ owner: string,
+ repo: string,
+ result: "success" | "auth-failed" | "rate-limited" | "quota-exceeded" | "error",
+ ): void {
incrementCounter("git_pushes_total", "Total git push attempts.", {
owner,
repo,
diff --git a/src/lib/packages/auth.ts b/src/lib/packages/auth.ts
index 65d899d..d7bd228 100644
--- a/src/lib/packages/auth.ts
+++ b/src/lib/packages/auth.ts
@@ -35,10 +35,7 @@ export function readPackageToken(req: Request): string | null {
}
/** True iff the request carries a valid push token for this repo. */
-export function authenticatePackagePush(
- repoId: number,
- req: Request,
-): boolean {
+export function authenticatePackagePush(repoId: number, req: Request): boolean {
const token = readPackageToken(req);
return !!token && verifyPushToken(repoId, token);
}
diff --git a/src/lib/packages/content-store.ts b/src/lib/packages/content-store.ts
index 35116ea..d30d897 100644
--- a/src/lib/packages/content-store.ts
+++ b/src/lib/packages/content-store.ts
@@ -1,7 +1,7 @@
import "server-only";
import { createHash } from "node:crypto";
-import { ensureContainer, setPublicReadAcl } from "@/lib/solid/containers";
import { log } from "@/lib/log";
+import { ensureContainer, setPublicReadAcl } from "@/lib/solid/containers";
/**
* Content-addressed blob store backed by a Solid pod (see
@@ -96,9 +96,7 @@ export class PodContentStore {
});
if (res.status === 404) return null;
if (!res.ok) {
- throw new Error(
- `content-store GET ${this.blobUrl(want)} failed: ${res.status}`,
- );
+ throw new Error(`content-store GET ${this.blobUrl(want)} failed: ${res.status}`);
}
const bytes = new Uint8Array(await res.arrayBuffer());
const got = createHash("sha256").update(bytes).digest("hex");
diff --git a/src/lib/packages/repo-store.ts b/src/lib/packages/repo-store.ts
index dae36f4..3d70d1e 100644
--- a/src/lib/packages/repo-store.ts
+++ b/src/lib/packages/repo-store.ts
@@ -1,7 +1,7 @@
import "server-only";
+import { PodContentStore } from "@/lib/packages/content-store";
import type { Repo } from "@/lib/registry/repos";
import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
-import { PodContentStore } from "@/lib/packages/content-store";
/**
* Build a `PodContentStore` for a repo's owner.
diff --git a/src/lib/packages/store.ts b/src/lib/packages/store.ts
index 92cacc4..456c278 100644
--- a/src/lib/packages/store.ts
+++ b/src/lib/packages/store.ts
@@ -38,8 +38,7 @@ const NPM_NAME_RE = /^(@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
const SEGMENT_RE = /^[a-z0-9][a-z0-9._+-]*$/i;
// OCI image names: lowercase path components separated by `/` (the Docker
// "repository name" grammar), e.g. `myimage` or `team/service`.
-const OCI_NAME_RE =
- /^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/;
+const OCI_NAME_RE = /^[a-z0-9]+(?:[._-][a-z0-9]+)*(?:\/[a-z0-9]+(?:[._-][a-z0-9]+)*)*$/;
// A content digest, e.g. `sha256:` — a valid OCI manifest reference.
const DIGEST_RE = /^sha(?:256|512):[a-f0-9]{32,128}$/i;
@@ -49,15 +48,9 @@ export function validatePackageName(name: string, type: PackageType): void {
else if (type === "oci") pattern = OCI_NAME_RE;
else pattern = SEGMENT_RE;
const ok =
- typeof name === "string" &&
- name.length <= 214 &&
- !name.includes("..") &&
- pattern.test(name);
+ typeof name === "string" && name.length <= 214 && !name.includes("..") && pattern.test(name);
if (!ok) {
- throw new PackageError(
- `invalid ${type} package name: ${JSON.stringify(name)}`,
- "INVALID_NAME",
- );
+ throw new PackageError(`invalid ${type} package name: ${JSON.stringify(name)}`, "INVALID_NAME");
}
}
@@ -70,10 +63,7 @@ export function validateVersion(version: string): void {
version.includes("..") ||
!(SEGMENT_RE.test(version) || DIGEST_RE.test(version))
) {
- throw new PackageError(
- `invalid version: ${JSON.stringify(version)}`,
- "INVALID_VERSION",
- );
+ throw new PackageError(`invalid version: ${JSON.stringify(version)}`, "INVALID_VERSION");
}
}
@@ -142,11 +132,7 @@ export function getPackageVersion(
}
/** All versions of one package, newest first. */
-export function listVersions(
- repoId: number,
- type: PackageType,
- name: string,
-): PackageRecord[] {
+export function listVersions(repoId: number, type: PackageType, name: string): PackageRecord[] {
return (
getDb()
.prepare(
@@ -159,15 +145,10 @@ export function listVersions(
}
/** Every package version in a repo (optionally filtered by type), newest first. */
-export function listPackages(
- repoId: number,
- type?: PackageType,
-): PackageRecord[] {
+export function listPackages(repoId: number, type?: PackageType): PackageRecord[] {
const rows = type
? getDb()
- .prepare(
- `SELECT * FROM packages WHERE repo_id = ? AND type = ? ORDER BY created_at DESC`,
- )
+ .prepare(`SELECT * FROM packages WHERE repo_id = ? AND type = ? ORDER BY created_at DESC`)
.all(repoId, type)
: getDb()
.prepare(`SELECT * FROM packages WHERE repo_id = ? ORDER BY created_at DESC`)
diff --git a/src/lib/pages/preview.ts b/src/lib/pages/preview.ts
index 006bf39..a38a71a 100644
--- a/src/lib/pages/preview.ts
+++ b/src/lib/pages/preview.ts
@@ -3,23 +3,18 @@ import { createWriteStream, type WriteStream } from "node:fs";
import { mkdir, mkdtemp, readFile, rm, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import * as path from "node:path";
-import { getRepoById, type PagesConfig, type Repo } from "@/lib/registry/repos";
-import {
- getPullRequest,
- updatePullPreview,
- type PullRequest,
-} from "@/lib/registry/pulls";
-import { repoPath, readBranchHead } from "@/lib/git/backend";
+import { readBranchHead, repoPath } from "@/lib/git/backend";
import { checkoutBranchToTempDir } from "@/lib/git/checkout";
-import { parseWorkflow } from "@/lib/workflows/parse";
-import { resolveRunnerMode, runShellBatch } from "@/lib/workflows/docker";
import { publishDirectory } from "@/lib/pages/publisher";
+import { getPullRequest, type PullRequest, updatePullPreview } from "@/lib/registry/pulls";
+import { getRepoById, type PagesConfig, type Repo } from "@/lib/registry/repos";
import { OwnerFetchUnavailableError } from "@/lib/solid/fetch-for-owner";
+import { resolveRunnerMode, runShellBatch } from "@/lib/workflows/docker";
+import { parseWorkflow } from "@/lib/workflows/parse";
// Same location agent-run logs use (dispatch.ts owns the const, but importing
// it here would create a cycle: dispatch.ts → preview.ts → dispatch.ts).
-const AGENT_LOGS_DIR =
- process.env.AGENT_LOGS_DIR ?? path.join(process.cwd(), ".agent-logs");
+const AGENT_LOGS_DIR = process.env.AGENT_LOGS_DIR ?? path.join(process.cwd(), ".agent-logs");
/**
* PR previews. A PR's source branch is built (if it ships a
@@ -41,9 +36,7 @@ const WORKFLOW_REL = ".mind/workflow.yml";
/** Pod container a PR's preview is published to (under /public → public-read). */
export function previewContainerFor(repo: Repo, pullNumber: number): string {
- const base = repo.ownerPodRoot.endsWith("/")
- ? repo.ownerPodRoot
- : `${repo.ownerPodRoot}/`;
+ const base = repo.ownerPodRoot.endsWith("/") ? repo.ownerPodRoot : `${repo.ownerPodRoot}/`;
return `${base}public/previews/${repo.name}/${pullNumber}/`;
}
@@ -79,9 +72,8 @@ export async function buildAndPublishPreview(pull: PullRequest): Promise {
if (!repo) return;
const liveSha =
- (await readBranchHead(repo.owner, repo.name, pull.sourceBranch).catch(
- () => null,
- )) ?? pull.sourceSha;
+ (await readBranchHead(repo.owner, repo.name, pull.sourceBranch).catch(() => null)) ??
+ pull.sourceSha;
// SHA-guard — don't rebuild an unchanged branch that's already live.
// The POST route optimistically marks "building" before this runs, so
@@ -117,10 +109,7 @@ export async function buildAndPublishPreview(pull: PullRequest): Promise {
// Tier 2 (build) if a workflow exists; else Tier 1 (static, instant).
let publishDir = tempDir;
- const wfSource = await readFile(
- path.join(tempDir, WORKFLOW_REL),
- "utf-8",
- ).catch(() => null);
+ const wfSource = await readFile(path.join(tempDir, WORKFLOW_REL), "utf-8").catch(() => null);
if (wfSource !== null) {
const wf = parseWorkflow(wfSource);
const mode = await resolveRunnerMode();
@@ -167,8 +156,7 @@ export async function buildAndPublishPreview(pull: PullRequest): Promise {
);
} catch (err) {
const message =
- err instanceof OwnerFetchUnavailableError &&
- err.reason === "needs-reauthorization"
+ err instanceof OwnerFetchUnavailableError && err.reason === "needs-reauthorization"
? "preview failed: reconnect your pod at /connect"
: err instanceof Error
? err.message
@@ -211,10 +199,7 @@ export async function deletePreview(pull: PullRequest): Promise {
}
/** Build a preview for (repoId, prNumber) by id — convenience for triggers. */
-export async function buildPreviewForPull(
- repoId: number,
- prNumber: number,
-): Promise {
+export async function buildPreviewForPull(repoId: number, prNumber: number): Promise {
const pull = getPullRequest(repoId, prNumber);
if (pull) await buildAndPublishPreview(pull);
}
diff --git a/src/lib/pages/publisher.ts b/src/lib/pages/publisher.ts
index c62d72c..97f4afc 100644
--- a/src/lib/pages/publisher.ts
+++ b/src/lib/pages/publisher.ts
@@ -1,26 +1,26 @@
import "server-only";
-import { readFile, readdir, lstat } from "node:fs/promises";
-import { join, relative, resolve, sep, extname } from "node:path";
+import { lstat, readdir, readFile } from "node:fs/promises";
+import { extname, join, relative, resolve, sep } from "node:path";
+import { readBranchHead, repoPath } from "@/lib/git/backend";
+import { checkoutBranchToTempDir } from "@/lib/git/checkout";
+import { clip, log, scrubWebId } from "@/lib/log";
+import { Metrics } from "@/lib/metrics";
+import { mimeForPath } from "@/lib/pages/mime";
+import { withPublishLock } from "@/lib/pages/publish-lock";
import {
- getRepoById,
getPagesConfig,
- markPagesPublished,
+ getRepoById,
markPagesFailed,
+ markPagesPublished,
type PagesConfig,
type Repo,
} from "@/lib/registry/repos";
-import { repoPath, readBranchHead } from "@/lib/git/backend";
-import { checkoutBranchToTempDir } from "@/lib/git/checkout";
+import { ensureContainer, setPublicReadAcl } from "@/lib/solid/containers";
import {
getOwnerFetch,
- OwnerFetchUnavailableError,
type OwnerFetch,
+ OwnerFetchUnavailableError,
} from "@/lib/solid/fetch-for-owner";
-import { ensureContainer, setPublicReadAcl } from "@/lib/solid/containers";
-import { mimeForPath } from "@/lib/pages/mime";
-import { withPublishLock } from "@/lib/pages/publish-lock";
-import { log, scrubWebId, clip } from "@/lib/log";
-import { Metrics } from "@/lib/metrics";
// Files / directories the publisher must NEVER upload to a pod.
// Applied during the walk, before any PUT request is sent. Symlinks of
@@ -45,21 +45,8 @@ const FORBIDDEN_FILE_PREFIXES = [
"credentials",
"secrets",
];
-const FORBIDDEN_FILE_EXTENSIONS = new Set([
- ".pem",
- ".key",
- ".p12",
- ".pfx",
- ".asc",
- ".crt",
-]);
-const FORBIDDEN_FILE_NAMES = new Set([
- ".DS_Store",
- ".netrc",
- ".npmrc",
- ".pypirc",
- ".dockercfg",
-]);
+const FORBIDDEN_FILE_EXTENSIONS = new Set([".pem", ".key", ".p12", ".pfx", ".asc", ".crt"]);
+const FORBIDDEN_FILE_NAMES = new Set([".DS_Store", ".netrc", ".npmrc", ".pypirc", ".dockercfg"]);
// Hard cap to keep a 5 GB asset from OOM-ing the publisher (P0-R7).
// Files above this are skipped with a warning; eventually we should
@@ -100,16 +87,9 @@ export async function publishPages(repoId: number): Promise<{
// Snapshot HEAD before the checkout so we can record exactly which
// commit reached the pod. Used by the reconciler (P0-R4) to detect
// drift when the post-receive hook silently fails.
- const headSha = await readBranchHead(
- repo.owner,
- repo.name,
- pages.sourceBranch,
- );
+ const headSha = await readBranchHead(repo.owner, repo.name, pages.sourceBranch);
- const { tempDir, cleanup } = await checkoutBranchToTempDir(
- bare,
- pages.sourceBranch,
- );
+ const { tempDir, cleanup } = await checkoutBranchToTempDir(bare, pages.sourceBranch);
try {
const sourceRoot = resolveSourceDir(tempDir, pages.sourcePath);
@@ -181,12 +161,7 @@ export async function publishDirectory(input: {
mode: authed.mode,
});
- await ensureContainerPath(
- authed.fetch,
- repo.ownerPodRoot,
- target,
- repo.ownerWebId,
- );
+ await ensureContainerPath(authed.fetch, repo.ownerPodRoot, target, repo.ownerWebId);
// Track every relative URL we just (re-)uploaded so the prune step
// afterwards can DELETE anything the source no longer contains.
@@ -278,9 +253,7 @@ async function pruneStale(
pruned += 1;
console.log(`[publisher] pruned ${childAbsUrl}`);
} else if (res.status !== 404) {
- console.warn(
- `[publisher] DELETE ${childAbsUrl} failed: ${res.status} ${res.statusText}`,
- );
+ console.warn(`[publisher] DELETE ${childAbsUrl} failed: ${res.status} ${res.statusText}`);
}
} catch (e) {
console.warn(`[publisher] DELETE ${childAbsUrl} threw:`, e);
@@ -306,9 +279,7 @@ async function listContainerChildren(
});
if (res.status === 404) return [];
if (!res.ok) {
- throw new Error(
- `GET ${containerUrl} failed during prune: ${res.status} ${res.statusText}`,
- );
+ throw new Error(`GET ${containerUrl} failed during prune: ${res.status} ${res.statusText}`);
}
const body = await res.text();
return parseLdpContains(body);
@@ -376,9 +347,7 @@ async function ensureContainerPath(
ownerWebId: string,
): Promise {
if (!target.startsWith(podRoot)) {
- throw new Error(
- `targetContainer (${target}) is not inside ownerPodRoot (${podRoot})`,
- );
+ throw new Error(`targetContainer (${target}) is not inside ownerPodRoot (${podRoot})`);
}
const trail = target.slice(podRoot.length).split("/").filter(Boolean);
let cursor = podRoot.endsWith("/") ? podRoot : podRoot + "/";
@@ -437,4 +406,3 @@ export async function* walk(dir: string): AsyncGenerator {
yield full;
}
}
-
diff --git a/src/lib/pages/reconciler.ts b/src/lib/pages/reconciler.ts
index a95e566..5efe1fa 100644
--- a/src/lib/pages/reconciler.ts
+++ b/src/lib/pages/reconciler.ts
@@ -1,11 +1,7 @@
import "server-only";
-import {
- getPagesConfig,
- listRepos,
- type Repo,
-} from "@/lib/registry/repos";
import { readBranchHead } from "@/lib/git/backend";
import { publishPages } from "@/lib/pages/publisher";
+import { getPagesConfig, listRepos, type Repo } from "@/lib/registry/repos";
/**
* P0-R4 — HEAD-vs-last_published_sha reconciler.
@@ -60,9 +56,7 @@ export function reconcilePages(): Promise {
for (const repo of repos) {
outcomes.push(await reconcileOne(repo));
}
- const driftCount = outcomes.filter(
- (o) => o.status === "republished",
- ).length;
+ const driftCount = outcomes.filter((o) => o.status === "republished").length;
const failureCount = outcomes.filter((o) => o.status === "failed").length;
if (driftCount > 0 || failureCount > 0) {
console.log(
@@ -155,17 +149,11 @@ let timer: NodeJS.Timeout | null = null;
export function startReconciler(): void {
if (timerStarted) return;
timerStarted = true;
- console.log(
- `[reconciler] starting — interval=${RECONCILE_INTERVAL_MS}ms`,
- );
+ console.log(`[reconciler] starting — interval=${RECONCILE_INTERVAL_MS}ms`);
// Kick off the first pass async so the bootstrap doesn't block on it.
- void reconcilePages().catch((e) =>
- console.warn(`[reconciler] initial pass failed:`, e),
- );
+ void reconcilePages().catch((e) => console.warn(`[reconciler] initial pass failed:`, e));
timer = setInterval(() => {
- void reconcilePages().catch((e) =>
- console.warn(`[reconciler] pass failed:`, e),
- );
+ void reconcilePages().catch((e) => console.warn(`[reconciler] pass failed:`, e));
}, RECONCILE_INTERVAL_MS);
// Allow the process to exit cleanly during dev / scripts.
if (typeof timer.unref === "function") timer.unref();
diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts
index c5c1302..4fb2f01 100644
--- a/src/lib/rate-limit.ts
+++ b/src/lib/rate-limit.ts
@@ -56,10 +56,7 @@ async function clientKey(scope: string): Promise {
* Enforce a rate limit. Returns null when allowed; otherwise returns a
* pre-built 429 NextResponse the caller can return directly.
*/
-export async function rateLimit(
- scope: string,
- cfg: RateLimitConfig,
-): Promise {
+export async function rateLimit(scope: string, cfg: RateLimitConfig): Promise {
const key = await clientKey(scope);
if (take(key, cfg)) return null;
return NextResponse.json(
@@ -80,10 +77,7 @@ export async function rateLimit(
* Use for auth-failure brute-force defense: peek before attempting auth,
* consume only when auth fails. A successful credential burns no budget.
*/
-export async function isLockedOut(
- scope: string,
- cfg: RateLimitConfig,
-): Promise {
+export async function isLockedOut(scope: string, cfg: RateLimitConfig): Promise {
const key = await clientKey(scope);
const now = Date.now();
const b = buckets.get(key);
@@ -93,10 +87,7 @@ export async function isLockedOut(
return projected < 1;
}
-export async function recordFailure(
- scope: string,
- cfg: RateLimitConfig,
-): Promise {
+export async function recordFailure(scope: string, cfg: RateLimitConfig): Promise {
const key = await clientKey(scope);
take(key, cfg);
}
diff --git a/src/lib/registry/activity.ts b/src/lib/registry/activity.ts
index 2c95a67..28f7957 100644
--- a/src/lib/registry/activity.ts
+++ b/src/lib/registry/activity.ts
@@ -39,10 +39,7 @@ export type ActivityItem = {
* without paging. Bumped this up cap means more SQL work; the per-source
* LIMIT keeps the joined cost bounded.
*/
-export function listActivityForWebId(
- webId: string,
- limit = 25,
-): ActivityItem[] {
+export function listActivityForWebId(webId: string, limit = 25): ActivityItem[] {
const db = getDb();
const perSourceCap = limit;
const items: ActivityItem[] = [];
@@ -75,9 +72,8 @@ export function listActivityForWebId(
const ownedRepoIds = ownedRepos.map((r) => r.id);
// Conditional IN list. Avoid an empty `()` which is a syntax error.
- const inClause = ownedRepoIds.length > 0
- ? `(${ownedRepoIds.map(() => "?").join(",")})`
- : "(NULL)";
+ const inClause =
+ ownedRepoIds.length > 0 ? `(${ownedRepoIds.map(() => "?").join(",")})` : "(NULL)";
// Issues — authored OR on user's repos.
const issueRows = db
@@ -217,9 +213,7 @@ export function listActivityForWebId(
repo_name: string;
}[];
for (const row of runRows) {
- const issuePart = row.issue_number
- ? `#${row.issue_number}`
- : `repo`;
+ const issuePart = row.issue_number ? `#${row.issue_number}` : `repo`;
items.push({
kind: "agent.ran",
ts: row.created_at,
diff --git a/src/lib/registry/agent-runs.ts b/src/lib/registry/agent-runs.ts
index 9142756..9ff71e8 100644
--- a/src/lib/registry/agent-runs.ts
+++ b/src/lib/registry/agent-runs.ts
@@ -51,24 +51,16 @@ export function createAgentRun(input: {
(repo_id, issue_id, event_type, role, driver, status, summary, error_message, log_path, created_at)
VALUES (?, ?, ?, ?, ?, 'running', '', NULL, NULL, ?)`,
)
- .run(
- input.repoId,
- input.issueId,
- input.eventType,
- input.role,
- input.driver,
- Date.now(),
- );
+ .run(input.repoId, input.issueId, input.eventType, input.role, input.driver, Date.now());
const id = info.lastInsertRowid as number;
// Derive the log path from the id so multiple runs in flight at once
// can never collide on the file.
const logPath = `${id}.log`;
- getDb()
- .prepare("UPDATE agent_runs SET log_path = ? WHERE id = ?")
- .run(logPath, id);
- const row = getDb()
- .prepare("SELECT * FROM agent_runs WHERE id = ?")
- .get(id) as Record;
+ getDb().prepare("UPDATE agent_runs SET log_path = ? WHERE id = ?").run(logPath, id);
+ const row = getDb().prepare("SELECT * FROM agent_runs WHERE id = ?").get(id) as Record<
+ string,
+ unknown
+ >;
return rowToRun(row);
}
@@ -90,22 +82,17 @@ export function finishAgentRun(
SET status = ?, summary = ?, error_message = ?
WHERE id = ?`,
)
- .run(
- input.status,
- input.summary.slice(0, 4000),
- input.errorMessage ?? null,
- id,
- );
- const row = getDb()
- .prepare("SELECT * FROM agent_runs WHERE id = ?")
- .get(id) as Record | undefined;
+ .run(input.status, input.summary.slice(0, 4000), input.errorMessage ?? null, id);
+ const row = getDb().prepare("SELECT * FROM agent_runs WHERE id = ?").get(id) as
+ | Record
+ | undefined;
return row ? rowToRun(row) : null;
}
export function getAgentRun(id: number): AgentRun | null {
- const row = getDb()
- .prepare("SELECT * FROM agent_runs WHERE id = ?")
- .get(id) as Record | undefined;
+ const row = getDb().prepare("SELECT * FROM agent_runs WHERE id = ?").get(id) as
+ | Record
+ | undefined;
return row ? rowToRun(row) : null;
}
diff --git a/src/lib/registry/db.ts b/src/lib/registry/db.ts
index 4fd99d7..b50b226 100644
--- a/src/lib/registry/db.ts
+++ b/src/lib/registry/db.ts
@@ -1,16 +1,12 @@
import "server-only";
-import Database from "better-sqlite3";
-import { readdirSync, readFileSync, mkdirSync, existsSync } from "node:fs";
-import { join, dirname } from "node:path";
+import { existsSync, mkdirSync, readdirSync, readFileSync } from "node:fs";
+import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
+import Database from "better-sqlite3";
-const DATA_DIR =
- process.env.REGISTRY_DATA_DIR ?? join(process.cwd(), ".registry-data");
+const DATA_DIR = process.env.REGISTRY_DATA_DIR ?? join(process.cwd(), ".registry-data");
const DB_PATH = join(DATA_DIR, "registry.db");
-const MIGRATIONS_DIR = join(
- dirname(fileURLToPath(import.meta.url)),
- "migrations",
-);
+const MIGRATIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), "migrations");
const GLOBAL_KEY = "__mc_registry_db__";
@@ -73,9 +69,7 @@ function runMigrations(db: Database.Database) {
const sql = readFileSync(join(MIGRATIONS_DIR, file), "utf-8");
const tx = db.transaction(() => {
db.exec(sql);
- db.prepare(
- "INSERT INTO _migration (name, applied_at) VALUES (?, ?)",
- ).run(file, Date.now());
+ db.prepare("INSERT INTO _migration (name, applied_at) VALUES (?, ?)").run(file, Date.now());
});
tx();
console.log(`[registry] applied migration ${file}`);
diff --git a/src/lib/registry/identities.ts b/src/lib/registry/identities.ts
index 04cbeec..2d46f0f 100644
--- a/src/lib/registry/identities.ts
+++ b/src/lib/registry/identities.ts
@@ -1,8 +1,8 @@
import "server-only";
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
import type { IStorage } from "@inrupt/solid-client-authn-core";
-import { getDb } from "@/lib/registry/db";
import { getEnv } from "@/lib/env";
+import { getDb } from "@/lib/registry/db";
export type Identity = {
webId: string;
@@ -28,10 +28,7 @@ function encrypt(plaintext: string): string {
const key = getEnv().identityEncryptionKey;
const iv = randomBytes(12); // 96-bit IV for AES-GCM
const cipher = createCipheriv("aes-256-gcm", key, iv);
- const enc = Buffer.concat([
- cipher.update(plaintext, "utf-8"),
- cipher.final(),
- ]);
+ const enc = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
const tag = cipher.getAuthTag();
return `${CIPHER_VERSION}:${iv.toString("base64")}:${tag.toString("base64")}:${enc.toString("base64")}`;
}
@@ -49,11 +46,7 @@ function decrypt(stored: string): string | null {
const iv = Buffer.from(parts[1], "base64");
const tag = Buffer.from(parts[2], "base64");
const data = Buffer.from(parts[3], "base64");
- const decipher = createDecipheriv(
- "aes-256-gcm",
- getEnv().identityEncryptionKey,
- iv,
- );
+ const decipher = createDecipheriv("aes-256-gcm", getEnv().identityEncryptionKey, iv);
decipher.setAuthTag(tag);
const out = Buffer.concat([decipher.update(data), decipher.final()]);
return out.toString("utf-8");
@@ -72,23 +65,17 @@ function decrypt(stored: string): string | null {
*/
export function makeIdentityStorage(sessionId: string): IStorage {
const db = getDb();
- const getStmt = db.prepare(
- "SELECT value FROM identity_storage WHERE session_id = ? AND key = ?",
- );
+ const getStmt = db.prepare("SELECT value FROM identity_storage WHERE session_id = ? AND key = ?");
const setStmt = db.prepare(
`INSERT INTO identity_storage (session_id, key, value)
VALUES (?, ?, ?)
ON CONFLICT(session_id, key) DO UPDATE SET value = excluded.value`,
);
- const delStmt = db.prepare(
- "DELETE FROM identity_storage WHERE session_id = ? AND key = ?",
- );
+ const delStmt = db.prepare("DELETE FROM identity_storage WHERE session_id = ? AND key = ?");
return {
async get(key: string): Promise {
- const row = getStmt.get(sessionId, key) as
- | { value: string }
- | undefined;
+ const row = getStmt.get(sessionId, key) as { value: string } | undefined;
if (!row) return undefined;
const plain = decrypt(row.value);
return plain ?? undefined;
@@ -103,9 +90,7 @@ export function makeIdentityStorage(sessionId: string): IStorage {
// overwriting. Safe because the SDK never relies on field *removal*
// via setForUser — explicit deletes go through `delete(key)`.
if (key.startsWith("solidClientAuthenticationUser:")) {
- const existing = getStmt.get(sessionId, key) as
- | { value: string }
- | undefined;
+ const existing = getStmt.get(sessionId, key) as { value: string } | undefined;
if (existing) {
const decrypted = decrypt(existing.value);
if (decrypted) {
@@ -144,9 +129,7 @@ export function saveIdentity(input: {
.prepare("SELECT session_id FROM identities WHERE web_id = ?")
.get(input.webId) as { session_id: string } | undefined;
if (existing && existing.session_id !== input.sessionId) {
- db.prepare("DELETE FROM identity_storage WHERE session_id = ?").run(
- existing.session_id,
- );
+ db.prepare("DELETE FROM identity_storage WHERE session_id = ?").run(existing.session_id);
}
db.prepare(
`INSERT INTO identities (web_id, session_id, oidc_issuer, connected_at)
@@ -206,9 +189,7 @@ export function deleteIdentity(webId: string): boolean {
const db = getDb();
const existing = getIdentityByWebId(webId);
if (!existing) return false;
- db.prepare("DELETE FROM identity_storage WHERE session_id = ?").run(
- existing.sessionId,
- );
+ db.prepare("DELETE FROM identity_storage WHERE session_id = ?").run(existing.sessionId);
const info = db.prepare("DELETE FROM identities WHERE web_id = ?").run(webId);
return info.changes > 0;
}
diff --git a/src/lib/registry/issue-projection.ts b/src/lib/registry/issue-projection.ts
index 3933623..75d152f 100644
--- a/src/lib/registry/issue-projection.ts
+++ b/src/lib/registry/issue-projection.ts
@@ -1,8 +1,8 @@
import "server-only";
import { getDb } from "@/lib/registry/db";
-import type { Tracker } from "@/lib/tracker/model";
-import { trackerContainerUrl } from "@/lib/solid/tracker-pod";
import type { Repo } from "@/lib/registry/repos";
+import { trackerContainerUrl } from "@/lib/solid/tracker-pod";
+import type { Tracker } from "@/lib/tracker/model";
/**
* Project a repo's `.mind`-derived `flow:Tracker` (read from the pod) into the
@@ -21,10 +21,7 @@ import type { Repo } from "@/lib/registry/repos";
* (`{container}state.ttl#{id}`), the canonical resource a pod-native reader
* (mind-issues, the SolidOS issue-pane) would dereference.
*/
-export function projectTrackerToRegistry(
- repo: Repo,
- tracker: Tracker,
-): { upserted: number } {
+export function projectTrackerToRegistry(repo: Repo, tracker: Tracker): { upserted: number } {
const stateDoc = `${trackerContainerUrl(repo)}state.ttl`;
const db = getDb();
const now = Date.now();
diff --git a/src/lib/registry/issues.ts b/src/lib/registry/issues.ts
index a0c45db..6b41f66 100644
--- a/src/lib/registry/issues.ts
+++ b/src/lib/registry/issues.ts
@@ -78,10 +78,7 @@ function normaliseLabels(input: unknown): string[] {
const norm = raw.trim().toLowerCase().replace(/\s+/g, "-");
if (!norm) continue;
if (!/^[a-z0-9][a-z0-9._-]{0,31}$/.test(norm)) {
- throw new RegistryError(
- `invalid label ${JSON.stringify(raw)}`,
- "INVALID_INPUT",
- );
+ throw new RegistryError(`invalid label ${JSON.stringify(raw)}`, "INVALID_INPUT");
}
if (!seen.has(norm)) {
seen.add(norm);
@@ -122,9 +119,7 @@ export function createIssue(input: {
const tx = db.transaction(() => {
const next = db
- .prepare(
- "SELECT COALESCE(MAX(number), 0) + 1 AS n FROM issues WHERE repo_id = ?",
- )
+ .prepare("SELECT COALESCE(MAX(number), 0) + 1 AS n FROM issues WHERE repo_id = ?")
.get(input.repoId) as { n: number };
const info = db
@@ -154,9 +149,9 @@ export function createIssue(input: {
}
export function getIssueById(id: number): Issue | null {
- const row = getDb()
- .prepare("SELECT * FROM issues WHERE id = ?")
- .get(id) as Record | undefined;
+ const row = getDb().prepare("SELECT * FROM issues WHERE id = ?").get(id) as
+ | Record
+ | undefined;
return row ? rowToIssue(row) : null;
}
@@ -195,9 +190,7 @@ export function countIssuesByStatus(repoId: number): {
closed: number;
} {
const rows = getDb()
- .prepare(
- "SELECT status, COUNT(*) AS n FROM issues WHERE repo_id = ? GROUP BY status",
- )
+ .prepare("SELECT status, COUNT(*) AS n FROM issues WHERE repo_id = ? GROUP BY status")
.all(repoId) as { status: string; n: number }[];
const out = { open: 0, closed: 0 };
for (const r of rows) {
@@ -303,18 +296,16 @@ export function addComment(input: {
now,
);
- db.prepare("UPDATE issues SET updated_at = ? WHERE id = ?").run(
- now,
- input.issueId,
- );
+ db.prepare("UPDATE issues SET updated_at = ? WHERE id = ?").run(now, input.issueId);
return info.lastInsertRowid as number;
});
const id = tx();
- const row = db
- .prepare("SELECT * FROM issue_comments WHERE id = ?")
- .get(id) as Record;
+ const row = db.prepare("SELECT * FROM issue_comments WHERE id = ?").get(id) as Record<
+ string,
+ unknown
+ >;
return rowToComment(row);
}
@@ -324,15 +315,11 @@ export function addComment(input: {
* number (from the row insert) before it can compute the pod URL.
*/
export function setIssuePodUrl(id: number, podUrl: string): void {
- getDb()
- .prepare("UPDATE issues SET pod_url = ? WHERE id = ?")
- .run(podUrl, id);
+ getDb().prepare("UPDATE issues SET pod_url = ? WHERE id = ?").run(podUrl, id);
}
export function setCommentPodUrl(id: number, podUrl: string): void {
- getDb()
- .prepare("UPDATE issue_comments SET pod_url = ? WHERE id = ?")
- .run(podUrl, id);
+ getDb().prepare("UPDATE issue_comments SET pod_url = ? WHERE id = ?").run(podUrl, id);
}
export function countComments(issueId: number): number {
@@ -344,9 +331,7 @@ export function countComments(issueId: number): number {
export function listComments(issueId: number): IssueComment[] {
const rows = getDb()
- .prepare(
- "SELECT * FROM issue_comments WHERE issue_id = ? ORDER BY created_at ASC",
- )
+ .prepare("SELECT * FROM issue_comments WHERE issue_id = ? ORDER BY created_at ASC")
.all(issueId) as Record[];
return rows.map(rowToComment);
}
diff --git a/src/lib/registry/pulls.ts b/src/lib/registry/pulls.ts
index 313d386..e8752c7 100644
--- a/src/lib/registry/pulls.ts
+++ b/src/lib/registry/pulls.ts
@@ -84,11 +84,9 @@ export function upsertPullRequest(input: {
`SELECT * FROM pull_requests
WHERE repo_id = ? AND source_branch = ? AND target_branch = ? AND status = 'open'`,
)
- .get(
- input.repoId,
- input.sourceBranch,
- input.targetBranch,
- ) as Record | undefined;
+ .get(input.repoId, input.sourceBranch, input.targetBranch) as
+ | Record
+ | undefined;
if (existing) {
db.prepare(
`UPDATE pull_requests
@@ -115,9 +113,7 @@ export function upsertPullRequest(input: {
const number = (
db
- .prepare(
- "SELECT COALESCE(MAX(number), 0) + 1 AS n FROM pull_requests WHERE repo_id = ?",
- )
+ .prepare("SELECT COALESCE(MAX(number), 0) + 1 AS n FROM pull_requests WHERE repo_id = ?")
.get(input.repoId) as { n: number }
).n;
@@ -149,22 +145,14 @@ export function upsertPullRequest(input: {
return rowToPull(row);
}
-export function getPullRequest(
- repoId: number,
- number: number,
-): PullRequest | null {
+export function getPullRequest(repoId: number, number: number): PullRequest | null {
const row = getDb()
- .prepare(
- "SELECT * FROM pull_requests WHERE repo_id = ? AND number = ?",
- )
+ .prepare("SELECT * FROM pull_requests WHERE repo_id = ? AND number = ?")
.get(repoId, number) as Record | undefined;
return row ? rowToPull(row) : null;
}
-export function listPullRequests(
- repoId: number,
- status?: PullStatus | "all",
-): PullRequest[] {
+export function listPullRequests(repoId: number, status?: PullStatus | "all"): PullRequest[] {
const filter = status && status !== "all" ? status : null;
const rows = filter
? (getDb()
@@ -186,16 +174,16 @@ export function listPullRequests(
export function countOpenPullRequests(repoId: number): number {
const row = getDb()
- .prepare(
- "SELECT COUNT(*) AS n FROM pull_requests WHERE repo_id = ? AND status = 'open'",
- )
+ .prepare("SELECT COUNT(*) AS n FROM pull_requests WHERE repo_id = ? AND status = 'open'")
.get(repoId) as { n: number };
return row.n;
}
-export function countPullRequestsByStatus(
- repoId: number,
-): { open: number; merged: number; closed: number } {
+export function countPullRequestsByStatus(repoId: number): {
+ open: number;
+ merged: number;
+ closed: number;
+} {
const rows = getDb()
.prepare(
`SELECT status, COUNT(*) AS n FROM pull_requests
@@ -219,10 +207,7 @@ export function listPullRequestsForIssue(issueId: number): PullRequest[] {
return rows.map(rowToPull);
}
-export function markPullRequestMerged(
- id: number,
- mergeSha: string,
-): PullRequest {
+export function markPullRequestMerged(id: number, mergeSha: string): PullRequest {
const now = Date.now();
const db = getDb();
const info = db
@@ -235,9 +220,10 @@ export function markPullRequestMerged(
if (info.changes === 0) {
throw new RegistryError("pull request is not open", "INVALID_INPUT");
}
- const row = db
- .prepare("SELECT * FROM pull_requests WHERE id = ?")
- .get(id) as Record;
+ const row = db.prepare("SELECT * FROM pull_requests WHERE id = ?").get(id) as Record<
+ string,
+ unknown
+ >;
return rowToPull(row);
}
@@ -299,8 +285,9 @@ export function closePullRequest(id: number): PullRequest {
if (info.changes === 0) {
throw new RegistryError("pull request is not open", "INVALID_INPUT");
}
- const row = db
- .prepare("SELECT * FROM pull_requests WHERE id = ?")
- .get(id) as Record;
+ const row = db.prepare("SELECT * FROM pull_requests WHERE id = ?").get(id) as Record<
+ string,
+ unknown
+ >;
return rowToPull(row);
}
diff --git a/src/lib/registry/quotas.ts b/src/lib/registry/quotas.ts
index 362f7ec..61ef205 100644
--- a/src/lib/registry/quotas.ts
+++ b/src/lib/registry/quotas.ts
@@ -37,10 +37,7 @@ export const QUOTAS = {
maxReposPerOwner: envInt("MAX_REPOS_PER_OWNER", 50),
maxTokensPerRepo: envInt("MAX_TOKENS_PER_REPO", 10),
maxRunsPerOwnerPerDay: envInt("MAX_RUNS_PER_OWNER_PER_DAY", 500),
- maxDiskPerRepoBytes: envInt(
- "MAX_DISK_PER_REPO_BYTES",
- 1024 * 1024 * 1024,
- ),
+ maxDiskPerRepoBytes: envInt("MAX_DISK_PER_REPO_BYTES", 1024 * 1024 * 1024),
// Mind Packages (docs/PACKAGES-PLAN.md). Package blobs live in the pod, so
// these guard the bridge's own ingest path, not local disk:
// • a single artifact larger than this is refused outright (default
@@ -49,10 +46,7 @@ export const QUOTAS = {
// • the sum of a repo's published blob sizes is capped separately from
// git disk so a few large images can't fill the pod (default 2 GiB)
maxPackageBlobBytes: envInt("MAX_PACKAGE_BLOB_BYTES", 100 * 1024 * 1024),
- maxPackageBytesPerRepo: envInt(
- "MAX_PACKAGE_BYTES_PER_REPO",
- 2 * 1024 * 1024 * 1024,
- ),
+ maxPackageBytesPerRepo: envInt("MAX_PACKAGE_BYTES_PER_REPO", 2 * 1024 * 1024 * 1024),
};
export class QuotaExceededError extends Error {
@@ -61,27 +55,21 @@ export class QuotaExceededError extends Error {
public readonly limit: number,
public readonly observed: number,
) {
- super(
- `quota exceeded: ${quota} (observed ${observed} >= limit ${limit})`,
- );
+ super(`quota exceeded: ${quota} (observed ${observed} >= limit ${limit})`);
}
}
export function countReposForOwner(owner: string): number {
- const row = getDb()
- .prepare("SELECT COUNT(*) AS c FROM repos WHERE owner = ?")
- .get(owner) as { c: number };
+ const row = getDb().prepare("SELECT COUNT(*) AS c FROM repos WHERE owner = ?").get(owner) as {
+ c: number;
+ };
return row.c;
}
export function assertCanCreateRepo(owner: string): void {
const observed = countReposForOwner(owner);
if (observed >= QUOTAS.maxReposPerOwner) {
- throw new QuotaExceededError(
- "maxReposPerOwner",
- QUOTAS.maxReposPerOwner,
- observed,
- );
+ throw new QuotaExceededError("maxReposPerOwner", QUOTAS.maxReposPerOwner, observed);
}
}
@@ -95,11 +83,7 @@ export function countTokensForRepo(repoId: number): number {
export function assertCanMintToken(repoId: number): void {
const observed = countTokensForRepo(repoId);
if (observed >= QUOTAS.maxTokensPerRepo) {
- throw new QuotaExceededError(
- "maxTokensPerRepo",
- QUOTAS.maxTokensPerRepo,
- observed,
- );
+ throw new QuotaExceededError("maxTokensPerRepo", QUOTAS.maxTokensPerRepo, observed);
}
}
@@ -130,11 +114,7 @@ export function countRunsForOwnerPast24h(owner: string): number {
export function assertCanDispatchRun(owner: string): void {
const observed = countRunsForOwnerPast24h(owner);
if (observed >= QUOTAS.maxRunsPerOwnerPerDay) {
- throw new QuotaExceededError(
- "maxRunsPerOwnerPerDay",
- QUOTAS.maxRunsPerOwnerPerDay,
- observed,
- );
+ throw new QuotaExceededError("maxRunsPerOwnerPerDay", QUOTAS.maxRunsPerOwnerPerDay, observed);
}
}
@@ -155,11 +135,7 @@ export function sumPackageBytesForRepo(repoId: number): number {
*/
export function assertCanStorePackage(repoId: number, addBytes: number): void {
if (addBytes > QUOTAS.maxPackageBlobBytes) {
- throw new QuotaExceededError(
- "maxPackageBlobBytes",
- QUOTAS.maxPackageBlobBytes,
- addBytes,
- );
+ throw new QuotaExceededError("maxPackageBlobBytes", QUOTAS.maxPackageBlobBytes, addBytes);
}
const observed = sumPackageBytesForRepo(repoId);
if (observed + addBytes > QUOTAS.maxPackageBytesPerRepo) {
diff --git a/src/lib/registry/repos.ts b/src/lib/registry/repos.ts
index 743ed9f..d68ebc2 100644
--- a/src/lib/registry/repos.ts
+++ b/src/lib/registry/repos.ts
@@ -38,11 +38,7 @@ const NAME_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/i;
export class RegistryError extends Error {
constructor(
message: string,
- public readonly code:
- | "INVALID_NAME"
- | "ALREADY_EXISTS"
- | "NOT_FOUND"
- | "INVALID_INPUT",
+ public readonly code: "INVALID_NAME" | "ALREADY_EXISTS" | "NOT_FOUND" | "INVALID_INPUT",
) {
super(message);
}
@@ -50,10 +46,7 @@ export class RegistryError extends Error {
export function validateName(value: string, field: "owner" | "repo"): void {
if (typeof value !== "string" || !NAME_RE.test(value) || value.includes("..")) {
- throw new RegistryError(
- `Invalid ${field} name: ${JSON.stringify(value)}`,
- "INVALID_NAME",
- );
+ throw new RegistryError(`Invalid ${field} name: ${JSON.stringify(value)}`, "INVALID_NAME");
}
}
@@ -122,10 +115,7 @@ export function createRepo(input: {
"code" in e &&
(e as { code: string }).code === "SQLITE_CONSTRAINT_UNIQUE"
) {
- throw new RegistryError(
- `Repo ${input.owner}/${input.name} already exists`,
- "ALREADY_EXISTS",
- );
+ throw new RegistryError(`Repo ${input.owner}/${input.name} already exists`, "ALREADY_EXISTS");
}
throw e;
}
@@ -152,9 +142,9 @@ function rowToRepo(row: Record): Repo {
}
export function getRepoById(id: number): Repo | null {
- const row = getDb()
- .prepare("SELECT * FROM repos WHERE id = ?")
- .get(id) as Record | undefined;
+ const row = getDb().prepare("SELECT * FROM repos WHERE id = ?").get(id) as
+ | Record
+ | undefined;
return row ? rowToRepo(row) : null;
}
@@ -169,18 +159,17 @@ export function getRepo(owner: string, name: string): Repo | null {
export function listRepos(): Repo[] {
return (
- getDb()
- .prepare("SELECT * FROM repos ORDER BY created_at DESC")
- .all() as Record[]
+ getDb().prepare("SELECT * FROM repos ORDER BY created_at DESC").all() as Record<
+ string,
+ unknown
+ >[]
).map(rowToRepo);
}
export function updateRepo(
owner: string,
name: string,
- patch: Partial<
- Pick
- >,
+ patch: Partial>,
): Repo {
const repo = getRepo(owner, name);
if (!repo) throw new RegistryError("repo not found", "NOT_FOUND");
@@ -220,7 +209,9 @@ export function updateRepo(
if (fields.length === 0) return repo;
values.push(repo.id);
- getDb().prepare(`UPDATE repos SET ${fields.join(", ")} WHERE id = ?`).run(...values);
+ getDb()
+ .prepare(`UPDATE repos SET ${fields.join(", ")} WHERE id = ?`)
+ .run(...values);
return getRepoById(repo.id)!;
}
@@ -234,9 +225,7 @@ function rowToPages(row: Record): PagesConfig {
targetContainer: row.target_container as string,
lastPublishedAt: (row.last_published_at as number | null) ?? null,
lastPublishStatus:
- status === "success" || status === "failed" || status === "needs-reauth"
- ? status
- : null,
+ status === "success" || status === "failed" || status === "needs-reauth" ? status : null,
lastPublishError: (row.last_publish_error as string | null) ?? null,
lastPublishAttempt: (row.last_publish_attempt as number | null) ?? null,
lastPublishedSha: (row.last_published_sha as string | null) ?? null,
@@ -244,9 +233,9 @@ function rowToPages(row: Record): PagesConfig {
}
export function getPagesConfig(repoId: number): PagesConfig | null {
- const row = getDb()
- .prepare("SELECT * FROM pages_configs WHERE repo_id = ?")
- .get(repoId) as Record | undefined;
+ const row = getDb().prepare("SELECT * FROM pages_configs WHERE repo_id = ?").get(repoId) as
+ | Record
+ | undefined;
return row ? rowToPages(row) : null;
}
@@ -262,10 +251,7 @@ export function updatePagesConfig(
patch.targetContainer !== "" &&
!patch.targetContainer.startsWith("http")
) {
- throw new RegistryError(
- "targetContainer must be http(s) URL or empty",
- "INVALID_INPUT",
- );
+ throw new RegistryError("targetContainer must be http(s) URL or empty", "INVALID_INPUT");
}
if (
patch.sourcePath !== undefined &&
@@ -294,13 +280,7 @@ export function updatePagesConfig(
SET enabled = ?, source_branch = ?, source_path = ?, target_container = ?
WHERE repo_id = ?`,
)
- .run(
- next.enabled ? 1 : 0,
- next.sourceBranch,
- next.sourcePath,
- next.targetContainer,
- repoId,
- );
+ .run(next.enabled ? 1 : 0, next.sourceBranch, next.sourcePath, next.targetContainer, repoId);
return next;
}
diff --git a/src/lib/registry/runs.ts b/src/lib/registry/runs.ts
index d83f6e1..659398d 100644
--- a/src/lib/registry/runs.ts
+++ b/src/lib/registry/runs.ts
@@ -1,12 +1,7 @@
import "server-only";
import { getDb } from "@/lib/registry/db";
-export type RunStatus =
- | "queued"
- | "running"
- | "success"
- | "failed"
- | "error";
+export type RunStatus = "queued" | "running" | "success" | "failed" | "error";
export type WorkflowRun = {
id: number;
@@ -48,9 +43,9 @@ export function createRun(repoId: number, ref: string): WorkflowRun {
}
export function getRunById(id: number): WorkflowRun | null {
- const row = getDb()
- .prepare("SELECT * FROM workflow_runs WHERE id = ?")
- .get(id) as Record | undefined;
+ const row = getDb().prepare("SELECT * FROM workflow_runs WHERE id = ?").get(id) as
+ | Record
+ | undefined;
return row ? rowToRun(row) : null;
}
@@ -66,18 +61,14 @@ export function getLatestRunForRepo(repoId: number): WorkflowRun | null {
/** Total workflow runs across all repos. Used by the landing-page tile. */
export function countAllRuns(): number {
- const row = getDb()
- .prepare("SELECT COUNT(*) AS c FROM workflow_runs")
- .get() as { c: number };
+ const row = getDb().prepare("SELECT COUNT(*) AS c FROM workflow_runs").get() as { c: number };
return row.c;
}
/** Most recent run across every repo, or null if nothing has run yet. */
export function getLatestRunOverall(): WorkflowRun | null {
const row = getDb()
- .prepare(
- `SELECT * FROM workflow_runs ORDER BY started_at DESC LIMIT 1`,
- )
+ .prepare(`SELECT * FROM workflow_runs ORDER BY started_at DESC LIMIT 1`)
.get() as Record | undefined;
return row ? rowToRun(row) : null;
}
@@ -94,9 +85,7 @@ export function listRunsForRepo(repoId: number, limit = 20): WorkflowRun[] {
}
export function markRunRunning(id: number): void {
- getDb()
- .prepare("UPDATE workflow_runs SET status='running' WHERE id = ?")
- .run(id);
+ getDb().prepare("UPDATE workflow_runs SET status='running' WHERE id = ?").run(id);
}
export function finishRun(
@@ -115,22 +104,12 @@ export function finishRun(
SET status=?, exit_code=?, finished_at=?, log_tail=?, error_message=?
WHERE id = ?`,
)
- .run(
- input.status,
- input.exitCode,
- Date.now(),
- tail,
- input.errorMessage ?? null,
- id,
- );
+ .run(input.status, input.exitCode, Date.now(), tail, input.errorMessage ?? null, id);
}
function truncateTail(log: string): string {
if (log.length <= LOG_TAIL_CAP) return log;
- return (
- "… (truncated, showing last 64 KB) …\n" +
- log.slice(log.length - LOG_TAIL_CAP)
- );
+ return "… (truncated, showing last 64 KB) …\n" + log.slice(log.length - LOG_TAIL_CAP);
}
/**
diff --git a/src/lib/registry/tokens.ts b/src/lib/registry/tokens.ts
index 1e3a37e..b176b71 100644
--- a/src/lib/registry/tokens.ts
+++ b/src/lib/registry/tokens.ts
@@ -1,5 +1,5 @@
import "server-only";
-import { randomBytes, createHash } from "node:crypto";
+import { createHash, randomBytes } from "node:crypto";
import { getDb } from "@/lib/registry/db";
const TOKEN_PREFIX = "scp_";
@@ -65,9 +65,7 @@ export function verifyPushToken(repoId: number, plaintext: string): boolean {
if (!plaintext.startsWith(TOKEN_PREFIX)) return false;
const hash = sha256(plaintext);
const row = getDb()
- .prepare(
- "SELECT 1 AS ok FROM push_tokens WHERE repo_id = ? AND token_hash = ?",
- )
+ .prepare("SELECT 1 AS ok FROM push_tokens WHERE repo_id = ? AND token_hash = ?")
.get(repoId, hash);
return row !== undefined;
}
diff --git a/src/lib/registry/users.ts b/src/lib/registry/users.ts
index 6d4e259..ec3f9d8 100644
--- a/src/lib/registry/users.ts
+++ b/src/lib/registry/users.ts
@@ -71,15 +71,15 @@ export function createUser(input: {
}
export function getUserByWebId(webId: string): User | null {
- const row = getDb()
- .prepare("SELECT * FROM users WHERE web_id = ?")
- .get(webId) as Record | undefined;
+ const row = getDb().prepare("SELECT * FROM users WHERE web_id = ?").get(webId) as
+ | Record
+ | undefined;
return row ? rowToUser(row) : null;
}
export function getUserBySlug(slug: string): User | null {
- const row = getDb()
- .prepare("SELECT * FROM users WHERE owner_slug = ?")
- .get(slug) as Record | undefined;
+ const row = getDb().prepare("SELECT * FROM users WHERE owner_slug = ?").get(slug) as
+ | Record
+ | undefined;
return row ? rowToUser(row) : null;
}
diff --git a/src/lib/solid/auth.ts b/src/lib/solid/auth.ts
index 9a435aa..155bb65 100644
--- a/src/lib/solid/auth.ts
+++ b/src/lib/solid/auth.ts
@@ -29,31 +29,19 @@ export async function getCssAuthedFetch(input: {
const { controls } = await discoverAccountControls(cssBaseUrl);
const loginUrl = controls.password?.login;
if (!loginUrl) {
- throw new Error(
- `CSS at ${cssBaseUrl} did not advertise a password-login endpoint.`,
- );
+ throw new Error(`CSS at ${cssBaseUrl} did not advertise a password-login endpoint.`);
}
const { authorization } = await loginAccount(loginUrl, email, password);
- const { controls: postLoginControls } = await discoverAccountControls(
- cssBaseUrl,
- authorization,
- );
+ const { controls: postLoginControls } = await discoverAccountControls(cssBaseUrl, authorization);
const credsUrl = postLoginControls.account?.clientCredentials;
if (!credsUrl) {
- throw new Error(
- `CSS at ${cssBaseUrl} did not advertise clientCredentials after login.`,
- );
+ throw new Error(`CSS at ${cssBaseUrl} did not advertise clientCredentials after login.`);
}
const credName = `mind-codespaces-publisher-${Date.now()}`;
- const { id, secret } = await createClientCredentials(
- credsUrl,
- authorization,
- credName,
- webId,
- );
+ const { id, secret } = await createClientCredentials(credsUrl, authorization, credName, webId);
const session = new Session();
await session.login({
@@ -71,10 +59,7 @@ export async function getCssAuthedFetch(input: {
};
}
-async function discoverAccountControls(
- cssBaseUrl: string,
- authorization?: string,
-) {
+async function discoverAccountControls(cssBaseUrl: string, authorization?: string) {
const headers: Record = { Accept: "application/json" };
if (authorization) {
headers.Authorization = `CSS-Account-Token ${authorization}`;
diff --git a/src/lib/solid/containers.ts b/src/lib/solid/containers.ts
index 8608042..e970954 100644
--- a/src/lib/solid/containers.ts
+++ b/src/lib/solid/containers.ts
@@ -14,16 +14,11 @@ import "server-only";
* Ensure a container exists at `url`. Returns `true` if we created it,
* `false` if it already existed. Throws on any other error.
*/
-export async function ensureContainer(
- fetcher: typeof fetch,
- url: string,
-): Promise {
+export async function ensureContainer(fetcher: typeof fetch, url: string): Promise {
const head = await fetcher(url, { method: "HEAD" });
if (head.ok) return false;
if (head.status !== 404) {
- throw new Error(
- `unexpected response checking container ${url}: ${head.status}`,
- );
+ throw new Error(`unexpected response checking container ${url}: ${head.status}`);
}
const put = await fetcher(url, {
method: "PUT",
@@ -33,9 +28,7 @@ export async function ensureContainer(
},
});
if (!put.ok && put.status !== 409 /* already exists */) {
- throw new Error(
- `failed to create container ${url}: ${put.status} ${put.statusText}`,
- );
+ throw new Error(`failed to create container ${url}: ${put.status} ${put.statusText}`);
}
return true;
}
@@ -75,9 +68,7 @@ export async function setPublicReadAcl(
body,
});
if (!res.ok) {
- throw new Error(
- `failed to set public-read ACL on ${aclUrl}: ${res.status} ${res.statusText}`,
- );
+ throw new Error(`failed to set public-read ACL on ${aclUrl}: ${res.status} ${res.statusText}`);
}
}
@@ -112,9 +103,7 @@ export async function setOwnerOnlyAcl(
body,
});
if (!res.ok) {
- throw new Error(
- `failed to set owner-only ACL on ${aclUrl}: ${res.status} ${res.statusText}`,
- );
+ throw new Error(`failed to set owner-only ACL on ${aclUrl}: ${res.status} ${res.statusText}`);
}
}
@@ -166,9 +155,7 @@ ${memberRules}`;
body,
});
if (!res.ok) {
- throw new Error(
- `failed to set member-read ACL on ${aclUrl}: ${res.status} ${res.statusText}`,
- );
+ throw new Error(`failed to set member-read ACL on ${aclUrl}: ${res.status} ${res.statusText}`);
}
}
@@ -187,8 +174,15 @@ function isSafeAclIri(value: string): boolean {
// the ACL `<...>`: < > " { } | backslash ^ ` (backtick).
if (c <= 0x20) return false;
if (
- c === 0x3c || c === 0x3e || c === 0x22 || c === 0x7b || c === 0x7d ||
- c === 0x7c || c === 0x5c || c === 0x5e || c === 0x60
+ c === 0x3c ||
+ c === 0x3e ||
+ c === 0x22 ||
+ c === 0x7b ||
+ c === 0x7d ||
+ c === 0x7c ||
+ c === 0x5c ||
+ c === 0x5e ||
+ c === 0x60
)
return false;
}
@@ -272,8 +266,6 @@ ${publicRule}`;
body,
});
if (!res.ok) {
- throw new Error(
- `failed to set inbox ACL on ${aclUrl}: ${res.status} ${res.statusText}`,
- );
+ throw new Error(`failed to set inbox ACL on ${aclUrl}: ${res.status} ${res.statusText}`);
}
}
diff --git a/src/lib/solid/css-account.ts b/src/lib/solid/css-account.ts
index 47ef5a3..2fbc203 100644
--- a/src/lib/solid/css-account.ts
+++ b/src/lib/solid/css-account.ts
@@ -73,11 +73,8 @@ async function reqJson(
});
// The Fetch API exposes Set-Cookie via getSetCookie() in Node 20+.
const setCookies =
- typeof (res.headers as unknown as { getSetCookie?: () => string[] })
- .getSetCookie === "function"
- ? (
- res.headers as unknown as { getSetCookie: () => string[] }
- ).getSetCookie()
+ typeof (res.headers as unknown as { getSetCookie?: () => string[] }).getSetCookie === "function"
+ ? (res.headers as unknown as { getSetCookie: () => string[] }).getSetCookie()
: [];
mergeSetCookies(jar, setCookies);
const location = res.headers.get("location");
@@ -104,11 +101,8 @@ async function reqRaw(
redirect: "manual",
});
const setCookies =
- typeof (res.headers as unknown as { getSetCookie?: () => string[] })
- .getSetCookie === "function"
- ? (
- res.headers as unknown as { getSetCookie: () => string[] }
- ).getSetCookie()
+ typeof (res.headers as unknown as { getSetCookie?: () => string[] }).getSetCookie === "function"
+ ? (res.headers as unknown as { getSetCookie: () => string[] }).getSetCookie()
: [];
mergeSetCookies(jar, setCookies);
return {
@@ -141,16 +135,9 @@ export async function runPasswordLoginOidcFlow(input: {
const issuer = new URL(input.oidcRedirectUrl).origin;
const accountIndex = await reqJson(`${issuer}/.account/`, jar);
if (accountIndex.status !== 200 || !accountIndex.body) {
- throw new HttpError(
- "could not read CSS account controls",
- accountIndex.status,
- );
+ throw new HttpError("could not read CSS account controls", accountIndex.status);
}
- const passwordLogin = pickStringPath(accountIndex.body, [
- "controls",
- "password",
- "login",
- ]);
+ const passwordLogin = pickStringPath(accountIndex.body, ["controls", "password", "login"]);
if (!passwordLogin) {
throw new HttpError("CSS account did not advertise password login", 500);
}
@@ -173,24 +160,13 @@ export async function runPasswordLoginOidcFlow(input: {
// Expect a 303 to /.account/ — that's CSS asking the client to drive
// the pick-webid / consent prompts via the account API.
if (oidc.status !== 303 || !oidc.location) {
- throw new HttpError(
- `unexpected OIDC start response (HTTP ${oidc.status})`,
- oidc.status,
- );
+ throw new HttpError(`unexpected OIDC start response (HTTP ${oidc.status})`, oidc.status);
}
// Step 4: refresh account view — now the `oidc.*` controls exist.
const accountMid = await reqJson(`${issuer}/.account/`, jar);
- const pickWebIdUrl = pickStringPath(accountMid.body ?? {}, [
- "controls",
- "oidc",
- "webId",
- ]);
- const consentUrl = pickStringPath(accountMid.body ?? {}, [
- "controls",
- "oidc",
- "consent",
- ]);
+ const pickWebIdUrl = pickStringPath(accountMid.body ?? {}, ["controls", "oidc", "webId"]);
+ const consentUrl = pickStringPath(accountMid.body ?? {}, ["controls", "oidc", "consent"]);
if (!pickWebIdUrl || !consentUrl) {
throw new HttpError(
"CSS account did not advertise OIDC controls — is an interaction active?",
@@ -199,24 +175,15 @@ export async function runPasswordLoginOidcFlow(input: {
}
// Find a WebID linked to this account.
- const accountWebIdsUrl = pickStringPath(accountMid.body ?? {}, [
- "controls",
- "account",
- "webId",
- ]);
+ const accountWebIdsUrl = pickStringPath(accountMid.body ?? {}, ["controls", "account", "webId"]);
if (!accountWebIdsUrl) {
throw new HttpError("CSS did not expose account.webId control", 500);
}
const webIds = await reqJson(accountWebIdsUrl, jar);
- const links = webIds.body?.["webIdLinks"] as
- | Record
- | undefined;
+ const links = webIds.body?.["webIdLinks"] as Record | undefined;
const candidate = links ? Object.keys(links) : [];
if (candidate.length === 0) {
- throw new HttpError(
- "no WebID linked to this account — register a pod first",
- 400,
- );
+ throw new HttpError("no WebID linked to this account — register a pod first", 400);
}
// For the single-WebID-per-account case (the common path on CSS)
// we just pick it. Multi-WebID accounts could surface a UI later.
@@ -228,13 +195,9 @@ export async function runPasswordLoginOidcFlow(input: {
body: { webId },
});
if (pick.status >= 400) {
- throw new HttpError(
- stringField(pick.body, "message") ?? "pick-webid failed",
- pick.status,
- );
+ throw new HttpError(stringField(pick.body, "message") ?? "pick-webid failed", pick.status);
}
- const resumeAfterPick =
- stringField(pick.body, "location") ?? `${issuer}/.account/`;
+ const resumeAfterPick = stringField(pick.body, "location") ?? `${issuer}/.account/`;
// Step 6: resume the OIDC flow once — moves prompt from "login" to
// "consent". We follow the redirect manually so we capture cookies.
@@ -275,10 +238,7 @@ export async function runPasswordLoginOidcFlow(input: {
}
const resumeAfterConsent = stringField(consent.body, "location");
if (!resumeAfterConsent) {
- throw new HttpError(
- "OIDC consent did not return a resume URL",
- 500,
- );
+ throw new HttpError("OIDC consent did not return a resume URL", 500);
}
// Step 8: follow the final resume — CSS now 303s to the bridge's
@@ -286,18 +246,12 @@ export async function runPasswordLoginOidcFlow(input: {
// `completeAuthFlow` expects.
const final = await reqRaw(resumeAfterConsent, jar);
if (final.status !== 303 || !final.location) {
- throw new HttpError(
- `OIDC final resume did not redirect (HTTP ${final.status})`,
- final.status,
- );
+ throw new HttpError(`OIDC final resume did not redirect (HTTP ${final.status})`, final.status);
}
return { callbackUrl: final.location };
}
-function pickStringPath(
- obj: unknown,
- path: string[],
-): string | undefined {
+function pickStringPath(obj: unknown, path: string[]): string | undefined {
let cur: unknown = obj;
for (const k of path) {
if (!cur || typeof cur !== "object") return undefined;
@@ -306,10 +260,7 @@ function pickStringPath(
return typeof cur === "string" ? cur : undefined;
}
-function stringField(
- obj: Record | null,
- key: string,
-): string | undefined {
+function stringField(obj: Record | null, key: string): string | undefined {
if (!obj) return undefined;
const v = obj[key];
return typeof v === "string" ? v : undefined;
diff --git a/src/lib/solid/fetch-for-owner.ts b/src/lib/solid/fetch-for-owner.ts
index e67e640..93b1472 100644
--- a/src/lib/solid/fetch-for-owner.ts
+++ b/src/lib/solid/fetch-for-owner.ts
@@ -1,10 +1,7 @@
import "server-only";
-import { getCssAuthedFetch } from "@/lib/solid/auth";
-import {
- loadAuthedFetchForWebId,
- OidcRefreshFailedError,
-} from "@/lib/solid/oidc-server";
import { getEnv } from "@/lib/env";
+import { getCssAuthedFetch } from "@/lib/solid/auth";
+import { loadAuthedFetchForWebId, OidcRefreshFailedError } from "@/lib/solid/oidc-server";
export type OwnerFetch = {
fetch: typeof fetch;
@@ -19,9 +16,7 @@ export type OwnerFetch = {
*/
export class OwnerFetchUnavailableError extends Error {
constructor(
- public readonly reason:
- | "needs-reauthorization"
- | "no-identity-and-seeded-disabled",
+ public readonly reason: "needs-reauthorization" | "no-identity-and-seeded-disabled",
public readonly webId: string,
) {
super(
@@ -70,10 +65,7 @@ export async function getOwnerFetch(webId: string): Promise {
const env = getEnv();
if (env.isProd || !env.allowSeededFallback) {
- throw new OwnerFetchUnavailableError(
- "no-identity-and-seeded-disabled",
- webId,
- );
+ throw new OwnerFetchUnavailableError("no-identity-and-seeded-disabled", webId);
}
const seeded = await getCssAuthedFetch({
diff --git a/src/lib/solid/inbox.ts b/src/lib/solid/inbox.ts
index b272f24..549629e 100644
--- a/src/lib/solid/inbox.ts
+++ b/src/lib/solid/inbox.ts
@@ -1,16 +1,16 @@
import "server-only";
import { randomUUID } from "node:crypto";
import {
- getSolidDataset,
getContainedResourceUrlAll,
- getThing,
+ getDatetime,
+ getSolidDataset,
getStringNoLocale,
+ getThing,
getUrl,
- getDatetime,
} from "@inrupt/solid-client";
import type { Repo } from "@/lib/registry/repos";
-import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
import { ensureContainer, setInboxAcl } from "@/lib/solid/containers";
+import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
import { NS } from "@/lib/vocab";
/**
@@ -61,8 +61,7 @@ export type Proposal = {
createdAt: number | null;
};
-const UUID_RE =
- /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
+const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
/**
* Triple-quoted Turtle long string — keeps newlines literal. Every `"` is
@@ -129,10 +128,7 @@ export function renderProposalTurtle(input: ProposalInput): string {
}
/** Ensure the inbox container + its append-only ACL exist. Idempotent. */
-export async function ensureInbox(
- fetcher: typeof fetch,
- repo: Repo,
-): Promise {
+export async function ensureInbox(fetcher: typeof fetch, repo: Repo): Promise {
const url = inboxContainerUrl(repo);
const created = await ensureContainer(fetcher, url);
if (created) {
@@ -213,10 +209,7 @@ export async function listProposals(repo: Repo): Promise {
contact: getStringNoLocale(thing, `${NS.solidgit}contact`),
createdAt: getDatetime(thing, `${NS.dcterms}created`)?.getTime() ?? null,
});
- } catch {
- // A malformed member shouldn't sink the whole listing.
- continue;
- }
+ } catch {}
}
out.sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0));
return out;
@@ -230,10 +223,7 @@ export async function listProposals(repo: Repo): Promise {
}
/** Read a single proposal by id (owner fetch), or null if absent. */
-export async function getProposal(
- repo: Repo,
- id: string,
-): Promise {
+export async function getProposal(repo: Repo, id: string): Promise {
if (!UUID_RE.test(id)) return null;
const all = await listProposals(repo);
return all.find((p) => p.id === id) ?? null;
diff --git a/src/lib/solid/issues.ts b/src/lib/solid/issues.ts
index a749834..2686a6e 100644
--- a/src/lib/solid/issues.ts
+++ b/src/lib/solid/issues.ts
@@ -1,8 +1,8 @@
import "server-only";
-import type { Repo } from "@/lib/registry/repos";
import type { Issue, IssueComment } from "@/lib/registry/issues";
-import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
+import type { Repo } from "@/lib/registry/repos";
import { ensureContainer, setPublicReadAcl } from "@/lib/solid/containers";
+import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
import { NS } from "@/lib/vocab";
/**
@@ -55,9 +55,7 @@ function isoDt(ms: number): string {
function renderIssueTurtle(repo: Repo, issue: Issue): string {
const repoUrl = `${trailingSlash(repo.ownerPodRoot)}codespaces/${repo.name}/index.ttl#repo`;
- const labels = issue.labels
- .map((l) => `"${l.replace(/"/g, '\\"')}"`)
- .join(", ");
+ const labels = issue.labels.map((l) => `"${l.replace(/"/g, '\\"')}"`).join(", ");
const lines = [
`@prefix solidgit: <${NS.solidgit}>.`,
@@ -87,11 +85,7 @@ function renderIssueTurtle(repo: Repo, issue: Issue): string {
return lines.join("\n");
}
-function renderCommentTurtle(
- repo: Repo,
- issueNumber: number,
- comment: IssueComment,
-): string {
+function renderCommentTurtle(repo: Repo, issueNumber: number, comment: IssueComment): string {
const parent = `${issueContainerUrl(repo, issueNumber)}issue.ttl#issue`;
return `@prefix solidgit: <${NS.solidgit}>.
@prefix dcterms: <${NS.dcterms}>.
diff --git a/src/lib/solid/members.ts b/src/lib/solid/members.ts
index a5aa03f..b596596 100644
--- a/src/lib/solid/members.ts
+++ b/src/lib/solid/members.ts
@@ -1,16 +1,8 @@
import "server-only";
-import {
- getSolidDataset,
- getThingAll,
- getUrl,
- getStringNoLocale,
-} from "@inrupt/solid-client";
+import { getSolidDataset, getStringNoLocale, getThingAll, getUrl } from "@inrupt/solid-client";
import type { Repo } from "@/lib/registry/repos";
+import { ensureContainer, setVisibilityAcl } from "@/lib/solid/containers";
import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
-import {
- ensureContainer,
- setVisibilityAcl,
-} from "@/lib/solid/containers";
import { NS } from "@/lib/vocab";
/**
@@ -77,8 +69,15 @@ export function isSafeWebId(value: string): boolean {
const c = value.charCodeAt(i);
if (c <= 0x20) return false;
if (
- c === 0x3c || c === 0x3e || c === 0x22 || c === 0x7b || c === 0x7d ||
- c === 0x7c || c === 0x5c || c === 0x5e || c === 0x60
+ c === 0x3c ||
+ c === 0x3e ||
+ c === 0x22 ||
+ c === 0x7b ||
+ c === 0x7d ||
+ c === 0x7c ||
+ c === 0x5c ||
+ c === 0x5e ||
+ c === 0x60
)
return false;
}
@@ -112,9 +111,7 @@ export function renderMembersTurtle(repo: Repo, members: Member[]): string {
];
if (safe.length > 0) {
lines[lines.length - 1] += " ;";
- lines.push(
- ` solidgit:hasMember ${safe.map((_, i) => `<#m${i}>`).join(", ")} .`,
- );
+ lines.push(` solidgit:hasMember ${safe.map((_, i) => `<#m${i}>`).join(", ")} .`);
} else {
lines[lines.length - 1] += " .";
}
@@ -135,18 +132,12 @@ export function renderMembersTurtle(repo: Repo, members: Member[]): string {
* thread the current members into a container's ACL without a second login.
* Empty on absence/transient error.
*/
-export async function readMembersWithFetch(
- fetcher: typeof fetch,
- repo: Repo,
-): Promise {
+export async function readMembersWithFetch(fetcher: typeof fetch, repo: Repo): Promise {
return fetchRoster(fetcher, repo);
}
/** Parse the roster from an already-fetched pod dataset URL. */
-async function fetchRoster(
- fetcher: typeof fetch,
- repo: Repo,
-): Promise {
+async function fetchRoster(fetcher: typeof fetch, repo: Repo): Promise {
let ds;
try {
ds = await getSolidDataset(membersUrl(repo), { fetch: fetcher });
@@ -168,11 +159,7 @@ async function fetchRoster(
}
/** PUT the roster document with the owner's authed fetch. */
-async function putRoster(
- fetcher: typeof fetch,
- repo: Repo,
- members: Member[],
-): Promise {
+async function putRoster(fetcher: typeof fetch, repo: Repo, members: Member[]): Promise {
const url = membersUrl(repo);
const res = await fetcher(url, {
method: "PUT",
@@ -227,10 +214,7 @@ export async function readMembers(repo: Repo): Promise {
* `admin` (no pod read needed — the common case). Anyone else is looked up in
* the roster; returns null if they are not a member.
*/
-export async function resolveMemberRole(
- repo: Repo,
- webId: string,
-): Promise {
+export async function resolveMemberRole(repo: Repo, webId: string): Promise {
if (webId === repo.ownerWebId) return "admin";
const roster = await readMembers(repo);
return roster.find((m) => m.webId === webId)?.role ?? null;
@@ -241,10 +225,7 @@ export async function resolveMemberRole(
* mirrors `ensureInbox`). Only writes when the roster does not already exist so
* an existing roster is never clobbered.
*/
-export async function ensureMembers(
- fetcher: typeof fetch,
- repo: Repo,
-): Promise {
+export async function ensureMembers(fetcher: typeof fetch, repo: Repo): Promise {
const head = await fetcher(membersUrl(repo), { method: "HEAD" });
if (head.ok) return; // already provisioned — leave the roster as-is
await putRoster(fetcher, repo, []);
@@ -256,11 +237,7 @@ export async function ensureMembers(
* Returns the updated roster. Owner-mediated: the bridge writes as the owner
* via the delegated fetch; the member never writes the owner's pod directly.
*/
-export async function addMember(
- repo: Repo,
- webId: string,
- role: MemberRole,
-): Promise {
+export async function addMember(repo: Repo, webId: string, role: MemberRole): Promise {
if (!isSafeWebId(webId)) {
throw new Error(`refusing to add unsafe WebID: ${JSON.stringify(webId)}`);
}
@@ -286,10 +263,7 @@ export async function addMember(
* single roster write + ACL rewrite — atomic, unlike spraying WAC grants).
* Returns the updated roster.
*/
-export async function removeMember(
- repo: Repo,
- webId: string,
-): Promise {
+export async function removeMember(repo: Repo, webId: string): Promise {
const authed = await getOwnerFetch(repo.ownerWebId);
try {
const roster = await fetchRoster(authed.fetch, repo);
diff --git a/src/lib/solid/oidc-server.ts b/src/lib/solid/oidc-server.ts
index f8e1f19..265e1f7 100644
--- a/src/lib/solid/oidc-server.ts
+++ b/src/lib/solid/oidc-server.ts
@@ -1,13 +1,9 @@
import "server-only";
import { randomUUID } from "node:crypto";
-import { Session, getSessionFromStorage } from "@inrupt/solid-client-authn-node";
-import {
- getIdentityByWebId,
- makeIdentityStorage,
- saveIdentity,
-} from "@/lib/registry/identities";
+import { getSessionFromStorage, Session } from "@inrupt/solid-client-authn-node";
import { getEnv } from "@/lib/env";
import { log, scrubWebId } from "@/lib/log";
+import { getIdentityByWebId, makeIdentityStorage, saveIdentity } from "@/lib/registry/identities";
const CLIENT_NAME = "Mind Codespaces";
@@ -114,14 +110,9 @@ export async function completeAuthFlow(input: {
}
// Find the issuer the SDK chose so we can record it. Prefer the
// session info, fall back to walking storage.
- const userRecord = await storage.get(
- `solidClientAuthenticationUser:${input.sessionId}`,
- );
+ const userRecord = await storage.get(`solidClientAuthenticationUser:${input.sessionId}`);
const oidcIssuer =
- (await storage.get("issuer")) ??
- (await storage.get("oidc:issuer")) ??
- userRecord ??
- "";
+ (await storage.get("issuer")) ?? (await storage.get("oidc:issuer")) ?? userRecord ?? "";
// Connect-time persistence check (MC-176). The publish path fails for newly
// connected WebIDs with "refresh token failed". This pins down *which* half
// of the split it is — "no refresh token was ever stored" vs "the IdP later
@@ -134,8 +125,7 @@ export async function completeAuthFlow(input: {
if (userRecord) {
try {
hasRefreshToken =
- typeof (JSON.parse(userRecord) as { refreshToken?: unknown })
- .refreshToken === "string";
+ typeof (JSON.parse(userRecord) as { refreshToken?: unknown }).refreshToken === "string";
} catch {
/* non-JSON record — leave hasRefreshToken false */
}
@@ -181,9 +171,7 @@ function parseIssuerFromStorageValue(v: string): string {
* up that records what actually went wrong so the
* `OidcRefreshFailedError` is no longer opaque.
*/
-export async function loadAuthedFetchForWebId(
- webId: string,
-): Promise {
+export async function loadAuthedFetchForWebId(webId: string): Promise {
const identity = getIdentityByWebId(webId);
if (!identity) return null;
diff --git a/src/lib/solid/profile.ts b/src/lib/solid/profile.ts
index 2376523..b57d00e 100644
--- a/src/lib/solid/profile.ts
+++ b/src/lib/solid/profile.ts
@@ -1,11 +1,11 @@
import "server-only";
import {
+ getContainedResourceUrlAll,
getSolidDataset,
- getThing,
getStringNoLocale,
+ getThing,
getUrl,
getUrlAll,
- getContainedResourceUrlAll,
} from "@inrupt/solid-client";
const FOAF = "http://xmlns.com/foaf/0.1/";
@@ -34,9 +34,7 @@ export async function fetchProfile(webId: string): Promise {
const document = stripFragment(webId);
const [dataset, rawTurtle] = await Promise.all([
getSolidDataset(document),
- fetch(document, { headers: { Accept: "text/turtle" } }).then((r) =>
- r.ok ? r.text() : "",
- ),
+ fetch(document, { headers: { Accept: "text/turtle" } }).then((r) => (r.ok ? r.text() : "")),
]);
const thing = getThing(dataset, webId);
if (!thing) {
@@ -60,9 +58,7 @@ export async function fetchProfile(webId: string): Promise {
document,
name: getStringNoLocale(thing, `${FOAF}name`),
nick: getStringNoLocale(thing, `${FOAF}nick`),
- bio:
- getStringNoLocale(thing, `${VCARD}note`) ??
- getStringNoLocale(thing, `${RDFS}comment`),
+ bio: getStringNoLocale(thing, `${VCARD}note`) ?? getStringNoLocale(thing, `${RDFS}comment`),
homepage: getUrl(thing, `${FOAF}homepage`),
oidcIssuer: getUrl(thing, `${SOLID}oidcIssuer`),
img: getUrl(thing, `${FOAF}img`),
@@ -180,9 +176,8 @@ export async function verifyPodRootForWebId(
// fetch() joins them with `, ` per the Web spec, so parse with a
// regex that tolerates that.
const link = headResp.headers.get("link") ?? "";
- const isStorage = /<\s*http:\/\/www\.w3\.org\/ns\/pim\/space#Storage\s*>\s*;\s*rel\s*=\s*"?type"?/i.test(
- link,
- );
+ const isStorage =
+ /<\s*http:\/\/www\.w3\.org\/ns\/pim\/space#Storage\s*>\s*;\s*rel\s*=\s*"?type"?/i.test(link);
if (!isStorage) {
return {
ok: false,
diff --git a/src/lib/solid/pulls.ts b/src/lib/solid/pulls.ts
index 93d4b98..d43e3da 100644
--- a/src/lib/solid/pulls.ts
+++ b/src/lib/solid/pulls.ts
@@ -1,9 +1,9 @@
import "server-only";
-import type { Repo } from "@/lib/registry/repos";
-import type { PullRequest } from "@/lib/registry/pulls";
import { getIssueById } from "@/lib/registry/issues";
-import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
+import type { PullRequest } from "@/lib/registry/pulls";
+import type { Repo } from "@/lib/registry/repos";
import { ensureContainer, setVisibilityAcl } from "@/lib/solid/containers";
+import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
import { issueUrl } from "@/lib/solid/issues";
import { readMembersWithFetch } from "@/lib/solid/members";
import { NS } from "@/lib/vocab";
@@ -111,13 +111,7 @@ async function ensurePullContainers(
repo.visibility === "private"
? (await readMembersWithFetch(fetcher, repo)).map((m) => m.webId)
: [];
- await setVisibilityAcl(
- fetcher,
- pullsUrl,
- repo.ownerWebId,
- repo.visibility,
- memberWebIds,
- );
+ await setVisibilityAcl(fetcher, pullsUrl, repo.ownerWebId, repo.visibility, memberWebIds);
}
await ensureContainer(fetcher, pullContainerUrl(repo, pullNumber));
}
diff --git a/src/lib/solid/repo-metadata.ts b/src/lib/solid/repo-metadata.ts
index 25461be..c8ca3c5 100644
--- a/src/lib/solid/repo-metadata.ts
+++ b/src/lib/solid/repo-metadata.ts
@@ -1,7 +1,7 @@
import "server-only";
-import type { Repo, PagesConfig } from "@/lib/registry/repos";
-import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
+import type { PagesConfig, Repo } from "@/lib/registry/repos";
import { ensureContainer, setPublicReadAcl } from "@/lib/solid/containers";
+import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
import { ensureInbox, inboxContainerUrl } from "@/lib/solid/inbox";
import { ensureMembers, membersUrl } from "@/lib/solid/members";
import { NS } from "@/lib/vocab";
@@ -16,16 +16,12 @@ import { NS } from "@/lib/vocab";
* can discover the repository description without authenticating.
*/
function metadataUrl(repo: Repo): string {
- const root = repo.ownerPodRoot.endsWith("/")
- ? repo.ownerPodRoot
- : `${repo.ownerPodRoot}/`;
+ const root = repo.ownerPodRoot.endsWith("/") ? repo.ownerPodRoot : `${repo.ownerPodRoot}/`;
return `${root}codespaces/${repo.name}/index.ttl`;
}
function metadataContainer(repo: Repo): string {
- const root = repo.ownerPodRoot.endsWith("/")
- ? repo.ownerPodRoot
- : `${repo.ownerPodRoot}/`;
+ const root = repo.ownerPodRoot.endsWith("/") ? repo.ownerPodRoot : `${repo.ownerPodRoot}/`;
return `${root}codespaces/${repo.name}/`;
}
@@ -54,15 +50,9 @@ function renderTurtle(repo: Repo, pages: PagesConfig | null): string {
if (pages?.enabled && pages.targetContainer) {
lines[lines.length - 1] += " ;";
lines.push(` solidgit:pagesEnabled true ;`);
- lines.push(
- ` solidgit:pagesSourceBranch ${JSON.stringify(pages.sourceBranch)} ;`,
- );
- lines.push(
- ` solidgit:pagesSourcePath ${JSON.stringify(pages.sourcePath)} ;`,
- );
- lines.push(
- ` solidgit:pagesTarget <${pages.targetContainer}>`,
- );
+ lines.push(` solidgit:pagesSourceBranch ${JSON.stringify(pages.sourceBranch)} ;`);
+ lines.push(` solidgit:pagesSourcePath ${JSON.stringify(pages.sourcePath)} ;`);
+ lines.push(` solidgit:pagesTarget <${pages.targetContainer}>`);
}
// Advertise the proposal inbox. `ldp:inbox` makes it discoverable by any
// LDN-aware agent (the spec hook); `solidgit:proposalsEnabled` records
@@ -116,11 +106,7 @@ async function writeRepoMetadataOnce(
const authed = await getOwnerFetch(repo.ownerWebId);
try {
- await ensureCodespacesContainer(
- authed.fetch,
- repo.ownerPodRoot,
- repo.ownerWebId,
- );
+ await ensureCodespacesContainer(authed.fetch, repo.ownerPodRoot, repo.ownerWebId);
await ensureContainer(authed.fetch, metadataContainer(repo));
// Provision the LDN proposal inbox while we already hold the owner's
// authenticated fetch. Idempotent — only the first call writes the
@@ -139,9 +125,7 @@ async function writeRepoMetadataOnce(
body,
});
if (!res.ok) {
- throw new Error(
- `PUT ${url} failed: ${res.status} ${res.statusText}`,
- );
+ throw new Error(`PUT ${url} failed: ${res.status} ${res.statusText}`);
}
return { url, mode: authed.mode };
} finally {
diff --git a/src/lib/solid/tracker-pod.ts b/src/lib/solid/tracker-pod.ts
index 50dba1b..00a57a7 100644
--- a/src/lib/solid/tracker-pod.ts
+++ b/src/lib/solid/tracker-pod.ts
@@ -1,11 +1,11 @@
import "server-only";
+import { hasAnyCommits, readBlob } from "@/lib/git/objects";
+import { log } from "@/lib/log";
import type { Repo } from "@/lib/registry/repos";
-import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
import { ensureContainer, setPublicReadAcl } from "@/lib/solid/containers";
-import { readBlob, hasAnyCommits } from "@/lib/git/objects";
-import { parseTrackerTrio } from "@/lib/tracker/parse";
+import { getOwnerFetch } from "@/lib/solid/fetch-for-owner";
import type { Tracker } from "@/lib/tracker/model";
-import { log } from "@/lib/log";
+import { parseTrackerTrio } from "@/lib/tracker/parse";
/**
* Mirror a repo's `.mind`-derived `flow:Tracker` into the **owner's pod**, so
@@ -77,9 +77,7 @@ export async function publishTrackerToPod(
body: outputs[key],
});
if (!res.ok && res.status !== 201 && res.status !== 205) {
- throw new Error(
- `failed to PUT tracker doc ${url}: ${res.status} ${res.statusText}`,
- );
+ throw new Error(`failed to PUT tracker doc ${url}: ${res.status} ${res.statusText}`);
}
written.push(url);
}
@@ -93,9 +91,7 @@ export async function publishTrackerToPod(
* a missing `tracker.ttl`/`epics.ttl` degrades gracefully (parser tolerates
* nulls).
*/
-export async function readPodTrackerOutputs(
- repo: Repo,
-): Promise {
+export async function readPodTrackerOutputs(repo: Repo): Promise {
const container = trackerContainerUrl(repo);
const owner = await getOwnerFetch(repo.ownerWebId);
@@ -103,19 +99,14 @@ export async function readPodTrackerOutputs(
const res = await owner.fetch(`${container}${name}`, { method: "GET" });
if (res.status === 404) return null;
if (!res.ok) {
- throw new Error(
- `failed to GET tracker doc ${container}${name}: ${res.status}`,
- );
+ throw new Error(`failed to GET tracker doc ${container}${name}: ${res.status}`);
}
return res.text();
};
const state = await get(DOC_NAMES.state);
if (state === null) return null;
- const [tracker, epics] = await Promise.all([
- get(DOC_NAMES.tracker),
- get(DOC_NAMES.epics),
- ]);
+ const [tracker, epics] = await Promise.all([get(DOC_NAMES.tracker), get(DOC_NAMES.epics)]);
return { tracker: tracker ?? "", epics: epics ?? "", state };
}
@@ -160,10 +151,7 @@ export async function mirrorTrackerFromGit(
};
const state = await read(DOC_NAMES.state);
if (state === null) return false; // no tracker in this push
- const [tracker, epics] = await Promise.all([
- read(DOC_NAMES.tracker),
- read(DOC_NAMES.epics),
- ]);
+ const [tracker, epics] = await Promise.all([read(DOC_NAMES.tracker), read(DOC_NAMES.epics)]);
await publishTrackerToPod(repo, {
tracker: tracker ?? "",
epics: epics ?? "",
diff --git a/src/lib/tracker/author.ts b/src/lib/tracker/author.ts
index e908008..cefa58a 100644
--- a/src/lib/tracker/author.ts
+++ b/src/lib/tracker/author.ts
@@ -1,8 +1,8 @@
import "server-only";
import { spawn } from "node:child_process";
-import { mkdir, writeFile } from "node:fs/promises";
-import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { randomBytes } from "node:crypto";
+import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
+import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { checkoutBranchToTempDir } from "@/lib/git/checkout";
import { buildTrackerOutputs, TrackerBuildError } from "./build";
@@ -199,10 +199,7 @@ export async function createMindIssue(
try {
checkout = await checkoutBranchToTempDir(bareRepoPath, branch);
} catch (e) {
- throw new IssueAuthorError(
- `could not check out "${branch}": ${(e as Error).message}`,
- 500,
- );
+ throw new IssueAuthorError(`could not check out "${branch}": ${(e as Error).message}`, 500);
}
const { tempDir, cleanup } = checkout;
@@ -214,10 +211,7 @@ export async function createMindIssue(
current = buildTrackerOutputs(tempDir);
} catch (e) {
if (e instanceof TrackerBuildError) {
- throw new IssueAuthorError(
- `this repo has no usable .mind tracker (${e.message})`,
- 409,
- );
+ throw new IssueAuthorError(`this repo has no usable .mind tracker (${e.message})`, 409);
}
throw e;
}
@@ -250,23 +244,19 @@ export async function createMindIssue(
const typeId = category.label;
const wantsEpic = input.epicSlug && input.epicSlug !== "general";
- const epic = wantsEpic
- ? tracker.epics.find((e) => e.slug === input.epicSlug)
- : undefined;
+ const epic = wantsEpic ? tracker.epics.find((e) => e.slug === input.epicSlug) : undefined;
if (wantsEpic && !epic) {
throw new IssueAuthorError(`unknown epic "${input.epicSlug}"`);
}
- const nextNumber =
- tracker.issues.reduce((max, i) => Math.max(max, i.number ?? 0), 0) + 1;
+ const nextNumber = tracker.issues.reduce((max, i) => Math.max(max, i.number ?? 0), 0) + 1;
const id = mintIssueId(nextNumber);
const slug = slugify(title);
const now = new Date();
const date = now.toISOString().slice(0, 10); // YYYY-MM-DD
const hhmm =
- String(now.getUTCHours()).padStart(2, "0") +
- String(now.getUTCMinutes()).padStart(2, "0");
+ String(now.getUTCHours()).padStart(2, "0") + String(now.getUTCMinutes()).padStart(2, "0");
const tag = actorTag(input.authorWebId);
const issuesDir = join(tempDir, ".mind", "issues");
@@ -414,17 +404,13 @@ export async function createMindEpic(
): Promise {
const title = input.title.trim();
if (!title) throw new IssueAuthorError("title is required");
- const status =
- input.status && EPIC_STATUSES.has(input.status) ? input.status : "planned";
+ const status = input.status && EPIC_STATUSES.has(input.status) ? input.status : "planned";
let checkout: { tempDir: string; cleanup: () => Promise };
try {
checkout = await checkoutBranchToTempDir(bareRepoPath, branch);
} catch (e) {
- throw new IssueAuthorError(
- `could not check out "${branch}": ${(e as Error).message}`,
- 500,
- );
+ throw new IssueAuthorError(`could not check out "${branch}": ${(e as Error).message}`, 500);
}
const { tempDir, cleanup } = checkout;
@@ -434,10 +420,7 @@ export async function createMindEpic(
buildTrackerOutputs(tempDir);
} catch (e) {
if (e instanceof TrackerBuildError) {
- throw new IssueAuthorError(
- `this repo has no usable .mind tracker (${e.message})`,
- 409,
- );
+ throw new IssueAuthorError(`this repo has no usable .mind tracker (${e.message})`, 409);
}
throw e;
}
diff --git a/src/lib/tracker/build.ts b/src/lib/tracker/build.ts
index b749bee..3311014 100644
--- a/src/lib/tracker/build.ts
+++ b/src/lib/tracker/build.ts
@@ -12,7 +12,7 @@
* `/.mind/issues/**` and returns the three `build/*.ttl` documents as
* strings; the caller decides where to write them.
*/
-import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
+import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { parse as parseYaml } from "yaml";
@@ -100,7 +100,10 @@ function frontmatter(text: string, file: string): { data: any; body: string } {
const end = text.indexOf("\n---", 3);
if (end === -1) fail(`${file}: unterminated YAML frontmatter (no closing '---')`);
const yaml = text.slice(3, end);
- const body = text.slice(end + 4).replace(/^\r?\n/, "").trimEnd();
+ const body = text
+ .slice(end + 4)
+ .replace(/^\r?\n/, "")
+ .trimEnd();
let data: any;
try {
data = parseYaml(yaml) ?? {};
@@ -164,7 +167,10 @@ function loadConfig(issuesDir: string, rootDir: string): Config {
if (data[k] == null) fail(`tracker.config.md: missing required key "${k}"`);
}
const states: State[] = (data.states as any[]).map((s) => ({ ...s, label: s.label ?? s.id }));
- const categories: Category[] = (data.categories as any[]).map((c) => ({ ...c, label: c.label ?? c.id }));
+ const categories: Category[] = (data.categories as any[]).map((c) => ({
+ ...c,
+ label: c.label ?? c.id,
+ }));
const cfg: Config = {
title: data.title,
description: data.description,
@@ -190,7 +196,9 @@ function foldEvents(
rel: string,
): { state: string; holder?: string; afk?: boolean; blocks: string[]; modified?: string } | null {
if (!existsSync(eventsDir)) return null;
- const files = readdirSync(eventsDir).filter((f) => f.endsWith(".md")).sort();
+ const files = readdirSync(eventsDir)
+ .filter((f) => f.endsWith(".md"))
+ .sort();
if (!files.length) return null;
const stateIds = new Set(cfg.states.map((s) => s.id));
@@ -202,7 +210,8 @@ function foldEvents(
const { data } = frontmatter(readFileSync(join(eventsDir, f), "utf8"), `${rel}/events/${f}`);
if (data.at != null) lastAt = data.at;
if (data.to != null) {
- if (!stateIds.has(data.to)) fail(`${rel}/events/${f}: \`to: ${data.to}\` is not a declared state`);
+ if (!stateIds.has(data.to))
+ fail(`${rel}/events/${f}: \`to: ${data.to}\` is not a declared state`);
state = data.to;
}
if (Array.isArray(data.blocks)) blocks = data.blocks.map(String);
@@ -231,10 +240,12 @@ function loadIssuesIn(cfg: Config, groupDir: string, groupRel: string): Issue[]
if (data.state != null)
fail(`${rel}/issue.md: must NOT carry a \`state:\` field — state is the fold of events/`);
const category = String(data.type);
- if (!catIds.has(category)) fail(`${rel}/issue.md: type "${category}" not in tracker.config.md categories`);
+ if (!catIds.has(category))
+ fail(`${rel}/issue.md: type "${category}" not in tracker.config.md categories`);
const folded = foldEvents(cfg, join(dir, "events"), rel);
- if (!folded) fail(`${rel}/: no events/ — an issue needs at least an \`open\` event to have a state`);
+ if (!folded)
+ fail(`${rel}/: no events/ — an issue needs at least an \`open\` event to have a state`);
issues.push({
id: String(data.id),
@@ -264,7 +275,15 @@ function loadEpics(cfg: Config, issuesDir: string, rootDir: string): Epic[] {
if (existsSync(generalDir) && statSync(generalDir).isDirectory()) {
const issues = loadIssuesIn(cfg, generalDir, GENERAL_DIR);
if (issues.length)
- epics.push({ slug: GENERAL_DIR, number: 0, title: "General", status: "active", body: "", issues, isGeneral: true });
+ epics.push({
+ slug: GENERAL_DIR,
+ number: 0,
+ title: "General",
+ status: "active",
+ body: "",
+ issues,
+ isGeneral: true,
+ });
}
// Epics sort by their on-disk address (timestamp-prefixed ⇒ creation order);
@@ -404,7 +423,8 @@ function renderState(cfg: Config, epics: Epic[]): string {
if (i.modified) lines.push(`dct:modified "${i.modified}"^^xsd:date`);
if (i.assignee) lines.push(`wf:assignee <${i.assignee}>`);
if (i.afk != null) lines.push(`mc:afk ${i.afk ? "true" : "false"}`);
- if (i.blocks.length) lines.push(`mc:blocks ${i.blocks.map((b) => `<#${issueFrag(b)}>`).join(" , ")}`);
+ if (i.blocks.length)
+ lines.push(`mc:blocks ${i.blocks.map((b) => `<#${issueFrag(b)}>`).join(" , ")}`);
if (i.blockedBy.length)
lines.push(`mc:blockedBy ${i.blockedBy.map((b) => `<#${issueFrag(b)}>`).join(" , ")}`);
lines.push(`wf:description ${ttlLong(i.body)}`);
diff --git a/src/lib/tracker/model.ts b/src/lib/tracker/model.ts
index fe57595..45e41ca 100644
--- a/src/lib/tracker/model.ts
+++ b/src/lib/tracker/model.ts
@@ -105,10 +105,7 @@ export function localName(iri: string): string {
* on the board, while an epic whose issues are merely filtered out (e.g. all
* closed while viewing "open") stays hidden rather than cluttering the view.
*/
-export function groupByEpic(
- tracker: Tracker,
- issues: TrackerIssue[],
-): TrackerGroup[] {
+export function groupByEpic(tracker: Tracker, issues: TrackerIssue[]): TrackerGroup[] {
const groups: TrackerGroup[] = [];
const general = issues.filter(
(i) => !i.epicSlug || !tracker.epics.some((e) => e.slug === i.epicSlug),
@@ -116,9 +113,7 @@ export function groupByEpic(
if (general.length > 0) groups.push({ kind: "general", issues: general });
for (const epic of tracker.epics) {
const inEpic = issues.filter((i) => i.epicSlug === epic.slug);
- const totalInEpic = tracker.issues.filter(
- (i) => i.epicSlug === epic.slug,
- ).length;
+ const totalInEpic = tracker.issues.filter((i) => i.epicSlug === epic.slug).length;
if (inEpic.length > 0 || totalInEpic === 0) {
groups.push({ kind: "epic", epic, issues: inEpic });
}
diff --git a/src/lib/tracker/parse.ts b/src/lib/tracker/parse.ts
index a26dd2f..a50c579 100644
--- a/src/lib/tracker/parse.ts
+++ b/src/lib/tracker/parse.ts
@@ -1,8 +1,8 @@
import { Parser, type Quad, type Term } from "n3";
import {
- localName,
type IssueCategory,
type IssueState,
+ localName,
type Tracker,
type TrackerEpic,
type TrackerIssue,
@@ -192,12 +192,8 @@ export function parseTrackerTrio(
idx.add(parseDoc(ttl.state, baseRoot + "state.ttl"));
const trackerIri = idx.subjects().find((s) => idx.hasType(s, FLOW_TRACKER));
- const issueClassIri = trackerIri
- ? idx.iri(trackerIri, FLOW_ISSUE_CLASS)
- : undefined;
- const categoryClassIri = trackerIri
- ? idx.iri(trackerIri, FLOW_ISSUE_CATEGORY)
- : undefined;
+ const issueClassIri = trackerIri ? idx.iri(trackerIri, FLOW_ISSUE_CLASS) : undefined;
+ const categoryClassIri = trackerIri ? idx.iri(trackerIri, FLOW_ISSUE_CATEGORY) : undefined;
const states = parseStates(idx, issueClassIri);
const categories = parseCategories(idx, categoryClassIri);
@@ -246,31 +242,33 @@ function parseCategories(idx: Index, categoryClassIri?: string): IssueCategory[]
return idx
.subjects()
.filter(
- (s) =>
- s !== categoryClassIri &&
- idx.iris(s, RDFS_SUBCLASSOF).includes(categoryClassIri),
+ (s) => s !== categoryClassIri && idx.iris(s, RDFS_SUBCLASSOF).includes(categoryClassIri),
)
- .map((s): IssueCategory => ({
- id: localName(s),
- classIri: s,
- label: idx.str(s, RDFS_LABEL) ?? localName(s),
- }));
+ .map(
+ (s): IssueCategory => ({
+ id: localName(s),
+ classIri: s,
+ label: idx.str(s, RDFS_LABEL) ?? localName(s),
+ }),
+ );
}
function parseEpics(idx: Index, mc: McVocab): TrackerEpic[] {
return idx
.subjects()
.filter((s) => idx.hasType(s, mc.epicClass))
- .map((s): TrackerEpic => ({
- slug: localName(s),
- iri: s,
- number: idx.int(s, mc.number),
- title: idx.strAny(s, DCT_TITLE, RDFS_LABEL) ?? localName(s),
- status: idx.str(s, mc.status),
- issueCount: idx.int(s, mc.issueCount),
- description: idx.str(s, DCT_DESCRIPTION),
- created: idx.str(s, DCT_CREATED),
- }))
+ .map(
+ (s): TrackerEpic => ({
+ slug: localName(s),
+ iri: s,
+ number: idx.int(s, mc.number),
+ title: idx.strAny(s, DCT_TITLE, RDFS_LABEL) ?? localName(s),
+ status: idx.str(s, mc.status),
+ issueCount: idx.int(s, mc.issueCount),
+ description: idx.str(s, DCT_DESCRIPTION),
+ created: idx.str(s, DCT_CREATED),
+ }),
+ )
.sort((a, b) => (a.number ?? 0) - (b.number ?? 0));
}
@@ -286,9 +284,7 @@ function parseIssues(
// "has a known state type" for trackers that omit the back-link.
let subjects = idx.subjects().filter((s) => idx.iri(s, WF_TRACKER));
if (subjects.length === 0) {
- subjects = idx
- .subjects()
- .filter((s) => idx.iris(s, RDF_TYPE).some((t) => stateIris.has(t)));
+ subjects = idx.subjects().filter((s) => idx.iris(s, RDF_TYPE).some((t) => stateIris.has(t)));
}
return subjects
.map((s): TrackerIssue => {
diff --git a/src/lib/tracker/read.ts b/src/lib/tracker/read.ts
index 2df1371..c5082d4 100644
--- a/src/lib/tracker/read.ts
+++ b/src/lib/tracker/read.ts
@@ -1,25 +1,21 @@
import "server-only";
import { hasAnyCommits, readBlob } from "@/lib/git/objects";
-import { parseTrackerTrio } from "./parse";
import { groupByEpic, localName, type Tracker } from "./model";
+import { parseTrackerTrio } from "./parse";
-export { groupByEpic, localName, parseTrackerTrio };
export type {
+ IssueCategory,
+ IssueState,
Tracker,
TrackerEpic,
- TrackerIssue,
TrackerGroup,
- IssueState,
- IssueCategory,
+ TrackerIssue,
} from "./model";
+export { groupByEpic, localName, parseTrackerTrio };
const BUILD_DIR = ".mind/build";
-async function readUtf8(
- bare: string,
- ref: string,
- path: string,
-): Promise {
+async function readUtf8(bare: string, ref: string, path: string): Promise {
const blob = await readBlob(bare, ref, path);
return blob ? blob.bytes.toString("utf-8") : null;
}
diff --git a/src/lib/tracker/source.ts b/src/lib/tracker/source.ts
index 3a93457..64f8a74 100644
--- a/src/lib/tracker/source.ts
+++ b/src/lib/tracker/source.ts
@@ -1,10 +1,10 @@
import "server-only";
-import type { Repo } from "@/lib/registry/repos";
import { repoPath } from "@/lib/git/backend";
-import { readGitTracker } from "./read";
+import { log } from "@/lib/log";
+import type { Repo } from "@/lib/registry/repos";
import { readPodTracker } from "@/lib/solid/tracker-pod";
import type { Tracker } from "./model";
-import { log } from "@/lib/log";
+import { readGitTracker } from "./read";
/**
* Read a repo's `flow:Tracker` for rendering — the single seam the dashboard
diff --git a/src/lib/workflows/parse.ts b/src/lib/workflows/parse.ts
index 0f02a75..9f86293 100644
--- a/src/lib/workflows/parse.ts
+++ b/src/lib/workflows/parse.ts
@@ -31,9 +31,7 @@ export function parseWorkflow(source: string): Workflow {
try {
raw = parseYaml(source);
} catch (e) {
- throw new WorkflowParseError(
- `invalid YAML: ${(e as Error).message ?? "parse error"}`,
- );
+ throw new WorkflowParseError(`invalid YAML: ${(e as Error).message ?? "parse error"}`);
}
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
throw new WorkflowParseError("workflow root must be a mapping");
@@ -56,9 +54,7 @@ export function parseWorkflow(source: string): Workflow {
const run: string[] = [];
for (const [i, cmd] of obj.run.entries()) {
if (typeof cmd !== "string" || !cmd.trim()) {
- throw new WorkflowParseError(
- `run[${i}] must be a non-empty string`,
- );
+ throw new WorkflowParseError(`run[${i}] must be a non-empty string`);
}
run.push(cmd);
}
@@ -88,9 +84,7 @@ export function parseWorkflow(source: string): Workflow {
obj.timeout < 1 ||
obj.timeout > MAX_TIMEOUT_S
) {
- throw new WorkflowParseError(
- `\`timeout\` must be an integer between 1 and ${MAX_TIMEOUT_S}`,
- );
+ throw new WorkflowParseError(`\`timeout\` must be an integer between 1 and ${MAX_TIMEOUT_S}`);
}
timeoutS = obj.timeout;
}
diff --git a/src/lib/workflows/runner.ts b/src/lib/workflows/runner.ts
index e83c4d8..d84a86b 100644
--- a/src/lib/workflows/runner.ts
+++ b/src/lib/workflows/runner.ts
@@ -1,13 +1,12 @@
import "server-only";
import { readFile, stat } from "node:fs/promises";
import { join, resolve, sep } from "node:path";
-import {
- getRepoById,
- getPagesConfig,
- markPagesPublished,
-} from "@/lib/registry/repos";
-import { repoPath, readBranchHead } from "@/lib/git/backend";
+import { readBranchHead, repoPath } from "@/lib/git/backend";
import { checkoutBranchToTempDir } from "@/lib/git/checkout";
+import { Metrics } from "@/lib/metrics";
+import { withPublishLock } from "@/lib/pages/publish-lock";
+import { publishDirectory } from "@/lib/pages/publisher";
+import { getPagesConfig, getRepoById, markPagesPublished } from "@/lib/registry/repos";
import {
createRun,
finishRun,
@@ -15,11 +14,8 @@ import {
markRunRunning,
type WorkflowRun,
} from "@/lib/registry/runs";
-import { parseWorkflow, WorkflowParseError } from "@/lib/workflows/parse";
-import { publishDirectory } from "@/lib/pages/publisher";
import { resolveRunnerMode, runShellBatch } from "@/lib/workflows/docker";
-import { withPublishLock } from "@/lib/pages/publish-lock";
-import { Metrics } from "@/lib/metrics";
+import { parseWorkflow, WorkflowParseError } from "@/lib/workflows/parse";
const WORKFLOW_PATH = ".mind/workflow.yml";
@@ -56,10 +52,7 @@ export async function runWorkflow(input: {
if (!repo) throw new Error(`repo id=${input.repoId} not found`);
const bare = repoPath(repo.owner, repo.name);
- const { tempDir, cleanup } = await checkoutBranchToTempDir(
- bare,
- input.branch,
- );
+ const { tempDir, cleanup } = await checkoutBranchToTempDir(bare, input.branch);
// No-workflow path: silently return; caller falls back to legacy Pages publish.
const workflowPath = join(tempDir, WORKFLOW_PATH);
@@ -136,11 +129,7 @@ export async function runWorkflow(input: {
log += `[publish coalesced into an in-flight run]\n`;
} else {
log += `[published ${lockResult.uploaded} file(s)]\n`;
- const headSha = await readBranchHead(
- repo.owner,
- repo.name,
- input.branch,
- );
+ const headSha = await readBranchHead(repo.owner, repo.name, input.branch);
markPagesPublished(repo.id, { sha: headSha });
}
} catch (e) {
@@ -189,4 +178,3 @@ async function exists(path: string): Promise {
return false;
}
}
-
diff --git a/src/proxy.ts b/src/proxy.ts
index fe86420..c15d207 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -1,5 +1,5 @@
-import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
+import { NextResponse } from "next/server";
/**
* CORS allowlist for the JSON API (P0-S8 second half).
@@ -70,10 +70,9 @@ export function proxy(req: NextRequest): NextResponse {
headers: {
"Access-Control-Allow-Origin": origin ?? "*",
"Access-Control-Allow-Methods": "GET,POST,PUT,PATCH,DELETE,OPTIONS",
- "Access-Control-Allow-Headers":
- "Content-Type,Authorization,X-CSRF-Token,X-Mind-Dev-WebId",
+ "Access-Control-Allow-Headers": "Content-Type,Authorization,X-CSRF-Token,X-Mind-Dev-WebId",
"Access-Control-Max-Age": "600",
- "Vary": "Origin",
+ Vary: "Origin",
},
});
}
diff --git a/tests/create-epic.test.ts b/tests/create-epic.test.ts
index 56d96c4..df79b21 100644
--- a/tests/create-epic.test.ts
+++ b/tests/create-epic.test.ts
@@ -1,7 +1,7 @@
-import { afterAll, describe, expect, it } from "vitest";
import { cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { afterAll, describe, expect, it } from "vitest";
import { buildTrackerOutputs } from "@/lib/tracker/build";
import { parseTrackerTrio } from "@/lib/tracker/parse";
diff --git a/tests/fetch-for-owner.test.ts b/tests/fetch-for-owner.test.ts
index fb09626..8f58eb5 100644
--- a/tests/fetch-for-owner.test.ts
+++ b/tests/fetch-for-owner.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, vi, beforeEach } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
/**
* MC-173 contract: `getOwnerFetch` must convert a dead delegated identity
@@ -61,16 +61,12 @@ describe("getOwnerFetch — stale delegated identity", () => {
"@/lib/solid/fetch-for-owner"
);
getEnv.mockReturnValue(devEnv()); // seeded fallback IS enabled
- loadAuthedFetchForWebId.mockRejectedValue(
- new MockOidcRefreshFailedError(WEBID),
- );
+ loadAuthedFetchForWebId.mockRejectedValue(new MockOidcRefreshFailedError(WEBID));
await expect(getOwnerFetch(WEBID)).rejects.toMatchObject({
reason: "needs-reauthorization",
});
- await expect(getOwnerFetch(WEBID)).rejects.toBeInstanceOf(
- OwnerFetchUnavailableError,
- );
+ await expect(getOwnerFetch(WEBID)).rejects.toBeInstanceOf(OwnerFetchUnavailableError);
// The connected-but-dead identity must never use the operator account.
expect(getCssAuthedFetch).not.toHaveBeenCalled();
});
diff --git a/tests/identity-storage-restart.test.ts b/tests/identity-storage-restart.test.ts
index e995814..4225fcf 100644
--- a/tests/identity-storage-restart.test.ts
+++ b/tests/identity-storage-restart.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
/**
* MC-150 regression: "Bridge restart drops pod auth → re-connect loop."
@@ -83,9 +83,7 @@ describe("identity storage survives a bridge restart (MC-150)", () => {
const { getDb } = await import("@/lib/registry/db");
const raw = (
getDb()
- .prepare(
- "SELECT value FROM identity_storage WHERE session_id = ? AND key = ?",
- )
+ .prepare("SELECT value FROM identity_storage WHERE session_id = ? AND key = ?")
.get(SESSION_ID, STORAGE_KEY) as { value: string }
).value;
expect(raw.startsWith("v1:")).toBe(true);
@@ -128,16 +126,11 @@ describe("identity storage survives a bridge restart (MC-150)", () => {
);
// …a later disjoint write (e.g. a rotated refresh token) must MERGE, not
// clobber the keypair (the identities IStorage merges this special key).
- await store.set(
- key,
- JSON.stringify({ refreshToken: "rt_rotated", isLoggedIn: "true" }),
- );
+ await store.set(key, JSON.stringify({ refreshToken: "rt_rotated", isLoggedIn: "true" }));
closeDb();
- const record = JSON.parse(
- (await makeIdentityStorage(sid).get(key)) as string,
- );
+ const record = JSON.parse((await makeIdentityStorage(sid).get(key)) as string);
expect(record.privateKey).toBe(SESSION_RECORD.privateKey);
expect(record.refreshToken).toBe("rt_rotated");
expect(record.dpop).toBe("true");
diff --git a/tests/inbox-acl.test.ts b/tests/inbox-acl.test.ts
index 5c30d74..bb3a451 100644
--- a/tests/inbox-acl.test.ts
+++ b/tests/inbox-acl.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { setInboxAcl } from "@/lib/solid/containers";
// The inbox ACL is the security boundary of the whole proposals feature:
diff --git a/tests/inbox-roundtrip.test.ts b/tests/inbox-roundtrip.test.ts
index f9abee3..efad08c 100644
--- a/tests/inbox-roundtrip.test.ts
+++ b/tests/inbox-roundtrip.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, beforeEach, vi } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
/**
* End-to-end exercise of the LDN inbox module against an in-memory pod —
@@ -50,10 +50,7 @@ const { pod } = vi.hoisted(() => {
// GET
if (url.endsWith("/")) {
const children = [...store.keys()].filter(
- (k) =>
- k.startsWith(url) &&
- !k.slice(url.length).includes("/") &&
- !k.endsWith(".acl"),
+ (k) => k.startsWith(url) && !k.slice(url.length).includes("/") && !k.endsWith(".acl"),
);
const contains = children.length
? ` ;\n ldp:contains ${children.map((c) => `<${c}>`).join(", ")}`
@@ -130,8 +127,7 @@ describe("inbox round-trip", () => {
const { postProposal, listProposals } = await import("@/lib/solid/inbox");
const hostileTitle = 'Pwn "me" now';
- const hostileBody =
- 'line one\n""" .\n<#evil> .\n"""more';
+ const hostileBody = 'line one\n""" .\n<#evil> .\n"""more';
await postProposal(repo, {
title: hostileTitle,
body: hostileBody,
@@ -188,9 +184,7 @@ describe("inbox round-trip", () => {
});
it("dismiss deletes the notification", async () => {
- const { postProposal, listProposals, deleteProposal } = await import(
- "@/lib/solid/inbox"
- );
+ const { postProposal, listProposals, deleteProposal } = await import("@/lib/solid/inbox");
const { id } = await postProposal(repo, {
title: "first",
diff --git a/tests/issue-projection.test.ts b/tests/issue-projection.test.ts
index 51cde46..d03fa5d 100644
--- a/tests/issue-projection.test.ts
+++ b/tests/issue-projection.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
/**
* MC-160 C5: "the Registry index is rebuildable from the pod tracker
@@ -37,12 +37,8 @@ function trackerWith(issues: Array>) {
describe("projectTrackerToRegistry (MC-160)", () => {
it("projects tracker issues into the registry index by number, idempotently", async () => {
const { createRepo } = await import("@/lib/registry/repos");
- const { projectTrackerToRegistry } = await import(
- "@/lib/registry/issue-projection"
- );
- const { listIssues, getIssueByNumber } = await import(
- "@/lib/registry/issues"
- );
+ const { projectTrackerToRegistry } = await import("@/lib/registry/issue-projection");
+ const { listIssues, getIssueByNumber } = await import("@/lib/registry/issues");
const repo = createRepo({
owner: "alice",
@@ -103,12 +99,8 @@ describe("projectTrackerToRegistry (MC-160)", () => {
it("leaves a coexisting flat issue.ttl row untouched (back-compat)", async () => {
const { createRepo } = await import("@/lib/registry/repos");
- const { createIssue, getIssueByNumber } = await import(
- "@/lib/registry/issues"
- );
- const { projectTrackerToRegistry } = await import(
- "@/lib/registry/issue-projection"
- );
+ const { createIssue, getIssueByNumber } = await import("@/lib/registry/issues");
+ const { projectTrackerToRegistry } = await import("@/lib/registry/issue-projection");
const repo = createRepo({
owner: "alice",
diff --git a/tests/ledger-gate.test.ts b/tests/ledger-gate.test.ts
index fe0faf0..9475230 100644
--- a/tests/ledger-gate.test.ts
+++ b/tests/ledger-gate.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect } from "vitest";
+import { describe, expect, it } from "vitest";
import { gateEnvFallback } from "@/lib/ledger/policy";
describe("gateEnvFallback — free-allotment gate for the bridge-default key", () => {
diff --git a/tests/members-roundtrip.test.ts b/tests/members-roundtrip.test.ts
index 310e11c..cd4d9f8 100644
--- a/tests/members-roundtrip.test.ts
+++ b/tests/members-roundtrip.test.ts
@@ -1,5 +1,5 @@
-import { describe, it, expect, beforeEach, vi } from "vitest";
import { Parser, type Quad } from "n3";
+import { beforeEach, describe, expect, it, vi } from "vitest";
/**
* MC-157: pod-native repo membership (ADR-0002). Exercised against an
@@ -114,9 +114,7 @@ describe("renderMembersTurtle (MC-157)", () => {
),
).toHaveLength(1);
expect(
- quads.filter(
- (q) => q.predicate.value === RDF_TYPE && q.object.value === `${SOLIDGIT}Member`,
- ),
+ quads.filter((q) => q.predicate.value === RDF_TYPE && q.object.value === `${SOLIDGIT}Member`),
).toHaveLength(2);
expect(objectsOf(quads, `${SOLIDGIT}agent`)).toEqual([BOB, CAROL]);
expect(objectsOf(quads, `${SOLIDGIT}role`).sort()).toEqual(["reader", "writer"]);
@@ -182,9 +180,7 @@ describe("removeMember (MC-157)", () => {
await addMember(privateRepo as never, BOB, "writer");
await addMember(privateRepo as never, CAROL, "reader");
await removeMember(privateRepo as never, BOB);
- expect(await readMembers(privateRepo as never)).toEqual([
- { webId: CAROL, role: "reader" },
- ]);
+ expect(await readMembers(privateRepo as never)).toEqual([{ webId: CAROL, role: "reader" }]);
const acl = pod.store.get(PULLS_ACL);
expect(acl).toContain(CAROL);
expect(acl).not.toContain(BOB); // revoked
diff --git a/tests/merge-empty-target.test.ts b/tests/merge-empty-target.test.ts
index 05d7cc7..dd46985 100644
--- a/tests/merge-empty-target.test.ts
+++ b/tests/merge-empty-target.test.ts
@@ -1,8 +1,8 @@
-import { describe, it, expect } from "vitest";
import { execSync } from "node:child_process";
-import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
+import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { describe, expect, it } from "vitest";
import { mergeBranches } from "@/lib/git/merge";
function makeBareRepo(): string {
@@ -55,20 +55,15 @@ describe("mergeBranches — empty target branch", () => {
expect(refs).toContain("refs/heads/agent/issue-1");
expect(refs).not.toContain("refs/heads/main\n");
- const result = await mergeBranches(
- bare,
- "agent/issue-1",
- "main",
- "Merge pull request #1",
- { name: "alice", email: "https://example.org/alice#me" },
- );
+ const result = await mergeBranches(bare, "agent/issue-1", "main", "Merge pull request #1", {
+ name: "alice",
+ email: "https://example.org/alice#me",
+ });
expect(result.ok).toBe(true);
if (result.ok) expect(result.mergeSha).toBe(sha);
// main now points at the source SHA on the bare.
- const mainSha = execSync(`git -C "${bare}" rev-parse refs/heads/main`)
- .toString()
- .trim();
+ const mainSha = execSync(`git -C "${bare}" rev-parse refs/heads/main`).toString().trim();
expect(mainSha).toBe(sha);
} finally {
rmSync(bare, { recursive: true, force: true });
@@ -78,13 +73,10 @@ describe("mergeBranches — empty target branch", () => {
it("returns a clean error when neither target nor source exist", async () => {
const bare = makeBareRepo();
try {
- const result = await mergeBranches(
- bare,
- "agent/issue-1",
- "main",
- "Merge pull request #1",
- { name: "alice", email: "https://example.org/alice#me" },
- );
+ const result = await mergeBranches(bare, "agent/issue-1", "main", "Merge pull request #1", {
+ name: "alice",
+ email: "https://example.org/alice#me",
+ });
expect(result.ok).toBe(false);
if (!result.ok) {
expect(result.conflict).toBe(false);
diff --git a/tests/oidc-refresh-normalize.test.ts b/tests/oidc-refresh-normalize.test.ts
index 25a763e..171c788 100644
--- a/tests/oidc-refresh-normalize.test.ts
+++ b/tests/oidc-refresh-normalize.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, vi, beforeEach } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
/**
* MC-173 regression: a stale delegated identity (the issuer's dynamic-client
@@ -53,9 +53,7 @@ describe("loadAuthedFetchForWebId — dead registration normalization", () => {
getIdentityByWebId.mockReturnValue(IDENTITY);
// The SDK throws `invalid_client` when the dynamic registration is gone.
getSessionFromStorage.mockRejectedValue(new Error("invalid_client"));
- await expect(loadAuthedFetchForWebId(WEBID)).rejects.toBeInstanceOf(
- OidcRefreshFailedError,
- );
+ await expect(loadAuthedFetchForWebId(WEBID)).rejects.toBeInstanceOf(OidcRefreshFailedError);
});
it("throws OidcRefreshFailedError when refresh RETURNS a non-logged-in session", async () => {
@@ -64,9 +62,7 @@ describe("loadAuthedFetchForWebId — dead registration normalization", () => {
);
getIdentityByWebId.mockReturnValue(IDENTITY);
getSessionFromStorage.mockResolvedValue({ info: { isLoggedIn: false } });
- await expect(loadAuthedFetchForWebId(WEBID)).rejects.toBeInstanceOf(
- OidcRefreshFailedError,
- );
+ await expect(loadAuthedFetchForWebId(WEBID)).rejects.toBeInstanceOf(OidcRefreshFailedError);
});
it("returns the session fetch on a healthy refresh", async () => {
diff --git a/tests/oidc-session-cache.test.ts b/tests/oidc-session-cache.test.ts
index e3a228d..303c944 100644
--- a/tests/oidc-session-cache.test.ts
+++ b/tests/oidc-session-cache.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, vi, beforeEach } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
/**
* MC-176: Pages publish failed for newly-connected WebIDs with "refresh token
@@ -45,9 +45,7 @@ function sessionLoggedIn(label: string, expirationDate?: number) {
describe("loadAuthedFetchForWebId — in-process session cache (MC-176)", () => {
it("reuses the cached fetch instead of refreshing again while the token is valid", async () => {
- const { loadAuthedFetchForWebId, clearCachedSession } = await import(
- "@/lib/solid/oidc-server"
- );
+ const { loadAuthedFetchForWebId, clearCachedSession } = await import("@/lib/solid/oidc-server");
clearCachedSession(WEBID);
getIdentityByWebId.mockReturnValue({
webId: WEBID,
@@ -55,9 +53,7 @@ describe("loadAuthedFetchForWebId — in-process session cache (MC-176)", () =>
oidcIssuer: "",
});
// Access token valid for 5 more minutes.
- getSessionFromStorage.mockResolvedValue(
- sessionLoggedIn("first", Date.now() + 5 * 60_000),
- );
+ getSessionFromStorage.mockResolvedValue(sessionLoggedIn("first", Date.now() + 5 * 60_000));
const first = await loadAuthedFetchForWebId(WEBID);
const second = await loadAuthedFetchForWebId(WEBID);
@@ -68,18 +64,14 @@ describe("loadAuthedFetchForWebId — in-process session cache (MC-176)", () =>
});
it("re-derives when the WebID re-connects (sessionId changes)", async () => {
- const { loadAuthedFetchForWebId, clearCachedSession } = await import(
- "@/lib/solid/oidc-server"
- );
+ const { loadAuthedFetchForWebId, clearCachedSession } = await import("@/lib/solid/oidc-server");
clearCachedSession(WEBID);
getIdentityByWebId.mockReturnValue({
webId: WEBID,
sessionId: "sess-old",
oidcIssuer: "",
});
- getSessionFromStorage.mockResolvedValue(
- sessionLoggedIn("old", Date.now() + 5 * 60_000),
- );
+ getSessionFromStorage.mockResolvedValue(sessionLoggedIn("old", Date.now() + 5 * 60_000));
await loadAuthedFetchForWebId(WEBID);
// User re-/connect → new sessionId. The cached entry must be discarded.
@@ -88,18 +80,14 @@ describe("loadAuthedFetchForWebId — in-process session cache (MC-176)", () =>
sessionId: "sess-new",
oidcIssuer: "",
});
- getSessionFromStorage.mockResolvedValue(
- sessionLoggedIn("new", Date.now() + 5 * 60_000),
- );
+ getSessionFromStorage.mockResolvedValue(sessionLoggedIn("new", Date.now() + 5 * 60_000));
await loadAuthedFetchForWebId(WEBID);
expect(getSessionFromStorage).toHaveBeenCalledTimes(2);
});
it("re-derives once the cached access token is within the expiry skew", async () => {
- const { loadAuthedFetchForWebId, clearCachedSession } = await import(
- "@/lib/solid/oidc-server"
- );
+ const { loadAuthedFetchForWebId, clearCachedSession } = await import("@/lib/solid/oidc-server");
clearCachedSession(WEBID);
getIdentityByWebId.mockReturnValue({
webId: WEBID,
@@ -107,9 +95,7 @@ describe("loadAuthedFetchForWebId — in-process session cache (MC-176)", () =>
oidcIssuer: "",
});
// Token already inside the 60s skew window → must not be reused.
- getSessionFromStorage.mockResolvedValue(
- sessionLoggedIn("stale", Date.now() + 10_000),
- );
+ getSessionFromStorage.mockResolvedValue(sessionLoggedIn("stale", Date.now() + 10_000));
await loadAuthedFetchForWebId(WEBID);
await loadAuthedFetchForWebId(WEBID);
@@ -117,18 +103,14 @@ describe("loadAuthedFetchForWebId — in-process session cache (MC-176)", () =>
});
it("clearCachedSession forces the next call to refresh", async () => {
- const { loadAuthedFetchForWebId, clearCachedSession } = await import(
- "@/lib/solid/oidc-server"
- );
+ const { loadAuthedFetchForWebId, clearCachedSession } = await import("@/lib/solid/oidc-server");
clearCachedSession(WEBID);
getIdentityByWebId.mockReturnValue({
webId: WEBID,
sessionId: "sess-clear",
oidcIssuer: "",
});
- getSessionFromStorage.mockResolvedValue(
- sessionLoggedIn("c", Date.now() + 5 * 60_000),
- );
+ getSessionFromStorage.mockResolvedValue(sessionLoggedIn("c", Date.now() + 5 * 60_000));
await loadAuthedFetchForWebId(WEBID);
clearCachedSession(WEBID);
await loadAuthedFetchForWebId(WEBID);
diff --git a/tests/packages-content-store.test.ts b/tests/packages-content-store.test.ts
index 93ad95e..e5421dc 100644
--- a/tests/packages-content-store.test.ts
+++ b/tests/packages-content-store.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// PodContentStore against an in-memory fake pod. Verifies:
// • save returns the sha256 of the bytes (content-addressed)
@@ -31,8 +31,7 @@ function makeFakePod() {
putCount += 1;
const body = init?.body;
// Containers are PUT with no body; blobs carry a Uint8Array body.
- const bytes =
- body instanceof Uint8Array ? body : new Uint8Array(0);
+ const bytes = body instanceof Uint8Array ? body : new Uint8Array(0);
store.set(u, bytes);
return new Response(null, { status: 201 });
}
@@ -57,8 +56,7 @@ describe("PodContentStore", () => {
const bytes = new Uint8Array(Buffer.from("hello"));
// sha256("hello") is a known constant.
- const HELLO_SHA =
- "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
+ const HELLO_SHA = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
expect(await store.has(`sha256:${HELLO_SHA}`)).toBe(false);
diff --git a/tests/packages-npm.test.ts b/tests/packages-npm.test.ts
index 166697a..fe2bebf 100644
--- a/tests/packages-npm.test.ts
+++ b/tests/packages-npm.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// npm protocol helpers. Verifies:
// • parseNpmPublish pulls the single version + base64 tarball out of a
@@ -45,24 +45,20 @@ describe("npm protocol", () => {
expect(parsed.name).toBe("@alice/lib");
expect(parsed.version).toBe("1.2.3");
expect(parsed.tarballFilename).toBe("lib-1.2.3.tgz");
- expect(Buffer.from(parsed.tarballBytes).toString("utf-8")).toBe(
- "fake-tgz-bytes",
- );
+ expect(Buffer.from(parsed.tarballBytes).toString("utf-8")).toBe("fake-tgz-bytes");
expect(parsed.distTags.latest).toBe("1.2.3");
});
it("rejects malformed publish bodies", async () => {
const { parseNpmPublish, NpmPublishError } = await import("@/lib/packages/npm");
expect(() => parseNpmPublish({ name: "" } as never)).toThrow(NpmPublishError);
- expect(() =>
- parseNpmPublish({ name: "x", versions: {}, _attachments: {} } as never),
- ).toThrow(NpmPublishError);
+ expect(() => parseNpmPublish({ name: "x", versions: {}, _attachments: {} } as never)).toThrow(
+ NpmPublishError,
+ );
});
it("builds a packument with rewritten tarball URLs and latest tag", async () => {
- const { buildPackument, findVersionByFilename } = await import(
- "@/lib/packages/npm"
- );
+ const { buildPackument, findVersionByFilename } = await import("@/lib/packages/npm");
const now = 1_000;
const rows = [
// newest-first, as listVersions returns
diff --git a/tests/packages-oci-blob-cap.test.ts b/tests/packages-oci-blob-cap.test.ts
index cb0a31e..8da5f65 100644
--- a/tests/packages-oci-blob-cap.test.ts
+++ b/tests/packages-oci-blob-cap.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// Issue #175 — oversize OCI blob uploads must be rejected with a 413 *before*
// the body is buffered into memory (the bridge buffers each blob in RAM, so an
@@ -41,10 +41,11 @@ describe("OCI blob upload size cap (#175)", () => {
// PATCH (append a chunk) — oversize chunk → 413, no OOM.
const uuid = startUpload();
- const patchReq = new Request(
- `http://localhost/v2/alice/blobcap/blobs/uploads/${uuid}`,
- { method: "PATCH", headers: { authorization: auth }, body: over },
- );
+ const patchReq = new Request(`http://localhost/v2/alice/blobcap/blobs/uploads/${uuid}`, {
+ method: "PATCH",
+ headers: { authorization: auth },
+ body: over,
+ });
const patchRes = await PATCH(patchReq, {
params: Promise.resolve({ path: ["alice", "blobcap", "blobs", "uploads", uuid] }),
});
@@ -62,10 +63,11 @@ describe("OCI blob upload size cap (#175)", () => {
// A small chunk under the cap is NOT rejected by the guard (202 accepted).
const okUuid = startUpload();
- const okReq = new Request(
- `http://localhost/v2/alice/blobcap/blobs/uploads/${okUuid}`,
- { method: "PATCH", headers: { authorization: auth }, body: under },
- );
+ const okReq = new Request(`http://localhost/v2/alice/blobcap/blobs/uploads/${okUuid}`, {
+ method: "PATCH",
+ headers: { authorization: auth },
+ body: under,
+ });
const okRes = await PATCH(okReq, {
params: Promise.resolve({ path: ["alice", "blobcap", "blobs", "uploads", okUuid] }),
});
diff --git a/tests/packages-oci.test.ts b/tests/packages-oci.test.ts
index d95b187..82737d4 100644
--- a/tests/packages-oci.test.ts
+++ b/tests/packages-oci.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// OCI Distribution Spec routing + upload sessions. Verifies:
// • parseOciRequest classifies each /v2 endpoint and splits owner/repo/image
@@ -43,9 +43,7 @@ describe("OCI routing", () => {
expect(b.kind).toBe("blob");
if (b.kind === "blob") expect(b.digest).toBe("sha256:deadbeef");
- expect(parseOciRequest(["alice", "repo", "blobs", "uploads"]).kind).toBe(
- "upload-start",
- );
+ expect(parseOciRequest(["alice", "repo", "blobs", "uploads"]).kind).toBe("upload-start");
const u = parseOciRequest(["alice", "repo", "blobs", "uploads", "uuid-123"]);
expect(u.kind).toBe("upload-session");
@@ -58,8 +56,9 @@ describe("OCI routing", () => {
});
it("validates references and image names", async () => {
- const { validateVersion, validatePackageName, isDigestRef, PackageError } =
- await import("@/lib/packages/store");
+ const { validateVersion, validatePackageName, isDigestRef, PackageError } = await import(
+ "@/lib/packages/store"
+ );
expect(isDigestRef("sha256:" + "a".repeat(64))).toBe(true);
expect(isDigestRef("v1.2.3")).toBe(false);
diff --git a/tests/packages-store.test.ts b/tests/packages-store.test.ts
index 484a97d..79e23dc 100644
--- a/tests/packages-store.test.ts
+++ b/tests/packages-store.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// The packages index. Verifies:
// • upsert inserts, and re-upserting the same (repo,type,name,version)
@@ -97,8 +97,9 @@ describe("packages index", () => {
it("sums per-repo package bytes for the quota", async () => {
const { createRepo } = await import("@/lib/registry/repos");
const { upsertPackageVersion } = await import("@/lib/packages/store");
- const { sumPackageBytesForRepo, assertCanStorePackage, QuotaExceededError } =
- await import("@/lib/registry/quotas");
+ const { sumPackageBytesForRepo, assertCanStorePackage, QuotaExceededError } = await import(
+ "@/lib/registry/quotas"
+ );
const repo = createRepo({
owner: "alice",
@@ -127,8 +128,6 @@ describe("packages index", () => {
// A blob larger than the single-blob cap (default 100 MiB) is refused.
expect(() => assertCanStorePackage(repo.id, 1)).not.toThrow();
- expect(() => assertCanStorePackage(repo.id, 500 * 1024 * 1024)).toThrow(
- QuotaExceededError,
- );
+ expect(() => assertCanStorePackage(repo.id, 500 * 1024 * 1024)).toThrow(QuotaExceededError);
});
});
diff --git a/tests/parse-porcelain.test.ts b/tests/parse-porcelain.test.ts
index 8871607..e947c3c 100644
--- a/tests/parse-porcelain.test.ts
+++ b/tests/parse-porcelain.test.ts
@@ -1,8 +1,8 @@
-import { describe, it, expect } from "vitest";
import { execSync } from "node:child_process";
-import { mkdtempSync, writeFileSync, appendFileSync, rmSync } from "node:fs";
+import { appendFileSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { describe, expect, it } from "vitest";
import { parsePorcelain } from "@/lib/agents/drivers/coder";
describe("parsePorcelain — documented git status shapes", () => {
@@ -22,9 +22,7 @@ describe("parsePorcelain — documented git status shapes", () => {
});
it("multiline output", () => {
- expect(
- parsePorcelain(" M index.html\n?? README.md\n"),
- ).toEqual(["index.html", "README.md"]);
+ expect(parsePorcelain(" M index.html\n?? README.md\n")).toEqual(["index.html", "README.md"]);
});
it("quoted paths with special chars", () => {
@@ -41,16 +39,13 @@ describe("parsePorcelain — against real git output", () => {
execSync(`git clone "${bare}" "${work}"`, { stdio: "ignore" });
writeFileSync(join(work, "index.html"), "v1 \n");
execSync(`git -C "${work}" -c user.email=t@t -c user.name=t add index.html`);
- execSync(
- `git -C "${work}" -c user.email=t@t -c user.name=t commit -m initial`,
- { stdio: "ignore" },
- );
+ execSync(`git -C "${work}" -c user.email=t@t -c user.name=t commit -m initial`, {
+ stdio: "ignore",
+ });
execSync(`git -C "${work}" push origin main`, { stdio: "ignore" });
// Now mutate the file (the iteration scenario).
appendFileSync(join(work, "index.html"), "\n");
- const raw = execSync(
- `git -C "${work}" status --porcelain -uall`,
- ).toString();
+ const raw = execSync(`git -C "${work}" status --porcelain -uall`).toString();
const parsed = parsePorcelain(raw.trimEnd());
expect(parsed).toEqual(["index.html"]);
} finally {
@@ -67,16 +62,13 @@ describe("parsePorcelain — against real git output", () => {
execSync(`git clone "${bare}" "${work}"`, { stdio: "ignore" });
writeFileSync(join(work, "index.html"), "\n");
execSync(`git -C "${work}" -c user.email=t@t -c user.name=t add index.html`);
- execSync(
- `git -C "${work}" -c user.email=t@t -c user.name=t commit -m initial`,
- { stdio: "ignore" },
- );
+ execSync(`git -C "${work}" -c user.email=t@t -c user.name=t commit -m initial`, {
+ stdio: "ignore",
+ });
execSync(`git -C "${work}" push origin main`, { stdio: "ignore" });
appendFileSync(join(work, "index.html"), "\n");
writeFileSync(join(work, "README.md"), "# hello\n");
- const raw = execSync(
- `git -C "${work}" status --porcelain -uall`,
- ).toString();
+ const raw = execSync(`git -C "${work}" status --porcelain -uall`).toString();
const parsed = parsePorcelain(raw.trimEnd());
expect(parsed.sort()).toEqual(["README.md", "index.html"]);
} finally {
diff --git a/tests/path-traversal.test.ts b/tests/path-traversal.test.ts
index 2f30165..4ce512a 100644
--- a/tests/path-traversal.test.ts
+++ b/tests/path-traversal.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// Test 1 in §3.2 priority list: every user-supplied name flows through
// validateName, and every filesystem path is resolved via repoPath. The
@@ -47,10 +47,7 @@ describe("validateName", () => {
"x".repeat(65),
];
for (const name of bad) {
- expect(
- () => validateName(name, "owner"),
- `should reject ${JSON.stringify(name)}`,
- ).toThrow();
+ expect(() => validateName(name, "owner"), `should reject ${JSON.stringify(name)}`).toThrow();
}
});
});
diff --git a/tests/publisher-walk.test.ts b/tests/publisher-walk.test.ts
index 7680603..5d54348 100644
--- a/tests/publisher-walk.test.ts
+++ b/tests/publisher-walk.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
-import { mkdtempSync, mkdirSync, writeFileSync, symlinkSync } from "node:fs";
+import { mkdirSync, mkdtempSync, symlinkSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// Test 5 in §3.2 priority list: the publisher walk must not yield:
// • dot-prefixed config (`.env*`)
diff --git a/tests/pulls-roundtrip.test.ts b/tests/pulls-roundtrip.test.ts
index d0590e9..4f2b13c 100644
--- a/tests/pulls-roundtrip.test.ts
+++ b/tests/pulls-roundtrip.test.ts
@@ -1,5 +1,5 @@
-import { describe, it, expect, beforeEach, vi } from "vitest";
import { Parser, type Quad } from "n3";
+import { beforeEach, describe, expect, it, vi } from "vitest";
/**
* MC-142: opening a PR writes pod-native Turtle, parallel to how issues are
@@ -108,7 +108,9 @@ function makePull(over: Partial> = {}) {
}
function parse(ttl: string): Quad[] {
- return new Parser({ baseIRI: "http://localhost:3011/alice/codespaces/site/pulls/3/pull.ttl" }).parse(ttl);
+ return new Parser({
+ baseIRI: "http://localhost:3011/alice/codespaces/site/pulls/3/pull.ttl",
+ }).parse(ttl);
}
function objectsOf(quads: Quad[], pred: string): string[] {
return quads.filter((q) => q.predicate.value === pred).map((q) => q.object.value);
@@ -162,9 +164,7 @@ describe("renderPullTurtle (MC-142)", () => {
it("agent-authored PR (no author) omits the creator triple but stays valid", async () => {
const { renderPullTurtle } = await import("@/lib/solid/pulls");
- const quads = parse(
- renderPullTurtle(repo as never, makePull({ authorWebId: null }) as never),
- );
+ const quads = parse(renderPullTurtle(repo as never, makePull({ authorWebId: null }) as never));
expect(objectsOf(quads, `${SIOC}has_creator`)).toHaveLength(0);
// still a well-formed PullRequest with a number
expect(objectsOf(quads, `${SOLIDGIT}number`)).toContain("3");
@@ -192,9 +192,7 @@ describe("writePullToPod (MC-142)", () => {
expect(res.url).toBe(url);
expect(pod.store.has(url)).toBe(true);
- const acl = pod.store.get(
- "http://localhost:3011/alice/codespaces/site/pulls/.acl",
- );
+ const acl = pod.store.get("http://localhost:3011/alice/codespaces/site/pulls/.acl");
expect(acl).toBeDefined();
expect(acl).toContain("foaf:Agent"); // public-read rule present
expect(acl).toContain("acl:Read");
@@ -202,13 +200,8 @@ describe("writePullToPod (MC-142)", () => {
it("writes an owner-only ACL on a private repo (no public rule)", async () => {
const { writePullToPod } = await import("@/lib/solid/pulls");
- await writePullToPod(
- { ...repo, visibility: "private" } as never,
- makePull() as never,
- );
- const acl = pod.store.get(
- "http://localhost:3011/alice/codespaces/site/pulls/.acl",
- );
+ await writePullToPod({ ...repo, visibility: "private" } as never, makePull() as never);
+ const acl = pod.store.get("http://localhost:3011/alice/codespaces/site/pulls/.acl");
expect(acl).toBeDefined();
expect(acl).not.toContain("foaf:Agent"); // no public access on a private repo
expect(acl).toContain(repo.ownerWebId); // owner still has access
diff --git a/tests/push-tokens.test.ts b/tests/push-tokens.test.ts
index 320b1b9..90df214 100644
--- a/tests/push-tokens.test.ts
+++ b/tests/push-tokens.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// Test 2 in §3.2 priority list: push-token lifecycle. Verifies:
// • mint returns a `scp_`-prefixed token whose plaintext is never
@@ -22,8 +22,9 @@ beforeAll(() => {
describe("push tokens", () => {
it("mints, verifies, and revokes", async () => {
const { createRepo } = await import("@/lib/registry/repos");
- const { createPushToken, verifyPushToken, listPushTokens, revokePushToken } =
- await import("@/lib/registry/tokens");
+ const { createPushToken, verifyPushToken, listPushTokens, revokePushToken } = await import(
+ "@/lib/registry/tokens"
+ );
const { getDb } = await import("@/lib/registry/db");
const repo = createRepo({
diff --git a/tests/quotas.test.ts b/tests/quotas.test.ts
index ab755c6..3df9eef 100644
--- a/tests/quotas.test.ts
+++ b/tests/quotas.test.ts
@@ -1,7 +1,7 @@
-import { describe, it, expect, beforeAll } from "vitest";
import { mkdtempSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
+import { beforeAll, describe, expect, it } from "vitest";
// Per-user / per-repo quotas (§4 multi-user). The defaults are env-
// driven; we lower them here so the test doesn't have to create 50
@@ -19,9 +19,7 @@ beforeAll(() => {
describe("quotas", () => {
it("refuses repo creation past MAX_REPOS_PER_OWNER", async () => {
const { createRepo } = await import("@/lib/registry/repos");
- const { assertCanCreateRepo, QuotaExceededError } = await import(
- "@/lib/registry/quotas"
- );
+ const { assertCanCreateRepo, QuotaExceededError } = await import("@/lib/registry/quotas");
const owner = "quotatest";
const baseInput = {
owner,
@@ -39,9 +37,7 @@ describe("quotas", () => {
it("refuses token mint past MAX_TOKENS_PER_REPO", async () => {
const { createRepo } = await import("@/lib/registry/repos");
const { createPushToken } = await import("@/lib/registry/tokens");
- const { assertCanMintToken, QuotaExceededError } = await import(
- "@/lib/registry/quotas"
- );
+ const { assertCanMintToken, QuotaExceededError } = await import("@/lib/registry/quotas");
const repo = createRepo({
owner: "quotatokens",
name: "tk",
diff --git a/tests/service-secret-auth.test.ts b/tests/service-secret-auth.test.ts
index f99c909..50c5edd 100644
--- a/tests/service-secret-auth.test.ts
+++ b/tests/service-secret-auth.test.ts
@@ -1,4 +1,4 @@
-import { describe, it, expect, vi, beforeEach } from "vitest";
+import { beforeEach, describe, expect, it, vi } from "vitest";
// In-memory header/cookie stores driven per test.
const hdrStore = new Map();
diff --git a/tests/tracker-parse.test.ts b/tests/tracker-parse.test.ts
index 0b37236..c82a636 100644
--- a/tests/tracker-parse.test.ts
+++ b/tests/tracker-parse.test.ts
@@ -1,8 +1,8 @@
-import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
-import { parseTrackerTrio } from "@/lib/tracker/parse";
+import { describe, expect, it } from "vitest";
import { groupByEpic } from "@/lib/tracker/model";
+import { parseTrackerTrio } from "@/lib/tracker/parse";
// Fixtures: this prototype's own committed `.mind/build` trio. The seed script
// pushes the same trio into a demo repo, so parsing it here guards the exact
@@ -100,9 +100,7 @@ describe("parseTrackerTrio", () => {
expect(issue.stateId).toBe("Doing");
expect(issue.open).toBe(true);
expect(issue.categoryId).toBe("Bug");
- expect(issue.assignee).toBe(
- "http://localhost:3011/claude/profile/card#me",
- );
+ expect(issue.assignee).toBe("http://localhost:3011/claude/profile/card#me");
});
it("groups issues by epic with a trailing General bucket", () => {
diff --git a/tests/tracker-pod.test.ts b/tests/tracker-pod.test.ts
index b4fef5a..015734e 100644
--- a/tests/tracker-pod.test.ts
+++ b/tests/tracker-pod.test.ts
@@ -1,6 +1,6 @@
-import { describe, it, expect, beforeEach, vi } from "vitest";
import { readFileSync } from "node:fs";
import { join } from "node:path";
+import { beforeEach, describe, expect, it, vi } from "vitest";
/**
* MC-160: the `.mind`-derived `flow:Tracker` is mirrored into the owner's pod
@@ -86,9 +86,7 @@ beforeEach(() => {
describe("tracker → pod mirror (MC-160)", () => {
it("publishes the trio + a public-read ACL, idempotently", async () => {
- const { publishTrackerToPod, trackerContainerUrl } = await import(
- "@/lib/solid/tracker-pod"
- );
+ const { publishTrackerToPod, trackerContainerUrl } = await import("@/lib/solid/tracker-pod");
expect(trackerContainerUrl(repo)).toBe(CONTAINER);
const res = await publishTrackerToPod(repo, OUTPUTS);
@@ -107,11 +105,7 @@ describe("tracker → pod mirror (MC-160)", () => {
await publishTrackerToPod(repo, OUTPUTS);
const docs = [...pod.store.keys()].filter((k) => k.endsWith(".ttl"));
expect(docs.sort()).toEqual(
- [
- `${CONTAINER}epics.ttl`,
- `${CONTAINER}state.ttl`,
- `${CONTAINER}tracker.ttl`,
- ].sort(),
+ [`${CONTAINER}epics.ttl`, `${CONTAINER}state.ttl`, `${CONTAINER}tracker.ttl`].sort(),
);
});
@@ -126,9 +120,7 @@ describe("tracker → pod mirror (MC-160)", () => {
});
it("reads the pod tracker back and parses it grouped by epic", async () => {
- const { publishTrackerToPod, readPodTracker } = await import(
- "@/lib/solid/tracker-pod"
- );
+ const { publishTrackerToPod, readPodTracker } = await import("@/lib/solid/tracker-pod");
await publishTrackerToPod(repo, OUTPUTS);
const tracker = await readPodTracker(repo, "alice", "site");
diff --git a/tsconfig.json b/tsconfig.json
index 5e150fa..af3e981 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "ES2017",
- "lib": [
- "dom",
- "dom.iterable",
- "esnext"
- ],
+ "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
@@ -23,9 +19,7 @@
}
],
"paths": {
- "@/*": [
- "./src/*"
- ]
+ "@/*": ["./src/*"]
}
},
"include": [
@@ -38,7 +32,5 @@
".next-build/types/**/*.ts",
".next-build/dev/types/**/*.ts"
],
- "exclude": [
- "node_modules"
- ]
+ "exclude": ["node_modules"]
}
diff --git a/vitest.config.ts b/vitest.config.ts
index ccc13c6..5f84b81 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -1,5 +1,5 @@
-import { defineConfig } from "vitest/config";
import { resolve } from "node:path";
+import { defineConfig } from "vitest/config";
export default defineConfig({
test: {