Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
7ae502e
refactor(eslint): implement custom ESLint rules for React and TypeScript
stefanoamorelli Jun 29, 2025
34b795a
chore(package): add lint scripts and update package manager
stefanoamorelli Jun 29, 2025
911d4fe
feat(ci): introduce frontend checks GitHub Action
stefanoamorelli Jun 29, 2025
2fe0a83
feattypescript: add IPlan type import
stefanoamorelli Jun 29, 2025
efb0382
fix/runtime: ensure session status type safety
stefanoamorelli Jun 29, 2025
20e4503
feat(sidebar): improve session status type safety
stefanoamorelli Jun 29, 2025
b55ccd7
fix(signin): use submit type for sign in button
stefanoamorelli Jun 29, 2025
ff61be7
feat(env): add environment variable loading
stefanoamorelli Jun 29, 2025
ee00213
fix(markdown): remove node prop from code render
stefanoamorelli Jun 29, 2025
2a415a3
refactor(button): simplify LearnPlanButton state management
stefanoamorelli Jun 29, 2025
4f27609
feat(accessibility): improve keyboard navigation for file modal and c…
stefanoamorelli Jun 29, 2025
4f178d3
fix(a11y): enable keyboard navigation for content header button
stefanoamorelli Jun 29, 2025
22c61bf
fix(404): escape apostrophe in page not found message
stefanoamorelli Jun 29, 2025
05d50da
fix(Plans): add id and htmlFor to plan title input
stefanoamorelli Jun 29, 2025
4021a46
style(PlanList): remove unused props
stefanoamorelli Jun 29, 2025
cc4cc21
style(cleanup): remove redundant state initialization
stefanoamorelli Jun 29, 2025
3c9ebe9
refactor(sidebar): remove unused imports and props
stefanoamorelli Jun 29, 2025
a870e05
fix(statusicon): handle awaiting_input status
stefanoamorelli Jun 29, 2025
cdb5350
refactor(provider): extract wrapper component
stefanoamorelli Jun 29, 2025
d7cebe3
fix(search): add quotes to search term
stefanoamorelli Jun 29, 2025
ed0ecd0
refactor(Layout): simplify UI component props
stefanoamorelli Jun 29, 2025
a1c1e2d
fix(modal): remove unused tabIndex attribute
stefanoamorelli Jun 29, 2025
7cece54
fix(status): handle complete status
stefanoamorelli Jun 29, 2025
c03a7ed
fix(settings): remove unused imports
stefanoamorelli Jun 29, 2025
f71190f
refactor(statusicon): simplify icon logic
stefanoamorelli Jun 29, 2025
6fd42bd
fix(session management): prevent Promise resolution
stefanoamorelli Jun 29, 2025
6d88150
feat(modal): improve accessibility and keyboard navigation
stefanoamorelli Jun 29, 2025
c36f89c
refactor(types): simplify datamodel imports
stefanoamorelli Jun 29, 2025
8f33817
fix(chat): prevent crashes when processing messages
stefanoamorelli Jun 29, 2025
81427d1
fix(chat): ignore JSON parsing errors and improve fallback
stefanoamorelli Jun 29, 2025
d0d607c
refactor(plan): simplify component logic and remove unused imports
stefanoamorelli Jun 29, 2025
6e04c83
refactor(chat): simplify component logic and remove unused state
stefanoamorelli Jun 29, 2025
3488d0c
refactor(chatinput): simplify component logic and remove unused imports
stefanoamorelli Jun 29, 2025
6240d9e
refactor(chat): simplify component logic and improve error handling
stefanoamorelli Jun 29, 2025
c186032
refactor(approval): simplify approval buttons logic
stefanoamorelli Jun 29, 2025
44fc927
feat(accessibility): improve keyboard navigation in chat detail viewer
stefanoamorelli Jun 29, 2025
33f888d
fix(feedback): escape apostrophe in feedback form text
stefanoamorelli Jun 29, 2025
db1a95a
feat(modal): improve keyboard navigation and accessibility
stefanoamorelli Jun 29, 2025
264b83d
fix(api): handle missing or invalid plan data
stefanoamorelli Jun 29, 2025
8700925
fix(modal): improve keyboard navigation
stefanoamorelli Jun 29, 2025
e880e77
fix: handle error cases in chat view
stefanoamorelli Jun 29, 2025
bea7660
refactor(chatinput): simplify component props
stefanoamorelli Jun 29, 2025
414ac81
feat(detail viewer): add view mode property and control handover form
stefanoamorelli Jun 29, 2025
905db80
fix(chat): remove onBlur event handler
stefanoamorelli Jun 29, 2025
72d5d79
refactor(RenderMessage): rename props for clarity
stefanoamorelli Jun 29, 2025
62bde8f
Merge branch 'main' into feat/linting-2
stefanoamorelli Jun 29, 2025
427be18
fix(detail viewer): simplify state management and event handling
stefanoamorelli Jun 30, 2025
16d07d1
Merge branch 'main' into feat/linting-2
stefanoamorelli Jun 30, 2025
9fd728b
fix(components): remove unused imports and simplify code
stefanoamorelli Jun 30, 2025
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
66 changes: 66 additions & 0 deletions .github/workflows/frontend-checks.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Frontend Checks

