Skip to content

Breaking/2026 stack modernization#23

Open
codeGlaze wants to merge 138 commits intodevelopfrom
breaking/2026-stack-modernization
Open

Breaking/2026 stack modernization#23
codeGlaze wants to merge 138 commits intodevelopfrom
breaking/2026-stack-modernization

Conversation

@codeGlaze
Copy link
Owner

Description:

Related issue (if applicable): fixes #

Checklist:

  • The code change is tested and works locally.
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation if necessary
  • There is no commented out code in this PR.
  • My changes generate no new warnings (check the console)

Dependency upgrades: Clojure 1.12.4, ClojureScript 1.12.134,
Pedestal 0.7.0, Datomic Pro 1.0.7482, React 18.3.1, Reagent 2.0.1,
re-frame 1.4.4, PDFBox 3.0.6, Buddy 3.x, figwheel-main 0.2.20,
java-time 1.4.2, and supporting library updates.

Key changes:
- Datomic Free -> Pro (Java 21 compatibility)
- Pedestal interceptor wrapping + CSP nonce handling (required by 0.7)
- React 18 createRoot migration
- clj-time -> java-time
- lein-figwheel -> figwheel-main
- CI updated to Java 21
- Devcontainer + scripts for Datomic Pro setup

74 tests, 237 assertions, 0 failures.
Migration index + per-topic docs covering Pedestal 0.7 (CSP, interceptor
wrapping, Jetty 11 pin), Datomic Pro, React 18/Reagent 2, library upgrades,
dev tooling, Java 21 compatibility, and environment variables.

Also fixes CI workflow Java version 8 → 21.
Move dev_init.clj and dev_tools.clj into dev/user.clj so dev tooling
stays off the production classpath (src/clj/ is AOT-compiled into the
uberjar). The user namespace now serves as both REPL helper and CLI
entrypoint via -main dispatch.

- user.clj: add :gen-class, conn helper, user CRUD, -main CLI
- project.clj: :init-db profile includes dev/ in source-paths
- scripts: rewire start.sh, create_dummy_user.sh, dev-setup.sh
- docs/migration/dev-tooling.md: rewrite for Clojure newcomers
- README.md: full overhaul for modern stack (Java 21, Datomic Pro,
  Pedestal 0.7, React 18, figwheel-main)
- Delete src/clj/orcpub/dev_init.clj and dev_tools.clj
Frontend:
- Fix message component white-on-white text (Garden CSS class)
- Fix login form input alignment (Garden CSS class)
- Fix favicon.clj 196→192 size typo
- Use :class instead of :class-name (Reagent 2.x merge behavior)

Build:
- Add externs.js for React 18 APIs (ReactDOM.Root.render, flushSync)
- Upgrade cljs-http 0.1.45→0.1.49 (fixes no.en.core shadowing warnings)
- Switch to lambdaisland/garden 1.9.606 (fixes clojure.core/abs shadowing)
- Add simplelogger.properties to suppress PDFBox font fallback WARN
- Add fonts-liberation to Dockerfile (root cause fix for font fallback)
- All builds now 0 errors, 0 warnings

Dev tooling:
- CSP nonce interceptor is no-op in dev mode (eliminates console spam)
- Figwheel uses fig:watch (headless) via start.sh; fig:dev for interactive
- menu CLI for user management (create/verify/delete with .test-users log)
- dev-setup.sh auto-creates test user (--no-test-user to skip)
- Updated migration docs (CSP behavior, core shadowing, externs, figwheel modes)
Extract pure compute helpers to compute.cljc and shared utils to
event_utils.cljc (moved from .cljs). Replace all 12 instances of
@(subscribe [...]) inside event handlers with direct db reads,
component-passed parameters, or replicated computation chains.

Adds autosave_fx.cljs with track!-based template caching for the
autosave handler that previously depended on @(subscribe [:built-character]).
Add fig:test alias and test.cljs.edn for figwheel-based CLJS testing.
New tests: compute_test.cljc (plugin vals, spell/item filtering, sort
order), event_utils_test.cljc (auth headers, mod config), events_test.cljs
(re-frame handler integration via dispatch-sync). Fix character_test.cljc
for CLJS compatibility with reader conditionals around JVM-only APIs.
Reagent 2.x changed :class-name behavior — it now OVERWRITES hiccup
tag classes instead of merging them. This caused widespread visual
regressions where base CSS classes on elements were silently dropped.

