Previous approach read config.json once at module load time. If the PVC
wasn't fully mounted yet, it fell back to 'admin' and stayed there for
the lifetime of the process. New approach caches for 5 seconds and re-reads
on expiry, so it recovers from startup races and picks up changes without
requiring a restart.
- ALLOWED_ORIGINS and NODE_ENV now read via $env/dynamic/private so Vite dev
server picks them up correctly from .env
- .env.example: document ALLOWED_ORIGINS with dev IP example
- .env: add http://192.168.0.94:5173 to ALLOWED_ORIGINS for local dev
- login/+server.ts: read env vars via $env/dynamic/private (works in Vite dev)
- auth.ts: requireAuth now takes secret as parameter instead of reading process.env
- authRequest.ts: new route-level helper that reads SESSION_SECRET from
$env/dynamic/private and passes it to requireAuth
- All admin routes updated to import authRequest from authRequest.ts
- SESSION_SECRET and PASSWORD_HASH moved out of config.json into env vars
- data.ts: AppConfig no longer holds secrets; loadConfig/loadData seed from
defaults/ on first run if data/ files are missing
- auth.ts: requireAuth/authRequest read SESSION_SECRET from process.env directly
- login/+server.ts: reads PASSWORD_HASH and SESSION_SECRET from process.env
- defaults/config.json: ships with image (no secrets)
- defaults/units.json: ships with image as initial unit data
- package.json: add dotenv dep; start/serve load .env via -r dotenv/config
- Dockerfile: copy defaults/ into image; data/ is PVC-only
- .env.example: documents required env vars for local dev
- Remove k8s/ — managed externally
- loadConfig(): if config.json missing, create data/ dir, write default config
with random sessionSecret and empty passwordHash, log instructions
- loadData(): throw clear error if units.json missing instead of crashing opaquely
- deployment.yaml: remove envFrom secretRef (SESSION_SECRET/PASSWORD_HASH not
read from env — config.json on PVC is the source of truth)
- Remove build-local.sh and old build-push.sh (crictl/scp/buildah workflow)
- New build-push.sh: docker build + push to registry.nerdrage.cloud/dickloads
- deployment.yaml: update image to registry.nerdrage.cloud/dickloads:latest, imagePullPolicy Always
- hooks.server.ts: replace empty stub with multi-origin CSRF guard
- Always allows humorunits.com and dickloads.com (+ www variants)
- ALLOWED_ORIGINS env var for additional origins (staging, preview)
- Dev: localhost:3000/4173/5173 auto-allowed when NODE_ENV != production
or ALLOWED_ORIGINS is unset — no config needed for local dev
- svelte.config.js: disable built-in single-origin CSRF check (we own it now)
trustProxy: true so X-Forwarded-Proto/IP are correct behind nginx-ingress
- login route: derive secure-cookie flag from X-Forwarded-Proto header so
session cookies are marked Secure even when Node sees plain HTTP from ingress
- deployment.yaml: add NODE_ENV=production and explicit ALLOWED_ORIGINS
- ⇅ button on each group header: sorts units alpha (labelPlural + symbol tiebreak) via order API
- ⚙ Settings moved to admin-header alongside Log out
- Unit type gets sortPosition?: number (1-based, optional)
- Converter sort: partitions pinned/floating, inserts pins at requested slots
Pins > length append at end; gaps fill naturally with floating items
- Admin unit form: Force position number input
- Unit ID rename: updates group.baseUnitId refs + {old-id:*} in all descriptions
- Group ID rename: updates unit.group refs + {old-id:*} in all descriptions
- Admin form ID fields unlocked; hint text updated
- renameIdInDescription() shared helper in both PUT handlers
- start.sh: builds and launches Node production server on 192.168.0.94:4078
- Multi-domain CSRF: not needed — SvelteKit natively passes when Origin matches Host
- hooks.server.ts: minimal stub with explanation
- svelte.config.js: reverted csrf experiment (not supported in this version)
Concurrent calls from rapid reloads or rapid saves could pile up,
each writing state on completion and potentially re-triggering more loads.
Guards ensure only one load is in flight at a time; extra calls are dropped.
- AppConfig gains yoloVisibility: 'auto' | 'never' (default: auto)
- Group gains alwaysShowLabel?: boolean
- YOLO toggle only renders when showYoloToggle is true (auto + >1 group)
- Group divider shows when >1 group OR alwaysShowLabel is set
- New /admin/api/config GET+PUT endpoint for yolo settings
- Admin: alwaysShowLabel checkbox in group form
- Admin: ⚙ Settings panel with YOLO label, description, and visibility controls
When a {id:description} reference forms a cycle, the back-reference
now expands to 'Label (symbol)' rather than being stripped.
So pound ↔ dickload renders as 'See dickload (dl)' and 'See pound (lb)'.
Two bugs:
1. Sentinel used double-underscore delimiters (__DESC:id__) — fragile regex
[^_]+ would break on IDs containing underscores, and Pass-1 output
containing sentinels would leak through Pass 2 unstripped.
2. After Pass 2 substitution, sentinels inside substituted text (from
Pass-1 output of the referenced id) were never cleaned up.
Fix: switch to null-byte delimiters (\x00DESC:id\x00) which cannot appear
in user text, and add a second .replace(SENTINEL_RE, '') pass to strip any
sentinels that survive after substitution (self-refs, nested cycles).
- selectUnit/selectGroup now async; call autoSaveCurrentForm before switching
- Enter in any form field (except textarea) triggers save
- Escape restores form to snapshot (discards unsaved changes)
- discardUnitForm/discardGroupForm helpers for snapshot restore