fix: resolve TypeScript errors in migrated components

This commit is contained in:
Jason Woltje
2026-01-29 22:00:14 -06:00
parent d54714ea06
commit abbf886483
36 changed files with 758 additions and 43 deletions

View File

@@ -30,7 +30,10 @@
"@mosaic/shared": "workspace:*",
"@nestjs/common": "^11.1.12",
"@nestjs/core": "^11.1.12",
"@nestjs/mapped-types": "^2.1.0",
"@nestjs/platform-express": "^11.1.12",
"@nestjs/platform-socket.io": "^11.1.12",
"@nestjs/websockets": "^11.1.12",
"@prisma/client": "^6.19.2",
"@types/marked": "^6.0.0",
"better-auth": "^1.4.17",
@@ -40,10 +43,12 @@
"marked": "^17.0.1",
"marked-gfm-heading-id": "^4.1.3",
"marked-highlight": "^2.2.3",
"ollama": "^0.6.3",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"sanitize-html": "^2.17.0",
"slugify": "^1.6.6"
"slugify": "^1.6.6",
"socket.io": "^4.8.3"
},
"devDependencies": {
"@better-auth/cli": "^1.4.17",

View File

@@ -22,6 +22,7 @@
"@mosaic/ui": "workspace:*",
"@tanstack/react-query": "^5.90.20",
"@xyflow/react": "^12.5.3",
"better-auth": "^1.4.17",
"date-fns": "^4.1.0",
"elkjs": "^0.9.3",
"lucide-react": "^0.563.0",
@@ -29,7 +30,8 @@
"next": "^16.1.6",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-grid-layout": "^2.2.2"
"react-grid-layout": "^2.2.2",
"socket.io-client": "^4.8.3"
},
"devDependencies": {
"@mosaic/config": "workspace:*",

View File

@@ -3,9 +3,9 @@
import { useState, useEffect } from "react";
import type { Domain } from "@mosaic/shared";
import { DomainList } from "@/components/domains/DomainList";
import { fetchDomains, createDomain, updateDomain, deleteDomain } from "@/lib/api/domains";
import { fetchDomains, deleteDomain } from "@/lib/api/domains";
export default function DomainsPage(): JSX.Element {
export default function DomainsPage(): React.ReactElement {
const [domains, setDomains] = useState<Domain[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);

View File

@@ -25,7 +25,7 @@ import {
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
export default function PersonalitiesPage(): JSX.Element {
export default function PersonalitiesPage(): React.ReactElement {
const [personalities, setPersonalities] = useState<Personality[]>([]);
const [selectedPersonality, setSelectedPersonality] = useState<Personality | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(true);

View File

@@ -11,7 +11,7 @@ import { TaskStatus, TaskPriority, type Task } from "@mosaic/shared";
* This page demonstrates the GanttChart component with sample data
* showing various task states, durations, and interactions.
*/
export default function GanttDemoPage(): JSX.Element {
export default function GanttDemoPage(): React.ReactElement {
// Sample tasks for demonstration
const baseTasks: Task[] = [
{

View File

@@ -217,12 +217,12 @@ export const Chat = forwardRef<ChatRef, ChatProps>(function Chat({
// Show a witty loading message after 3 seconds
const quipTimerId = setTimeout(() => {
setLoadingQuip(getRandomQuip(WAITING_QUIPS));
setLoadingQuip(getRandomQuip(WAITING_QUIPS) ?? null);
}, 3000);
// Change quip every 5 seconds if still waiting
const quipIntervalId = setInterval(() => {
setLoadingQuip(getRandomQuip(WAITING_QUIPS));
setLoadingQuip(getRandomQuip(WAITING_QUIPS) ?? null);
}, 5000);
try {

View File

@@ -45,7 +45,7 @@ export function MessageList({ messages, isLoading, loadingQuip }: MessageListPro
<MessageBubble key={message.id} message={message} />
))}
{isLoading && <LoadingIndicator quip={loadingQuip} />}
{isLoading && <LoadingIndicator {...(loadingQuip != null && { quip: loadingQuip })} />}
</div>
);
}

View File

@@ -12,7 +12,7 @@ export function DomainFilter({
domains,
selectedDomain,
onFilterChange,
}: DomainFilterProps): JSX.Element {
}: DomainFilterProps): React.ReactElement {
return (
<div className="flex gap-2 flex-wrap">
<button

View File

@@ -12,7 +12,7 @@ export function DomainItem({
domain,
onEdit,
onDelete,
}: DomainItemProps): JSX.Element {
}: DomainItemProps): React.ReactElement {
return (
<div className="border rounded-lg p-4 hover:shadow-md transition-shadow">
<div className="flex items-start justify-between">

View File

@@ -15,7 +15,7 @@ export function DomainList({
isLoading,
onEdit,
onDelete,
}: DomainListProps): JSX.Element {
}: DomainListProps): React.ReactElement {
if (isLoading) {
return (
<div className="flex justify-center items-center p-8">

View File

@@ -16,7 +16,7 @@ export function DomainSelector({
onChange,
placeholder = "Select a domain",
className = "",
}: DomainSelectorProps): JSX.Element {
}: DomainSelectorProps): React.ReactElement {
return (
<select
value={value ?? ""}

View File

@@ -215,7 +215,7 @@ export function GanttChart({
onTaskClick,
height = 400,
showDependencies = false,
}: GanttChartProps): JSX.Element {
}: GanttChartProps): React.ReactElement {
// Sort tasks by start date
const sortedTasks = useMemo(() => {
return [...tasks].sort((a, b) => a.startDate.getTime() - b.startDate.getTime());

View File

@@ -40,7 +40,7 @@ const columns = [
* - Task cards with title, priority badge, assignee avatar
* - PATCH /api/tasks/:id on status change
*/
export function KanbanBoard({ tasks = [], onStatusChange }: KanbanBoardProps): JSX.Element {
export function KanbanBoard({ tasks = [], onStatusChange }: KanbanBoardProps): React.ReactElement {
const [activeTaskId, setActiveTaskId] = useState<string | null>(null);
const sensors = useSensors(

View File

@@ -35,7 +35,7 @@ const statusBadgeColors = {
* A droppable column for tasks of a specific status.
* Uses @dnd-kit/core for drag-and-drop functionality.
*/
export function KanbanColumn({ status, title, tasks }: KanbanColumnProps): JSX.Element {
export function KanbanColumn({ status, title, tasks }: KanbanColumnProps): React.ReactElement {
const { setNodeRef, isOver } = useDroppable({
id: status,
});

View File

@@ -48,7 +48,7 @@ function getInitials(name: string): string {
* - Assignee avatar (if assigned)
* - Due date (if set)
*/
export function TaskCard({ task }: TaskCardProps): JSX.Element {
export function TaskCard({ task }: TaskCardProps): React.ReactElement {
const {
attributes,
listeners,

View File

@@ -1,3 +1,3 @@
export { KanbanBoard } from "./kanban-board";
export { KanbanColumn } from "./kanban-column";
export { TaskCard } from "./task-card";
export { KanbanBoard } from "./KanbanBoard";
export { KanbanColumn } from "./KanbanColumn";
export { TaskCard } from "./TaskCard";

View File

@@ -39,7 +39,7 @@ export function MindmapViewer({
updateNode,
deleteNode,
createEdge,
} = useGraphData({ rootId, maxDepth });
} = useGraphData({ ...(rootId && { rootId }), maxDepth });
const handleViewModeChange = useCallback(
async (mode: ViewMode) => {
@@ -170,9 +170,11 @@ export function MindmapViewer({
<ReactFlowEditor
graphData={graph}
onNodeSelect={setSelectedNode}
onNodeUpdate={readOnly ? undefined : updateNode}
onNodeDelete={readOnly ? undefined : handleDeleteNode}
onEdgeCreate={readOnly ? undefined : handleCreateEdge}
{...(!readOnly && {
onNodeUpdate: updateNode,
onNodeDelete: handleDeleteNode,
onEdgeCreate: handleCreateEdge,
})}
readOnly={readOnly}
className="h-full"
/>

View File

@@ -249,8 +249,10 @@ export function ReactFlowEditor({
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={readOnly ? undefined : onNodesChange}
onEdgesChange={readOnly ? undefined : onEdgesChange}
{...(!readOnly && {
onNodesChange,
onEdgesChange,
})}
onConnect={onConnect}
onNodeClick={onNodeClick}
onPaneClick={onPaneClick}

View File

@@ -40,7 +40,7 @@ const FORMALITY_OPTIONS = [
{ value: "VERY_FORMAL", label: "Very Formal" },
];
export function PersonalityForm({ personality, onSubmit, onCancel }: PersonalityFormProps): JSX.Element {
export function PersonalityForm({ personality, onSubmit, onCancel }: PersonalityFormProps): React.ReactElement {
const [formData, setFormData] = useState<PersonalityFormData>({
name: personality?.name || "",
description: personality?.description || "",

View File

@@ -26,7 +26,7 @@ const FORMALITY_LABELS: Record<string, string> = {
VERY_FORMAL: "Very Formal",
};
export function PersonalityPreview({ personality }: PersonalityPreviewProps): JSX.Element {
export function PersonalityPreview({ personality }: PersonalityPreviewProps): React.ReactElement {
const [selectedPrompt, setSelectedPrompt] = useState<string>(SAMPLE_PROMPTS[0]);
return (

View File

@@ -25,7 +25,7 @@ export function PersonalitySelector({
onChange,
label = "Select Personality",
className,
}: PersonalitySelectorProps): JSX.Element {
}: PersonalitySelectorProps): React.ReactElement {
const [personalities, setPersonalities] = useState<Personality[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(true);

View File

@@ -0,0 +1,121 @@
import * as React from "react";
export interface AlertDialogProps {
open?: boolean;
onOpenChange?: (open: boolean) => void;
children?: React.ReactNode;
}
export interface AlertDialogTriggerProps {
children?: React.ReactNode;
asChild?: boolean;
}
export interface AlertDialogContentProps {
children?: React.ReactNode;
}
export interface AlertDialogHeaderProps {
children?: React.ReactNode;
}
export interface AlertDialogFooterProps {
children?: React.ReactNode;
}
export interface AlertDialogTitleProps {
children?: React.ReactNode;
}
export interface AlertDialogDescriptionProps {
children?: React.ReactNode;
}
export interface AlertDialogActionProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children?: React.ReactNode;
}
export interface AlertDialogCancelProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
children?: React.ReactNode;
}
const AlertDialogContext = React.createContext<{
open?: boolean;
onOpenChange?: (open: boolean) => void;
}>({});
export function AlertDialog({ open, onOpenChange, children }: AlertDialogProps) {
return (
<AlertDialogContext.Provider value={{ open, onOpenChange }}>
{children}
</AlertDialogContext.Provider>
);
}
export function AlertDialogTrigger({ children, asChild }: AlertDialogTriggerProps) {
const { onOpenChange } = React.useContext(AlertDialogContext);
if (asChild && React.isValidElement(children)) {
return React.cloneElement(children, {
onClick: () => onOpenChange?.(true),
} as React.HTMLAttributes<HTMLElement>);
}
return <div onClick={() => onOpenChange?.(true)}>{children}</div>;
}
export function AlertDialogContent({ children }: AlertDialogContentProps) {
const { open, onOpenChange } = React.useContext(AlertDialogContext);
if (!open) return null;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="fixed inset-0 bg-black/50" onClick={() => onOpenChange?.(false)} />
<div className="relative z-50 w-full max-w-lg rounded-lg bg-white p-6 shadow-lg">
{children}
</div>
</div>
);
}
export function AlertDialogHeader({ children }: AlertDialogHeaderProps) {
return <div className="mb-4">{children}</div>;
}
export function AlertDialogFooter({ children }: AlertDialogFooterProps) {
return <div className="mt-4 flex justify-end gap-2">{children}</div>;
}
export function AlertDialogTitle({ children }: AlertDialogTitleProps) {
return <h2 className="text-lg font-semibold">{children}</h2>;
}
export function AlertDialogDescription({ children }: AlertDialogDescriptionProps) {
return <p className="text-sm text-gray-600">{children}</p>;
}
export function AlertDialogAction({ children, ...props }: AlertDialogActionProps) {
return (
<button
className="rounded-md bg-blue-600 px-4 py-2 text-sm text-white hover:bg-blue-700"
{...props}
>
{children}
</button>
);
}
export function AlertDialogCancel({ children, ...props }: AlertDialogCancelProps) {
const { onOpenChange } = React.useContext(AlertDialogContext);
return (
<button
className="rounded-md border border-gray-300 px-4 py-2 text-sm hover:bg-gray-100"
onClick={() => onOpenChange?.(false)}
{...props}
>
{children}
</button>
);
}

View File

@@ -0,0 +1,22 @@
import { Badge as BaseBadge } from "@mosaic/ui";
import type { BadgeProps as BaseBadgeProps, BadgeVariant as BaseBadgeVariant } from "@mosaic/ui";
// Extend BadgeVariant to include shadcn/ui variants
export type BadgeVariant = BaseBadgeVariant | "secondary" | "outline" | "default" | "destructive";
export interface BadgeProps extends Omit<BaseBadgeProps, "variant"> {
variant?: BadgeVariant;
}
// Map extended variants to base variants
const variantMap: Record<string, BaseBadgeVariant> = {
"secondary": "status-neutral",
"outline": "status-info",
"default": "status-neutral",
"destructive": "status-error",
};
export function Badge({ variant = "default", ...props }: BadgeProps) {
const mappedVariant = (variantMap[variant] || variant) as BaseBadgeVariant;
return <BaseBadge variant={mappedVariant} {...props} />;
}

View File

@@ -0,0 +1,26 @@
import { Button as BaseButton } from "@mosaic/ui";
import type { ButtonProps as BaseButtonProps } from "@mosaic/ui";
import type { ReactNode, ButtonHTMLAttributes } from "react";
// Extend Button to support additional variants
type ExtendedVariant = "primary" | "secondary" | "danger" | "ghost" | "outline" | "destructive" | "link";
export interface ButtonProps extends Omit<BaseButtonProps, "variant"> {
variant?: ExtendedVariant;
size?: "sm" | "md" | "lg" | "icon";
children: ReactNode;
}
// Map extended variants to base variants
const variantMap: Record<string, "primary" | "secondary" | "danger" | "ghost"> = {
"outline": "ghost",
"destructive": "danger",
"link": "ghost",
};
export function Button({ variant = "primary", size = "md", ...props }: ButtonProps) {
const mappedVariant = variantMap[variant] || variant;
const mappedSize = size === "icon" ? "sm" : size;
return <BaseButton variant={mappedVariant as "primary" | "secondary" | "danger" | "ghost"} size={mappedSize as "sm" | "md" | "lg"} {...props} />;
}

View File

@@ -0,0 +1,22 @@
export { Card, CardHeader, CardContent, CardFooter } from "@mosaic/ui";
export type { CardProps, CardHeaderProps, CardContentProps, CardFooterProps } from "@mosaic/ui";
// Additional Card sub-components for shadcn/ui compatibility
import * as React from "react";
export interface CardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {}
export interface CardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}
export const CardTitle = React.forwardRef<HTMLHeadingElement, CardTitleProps>(
({ className = "", ...props }, ref) => (
<h3 ref={ref} className={`text-2xl font-semibold leading-none tracking-tight ${className}`} {...props} />
)
);
CardTitle.displayName = "CardTitle";
export const CardDescription = React.forwardRef<HTMLParagraphElement, CardDescriptionProps>(
({ className = "", ...props }, ref) => (
<p ref={ref} className={`text-sm text-gray-600 ${className}`} {...props} />
)
);
CardDescription.displayName = "CardDescription";

View File

@@ -0,0 +1,2 @@
export { Input } from "@mosaic/ui";
export type { InputProps } from "@mosaic/ui";

View File

@@ -0,0 +1,17 @@
import * as React from "react";
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {}
export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
({ className = "", ...props }, ref) => {
return (
<label
ref={ref}
className={`text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 ${className}`}
{...props}
/>
);
}
);
Label.displayName = "Label";

View File

@@ -0,0 +1,102 @@
import * as React from "react";
export interface SelectProps {
value?: string;
onValueChange?: (value: string) => void;
defaultValue?: string;
disabled?: boolean;
children?: React.ReactNode;
}
export interface SelectTriggerProps {
id?: string;
className?: string;
children?: React.ReactNode;
}
export interface SelectContentProps {
children?: React.ReactNode;
}
export interface SelectItemProps {
value: string;
children?: React.ReactNode;
}
export interface SelectValueProps {
placeholder?: string;
}
const SelectContext = React.createContext<{
value?: string;
onValueChange?: (value: string) => void;
isOpen: boolean;
setIsOpen: (open: boolean) => void;
}>({ isOpen: false, setIsOpen: () => {} });
export function Select({ value, onValueChange, defaultValue, disabled, children }: SelectProps) {
const [isOpen, setIsOpen] = React.useState(false);
const [internalValue, setInternalValue] = React.useState(defaultValue);
const currentValue = value !== undefined ? value : internalValue;
const handleValueChange = (newValue: string) => {
if (value === undefined) {
setInternalValue(newValue);
}
onValueChange?.(newValue);
setIsOpen(false);
};
return (
<SelectContext.Provider value={{ value: currentValue, onValueChange: handleValueChange, isOpen, setIsOpen }}>
<div className="relative">{children}</div>
</SelectContext.Provider>
);
}
export function SelectTrigger({ id, className = "", children }: SelectTriggerProps) {
const { isOpen, setIsOpen } = React.useContext(SelectContext);
return (
<button
id={id}
type="button"
onClick={() => setIsOpen(!isOpen)}
className={`flex h-10 w-full items-center justify-between rounded-md border border-gray-300 bg-white px-3 py-2 text-sm ${className}`}
>
{children}
</button>
);
}
export function SelectValue({ placeholder }: SelectValueProps) {
const { value } = React.useContext(SelectContext);
return <span>{value || placeholder}</span>;
}
export function SelectContent({ children }: SelectContentProps) {
const { isOpen } = React.useContext(SelectContext);
if (!isOpen) return null;
return (
<div className="absolute z-50 mt-1 max-h-60 w-full overflow-auto rounded-md border border-gray-300 bg-white shadow-lg">
{children}
</div>
);
}
export function SelectItem({ value, children }: SelectItemProps) {
const { onValueChange } = React.useContext(SelectContext);
return (
<div
onClick={() => onValueChange?.(value)}
className="cursor-pointer px-3 py-2 text-sm hover:bg-gray-100"
>
{children}
</div>
);
}

View File

@@ -0,0 +1,28 @@
import * as React from "react";
export interface SwitchProps {
id?: string;
checked?: boolean;
onCheckedChange?: (checked: boolean) => void;
disabled?: boolean;
className?: string;
}
export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
({ id, checked, onCheckedChange, disabled, className = "" }, ref) => {
return (
<input
type="checkbox"
role="switch"
ref={ref}
id={id}
checked={checked}
onChange={(e) => onCheckedChange?.(e.target.checked)}
disabled={disabled}
className={`w-11 h-6 rounded-full ${className}`}
/>
);
}
);
Switch.displayName = "Switch";

View File

@@ -0,0 +1,2 @@
export { Textarea } from "@mosaic/ui";
export type { TextareaProps } from "@mosaic/ui";

View File

@@ -7,7 +7,7 @@
* - Automatic token refresh
*/
import { createAuthClient } from "better-auth/react";
import { credentialsClient } from "better-auth-credentials-plugin/client";
// Note: Credentials plugin import removed - better-auth has built-in credentials support
/**
* Auth client instance configured for Jarvis.
@@ -18,11 +18,8 @@ export const authClient = createAuthClient({
? window.location.origin
: process.env.BETTER_AUTH_URL || "http://localhost:3042",
// Plugins
plugins: [
// Credentials client for username/password auth
credentialsClient(),
],
// Plugins can be added here when needed
plugins: [],
});
/**

View File

@@ -4,7 +4,9 @@
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"noUnusedLocals": false,
"noUnusedParameters": false
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]