114 hiccup prop instances fixed across 6 files. Data map :class-name
keys (D&D class names in character/template/spell data) left unchanged.
22 subscribe calls in options.cljc prereq-fn closures replaced with
direct character/* pure function calls. These were never reactive —
the subscribe signal wrapped the passed entity in a plain atom. Now
prereq-fns receive built-char directly from callers (views_aux,
character_builder, entity) matching the old data flow exactly.

Also: fix meets-prereqs? when/if bug, overflow-x:hidden for
horizontal scroll, user.clj clean exit, search-input-keypress fix.
…cljc

7 subscribe calls in prereq-fn closures replaced with direct
char5e/ability-values and char5e/spells-known calls. Also removed
unused re-frame.core/subscribe import.
Phase 2: 13 subscribes across options.cljc, pdf_spec.cljc,
equipment_subs.cljs, and views.cljs replaced with pure functions,
parameter threading, plugin-data maps, and reg-sub-raw.

pdf_spec.cljc is now fully pure — no re-frame dependency.
options.cljc threads race-map from caller instead of subscribing.
equipment_subs ::mi5e/item converted to reg-sub-raw for conditional sub.

Also fixes input-field flickering in components.cljc — tracks
:prev-value to clear temp-val only when parent value catches up,
instead of clearing in setTimeout (which raced with Reagent 2.x
synchronous rendering).
Tests cover the SSOT pure functions and parameter threading changes
from the subscribe refactor:

- compute-all-weapons-map: PHB weapons, magic weapon merge, custom
  weapon expansion, nil/empty equivalence, custom doesn't clobber base
- feat-prereqs: exact label assertions for ability/spellcasting/armor
  prereqs, race-map parameter threading, mixed prereq combinations
- pdf_spec: class-string formatting (single/multi/subclass/empty),
  entity-vals key mapping and rename behavior

123 tests, 332 assertions, 0 failures.
…tion

input-field now dispatches on every keystroke. The expensive
entity/build call is debounced in the :built-character subscription
with leading+trailing edge: first change computes immediately
(dropdowns stay snappy), rapid keystrokes batch until 500ms quiet.

input-field keeps a minimal local atom to buffer typed text and
prevent controlled-input flicker while re-frame propagates.
option-language-proficiency-choice used def+partial which evaluated
@(subscribe [::langs/languages]) at namespace load time, outside any
reactive context. Converted to defn so subscribe runs during render.
Merges 24 commits from origin/develop:
- Character folders feature (13 commits)
- Equipment/weapon fixes: special, loading properties (5 commits)
- Docker verified users and setup scripts (3 commits)
- Additional test coverage

Conflict resolutions:
- views.cljs: merged folder UI with subscribe refactor + :class-name fixes
- docker-compose*.yaml: kept ${VAR:-default} pattern with datomic:dev:// URLs
- magic_items_test.clj: kept both compute-all-weapons-map and weapon property tests
- pdf_spec.cljc: threaded all-magic-items-map through plugin-data (new subscribe from develop)

Post-merge fixes:
- 4 new :class-name → :class conversions in folder/filter code (Reagent 2.x)
- Added :all-magic-items-map to plugin-data assembly in views.cljs
- Fix events/show-generic-error undefined alias in subs.cljs
- Add on-failure handlers to all folder HTTP ops (re-fetch on error)
- Client + server validation: reject blank folder names
- Wrap check-folder-owner with interceptor/interceptor, add 404
- Use named tempid in create-folder (d/resolve-tempid)
- Add default clause to folders subscription case statement
- Fix builder-dropdown CSS class (undefined → builder-option-dropdown)
- Add 3 server-side tests: blank rejection, trim, nil-name default
- Update MIGRATION-INDEX test counts, frontend-stack subscribe totals
- Add docs/STACK.md: library/dependency onboarding guide
Delete 4 static map wrapper subs (weapons-map, armor-map, equipment-map,
treasure-map) superseded by homebrew-aware versions. Reader-discard 7
unused subs with explanatory comments. All pre-existing tech debt.
Use event-utils/show-generic-error instead of the def alias that
appears later in the file. Brings lint errors from 1 to 0.
591 if-without-else converted to when/when-let/when-not across 33 files.
Resolved 11 merge conflicts (re-ran fix script on breaking's versions).
Fixed 2 pre-existing bugs: when with implicit do discarding first expr
in classes.cljc (ranger terrain) and options.cljc (spell level title).

Lint: 0 errors, 0 warnings. Tests: 174/444/0.
- Move all linter config from project.clj :lint profile to .clj-kondo/config.edn
  (single source of truth for both IDE and CLI)
- Expand lint scope: src, native, test, web (was src only)
- Bump clj-kondo 2024.05.22 → 2026.01.19
- Fix deprecated :if → :missing-else-branch linter name
- Add :clojure-lsp/unused-public-var :exclude for live false-positives
- Add :exclude-when-defined-by for re-frame registration macros
- Add shadowed-var :exclude list for intentional shadows
#_ discard 43 dead vars:
- common.cljc (4): ptime, hours-per-day, rounds-to-hours, rounds-to-minutes
- character.cljc (6): add-namespaces + 2 cascade helpers, base-climbing-speed,
  saving-throw-advantages, max-armor-class
- classes.cljc (4): blessings-of-knowledge-skill, spell-level-to-cleric-level,
  spell-in-spells-known?, pact-weapon-option
- options.cljc (15): deprecated ua/scag refs, #_ template refs, zero-caller defs
- template.cljc (14): ability roller UI (superseded by character_builder.cljs),
  amazon frames, content-list, custom-race-builder;
  #_ subscribe/dispatch refers (no live callers remain)

Redundant expression fixes:
- classes.cljc: remove nested (str (str ...))
- options.cljc: flatten (and (and ...)) in dual-wield checks,
  remove duplicate source destructuring param
#_ discard 49 dead vars:
- views.cljs (25): dead style defs, duplicate constants, superseded components
- events.cljs (22): dead defs (max-iterations, dnd-5e-characters-path,
  validate-registration, set-active-tabs, remove-subtypes, compute aliases),
  17 never-dispatched reg-event-db/fx handlers
- db.cljs (1): musical-instrument-choice-cfg (duplicate of classes.cljc)
- subs.cljs (1): ::char5e/summary reg-sub (never subscribed to)

Redundant expression fixes:
- views.cljs, character_builder.cljs, events.cljs: remove (str "literal")
Native:
- Remove unused refers (atom, subscribe, dispatch, dispatch-sync)
- Add missing font-size param to remaining-bubble
- if→when for side-effect-only branches

Web:
- if→when for protocol redirect and route matching

Tests:
- Narrow :refer lists (deftest/is only, remove unused diff/testing)
- Fix misindented (* 2 v) in entity_spec_test
- if→when in event_handlers_test

Registration:
- Remove unreachable {} before cond-> in validate-registration

Docs:
- CHANGELOG: document lint commits (if→when, forward-ref fix), update status
- MIGRATION-INDEX: update lint status to 0 errors, 0 warnings
Merge feature/error-handling-import-validation into breaking/2026-stack-modernization.
29 conflicting files resolved, 21 new files auto-merged, 206 tests passing.

New features: import/export validation pipeline, email change flow,
missing content detection, conflict resolution UI, backend error handling,
nil guards in PDF/routes, orcbrew CLI tool.

Post-merge fixes: handle-api-response moved to event_utils.cljc,
srd-link un-discarded, key-to-name → common/kw-to-name,
:class-name → :class for Reagent 2.x, hiccup2 str wrapping.
Auto-merged content_reconciliation.cljs references these functions
but they didn't survive the merge into common.cljc. Restored from
feature branch. 0 CLJS warnings, 206 JVM tests passing.
…ions

::char5e/template is used by autosave_fx.cljs and as input signal
for ::char5e/built-template. ::char5e/summary-map is used by the
combat tracker in views.cljs. Both were false-positive dead code.
header-tab: on-mouse-over/out → on-mouse-enter/leave to prevent
menu from disappearing when cursor moves from button to dropdown.
mouse-enter/leave doesn't fire between parent and child elements.

dropdown: map → map-indexed with index-prefixed keys to handle
duplicate option values from homebrew plugin data (e.g.,
:alchemical-homunculus appearing twice in the same selection).
Builder: block save when option names are empty or duplicated, with
inline red borders and error messages on offending fields. Import
pipeline: dedup identical options silently, rename different-content
same-name options with number suffixes. Dropdown: distinct-by safety
net for pre-existing data in localStorage.
set-combat-path-prop now falls back to default-combat when the path
interceptor extracts nil, preventing assoc-in from building maps with
integer keys instead of vectors. Also improves the invalid-item log
message to include the localStorage key name for easier debugging.

Adds docs/TODO.md to track the broader localStorage cleanup strategy.
4 static equipment map subscriptions (weapons-map, armor-map,
equipment-map, treasure-map) were deleted during orphan cleanup but
are still used by character_builder's inventory-selector via dynamic
sub vectors. Restores them — fixes IDeref crash on equipment page.

Template cache init (autosave_fx) now retries if the subscription
isn't registered yet. In dev mode each namespace loads as a separate
script, and setTimeout 0 can fire before equipment_subs.cljs has
loaded — preventing the IDeref crash on cold page load.
…sitives

autosave_fx: require equipment-subs directly instead of retry logic.
This guarantees ::char5e/template sub is registered before the defonce
runs, so subscribe stays inside r/track! (reactive context, no warning).

content_reconciliation: remove catch-all that labeled everything under
a class path as "Class" content. Keys like :roll (HP method), :feat
(ASI choice), :great-weapon-fighting (fighting style), :intimidation
(skill pick) were being flagged as missing homebrew. Now only flags
keys at content-relevant depths: class at [:class], subclass at
[:class :archetype-type], race, subrace, background.
- 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.
…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 (8627b59).
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.
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.

1 participant