Skip to content

[FGA-020] Image Attribution Support (LEAN-6032, LEAN-6036) - #360

Open
asouqi wants to merge 10 commits into
masterfrom
LEAN-6032
Open

asouqi wants to merge 10 commits into
masterfrom
LEAN-6032

Conversation

@asouqi

@asouqi asouqi commented Sep 10, 2026

Copy link
Copy Markdown
Contributor
  • Add Attribution to both Image_element and hero_image
  • Remove the attribution from figure_element attribute as we have that already as a node

Copilot AI lite review requested due to automatic review settings September 10, 2026 11:31

Copilot AI 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.

🟡 Changes recommended

The importer’s attribution selection and ordering logic introduces a couple of correctness/maintainability issues and the new attribution behavior for image_element/hero_image is not clearly covered by tests.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates the manuscript schema and JATS import/export pipeline to model image attributions as attribution child nodes (instead of a figure_element attribute), and expands attribution support to image_element and hero_image.

Changes:

  • Allow attribution* children on image_element and hero_image nodes.
  • Remove figure_element.attrs.attribution and switch JATS export to serialize attribution child nodes.
  • Update the JATS importer to parse <attrib> into attribution nodes for relevant image contexts and preserve correct child ordering.
File summaries
File Description
src/schema/nodes/image_element.ts Updates the node content expression to allow attribution* under image_element.
src/schema/nodes/hero_image.ts Updates the node content expression to allow attribution* under hero_image.
src/schema/nodes/figure_element.ts Removes legacy attribution attribute from figure_element in favor of attribution child nodes.
src/jats/importer/jats-dom-parser.ts Parses <attrib> for image-related nodes and ensures caption ordering relative to attributions.
src/jats/exporter/jats-exporter.ts Exports attribution child nodes as <attrib> (including for image_element graphics).
src/jats/tests/snapshots/jats-importer.test.ts.snap Updates snapshots to reflect the schema change (attribution no longer stored in figure_element attrs).
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/jats/importer/jats-dom-parser.ts Outdated
Comment thread src/jats/exporter/jats-exporter.ts
Comment thread src/jats/importer/jats-dom-parser.ts Outdated
Comment thread src/schema/nodes/hero_image.ts Outdated

export const heroImage: NodeSpec = {
content: 'figure? alt_text long_desc',
content: 'figure? attribution* alt_text long_desc',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is "*" what we want here? can't it be "?"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes we can have one, and actually the example in JATS spec shows multiple sources in the same attrib tag. updated and as the JATS Content Model allows us to have multiple attrib so in that case I will merge them if that is ok in one node

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think you meant that we also need to do that update to figure_element and image_element

Comment thread src/jats/importer/jats-dom-parser.ts Outdated
)
const fig = this.getFigContent(element).content
return Fragment.from([...fig.slice(0, 1), caption, ...fig.slice(1)])
const attributionIndex = findLastIndex(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

can you explain what this code does?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That was to place the attribution in the right place in the PM schema, I reverted it as we moved the attribution to be after the caption. It will be simple now as the placements of the content in image_element and hero_image will be the same

Comment thread src/jats/importer/jats-dom-parser.ts Outdated
node: 'attribution',
// we use closest() instead of `context` as it matches ProseMirror's parse-time node stack, not real DOM ancestry.
// Non-matching <attrib> (e.g. in <verse-group>) falls to `skip: true` below.
getAttrs: (node) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think the complexity is really needed. What would be the problem if we just parse attrib as an attribution node all the time?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated with context

Copilot AI 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.

🟡 Changes recommended

Unresolved critical issues remain in migration, DOM parsing, and JATS attribution ordering.

Get a fresh assessment by requesting another Copilot review.

Review details

Suppressed comments (2)

src/jats/importer/jats-dom-parser.ts:283

  • Please add round-trip coverage for an <attrib> inside a standalone <graphic> and the leading/hero <graphic>. The existing fixtures exercise direct <fig> attributions, but the hero fixture contains only alt-text and long-desc, so the new image/hero parsing and serialization paths can regress without a test failure.
    const attribution = element.querySelector('attrib')
    if (attribution) {
      const attributionNode = this.schema.nodes.attribution.create()
      content.push(this.parse(attribution, { topNode: attributionNode }))
    }

src/schema/migration/migration-scripts/4.5.7.ts:42

  • The reducer prepends the previously accumulated content after each current attribution, reversing multiple legacy credits. For input attributions [A, B], the migrated node contains B followed by A, so the displayed attribution order changes; accumulate the prior content before the current content.
        (n, attrib) => ({
          ...attrib,
          content: [...(attrib.content || []), ...(n.content || [])],
  • Files reviewed: 12/12 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment thread src/jats/exporter/jats-exporter.ts Outdated
Comment thread src/schema/migration/migration-scripts/4.5.7.ts
Comment thread src/schema/nodes/attribution.ts
@asouqi
asouqi requested a review from mnatsheh September 14, 2026 13:17
Comment thread src/jats/exporter/jats-exporter.ts Outdated
const $attrib = this.createElement('attrib')
$attrib.textContent = node.attrs.attribution.literal
$element.appendChild($attrib)
const appendAttribution = ($element: Element, node: ManuscriptNode) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

do we need this, or can we use appendChildNodeOfType?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated

Comment thread src/jats/importer/jats-transformations.ts
@asouqi
asouqi requested a review from mnatsheh September 15, 2026 11:09
) => {
const parents = [
...doc.querySelectorAll(
'fig:has(> attrib), graphic:not(fig graphic):has(> attrib)'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is there a reason why specify the node types? did *:has(> attrib) not work?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

we could have attribution for other node types that we still don't support yet, so I restricted that to the types we need to support. But I think that will be a general behavior to have a single attrib node, so I updated it to get all parents

@zax-assistant

zax-assistant Bot commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Published version 4.5.8-LEAN-6032.0

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