Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ jobs:
echo " Examples: 'feat: add task graph', 'fix(ai): rate limiter timing'"
exit 1
fi
- name: Format check
run: bun run format:check
- name: Lint
run: bun run lint
- name: Typecheck
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# dependencies
/node_modules
/mcp/node_modules
/.pnp
.pnp.*

Expand Down
11 changes: 5 additions & 6 deletions app/(auth)/consent/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,9 @@ export default function ConsentPage() {
useEffect(() => {
if (!clientId) return;
const controller = new AbortController();
fetch(
`/api/oauth/consent-meta?client_id=${encodeURIComponent(clientId)}`,
{ signal: controller.signal },
)
fetch(`/api/oauth/consent-meta?client_id=${encodeURIComponent(clientId)}`, {
signal: controller.signal,
})
.then(async (res) => {
if (!res.ok) {
setFetchError(
Expand Down Expand Up @@ -227,8 +226,8 @@ export default function ConsentPage() {
Authorization sent
</h1>
<p className="text-sm text-text-muted">
Return to your application to finish signing in. You can close
this tab.
Return to your application to finish signing in. You can close this
tab.
</p>
</div>
</div>
Expand Down
22 changes: 11 additions & 11 deletions app/(auth)/sign-in/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Link from 'next/link';
import { AuthShell } from '@/components/auth/AuthShell';
import { AuthBrand } from '@/components/auth/AuthBrand';
import { AuthHero } from '@/components/auth/AuthHero';
import { SocialButtons } from '@/components/auth/SocialButtons';
import { SignInForm } from '@/components/auth/SignInForm';
import Link from "next/link";
import { AuthShell } from "@/components/auth/AuthShell";
import { AuthBrand } from "@/components/auth/AuthBrand";
import { AuthHero } from "@/components/auth/AuthHero";
import { SocialButtons } from "@/components/auth/SocialButtons";
import { SignInForm } from "@/components/auth/SignInForm";

/**
* Sign-in page — two-column auth surface matching the design prototype.
Expand All @@ -26,27 +26,27 @@ export default function SignInPage() {
<AuthBrand />
<h1
className="text-[26px] font-semibold text-text-primary"
style={{ letterSpacing: '-0.01em', lineHeight: 1.15 }}
style={{ letterSpacing: "-0.01em", lineHeight: 1.15 }}
>
Walk into every session knowing what to do next.
</h1>
<p
className="mb-7 mt-2.5 text-[13.5px] text-text-muted"
style={{ lineHeight: 1.55 }}
>
The agent-native project graph. Sign in to continue, or onboard
a repo from your CLI.
The agent-native project graph. Sign in to continue, or onboard a
repo from your CLI.
</p>

<SocialButtons />
<SignInForm />

<p className="mt-3.5 text-center text-[12px] text-text-muted">
New to Mymir?{' '}
New to Mymir?{" "}
<Link
href="/sign-up"
className="text-accent-light hover:underline"
style={{ color: 'var(--color-accent-light)' }}
style={{ color: "var(--color-accent-light)" }}
>
Create an account
</Link>
Expand Down
22 changes: 11 additions & 11 deletions app/(auth)/sign-up/page.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Link from 'next/link';
import { AuthShell } from '@/components/auth/AuthShell';
import { AuthBrand } from '@/components/auth/AuthBrand';
import { AuthHero } from '@/components/auth/AuthHero';
import { SocialButtons } from '@/components/auth/SocialButtons';
import { SignUpForm } from '@/components/auth/SignUpForm';
import Link from "next/link";
import { AuthShell } from "@/components/auth/AuthShell";
import { AuthBrand } from "@/components/auth/AuthBrand";
import { AuthHero } from "@/components/auth/AuthHero";
import { SocialButtons } from "@/components/auth/SocialButtons";
import { SignUpForm } from "@/components/auth/SignUpForm";

/**
* Sign-up page — mirrors the sign-in two-column shell with the
Expand All @@ -23,27 +23,27 @@ export default function SignUpPage() {
<AuthBrand />
<h1
className="text-[26px] font-semibold text-text-primary"
style={{ letterSpacing: '-0.01em', lineHeight: 1.15 }}
style={{ letterSpacing: "-0.01em", lineHeight: 1.15 }}
>
Create an account.
</h1>
<p
className="mb-7 mt-2.5 text-[13.5px] text-text-muted"
style={{ lineHeight: 1.55 }}
>
Your project graph and decision history live here. Connect
agents through MCP from your CLI once you&rsquo;re in.
Your project graph and decision history live here. Connect agents
through MCP from your CLI once you&rsquo;re in.
</p>

<SocialButtons />
<SignUpForm />

<p className="mt-3.5 text-center text-[12px] text-text-muted">
Already have an account?{' '}
Already have an account?{" "}
<Link
href="/sign-in"
className="hover:underline"
style={{ color: 'var(--color-accent-light)' }}
style={{ color: "var(--color-accent-light)" }}
>
Sign in
</Link>
Expand Down
8 changes: 6 additions & 2 deletions app/_components/HomeGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,9 @@ function GroupedGrid({ projects, teams }: GroupedGridProps) {
totalTasks={project.taskStats.total}
cancelledTasks={project.taskStats.cancelled}
tasksInProgress={project.taskStats.inProgress}
lastActive={dateFormatter.format(new Date(project.updatedAt))}
lastActive={dateFormatter.format(
new Date(project.updatedAt),
)}
canDelete={roleHasProjectPermission(project.memberRole, [
"delete",
])}
Expand Down Expand Up @@ -241,7 +243,9 @@ interface EmptyFilterHintProps {

/** Hint shown when the current filter has no matches. */
function EmptyFilterHint({ teamFilter, teams }: EmptyFilterHintProps) {
const filteredTeam = teamFilter ? teams.find((t) => t.id === teamFilter) : undefined;
const filteredTeam = teamFilter
? teams.find((t) => t.id === teamFilter)
: undefined;
return (
<EmptyHint
title={
Expand Down
20 changes: 10 additions & 10 deletions app/api/project/[projectId]/graph/route.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import {
getProjectGraphSlim,
getProjectMaxUpdatedAt,
} from '@/lib/data/project';
import { getAuthContext } from '@/lib/auth/context';
import { ForbiddenError } from '@/lib/auth/authorization';
import { conditionalRespond, etagMatches } from '@/lib/api/conditional';
import { internalError } from '@/lib/api/error';
import { error } from '@/lib/api/response';
} from "@/lib/data/project";
import { getAuthContext } from "@/lib/auth/context";
import { ForbiddenError } from "@/lib/auth/authorization";
import { conditionalRespond, etagMatches } from "@/lib/api/conditional";
import { internalError } from "@/lib/api/error";
import { error } from "@/lib/api/response";

/**
* Conditional handler for `GET` and `HEAD` on the project slim graph.
Expand All @@ -31,23 +31,23 @@ async function handle(req: Request, projectId: string): Promise<Response> {
try {
ctx = await getAuthContext();
} catch {
return error('Unauthorized', 401);
return error("Unauthorized", 401);
}

try {
const max = await getProjectMaxUpdatedAt(ctx, projectId);

if (req.method === 'HEAD' || etagMatches(req, max)) {
if (req.method === "HEAD" || etagMatches(req, max)) {
return conditionalRespond(req, null, max);
}

const body = await getProjectGraphSlim(ctx, projectId);
return conditionalRespond(req, body, max);
} catch (err) {
if (err instanceof ForbiddenError) {
return error('Project not found', 404);
return error("Project not found", 404);
}
return internalError('graph', err);
return internalError("graph", err);
}
}

Expand Down
22 changes: 11 additions & 11 deletions app/api/task/[taskId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getAuthContext } from '@/lib/auth/context';
import { ForbiddenError, assertTaskAccess } from '@/lib/auth/authorization';
import { conditionalRespond, etagMatches } from '@/lib/api/conditional';
import { getTaskFull } from '@/lib/data/task';
import { broker } from '@/lib/realtime/broker';
import { internalError } from '@/lib/api/error';
import { error } from '@/lib/api/response';
import { getAuthContext } from "@/lib/auth/context";
import { ForbiddenError, assertTaskAccess } from "@/lib/auth/authorization";
import { conditionalRespond, etagMatches } from "@/lib/api/conditional";
import { getTaskFull } from "@/lib/data/task";
import { broker } from "@/lib/realtime/broker";
import { internalError } from "@/lib/api/error";
import { error } from "@/lib/api/response";

/** TTL for fetch-implicit task subscriptions — 10 minutes. */
const TASK_SUBSCRIPTION_TTL_MS = 10 * 60_000;
Expand Down Expand Up @@ -35,7 +35,7 @@ async function handle(req: Request, taskId: string): Promise<Response> {
try {
ctx = await getAuthContext();
} catch {
return error('Unauthorized', 401);
return error("Unauthorized", 401);
}

try {
Expand All @@ -47,7 +47,7 @@ async function handle(req: Request, taskId: string): Promise<Response> {
// call authorizes itself.
const access = await assertTaskAccess(taskId, ctx);

if (req.method === 'HEAD' || etagMatches(req, access.updatedAt)) {
if (req.method === "HEAD" || etagMatches(req, access.updatedAt)) {
return conditionalRespond(req, null, access.updatedAt);
}

Expand All @@ -59,9 +59,9 @@ async function handle(req: Request, taskId: string): Promise<Response> {
return conditionalRespond(req, task, task.updatedAt);
} catch (err) {
if (err instanceof ForbiddenError) {
return error('Task not found', 404);
return error("Task not found", 404);
}
return internalError('task', err);
return internalError("task", err);
}
}

Expand Down
Loading
Loading