Skip to content

ADAPT-3785: Fix storyboard Word/PDF export formatting, MCQ title, and… - #311

Open
lokeshece1 wants to merge 4 commits into
developfrom
bugfix/ADAPT-3785-Storyboard-export-and-UI-issues-fixed
Open

ADAPT-3785: Fix storyboard Word/PDF export formatting, MCQ title, and…#311
lokeshece1 wants to merge 4 commits into
developfrom
bugfix/ADAPT-3785-Storyboard-export-and-UI-issues-fixed

Conversation

@lokeshece1

@lokeshece1 lokeshece1 commented Aug 30, 2026

Copy link
Copy Markdown

✅ PR Completion Checklist

  • PR title follows format: Prefix: ADAPT-XXXX Brief description
  • JIRA ID linked in the Context section below
  • Plugin version updated in bower.json (if applicable)
  • npm run test-e2e-dev-pipeline executed and passing
  • No new issues reported by SonarLint (attach screenshot if applicable)
  • Own diff reviewed before requesting review
  • At least two reviewers added (Copilot set as default)

Context

Resolves / Addresses ADAPT-3785

Fixed the UI storyboard

… PDF overlap

Word export: embed image bytes via new assetResolver; strip HTML from MCQ option text and feedback; match Storyboard Preview layout (type badge, bulleted options with correctness glyphs, indented feedback, italic submit hint).

PDF export: pdfDrawImage helper caps width to content area, preserves aspect, page-break aware, advances doc.y after draw (pdfkit doc.image does NOT move the cursor - root cause of text-over-image overlap). pdfResetText between blocks; pdfEnsureRoom before component/assessment headers; removed all continued:true chains; bufferPages + lineGap(2) + moveDown(0.8). Verified: 56 pages / 40 images / 1010 text runs / 0 overlaps via pdfjs bbox analysis.

MCQ title mapping: question body IS the MCQ title (stored as displayTitle); block-title is fallback header; body paragraph only shown when it differs from header. validateAssessment accepts blockTitle. Save writes both title/displayTitle and dedupes body. Load reads displayTitle first, keeps props.title only when distinct. DEFAULT_SCHEMA_TITLES filter blocks 'New Component Title'/'Article title' from UI and export.
@lokeshece1
lokeshece1 requested review from Vaahwan, VadirajJoshi-Laerdal and kaveritekawade and a lite review from Copilot August 30, 2026 15:16
@lokeshece1 lokeshece1 self-assigned this Aug 30, 2026
@lokeshece1 lokeshece1 added the bug Something isn't working label Aug 30, 2026

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.

Pull request overview

This PR targets ADAPT-3785 by aligning the Storyboard’s on-screen Preview, course projection, and Word/PDF exports—especially around spacing, placeholder-title suppression, assessment rendering, and embedding real asset bytes in exports.

Changes:

  • Reworked server-side Word/PDF export to embed images/posters, improve spacing, and render assessments/options/feedback more faithfully.
  • Updated Storyboard UI flows to avoid leaking schema-default placeholder titles, improve Preview defaults, and ensure Export persists the live editor document.
  • Adjusted course/storyboard generation and bootstrap/title plumbing to reduce duplication and better match learner-facing output.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
plugins/content/storyboard/utils/documentConvert.js Major refactor of docx/pdf export rendering (headings, components, assessments, images, spacing).
plugins/content/storyboard/utils/assetResolver.js New server-side resolver to load asset bytes/dimensions for export embedding.
plugins/content/storyboard/routes/requestHandlers.js Pass explicit user/tenant context into export conversion for asset resolution.
new-ui-source/src/types/storyboard.ts Adjust assessment validation signature/behavior and clarify assessment title/body expectations.
new-ui-source/src/pages/StoryboardPage.tsx Fetch course title when navigation state is missing to avoid stale/placeholder titles.
new-ui-source/src/pages/SetupPage.tsx Hide duplicated Setup chrome when in Storyboard mode.
new-ui-source/src/index.css Minor styling updates for the Storyboard toolbar button variant.
new-ui-source/src/hooks/useStoryboard.ts Allow saving an explicit document snapshot (used by export flow).
new-ui-source/src/components/storyboard/StoryboardWorkspace.tsx Strip placeholder headings, persist live editor content before export, and suppress placeholder course titles.
new-ui-source/src/components/storyboard/DocumentToolbar.tsx Toolbar layout change (single-row with right-aligned refresh/AI actions).
new-ui-source/src/components/storyboard/blocks/componentBlock.tsx Default to collapsed Preview for non-empty components (expand only for new/blank).
new-ui-source/src/components/storyboard/blocks/assessmentBlock.tsx Expanded collapsed Preview rendering (options/feedback/footer) and updated validation call.
new-ui-source/src/api/storyboardGeneration.ts Adjust assessment title/body mapping and block layout selection (full vs left/right).
new-ui-source/src/api/adaptAuthoring.ts Centralize placeholder-title handling and improve storyboard block projection rules.
Suppressed comments (2)

