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
5 changes: 5 additions & 0 deletions .changeset/add-rtk-query-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@bilbomd/ui': minor
---

Add comprehensive TypeScript types to RTK Query endpoints. Previously, 36 out of 69 RTK Query endpoints (52%) lacked explicit type parameters, causing result types to default to `any` and bypass TypeScript's type safety. This change adds proper generic type parameters `<ResultType, ArgType>` to all untyped endpoints across authApiSlice, configsApiSlice, statsApiSlice, adminApiSlice, usersApiSlice, and jobsApiSlice. Benefits include compile-time type safety, better IDE autocomplete, and self-documenting API contracts.
8 changes: 7 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,13 @@
"Bash(gh pr checks:*)",
"Bash(pnpm -F @bilbomd/worker test:*)",
"Bash(gh pr view:*)",
"Bash(git fetch:*)"
"Bash(git fetch:*)",
"Bash(pnpm vitest:*)",
"Bash(grep:*)",
"Bash(done)",
"Bash(pnpm tsc:*)",
"Bash(git restore:*)",
"Bash(pnpm:*)"
]
}
}
66 changes: 66 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,72 @@ Environment: copy `infra/.env.example` to `infra/.env.local`.
- All modules use ESM (`"type": "module"`)
- TypeScript target: ES2022, module: NodeNext

## Development Workflow

**IMPORTANT**: When working on code changes, always follow this workflow:

### 1. Create a New Git Branch

Before starting any code changes, create a new branch using the appropriate naming convention:

```bash
git checkout -b <prefix>/<descriptive-name>
# Examples:
git checkout -b feature/add-user-roles
git checkout -b fix/job-timeout
git checkout -b refactor/add-rtk-query-types
```

See [Git Branch Naming Convention](#git-branch-naming-convention) for prefix guidelines.

### 2. Verify All Checks Pass Before Completion

**Before considering work complete**, ensure all of the following pass without errors:

```bash
# 1. Linting - must pass with zero warnings/errors
pnpm lint

# 2. Build - must complete successfully
pnpm build

# 3. Tests - all tests must pass
pnpm test
```

**For package-specific work**, run the checks filtered to that package:

```bash
# Example for UI package
pnpm -F @bilbomd/ui lint
pnpm -F @bilbomd/ui build
pnpm -F @bilbomd/ui test

# Example for backend package
pnpm -F @bilbomd/backend lint
pnpm -F @bilbomd/backend build
pnpm -F @bilbomd/backend test
```

### 3. Fix Any Issues

If any of the checks fail:
- **Linting errors**: Fix ESLint warnings and errors before committing
- **Build errors**: Resolve TypeScript errors and build issues
- **Test failures**: Fix failing tests or update tests if behavior changed intentionally

**Do not commit or push code that fails any of these checks.**

### 4. Commit and Push

Once all checks pass:

```bash
git add -A
git commit -m "descriptive commit message"
git push origin <branch-name>
```

## Versioning

Uses **Changesets** for per-package versioning. After code changes:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,10 @@ const validationSchemas = [
return false
}
const chainStart = ctx.from[2].value.chains.find(
(x) => x.id === ctx.parent.chainid
(x: Chain) => x.id === ctx.parent.chainid
).first_res
const chainEnd = ctx.from[2].value.chains.find(
(x) => x.id === ctx.parent.chainid
(x: Chain) => x.id === ctx.parent.chainid
).last_res
if (value >= chainStart && value <= chainEnd) {
return true
Expand Down
9 changes: 7 additions & 2 deletions apps/ui/src/components/ErrorFallback.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
const ErrorFallback = ({ error }) => {
import { FallbackProps } from 'react-error-boundary'

const ErrorFallback = ({ error, resetErrorBoundary: _ }: FallbackProps) => {
const errorMessage =
error instanceof Error ? error.message : 'An unknown error occurred'

return (
<div role='alert'>
<p>
Something went wrong. Please send a screenshot of the error message to
Scott.
</p>
<pre style={{ color: 'red' }}>{error.message}</pre>
<pre style={{ color: 'red' }}>{errorMessage}</pre>
</div>
)
}
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/components/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const Home = ({ title = 'BilboMD' }) => {
useEffect(() => {
const verifyRefreshToken = async () => {
try {
await refresh({})
await refresh(undefined)
setTrueSuccess(true)
} catch (error) {
console.error('verifyRefreshToken error:', error)
Expand Down
6 changes: 3 additions & 3 deletions apps/ui/src/components/Loadable.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Suspense } from 'react'
import { Suspense, ComponentType } from 'react'
import Loader from './Loader'

const Loadable = (Component) => {
const LoadableComponent = (props) => (
const Loadable = <P extends object>(Component: ComponentType<P>) => {
const LoadableComponent = (props: P) => (
<Suspense fallback={<Loader />}>
<Component {...props} />
</Suspense>
Expand Down
10 changes: 8 additions & 2 deletions apps/ui/src/components/__tests__/ErrorFallback.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import ErrorFallback from '../ErrorFallback'

test('renders error message and alert role', () => {
// Mock error object
const error = { message: 'Test error message' }
const error = new Error('Test error message')
const resetErrorBoundary = vi.fn()

// Render the component with the mock error
const { getByRole, getByText } = render(<ErrorFallback error={error} />)
const { getByRole, getByText } = render(
<ErrorFallback
error={error}
resetErrorBoundary={resetErrorBoundary}
/>
)

// Check if the alert role is present
expect(getByRole('alert')).toBeInTheDocument()
Expand Down
5 changes: 3 additions & 2 deletions apps/ui/src/features/admin/QueueDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ const QueueDetailsPage = () => {
setAnchorEl(null)
setMenuJobId(null)
}
const { data: queues, isLoading, error } = useGetQueuesQuery({})
const { data: queues, isLoading, error } = useGetQueuesQuery(undefined)
const {
data: jobs,
isLoading: jobsLoading,
Expand Down Expand Up @@ -138,7 +138,8 @@ const QueueDetailsPage = () => {

console.log('Jobs:', jobs)

const typedJobs: FrontendBullMQJob<BilboMDJobData>[] = jobs?.jobs ?? []
const typedJobs: FrontendBullMQJob<BilboMDJobData>[] = (jobs?.jobs ??
[]) as unknown as FrontendBullMQJob<BilboMDJobData>[]

const filteredJobs = typedJobs.filter((job) => {
const matchesType = typeFilter === 'All' || job.data?.type === typeFilter
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/features/admin/QueueOverviewPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const QueueOverviewPanel = () => {
error,
isFetching
} = useGetQueuesQuery(
{},
undefined,
{
pollingInterval: pollingEnabled ? 5000 : 0
}
Expand Down
47 changes: 32 additions & 15 deletions apps/ui/src/features/alphafoldjob/NewAlphaFoldJobForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,27 @@ const NewAlphaFoldJob = ({ mode = 'authenticated' }: NewJobFormProps) => {
const [addNewPublicJob, { isSuccess: isAnonSuccess, data: anonJobResponse }] =
useAddNewPublicJobMutation()
const isSuccess = mode === 'anonymous' ? isAnonSuccess : isAuthSuccess
const jobResponse = mode === 'anonymous' ? anonJobResponse : authJobResponse

// Transform responses to expected shape
const publicJobResponse =
anonJobResponse && mode === 'anonymous'
? {
resultUrl: anonJobResponse.resultUrl,
publicId: anonJobResponse.publicId,
md_engine: anonJobResponse.md_engine
}
: undefined

const authSuccessResponse =
authJobResponse && mode === 'authenticated'
? {
message: 'Job submitted successfully',
jobid: authJobResponse.id,
uuid: authJobResponse.mongo.uuid,
md_engine: authJobResponse.mongo.md_engine
}
: undefined

const [isPerlmutterUnavailable, setIsPerlmutterUnavailable] = useState(false)
const handleStatusCheck = (isUnavailable: boolean) => {
setIsPerlmutterUnavailable(isUnavailable)
Expand All @@ -484,6 +504,8 @@ const NewAlphaFoldJob = ({ mode = 'authenticated' }: NewJobFormProps) => {
if (configIsLoading) return <LinearProgress />
if (configError)
return <Alert severity="error">Error loading configuration</Alert>
if (!config)
return <Alert severity="error">Configuration not available</Alert>

const useAlphaFold = config.enableBilboMdAlphaFold?.toLowerCase() === 'true'
const useNersc = config.useNersc?.toLowerCase() === 'true'
Expand All @@ -504,10 +526,7 @@ const NewAlphaFoldJob = ({ mode = 'authenticated' }: NewJobFormProps) => {
md_engine: 'charmm'
}

const onSubmit = async (
values: NewAlphaFoldJobFormValues,
{ setStatus }: { setStatus: (status: string) => void }
) => {
const onSubmit = async (values: NewAlphaFoldJobFormValues) => {
setSubmitError(null)
const form = new FormData()
form.append('title', values.title)
Expand All @@ -526,11 +545,9 @@ const NewAlphaFoldJob = ({ mode = 'authenticated' }: NewJobFormProps) => {
}

try {
const newJob =
mode === 'anonymous'
? await addNewPublicJob(form).unwrap()
: await addNewAlphaFoldJob(form).unwrap()
setStatus(newJob)
await (mode === 'anonymous'
? addNewPublicJob(form).unwrap()
: addNewAlphaFoldJob(form).unwrap())
} catch (error) {
console.error('rejected', error)
setSubmitError(
Expand Down Expand Up @@ -576,17 +593,17 @@ const NewAlphaFoldJob = ({ mode = 'authenticated' }: NewJobFormProps) => {
</Link>
</Alert>
) : isSuccess ? (
mode === 'anonymous' ? (
mode === 'anonymous' && publicJobResponse ? (
<PublicJobSuccessAlert
jobResponse={jobResponse}
jobResponse={publicJobResponse}
jobType="AF"
/>
) : (
) : authSuccessResponse ? (
<JobSuccessAlert
jobResponse={jobResponse}
jobResponse={authSuccessResponse}
jobType="AF"
/>
)
) : null
) : (
<Formik<NewAlphaFoldJobFormValues>
initialValues={initialValues}
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/features/auth/LogOut.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const LogOut = () => {

const onClickLogout = async () => {
setOpen(false)
await sendLogout({})
await sendLogout(undefined)
void navigate('/')
}

Expand Down
4 changes: 4 additions & 0 deletions apps/ui/src/features/auth/MagickLinkAuth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const MagickLinkAuth = () => {
let timeoutId: NodeJS.Timeout

const authenticateOTP = async () => {
if (!otp) {
setAuthErrorMsg('No OTP provided')
return
}
try {
const { accessToken } = await login({ otp }).unwrap()
dispatch(setCredentials({ accessToken }))
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/features/auth/OrcidConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const validationSchema = Yup.object().shape({
})

export default function OrcidConfirmation() {
const { data: profile, isLoading, isError } = useGetOrcidSessionQuery({})
const { data: profile, isLoading, isError } = useGetOrcidSessionQuery(undefined)
const [finalizeOrcid] = useFinalizeOrcidMutation()

const formik = useFormik({
Expand Down
2 changes: 1 addition & 1 deletion apps/ui/src/features/auth/PersistLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const PersistLogin = () => {
useEffect(() => {
const verifyRefreshToken = async () => {
try {
await refresh({}).unwrap()
await refresh(undefined).unwrap()
setTrueSuccess(true)
} catch (err) {
console.error('Refresh failed:', err)
Expand Down
2 changes: 2 additions & 0 deletions apps/ui/src/features/auth/Welcome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const Welcome: React.FC<WelcomeProps> = ({ mode }: WelcomeProps) => {
<Alert severity="info">Loading system configuration...</Alert>
) : configError ? (
<Alert severity="error">Failed to load system configuration.</Alert>
) : !config ? (
<Alert severity="error">Configuration not available.</Alert>
) : (
<Box>
<Typography sx={{ m: 0 }}>
Expand Down
Loading
Loading