diff --git a/docs/AI_USAGE.md b/docs/AI_USAGE.md index 44e324f..42fefcb 100644 --- a/docs/AI_USAGE.md +++ b/docs/AI_USAGE.md @@ -596,3 +596,11 @@ - **What the team did:** Took the peer feedback that the README (about 59 kB) is too long to find your way in, wrote issue #182, and asked for the reasoning to be kept. - **What Claude did:** Moved four long sections unchanged to their own documents (`ELEMENTS_PAGE.md`, `DATABASE_SETUP.md`, `CATALOG_SEARCH.md`, `E2E_TESTS.md`) with a short summary and a link in the README (now about half the size). Added a "Where is what" table that points from each feature to the file where it starts, and a list of contents at the top of `docs/API.md`. A script checked that every relative link and anchor in the changed files works. - **Limits:** The text of the moved sections was not shortened or checked again, so what was outdated in them is still outdated (that is issue #148). A first run of the move emptied `README.md` because a formatting command was cut off in a pipe; it was restored from git and run again. + +## Brotli and the size of the icons (`docs/SUSTAINABILITY.md`) + +- **Tool:** Claude Code +- **What the team did:** Took the peer feedback that Lucide icons cannot be tree-shaken and that Brotli could help, and wrote issue #203 with the order: measure first, change only if the claim is true. +- **What Claude did:** Measured the Lucide modules in the build with a temporary bundler plugin (not committed): the 13 icons the app imports are in the bundle, with 12 helper modules, 12.1 kB before minification, so nothing was changed. Measured gzip and Brotli (qualities 5 and 11, with the time each takes) on the built files with Node's `zlib`, and checked what the VM sends with `curl` (gzip, and no Brotli). Read the `mod_brotli` documentation and wrote the numbers and the decision in `docs/SUSTAINABILITY.md`. +- **Found by testing:** The example in the `mod_brotli` documentation (`AddOutputFilterByType BROTLI_COMPRESS …`) is not enough on Ubuntu: a container test with the same Ubuntu and Apache versions as the VM showed that a browser (`Accept-Encoding: gzip, deflate, br`) still got gzip, because `deflate.conf` adds the gzip filter first. Four ways to set it up were compared in the container, and `SetOutputFilter BROTLI_COMPRESS;DEFLATE` in a `LocationMatch` gave Brotli to browsers, gzip to gzip-only clients and nothing to the fonts. The documentation does not say which filter wins, so `docs/SUSTAINABILITY.md` says that it was found by testing. +- **Limits:** The Apache setup was tested in a container, not on the VM itself. The sizes are of the built files and not of a visit. The Lucide size is before minification, so the real share is a little smaller. The numbers are for the build of 2026-11-11, before the split per route (#176). diff --git a/docs/SUSTAINABILITY.md b/docs/SUSTAINABILITY.md index 3900b4c..92d9a3e 100644 --- a/docs/SUSTAINABILITY.md +++ b/docs/SUSTAINABILITY.md @@ -34,6 +34,27 @@ The catalog JSON is no longer imported into the frontend to build the element de **Where:** the API-backed detail query in [catalogOperations.ts](../src/lib/catalogOperations.ts) and its use in [ElementDetailPage.tsx](../src/pages/ElementDetailPage.tsx); the historical comparison is recorded in [AI_USAGE.md](./AI_USAGE.md). +### Compress what is sent, and check what the icons add + +The peer feedback (collected in #167, translated from Norwegian) said: + +> Lucide icons cannot be tree-shaken by default, but it is possible with Babel, a custom Rollup plugin or direct imports. Brotli compression can also help. + +We checked both before changing anything. **Result: we change no code, and Brotli is not set up on the VM.** The icon imports stay as they are because the check showed nothing to fix. Brotli was considered, but the team did not have the time and capacity for it and had to prioritise other things, and the gain is small (see below). The measurements are written down so that it can be done later. + +**Compression.** The VM compresses the JavaScript and CSS with gzip, but not with Brotli: a request with `Accept-Encoding: br` got the 720 kB file as it was, and one with `gzip` got 225 kB (checked with `curl` on 2026-11-11, Apache 2.4.66). Brotli is supported by every current browser. We measured what it would save on the build of 2026-11-11 (one JavaScript file of 766.9 kB and one CSS file of 70.8 kB, before the split per route in #176), with Node's `zlib` on the files in `dist/assets/`: + +| | JavaScript | CSS | +| ------------------------------------------ | ------------------------------ | ------------------- | +| Uncompressed | 766,907 B | 70,762 B | +| gzip, level 6 (Apache's default) | 239,781 B | 13,183 B | +| Brotli, quality 5 (`mod_brotli`'s default) | 225,354 B (6% less than gzip) | 12,382 B (6% less) | +| Brotli, quality 11 | 203,138 B (15% less than gzip) | 11,265 B (15% less) | + +The time it takes matters when the server compresses on every request: quality 5 took 15 ms for the JavaScript (the same as gzip), and quality 11 took 1,050 ms. So `mod_brotli` at its default quality saves about 14 kB of 240 kB on the first load, and the 15% of quality 11 needs the files to be compressed once when the app is built and not for each visitor (not done). The numbers above are measured on the built files and not on the wire, and nothing was changed on the VM. **Why it was not done:** there were two reasons. The team did not have the time and capacity, and had to prioritise other work. And the gain is small: the default quality gives about 6% on a file that gzip already compresses by 69%, and the larger gain needs a compression step in the deployment that every person who deploys would have to remember. If it is done later: use `SetOutputFilter BROTLI_COMPRESS;DEFLATE` in a `LocationMatch` for `/project2/assets/`, and not `AddOutputFilterByType BROTLI_COMPRESS` alone (the example in the `mod_brotli` documentation). In a container with the same Ubuntu and Apache versions as the VM, the second one gave gzip to a browser, because `deflate.conf` adds the gzip filter first. It was not tried on the VM itself. With the split per route (#176, a first file of 522 kB) the same measurement gives 164,654 B with gzip, 157,251 B with Brotli at quality 5 and 142,851 B at quality 11. + +**Icons.** The peer feedback said that the Lucide icons cannot be tree-shaken. We measured it with a small temporary plugin that listed the modules of `lucide-react` in the build (`chunk.modules` of the bundler, not committed). The app imports 13 icons by name, and exactly 13 icon modules are in the bundle, together with 12 small helper modules that every icon needs; the other 4,300 or so icons of the package are not. All 25 modules are **12.1 kB before minification** (the icons are 270–560 B each, 4.7 kB together, and the helpers 7.4 kB), out of 767 kB for the file. Importing from `lucide-react` does tree-shake with the current Vite, so nothing was changed in how the icons are imported. The package is built as ES modules and is meant to be used with named imports, as we do (`import { Plus } from 'lucide-react'`); what does not tree-shake is importing the whole `icons` object, which we do not do ([the guide of `lucide-react`](https://lucide.dev/guide/packages/lucide-react) says the same). A Babel or Rollup plugin for the icons would save at most those 12.1 kB and add a build step. + ### Reuse cached search results Apollo Client's in-memory cache stores search pages by search text, filters, and sort order. When a user returns to a search already loaded during the same app session, Apollo can display the cached results without requesting them again. Loading another page adds it to that search's cached result list.