on:
push:
branches:
- main
paths:
- "frontend/**"
pull_request:
branches:
- main
paths:
- "frontend/**"

permissions:
contents: read

jobs:
frontend-lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "18"
cache: "yarn"
cache-dependency-path: "frontend/yarn.lock"

- name: Install dependencies
working-directory: frontend
run: yarn install --frozen-lockfile

- name: Run ESLint (must have 0 errors)
working-directory: frontend
run: |
echo "🧹 Running ESLint - checking for 0 errors..."

# Run lint and capture output
LINT_OUTPUT=$(yarn lint 2>&1)
LINT_EXIT_CODE=$?

echo "$LINT_OUTPUT"

# Parse the output to check for errors (but not warnings)
if [ $LINT_EXIT_CODE -ne 0 ]; then
echo "❌ ESLint failed with exit code $LINT_EXIT_CODE"
exit 1
else
echo "✅ ESLint passed with 0 errors!"
fi

- name: Run TypeScript check
working-directory: frontend
run: |
echo "🔍 Running TypeScript check..."
yarn typecheck

if [ $? -eq 0 ]; then
echo "✅ TypeScript check passed!"
else
echo "❌ TypeScript check failed"
exit 1
fi
50 changes: 50 additions & 0 deletions frontend/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: [
'react',
'@typescript-eslint',
'react-hooks',
'jsx-a11y',
],
rules: {
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'warn',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn',
},
settings: {
react: {
version: 'detect',
},
},
ignorePatterns: [
'node_modules/',
'public/',
'.cache/',
'gatsby-*.js',
'*.config.js',
],
};
1 change: 1 addition & 0 deletions frontend/gatsby-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ fs.access(envFile, fs.constants.F_OK, (err) => {
}
});

// eslint-disable-next-line @typescript-eslint/no-var-requires
require("dotenv").config({
path: envFile,
});
Expand Down
5 changes: 4 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
"build": "gatsby clean && rm -rf ../src/magentic_ui/backend/web/ui && PREFIX_PATH_VALUE='' gatsby build --prefix-paths && rsync -a --delete public/ ../src/magentic_ui/backend/web/ui/",
"serve": "gatsby serve",
"clean": "gatsby clean",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --max-warnings 150",
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"typecheck": "tsc --noEmit"
},
"dependencies": {
Expand Down Expand Up @@ -68,5 +70,6 @@
"prismjs": "1.30.0",
"cookie": "0.7.0",
"base-x": "3.0.11"
}
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
4 changes: 1 addition & 3 deletions frontend/src/components/common/Icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@ interface IconProps {
}

