{"id":"6a464fa42396a08c57450a75","title":"2026 07 02","path":"how-to/committee/release-notes/2026-07-02","contentMarkdown":"# 02-Jul-2026 — perf+fix(server+admin): memory economies, restart reporting when session is lost, CSV d... [#304](https://github.com/nbarrett/ngx-ramblers/issues/304)\n\n## [build 727](https://github.com/nbarrett/ngx-ramblers/actions/runs/28586959360) — [commit 50f784d](https://github.com/nbarrett/ngx-ramblers/commit/50f784dd7056af41cc85a22867503aadcbcbe90c)\n\n_____\n\n## What's new\n\nForensic memory work driven by a production site hovering at 89% of its 259MB heap limit, plus two small fixes from production testing. The server was paying for admin-only features on every boot: playwright (28MB), jsdom (29MB) and xlsx (13MB) all loaded eagerly even though they are only used for static-site migration, document conversion and member bulk load. They now load on first use, cutting roughly 70MB from the boot baseline of every machine. The area-map GeoJSON caches, which could hold up to three 50MB copies of the full-UK district file forever after a single admin visit to area configuration, now evict after 10 minutes idle and share one parse instead of two. The machine restart button no longer misreports a successful restart as a failure when the login session does not survive the restart, and a CSV exported from the walk export page can now be re-imported through the walk import page without date errors.\n\n## At a glance\n\n- Server boot memory drops by roughly 70MB on every environment; the biggest saving on 512MB machines.\n- Area-map admin pages no longer pin ~100MB of UK district geometry in memory for the life of the process\n- the data loads on demand and is released after 10 minutes unused.\n- Campaign recipient exports cache at most 3 campaigns instead of growing without limit.\n- The unused googleapis dependency (200MB of node_modules, ~60MB of heap if ever imported) is removed; google-auth-library, which the inbox OAuth flow actually uses, is now declared directly and a lint rule stops googleapis creeping back in.\n- Restarting a machine from Memory Diagnostics now shows \"Machine restarted\" with a prompt to log in again, instead of \"Restart failed\", when the restart logs you out.\n- Walk CSV files import with dates in either yyyy-MM-dd or dd/MM/yyyy form, so NGX-exported CSVs round-trip cleanly.\n- Fly machine history gains 15m and 30m ranges alongside 1h-7d, showing per-scrape (15s) detail for close-up analysis of restarts and upload runs, plus its own Refresh button beside the Range toggle; the history now lives in its own Fly Machine History frame rather than inside Memory Diagnostics.\n\n## Technical changes\n\n- playwright, jsdom, mammoth, pdf-parse and xlsx move from top-level imports to dynamic imports at their point of use; verified none of them load when the owning route modules are imported.\n- New shared idleCached() helper (single-value async cache with idle-TTL eviction and clear()); the bundled GeoJSON, S3 GeoJSON and district index in areas.ts use it, replacing four permanent module-level caches, and the serving path reuses the bundled parse rather than re-reading the file.\n- idleCached() clears via a generation counter so a load in flight when clear() is called cannot re-cache its stale result.\n- Brevo campaign recipients rowsCache evicts oldest entries beyond 3 campaigns.\n- googleapis removed from server dependencies (nothing imports it; Gmail and Google Cloud go via REST with fetch); google-auth-library promoted to a direct dependency; no-restricted-imports lint rule bans googleapis with a pointer to the fetch pattern.\n- The npm \"fs\" security-holding package removed from dependencies (require(\"fs\") always resolves to the Node builtin); all server package.json versions now pinned exact.\n- The restart poll treats a 401 from the machine-state endpoint as restart-complete-but-session-expired (environment target only\n- a worker-target 401 keeps polling): polling stops and a success alert explains the figures cannot refresh until a new login.\n- parseCsvDate() tries ISO first and falls back to the Walks Manager CSV export format (dd/MM/yyyy), aligning the import path with what the export writes.","contentHtml":"<h1>02-Jul-2026 — perf+fix(server+admin): memory economies, restart reporting when session is lost, CSV d... <a href=\"https://github.com/nbarrett/ngx-ramblers/issues/304\">#304</a></h1>\n<h2><a href=\"https://github.com/nbarrett/ngx-ramblers/actions/runs/28586959360\">build 727</a> — <a href=\"https://github.com/nbarrett/ngx-ramblers/commit/50f784dd7056af41cc85a22867503aadcbcbe90c\">commit 50f784d</a></h2>\n<hr>\n<h2>What&#39;s new</h2>\n<p>Forensic memory work driven by a production site hovering at 89% of its 259MB heap limit, plus two small fixes from production testing. The server was paying for admin-only features on every boot: playwright (28MB), jsdom (29MB) and xlsx (13MB) all loaded eagerly even though they are only used for static-site migration, document conversion and member bulk load. They now load on first use, cutting roughly 70MB from the boot baseline of every machine. The area-map GeoJSON caches, which could hold up to three 50MB copies of the full-UK district file forever after a single admin visit to area configuration, now evict after 10 minutes idle and share one parse instead of two. The machine restart button no longer misreports a successful restart as a failure when the login session does not survive the restart, and a CSV exported from the walk export page can now be re-imported through the walk import page without date errors.</p>\n<h2>At a glance</h2>\n<ul>\n<li>Server boot memory drops by roughly 70MB on every environment; the biggest saving on 512MB machines.</li>\n<li>Area-map admin pages no longer pin ~100MB of UK district geometry in memory for the life of the process</li>\n<li>the data loads on demand and is released after 10 minutes unused.</li>\n<li>Campaign recipient exports cache at most 3 campaigns instead of growing without limit.</li>\n<li>The unused googleapis dependency (200MB of node_modules, ~60MB of heap if ever imported) is removed; google-auth-library, which the inbox OAuth flow actually uses, is now declared directly and a lint rule stops googleapis creeping back in.</li>\n<li>Restarting a machine from Memory Diagnostics now shows &quot;Machine restarted&quot; with a prompt to log in again, instead of &quot;Restart failed&quot;, when the restart logs you out.</li>\n<li>Walk CSV files import with dates in either yyyy-MM-dd or dd/MM/yyyy form, so NGX-exported CSVs round-trip cleanly.</li>\n<li>Fly machine history gains 15m and 30m ranges alongside 1h-7d, showing per-scrape (15s) detail for close-up analysis of restarts and upload runs, plus its own Refresh button beside the Range toggle; the history now lives in its own Fly Machine History frame rather than inside Memory Diagnostics.</li>\n</ul>\n<h2>Technical changes</h2>\n<ul>\n<li>playwright, jsdom, mammoth, pdf-parse and xlsx move from top-level imports to dynamic imports at their point of use; verified none of them load when the owning route modules are imported.</li>\n<li>New shared idleCached() helper (single-value async cache with idle-TTL eviction and clear()); the bundled GeoJSON, S3 GeoJSON and district index in areas.ts use it, replacing four permanent module-level caches, and the serving path reuses the bundled parse rather than re-reading the file.</li>\n<li>idleCached() clears via a generation counter so a load in flight when clear() is called cannot re-cache its stale result.</li>\n<li>Brevo campaign recipients rowsCache evicts oldest entries beyond 3 campaigns.</li>\n<li>googleapis removed from server dependencies (nothing imports it; Gmail and Google Cloud go via REST with fetch); google-auth-library promoted to a direct dependency; no-restricted-imports lint rule bans googleapis with a pointer to the fetch pattern.</li>\n<li>The npm &quot;fs&quot; security-holding package removed from dependencies (require(&quot;fs&quot;) always resolves to the Node builtin); all server package.json versions now pinned exact.</li>\n<li>The restart poll treats a 401 from the machine-state endpoint as restart-complete-but-session-expired (environment target only</li>\n<li>a worker-target 401 keeps polling): polling stops and a success alert explains the figures cannot refresh until a new login.</li>\n<li>parseCsvDate() tries ISO first and falls back to the Walks Manager CSV export format (dd/MM/yyyy), aligning the import path with what the export writes.</li>\n</ul>\n"}