diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4e761bb --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Copy to `.env` at the repo root (gitignored) if you want plain +# `docker compose up -d --build` to interpolate ${FRONTEND_PORT} etc. +# from backend/.env. Secrets stay in backend/.env only — never commit .env. +COMPOSE_ENV_FILES=./backend/.env diff --git a/.gitignore b/.gitignore index 3b123d1..d189398 100644 --- a/.gitignore +++ b/.gitignore @@ -34,13 +34,13 @@ __pycache__/ venv/ env/ -# Environment / secrets — app secrets only in backend/.env (never commit it). -# Root .env is a Compose pointer (COMPOSE_ENV_FILES) with no secrets; keep it. -backend/.env +# Environment / secrets — NEVER commit real .env files +.env +**/.env .env.* -!.env +**/.env.* !.env.example -!backend/.env.example +!**/.env.example !frontend/.env.development !frontend/.env.production @@ -71,3 +71,7 @@ tests/** # Paper form source documents (Annexure A/E/J) — reference material, not code. # Root-anchored: backend/candidate_forms/ is the forms domain package and IS tracked. /candidate_forms/ +frontend/dist/** */ +docker.local.frontend/dist/** */ +frontend/dist/index.html +frontend/dist/index.html diff --git a/frontend/dist/index.html b/frontend/dist/index.html index 7498ad5..df37875 100644 --- a/frontend/dist/index.html +++ b/frontend/dist/index.html @@ -23,7 +23,7 @@ - + diff --git a/frontend/src/lib/charts.js b/frontend/src/lib/charts.js index f86617a..08f73d8 100644 --- a/frontend/src/lib/charts.js +++ b/frontend/src/lib/charts.js @@ -48,7 +48,17 @@ function css(name) { return getComputedStyle(document.documentElement).getProper const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); const w = rect.width || canvas.clientWidth || 600; - const h = parseInt(canvas.getAttribute('height')) || 260; + // The LOGICAL height, remembered on first setup. Reading the `height` + // attribute here instead would read back the DEVICE-pixel value written + // below (h * dpr): with dpr > 1 every redraw multiplied the canvas by dpr, + // and the ResizeObserver on the auto-height .chart-wrap fed each growth + // straight back in — canvases reached millions of px tall and the chart + // became an invisible speck at the top of a giant blank canvas. + let h = Number(canvas.dataset.baseHeight); + if (!h) { + h = parseInt(canvas.getAttribute('height')) || 260; + canvas.dataset.baseHeight = String(h); + } canvas.width = w * dpr; canvas.height = h * dpr; canvas.style.height = h + 'px'; const ctx = canvas.getContext('2d'); diff --git a/frontend/src/ui/Chart.jsx b/frontend/src/ui/Chart.jsx index 2d6d55f..e96f608 100644 --- a/frontend/src/ui/Chart.jsx +++ b/frontend/src/ui/Chart.jsx @@ -29,6 +29,11 @@ export default function Chart({ type, data, options, height = 260, className = ' const canvas = ref.current if (!canvas || typeof Charts[type] !== 'function') return undefined + // setup() caches the logical height on first draw (dataset.baseHeight) so a + // redraw never reads back the device-pixel height it wrote. Clear it here so + // a changed `height` prop is picked up rather than the stale cached value. + delete canvas.dataset.baseHeight + const draw = () => { if (canvas.isConnected && canvas.getBoundingClientRect().width > 0) { Charts[type](canvas, data, options)