plugins/content/storyboard/utils/documentConvert.js:924

  • Same duplication issue as the docx exporter: headerText prefers question, but showQuestionParagraph still prints question again when a distinct title exists, resulting in repeated question text in the PDF output.
  // See docx-side comment: question body is the MCQ Title when present.
  const headerText = question || title;
  const showQuestionParagraph = !!question && !!title && question !== title;
  doc.font('Helvetica-Bold').fontSize(11).text(`${kindLabel}${headerText ? ' — ' : ''}${headerText}`);

plugins/content/storyboard/utils/documentConvert.js:1049

  • PDF export has the same issue as docx: numberedListItem blocks are rendered with a bullet () prefix, so ordered lists lose numbering in the PDF output.
    } else if (b.type === 'bulletListItem' || b.type === 'numberedListItem') {
      const text = inlineToText(b.content);
      if (text) doc.font('Helvetica').fontSize(11).text(`• ${text}`, { indent: 12 });
    } else {

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

Comment thread plugins/content/storyboard/utils/documentConvert.js Outdated
Comment thread new-ui-source/src/api/storyboardGeneration.ts Outdated
Comment thread new-ui-source/src/types/storyboard.ts Outdated
Comment thread plugins/content/storyboard/utils/documentConvert.js
Comment thread new-ui-source/src/components/storyboard/blocks/assessmentBlock.tsx Outdated
@kaveritekawade

Copy link
Copy Markdown
Collaborator

@lokeshece1 Update title and address co-pilot comments.

Comment thread new-ui-source/src/api/adaptAuthoring.ts Outdated
…ered lists, storyboard helpers moved out of common files

- Assessment Title/Body precedence unified to blockTitle || question across
  docx/pdf export, collapsed preview, and generation (body only rendered when
  distinct from the header, so text never duplicates and a distinct Body is
  no longer unreachable)
- numberedListItem exports with sequential 1. 2. 3. numbering (docx + pdf),
  counter resets between list runs
- Placeholder-title helpers relocated from api/adaptAuthoring.ts to
  components/storyboard/placeholderTitles.ts; bootstrap title change reverted;
  Topic/Section/Content Group terminology added client- and server-side
- validateAssessment docstring updated to the Title-primary hydration

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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 4 comments.

Suppressed comments (1)

Previously missed (1) — in code that hasn't changed since the last review.

new-ui-source/src/components/storyboard/blocks/componentBlock.tsx:218

  • hasComponentContent() assumes groupedContent item fields are always strings (calling .trim()), and it ignores assetId-only selections for image/media refs. If a persisted document contains partial item objects (or an asset picker stores only assetId/link later), this can throw or misclassify a non-empty component as blank, changing the initial collapsed state incorrectly.
function hasComponentContent(kind: ComponentKind, data: ComponentData, title: string): boolean {
  if (title.trim() || data.description.trim() || data.instruction.trim()) return true;
  switch (kind) {
    case 'groupedContent':
      return (data.items || []).some((it) => it.title.trim() || it.body.trim() || it.image.trim());
    case 'image':
      return !!(data.image?.link || data.image?.url);
    case 'video':
    case 'audio':
      return !!(data.media?.asset?.link || data.media?.asset?.url || data.media?.poster?.link || data.media?.poster?.url);
    case 'h5p':
      return !!(data.media?.asset?.link || data.media?.asset?.url);
    case 'laerdalForm':
      return (data.fields || []).some((f) => f.label.trim());
    case 'assessmentResult':
      return !!(data.result?.assessmentId.trim() || (data.result?.bands || []).some((b) => b.feedback.trim()));
    default:

Comment thread new-ui-source/src/components/storyboard/blocks/assessmentBlock.tsx
Comment thread plugins/content/storyboard/utils/assetResolver.js Outdated
Comment thread plugins/content/storyboard/utils/assetResolver.js
Comment thread new-ui-source/src/components/storyboard/placeholderTitles.ts

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.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated 2 comments.

Suppressed comments (7)

new-ui-source/src/components/storyboard/blocks/componentBlock.tsx:205

  • For image components, hasComponentContent() ignores image.assetId. If the selected asset only populates assetId (common for DAM-picked assets), the block is misclassified as blank and opens in edit mode on reload.
    case 'image':
      return !!(data.image?.link || data.image?.url);

new-ui-source/src/components/storyboard/blocks/componentBlock.tsx:208

  • For video/audio components, hasComponentContent() ignores DAM asset ids (asset.assetId / poster.assetId). This can misclassify existing media content as blank, opening the card expanded unexpectedly.
    case 'video':
    case 'audio':
      return !!(data.media?.asset?.link || data.media?.asset?.url || data.media?.poster?.link || data.media?.poster?.url);

new-ui-source/src/components/storyboard/blocks/componentBlock.tsx:210

  • For H5P components, hasComponentContent() ignores media.asset.assetId, so DAM-picked H5P sources can be treated as blank.
    case 'h5p':
      return !!(data.media?.asset?.link || data.media?.asset?.url);

new-ui-source/src/components/storyboard/blocks/assessmentBlock.tsx:405

  • The Preview-mode filters treat any non-empty string as authored, so legacy HTML like "

    " passes .trim() and produces empty-looking rows/options in the preview. Consider stripping tags for the preview-only “displayed*” filters.
      const displayedFeedback = feedbackRows.filter(([k]) => fb[k] && fb[k].trim());
      const displayedOptions = (model.options ?? []).filter((o) => o.text.trim());
      const displayedItems = (model.items ?? []).filter((i) => i && i.trim());
      const displayedPairs = (model.pairs ?? []).filter((p) => p && (p.prompt || p.answer));
      const displayedAnswers = (model.answers ?? []).filter((a) => a && a.trim());

new-ui-source/src/components/storyboard/blocks/assessmentBlock.tsx:463

  • In Preview mode, per-option feedback is rendered as raw text. If legacy records contain HTML (e.g. "

    Correct

    "), the tags will display literally and won’t match the Word/PDF export (which strips HTML).
                    {o.feedback && o.feedback.trim() && (
                      <div className="ml-5 mt-0.5 text-[13px] italic text-muted-foreground">
                        {o.feedback}
                      </div>
                    )}

new-ui-source/src/components/storyboard/blocks/assessmentBlock.tsx:518

  • In Preview mode, whole-question feedback is rendered without stripping HTML, so legacy values like "

    Incorrect

    " will show tags and can create blank-looking rows.
                  <div key={k}>
                    <span className="font-semibold text-foreground">{lbl}:</span>{' '}
                    <span className="text-foreground">{fb[k]}</span>
                  </div>

new-ui-source/src/components/storyboard/placeholderTitles.ts:50

  • placeholder-title detection is case-sensitive here, but the server-side export filter is case-insensitive (lowercases before matching). This mismatch can let placeholder titles with different casing leak into the storyboard header/export on the client side.
export function isDefaultSchemaTitle(text: string | undefined | null): boolean {
  const t = (text || '').trim();
  return !t || DEFAULT_SCHEMA_TITLES.has(t);
}

Comment thread new-ui-source/src/components/storyboard/blocks/componentBlock.tsx Outdated
Comment thread plugins/content/storyboard/utils/assetResolver.js Outdated

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.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

new-ui-source/src/components/storyboard/placeholderTitles.ts:50

  • isDefaultSchemaTitle / storyboardLabel are currently case-sensitive, but the server-side placeholder-title filter is explicitly case-insensitive (lowercases before lookup). As a result, legacy variants like "Article title" / "article title" can slip through in the UI even though they’re suppressed in export, breaking the "kept in sync" contract.
export function isDefaultSchemaTitle(text: string | undefined | null): boolean {
  const t = (text || '').trim();
  return !t || DEFAULT_SCHEMA_TITLES.has(t);
}

new-ui-source/src/components/storyboard/blocks/assessmentBlock.tsx:452

  • Assessment preview renders option text/feedback verbatim. The backend can store these fields as HTML (e.g. <p>Correct</p>), so the Preview will show raw tags. Since the Word/PDF export path already strips HTML for these values, the UI preview should also strip tags for consistency/readability.
                      <span className={o.correct ? 'font-medium text-foreground' : 'text-foreground'}>
                        {o.text}
                      </span>
                    </div>
                    {kind === 'gmcq' && (o.imageUrl || o.image) && (

new-ui-source/src/components/storyboard/blocks/assessmentBlock.tsx:518

  • Whole-question feedback is also rendered verbatim in Preview; if stored as HTML it will display raw tags. Strip tags here the same way as option feedback so the Preview matches the exported document.
                  <div key={k}>
                    <span className="font-semibold text-foreground">{lbl}:</span>{' '}
                    <span className="text-foreground">{fb[k]}</span>
                  </div>

Comment thread plugins/content/storyboard/utils/assetResolver.js Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants