Skip to content

Commit e5b038a

Browse files
authored
Merge pull request #17 from holtschn/docs/readme-ci-caching-plan
Add Documentation, CI, and Performance Caching Roadmap
2 parents be35d74 + 903755d commit e5b038a

9 files changed

Lines changed: 496 additions & 57 deletions

File tree

.github/workflows/test.yml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main, develop]
6+
pull_request:
7+
branches: [main, develop]
8+
9+
jobs:
10+
test:
11+
name: Run Tests
12+
runs-on: ubuntu-latest
13+
14+
permissions:
15+
contents: read
16+
pull-requests: write
17+
18+
strategy:
19+
matrix:
20+
node-version: [20.x]
21+
22+
steps:
23+
- name: Checkout code
24+
uses: actions/checkout@v4
25+
26+
- name: Setup Node.js ${{ matrix.node-version }}
27+
uses: actions/setup-node@v4
28+
with:
29+
node-version: ${{ matrix.node-version }}
30+
cache: 'npm'
31+
32+
- name: Install dependencies
33+
run: npm ci
34+
35+
- name: Run tests
36+
run: npm run test:ci
37+
38+
- name: Upload coverage reports
39+
uses: actions/upload-artifact@v4
40+
if: always()
41+
with:
42+
name: coverage-report
43+
path: coverage/
44+
retention-days: 30
45+
46+
- name: Comment test results on PR
47+
if: github.event_name == 'pull_request'
48+
uses: actions/github-script@v7
49+
with:
50+
script: |
51+
const fs = require('fs');
52+
53+
// Check if coverage summary exists
54+
if (fs.existsSync('coverage/coverage-summary.json')) {
55+
const coverage = JSON.parse(fs.readFileSync('coverage/coverage-summary.json', 'utf8'));
56+
const total = coverage.total;
57+
58+
const body = `## Test Coverage Report
59+
60+
| Metric | Coverage |
61+
|--------|----------|
62+
| Statements | ${total.statements.pct}% |
63+
| Branches | ${total.branches.pct}% |
64+
| Functions | ${total.functions.pct}% |
65+
| Lines | ${total.lines.pct}% |
66+
67+
📊 [Full coverage report available in artifacts]`;
68+
69+
github.rest.issues.createComment({
70+
issue_number: context.issue.number,
71+
owner: context.repo.owner,
72+
repo: context.repo.repo,
73+
body: body
74+
});
75+
}

CLAUDE.md

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,46 @@ None currently.
882882

883883
### 🚀 Performance
884884

885+
**PERF-0: Server-Side Caching Foundation** (High Priority - Prerequisite for Phase 5)
886+
- **Problem:** External NDB API is slow (~85kB scores response), current client-side fetching bypasses Next.js Data Cache
887+
- **Solution:** Leverage existing cache infrastructure with Server Components
888+
- **Phase 0.1: Granular Cache Tags** (15 min) ✅ READY TO IMPLEMENT
889+
- Update `/api/ndb/[...endpoint]/route.ts` to use endpoint-specific tags
890+
- Map endpoints to tags: `scores``ndb-scores`, `setlists``ndb-setlists`, `samples``ndb-samples`
891+
- Replace global `revalidateTag('caching-tag')` with specific tags
892+
- Benefit: Updating a score doesn't invalidate setlists cache
893+
- **Phase 0.2: Convert Scores List to Server Component** (1.5 hours)
894+
- Convert `/intern/ndb/page.client.tsx` to Server Component
895+
- Move `useScores()` call to server-side `fetchAllScores()` action
896+
- Extract interactive UI to Client Components:
897+
- `ScoresTableToolbar``'use client'` (search, filters)
898+
- `ScoresTable``'use client'` (column filters, click handlers)
899+
- Pass scores data as props from Server Component
900+
- Expected benefit: 85kB cached indefinitely, instant subsequent loads
901+
- **Phase 0.3: Convert Score Detail to Server Component** (1.5 hours)
902+
- Convert `/intern/ndb/[id]/page.client.tsx` to Server Component
903+
- Move data fetching to server-side
904+
- Optional optimization: Create `/api/ndb/scores/[id]` endpoint (fetch 1 score instead of all 85kB)
905+
- Extract `ScoreEditForm` to `'use client'` component
906+
- Keep `ScoreDetailsCard` as Server Component (read-only)
907+
- Expected benefit: Instant navigation between scores, SEO-friendly
908+
- **Phase 0.4: Cache Revalidation** (30 min)
909+
- Add `revalidatePath('/intern/ndb')` after score mutations
910+
- Consider time-based revalidation: `export const revalidate = 3600` (1 hour)
911+
- Test cache invalidation flow
912+
- **Optional Phase 0.5: Convert Setlist Pages** (2 hours)
913+
- Apply same pattern to `/intern/ndb/setlists/page.client.tsx`
914+
- Apply to `/intern/ndb/setlists/[id]/page.client.tsx` if beneficial
915+
- Lower priority (smaller datasets, less frequent access)
916+
- **Expected Results:**
917+
- First load: 85kB from slow API (unavoidable)
918+
- Subsequent loads: **Instant** (served from Next.js Data Cache)
919+
- After mutations: Cache invalidated only for affected resources
920+
- Across users: Shared cache (all users benefit from warm cache)
921+
- **Total Effort:** ~3.5 hours for core phases (0.1-0.4)
922+
- **Depends on:** Nothing (uses existing infrastructure)
923+
- **Blocks:** Should be done before Phase 5 (Integration) to improve performance baseline
924+
885925
**PERF-1: Code Splitting** (Medium Priority)
886926
- Dynamic imports: allocation grid, PDF export, @dnd-kit, column modal
887927
- Install `@next/bundle-analyzer`
@@ -905,15 +945,17 @@ None currently.
905945
- Fallback to SSR for new scores
906946
- **Depends on:** Nothing
907947

908-
**PERF-4: API Response Caching** (Requires Discussion/Approval)
909-
- Install `@tanstack/react-query`
910-
- Wrap app in `QueryClientProvider`
911-
- Create hooks: `useScores()`, `useScore(id)`, `useSetlists()`, `useSetlist(id)`, `useUsers()`
912-
- Configure stale time, cache time
913-
- Cache invalidation on mutations
914-
- Optimistic updates
915-
- **Depends on:** User approval
916-
- **Note:** TanStack Query approach needs approval before implementation
948+
**PERF-4: Advanced Client-Side Caching** (OPTIONAL - Low Priority)
949+
- ~~Install `@tanstack/react-query`~~ **SUPERSEDED by PERF-0**
950+
- **Status:** OPTIONAL - Only consider if PERF-0 (Server Components) is insufficient
951+
- **Use cases that might still need this:**
952+
- Optimistic updates in forms
953+
- Offline support
954+
- Real-time collaboration features
955+
- Complex client-side state synchronization
956+
- **Current assessment:** PERF-0 Server Components approach is sufficient for current requirements
957+
- **Recommendation:** Skip unless specific use case emerges
958+
- **Depends on:** PERF-0 implemented and evaluated first
917959

918960
---
919961

