Skip to content

Breaking/2026 stack modernization round 2#661

Merged
datdamnzotz merged 47 commits intoOrcpub:developfrom
codeGlaze:breaking/2026-stack-modernization
Mar 13, 2026
Merged

Breaking/2026 stack modernization round 2#661
datdamnzotz merged 47 commits intoOrcpub:developfrom
codeGlaze:breaking/2026-stack-modernization

Conversation

@codeGlaze
Copy link

Summary

Brings develop up to date with the 2026 stack modernization branch. Major areas:

  • Docker CLI (./run): Full pipeline script replacing docker-setup.sh — setup, build, deploy, upgrade, secrets, Swarm support, --down teardown. 46-assertion test suite (no daemon needed)
  • Fork architecture: Branch-neutral fork/ pattern — branding config, integrations hooks, user tier stubs, privacy content. Shared code calls fork hooks; each branch provides its own implementation
  • Bug fixes: PDFBox 3.x API migration, loading counter (parallel HTTP), session token path, mobile header flyout, dev-mode boolean parsing, error email throttle/scrub, subscribe-outside-reactive-context warnings
  • Features: Email preferences (My Account toggle + JWT unsubscribe), Figwheel WebSocket auto-detect for Codespaces, CSP extensibility, Docker secrets support in config.clj
  • Docs: DOCKER, ENVIRONMENT, ERROR_HANDLING, email-system, branding-and-integrations

Tests

  • bash test/docker/test-upgrade.sh — 46/46 pass
  • lein test — 210 JVM tests
  • lein fig:build — CLJS compilation check
  • Dry-run merge confirmed clean (no conflicts)

script-tag now passes through all attributes (was dropping anything
besides :src and :nonce). integrations.clj is a stub with commented
examples — configure via env vars to enable.
integrations.clj exports csp-domains (connect-src, frame-src lists)
which csp.clj merges into the Content-Security-Policy header via
pedestal.clj. Stub returns empty map — no behavior change until
integrations are enabled via env vars.
- classes.cljc: "exhaustrion" → "exhaustion" (Barbarian Frenzy trait)
- email.clj: "please do no click" → "please do NOT click"
- views.cljs: remove `prn "FRAME?"` debug output from character-page
Centralizes app name, logos, copyright, email sender, and social links
behind env vars with neutral defaults. Forks override via APP_NAME,
APP_LOGO_PATH, etc. instead of find-and-replace across source files.
- routes.clj: default title/description/image read from branding vars,
  fix http→https for OG meta image URL
- email.clj: all sender names, subjects, and greeting text use
  branding/app-name and branding/email-sender-name
- views_2.cljc: logo path and copyright use branding vars (CLJ side),
  with neutral CLJS fallbacks for compilation
Replace ~60 hardcoded "OrcPub" and "Dungeon Master's Vault" references
with branding/app-name across privacy policy, terms of use, community
guidelines, and cookie policy sections. Logo path uses branding/logo-path.
Legal uppercase sections use (.toUpperCase branding/app-name).
New integrations.cljs provides no-op hooks for page view tracking
and ad banner placement. Forks override with real implementations.
Wire track-page-view! into :route event handler (single choke point
for all navigation). Document server→client config bridge pattern
in integrations.clj.
Document all new APP_* branding vars and integration vars
with examples and cross-reference to fork-customization KB doc.
System/getenv returns nil when PORT isn't in shell env,
causing Jetty to fail with "HTTP was turned off with nil port".
Now falls back to 8890 (matching dev config).
…ral integrations

Phase B: public repo abstraction layer matching DMV overrides.

New files:
- branding.cljs: reads window.__BRANDING__ with OrcPub defaults
- user_tier.cljs: :user-tier sub always returns :free
- user_data.clj: pass-through enrich-response + empty registration-defaults

Modified:
- branding.clj: add support-email, help-url, email-from-address, client-config
- index.clj: inject window.__BRANDING__ JSON via cheshire
- integrations.cljs: rename ad-banner → content-slot, add on-app-mount!,
  strip ad/analytics example comments
- integrations.clj: strip analytics/SDK example comments, simplify docs
- views.cljs: wire branding (logo, social links, copyright, app-name),
  require user-tier + integrations
- character_builder.cljs: patreon-link-props → supporter-link-props,
  wire to branding/social-links
- routes.clj: wire user-data hooks (enrich-response, registration-defaults)
- email.clj: emailfrom → branding/email-from-address
- .env.example: add APP_SUPPORT_EMAIL, APP_HELP_URL, clean integration comments
…list! stub

- views_2.cljc: remove CLJS reader conditionals, use branding on both sides
- integrations.cljs: add track-character-list! no-op stub
Mirror DMV integration hook API on public branch:
- supporter-link: header supporter button (shows when URL configured)
- support-banner: no-op stub (fork overrides with announcements)
- content-slot: now accepts user-tier arg, returns nil by default
- pdf-options-slot: no-op stub below PDF sheet options
- share-links / share-link-www: email share with dynamic protocol
- track-character-list!: no-op stub

Add field-limits to branding config bridge ({:notes 50000 :text 255
:number 7}). Add current-year and raw-html generic helpers to views.cljs.
Social links section gets usage examples in comments.

Shared files (views.cljs, character_builder.cljs) now call integrations
hooks identically to DMV — fork-specific rendering decisions live in
the override file, not inline in shared code.
branding.clj: add app-url def, :bluesky social link, dynamic copyright-year
email.clj: add error handling to send-email-change-verification, fix
  "Patron" greeting to use name/neutral, fix grammar
integrations.clj: add client-config stub (empty map)
index.clj: inject window.__INTEGRATIONS__ alongside __BRANDING__
privacy.clj: replace hardcoded "orcpub.com" with branding/app-name
views.cljs: add bluesky inline SVG icon, add discord/bluesky to social links
- Add send-updates? to user-body API response
- Add /unsubscribe GET endpoint with JWT-signed token verification
- Add /unsubscribe-success SPA page (follows verify-success pattern)
- Add PUT /user for update-user-preferences (toggle send-updates?)
- Add :toggle-send-updates event, :send-updates? subscription
- Add email updates checkbox to My Account page
- Backport social-links-footer to email.clj (self-gating on empty config)
- Add unsubscribe-url helper to email.clj
- Add 4 new test suites (token roundtrip, handler, preferences, user-body)
- 210 tests, 963 assertions, 0 failures / 0 CLJS warnings
Figwheel's default ws://localhost:3449 fails in remote environments
where the browser connects through a forwarded hostname. start.sh now
auto-detects GitHub Codespaces and passes --fw-opts to override the
connect URL with the correct wss:// endpoint. FIGWHEEL_CONNECT_URL
env var available for other remote setups (Gitpod, tunnels).

- scripts/start.sh: Codespaces detection + --fw-opts EDN override
- scripts/common.sh: FIGWHEEL_CONNECT_URL env var
- .devcontainer/devcontainer.json: port 3449 public (WebSocket needs it)
- .env.example: document FIGWHEEL_CONNECT_URL
- start.sh show_help(): FIGWHEEL_CONNECT_URL + FIGWHEEL_PORT in env
  vars list, Remote Dev section in notes
- start.sh run_checks(): report remote dev detection status for
  figwheel and all targets (auto-detect / configured / local)
- menu show_help(): Remote Dev section with FIGWHEEL_CONNECT_URL ref
characters, parties, and user subscriptions (reg-sub-raw) were firing
HTTP requests even when no auth token existed in app-db, producing
spurious 401s in the browser console on every page load.

Added token check ([:user-data :token]) before the go block in each
subscription — no token means no request, just return [].

Also fixed :user sub which checked the wrong path ([:user :token]
instead of [:user-data :token]) — worked by accident, now correct.

