Skip to content

v2.0.0: rewritten data pipeline, finished UI, monthly auto-refresh#72

Open
maxiride wants to merge 47 commits into
mainfrom
v2
Open

v2.0.0: rewritten data pipeline, finished UI, monthly auto-refresh#72
maxiride wants to merge 47 commits into
mainfrom
v2

Conversation

@maxiride

@maxiride maxiride commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • New AoN data pipeline: single Elasticsearch request instead of Playwright browser automation. Captures monsters + NPCs together (4,665 total, up from 3,365; 917 NPCs, up from 749 previously missing).
  • Finished the v2 frontend: EncounterList (was ~80% placeholder), restored alignment/family filters, searchable multi-selects, rebuilt ThreatBar.
  • Fixed a Pinia reactivity bug where XP costs did not update on party-level change (closes Creatures is XP isn't updated reactively upon party level change #17).
  • Monthly scheduled GitHub Action refreshes creature data (1st of month) and redeploys to gh-pages.
  • Full technical changelog: CHANGELOG.md (Unreleased section). User-facing notes: RELEASE_NOTES.md.

Issues closed by this merge

#46, #17, #1, #55, #53, #57, #68, #61, #70

Known gaps (not in this release)

#62 (delta-level cost clamping beyond ±4), #43 (separate player-XP-award figure), #25 (dark mode), #52/#51 (save/share encounters), #18 (random creature filter).

Before merging

  • main still carries the v1 code (GPLv3 LICENSE.md, semantic-release, umami analytics) that this branch does not have — needs reconciling (port LICENSE.md, decide on semantic-release/analytics) before or as part of this merge.
  • Review GitHub's Dependabot alerts on the default branch before/after merge.

Test plan

  • pnpm lint clean
  • pnpm build (vue-tsc strict) clean
  • Manual browser pass: creature load, search/filters (name, type, level range, rarity, size, alignment, family, traits, source), add-to-encounter, weak/base/elite XP math, threat bar thresholds, license dialog
  • Reviewer smoke-test on a fresh clone

maxiride and others added 30 commits February 18, 2025 08:29
Closes: #53, #48, #56

Upgraded Quasar and Vue, added Pinia integration.
Moved all encounter logic to dedicated Pinia store and simplified some business logic.

Adapted tables and objects to new AoN JSON dump obtainable directly from their website.

Added new menu buttons for support, donation and release info (to be reviewed further).
Allows developing and deploying the app under a subfolder by reading the base path from an environment variable. This enables hosting on GitHub Pages and having two versions coexist simultaneously before promoting to the root.
Adjusted `$primary` and `$secondary` color values in Quasar variables to align with project colors.
…eatures

Adjusted logic for level modifications, fixes #57
…rated to dedicated repository

Deleted unused scraper module, including associated configuration, dependencies, and source files, to clean up the codebase.
…ated to pnpm

Since the deletion of the webscraper (moved to separate repo), moved the quasar spa code to the root level.
…ed caching

Replaced `/table-data_stripped.json` with `/creatures.json` to load the correct data source.
…ogic

We no longer compute metadata values on the client but the app is provided with a metadata.json containing all of them.

Removed `indexedProperties` and related compute methods. Added metadata fetching to streamline handling of creature metadata like sizes and traits. Enhanced getters for improved state access.
Deleted `example-store.js` as it was unused.
… improving utility functions

Added meaningful comments to document getters and actions. Refactored `computeDeltaCost` to use a cleaner mapping approach for predefined deltas.
Introduced `$reset` method to reset the encounter state, clearing all creatures. Streamlines resetting functionality for encounters.
Deleted outdated `table-data_all.json` data source to clean up unnecessary artifacts and reduce redundancy.
… files

Added `quasar.config.ts`, `tsconfig.json`, and updated project files for TypeScript support. Configured ESLint and Prettier to align with project standards.
node_modules is centrally handled by pnpm through workspaces
…fetch

The old fetcher drove a headless Chromium browser through the AoN
Creatures.aspx UI (click "Load remaining", click "Export as JSON") to
scrape data. It broke when AoN added a cookie-consent overlay and
never captured NPCs, since the UI export only covers monsters.

The new tools/aon-downloader/fetch-creatures.js queries AoN's public
Elasticsearch endpoint directly: one HTTP request returns every
monster and NPC (4665 total, 917 of them NPCs) with a richer schema
(alignment, family, numeric level/hp/ac, array traits/size/sources).
Zero dependencies, no browser binary. A 7-day freshness guard means
routine dev/install runs never hit AoN's servers; refresh is explicit
(--force) or via the new monthly scheduled workflow.

Removes playwright, pino and the build-report.js companion script
(metadata generation is now inline in the fetch script).
CreaturesTable: name search, monster/NPC toggle, level range slider,
and a collapsible advanced filter panel (rarity, size, alignment,
family, traits, source). Restores filters lost in the v2 rewrite
(alignment, family) and adds searchable multi-select (FilterSelect.vue)
for the long trait/source/family lists.

EncounterList: was ~80% placeholder, now fully functional — weak/base
/elite toggle per creature, count stepper, per-creature and total XP,
clear-encounter action, AoN link per creature.

ThreatBar: rebuilt as a CSS track/fill instead of manually positioned
absolute badges; scale compresses instead of overflowing past the
extreme budget.

Fixes a latent Pinia reactivity bug (closes #17, milestone v2):
IndexPage destructured store computeds directly off the setup-store
(`const xpCost = encounterStore.xpCost`), freezing their value at
mount instead of tracking party level/size changes. Everything now
goes through storeToRefs.

Also: license dialog renders real Paizo Community Use Policy text
instead of a commented-out block (closes #68); header/menu links
point at real destinations; removed dead code (unused axios boot,
example-store, stub user-store, duplicate stores/index.js, debug
console.log/CSS).
update-creatures.yml: scheduled job (1st of every month) runs the new
fetch-creatures.js --force, commits refreshed public/creatures.json +
metadata.json to both development and main, then triggers deploy.

deploy.yml: builds the Quasar SPA and publishes dist/spa (data
included) to the gh-pages branch on push to main.

Removes GitHubPagesDeploymentWorkflow.yml and build-deploy.yaml — both
yarn-based and targeting the old src/webui v1 layout, non-functional
against this codebase.
Technical changelog entry covering the data pipeline rewrite, frontend
completion, and CI changes, plus a list of known gaps not addressed in
this release (issues #62, #43, #25, #52, #51, #18).

RELEASE_NOTES.md: user-facing summary — creature/NPC counts before and
after, the move to a single polite Elasticsearch request instead of
browser automation, and the encounter-builder improvements.
Reintroduces the Umami integration main had (same website ID, same
`encounter-add-creature` bounce-workaround event from #66), gated to
production builds only via the existing ctx.prod pattern so local dev
never sends real traffic.

Since users continuously add/remove creatures with no "encounter
finished" signal to hook, tracking is built around discrete mutation
events (creature added, weak/base/elite variant changed) plus a
debounced "encounter-snapshot" (party level/size, XP cost, threat)
fired 8s after the last edit, with an immediate flush on tab-hide/
pagehide so closing right after a change isn't lost. All three call
sites are centralized in encounter-store.ts, the single place that
already mutates this state.
package.json productName/description were still the Quasar CLI
scaffold defaults ("Quasar App" / "A Quasar Project"), which is what
showed up as the browser tab title.

Also adds a Buy Me a Coffee button to the header.
GM Core's Creature XP table only defines costs from party level -4 to
+4. computeDeltaCost silently clamped anything past that to the same
edge value, so a level 1 party vs. a level 25 solo still read as
merely "Extreme" instead of the effectively unwinnable fight it is.

Per the issue owner's own resolution direction (no unofficial
extrapolated formula, just make the cap visible): encounter-store now
exports isDeltaOutOfBounds(), and EncounterList shows a warning icon +
tooltip on any creature whose delta exceeds the table's range. The
per-creature cost itself is unchanged — still RAW as written.

Party-size XP budget scaling (GM Core "Different Party Sizes", pg. 76)
was already correct — xpBudget already scales with partySize and the
per-creature cost already factors in partyLevel via the delta.

Fixes #62
maxiride added 8 commits July 12, 2026 17:13
Vue's whitespace-condense compiler strips a leading literal space at
the start of an element's text content, so "Trivial" and "40" rendered
back-to-back as "Trivial40". Using   instead of a plain space
survives the trim.
First automated test suite for the project. Two thin layers:

- Vitest (src/stores/encounter-store.spec.ts): encodes GM Core's rules
  tables row by row — XP budget per party size, creature XP per level
  delta, weak/elite exceptions — plus store actions and the analytics
  event contract (debounced snapshot via fake timers). happy-dom
  environment since the store registers window/document listeners.
- Playwright, chromium-only (e2e/encounter-builder.spec.ts): smoke
  coverage of browse -> filter -> add -> balance against the real dev
  server and real AoN data, with regression tests for #17 (XP
  reactivity) and #62 (out-of-bounds delta warning). Config pins
  channel:'chromium' (full browser, not chromium_headless_shell) to
  dodge a known Windows worker-close hang (microsoft/playwright#39753).

ci.yml runs lint + both suites on pushes to development/v2 and PRs to
main, uploading the Playwright report on failure.

package.json: pnpm test now runs both suites; test:unit and test:e2e
run them individually.
Four ADRs document the non-obvious decisions made this cycle so future
contributors (this repo is public) don't have to reverse-engineer the
why from commit history:

- 0001: creature data via AoN's public Elasticsearch endpoint
- 0002: monthly data refresh + GitHub Pages deploy
- 0003: privacy-first analytics with self-hosted Umami
- 0004: testing strategy — Vitest for rules logic, Playwright for E2E

README rewritten for contributors: real tech stack table, setup
instructions, script reference, a repository tour, and a "how it
works"/contributing section pointing at the ADRs. Removed the stale
README_old.md (pre-rewrite v1 instructions, Vue CLI commands that no
longer apply).

CHANGELOG.md Unreleased section updated to reflect the testing and
docs additions.
Resolves the conflicts blocking PR #72 (v2 -> main). All five direct
conflicts were main-only bugfixes/tooling changes to src/webui/ (the
old Quasar 1 / Vue 2 app: NPC redirect fix, Umami analytics tweaks,
semantic-release setup) — code with no v2 equivalent because v2 is a
full rewrite that never carried src/webui/ forward. Resolution keeps
v2's deletion; none of those fixes apply to an app that no longer
exists.

Two more main-only additions surfaced once src/webui/ conflicts were
resolved (not flagged as direct conflicts, but git staged them from
main automatically since v2 has nothing at those paths):

- .releaserc.json (root): same semantic-release config as the
  src/webui one, just relocated there by git's rename heuristic.
  Versions off src/webui/package.json, which no longer exists. Removed
  for the same reason as the src/webui copy.
- .github/workflows/build-deploy.yaml: the same stale yarn-based
  deploy workflow already removed from development's own history
  (targeting the now-nonexistent src/webui layout) — main kept an
  independent copy. Removed; superseded by deploy.yml/ci.yml/
  update-creatures.yml.

One main-only change is kept: LICENSE_AON.md carries a substantially
more complete set of Paizo/OGL book notices on main (674 vs 283
lines) — a genuine content improvement, auto-merged cleanly with no
conflict.

Root LICENSE.md (GPLv3, main-only) merges in unchanged — no conflict,
resolves the "port LICENSE.md" item from the original PR body.
Non-technical recap for the actual audience (DMs and players, not
developers): bigger creature database, automatic monthly updates,
better filters, the new out-of-bounds difficulty warning, and a
cleaner UI. No jargon — no mention of Elasticsearch, CI, or anything
implementation-specific.

Shown once automatically (localStorage flag, versioned so the next
major release can announce itself the same way) and always reachable
again from the v2 menu ("What's new"). Also repoints the menu's
"Release notes" link from the technical CHANGELOG.md to the
user-facing RELEASE_NOTES.md — a better fit for the same audience.
CreaturesTable switches to q-table's native virtual-scroll — same
footprint (fixed height, no layout shift), scrolls to load more
instead of paging, still shows the total filtered count.

Found and fixed a real bug along the way: Vue 3.5.x silently breaks
Quasar 2.18.2's QTable virtual-scroll (renders zero rows, no error —
confirmed with an isolated repro outside this app's code). Pins vue
to an exact 3.4.38, documented in ADR 0005 so a routine dependency
bump doesn't reintroduce it invisibly.
Root cause of the reported multi-second scroll freeze: each row's
QBadge x2 + QBtn + QTooltip were mounting/unmounting on every scroll
tick. Replaced with plain HTML/CSS (.badge-outline spans, a native
<button> with a title attribute) — same look, zero component
overhead. Measured: worst single blocking task 256ms -> 88ms, total
wall time for a rapid-scroll burst 19.8s -> 3.6s.

Also dropped virtual-scroll entirely. It stopped being reliable across
a long debugging session (worked, then silently rendered zero rows
after restarts/HMR churn, reproduced even from a clean production
build) — not something to ship on. All ~4,665 filtered rows now render
at once inside a fixed-height, natively-scrollable table; no DOM
virtualization, but the lightweight per-row markup keeps that cheap.
Vue stays pinned to 3.4.38 regardless (still the known-good version).
Details in ADR 0005.
Reverts the previous commit's mistake. Rendering all ~4665 rows at once
(60k+ DOM nodes) was the actual cause of the reported Firefox freeze —
not virtual-scroll.

The earlier "virtual-scroll renders zero rows" diagnosis was a false
signal from the headless automation browser used for verification: it
measures the scroll viewport a frame too early, computes an empty
visible slice, and never recomputes without a resize/scroll event. Real
browsers settle layout on first paint and render correctly. Confirmed by
a user observing rows fine in Firefox while the automation browser showed
none until a forced resize.

Restores `virtual-scroll` (~60 rows in the DOM at a time). Drops the
markRaw() on the rows array — tested, it was never the cause. Keeps the
plain-HTML badges/button (a real per-row weight reduction).

Deletes ADR 0005: its whole premise (Vue 3.5 / markRaw break
virtual-scroll) rested on the same false automation signal and would
mislead future decisions. The Vue pin it justified is addressed
separately.
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.

Creatures is XP isn't updated reactively upon party level change

1 participant