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
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,22 @@ jobs:
- name: Set up Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 20
node-version: 24
cache: "npm"
cache-dependency-path: src/frontend/package-lock.json

- name: Install dependencies
working-directory: src/frontend
run: npm ci

- name: Lint
working-directory: src/frontend
run: npm run lint

- name: Check formatting
working-directory: src/frontend
run: npm run format:check

- name: Typecheck
working-directory: src/frontend
run: npm run typecheck
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
src/frontend/coverage/
htmlcov/
.tox/
.nox/
Expand Down
9 changes: 9 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,12 @@ repos:
- id: ruff-check
args: [--fix]
- id: ruff-format

- repo: local
hooks:
- id: oxfmt
name: oxfmt
entry: npx --prefix src/frontend oxfmt --config src/frontend/.oxfmtrc.json
language: system
types_or: [javascript, jsx, ts, tsx, css, json]
files: ^src/frontend/
25 changes: 24 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help install install-python-dev dev build preview typecheck frontend-test clean up up down docker-build refresh restart build-serve python-lint python-lint-fix python-format python-format-check python-typecheck python-dead-code python-checks python-test-lint python-test-lint-fix python-test-format python-test-format-check python-test-typecheck python-test-checks python-coverage prek-install
.PHONY: help install install-python-dev dev build preview typecheck frontend-lint frontend-format frontend-format-check frontend-checks frontend-test clean up up down docker-build refresh restart build-serve python-lint python-lint-fix python-format python-format-check python-typecheck python-dead-code python-checks python-test-lint python-test-lint-fix python-test-format python-test-format-check python-test-typecheck python-test-checks python-coverage prek-install

# Frontend directory
FRONTEND_DIR := src/frontend
Expand All @@ -17,6 +17,10 @@ help:
@echo " build-serve - Build and serve via Flask (test prod build without Docker)"
@echo " preview - Preview production build"
@echo " typecheck - Run TypeScript type checking"
@echo " frontend-lint - Run Oxlint against frontend code"
@echo " frontend-format - Format frontend code with Oxfmt"
@echo " frontend-format-check - Check frontend formatting with Oxfmt"
@echo " frontend-checks - Run all frontend static analysis checks"
@echo " frontend-test - Run frontend unit tests"
@echo " install-python-dev - Sync Python runtime + dev tooling with uv"
@echo " python-lint - Run Ruff against Python backend code"
Expand Down Expand Up @@ -52,6 +56,8 @@ install:
install-python-dev:
@echo "Syncing Python runtime and dev tooling with uv..."
uv sync --locked --extra browser
@echo "Installing prek git hooks..."
uv run prek install

# Start development server
dev:
Expand Down Expand Up @@ -137,6 +143,23 @@ prek-install:
@echo "Installing prek git hooks..."
uv run prek install

# Frontend linting
frontend-lint:
@echo "Running Oxlint..."
cd $(FRONTEND_DIR) && npm run lint

# Frontend formatting
frontend-format:
@echo "Formatting frontend code with Oxfmt..."
cd $(FRONTEND_DIR) && npm run format

frontend-format-check:
@echo "Checking frontend formatting with Oxfmt..."
cd $(FRONTEND_DIR) && npm run format:check

# All frontend static analysis
frontend-checks: frontend-lint frontend-format-check typecheck

# Run frontend unit tests
frontend-test:
@echo "Running frontend unit tests..."
Expand Down
15 changes: 15 additions & 0 deletions src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ src/
## Frontend Development

### Prerequisites

- Node.js (v16 or higher)
- npm or yarn

### Quick Start

From the project root:

```bash
# Install dependencies
make install
Expand All @@ -44,19 +46,22 @@ make typecheck
```

Alternatively, from `src/frontend`:

```bash
npm install
npm run dev
npm run build
```

### Technology Stack

- **Framework**: React 18 with TypeScript
- **Build Tool**: Vite 5
- **Styling**: TailwindCSS 3
- **Communication**: WebSocket for real-time updates

### Key Features

- **Search Interface**: Real-time book search with filtering
- **Download Queue**: Live status updates via WebSocket
- **Details Modal**: Rich book information display
Expand All @@ -65,34 +70,44 @@ npm run build
## Development Tips

### Hot Module Replacement (HMR)

The development server supports HMR for instant feedback during development.

### API Integration

The frontend communicates with the Flask backend via:

- REST API endpoints (`/api/*`)
- WebSocket connection (`ws://localhost:8084/ws`)

### Building for Production

The production build is optimized and minified:

```bash
make build
```

Output is generated in `src/frontend/dist/`

### Type Safety

Run TypeScript checks without building:

```bash
make typecheck
```

## Debugging

### Development Server Issues

- Ensure port 5173 is available
- Check that the backend is running on port 8084
- Verify WebSocket connection in browser console

### Build Issues

- Clear `node_modules` and reinstall: `make clean && make install`
- Check Node.js version compatibility
- Verify TypeScript configuration
12 changes: 12 additions & 0 deletions src/frontend/.oxfmtrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 100,
"singleQuote": true,
"trailingComma": "all",
"semi": true,
"tabWidth": 2,
"useTabs": false,
"sortImports": true,
"sortTailwindcss": true,
"ignorePatterns": ["dist/**"]
}
97 changes: 97 additions & 0 deletions src/frontend/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["react", "typescript", "unicorn", "oxc", "jsx-a11y", "import"],
"options": {
"typeAware": true
},
"categories": {
"correctness": "error",
"suspicious": "error",
"perf": "error"
},
"rules": {
"react/react-in-jsx-scope": "off",
"no-console": "off",
"unicorn/no-null": "off",
"unicorn/filename-case": "off",
"unicorn/prefer-top-level-await": "off",
"import/no-default-export": "error",
"import/no-cycle": ["error", { "maxDepth": 5 }],
"unicorn/no-abusive-eslint-disable": "error",
"no-new-func": "error",
"unicorn/custom-error-definition": "error",
"typescript/no-explicit-any": "error",
"typescript/no-unsafe-argument": "error",
"typescript/no-unsafe-assignment": "error",
"typescript/no-unsafe-call": "error",
"typescript/no-unsafe-member-access": "error",
"typescript/no-unsafe-return": "error",
"typescript/no-empty-object-type": "error",
"typescript/no-unsafe-function-type": "error",
"typescript/no-invalid-void-type": "error",
"typescript/ban-ts-comment": "error",
"typescript/no-require-imports": "error",
"typescript/no-import-type-side-effects": "error",
"typescript/no-misused-promises": "error",
"typescript/no-non-null-assertion": "error",
"typescript/only-throw-error": "error",
"react/no-danger": "error",
"react/no-clone-element": "error",
"react/no-react-children": "error",
"react/button-has-type": "error",
"no-var": "error",
"no-alert": "error",
"no-script-url": "error",
"no-template-curly-in-string": "error",
"eqeqeq": ["error", "always", { "null": "ignore" }],
"no-return-assign": "error",
"import/no-mutable-exports": "error",
"import/no-commonjs": "error",
"typescript/consistent-type-imports": "error",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/no-object-as-default-parameter": "error",
"unicorn/no-nested-ternary": "error",
"unicorn/no-unreadable-iife": "error",
"no-empty-function": "error",
"no-useless-return": "error",
"import/no-duplicates": "error",
"react/jsx-no-useless-fragment": "error",
"react/self-closing-comp": "error",
"typescript/switch-exhaustiveness-check": "error",
"no-restricted-imports": [
"error",
{
"paths": [
{
"name": "react",
"importNames": ["useEffect"],
"message": "Direct useEffect is not allowed. Derive state during render, use event handlers, reset with key, or use useMountEffect / a named sync hook for external system sync. See hooks/useMountEffect.ts and the React docs: https://react.dev/learn/you-might-not-need-an-effect"
}
]
}
]
},
"overrides": [
{
"files": [
"src/hooks/useMountEffect.ts",
"src/hooks/useBodyScrollLock.ts",
"src/hooks/useEscapeKey.ts",
"src/hooks/useDismiss.ts",
"src/hooks/app/useStatusChangeNotifications.ts",
"src/hooks/useRealtimeStatus.ts",
"src/hooks/useActivity.ts"
],
"rules": {
"no-restricted-imports": "off"
}
},
{
"files": ["src/tests/**"],
"rules": {
"typescript/no-explicit-any": "off"
}
}
],
"ignorePatterns": ["dist/**", "node_modules/**"]
}
2 changes: 1 addition & 1 deletion src/frontend/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
Expand Down
11 changes: 11 additions & 0 deletions src/frontend/knip.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/knip@6/schema-jsonc.json",

// Keep Knip strict about missing entry/config discovery so false positives
// are caught at the config level instead of papered over with ignores.
"treatConfigHintsAsErrors": true,

// This script is intentionally loaded from index.html as a classic script,
// so we need to declare it as an entry point manually.
"entry": ["public/theme-init.js"]
}
Loading
Loading