Matches the existing guard pattern in equipment_subs.cljs.
- views.cljs content-page: read user-tier/username/email from app-db
  directly in componentDidMount (lifecycle is not a reactive context)
- subs.cljs: add token guard to ::folder5e/folders (same pattern as
  characters/parties/user)
- core.cljs: remove subscribe trace monkey-patch (served its purpose)
- subs_test.cljs: add folders guard tests
Rewrites send-error-email with:
- 5-minute throttle per error fingerprint (prevents email storms)
- Request scrubbing (strips credentials, cookies, body params, Datomic objects)
- Stack trace filtering (orcpub.* frames, falls back to deepest non-infra)
- Full cause chain rendering
- Pedestal interceptor metadata extraction
- Fix missing closing paren that broke compilation
- pedestal.clj: replace bare prn with io.pedestal.log/error in ETag interceptor
- routes.clj: remove dead commented-out oauth require
- routes.clj: update-user-preferences re-reads DB after transact (authoritative response)
- routes.clj: re-throw unrecognized ExceptionInfo in save-character-handler
Env vars are strings. (boolean "false") returns true because any non-nil,
non-false value is truthy. Fixed to compare against the string "true".

This caused CSP nonce-interceptor to be a no-op in prod (DEV_MODE=false
was parsed as truthy), resulting in empty Content-Security-Policy headers.

Also removes dead devmode? def from index.clj (decoupled since 15b6d2a).
- BRANDING-AND-INTEGRATIONS.md: all paths updated to fork/ subdirectory,
  added integrations config bridge note and pedestal.clj CSP row
- ENVIRONMENT.md: DEV_MODE must be string "true", added FIGWHEEL_PORT
  and FIGWHEEL_CONNECT_URL, updated file-reads table for fork/ paths
- .env.example: fork/ path comments, APP_PAGE_TITLE rename, FIGWHEEL_PORT
- README.md: test count 74→210, assertions 237→963
- dev-tooling.md: dev-mode? description matches actual behavior
- docs/README.md: link to new KB index
- docs/TODO.md: Datomic transactor crash investigation entry
- docs/kb/: agent knowledge base — crash analysis with log evidence
- docs/error-email-improvements.md: analysis and handoff doc
…alling it

on-click had #(swap! hovered? not) which creates a new function and
discards it. Changed to (swap! hovered? not) which actually toggles.

Also: guard mouseenter/mouseleave with when-not mobile? to prevent
synthetic mouse events from immediately closing the flyout on touch.
Menu items now stopPropagation + close flyout on tap.
route-to-login now sets :loading false in app-db. Prevents the
loading overlay from covering the login page when multiple parallel
401 responses race to set/clear the boolean flag.
…er fight

set-loading now increments/decrements a counter. Overlay shows when > 0.
Multiple reg-sub-raw go blocks firing simultaneously (characters, parties,
folders, items) each toggle loading independently without stomping each
other. route-to-login resets counter to 0.
Three breaking PDFBox 2.x → 3.x API changes were crashing spell card
generation silently (caught by bare catch in add-spell-cards!), leaving
a blank page in exported PDFs:

1. setStrokingColor(225,225,225) — 3.x float overload requires 0.0-1.0
   range, not 0-255. Reflection dispatched to float method → validation
   error. Fixed by normalizing to (/ 225.0 255.0).

2. drawLine(x1,y1,x2,y2) — removed in 3.x. Replaced with
   moveTo/lineTo/stroke sequence.

3. moveTextPositionByAmount → newLineAtOffset, drawString → showText.

All setNonStrokingColor/setStrokingColor calls now use explicit (float)
casts to prevent reflection from hitting the wrong overload.
codeGlaze added 17 commits March 1, 2026 01:52
…used loading flash

:verify-user-session (startup auth check) tested (:token (:user db))
instead of (:token (:user-data db)). This meant it never fired, leaving
expired tokens in app-db. When reg-sub-raw subs later hit 401s, the
loading overlay flashed before redirect to login.

