Skip to content

Bring the v13 line to parity with v14 (Quests, image upload, bug fixes) - #48

Merged
camrun91 merged 11 commits into
mainfrom
feat/v13-parity
Aug 4, 2026
Merged

Bring the v13 line to parity with v14 (Quests, image upload, bug fixes)#48
camrun91 merged 11 commits into
mainfrom
feat/v13-parity

Conversation

@gharezlak

@gharezlak gharezlak commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

Context

Companion to #47. This backports the finished Quests feature, the image-upload/link-PATCH API work, and the bug fixes from that PR onto main (the legacy v13 line), so both lines stay at feature parity instead of main falling further behind staging.

Turned out main and the v14 line already share the same ApplicationV2 sheet framework — v13 doesn't need a FormApplication rewrite here, just the same feature/fix set applied on top. Diffed main against v14's pre-Quest-work state first to scope this precisely rather than guessing what was safe to port; most of it was a clean wholesale copy of already-reviewed v14 files, verified file-by-file that nothing v13-only was getting lost in the process.

What's here

Same feature set as #47 (Quests completion, image upload, link PATCH, the #46-derived bug fixes — see that PR's description for the full list), plus a few things specific to backporting cleanly:

  • compatibility stays untouched: minimum: "13.341", verified: "13.351". This branch is v13-only, same as main is today.
  • Kept Sidebar.TABS registration (ask-chat-sidebar-tab.js + the init/setup hook wiring) — v14 had to drop this because that API doesn't support dynamic tab addition there, but it's the mechanism this line actually needs for the chat sidebar tab. Layered the rest of the v14 file's Quest/Journal registrations and hooks on top of it rather than deleting it.
  • A live bug found while syncing branches: module.json's compatibility.verified was "13.351" while minimum was "14.359" on the pre-work v14 line (verified lower than minimum, which is nonsensical) — looks like the kind of v13/v14 field cross-contamination that likely caused the original v14-breaks-v13 incident this whole effort traces back to. Not present on main itself, just flagging since it's the same failure mode as the compat-field issue called out in Finish Quests feature, add image upload + link PATCH, fold in review fixes from #46 #47.
  • A few genuine independent bug fixes found along the way: a double-escaping bug in markdownToStoredHtml's no-library fallback, a sidebar tab-ordering fix (insertBefore instead of appendChild, fixed in both places it was duplicated), session-summary now markdown-converted before sync like every other entity type.
  • Removed scripts/modules/utils.js.bak — an unreferenced stray backup file, confirmed zero references anywhere.

Testing

npm run lint: 0 errors. All touched files pass node --check. Same caveat as #47 — no live Foundry pass yet, worth testing both PRs side by side if possible since they're meant to behave identically modulo the v13/v14-specific bits called out above.

🤖 Generated with Claude Code

Greptile Summary

This PR backports the completed Quests feature, image upload/link PATCH API support, and several bug fixes from the v14 staging line onto main (v13). The majority of changed files are clean wholesale ports of already-reviewed v14 code, with v13-specific elements (notably Sidebar.TABS registration in ask-chat-tab.js) carefully preserved.

  • New quest support across the full stack: reconcile-service.js fetches and upserts quest journals with full questData flags; world-setup-dialog.js imports quests during setup; page-sheet-v2.js adds QuestPageSheetV2 with objectives, progress log, and related-entity drop targets; the RTS createJournalEntry hook now seeds questData from the API create response so newly synced quests render without waiting for reconcile.
  • Bug fixes: double-escape in markdownToStoredHtml's no-library fallback corrected (escape first, then apply inline patterns); sidebar tab injected via insertBefore instead of appendChild for correct ordering; session summaries now converted with markdownToStoredHtml before storage (format: 1) on creation; world-setup import paths all switched from toMarkdownIfHtml (no-op on markdown) to markdownToStoredHtml.
  • Image upload and link PATCH: three-step presigned-upload flow (initImageUploaduploadImageBytescompleteImageUpload) and updateLink added to archivist-api.js; updateQuest now deletes campaign_id after _normalizeQuestPayload to prevent 422 rejections from QuestUpdate's additionalProperties: false schema.

Confidence Score: 5/5

This PR is safe to merge. All previously flagged issues have been addressed and the backport is a clean, well-scoped port of already-reviewed v14 code.

All three previously raised issues (quest RTS questData seeding, updateQuest leaking campaign_id, world-setup using the wrong markdown converter) are confirmed fixed in the diff. The new quest reconcile path, image upload flow, and link PATCH are all logically sound.

Files Needing Attention: No files require special attention. The most behaviorally complex additions are the QuestPageSheetV2 save/commit path in page-sheet-v2.js and the three-step image upload in archivist-api.js — both follow established patterns cleanly.

Important Files Changed

Filename Overview
scripts/services/archivist-api.js Adds full quest CRUD, image upload (3-step presigned flow), and link PATCH methods; updateQuest correctly strips campaign_id after normalization; payload whitelist in _normalizeQuestPayload is tight and correct.
scripts/modules/reconcile/reconcile-service.js Adds quest upsert path with getQuest full-fetch fallback; session recap creation now uses markdownToStoredHtml + format:1 (correct); _organizeFolders skips quest/journal types but those are already placed in correct folders at creation time.
scripts/modules/utils.js Adds buildQuestDataFromApi and _normalizeQuestProgressLog helpers; fixes double-escape in markdownToStoredHtml no-library fallback (escapeHTML applied before inline markdown patterns); ensureArchivistFolders/getArchivistFolder now cover quest type.
scripts/archivist-sync.js RTS createJournalEntry hook now seeds questData flags from createQuest response via buildQuestDataFromApi; quest creation correctly passes worldId as campaign_id through _normalizeQuestPayload.
scripts/dialogs/world-setup-dialog.js Quest import path added; all entity import paths now use markdownToStoredHtml (previous toMarkdownIfHtml no-op bug fixed); quest folders ensured during step 4.
scripts/modules/sheets/page-sheet-v2.js QuestPageSheetV2 added with objectives/progress/links tabs; quest save path builds correct snake_case payload for updateQuest; _renderRelatedQuests fetches and filters client-side with short-lived cache.
scripts/sidebar/ask-chat-tab.js Tab button now injected via insertBefore(li, lastElementChild) instead of appendChild, fixing sidebar ordering; v13 Sidebar.TABS registration preserved.
.cursor/rules/40-archivist-api.mdc Updated to reflect Beats PATCH uses plain application/json; clarifies non-uniform verbs and field casing; adds live OpenAPI schema as source of truth.
scripts/modules/utils.js.bak Stale backup file deleted; confirmed zero references in the codebase.

Reviews (4): Last reviewed commit: "fix: convert reconcile session recap sum..." | Re-trigger Greptile

gharezlak and others added 6 commits July 12, 2026 18:02
Investigation found v13 and v14 already share the same ApplicationV2
sheet framework (contrary to the assumption that v13 used a legacy
FormApplication) — the two lines diverged in features/fixes, not
platform. Diffed main against the v14 line's pre-Quest-work state to
scope this precisely rather than guessing.

Ported wholesale (confirmed no v13-only content lost in each case):
- Full Quest feature (this session's finished version, not the
  half-built one) and Journal support: new quest.hbs/journal.hbs
  sheets, archivist-api.js CRUD + normalization, config/utils map
  entries, reconcile-service/journal-manager quest+journal handling.
- Both API augments from the v14 branch: local image upload
  (Character/Item/Faction/Location) and link PATCH.
- Sync dialog UX fixes: shift-click multi-select, live progress
  indicator during sync (ported templates/sync-dialog.hbs +
  styles/sync-dialog.css to match), per-row error handling with a
  fail/success count instead of a blanket message, confirm-before-
  delete.
- Bug fixes: markdownToStoredHtml's double-escaping/library-lookup
  fix, a sidebar tab-ordering fix (insertBefore instead of append,
  applied to both places it was duplicated), session-summary now
  markdown-converted before sync like every other entity type.
- Dead code: removed the unreferenced scripts/modules/utils.js.bak
  (confirmed zero references on either line).
- New "Quests" tab on Character/Item/Location/Faction sheets, and the
  Cursor API docs' Quest/Journal/Links section.

Deliberately NOT ported:
- The Sidebar.TABS removal — v14 dropped it because that dynamic-tab
  API doesn't work there, but it's the mechanism v13 actually uses for
  the chat sidebar tab today. Kept v13's registration in both the
  `init` and `setup` hooks, layered the rest of the v14 file's Quest/
  Journal registrations and hooks on top.
- module.json stays untouched (1.x / compat 13.x).

Included as a side effect of a wholesale file copy (flagged, not hand-
picked): world-setup-dialog.js's large diff is mostly an unrelated
dead-code removal (a legacy "Step 4 mapping" wizard step already
guarded by `if (false && ...)`) that happened on the v14 line before
the Quest work — same dead code existed on main, so removing it here
is inert cleanup, not a functional change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same fix as the v14 line (projection/*.js is shared, unversioned
logic — confirmed byte-identical pre-fix). Project Descriptions is
documented as "Append an Archivist section to the most likely
description field," but projectDescription() only appended for
HTML-flagged slots. For the one plain-text slot in the registry
(dnd5e/pf2e Actor system.details.notes, adapter-registry.js
html:false) — which becomes the top candidate for actor subtypes
lacking a biography field, e.g. dnd5e's vehicle/group types — the code
did stripHtml(archivistHtml) with no reference to the field's existing
content, silently replacing whatever was there. Matches user reports
of losing existing descriptions.

Added mergeArchivistPlainSection (merge.js): wraps Archivist content
in a recognizable marker, appends it if none exists yet, and replaces
only that marked block on future syncs — never touching the rest of
the field's content, mirroring what mergeArchivistSection already did
for HTML fields.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same fix as the v14 line. Quests were already being auto-imported
exactly like Factions/Recaps (_importArchivistMissing() pulls them in
unconditionally, no reconcile/selection step), and the count was
already computed (count.imp.quests / plan.importFromArchivist.quests)
— it just never made it into the summaryRows array the Step 6 table
renders from. Added a Quests row identical in shape to the Recaps row.

Not porting the matching Step 4 helper-note text update from v14: that
note (and the wizard-cleanup rewrite it lives in) doesn't exist in
this branch's older world-setup-dialog.hbs, and this branch's Step 6
table is otherwise the same generic summaryRows-driven markup, so the
new row renders correctly without it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same fix as the v14 line (these files were wholesale-copied from there
during the parity backport, confirmed identical pre-fix). Two real
bugs, both regressions from the Quests/image-upload work:

1. The upload-local-image button reused .archivist-edit-toggle's exact
   absolute-position slot with no offset, rendering directly on top of
   the edit-mode toggle and making it unclickable. Added
   .archivist-upload-toggle (right: 48px) so they sit side by side.

2. _onRender fired the grid/quest/actor-item population functions
   without awaiting them. Verified against Foundry's own core source
   that _render() awaits _onRender via _doEvent — but since ours
   wasn't async, that await resolved before population finished.
   Opening a sheet via a card click in another custom sheet could
   leave cards populated but tab counts never applied. Made _onRender
   (base class, plus the Location and Quest overrides) async and
   properly awaited.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…l work

Same fixes as the v14 line (feat/api-augments-v14) — these files were
already wholesale-copied from there during the parity backport,
confirmed identical pre-fix. Not porting his module.json/compatibility
changes or the ask-chat-sidebar-tab.js deletion from the same source
branch — that reintroduces the exact incident this project started
from (v14-only code with a compat.minimum permissive enough for v13
clients, missing the Sidebar.TABS registration this branch still needs).

- sync-dialog.js: journal content was being dropped on import/sync
  (never checked row.content, where Journal's actual content lives);
  shift+click range-select got flipped back by the row click handler;
  unescaped journal names in the delete-confirmation dialog; no folder
  fallback for journals/quests on pre-migration worlds. Wholesale-
  replaced with the fixed version, re-added this branch's own
  relatedEntityRefs hydration needed by the richer quest-linking UI.
- world-setup-dialog.js (+ its template): added _updateSyncStatusUI()
  direct-DOM progress updates to the six always-imported-in-full loops,
  replacing a full render() per entity.
- archivist-sync.js: the quest content-edit realtime hook no longer
  PATCHes the unchanged quest name to the API on every keystroke commit
  (renames are already handled by a separate title-sync hook).
- archivist-api.js: _normalizeQuestPayload rewritten to a strict
  whitelist matching QuestCreate/QuestUpdate's extra="forbid" schema,
  and the keepalive guard is now conditional on body size (Chromium
  rejects keepalive fetches over ~64KB).
- merge.js: stripHtml no longer calls the nonexistent
  foundry.utils.TextEditor.stripHTML.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: camrun01 <cameron.b.llewellyn@gmail.com>
Same as the v14 line — wholesale-copied (confirmed identical pre-change).
Adopts camrun91's _localize(key) helper: tries game.i18n.localize()
and returns null (not the raw key) on a miss, so statusLabel/categoryLabel
can chain `|| fallback` to the existing hardcoded English strings.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: camrun01 <cameron.b.llewellyn@gmail.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 17779f13e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/services/archivist-api.js
Comment thread scripts/services/archivist-api.js
Comment thread scripts/archivist-sync.js
Comment thread scripts/modules/reconcile/reconcile-service.js Outdated
Comment thread scripts/modules/sheets/page-sheet-v2.js
Comment thread scripts/modules/sheets/page-sheet-v2.js
Comment thread scripts/modules/reconcile/reconcile-service.js Outdated
Comment thread scripts/dialogs/sync-dialog.js
Comment thread scripts/dialogs/world-setup-dialog.js
Comment thread scripts/archivist-sync.js
Comment thread scripts/services/archivist-api.js
cursoragent and others added 2 commits August 3, 2026 19:38
Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>
Populate flags.archivist.questData from the createQuest response so newly
created quest sheets show API defaults without waiting for reconcile.
Delete campaign_id after shared quest payload normalization in updateQuest
so incidental worldId/campaignId inputs cannot 422 QuestUpdate.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>
Comment thread scripts/dialogs/world-setup-dialog.js Outdated
cursoragent and others added 2 commits August 3, 2026 19:47
World-setup journal/character/item/location/faction imports were passing
API markdown through toMarkdownIfHtml (HTML→text) before storing in
Foundry journal pages. Use markdownToStoredHtml to match reconcile and
sync-dialog so markdown renders instead of showing literal **bold** etc.

Co-authored-by: Cameron Llewellyn <cameron.b.llewellyn@gmail.com>
Reconcile was writing raw markdown with format:2; recap pages need HTML at format:1 so Foundry renders them correctly.

Co-authored-by: Cursor <cursoragent@cursor.com>
@camrun91
camrun91 merged commit 4230c18 into main Aug 4, 2026
1 check passed
@camrun91
camrun91 deleted the feat/v13-parity branch August 5, 2026 15:30
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.

3 participants