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
8 changes: 8 additions & 0 deletions .mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcpServers": {
"shadcn": {
"command": "npx",
"args": ["shadcn@latest", "mcp"]
}
}
}
17 changes: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,20 @@ Pre-commit hooks auto-run on commit (ruff format, ruff check, import sorting).
- Services: MariaDB 10.6, Redis (ports 13000, 11000)
- Python 3.14, Node 24
- Runs: `bench --site test_site run-tests --app hazelnode`

## MCP Servers

### shadcn MCP

The project includes shadcn MCP server configuration (`.mcp.json`) for AI-assisted component management. This enables:

- Browse and search shadcn components
- Install components using natural language
- Works with Claude Code, Cursor, and VS Code

**Example prompts:**
- "Show me all available components in the shadcn registry"
- "Add the button, dialog and card components to my project"
- "Create a contact form using components from the shadcn registry"

**Configuration:** `frontend/components.json` (style: new-york, baseColor: slate)
16 changes: 8 additions & 8 deletions e2e/pages/workflow-list.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ export class WorkflowListPage {
this.loadingSkeleton = page.locator('[class*="skeleton"]');
this.errorMessage = page.locator('text=Error loading workflows');

// Create dialog elements - HeadlessUI dialog panel
// Use content-based selectors as HeadlessUI transitions can leave role="dialog" in DOM
this.createDialog = page.locator('text=Create new workflow');
// Input has id="title"
this.titleInput = page.locator('input#title');
this.createButton = page.locator('button:has-text("Create"):not(:has-text("Creating"))');
this.cancelButton = page.locator('button:has-text("Cancel")');
// Create dialog elements - shadcn/Radix dialog
// Use data-testid for stable selectors with Radix dialogs
this.createDialog = page.locator('[data-testid="create-workflow-dialog"]');
// Input has id="title" within the dialog
this.titleInput = this.createDialog.locator('input#title');
this.createButton = this.createDialog.locator('button:has-text("Create"):not(:has-text("Creating"))');
this.cancelButton = this.createDialog.locator('button:has-text("Cancel")');
}

/**
Expand Down Expand Up @@ -74,7 +74,7 @@ export class WorkflowListPage {
await this.newWorkflowButton.waitFor({ state: 'visible', timeout: 10000 });
await expect(this.newWorkflowButton).toBeEnabled();
await this.newWorkflowButton.click();
// Wait for dialog title to appear (HeadlessUI transitions)
// Wait for dialog to appear (Radix uses data-state="open")
await this.createDialog.waitFor({ state: 'visible', timeout: 15000 });
// Wait for input to be visible and interactable
await this.titleInput.waitFor({ state: 'visible', timeout: 5000 });
Expand Down
56 changes: 30 additions & 26 deletions frontend/src/components/common/confirm-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { createContext, useCallback, useRef, useState } from 'react';
import {
Dialog,
DialogActions,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/catalyst-dialog';
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';

interface ConfirmOptions {
Expand Down Expand Up @@ -47,33 +49,35 @@ export function ConfirmDialogProvider({
<ConfirmContext.Provider value={confirm}>
{children}
<Dialog
size="sm"
open={state.isOpen}
onClose={() => fn.current && fn.current(false)}
onOpenChange={(open) => !open && fn.current && fn.current(false)}
>
<DialogTitle>{state.title}</DialogTitle>
<DialogContent className="sm:max-w-sm" data-testid="confirm-dialog">
<DialogHeader>
<DialogTitle>{state.title}</DialogTitle>
{state.description && (
<DialogDescription>{state.description}</DialogDescription>
)}
</DialogHeader>

{state.description && (
<DialogDescription>{state.description}</DialogDescription>
)}

<DialogActions>
<Button outline onClick={() => fn.current && fn.current(false)}>
Cancel
</Button>
<Button
color={
state.actionType == 'danger'
? 'rose'
: state.actionType == 'warning'
? 'yellow'
: 'lime'
}
onClick={() => fn.current && fn.current(true)}
>
Confirm
</Button>
</DialogActions>
<DialogFooter className="mt-4">
<Button outline onClick={() => fn.current && fn.current(false)}>
Cancel
</Button>
<Button
color={
state.actionType == 'danger'
? 'rose'
: state.actionType == 'warning'
? 'yellow'
: 'lime'
}
onClick={() => fn.current && fn.current(true)}
>
Confirm
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</ConfirmContext.Provider>
);
Expand Down
134 changes: 0 additions & 134 deletions frontend/src/components/ui/catalyst-dialog.tsx

This file was deleted.

42 changes: 23 additions & 19 deletions frontend/src/components/workflows/create-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,24 @@ import { useFrappeCreateDoc } from 'frappe-react-sdk';

import {
Dialog,
DialogActions,
DialogBody,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/catalyst-dialog';
} from '@/components/ui/dialog';

import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Button } from '@/components/ui/button';
import { useNavigate } from '@tanstack/react-router';
import { type DialogProps as HeadlessDialogProps } from '@headlessui/react';

export default function CreateWorkflowDialog({
open,
onClose,
...props
}: {
open: boolean | undefined;
onClose: (isOpen: boolean) => void;
} & HeadlessDialogProps) {
}) {
const [workflowTitle, setWorkflowTitle] = useState('');
const navigate = useNavigate();

Expand Down Expand Up @@ -53,11 +52,16 @@ export default function CreateWorkflowDialog({
}

return (
<Dialog open={open} onClose={onClose} {...props}>
<DialogTitle>Create new workflow</DialogTitle>
<Dialog open={open} onOpenChange={onClose}>
<DialogContent
className="sm:max-w-lg"
data-testid="create-workflow-dialog"
>
<DialogHeader>
<DialogTitle>Create new workflow</DialogTitle>
</DialogHeader>

<DialogBody>
<div>
<div className="mt-4">
<Label htmlFor="title">Title</Label>
<Input
value={workflowTitle}
Expand All @@ -67,16 +71,16 @@ export default function CreateWorkflowDialog({
placeholder="Send an email on form submit"
/>
</div>
</DialogBody>

<DialogActions>
<Button outline onClick={() => onClose(false)}>
Cancel
</Button>
<Button color="lime" onClick={handleCreateWorkflow} disabled={loading}>
{loading ? 'Creating...' : 'Create'}
</Button>
</DialogActions>
<DialogFooter className="mt-6">
<Button outline onClick={() => onClose(false)}>
Cancel
</Button>
<Button color="lime" onClick={handleCreateWorkflow} disabled={loading}>
{loading ? 'Creating...' : 'Create'}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
Loading
Loading