From 0d64d547e62e4bc61ad73ed5ed7ebec1ac033d73 Mon Sep 17 00:00:00 2001 From: Oscar Grande Date: Sat, 14 Nov 2026 00:14:17 +0100 Subject: [PATCH] docs: update README and add accessibility and sustainability documentation --- README.md | 115 ++++++++++++----------- docs/accessibility-and-sustainability.md | 83 ++++++++++++++++ frontend/src/test/README.md | 38 ++++---- 3 files changed, 164 insertions(+), 72 deletions(-) create mode 100644 docs/accessibility-and-sustainability.md diff --git a/README.md b/README.md index 79b435c..6552263 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,11 @@ Full Circuit is a web application for exploring PC components and planning a cus - [Current prototype](#current-prototype) - [PC builder](#pc-builder) - [Navigation and shared build state](#navigation-and-shared-build-state) + - [Authentication decision and ownership](#authentication-decision-and-ownership) + - [Account builds](#account-builds) - [Published builds](#published-builds) - [Planned functionality](#planned-functionality) +- [Contributions](#contributions) - [Use of AI](#use-of-ai) - [Testing](#testing) - [Run the prototype locally](#run-the-prototype-locally) @@ -35,24 +38,15 @@ The app displays a component catalog backed by PostgreSQL and GraphQL, with sear ## Accessibility -We have worked to make Full Circuit usable for as many people as possible. We use semantic HTML elements, labels for inputs and buttons, and alternative text for component images. Keyboard users can navigate using focusable controls with a visible focus outline. We have also made the layout responsive so it works on both mobile and desktop. We will continue checking accessibility as the project develops. +We have used semantic HTML, labeled controls, keyboard focus styles, alternative text, and responsive layouts to make Full Circuit easier to use. Our dialogs and status messages also provide information for assistive technologies. -The add-to-build feedback dialog confirms the action on the current page and lets users choose between **Continue exploring** and **Go back to the build page**. This reduces the "boomerang effect" of moving back and forth between the catalog and builder just to check whether a component was added. Keeping users in their browsing context reduces the need to reorient themselves and gives them control over when to navigate. When replacing a component, the dialog also lets users keep their current selection or confirm the replacement. The dialog has an accessible title and description, and loading and success messages use a status announcement for assistive technologies. +See [Accessibility and sustainability](docs/accessibility-and-sustainability.md#accessibility) for implementation examples, checks, and improvements we still need to make. ## Sustainability -We aim to make as few unnecessary backend calls as possible by fetching data only when needed and reusing information already available in the browser. The catalog's data flow works as follows: +We have used WebP images, browser caching, paginated requests, a short-lived catalog cache, and memoized cards to reduce repeated downloads and rendering work. We also keep the current build in local storage instead of saving every selection to the backend. -1. **Check the catalogue cache.** Each query is identified by its search, filters, sorting, page size, and cursor. If a matching response is less than one minute old, the frontend reuses its parts, total count, and pagination information without contacting the backend. For example, switching from GPUs to CPUs and back can reuse the previous GPU results. -2. **Fetch summaries when there is no fresh cached response.** The catalog requests 40 components at a time, including fields such as name, price, category, and image URL. Full specifications are left out, so browsing does not download detailed data for every displayed part. The response is then cached for reuse. Requests are debounced by 300 ms to avoid a backend call for every keystroke during rapid typing. -3. **Fetch specifications when the user needs them.** Opening a component's details fetches its specifications if they are missing. Adding directly from a card also fetches missing specifications, because the build needs them for compatibility checks. -4. **Reuse loaded specifications.** A card keeps specifications fetched for its detail view while it remains mounted. Opening details, closing them, and then adding from that same card therefore requires only one specifications request. Adding from inside the detail view also reuses the complete part already available. - -The catalogue cache holds at most 100 pages to limit memory use and is cleared on a browser reload. Specifications stored in an individual card are discarded when that card unmounts. These measures reduce repeated network transfers and backend queries while allowing catalogue results to be refreshed after their cache entries expire. - -Memoized component cards and a stable details callback let React skip rendering unchanged cards. Stable IDs and keeping the previous results mounted during loading also avoid unnecessarily removing and recreating matching cards when filters change. This reduces repeated rendering work on the user's device and removes the brief empty-list flash. The result-count area keeps its space during loading to prevent the cards from jumping. - -Component images use WebP. When the mobile detail view crops image whitespace, it explicitly requests WebP output at quality `0.9` instead of the canvas default of PNG. This avoids generating PNG copies of WebP images and aims to keep the encoded image data compact. Cropping still requires pixel processing and re-encoding; the generated image is local and does not require another server download. Together, these choices target lower resource use, although we have not measured their energy or carbon savings. +See [Accessibility and sustainability](docs/accessibility-and-sustainability.md#sustainability) for our choices and their limitations. ## Dataset @@ -71,11 +65,11 @@ Each component has a unique ID, category, name, manufacturer, price in EUR, imag The repository includes 800 WebP images in `backend/data/part_images/`, named `.webp`. The backend serves these through its `/images/` endpoint. -The frontend no longer uses `frontend/src/data/parts.mock.json`. It retrieves components from PostgreSQL through the GraphQL API at `/api/graphql`, using `getParts` and `useParts`. The catalog requests 40 components at a time and supports search, filtering, sorting, and loading more results with cursor-based pagination. Full specifications are fetched only when a component's detail view is opened, reducing unnecessary data transfer. The backend supports extra specification filters, but the frontend does not use them yet. +The frontend no longer uses `frontend/src/data/parts.mock.json`. It retrieves components from PostgreSQL through the GraphQL API at `/api/graphql`, using `getParts` and `useParts`. The catalog requests 40 components at a time and supports search, filtering, sorting, and loading more results with cursor-based pagination. The catalog GraphQL query returns summaries without specifications. We fetch missing specifications when opening details or adding a component to the build, and reuse them while the card remains mounted. The backend supports extra specification filters, but the frontend does not use them yet. ## Database -The backend uses PostgreSQL, running directly on the virtual machine. It is only reachable from the VM itself (it listens on `localhost`). The dataset has 800 parts: 100 in each of the eight categories above. Specifications come from the [BuildCores OpenDB](https://github.com/buildcores/buildcores-open-db), prices are in EUR, and the images are 256x256 WebP files. +The backend uses PostgreSQL, running directly on the virtual machine. When working locally, we use a local database or connect to the VM database through an approved connection or SSH tunnel. Direct VM access requires NTNU network/VPN and PostgreSQL configured to accept the connection. The dataset has 800 parts: 100 in each of the eight categories above. Specifications come from the [BuildCores OpenDB](https://github.com/buildcores/buildcores-open-db), prices are in EUR, and the images are 256x256 WebP files. Files in `backend/`: @@ -85,7 +79,7 @@ Files in `backend/`: - `db/seed.sql` – empties `parts` and loads `parts.csv`. - `db/seed.sh` – runs `schema.sql` and `seed.sql`. -Each part is one row with the columns `id`, `category`, `name`, `manufacturer`, `price_eur`, `image_path`, and `specs` (details as `jsonb`). Published builds are saved in the `builds` table with a name, optional comment, date, and references to their selected parts. +Each part is one row with the columns `id`, `category`, `name`, `manufacturer`, `price_eur`, `image_path`, and `specs` (details as `jsonb`). Private and published builds are saved in the `builds` table with a name, optional comment, date, owner, visibility, and references to their selected parts. The `users` table links each Firebase UID to an internal user ID and optional display name. ### Seed the database @@ -95,10 +89,11 @@ To load the data on a new database, run from the repository root: ```bash export DATABASE_URL='postgresql://buildapc:change-me@localhost:5432/buildapc' -./backend/db/seed.sh +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f backend/db/schema.sql +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "\copy parts FROM 'backend/data/parts.csv' WITH (FORMAT csv, HEADER)" ``` -Do not run this script on an existing database without checking the schema first. The `builds` table is not created with `IF NOT EXISTS`, and the seed script is currently meant for initial setup. +Use these commands only for a new, empty database. We import directly because `seed.sh` runs `TRUNCATE parts`, which conflicts with the foreign keys from `builds`. The schema also creates `builds` without `IF NOT EXISTS`. We should update the seed scripts before using them as a repeatable setup or reset command. Changes to an existing database must be applied separately; editing `schema.sql` does not update it automatically. ## Backend @@ -139,7 +134,7 @@ sudo pm2 stop buildapc-backend # stop ### Run the backend locally -To run the backend on your computer, you need access to a PostgreSQL database. The VM database only accepts connections from the VM itself, so use a local database or set up an SSH tunnel. Set `CORS_ORIGIN=http://localhost:5173` in your `.env`: +To run the backend on your computer, you need access to a PostgreSQL database. Use your own local database, or ask for the correct VM connection details. If the VM only accepts local connections, use an SSH tunnel. Set `DATABASE_URL` to the database you are connecting to, and set `CORS_ORIGIN=http://localhost:5173` in your `.env`: ```bash cd backend/server @@ -149,16 +144,16 @@ npm run dev ### Abuse protection and request limits -Publishing stays anonymous, so the backend limits how much a single client can do. The limits are set in `backend/server/src/config.ts`. +Saving and publishing require login. We also limit write attempts per client IP to reduce abuse. The limits are set in `backend/server/src/config.ts`. -| Limit | Value | When exceeded | -| --- | --- | --- | -| Publishing builds (`publishBuild`) | 5 attempts per client IP in 10 minutes. | HTTP `429` with a `Retry-After` header and error code `RATE_LIMITED`. Nothing is written to the database. | -| Query depth | At most 8 levels of nested fields. | HTTP `400`, error code `GRAPHQL_VALIDATION_FAILED`. | -| Query size | At most 100 selected fields per operation (fragments and aliases are counted). | HTTP `400`, error code `GRAPHQL_VALIDATION_FAILED`. | -| Request body | At most 100 KB. | HTTP `413`, error code `PAYLOAD_TOO_LARGE`. | +| Limit | Value | When exceeded | +| ------------------------------------------------------ | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | +| Saving/publishing (`savePrivateBuild`, `publishBuild`) | 5 attempts per client IP in 10 minutes. | HTTP `429` with a `Retry-After` header and error code `RATE_LIMITED`. Nothing is written to the database. | +| Query depth | At most 8 levels of nested fields. | HTTP `400`, error code `GRAPHQL_VALIDATION_FAILED`. | +| Query size | At most 100 selected fields per operation (fragments and aliases are counted). | HTTP `400`, error code `GRAPHQL_VALIDATION_FAILED`. | +| Request body | At most 100 KB. | HTTP `413`, error code `PAYLOAD_TOO_LARGE`. | -Every publishing attempt counts, including invalid ones, and each alias of `publishBuild` in one request counts separately. Reading data is not rate limited, and introspection queries are not counted towards the depth and size limits. The counters are kept in the server's memory, so they reset when the backend restarts, and they are per backend process. +Authenticated save/publish attempts count, including invalid ones, and repeated mutation aliases count separately. Reading data is not rate limited, and introspection queries are not counted towards the depth and size limits. The counters are kept in the server's memory, so they reset when the backend restarts, and they are per backend process. The limit is per IP address, so the backend must see the visitor's real address. On the VM, Apache forwards requests to the backend, so `TRUST_PROXY=1` must be set in `backend/server/.env`. Without it, all visitors share the proxy's address and one limit. @@ -173,9 +168,9 @@ Catalog cards initially display summary data from the backend. Full specificatio Open the builder through the navigation bar or by visiting `/project2/builder`. Use **Add to build** on a catalog card or component detail page to select a part. - Each of the eight categories has one slot. Adding another component in the same category opens a confirmation dialog, allowing users to keep the current component or replace it. -- Feedback confirms when a component has been added and offers **Continue exploring** or **Go back to the build page**, so users choose when to leave the catalog. +- A nonmodal notification confirms when a component has been added and offers **View build**, so we can continue browsing or return to the builder. - Empty slots have a **Choose** action that opens the category catalog. Selected slots show the component image, name, first three specifications, and price, with actions to change or delete the selection. -- The summary shows the selected component count, progress, and total price in EUR. Missing prices currently contribute zero to the total, so the displayed sum may be incomplete. +- The summary shows the selected component count, progress, and total price in EUR. Components without prices are left out of the sum, and we display an incomplete-total message. - **Clear build** asks for confirmation before removing all selections. Selections and saved-build metadata are stored as JSON under `pc-builder-v2:` (or `pc-builder-v2:guest`) in `localStorage` and restored when the app loads. This preserves the current build across refreshes and page navigation in the same browser and site. Localhost and the deployed site have separate storage. The compatibility panel checks selected parts and shows results such as compatible, incompatible, warning, or unknown. The checks may not cover every possible combination. Signed-in users can save private builds or publish to Community, and retrieve them across sessions and devices through My builds. @@ -184,17 +179,20 @@ Selections and saved-build metadata are stored as JSON under `pc-builder-v2:` | -`BrowserRouter` in `frontend/src/main.tsx` uses `import.meta.env.BASE_URL` as its base path. With the `/project2/` deployment base, the builder's full path is `/project2/builder`. Category URLs use the values defined in `frontend/src/types/categories.ts`, such as `CPU`, rather than display labels such as `Processors`. Unknown routes display a 404 message. +`BrowserRouter` in `frontend/src/main.tsx` uses `import.meta.env.BASE_URL` as its base path. With the `/project2/` deployment base, the builder's full path is `/project2/builder`. Category URLs use the values defined in `frontend/src/types/categories.ts`, such as `CPU`, rather than display labels such as `Processors`. Unknown routes display a 404 message. Search, category filters, and sorting still use query parameters through `useCatalogParams`, so they survive refresh and browser back/forward navigation. We no longer use `?page=...` to select pages. -Most navigation uses React Router links or `navigate()`, changing pages without a full browser reload. The feedback dialog's **Go back to the build page** action currently uses `window.location.href`, which reloads the page; the build provider then restores saved selections from local storage. +Most navigation uses React Router links or `navigate()`, changing pages without a full browser reload. The feedback notification's **View build** action currently uses `window.location.href`, which reloads the page; the build provider then restores saved selections from local storage. `BuildProvider` wraps the app in `frontend/src/main.tsx`. Components use `useBuild()` from `frontend/src/context/BuildContext.ts` to access: @@ -203,17 +201,19 @@ Most navigation uses React Router links or `navigate()`, changing pages without - `removePart(category)`: remove a selection. - `resetBuild()`: clear the build. +We use `useSavedBuild()` for saved-build metadata and loading/saving actions. + Use `AddToBuildButton` for actions that add a component to this shared state. The generic `Button` remains available for other click handlers and form submissions. ### Authentication decision and ownership We evaluated three providers for our React, GraphQL, and existing PostgreSQL architecture: -| Provider | Fit and decision | -| --- | --- | -| [Clerk](https://clerk.com/docs/guides/sessions/manual-jwt-verification) | Provides authentication UI and verifiable session tokens. Suitable, but we chose to keep the small login UI in our own React application. | -| [Firebase Authentication](https://firebase.google.com/docs/auth/admin/verify-id-tokens) | Supports Google and email/password sign-in with ID tokens our GraphQL backend can verify. Selected: it fits our existing backend without moving application data. | -| [Supabase Auth](https://supabase.com/docs/guides/auth/architecture) | Supports token-based authentication and integrates with Supabase PostgreSQL. Suitable, but its integrated database features provide less benefit while we retain our VM database. | +| Provider | Fit and decision | +| --------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Clerk](https://clerk.com/docs/guides/sessions/manual-jwt-verification) | Provides authentication UI and verifiable session tokens. Suitable, but we chose to keep the small login UI in our own React application. | +| [Firebase Authentication](https://firebase.google.com/docs/auth/admin/verify-id-tokens) | Supports Google and email/password sign-in with ID tokens our GraphQL backend can verify. Selected: it fits our existing backend without moving application data. | +| [Supabase Auth](https://supabase.com/docs/guides/auth/architecture) | Supports token-based authentication and integrates with Supabase PostgreSQL. Suitable, but its integrated database features provide less benefit while we retain our VM database. | Firebase manages accounts and passwords. The frontend sends an ID token with authenticated GraphQL requests; Firebase Admin verifies its signature, expiry, issuer, and project. Basic token verification uses Google's public signing keys and the public project ID; no service-account private key is needed. We do not perform token-revocation checks or Firebase administrative account operations. Firebase public web configuration is in `frontend/src/lib/firebase.ts`, and the backend project ID is in `backend/server/src/firebase.ts`. Private database credentials remain in the ignored backend `.env`. @@ -255,11 +255,22 @@ The Community page at `/project2/community` lists published builds with their na Features we still plan to add: - Additional abuse protection beyond the existing per-IP publishing limit and GraphQL request limits. Authentication alone does not prevent publishing abuse. -- Firebase account deletion is outside the build ownership issue; build deletion is implemented. +- Account deletion and password-reset UI. We already support deleting individual builds. + +## Contributions + +We have summarized our contributions below based on the work recorded in the repository. We have worked across several areas, so these are examples rather than exclusive responsibilities. + +| Member | Contributions | +| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Kristian Gjertsen | Component cards and types, frontend GraphQL integration, publishing and Community pages, React Router navigation, responsive header, shared search/category controls, skeleton loading, and formatting setup. | +| Ludvig Zapffe | Dataset and PostgreSQL setup, catalog GraphQL API, deployment documentation, backend and compatibility tests, request/rate limits, build-parts batching, URL filter state, and UI/accessibility improvements. | +| Oscar Grande | Backend TypeScript setup, attribute filters, homepage and builder styling, keyboard/detail-view improvements, Firebase authentication, PostgreSQL users, and saving, reopening, updating, copying, and deleting owned builds with related tests. | +| Wilhelm Svensgaard Rolstad | Initial frontend setup, mobile details, search/filtering/pagination, view selection, compatibility checks, frontend testing, deferred specifications, catalog caching/memoization, image handling, and add/remove feedback. | ## Use of AI -The group used AI where we had less experience, as a discussion partner during development to explore solutions, understand unfamiliar tools and concepts, and help debug code. We also used it to help write documentation, and to help create issues and pull requests: we gave it our ideas and requirements, and it polished and formatted them. +We used AI where we had less experience, as a discussion partner during development to explore solutions, understand unfamiliar tools and concepts, and help debug code. We also used it to help write documentation, and to help create issues and pull requests: we gave it our ideas and requirements, and it polished and formatted them. AI suggestions were not used blindly. Some needed simplifying, and all were reviewed before being merged. @@ -273,19 +284,17 @@ npm run test See [frontend/src/test/README.md](frontend/src/test/README.md) for the test setup and covered scenarios. -Run backend authentication/build authorization tests from `backend/server/` with `npm test`. These execute the actual GraphQL schema and resolvers with isolated database fixtures, covering two user identities, anonymous requests, private reads, owner-only updates/deletion, private/public saving, copies, and ownerless legacy builds. They do not contact the VM, use real Google accounts, or prove the live database configuration. Frontend tests also cover reopening, account separation, draft persistence, and deletion confirmation and failure states. - -For a live deployment smoke test, use two accounts: save a private build as A, confirm it is absent from Community and B's My builds, and verify B cannot retrieve/update/delete its ID through authenticated GraphQL. Reopen/update it as A, publish it, confirm it appears publicly, then delete it as A. Keep this live check separate from the automated fixture tests. - -The backend has automated tests using Vitest, covering the GraphQL resolvers and build publishing validation. They mock the database, so no running PostgreSQL is needed. Run them from `backend/server/`: +We also have backend tests for catalog queries, input validation, request limits, batching, and build ownership. Run them from `backend/server/`: ```bash npm test ``` +This runs the Vitest suites, builds the backend, and runs the Node ownership tests. We use mocked database responses and isolated fixtures, so the tests do not require the VM or real Firebase accounts. We still need manual checks for deployed login and database access. + ## Run the prototype locally -You need Node.js and npm installed. From the repository root, run: +You need Node.js and npm installed. Use Node 24 or 26; the current React Router and Vitest dependencies do not support Node 20. From the repository root, run: ```bash cd frontend @@ -293,7 +302,7 @@ npm ci npm run dev ``` -Open the local URL printed by Vite in the terminal. +Open the local URL printed by Vite, normally `http://localhost:5173/project2/`. The backend must also be running and connected to a PostgreSQL database. In a second terminal: @@ -309,7 +318,7 @@ npm run dev The catalog requests 40 parts at a time, passing search, filter, sort, and pagination options to the API. Vite forwards `/api/*` to `http://localhost:3001/*` during development, including images. -On the VM, the web server must forward `/api/` requests to the backend on port 3001. The Vite development proxy is not included in the production build. The frontend is served under `/project2/`, but `/api/` is at the domain root. +On the VM, the web server must forward `/api/` requests to the backend on port 3001. The Vite development proxy is not included in the production build. The frontend is served under `/project2/`, but `/api/` is at the domain root. Apache must also serve `index.html` for React Router paths so direct links and refresh work. We need HTTPS for deployed login traffic. Available commands, run from `frontend/`: diff --git a/docs/accessibility-and-sustainability.md b/docs/accessibility-and-sustainability.md new file mode 100644 index 0000000..1efbe92 --- /dev/null +++ b/docs/accessibility-and-sustainability.md @@ -0,0 +1,83 @@ +# Accessibility and sustainability + +[Back to the README](../README.md) + +We have considered accessibility and sustainability while building Full Circuit. Below we describe our choices, examples from the implementation, and improvements we still need to make. + +## Accessibility + +We have used the four [WCAG principles](https://www.w3.org/WAI/fundamentals/accessibility-principles/) as a guide: content should be **perceivable**, **operable**, **understandable**, and **robust**. For Full Circuit, this means readable component information, controls that work without a mouse, clear feedback, and markup that assistive technologies can interpret. We have not completed a full WCAG audit. + +### Semantic HTML and screen readers + +We use `header`, `nav`, `main`, `footer`, headings, and lists to give the pages structure. The component detail views use definition lists (`dl`, `dt`, and `dd`) for specifications. We use real links for navigation and buttons for actions. + +Our form fields have labels, and icon buttons have accessible names such as **Close component details** and **Clear search**. Expandable specifications and compatibility sections expose their state with `aria-expanded`. Loading and success messages use `role="status"`, while errors use `role="alert"`. Compatibility results have text and icons as well as color, so we do not rely on color alone. + +Examples: [Header](../frontend/src/components/pageSections/Header.tsx), [desktop details](../frontend/src/components/DesktopComponentDetailPage.tsx), and [compatibility panel](../frontend/src/pages/pcBuilderPage/components/CompatabilityComponent.tsx). + +### Keyboard navigation and feedback + +We have added visible focus outlines so keyboard users can see which control is selected. The account cards can be reached with Tab and opened with Enter, and Delete is a separate button. The catalog has a **Load more** button as an alternative to automatic loading while scrolling. + +We use native dialogs for component details, replacement, clearing, saving, and deletion. Dialogs have accessible titles; the deletion dialog initially focuses **Keep build**. Successful component additions use a nonmodal notification, allowing us to keep browsing without being forced back to the builder. The back-to-top button returns focus to the header after scrolling. + +Examples: [focus styles](../frontend/src/index.css), [catalog](../frontend/src/components/AllPartsPreview.tsx), and [delete confirmation](../frontend/src/pages/accountPage/components/DeleteBuildButton.tsx). + +### Color contrast and images + +We have used light text against dark backgrounds and lighter status colors for readability. Using WCAG's relative-luminance formula, white text has a contrast ratio of **7.82:1** on our surface color, and the secondary text color has **4.70:1**. Our four compatibility status colors also exceed **4.5:1** on that surface. + +Orange text on the surface color is only **3.50:1**. This is below the [4.5:1 requirement for normal text](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html), so small orange links and labels still need improvement. Black text on our orange buttons has **9.40:1** contrast. These calculations cover the theme colors, not every hover state or control. + +Product images use the component name as alternative text, and catalog/detail views show a fallback when an image cannot load. We have also described the homepage illustration. Decorative icons are generally hidden from screen readers when nearby text already explains the action. + +Examples: [theme colors](../frontend/src/styles/colors.css), [product cards](../frontend/src/components/PcPartCard.tsx), and [homepage illustration](../frontend/src/pages/homePage/components/BuildPCGraphic.tsx). + +### Zoom, responsive layouts, and motion + +We have used responsive grids, wrapping text, and scrollable dialogs to keep content available on smaller screens. The builder stacks its columns on mobile, and component details have separate mobile and desktop layouts. We do not disable browser zoom. + +Several transitions respect `prefers-reduced-motion`, including the login form, detail views, and back-to-top scrolling. We still need to check motion consistently, especially the notification countdown. + +We should test text resizing at **200%** and reflow at **320 CSS pixels** or a 1280-pixel-wide browser at **400% zoom**, as described in [WCAG's reflow guidance](https://www.w3.org/WAI/WCAG22/Understanding/reflow.html). Responsive classes alone do not prove that every view passes these checks. + +### Verification and remaining improvements + +We have reviewed the markup and calculated the contrast ratios above from `colors.css`. Automated tests cover some accessible names, dialog focus, and keyboard-style interactions, but browser APIs are mocked. We have not recorded a full keyboard-only, screen-reader, or zoom test for this documentation update. + +Our next checks are to navigate the main flows using Tab, Shift+Tab, Enter, Space, and Escape; test dialogs with VoiceOver or NVDA; and check the header, forms, and details at increased zoom. We also need to improve small orange text, add a skip-to-content link, and review focus after React Router navigation. Notifications currently disappear after five seconds, so we should check reading time and focus behavior too. + +## Sustainability + +### Color choices and images + +We have chosen a dark theme for a consistent interface. Dark colors may reduce display power on some screens, but this depends on the device and brightness; we have not measured energy savings, and readability remains important. + +We use **256 × 256 WebP images** for the 800 components, with lazy loading on catalog cards. This keeps product images small and avoids immediately downloading every offscreen image. The mobile image-layout hook also produces WebP when cropping image whitespace locally, rather than creating a PNG copy. Cropping still uses processing time and memory. + +Examples: [product cards](../frontend/src/components/PcPartCard.tsx) and [image-layout hook](../frontend/src/hooks/usePartImageLayout.ts). + +### Browser caching and API requests + +We have configured the backend image endpoint with **seven-day caching** and `immutable`, allowing browsers to reuse fresh images. If we replace an image at the same URL, it may remain cached until expiry, so we should consider versioned image URLs when updating assets. + +The catalog requests **40 components at a time** instead of the entire dataset. Its GraphQL query leaves out specifications; we fetch missing details when opening or adding a part. Cards reuse specifications while mounted. The backend SQL still reads the specifications column, so there is more work we can reduce behind the API. + +We debounce uncached searches by **300 ms** and keep matching catalog responses for **one minute**, with at most **100 pages** in memory. This avoids repeated requests when returning to recent filters. We cancel obsolete requests, although server work may already have started. For build lists, a request-scoped DataLoader batches part queries instead of fetching parts separately for every build. + +Examples: [image caching](../backend/server/src/index.ts), [catalog requests](../frontend/src/api/parts.ts), [catalog cache](../frontend/src/hooks/useParts.ts), and [build-parts batching](../backend/server/src/resolvers.ts). + +### React rendering and local storage + +We have memoized catalog cards and kept callbacks stable so unchanged cards can skip rendering. Stable IDs and retaining results while loading also avoid unnecessarily recreating cards. + +We store the current draft and saved-build metadata in local storage, separately for each account. This preserves selections across refresh and navigation without a server save for every change. Saving to PostgreSQL is explicit. Local storage does not synchronize drafts across devices, and keeping full part details uses browser storage and JSON processing. + +We still keep all loaded catalog cards mounted rather than virtualizing long lists. Caching and memoization also use memory, so these are tradeoffs rather than proof of lower energy use in every situation. + +### Verification and remaining improvements + +We have checked the image formats, cache settings, request flow, and rendering code. Tests verify catalog cache reuse, pagination, card reuse, and batched build-part queries. The current automated results are recorded in the [test README](../frontend/src/test/README.md#test-results). + +We have not measured energy consumption or carbon savings. We should use browser network tools to compare cold and cached visits, inspect actual cache headers, and measure catalog versus detail payloads. React profiling can help us check unnecessary renders. Further improvements include reducing SQL overfetching, avoiding duplicate detail requests, and considering list virtualization if measurements show a need. diff --git a/frontend/src/test/README.md b/frontend/src/test/README.md index abafe1d..702e957 100644 --- a/frontend/src/test/README.md +++ b/frontend/src/test/README.md @@ -1,6 +1,6 @@ # Frontend Testing -The frontend uses automated tests to verify that the PC components catalog works as expected. +We have automated tests for the catalog, compatibility checks, build state, saved-build workflows, and deletion feedback. Tests are written using: @@ -20,7 +20,7 @@ cd frontend Install dependencies: ```bash -npm install +npm ci ``` Run all tests: @@ -35,16 +35,22 @@ This command runs the tests using Vitest and displays the results in the termina All tests are located in `frontend/src/test/`. -| Test File | Description | -| ----------------------------- | --------------------------------------------------------------------------------------- | -| `FilterComponent.test.tsx` | Tests user interaction with the search input. | -| `AllPartsPreview.test.tsx` | Tests search results, empty states, filtering, sorting, pagination, and error messages. | -| `useParts.test.tsx` | Tests the data-fetching hook and its handling of API responses and errors. | -| `CatalogIntegration.test.tsx` | Tests how the search and filter components interact with the results list. | +| Test file | Description | +| ---------------------------------------------------------- | ---------------------------------------------------------------------------------- | +| [FilterComponent.test.tsx](FilterComponent.test.tsx) | Search input updates. | +| [AllPartsPreview.test.tsx](AllPartsPreview.test.tsx) | Loading, search, filters, sorting, errors/retry, pagination, and card reuse. | +| [CatalogInteraction.test.tsx](CatalogInteraction.test.tsx) | Search, filter, and sort interactions through the catalog UI. | +| [useParts.test.tsx](useParts.test.tsx) | API results/errors, retry, catalog caching, and cache expiry. | +| [useCatalogParams.test.tsx](useCatalogParams.test.tsx) | URL state, reset, category defaults, and browser back navigation. | +| [PcPartCard.test.tsx](PcPartCard.test.tsx) | Fetching and reusing specifications, retry, and cancelled requests. | +| [checkCompatability.test.ts](checkCompatability.test.ts) | Compatibility rules and handling missing component data. | +| [BuildProvider.test.tsx](BuildProvider.test.tsx) | Storage validation and adding, removing, and clearing components. | +| [SavedBuildWorkflow.test.tsx](SavedBuildWorkflow.test.tsx) | Reopening saved builds, draft persistence, account separation, and tracking edits. | +| [DeleteBuildButton.test.tsx](DeleteBuildButton.test.tsx) | Confirmation, cancellation, successful deletion, and failure feedback. | ### Test Scenarios -The following five scenarios are covered: +Our catalog tests cover the following scenarios: #### 1. Search with Matching Results @@ -74,18 +80,12 @@ Simulates an API request failure and verifies that an appropriate error message ### Testing Approach -The tests use mocked API responses to simulate different scenarios without requiring a running backend. +We use mocked API responses and React Testing Library to test interactions without requiring a running backend. Catalog tests mock `useParts` or API calls, and pagination tests simulate `IntersectionObserver`. Saved-build tests also mock authentication and use router wrappers. -React Testing Library is used to render components and simulate user interactions, such as typing in the search field and selecting filters. - -The `useParts` hook is mocked in component tests to control the returned data. - -Pagination tests use a mocked `IntersectionObserver` to simulate infinite scrolling. - -The tests focus on frontend behavior and component interactions. They do not test the actual GraphQL backend or database. +We check loading, empty, error, and retry states as well as successful actions. Dialog and notification APIs are mocked where needed, so these tests do not replace manual keyboard, screen-reader, or responsive-layout checks. See our [accessibility and sustainability documentation](../../../docs/accessibility-and-sustainability.md). ### Test Results -The test suite contains **97 tests across 10 test files**. +On **14 November 2026**, we ran **97 frontend tests across 10 files**, and all passed. -The saved-build workflow tests cover reopening, restoring drafts after refresh, account separation, and detaching a deleted saved build. Deletion tests cover confirmation, cancellation, success, and failure. Backend authorization tests run separately with `npm test` in `backend/server`; they exercise GraphQL with isolated database fixtures, not the live VM. +We also have **85 backend Vitest tests** and **10 Node ownership tests**, all passing in the same check. Run them from `backend/server/` with `npm test`. This also builds the backend before running the ownership suite. We use database mocks/fixtures, so these results do not verify the live VM or real Firebase sign-in.