Merge remote-tracking branch 'origin/master' into kunal/remove-e2e-auth-bypass
This commit is contained in:
@@ -26,15 +26,23 @@ vi.mock("next/link", () => {
|
||||
// wrappers still render <SignedOut/> from @clerk/nextjs (which crashes in real builds).
|
||||
vi.mock("@clerk/nextjs", () => {
|
||||
return {
|
||||
ClerkProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
ClerkProvider: ({ children }: { children: React.ReactNode }) => (
|
||||
<>{children}</>
|
||||
),
|
||||
SignedIn: () => {
|
||||
throw new Error("@clerk/nextjs SignedIn rendered (unexpected in secretless mode)");
|
||||
throw new Error(
|
||||
"@clerk/nextjs SignedIn rendered (unexpected in secretless mode)",
|
||||
);
|
||||
},
|
||||
SignedOut: () => {
|
||||
throw new Error("@clerk/nextjs SignedOut rendered without ClerkProvider");
|
||||
},
|
||||
SignInButton: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
SignOutButton: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
SignInButton: ({ children }: { children: React.ReactNode }) => (
|
||||
<>{children}</>
|
||||
),
|
||||
SignOutButton: ({ children }: { children: React.ReactNode }) => (
|
||||
<>{children}</>
|
||||
),
|
||||
useAuth: () => ({ isLoaded: true, isSignedIn: false }),
|
||||
useUser: () => ({ isLoaded: true, isSignedIn: false, user: null }),
|
||||
};
|
||||
|
||||
@@ -68,16 +68,27 @@ const getBoardOptions = (boards: BoardRead[]): SearchableSelectOption[] =>
|
||||
label: board.name,
|
||||
}));
|
||||
|
||||
const normalizeIdentityProfile = (
|
||||
profile: IdentityProfile,
|
||||
): IdentityProfile | null => {
|
||||
const normalized: IdentityProfile = {
|
||||
role: profile.role.trim(),
|
||||
communication_style: profile.communication_style.trim(),
|
||||
emoji: profile.emoji.trim(),
|
||||
const mergeIdentityProfile = (
|
||||
existing: unknown,
|
||||
patch: IdentityProfile,
|
||||
): Record<string, unknown> | null => {
|
||||
const resolved: Record<string, unknown> =
|
||||
existing && typeof existing === "object"
|
||||
? { ...(existing as Record<string, unknown>) }
|
||||
: {};
|
||||
const updates: Record<string, string> = {
|
||||
role: patch.role.trim(),
|
||||
communication_style: patch.communication_style.trim(),
|
||||
emoji: patch.emoji.trim(),
|
||||
};
|
||||
const hasValue = Object.values(normalized).some((value) => value.length > 0);
|
||||
return hasValue ? normalized : null;
|
||||
for (const [key, value] of Object.entries(updates)) {
|
||||
if (value) {
|
||||
resolved[key] = value;
|
||||
} else {
|
||||
delete resolved[key];
|
||||
}
|
||||
}
|
||||
return Object.keys(resolved).length > 0 ? resolved : null;
|
||||
};
|
||||
|
||||
const withIdentityDefaults = (
|
||||
@@ -241,7 +252,8 @@ export default function EditAgentPage() {
|
||||
every: resolvedHeartbeatEvery.trim() || "10m",
|
||||
target: resolvedHeartbeatTarget,
|
||||
} as unknown as Record<string, unknown>,
|
||||
identity_profile: normalizeIdentityProfile(
|
||||
identity_profile: mergeIdentityProfile(
|
||||
loadedAgent.identity_profile,
|
||||
resolvedIdentityProfile,
|
||||
) as unknown as Record<string, unknown> | null,
|
||||
soul_template: resolvedSoulTemplate.trim() || null,
|
||||
|
||||
@@ -135,7 +135,11 @@ const SSE_RECONNECT_BACKOFF = {
|
||||
|
||||
type HeartbeatUnit = "s" | "m" | "h" | "d";
|
||||
|
||||
const HEARTBEAT_PRESETS: Array<{ label: string; amount: number; unit: HeartbeatUnit }> = [
|
||||
const HEARTBEAT_PRESETS: Array<{
|
||||
label: string;
|
||||
amount: number;
|
||||
unit: HeartbeatUnit;
|
||||
}> = [
|
||||
{ label: "30s", amount: 30, unit: "s" },
|
||||
{ label: "1m", amount: 1, unit: "m" },
|
||||
{ label: "2m", amount: 2, unit: "m" },
|
||||
@@ -781,22 +785,22 @@ export default function BoardGroupDetailPage() {
|
||||
{HEARTBEAT_PRESETS.map((preset) => {
|
||||
const value = `${preset.amount}${preset.unit}`;
|
||||
return (
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
className={cn(
|
||||
"rounded-md px-2.5 py-1 text-xs font-semibold transition-colors",
|
||||
heartbeatEvery === value
|
||||
? "bg-slate-900 text-white"
|
||||
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
||||
)}
|
||||
onClick={() => {
|
||||
setHeartbeatAmount(String(preset.amount));
|
||||
setHeartbeatUnit(preset.unit);
|
||||
}}
|
||||
>
|
||||
{preset.label}
|
||||
</button>
|
||||
<button
|
||||
key={value}
|
||||
type="button"
|
||||
className={cn(
|
||||
"rounded-md px-2.5 py-1 text-xs font-semibold transition-colors",
|
||||
heartbeatEvery === value
|
||||
? "bg-slate-900 text-white"
|
||||
: "text-slate-600 hover:bg-slate-100 hover:text-slate-900",
|
||||
)}
|
||||
onClick={() => {
|
||||
setHeartbeatAmount(String(preset.amount));
|
||||
setHeartbeatUnit(preset.unit);
|
||||
}}
|
||||
>
|
||||
{preset.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,11 @@ import {
|
||||
useDeleteBoardApiV1BoardsBoardIdDelete,
|
||||
useListBoardsApiV1BoardsGet,
|
||||
} from "@/api/generated/boards/boards";
|
||||
import type { BoardRead } from "@/api/generated/model";
|
||||
import {
|
||||
type listBoardGroupsApiV1BoardGroupsGetResponse,
|
||||
useListBoardGroupsApiV1BoardGroupsGet,
|
||||
} from "@/api/generated/board-groups/board-groups";
|
||||
import type { BoardGroupRead, BoardRead } from "@/api/generated/model";
|
||||
import { DashboardSidebar } from "@/components/organisms/DashboardSidebar";
|
||||
import { DashboardShell } from "@/components/templates/DashboardShell";
|
||||
import { Button, buttonVariants } from "@/components/ui/button";
|
||||
@@ -46,6 +50,9 @@ const formatTimestamp = (value?: string | null) => {
|
||||
});
|
||||
};
|
||||
|
||||
const compactId = (value: string) =>
|
||||
value.length > 8 ? `${value.slice(0, 8)}…` : value;
|
||||
|
||||
export default function BoardsPage() {
|
||||
const { isSignedIn } = useAuth();
|
||||
const queryClient = useQueryClient();
|
||||
@@ -63,6 +70,20 @@ export default function BoardsPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const groupsQuery = useListBoardGroupsApiV1BoardGroupsGet<
|
||||
listBoardGroupsApiV1BoardGroupsGetResponse,
|
||||
ApiError
|
||||
>(
|
||||
{ limit: 200 },
|
||||
{
|
||||
query: {
|
||||
enabled: Boolean(isSignedIn),
|
||||
refetchInterval: 30_000,
|
||||
refetchOnMount: "always",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
const boards = useMemo(
|
||||
() =>
|
||||
boardsQuery.data?.status === 200
|
||||
@@ -71,6 +92,19 @@ export default function BoardsPage() {
|
||||
[boardsQuery.data],
|
||||
);
|
||||
|
||||
const groups = useMemo<BoardGroupRead[]>(() => {
|
||||
if (groupsQuery.data?.status !== 200) return [];
|
||||
return groupsQuery.data.data.items ?? [];
|
||||
}, [groupsQuery.data]);
|
||||
|
||||
const groupById = useMemo(() => {
|
||||
const map = new Map<string, BoardGroupRead>();
|
||||
for (const group of groups) {
|
||||
map.set(group.id, group);
|
||||
}
|
||||
return map;
|
||||
}, [groups]);
|
||||
|
||||
const deleteMutation = useDeleteBoardApiV1BoardsBoardIdDelete<
|
||||
ApiError,
|
||||
{ previous?: listBoardsApiV1BoardsGetResponse }
|
||||
@@ -136,6 +170,28 @@ export default function BoardsPage() {
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "group",
|
||||
header: "Group",
|
||||
cell: ({ row }) => {
|
||||
const groupId = row.original.board_group_id;
|
||||
if (!groupId) {
|
||||
return <span className="text-sm text-slate-400">—</span>;
|
||||
}
|
||||
const group = groupById.get(groupId);
|
||||
const label = group?.name ?? compactId(groupId);
|
||||
const title = group?.name ?? groupId;
|
||||
return (
|
||||
<Link
|
||||
href={`/board-groups/${groupId}`}
|
||||
className="text-sm font-medium text-slate-700 hover:text-blue-600"
|
||||
title={title}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: "updated_at",
|
||||
header: "Updated",
|
||||
@@ -167,7 +223,7 @@ export default function BoardsPage() {
|
||||
),
|
||||
},
|
||||
],
|
||||
[],
|
||||
[groupById],
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react-hooks/incompatible-library
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
// IMPORTANT: keep this file dependency-free (no `"use client"`, no React, no Clerk imports)
|
||||
// so it can be used from both client and server/edge entrypoints.
|
||||
|
||||
export function isLikelyValidClerkPublishableKey(key: string | undefined): key is string {
|
||||
export function isLikelyValidClerkPublishableKey(
|
||||
key: string | undefined,
|
||||
): key is string {
|
||||
if (!key) return false;
|
||||
|
||||
// Clerk publishable keys look like: pk_test_... or pk_live_...
|
||||
|
||||
@@ -447,36 +447,6 @@ export function BoardOnboardingChat({
|
||||
<span className="font-medium text-slate-900">Emoji:</span>{" "}
|
||||
{draft.lead_agent.identity_profile?.emoji || "—"}
|
||||
</p>
|
||||
<p className="text-slate-700">
|
||||
<span className="font-medium text-slate-900">Autonomy:</span>{" "}
|
||||
{draft.lead_agent.autonomy_level || "—"}
|
||||
</p>
|
||||
<p className="text-slate-700">
|
||||
<span className="font-medium text-slate-900">Verbosity:</span>{" "}
|
||||
{draft.lead_agent.verbosity || "—"}
|
||||
</p>
|
||||
<p className="text-slate-700">
|
||||
<span className="font-medium text-slate-900">
|
||||
Output format:
|
||||
</span>{" "}
|
||||
{draft.lead_agent.output_format || "—"}
|
||||
</p>
|
||||
<p className="text-slate-700">
|
||||
<span className="font-medium text-slate-900">
|
||||
Update cadence:
|
||||
</span>{" "}
|
||||
{draft.lead_agent.update_cadence || "—"}
|
||||
</p>
|
||||
{draft.lead_agent.custom_instructions ? (
|
||||
<>
|
||||
<p className="mt-3 font-semibold text-slate-900">
|
||||
Custom instructions
|
||||
</p>
|
||||
<pre className="mt-1 whitespace-pre-wrap text-xs text-slate-600">
|
||||
{draft.lead_agent.custom_instructions}
|
||||
</pre>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,9 @@ import { clerkMiddleware } from "@clerk/nextjs/server";
|
||||
import { isLikelyValidClerkPublishableKey } from "@/auth/clerkKey";
|
||||
|
||||
const isClerkEnabled = () =>
|
||||
isLikelyValidClerkPublishableKey(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY);
|
||||
isLikelyValidClerkPublishableKey(
|
||||
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
||||
);
|
||||
|
||||
export default isClerkEnabled() ? clerkMiddleware() : () => NextResponse.next();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user