README.md

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
# Ensemble Web
2+
3+
[![Tests](https://github.com/holtschn/ensemble-web/actions/workflows/test.yml/badge.svg)](https://github.com/holtschn/ensemble-web/actions/workflows/test.yml)
4+
[![License: GPL-3.0](https://img.shields.io/badge/License-GPL--3.0-blue.svg)](LICENSE)
5+
6+
A modern web application for brass ensemble management, featuring a public website and an internal "Notendatenbank" (sheet music database) system.
7+
8+
## Features
9+
10+
- 🎵 **Notendatenbank (NDB)** - Comprehensive sheet music management system
11+
- Score catalog with metadata (composer, arranger, instrumentation, difficulty)
12+
- File management (parts, full scores, audio samples)
13+
- Advanced filtering and column configuration
14+
- Setlist creation and management
15+
- Player allocations for performances
16+
- 📄 **Content Management** - Dynamic pages and content via PayloadCMS
17+
- 🔐 **Authentication** - User management and role-based access control
18+
- 📅 **Event Management** - Calendar and event information
19+
- 📱 **Responsive Design** - Mobile-first, fully responsive UI
20+
-**Performance Optimized** - Server-side caching and static generation
21+
22+
## Tech Stack
23+
24+
- **Frontend:** [Next.js 15](https://nextjs.org/) (App Router) + [React 19](https://react.dev/)
25+
- **CMS:** [PayloadCMS 3](https://payloadcms.com/)
26+
- **Database:** [PostgreSQL](https://www.postgresql.org/)
27+
- **Styling:** [TailwindCSS 4](https://tailwindcss.com/)
28+
- **Storage:** [Vercel Blob Storage](https://vercel.com/docs/storage/vercel-blob)
29+
- **Email:** [Nodemailer](https://nodemailer.com/)
30+
- **Testing:** [Jest](https://jestjs.io/) + [React Testing Library](https://testing-library.com/react) + [MSW](https://mswjs.io/)
31+
32+
## Prerequisites
33+
34+
- **Node.js** 20.x or higher
35+
- **PostgreSQL** database
36+
- **npm** or **yarn** package manager
37+
38+
## Installation
39+
40+
1. **Clone the repository**
41+
42+
```bash
43+
git clone https://github.com/holtschn/ensemble-web.git
44+
cd ensemble-web
45+
```
46+
47+
2. **Install dependencies**
48+
49+
```bash
50+
npm install
51+
```
52+
53+
3. **Set up environment variables**
54+
55+
Copy the example environment file and configure it:
56+
57+
```bash
58+
cp .env.example .env.local
59+
```
60+
61+
Key environment variables to configure:
62+
63+
- `POSTGRES_URL` - PostgreSQL connection string
64+
- `POSTGRES_SCHEMA` - Database schema name
65+
- `PAYLOAD_SECRET` - Secret for PayloadCMS sessions
66+
- `NDB_API_URL` - External NDB API base URL
67+
- `NDB_USERNAME` / `NDB_PASSWORD` - NDB API credentials
68+
- `BLOB_READ_WRITE_TOKEN` - Vercel Blob Storage token
69+
- `NODEMAILER_HOST` / `NODEMAILER_USER` / `NODEMAILER_PASS` - Email configuration
70+
71+
See [`.env.example`](.env.example) for the complete list of required variables.
72+
73+
4. **Run database migrations** (if needed)
74+
75+
```bash
76+
npm run payload migrate
77+
```
78+
79+
5. **Generate TypeScript types**
80+
81+
```bash
82+
npm run generate:types
83+
```
84+
85+
## Development
86+
87+
Start the development server:
88+
89+
```bash
90+
npm run dev
91+
```
92+
93+
The application will be available at:
94+
- **Main site:** http://localhost:3000
95+
- **PayloadCMS admin:** http://localhost:3000/admin
96+
97+
### Other Development Commands
98+
99+
```bash
100+
# Development with cache clearing
101+
npm run devsafe
102+
103+
# Code quality
104+
npm run lint # Run ESLint
105+
npm run format # Format code with Prettier
106+
107+
# Build for production
108+
npm run build
109+
110+
# Start production server
111+
npm start
112+
```
113+
114+
## Testing
115+
116+
The project uses Jest with React Testing Library and MSW for API mocking.
117+
118+
```bash
119+
# Run all tests
120+
npm test
121+
122+
# Run tests in watch mode
123+
npm run test:watch
124+
125+
# Run tests with coverage
126+
npm run test:coverage
127+
128+
# Run NDB-specific tests
129+
npm run test:ndb
130+
131+
# Run tests in CI mode
132+
npm run test:ci
133+
```
134+
135+
Current test coverage:
136+
- **162 tests** across 9 test suites
137+
- Comprehensive coverage of NDB utilities and components
138+
- MSW handlers for all API endpoints
139+
140+
## Project Structure
141+
142+
```
143+
.
144+
├── src/
145+
│ ├── app/ # Next.js App Router
146+
│ │ ├── (pages)/ # Public pages group
147+
│ │ │ ├── intern/ndb/ # Internal sheet music database
148+
│ │ │ └── api/ # API routes
149+
│ │ └── (payload)/ # PayloadCMS admin group
150+
│ │
151+
│ ├── next/ # Next.js-specific code
152+
│ │ ├── ndb/ # Notendatenbank module
153+
│ │ │ ├── api/ # API client and proxy
154+
│ │ │ ├── components/ # UI components
155+
│ │ │ ├── hooks/ # React hooks
156+
│ │ │ └── utils/ # Utility functions
157+
│ │ ├── auth/ # Authentication
158+
│ │ └── components/ # Shared components
159+
│ │
160+
│ └── payload/ # PayloadCMS configuration
161+
│ ├── collections/ # Data collections
162+
│ └── globals/ # Global configs
163+
164+
├── old.gui/ # Legacy reference code (READ-ONLY)
165+
└── .github/workflows/ # CI/CD workflows
166+
```
167+
168+
## Deployment
169+
170+
This application is designed to be deployed on [Vercel](https://vercel.com/):
171+
172+
1. **Connect your repository** to Vercel
173+
2. **Configure environment variables** in Vercel dashboard
174+
3. **Deploy** - Vercel will automatically build and deploy
175+
176+
### Build Command
177+
```bash
178+
npm run build
179+
```
180+
181+
### Environment Variables
182+
Ensure all variables from `.env.example` are configured in your Vercel project settings.
183+
184+
## PayloadCMS
185+
186+
Access the PayloadCMS admin panel at `/admin` to manage:
187+
- Users and authentication
188+
- Media files
189+
- Pages and content
190+
- Events
191+
- Site settings (header, footer)
192+
193+
### Generate Types
194+
195+
After modifying PayloadCMS collections or globals:
196+
197+
```bash
198+
npm run generate:types
199+
```
200+
201+
This updates `src/payload-types.ts` with TypeScript type definitions.
202+
203+
## Contributing
204+
205+
For development guidance and architectural decisions, see [CLAUDE.md](CLAUDE.md).
206+
207+
### Development Workflow
208+
209+
1. Check the roadmap in [CLAUDE.md](CLAUDE.md)
210+
2. Create a feature branch from `develop`
211+
3. Implement changes with tests
212+
4. Run tests and linting
213+
5. Submit a pull request to `develop`
214+
215+
## License
216+
217+
This project is licensed under the **GNU General Public License v3.0** - see the [LICENSE](LICENSE) file for details.
218+
219+
## Architecture Notes
220+
221+
- **API Proxy Pattern:** All NDB API calls go through Next.js API routes for security (Basic Auth credentials hidden server-side)
222+
- **Server Components:** Leverages Next.js 15 Server Components for performance and SEO
223+
- **Data Cache:** Built-in caching with tag-based revalidation
224+
- **User Preferences:** Hybrid storage with Payload Preferences API + localStorage
225+
226+
For detailed architecture documentation, see [CLAUDE.md](CLAUDE.md).
227+
228+
## Known Issues
229+
230+
See [CLAUDE.md - Known Issues](CLAUDE.md#known-issues) for current issues and planned fixes.
231+
232+
## Support
233+
234+
For bugs and feature requests, please [open an issue](https://github.com/holtschn/ensemble-web/issues).

jest.config.cjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ module.exports = {
4242
collectCoverageFrom: ['src/next/ndb/**/*.{ts,tsx}', '!src/**/*.d.ts', '!src/**/*.config.ts', '!src/**/index.ts'],
4343

4444
coverageDirectory: 'coverage',
45-
coverageReporters: ['text', 'lcov', 'html'],
45+
coverageReporters: ['text', 'lcov', 'html', 'json-summary'],
4646

4747
// Ignore patterns
4848
testPathIgnorePatterns: ['<rootDir>/.next/', '<rootDir>/node_modules/', '<rootDir>/build/', '<rootDir>/dist/'],

0 commit comments

Comments
 (0)