Also reset loading counter on 401 in the check, and fixed duplicate comment.
Cherry-picked from dmv/hotfix-integrations (8627b598).
fork/integrations.cljs conflict resolved: kept breaking/ version (stubs).
LocalDate/now defaults to JVM timezone (UTC in Codespaces). A build at
10 PM CST reports as the next day. Macro now reads TZ env var if set,
falls back to JVM default. Added TZ=America/Chicago to .env, .env.example,
and Dockerfile (build + runtime stages with tzdata package).
Modern Docker Compose supports build: and image: in the same service.
Two-file split (from 2019) is dead weight — admin builds with bare
docker build, swarm ignores build: directives, nobody uses the
separate file. Single file now handles both pull and build-from-source.
docker build tags images as orcpub-app/orcpub-datomic, but compose
defaults to Docker Hub names. Compose pulled old Datomic Free images,
causing :unsupported-protocol :dev at runtime. Set ORCPUB_IMAGE and
DATOMIC_IMAGE env vars so compose uses the locally-built Pro images.
- .gitattributes: merge=ours for fork/ files and devcontainer.json
- .gitignore: untrack .claude/, newrelic*, deploy/transactor.properties
- devcontainer.json: restore Docker-in-Docker feature (lost in cherry-pick)
…+ test suite

File-copy from dmv/hotfix-integrations with DMV branding stripped:
- docker-setup.sh: 372 → 1332 lines (--check, --build, --deploy, --upgrade, --secrets)
- docker-compose.yaml: aligned env vars, Docker Secrets docs, variable comments
- docker-user.sh: ANSI-C color quoting fix, tr -d '\r' for Windows line endings
- .env.example: separate DATOMIC_PASSWORD, image tag vars, generic branding placeholders
- test/docker/: 46-test suite (test-upgrade.sh) with 8 fixture .env scenarios
- .gitignore: add generated secrets files
Mirror of dmv/hotfix-integrations fork extraction. Shared source files
are now identical between branches — only fork/ files differ.

New fork/ files (community/public stubs):
- fork/auth.clj: 24h tokens, no login tracking
- fork/splash.cljc: community label, no generators
- fork/privacy_content.clj: standard privacy policy

Extended fork/ files:
- fork/branding.clj + .cljs: copyright-url (empty),
  registration-logo-class (h-55), restrict-print-to-owner? (false)

Shared files updated to consume fork/ values:
- routes.clj, views_2.cljc, views.cljs, privacy.clj, .gitattributes
Cherry-picked shared files from dmv/ — no merge commit, preserving
clean merge path for breaking/ → dmv/ sync.

Infrastructure: docker-setup.sh → run CLI redesign, Docker secrets,
nginx dev config, transactor template, compose CI workflow updates.

App: PDFBox 3.x migration, CSP extensibility, email error throttle,
config.clj secrets support, session token path fix, cookie consent.

Docs: DOCKER, ENVIRONMENT, ERROR_HANDLING, email-system, branding.

Assets: 6 SVGs, favicon, SRD PDF → dnld/, 5e actions reference,
build.bat, removed unreferenced logo variants.
12 files still referenced the old script name in comments,
error messages, and documentation.
Suppress intentional SC2016 (literal '${' check), SC2001 (regex too
complex for parameter expansion), fix SC2129 (grouped redirects).
- Remove PR comment step from CI (fork PRs lack write access, always 403)
- Update docker-integration password validation for new URL format
  (password no longer embedded in DATOMIC_URL, appended at runtime)
./run --auto (naked mode) now runs the full pipeline (setup→build→up).
The CI --force step regenerated passwords against an existing H2 database
created by the first run, causing "Unable to connect to embedded storage".

Add docker compose down + rm -rf data/ between setup validation steps
so CI's explicit build/start steps get a clean slate.
@datdamnzotz datdamnzotz merged commit a889d69 into Orcpub:develop Mar 13, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants