Skip to content

Resolve semantic identifiers in PDF text macro links#23138

Draft
akabiru wants to merge 6 commits into
implementation/74315-use-formatted-id-and-displayid-for-wp-text-macrosfrom
implementation/74766-adapt-pdf-text-macro-link-handler-for-semantic-work-package-identifiers
Draft

Resolve semantic identifiers in PDF text macro links#23138
akabiru wants to merge 6 commits into
implementation/74315-use-formatted-id-and-displayid-for-wp-text-macrosfrom
implementation/74766-adapt-pdf-text-macro-link-handler-for-semantic-work-package-identifiers

Conversation

@akabiru
Copy link
Copy Markdown
Member

@akabiru akabiru commented May 8, 2026

Ticket

https://community.openproject.org/wp/74766

Note

This branch sits on top of two in-flight PRs:

What are you trying to accomplish?

Render #PROJ-1, ##PROJ-1, and ###PROJ-1 references inside markdown descriptions as proper mentions in PDF exports. In semantic mode the macro handler resolves semantic input through find_by_display_id and emits a <mention> whose data-id carries the user-facing identifier. Level 1 produces a clickable PROJ-1 link, level 2 expands inline to Task PROJ-1: <subject>, level 3 to <status> Task PROJ-1: <subject>.

Cache misses (unknown identifier, deleted WP) fall through to literal text. Historical aliases resolve to the current identifier — #OLDPROJ-1 written in pre-rename content renders as data-id="NEWPROJ-1". Classic mode rejects semantic-shape input entirely; #PROJ-1 in a classic export stays literal. Numeric input renders the mention from the matched id with no DB lookup.

The fast-path filter matches /#[A-Z\d]/ so semantic-only bodies (no numeric refs) still reach the full regex.

Screenshots

Before — semantic refs render as literal text:

wp-pdf-demo-before

After — semantic refs resolve to mentions with type/subject expansion:

wp-pdf-demo

What approach did you choose and why?

The macro layer mirrors the in-app WorkPackages link handler — same mode gate, same predicates, same end-to-end design where data-id carries display_id. PDF rendering walks Markly nodes directly rather than going through PatternMatcherFilter's preload pipeline, so each semantic reference does its own find_by_display_id round-trip. Acceptable for short bodies; preload optimisation is tracked separately if profiling shows N+1 hurts large exports.

render_link HTML-escapes the interpolated id and link text. The matcher regex constrains shape to \d+|[A-Z][A-Z0-9_]*-\d+, so the escape is defence-in-depth — a direct caller bypassing the matcher cannot regress into HTML attribute injection.

The merge commit on this branch pulls in #23093; the diff against dev collapses to just the macro link commit once both ancestors land.

Merge checklist

  • Added/updated tests
  • Added/updated documentation in Lookbook (patterns, previews, etc)
  • Tested major browsers (Chrome, Firefox, Edge, ...)

judithroth and others added 6 commits May 7, 2026 15:03
https://community.openproject.org/wp/74366

And also improve rendering of linked work packages for this.
Before, the macros were not expanded, but only the hashes and
ID output directly.
In semantic mode, the PDF text-macro handler resolves `#PROJ-1`,
`##PROJ-1`, and `###PROJ-1` through `find_by_display_id` and emits a
`<mention>` whose `data-id` carries the display id straight through to
the PDF renderer. Cache misses (unknown identifier, deleted WP) fall
through to literal text. Historical aliases resolve to the current
identifier. Classic mode rejects semantic-shape input — `#PROJ-1` in
a classic-mode export stays literal.

Numeric input keeps its current path: the matcher emits the mention
from the matched id without a DB lookup.

`render_link` HTML-escapes both interpolated values. The matcher
regex (`ID_ROUTE_CONSTRAINT`) already constrains shape to
`\d+|[A-Z][A-Z0-9_]*-\d+`, so the escape is defence-in-depth: a
future caller bypassing the matcher cannot regress into HTML
attribute injection.

The fast-path filter `Links.applicable?` matches `/#[A-Z\d]/` so
semantic-only bodies reach the full regex.

Downstream `Exports::PDF::Common::Markdown#wp_mention_macro` is
updated to `find_by_display_id` in PR 23093. This commit must land
after that PR — otherwise its existing `id[/\d+/]; find_by(id:)`
extracts the trailing digits from `data-id="PROJ-7"` and resolves
to WP id 7.
@akabiru akabiru changed the base branch from dev to implementation/74315-use-formatted-id-and-displayid-for-wp-text-macros May 8, 2026 12:47
@akabiru akabiru self-assigned this May 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants