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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ jobs:
- name: Type-check web UI
run: pnpm run type-check:web

- name: Lint web UI
run: pnpm run lint:web

- name: Build web UI
run: pnpm run build:web

Expand Down
46 changes: 46 additions & 0 deletions src/web-ui/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import js from '@eslint/js';
import globals from 'globals';
import tseslint from 'typescript-eslint';
import reactHooks from 'eslint-plugin-react-hooks';
Expand All @@ -10,12 +11,24 @@ export default tseslint.config(
'coverage/**',
'node_modules/**',
'public/monaco-editor/**',
'src/**/*.test.ts',
'src/**/*.test.tsx',
'src/**/*.spec.ts',
'src/**/*.spec.tsx',
'src/**/*.example.tsx',
'src/component-library/components/registry.tsx',
'src/component-library/preview/**',
'src/shared/context-system/core/types/**',
'src/shared/context-menu-system/examples/**',
'src/tools/mermaid-editor/examples/**',
],
},
{
files: ['src/**/*.{ts,tsx}'],
extends: [js.configs.recommended, ...tseslint.configs.recommended],
linterOptions: {
reportUnusedDisableDirectives: 'warn',
},
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
Expand All @@ -37,13 +50,46 @@ export default tseslint.config(
},
rules: {
...reactHooks.configs.recommended.rules,
'react-hooks/exhaustive-deps': 'error',
'no-undef': 'off',
'no-unused-vars': 'off',
'no-use-before-define': 'off',
'no-case-declarations': 'warn',
'no-cond-assign': 'warn',
'no-control-regex': 'warn',
'no-empty': ['warn', { allowEmptyCatch: true }],
'no-useless-escape': 'warn',
'prefer-const': 'warn',
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
'@typescript-eslint/no-empty-object-type': 'warn',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'warn',
'@typescript-eslint/no-use-before-define': [
'error',
{
functions: false,
classes: true,
variables: true,
enums: true,
typedefs: true,
ignoreTypeReferences: true,
},
],
'@typescript-eslint/no-unused-expressions': 'warn',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
},
},
{
files: ['*.{ts,mts,cts}', '*.config.{ts,mts,cts}', 'vite.config.ts'],
extends: [js.configs.recommended, ...tseslint.configs.recommended],
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
Expand Down
2 changes: 1 addition & 1 deletion src/web-ui/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useCallback, useState, useRef } from 'react';
import { ChatProvider, useAIInitialization } from '../infrastructure';
import { ViewModeProvider } from '../infrastructure/contexts/ViewModeContext';
import { ViewModeProvider } from '../infrastructure/contexts/ViewModeProvider';
import { SSHRemoteProvider } from '../features/ssh-remote';
import AppLayout from './layout/AppLayout';
import { useCurrentModelConfig } from '../hooks/useModelConfigs';
Expand Down
6 changes: 5 additions & 1 deletion src/web-ui/src/app/components/NavPanel/MainNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ const MainNav: React.FC<MainNavProps> = ({
const toggleSection = useCallback((id: string) => {
setExpandedSections(prev => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
if (next.has(id)) {
next.delete(id);
} else {
next.add(id);
}
return next;
});
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,6 @@ export const BranchQuickSwitch: React.FC<BranchQuickSwitchProps> = ({
}
}, [isOpen, anchorRef]);

useEffect(() => {
if (isOpen && repositoryPath) {
loadBranches();
}
}, [isOpen, repositoryPath]);

useEffect(() => {
if (!isOpen) {
setSearchTerm('');
Expand Down Expand Up @@ -113,7 +107,7 @@ export const BranchQuickSwitch: React.FC<BranchQuickSwitchProps> = ({
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onClose]);

const loadBranches = async () => {
const loadBranches = useCallback(async () => {
setIsLoading(true);
try {
const cachedState = gitStateManager.getState(repositoryPath);
Expand Down Expand Up @@ -142,7 +136,13 @@ export const BranchQuickSwitch: React.FC<BranchQuickSwitchProps> = ({
} finally {
setIsLoading(false);
}
};
}, [repositoryPath]);

useEffect(() => {
if (isOpen && repositoryPath) {
void loadBranches();
}
}, [isOpen, loadBranches, repositoryPath]);

const filteredBranches = useMemo(() => {
let result = branches;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@ import NotificationButton from '../../TitleBar/NotificationButton';
import { AboutDialog } from '../../AboutDialog';
import { RemoteConnectDialog } from '../../RemoteConnectDialog';
import {
getRemoteConnectDisclaimerAgreed,
setRemoteConnectDisclaimerAgreed,
RemoteConnectDisclaimerContent,
} from '../../RemoteConnectDialog/RemoteConnectDisclaimer';
import {
getRemoteConnectDisclaimerAgreed,
setRemoteConnectDisclaimerAgreed,
} from '../../RemoteConnectDialog/remoteConnectDisclaimerStorage';
import { MERMAID_INTERACTIVE_EXAMPLE } from '@/flow_chat/constants/mermaidExamples';

const PersistentFooterActions: React.FC = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
isRemoteWorkspace,
type WorkspaceInfo,
} from '@/shared/types';
import { SSHContext } from '@/features/ssh-remote/SSHRemoteProvider';
import { SSHContext } from '@/features/ssh-remote/SSHRemoteContext';

interface WorkspaceItemProps {
workspace: WorkspaceInfo;
Expand Down Expand Up @@ -213,7 +213,7 @@ const WorkspaceItem: React.FC<WorkspaceItemProps> = ({
} finally {
setIsResettingWorkspace(false);
}
}, [isActive, isDefaultAssistantWorkspace, isResettingWorkspace, resetAssistantWorkspace, t, workspace.id, workspace.rootPath]);
}, [isActive, isDefaultAssistantWorkspace, isResettingWorkspace, resetAssistantWorkspace, t, workspace.id, workspace.rootPath, workspace.workspaceKind]);

const handleReveal = useCallback(async () => {
setMenuOpen(false);
Expand Down Expand Up @@ -274,10 +274,7 @@ const WorkspaceItem: React.FC<WorkspaceItemProps> = ({
}, [
setActiveWorkspace,
t,
workspace.id,
workspace.rootPath,
workspace.workspaceKind,
workspace.connectionId,
workspace,
]);

const handleCreateCodeSession = useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ const WorkspaceListSection: React.FC<WorkspaceListSectionProps> = ({ variant })
dropTargetRef.current = next;
return next;
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Intentionally empty: reads from refs, not closed-over state

const handleDragLeave = useCallback((workspaceId: string) => (event: React.DragEvent<HTMLDivElement>) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ import {
type RemoteConnectStatus,
} from '@/infrastructure/api/service-api/RemoteConnectAPI';
import {
getRemoteConnectDisclaimerAgreed,
setRemoteConnectDisclaimerAgreed,
RemoteConnectDisclaimerContent,
} from './RemoteConnectDisclaimer';
import {
getRemoteConnectDisclaimerAgreed,
setRemoteConnectDisclaimerAgreed,
} from './remoteConnectDisclaimerStorage';
import './RemoteConnectDialog.scss';

// ── Types ────────────────────────────────────────────────────────────
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,6 @@ import { Badge } from '@/component-library';
import { useI18n } from '@/infrastructure/i18n';
import './RemoteConnectDisclaimer.scss';

export const REMOTE_CONNECT_DISCLAIMER_KEY = 'bitfun:remote-connect:disclaimer-agreed:v1';

export const getRemoteConnectDisclaimerAgreed = (): boolean => {
try {
return localStorage.getItem(REMOTE_CONNECT_DISCLAIMER_KEY) === 'true';
} catch {
return false;
}
};

export const setRemoteConnectDisclaimerAgreed = (): void => {
try {
localStorage.setItem(REMOTE_CONNECT_DISCLAIMER_KEY, 'true');
} catch {
// Ignore storage failures and fall back to in-memory state.
}
};

interface RemoteConnectDisclaimerContentProps {
agreed: boolean;
onClose: () => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const REMOTE_CONNECT_DISCLAIMER_KEY = 'bitfun:remote-connect:disclaimer-agreed:v1';

export const getRemoteConnectDisclaimerAgreed = (): boolean => {
try {
return localStorage.getItem(REMOTE_CONNECT_DISCLAIMER_KEY) === 'true';
} catch {
return false;
}
};

export const setRemoteConnectDisclaimerAgreed = (): void => {
try {
localStorage.setItem(REMOTE_CONNECT_DISCLAIMER_KEY, 'true');
} catch {
// Ignore storage failures and fall back to in-memory state.
}
};
2 changes: 1 addition & 1 deletion src/web-ui/src/app/components/TitleBar/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const TitleBar: React.FC<TitleBarProps> = ({
} catch (error) {
log.error('Failed to open workspace', error);
}
}, [openWorkspace]);
}, [openWorkspace, t]);

const handleNewProject = useCallback(() => {
setShowNewProjectDialog(true);
Expand Down
16 changes: 8 additions & 8 deletions src/web-ui/src/app/components/panels/BranchSelectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,12 +61,6 @@ export const BranchSelectModal: React.FC<BranchSelectModalProps> = ({
const resolvedTitle = title ?? t('branchSelect.title');


useEffect(() => {
if (isOpen && repositoryPath) {
loadBranches();
}
}, [isOpen, repositoryPath]);

useEffect(() => {
if (!isOpen) {
setSearchTerm('');
Expand All @@ -91,7 +85,7 @@ export const BranchSelectModal: React.FC<BranchSelectModalProps> = ({
return () => document.removeEventListener('keydown', handleEscape);
}, [isOpen, onClose]);

const loadBranches = async () => {
const loadBranches = useCallback(async () => {
setIsLoading(true);
setError(null);
try {
Expand All @@ -103,7 +97,13 @@ export const BranchSelectModal: React.FC<BranchSelectModalProps> = ({
} finally {
setIsLoading(false);
}
};
}, [repositoryPath, t]);

useEffect(() => {
if (isOpen && repositoryPath) {
void loadBranches();
}
}, [isOpen, loadBranches, repositoryPath]);

const filteredBranches = useMemo<SelectableBranch[]>(() => {
let result = branches;
Expand Down
3 changes: 2 additions & 1 deletion src/web-ui/src/app/components/panels/FilesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ const FilesPanel: React.FC<FilesPanelProps> = ({
}
}
prevWorkspacePathRef.current = workspacePath;
}, [workspacePath, clearSearch]);
}, [workspacePath, clearSearch, onViewModeChange]);

// ===== File Operation Handlers =====

Expand Down Expand Up @@ -866,6 +866,7 @@ const FilesPanel: React.FC<FilesPanelProps> = ({
confirmText={inputDialog.type === 'newFile' ? t('dialog.newFile.confirm') : t('dialog.newFolder.confirm')}
cancelText={inputDialog.type === 'newFile' ? t('dialog.newFile.cancel') : t('dialog.newFolder.cancel')}
validator={(value) => {
// eslint-disable-next-line no-control-regex -- Windows filename rules explicitly forbid ASCII control characters.
if (!/^[^<>:"/\\|?*\x00-\x1F]+$/.test(value)) {
return t('validation.invalidFilename');
}
Expand Down
Loading
Loading