diff --git a/README.md b/README.md index c51713b..8ac7377 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,187 @@ # T19-Project-1 -Repository for team IT2810-H26-T19 in IT2810-H26. +**Live application:** [http://it2810-19.idi.ntnu.no/project1](http://it2810-19.idi.ntnu.no/project1) ## Project -A digital library where users browse reading material fetched live from the -[OpenLibrary REST API](https://openlibrary.org/developers/api) — one book at a time, -with navigation, subject filtering, sorting, and persistent favorites. Built as the -course project for IT2810 Web Development (NTNU). +A digital library where users browse books fetched live from the +[OpenLibrary REST API](https://openlibrary.org/developers/api). The application +presents one book at a time and was built as the course project for IT2810 Web +Development at NTNU. + +The application supports: + +- Previous/next navigation and a jump-to-book selector +- Subject filtering and several sorting choices +- Persistent favorites with a separate favorites view +- Loading, error, empty-result, and missing-data states +- Responsive layouts for desktop and smaller screens ## Tech stack - [Vite](https://vite.dev/) + [React](https://react.dev/) + TypeScript -- [TanStack Query](https://tanstack.com/query) for REST data fetching -- [Vitest](https://vitest.dev/) + React Testing Library + [msw](https://mswjs.io/) - for testing (API requests are always mocked) -- ESLint + Prettier, plain CSS -- npm as package manager — the course environment guide recommends pnpm, but the - project spec explicitly names "Node og npm (og kompatible pakkeverktøy)", so npm - is kept for simplicity +- [TanStack Query](https://tanstack.com/query) for REST data fetching and caching +- [Vitest](https://vitest.dev/) + React Testing Library + [MSW](https://mswjs.io/) + for testing +- ESLint + Prettier +- Plain CSS with no third-party UI component library +- npm as package manager + +## Implementation choices + +The OpenLibrary search endpoint is called through `src/api/openLibrary.ts` and +used by the `useBooks` TanStack Query hook. The query key includes the selected +subject, so changing the subject fetches a new set of books. Navigation and +sorting use the books that have already been fetched and therefore do not +require additional API requests. + +React state is used to keep track of which book is currently displayed and +which view is active. Data and event handlers are passed to components through +props. Custom hooks are used to separate logic for fetching books, storing user +preferences, and managing favorites from the UI components. + +OpenLibrary data is normalized before it is displayed. Missing authors, +ratings, covers, and subjects are handled by the application. Invalid +publication years, such as `0`, are treated as missing instead of being shown +to the user. + +## Persistence + +The application uses both `sessionStorage` and `localStorage` for different +types of user choices: + +- Subject and sort choices are stored under `t19.preferences` in + `sessionStorage`. They survive page reloads during the current browser + session, but reset when the session ends. +- Favorites are stored under `t19.favorites` in `localStorage`. They remain + available after closing and reopening the browser. + +When stored values are missing or invalid, the application falls back to +default values instead of failing. + +## Responsive design and accessibility + +The interface uses plain CSS and media queries to adapt the book card, controls, +navigation, and favorites view to smaller screens. + +The application uses semantic HTML elements such as `header`, `nav`, `main`, +`section`, `article`, and `footer`, together with native buttons and form +controls. Controls have accessible labels, ARIA attributes are used where +appropriate, book covers have alternative text, and keyboard focus is visible. + +The main controls and navigation can also be operated using the keyboard. + +## Testing + +The test suite uses Vitest and React Testing Library. MSW intercepts +OpenLibrary requests during tests so the automated test suite does not depend +on the live API. Unhandled API requests cause the tests to fail. + +The tests cover: + +- Snapshot tests for rendered components +- Component props and state, including missing book data and cover failures +- User interaction for navigation, jumping, filtering, sorting, favorites, + and expanding subjects +- `sessionStorage` and `localStorage` persistence, including reload behavior + and invalid stored values +- Keyboard interaction, focus, labels, and accessible names +- Loading, API error, and no-results states +- API response normalization, including invalid publication years +- Request counts to check that navigation and local sorting do not cause + unnecessary REST API requests -## Getting started +### Manual testing -Requires Node.js 24.6+ and npm 11+. +Issue #30 documents a final manual check using the local production preview on +a desktop browser and a narrow mobile-sized viewport. The library flow, +keyboard use, visible focus, responsive layout, loading and error states, and +saved choices were checked, and no issues were found. + +## Running the project locally + +The project requires Node.js 24.6+ and npm 11+. + +Install the dependencies: ```bash npm install +``` + +Start the development server: + +```bash npm run dev ``` -| Command | Description | -| ---------------------- | ----------------------------- | -| `npm run dev` | Start the dev server | -| `npm run build` | Type-check and build for prod | -| `npm run preview` | Preview the production build | -| `npm run lint` | Run ESLint | -| `npm test` | Run tests once | -| `npm run test:watch` | Run tests in watch mode | -| `npm run format` | Format code with Prettier | -| `npm run format:check` | Check formatting (used in CI) | +The main project commands are: -## Working in this repository +| Command | Description | +| ---------------------- | ----------------------------------- | +| `npm run dev` | Start the development server | +| `npm run build` | Type-check and build for production | +| `npm run lint` | Run ESLint | +| `npm test` | Run the test suite | +| `npm run format` | Format code with Prettier | +| `npm run format:check` | Check formatting | -`main` is protected — you cannot push to it directly. Work on a branch and -open a pull request: +## Working in this repository -```bash -git checkout -b my-feature -git push -u origin my-feature -``` +The `main` branch is protected. Development is done on separate branches and +merged through pull requests. Before a pull request can be merged: -1. **A member of IT2810-H26-T19 must approve it.** You cannot approve your own pull - request, and approvals from staff do not count — you review each other's - code. -2. **Every review comment must be marked resolved.** Reply to each one, fix it - or explain why not, then click _Resolve conversation_. -3. **Pushing new commits dismisses the approval**, so if you change something - after being approved, ask for another review. This is deliberate: what was - approved is what gets merged. +1. A member of IT2810-H26-T19 must approve it. Group members review each + other's code rather than approving their own pull requests. +2. Review comments must be resolved before merging. +3. New commits after approval dismiss the existing approval, so the updated + pull request must be reviewed again. ### Commits -Use [conventional commit](https://www.conventionalcommits.org/) messages and link -issues so they close automatically: +We use +[Conventional Commits](https://www.conventionalcommits.org/) +and reference issues in commits and pull requests where relevant. -``` +Example: + +```text feat: add favorites toggle with localStorage (#42) ``` -### Issues and labels +### Issues and milestones + +Issues are organized under milestones from M1 Setup to M5 Delivery and use +labels for type, area, and priority. Issues with substantial AI assistance use +the `ai-assisted` label. -Issues are organized under milestones (M1 Setup → M5 Delivery) and labeled by type -(`type: feature|bug|docs|chore|test`), area (`area: ui|api|storage|styling|a11y`), -and priority. The schedule and current status live in [ROADMAP.md](ROADMAP.md). -Issues where substantial code was AI-generated carry the `ai-assisted` label; AI -usage is documented in the README as required by the course. +The project milestones and delivery status are documented in +[ROADMAP.md](ROADMAP.md). ## CI/CD -CI runs on our self-hosted runner on the group VM (see `docs/ci-runner.md`; the -NTNU GitHub Enterprise installation has no shared runners). Every pull request -runs formatting (Prettier), linting (ESLint), tests (Vitest), and the production -build — everything must pass with zero errors and warnings before merge. -Pushes to `main` additionally deploy the build to the Apache server on the VM, so -the prototype at http://it2810-19.idi.ntnu.no/project1 always reflects `main`. +CI runs on a self-hosted runner on the group VM. Pull requests targeting `main` +run Prettier, ESLint, Vitest, and the production build. -## Testing +Pushes to `main` run the same checks and, if they pass, deploy the production +build to Apache on the group VM. The deployed application is therefore updated +from the `main` branch. + +See [docs/ci-runner.md](docs/ci-runner.md) and +[docs/deployment.md](docs/deployment.md) for more information. + +## AI usage and reflection + +AI tools were used as assistants during the project for understanding React +and TypeScript concepts, discussing implementation options, debugging, +suggesting tests, reviewing responsive and accessibility behavior, and +checking the project against the course requirements. Issues with substantial +AI assistance were marked with the `ai-assisted` label. -- Snapshot tests of rendered components -- Component tests covering props, state, and user interaction -- Tests never touch the network: msw intercepts every OpenLibrary request - (handlers in `src/test/handlers.ts`), and any unhandled request fails tests - loudly +The team still made the implementation decisions, reviewed suggestions, ran +tests, and adjusted the results before including changes in the project. AI +was useful for finding edge cases, explaining unfamiliar concepts, and +structuring tests. At the same time, suggestions could sometimes be more +extensive than necessary, so they had to be evaluated against the project +requirements and the existing codebase. diff --git a/ROADMAP.md b/ROADMAP.md index 56bd7de..f3e8396 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,45 +1,54 @@ # Roadmap — Project 1: Digital Library Delivery: **Friday 18 September 2026** (medstudentvurdering by 25/9). + Issues are grouped under milestones. Day-to-day workflow status lives on the -[Kanban board](https://git.ntnu.no/orgs/IT2810-H26/projects/7) (Backlog → Ready → -In progress → In review → Done); issues also carry `status:*` labels. +[Kanban board](https://git.ntnu.no/orgs/IT2810-H26/projects/7) +(Backlog → Ready → In progress → In review → Done); issues also use status +labels. ## Milestones -| Milestone | Due | Scope | -| -------------------------- | -------- | ------------------------------------------------------------------------- | -| M1 — Setup | done | Template, tooling, CI, types + API client (#1–#4) | -| M2 — Core app | Wed 10/9 | BookCard, useBooks, navigation, jump list | -| M3 — Persistence & choices | Fri 12/9 | Subject filter, sorting (sessionStorage), favorites (localStorage) | -| M4 — Quality | Tue 15/9 | Responsive CSS, accessibility, Vitest suite, manual browser/device checks | -| M5 — Delivery | Thu 17/9 | Apache deployment, README, delivery artifacts (#5) | - -## Status (updated 8/9) - -- M1 — **done**: PR #3 (template + CI/CD + docs) and PR #15 (#4 types + API client) - merged; #1, #2, #4, #6 closed -- Infrastructure — **live**: self-hosted runner on it2810-19 (systemd service, - survives reboots); CI/CD verified end-to-end — pushes to `main` pass quality - gates and deploy to Apache automatically -- M2 — in review: PR #17 (useBooks + App integration) and PR #18 (BookJumpList), - both CI-green; #13 NavigationControls in progress (evenkkl); #12 stays open - until navigation and jump list are wired into the app -- M3 — issues #19–#22 created and Ready (assignments on the board); testing is an - acceptance criterion in each -- #5 — delivery artifacts (timeliste, VM, FeedbackFruits) — open +| Milestone | Due | Status and scope | +| -------------------------- | ------ | ------------------------------------------------------------------------------ | +| M1 — Setup | 4 Sep | Complete: template, tooling, CI, types, API client, and runner | +| M2 — Core app | 10 Sep | Complete: BookCard, data fetching, navigation, and jump list | +| M3 — Persistence & choices | 12 Sep | Complete: subject filtering, sorting, preferences, and favorites | +| M4 — Quality | 15 Sep | Complete: responsive design, accessibility, automated tests, and manual checks | +| M5 — Delivery | 17 Sep | Complete: final fixes, documentation, deployment, and delivery preparation | + +## Final status + +- M1–M5 are complete. +- The functional requirements are implemented, including navigation, subject + filtering, sorting, persistent preferences, and favorites. +- Responsive design, accessibility, automated tests, and manual checks were + completed as part of M4. +- CI runs on the self-hosted runner for pull requests and pushes to `main`. + Successful pushes to `main` deploy the production build to Apache on the + group VM. +- Final fixes, documentation, deployment, and delivery preparation were + completed as part of M5. + +The individual peer assessment in FeedbackFruits takes place after submission +and is due by 25/9. ## Workflow -1. Pick an issue from the current milestone (unassigned, `status: blocked` means wait) -2. Branch: `feat/-short-name` from `main` -3. Conventional commits referencing the issue -4. PR → teammate review → all conversations resolved → merge -5. Issue auto-closes; PR gets `status: in-review` while waiting +1. Pick an issue from the current milestone. +2. Create a branch from `main`, using names such as + `feat/-short-name` or `fix/-short-name`. +3. Use conventional commits and reference the relevant issue. +4. Open a pull request for teammate review. +5. Resolve review conversations and merge after approval. +6. Close the corresponding issue when the work is complete. + +## Delivery checklist -## Delivery checklist (18/9) +Delivery is tracked in issue #5 and includes: -Tracked in #5: code + docs on git.ntnu.no, prototype on the group VM (Apache, -[deployment guide](docs/deployment.md) — live at -http://it2810-19.idi.ntnu.no/project1), individual time log for Blackboard, -FeedbackFruits participation. +- Code and documentation on git.ntnu.no +- Working prototype on the group VM using Apache +- [Deployment documentation](docs/deployment.md) +- Individual time log for Blackboard +- FeedbackFruits participation diff --git a/docs/ci-runner.md b/docs/ci-runner.md index 4e5be78..7e28909 100644 --- a/docs/ci-runner.md +++ b/docs/ci-runner.md @@ -1,62 +1,35 @@ # Self-hosted CI runner on the group VM -The NTNU GitHub Enterprise installation (git.ntnu.no) has no shared Actions -runners, so CI runs on a runner registered on the group VM. Tracked in #6. - -The VM (`it2810-19.idi.ntnu.no`) is reachable only from the NTNU network or via -VPN, and you have sudo on it. Note that **VM sudo is not the same as repository -admin** — registering the runner needs repo admin. - -## Status - -- [x] Apache2 installed and enabled on the VM -- [x] rsync installed; `/etc/sudoers.d/github-runner-deploy` grants NOPASSWD - `rsync` to `erikhfj` (used by the CI deploy step) -- [x] Connectivity verified from the VM: git.ntnu.no, github.com, nodejs.org -- [x] Actions runner v2.337.0 downloaded and extracted in `~/actions-runner` -- [ ] Runner registered — **blocked on repository admin** - (requested from `it2810@idi.ntnu.no`) -- [ ] CI green on PR #3, deploy verified after merge to main - -## 1. Repository access - -Registering a runner requires **repository admin**. If _Settings → Actions_ is -not visible on the repo, email `it2810@idi.ntnu.no` and ask for admin access on -`IT2810-H26/T19-Project-1` (one person per group is enough to register; the -runner serves the whole repo afterwards). - -## 2. Install the runner (done) - -Downloaded from the official releases — -`https://github.com/actions/runner/releases/download/v2.337.0/actions-runner-linux-x64-2.337.0.tar.gz` -— extracted into `~/actions-runner`. A one-time registration token (valid 1 -hour) is fetched from _Settings → Actions → Runners → New self-hosted runner_, -or via `gh api -X POST repos/IT2810-H26/T19-Project-1/actions/runners/registration-token`. - -## 3. Register and run as a service (pending admin) - -```bash -cd ~/actions-runner -./config.sh --unattended --url https://git.ntnu.no/IT2810-H26/T19-Project-1 --token -sudo ./svc.sh install -sudo ./svc.sh start -``` - -`--unattended` accepts the defaults: runner name = hostname, work folder -`_work`, labels `self-hosted`, `linux`, `x64` — which is what `ci.yml` targets. - -## 4. Requirements (met) - -- Internet access from the VM — verified: git.ntnu.no, github.com (runner + - actions download), nodejs.org (setup-node fetches Node 24 into the tool cache) -- `git`, `curl`, `rsync` installed -- Apache2 installed — pushes to `main` deploy with - `sudo rsync -a --delete dist/ /var/www/html/project1/` (NOPASSWD via the - sudoers entry above) - -## 5. Verify - -1. The runner shows **Idle** under _Settings → Actions → Runners_ -2. Re-run the CI job on PR #3 — it should pick up and complete -3. After PR #3 merges, push to `main` → job deploys `dist/` to Apache → live at - http://it2810-19.idi.ntnu.no/project1 +The project uses a self-hosted +runner on the group VM, `it2810-19.idi.ntnu.no`. Issue #6 tracks the setup and is +closed; the runner is currently in use by the repository workflow. + +The runner is configured with the `self-hosted`, `linux`, and `x64` labels and runs on the group VM. + +## What triggers CI + +The workflow in `.github/workflows/ci.yml` runs on the self-hosted runner for: + +- Pull requests targeting `main` +- Pushes to `main` + +Each run installs dependencies with `npm ci`, checks Prettier formatting, runs +ESLint, runs the Vitest test suite, and creates a production build with `npm run +build`. + +Apache serves the deployed application at +[http://it2810-19.idi.ntnu.no/project1](http://it2810-19.idi.ntnu.no/project1). + +The runner and Apache are on the same group VM. The runner is responsible for +running the CI workflow, tests, build, and deployment, while Apache serves the +finished application to users. + +## Relationship to local deployment + +The CI deployment is the normal deployment path after changes reach `main`. +The manual procedure in [deployment.md](deployment.md) can be used if a manual +deployment is needed. + +No repository secrets or runner registration tokens belong in this repository. +For the manual Apache procedure and the `/project1` Vite base path, see +[deployment.md](deployment.md) diff --git a/docs/deployment.md b/docs/deployment.md index 0231837..8f8681d 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -1,59 +1,55 @@ # Deployment — Apache on the group VM -The prototype is published at **http://it2810-19.idi.ntnu.no/project1**. +The prototype is published at +**[http://it2810-19.idi.ntnu.no/project1](http://it2810-19.idi.ntnu.no/project1)**. + +The normal deployment path is through CI. When changes are pushed or merged to +`main`, the self-hosted runner runs the quality checks, builds the application, +and deploys the production build to Apache on the group VM. + +See [ci-runner.md](ci-runner.md) for more information about the CI setup. ## VM access -- Host: `it2810-19.idi.ntnu.no` — reachable only from the NTNU network or via VPN +- Host: `it2810-19.idi.ntnu.no` — reachable from the NTNU network or through VPN - Log in: `ssh @it2810-19.idi.ntnu.no` -- You have full control (sudo) on the VM +- Manual deployment requires the necessary VM and sudo access -## One-time setup +## Apache setup -```bash -ssh @it2810-19.idi.ntnu.no -sudo apt install apache2 +The application is served under the `/project1` path, which is why +`vite.config.ts` sets: + +```ts +base: "/project1"; ``` -The app is served under the `/project1` path, which is why `vite.config.ts` sets -`base: '/project1'`. +Apache serves the deployed application from `/var/www/html/project1/`. + +## Manual deployment -## Deploy (build locally, copy over) +If a manual deployment is needed, the application can be built locally and +copied to the VM: ```bash -npm run build # on your machine, outputs dist/ +npm run build scp -r dist @it2810-19.idi.ntnu.no:/tmp/dist-$USER ssh @it2810-19.idi.ntnu.no sudo rm -r /var/www/html/project1 sudo mv /tmp/dist-$USER /var/www/html/project1 ``` -Pitfalls (from the course notes): - -- Copying to a shared `/tmp/dist` collides between group members — always use a - personal target like `/tmp/dist-$USER` -- `mv`/`cp` into an existing target directory nests the source inside it and - looks like a stale site; delete the old directory first (or use - `sudo rsync -a /tmp/dist-$USER/ /var/www/html/project1/`) - -## Deploy alternative (build on the VM) - -Requires Node on the VM (install with nvm) and an SSH key for git.ntnu.no added -on the VM: - -```bash -curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash -nvm install node -git clone git@git.ntnu.no:IT2810-H26/T19-Project-1.git -cd T19-Project-1 && npm install && npm run build -sudo cp -r dist/* /var/www/html/project1/ -``` +Some pitfalls from the course notes: -Once the group VM runs our self-hosted Actions runner (issue #6, see -[ci-runner.md](ci-runner.md)), the build can also be produced by CI and copied -from there — that removes "forgot to build before copying" as a failure mode. +- Copying to a shared `/tmp/dist` can cause conflicts between group members, so + a personal target such as `/tmp/dist-$USER` should be used. +- Copying or moving into an existing target directory can place the source + inside the existing directory instead of replacing it. The old deployment + should therefore be removed first. ## Verify -Open http://it2810-19.idi.ntnu.no/project1 — the app must load and navigate -without console errors. Check after every deployment. +After deployment, open +[http://it2810-19.idi.ntnu.no/project1](http://it2810-19.idi.ntnu.no/project1) +and verify that the application loads and navigation works without console +errors.