const IconWrapper: React.FC<IconProps & { children: React.ReactNode }> = ({
className = "",
size = 16,
tooltip,
children
}) => {
const uniqueId = useId();
const groupClass = `tooltip-group-${uniqueId.replace(/:/g, '')}`;

return (
<div className={`relative ${groupClass} inline-flex`}>
{children}
Expand Down
26 changes: 22 additions & 4 deletions frontend/src/components/common/filerenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ const FileModal: React.FC<FileModalProps> = ({
file,
content,
}) => {
const [isFullScreen, setIsFullScreen] = useState<boolean>(false);
const [isFullScreen] = useState<boolean>(false);
const modalRef = React.useRef<HTMLDivElement>(null);
const [downloadUrl, setDownloadUrl] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
Expand Down Expand Up @@ -171,9 +171,9 @@ const FileModal: React.FC<FileModalProps> = ({

if (!isOpen || !file) return null;

const toggleFullScreen = (): void => {
setIsFullScreen(!isFullScreen);
};
// const toggleFullScreen = (): void => {
// setIsFullScreen(!isFullScreen);
// };

// Handle click outside the modal content
const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {
Expand Down Expand Up @@ -254,12 +254,22 @@ const FileModal: React.FC<FileModalProps> = ({
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50"
onClick={handleBackdropClick}
onKeyDown={(e) => {
if (e.key === 'Escape') {
onClose();
}
}}
role="presentation"
aria-label="File modal"
tabIndex={-1}
>
<div
ref={modalRef}
className={`bg-white rounded-lg shadow-lg overflow-hidden ${
isFullScreen ? "fixed inset-0" : "max-w-4xl w-full max-h-[85vh]"
}`}
role="dialog"
aria-modal="true"
>
{/* Header */}
<div className="flex justify-between items-center p-4 border-b">
Expand Down Expand Up @@ -406,6 +416,10 @@ const FileCard = memo<FileCardProps>(({ file, onFileClick }) => {
<div
className="group relative flex flex-col overflow-hidden rounded-lg border border-gray-200 hover:border-blue-500 shadow-sm hover:shadow-md cursor-pointer transition-all"
onClick={() => onFileClick(file)}
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && onFileClick(file)}
role="button"
tabIndex={0}
aria-label={`View ${file.name}`}
>
<ImageThumbnail file={file} />
<div className="p-2 bg-white border-t w-full">
Expand All @@ -422,6 +436,10 @@ const FileCard = memo<FileCardProps>(({ file, onFileClick }) => {
<div
className="group relative flex flex-col items-center p-3 rounded-lg border border-gray-200 hover:border-blue-500 cursor-pointer transition-colors shadow-sm hover:shadow-md"
onClick={() => onFileClick(file)}
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && onFileClick(file)}
role="button"
tabIndex={0}
aria-label={`View ${file.name}`}
>
<IconComponent className="w-8 h-8 mb-2 text-blue-500" />
<span className="text-xs text-center truncate w-full" title={file.name}>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/common/markdownrender.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
{children}
</a>
),
code: ({ node, className, children, ...props }) => {
code: ({ className, children, ...props }) => {
const match = /language-(\w+)/.exec(className || "");
const language = match ? match[1] : "";
const inline = !language;
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/contentheader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ const ContentHeader = ({
<div
className="flex items-center space-x-2 cursor-pointer"
onClick={() => setIsEmailModalOpen(true)}
onKeyDown={(e) => (e.key === 'Enter' || e.key === ' ') && setIsEmailModalOpen(true)}
role="button"
tabIndex={0}
aria-label="View or update your profile"
>
{user.avatar_url ? (
<img
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/features/Plans/LearnPlanButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const LearnPlanButton: React.FC<LearnPlanButtonProps> = ({
}) => {
const [isLearning, setIsLearning] = useState(false);
const [isLearned, setIsLearned] = useState(false);
const [error, setError] = useState<string | null>(null);
const [, setError] = useState<string | null>(null);
const { user, darkMode } = useContext(appContext);
const planAPI = new PlanAPI();

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/components/features/Plans/PlanCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -268,10 +268,11 @@ const PlanCard: React.FC<PlanCardProps> = ({
{isModalOpen && (
<div>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">
<label htmlFor="plan-title" className="block text-sm font-medium mb-1">
Plan Title
</label>
<Input
id="plan-title"
type="text"
value={localTask}
onChange={(e) => setLocalTask(e.target.value)}
Expand Down
25 changes: 11 additions & 14 deletions frontend/src/components/features/Plans/PlanList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ const normalizePlanData = (
task: planData.task || defaultTask,
steps: Array.isArray(planData.steps)
? planData.steps.map((step: any) => ({
title: step.title || "Untitled Step",
details: step.details || "",
enabled: step.enabled !== false,
open: step.open || false,
agent_name: step.agent_name || "",
}))
title: step.title || "Untitled Step",
details: step.details || "",
enabled: step.enabled !== false,
open: step.open || false,
agent_name: step.agent_name || "",
}))
: [],
user_id: planData.user_id || userId,
session_id: planData.session_id || null,
Expand All @@ -48,7 +48,6 @@ const normalizePlanData = (

const PlanList: React.FC<PlanListProps> = ({
onTabChange,
onSelectSession,
onCreateSessionFromPlan,
}) => {
const [plans, setPlans] = useState<IPlan[]>([]);
Expand All @@ -57,7 +56,7 @@ const PlanList: React.FC<PlanListProps> = ({
const { user } = useContext(appContext);
const planAPI = new PlanAPI();
const sessionAPI = new SessionAPI();
const [isCreatingPlan, setIsCreatingPlan] = useState<boolean>(false);
const [, setIsCreatingPlan] = useState<boolean>(false);
const fileInputRef = useRef<HTMLInputElement>(null);
const [isDragging, setIsDragging] = useState<boolean>(false);
const [searchTerm, setSearchTerm] = useState<string>("");
Expand Down Expand Up @@ -157,8 +156,7 @@ const PlanList: React.FC<PlanListProps> = ({
} catch (err) {
console.error("Error creating new plan:", err);
message.error(
`Failed to create plan: ${
err instanceof Error ? err.message : String(err)
`Failed to create plan: ${err instanceof Error ? err.message : String(err)
}`
);
} finally {
Expand Down Expand Up @@ -202,9 +200,8 @@ const PlanList: React.FC<PlanListProps> = ({
} catch (err) {
console.error("Error importing plan:", err);
message.error({
content: `Failed to import plan: ${
err instanceof Error ? err.message : String(err)
}`,
content: `Failed to import plan: ${err instanceof Error ? err.message : String(err)
}`,
duration: 5,
});
}
Expand Down Expand Up @@ -365,7 +362,7 @@ const PlanList: React.FC<PlanListProps> = ({
<SearchOutlined
style={{ fontSize: "48px", marginBottom: "16px" }}
/>
<p>No plans found matching "{searchTerm}"</p>
<p>No plans found matching &quot;{searchTerm}&quot;</p>
<Button
type="link"
onClick={() => setSearchTerm("")}
Expand Down
12 changes: 3 additions & 9 deletions frontend/src/components/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,13 @@ type Props = {
};

const MagenticUILayout = ({
meta,
title,
link,
showHeader = true,
restricted = false,
activeTab,
onTabChange,
}: Props) => {
const { darkMode, user, setUser } = React.useContext(appContext);
const { sidebar } = useConfigStore();
const { isExpanded } = sidebar;
const [isMobileMenuOpen, setIsMobileMenuOpen] = React.useState(false);
const [, setIsMobileMenuOpen] = React.useState(false);

// Mimic sign-in: if no user or user.email, set default user and localStorage
React.useEffect(() => {
Expand All @@ -51,9 +46,8 @@ const MagenticUILayout = ({
}, [link]);

React.useEffect(() => {
document.getElementsByTagName("html")[0].className = `${
darkMode === "dark" ? "dark bg-primary" : "light bg-primary"
}`;
document.getElementsByTagName("html")[0].className = `${darkMode === "dark" ? "dark bg-primary" : "light bg-primary"
}`;
}, [darkMode]);

const layoutContent = (
Expand Down
5 changes: 1 addition & 4 deletions frontend/src/components/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ import {
Tooltip,
Select,
Tabs,
Input as AntInput,
Upload,
message,
} from "antd";
import { InfoCircleOutlined, UploadOutlined } from "@ant-design/icons";
import { Plus } from "lucide-react";

const { TextArea } = AntInput;

interface SettingsMenuProps {
isOpen: boolean;
onClose: () => void;
Expand Down Expand Up @@ -229,7 +226,7 @@ action_guard_client: *client
if (!hasAllClients) {
message.error(
"YAML must include all required model clients: " +
requiredClients.join(", ")
requiredClients.join(", ")
);
return false;
}
Expand Down
Loading
Loading