Skip to content

Conversation

@carlobeltrame
Copy link
Member

No description provided.

@carlobeltrame carlobeltrame requested a review from a team August 21, 2025 00:12
@carlobeltrame carlobeltrame changed the title Page numbers in PDFs Configurable page numbers in PDFs Aug 23, 2025
@BacLuc BacLuc added the deploy! Creates a feature branch deployment for this PR label Aug 23, 2025
@BacLuc BacLuc temporarily deployed to feature-branch August 23, 2025 11:13 — with GitHub Actions Inactive
@github-actions
Copy link

github-actions bot commented Aug 23, 2025

Feature branch deployment currently inactive.

If the PR is still open, you can add the deploy! label to this PR to trigger a feature branch deployment.

},
],
documentName: 'test camp',
options: { pageNumbers: false },
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe this test could use a little drying in a future pr.

@BacLuc
Copy link
Contributor

BacLuc commented Aug 23, 2025

@coderabbitai review this pr

@BacLuc BacLuc requested a review from a team August 23, 2025 12:16
@coderabbitai
Copy link

coderabbitai bot commented Aug 23, 2025

🧩 Analysis chain

@BacLuc I'll review the PR for configurable page numbers in PDFs. Let me gather the changes and analyze the implementation.


🏁 Script executed:

#!/bin/bash
# Get PR information and changed files
echo "=== PR Information ==="
gh pr view 8003 --json title,body,files

echo -e "\n=== Changed Files ==="
gh pr diff 8003 --name-only

echo -e "\n=== PR Diff ==="
gh pr diff 8003

Length of output: 100092


@BacLuc I've reviewed the PR for configurable page numbers in PDFs. This is a comprehensive and well-implemented feature! Here's my analysis:

Strengths

1. Comprehensive Implementation

  • The feature is consistently implemented across all PDF components (Activity, Program, Picasso, Cover, TOC, etc.)
  • Proper UI integration with a clear checkbox in the PrintConfigurator
  • Backend support with conditional footer template generation

2. Excellent Configuration Management

  • The repairPrintConfig.js properly validates and defaults options.pageNumbers to false
  • Configuration is properly passed through all components and respected everywhere

3. Smart Page Number Tracking

  • Uses a global $toc object to track page numbers for table of contents
  • Page numbers are captured using render functions at the right component levels

4. Thorough Test Coverage

  • All 179 test cases in repairPrintConfig.spec.js updated to include the new option
  • Snapshot tests updated to reflect the structural changes
  • E2E test updated to include the new option

🔍 Technical Implementation Review

Frontend Components:

  • ✅ PrintConfigurator properly adds the option with German localization
  • ✅ All PDF components consistently implement the slot pattern for page numbers
  • ✅ TocEntryPageNumber component is well-designed with proper styling

PDF Structure:

  • ✅ Page number positioning is correctly implemented (bottom center, 15pt margin)
  • ✅ Dotted lines in TOC are properly styled
  • ✅ Conditional rendering based on config.options.pageNumbers

Backend:

  • ✅ Print server correctly generates conditional footer templates
  • ✅ Page number format matches between PDF content and footer (pageNumber / totalPages)

💡 Minor Suggestions

  1. Consider adding English localization - Only German localization is added, but the feature should work in other languages too.

🏁 Script executed:

#!/bin/bash
# Check if English localization exists and what other languages might need the new keys
echo "=== Checking localization files ==="
find frontend/src/locales -name "*.json" -exec echo "File: {}" \; -exec grep -A5 -B5 "printConfigurator" {} \; 2>/dev/null || echo "No printConfigurator found in other locale files"

echo -e "\n=== Available locale files ==="
ls -la frontend/src/locales/

Length of output: 3807


  1. Missing Localization for Other Languages - I can see that only German localization was added, but the app supports English, French, Italian, and Romansh. The new keys should be added to all locale files:

🏁 Script executed:

#!/bin/bash
# Check what the English localization currently has for printConfigurator
echo "=== English localization structure ==="
grep -A20 '"printConfigurator"' frontend/src/locales/en.json

echo -e "\n=== Suggested translations needed ==="
echo 'For en.json:'
echo '"options": "Options",'
echo '"pageNumbers": "Page numbers"'
echo ''
echo 'For fr.json:'
echo '"options": "Options",'  
echo '"pageNumbers": "Numéros de page"'
echo ''
echo 'For it.json:'
echo '"options": "Opzioni",'
echo '"pageNumbers": "Numeri di pagina"'
echo ''
echo 'For rm.json:'
echo '"options": "Opziuns",'
echo '"pageNumbers": "Numers da pagina"'

Length of output: 1573


Let me continue with the review and check one more technical aspect:


🏁 Script executed:

#!/bin/bash
# Check if there are any potential issues with the page number implementation
echo "=== Checking for potential TOC ID consistency ==="
echo "Looking for TOC ID usage patterns..."

# Check if all components properly set TOC IDs
grep -r "\$toc\[" pdf/src/campPrint/ --include="*.vue" | head -10

echo -e "\n=== Checking for CSS class consistency ==="
grep -r "page-number" pdf/src/ --include="*.vue"

echo -e "\n=== Checking print config validation logic ==="
head -15 frontend/src/components/print/repairPrintConfig.js | tail -10

Length of output: 2251


Final Assessment

This is an excellent implementation of configurable page numbers! The code quality is high and the feature is comprehensive.

Technical Excellence:

  • ✅ Consistent TOC ID patterns across all components
  • ✅ Proper page number tracking with global $toc object
  • ✅ Clean separation of concerns between PDF content and TOC display
  • ✅ Robust configuration validation in repairPrintConfig.js
  • ✅ Comprehensive test coverage with all edge cases handled

Missing Localization Fix

The only improvement needed is adding the missing translations. Here are the exact additions needed:

frontend/src/locales/en.json:

"printConfigurator": {
  "add": "Add content",
  "config": {
    // ... existing config entries
  },
  "options": "Options",
  "pageNumbers": "Page numbers"
}

frontend/src/locales/fr.json:

"options": "Options",
"pageNumbers": "Numéros de page"

frontend/src/locales/it.json:

"options": "Opzioni", 
"pageNumbers": "Numeri di pagina"

frontend/src/locales/rm.json:

"options": "Opziuns",
"pageNumbers": "Numers da pagina"

🎉 Recommendation

APPROVE - This is a well-architected feature that:

  • ✅ Follows existing code patterns consistently
  • ✅ Has comprehensive test coverage
  • ✅ Implements the feature thoroughly across all PDF components
  • ✅ Uses sensible defaults (page numbers off by default)
  • ✅ Provides a clean UI for configuration

The missing localizations are a minor issue that can be addressed in a follow-up commit or before merging.

Great work on this comprehensive feature implementation! 🚀

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Aug 23, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a print setting to toggle page numbers (default off).
    • When enabled, page numbers appear on pages and in the table of contents across all sections (Cover, Program, Story, Activity, Activity List, Picasso, Safety Considerations).
    • Footer page numbers are included in generated PDFs when enabled.
  • I18n

    • Added German translations for “Options” and “Page numbers”.
  • Tests

    • Updated unit, renderer, and end-to-end tests to cover the new page number option.

Walkthrough

Introduces a configurable page-number feature across print/PDF flows: adds options.pageNumbers to print configs and UI, normalizes it in repair, wires it into server PDF footer rendering, initializes a global $toc map, annotates pages with IDs and page numbers, updates TOC components to optionally display page numbers, and extends tests and locales accordingly.

Changes

Cohort / File(s) Summary
Print config option propagation
frontend/src/components/activity/ScheduleEntry.vue, frontend/src/views/camp/CampProgram.vue, frontend/src/views/camp/Story.vue
Added options: { pageNumbers: false } to computed printConfig() objects.
Configurator UI and defaults
frontend/src/components/print/PrintConfigurator.vue, frontend/src/locales/de.json
Added Options panel with checkbox bound to cnf.options.pageNumbers; defaulted option in config flow; added de locale keys printConfigurator.options and printConfigurator.pageNumbers.
Config repair and tests
frontend/src/components/print/repairPrintConfig.js, frontend/src/components/print/__tests__/repairPrintConfig.spec.js
Normalizes config.options object and coerces pageNumbers to boolean (default false); updated tests to include options in inputs/expectations.
E2E and renderer tests
e2e/specs/nuxtPrint.cy.js, pdf/src/renderer/__tests__/vueRenderer.spec.js
Updated tests to include options.pageNumbers in print configs and expectations.
Renderer setup
pdf/src/renderer/vueRenderer.js
Initialized global $toc = {} on Vue app.
Per-page number rendering
pdf/src/CampPrint.vue
Conditionally renders page number text on each page when config.options.pageNumbers is true; added .page-number style.
TOC page capture (View hooks and slots)
pdf/src/campPrint/cover/Cover.vue, .../activity/Activity.vue, .../activityList/ActivityList.vue, .../picasso/Picasso.vue, .../picasso/PicassoPage.vue, .../picasso/PicassoPeriod.vue, .../program/Program.vue, .../program/ProgramPeriod.vue, .../scheduleEntry/ScheduleEntry.vue, .../scheduleEntry/ScheduleEntryTitle.vue, .../summary/Story.vue, .../summary/SafetyConsiderations.vue, .../summary/SummaryPeriod.vue
Added slots and <View :render="({ pageNumber }) => ($toc[key]=pageNumber)" /> to record page numbers under composite IDs; passed IDs into titles/entries where needed.
TOC core and entry components
pdf/src/campPrint/tableOfContents/TableOfContents.vue, pdf/src/campPrint/tableOfContents/TocEntryPageNumber.vue, pdf/src/campPrint/tableOfContents/entry/Activity.vue, .../entry/ActivityList.vue, .../entry/Cover.vue, .../entry/Picasso.vue, .../entry/Program.vue, .../entry/ProgramPeriod.vue, .../entry/SafetyConsiderations.vue, .../entry/Story.vue, .../entry/Summary.vue, .../entry/Toc.vue
Added TOC page-number component and conditional rendering based on config.options.pageNumbers; propagated config prop to entries; registered new ActivityList entry; adjusted TOC styles and added page-number collector View.
Server PDF footer
print/server/api/pdf.js
Computes footerTemplate dynamically from queryParams.config to include page numbers when options.pageNumbers is true; replaces fixed footer; introduces JSON.parse path.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant UI as Frontend UI (Configurator)
  participant App as Frontend Components
  participant Srv as Print Server (pdf.js)
  participant Rend as PDF Renderer (Vue app)

  User->>UI: Toggle "Page numbers"
  UI->>App: onChange(config.options.pageNumbers)
  App->>Srv: Request PDF with query ?config=JSON(config)
  Srv->>Srv: Parse config JSON
  alt options.pageNumbers = true
    Srv->>Srv: Build footerTemplate with page numbers
  else
    Srv->>Srv: Build empty footerTemplate
  end
  Srv->>Rend: Render HTML via headless (header/footer/templates)
  activate Rend
  Rend->>Rend: Initialize $toc = {}
  Rend->>Rend: For each Page/View: record $toc[id]=pageNumber
  alt options.pageNumbers = true
    Rend->>Rend: Render per-page footer numbers and TOC numbers
  else
    Rend->>Rend: Suppress page/TOC numbers
  end
  Rend-->>User: PDF
  deactivate Rend
Loading
sequenceDiagram
  autonumber
  participant Page as Page Components
  participant Hook as View(render)
  participant Map as $toc
  participant TOC as TOC Entry Components

  Page->>Hook: Render with ({ pageNumber })
  Hook->>Map: $toc[compositeId] = pageNumber
  TOC->>Map: Lookup $toc[compositeId]
  alt config.options.pageNumbers
    TOC->>TOC: Render TocEntryPageNumber
  else
    TOC->>TOC: Omit page number
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.38.6)
frontend/src/components/print/__tests__/repairPrintConfig.spec.js

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
frontend/src/components/print/__tests__/repairPrintConfig.spec.js (1)

118-142: Add a positive test for pageNumbers: true

The grep confirmed there are currently no tests covering the true branch of pageNumbers. Please add at least one unit (or e2e) spec to ensure that supplying { options: { pageNumbers: true } } is preserved by repairConfig.

• Location

  • frontend/src/components/print/__tests__/repairPrintConfig.spec.js

• Example unit‐test to insert:

@@ describe('repairConfig', () => {
   test('fills empty config with default data', async () => { … })
+
+  test('preserves true pageNumbers option', () => {
+    // given
+    const config = {
+      camp: '/camps/1a2b3c4d',
+      contents: [{ type: 'Picasso', options: { periods: ['/periods/1a2b3c4d'] } }],
+      documentName: 'test camp',
+      options: { pageNumbers: true },
+      language: 'en-GB',
+    }
+
+    // when
+    const result = repairConfig(config, ...args)
+
+    // then
+    expect(result.options.pageNumbers).toBe(true)
+  })

Feel free to also add a matching e2e assertion against your PDF‐output stub to catch any regressions in the rendered output.

pdf/src/campPrint/picasso/PicassoPeriod.vue (2)

2-12: Add a key to the v-for’d component to stabilize instance identity

Without a :key, Vue may reuse component instances across pages, causing subtle state bleed and incorrect render caching.

Apply this diff:

-  <PicassoPage
-    v-for="pageDays in pages"
+  <PicassoPage
+    v-for="(pageDays, pageIndex) in pages"
+    :key="`${id}-${period.id}-${pageIndex}`"
     :id="id"
     :config="config"
     :content="content"
     :period="period"
     :days="pageDays"
     :bedtime="bedtimes.bedtime"
     :get-up-time="bedtimes.getUpTime"
     :time-step="timeStep"
     :filter="filter"

2-12: Enforce first-write-wins TOC mapping in PicassoPage.vue

The unguarded assignment in pdf/src/campPrint/picasso/PicassoPage.vue will overwrite the TOC entry on every page of a multi-page period, causing later pages to clobber the first-page mapping. We need to only set the entry once (on the first page).

• File: pdf/src/campPrint/picasso/PicassoPage.vue
• Line: 4

Recommended change:

-  <View :render="({ pageNumber }) => ($toc[`${id}-${period.id}`] = pageNumber)" />
+  <View
+    :render="({ pageNumber }) => {
+      const key = `${id}-${period.id}`
+      if ($toc[key] == null) {
+        $toc[key] = pageNumber
+      }
+      return null
+    }"
+  />

This ensures the TOC entry for each period ID is only set on its first page.

♻️ Duplicate comments (2)
frontend/src/components/print/__tests__/repairPrintConfig.spec.js (1)

118-3931: DRY the repeated expectations by centralizing defaults.

You’re repeating { options: { pageNumbers: false } } in virtually every test. This obscures the intent of each scenario.

Consider:

  • Introduce const DEFAULT_DOC = { language: 'en-GB', options: { pageNumbers: false } } and use expect.objectContaining(DEFAULT_DOC) for the common tail.
  • Or add a small helper expectConfig(result, overrides) that merges defaults and compares.

Example pattern you can adopt incrementally:

- expect(result).toEqual({
-   camp: '/camps/1a2b3c4d',
-   contents: [...],
-   documentName: 'test camp',
-   options: { pageNumbers: false },
-   language: 'en-GB',
- })
+ const BASE = { language: 'en-GB', options: { pageNumbers: false } }
+ expect(result).toEqual(
+   expect.objectContaining({
+     camp: '/camps/1a2b3c4d',
+     contents: [...],
+     documentName: 'test camp',
+     ...BASE,
+   })
+ )

This keeps focus on the part under test and reduces maintenance churn if defaults evolve again.

print/server/api/pdf.js (1)

124-127: Harden config parsing and extract a small factory (also addresses prior feedback)

Direct JSON.parse(queryParams.config) will throw if the param is URL-encoded or malformed, causing a 500. Also, BacLuc requested a factory over the ternary. Let’s safely decode/parse and centralize footer construction.

Apply this diff in-place:

-    const footerTemplate = JSON.parse(queryParams.config).options?.pageNumbers
-      ? `<div id="footer-template" style="font-size:7pt; text-align: center; width: 100%; font-family: Helvetica, sans-serif; font-weight: 500"><span class="pageNumber"></span> / <span class="totalPages"></span></div>`
-      : '<div id="footer-template" style="font-size:7pt; text-align: center; width: 100%; font-family: Helvetica, sans-serif; font-weight: 500"></div>'
+    const footerTemplate = createFooterTemplateFromConfig(queryParams.config)

Add these helpers (anywhere in module scope; below is fine), which safely handle both raw and percent-encoded payloads and default to no footer on parse failure:

+function createFooterTemplateFromConfig(configParam) {
+  const cfg = safeParseConfig(configParam)
+  const show = cfg?.options?.pageNumbers === true
+  const style =
+    'font-size:7pt; text-align: center; width: 100%; font-family: Helvetica, sans-serif; font-weight: 500'
+  return show
+    ? `<div id="footer-template" style="${style}"><span class="pageNumber"></span> / <span class="totalPages"></span></div>`
+    : `<div id="footer-template" style="${style}"></div>`
+}
+
+function safeParseConfig(configParam) {
+  if (!configParam || typeof configParam !== 'string') return {}
+  try {
+    const maybeDecoded =
+      configParam.includes('%') ? decodeURIComponent(configParam) : configParam
+    return JSON.parse(maybeDecoded)
+  } catch (e) {
+    // Keep PDF generation going without footer rather than failing the request
+    captureError?.(e)
+    return {}
+  }
+}
🧹 Nitpick comments (27)
pdf/src/campPrint/activityList/ActivityList.vue (1)

3-4: Gate TOC page-number capture behind config and avoid returning a value from the render callback

  • Avoid writing to $toc when page numbers are disabled.
  • Prefer a block-bodied arrow function to make the side effect explicit and avoid returning a number.

Apply this diff:

-    <slot></slot>
-    <View :render="({ pageNumber }) => ($toc[id] = pageNumber)" />
+    <slot></slot>
+    <View
+      v-if="config.options.pageNumbers"
+      :render="({ pageNumber }) => { $toc[id] = pageNumber }"
+    />

Optional: If this pattern repeats across pages, consider extracting a tiny helper like to DRY it up. I can sketch this if helpful.

frontend/src/components/activity/ScheduleEntry.vue (1)

413-414: Defaulting print options here is fine; consider centralizing defaults to avoid drift

Setting options: { pageNumbers: false } is correct and consistent with repair logic. To prevent future divergence across places that construct printConfig, consider centralizing default print options.

Example (no immediate change required, but if you opt in, do the following):

  1. Create a small helper:
// frontend/src/components/print/printOptions.js
export const DEFAULT_PRINT_OPTIONS = { pageNumbers: false }
  1. Use it here:
-        options: { pageNumbers: false },
+        options: DEFAULT_PRINT_OPTIONS,
  1. And import at the top of this file:
import { DEFAULT_PRINT_OPTIONS } from '@/components/print/printOptions.js'
frontend/src/components/print/repairPrintConfig.js (2)

23-31: Solid normalization; optionally coerce common non-boolean inputs

Current logic strictly enforces boolean, defaulting to false when not strictly true/false. If you want to be lenient with inputs coming from URLs, forms, or legacy configs, you can coerce 'true'/'false' strings.

Apply this diff:

   if (
-    configClone.options.pageNumbers !== true &&
-    configClone.options.pageNumbers !== false
+    typeof configClone.options.pageNumbers !== 'boolean'
   ) {
-    configClone.options.pageNumbers = false
+    const pn = configClone.options.pageNumbers
+    // Coerce common representations; default to false otherwise
+    configClone.options.pageNumbers = pn === true || pn === 'true'
   }

47-49: Guard against missing options when repairPrintFilterConfig is called standalone

repairPrintFilterConfig assumes config.options exists; if it’s ever called without repairConfig first, this can throw. Add a minimal guard.

Apply this diff:

 export function repairPrintFilterConfig(config, camp, knownPeriods) {
+  if (!config.options || typeof config.options !== 'object') {
+    config.options = {}
+  }
   if (!config.options.filter || typeof config.options.filter !== 'object') {
     config.options.filter = {
       category: [],
pdf/src/campPrint/cover/Cover.vue (1)

3-4: Mirror ActivityList: gate capture on config and prefer explicit side-effect function

Same reasoning as elsewhere: don’t populate $toc when page numbers are disabled; make the side effect explicit.

Apply this diff:

-    <slot></slot>
-    <View :render="({ pageNumber }) => ($toc[id] = pageNumber)" />
+    <slot></slot>
+    <View
+      v-if="config.options.pageNumbers"
+      :render="({ pageNumber }) => { $toc[id] = pageNumber }"
+    />
pdf/src/campPrint/summary/SafetyConsiderations.vue (1)

3-3: Consider capturing the page number for TOC consistency

Other sections (e.g., Cover, ActivityList) record their starting page in $toc. If SafetyConsiderations has a TOC entry, you’ll likely want the same capture here.

Apply this diff:

   <Page :id="id" class="page">
-    <slot></slot>
+    <slot></slot>
+    <View
+      v-if="config.options.pageNumbers"
+      :render="({ pageNumber }) => { $toc[id] = pageNumber }"
+    />

If a child like SummaryPeriod already performs the capture (or if the TOC entry intentionally omits a page number), feel free to skip this. Please confirm which component provides the capture for this section.

pdf/src/campPrint/program/ProgramPeriod.vue (1)

2-2: Gate TOC page-number tracking behind config and declare the prop.

You’re always mutating $toc during render, even when page numbers are disabled. Gate this to reduce side-effects and noise.

Apply this diff to gate the assignment:

-  <View :render="({ pageNumber }) => ($toc[`${id}-${period.id}`] = pageNumber)" />
+  <View
+    v-if="config && config.options && config.options.pageNumbers"
+    :render="({ pageNumber }) => ($toc[`${id}-${period.id}`] = pageNumber)"
+  />

Additionally, since ProgramPeriod is being passed :config="config" upstream, explicitly declare the prop to avoid leaking it into $attrs:

// Add to props (outside the changed lines)
props: {
  period: { type: Object, required: true },
  filter: { type: Object, default: () => ({}) },
  config: { type: Object, required: true },
},

If config was intentionally not a prop (e.g., inherited via PdfComponent), confirm that $attrs aren’t polluting the PDF DOM. I can generate a script to scan similar components for consistent prop declaration if helpful.

pdf/src/CampPrint.vue (1)

126-134: Align bottom offset with page padding to avoid overlap.

.page uses padding: 30; while the page number sits at bottom: 15;. With fixed positioning this can overlap content. Consider matching the bottom offset to the page padding or slightly above it.

-.page-number {
-  position: absolute;
-  bottom: 15;
+.page-number {
+  position: absolute;
+  bottom: 30; /* align with .page padding */
   left: 0;
   right: 0;
   width: 100%;
   text-align: center;
   font-size: 10;
 }

Optionally, expose this offset via a style constant if you expect different paddings per layout.

pdf/src/campPrint/activity/Activity.vue (1)

3-6: Gate TOC page-number tracking to the feature flag.

Mirror the gating you use for rendering the page-number footer and avoid populating $toc when disabled.

-    <slot></slot>
-    <View
-      :render="({ pageNumber }) => ($toc[`${id}-${scheduleEntry.id}`] = pageNumber)"
-    />
+    <slot></slot>
+    <View
+      v-if="config && config.options && config.options.pageNumbers"
+      :render="({ pageNumber }) => ($toc[`${id}-${scheduleEntry.id}`] = pageNumber)"
+    />

Double-check other components that write to $toc (e.g., Picasso pages, Program periods, Story) follow the same gating for consistency.

e2e/specs/nuxtPrint.cy.js (1)

18-24: Add a positive-coverage test for pageNumbers=true and assert UI behavior

Right now we only exercise the “off” path. Add a second case with pageNumbers: true and assert that page numbers appear in the TOC/preview. Exposing a stable selector in the page-number component will make this robust.

Apply this diff to add a second test in the same spec (assumes a data-cy hook on the page-number element):

@@
-        const printConfig = {
+        const printConfig = {
           language: 'en',
           documentName: 'camp',
           options: { pageNumbers: false },
@@
         cy.contains(camp.motto)
 
         cy.get('#content_0_cover').should('have.css', 'font-size', '50px') // this ensures Tailwind is properly built and integrated
       })
     })
   })
 
+  it('shows TOC page numbers when enabled', () => {
+    cy.login('test@example.com')
+    cy.request('/api/camps.jsonhal').then((response) => {
+      const camp = response.body._embedded.items.filter((c) => c.motto)[0]
+      const campUri = camp._links.self.href
+      cy.request(camp._links.periods.href).then((periodsResponse) => {
+        const periodUri = periodsResponse.body._embedded.items[0]._links.self.href
+        const printConfig = {
+          language: 'en',
+          documentName: 'camp',
+          options: { pageNumbers: true },
+          camp: campUri,
+          contents: [
+            { type: 'Cover', options: {} },
+            { type: 'Program', options: { periods: [periodUri], dayOverview: true } },
+            { type: 'Toc', options: {} },
+          ],
+        }
+        cy.visit(
+          Cypress.env('PRINT_URL') + '/?config=' + encodeURIComponent(JSON.stringify(printConfig))
+        )
+        // Assert that at least one TOC entry shows a page number
+        cy.get('[data-cy="toc-entry-page-number"]').should('exist')
+      })
+    })
+  })

And expose a stable selector in the page-number component:

--- a/pdf/src/campPrint/tableOfContents/TocEntryPageNumber.vue
+++ b/pdf/src/campPrint/tableOfContents/TocEntryPageNumber.vue
@@
-  <Text>{{ pageNumber }}</Text>
+  <Text data-cy="toc-entry-page-number">{{ pageNumber }}</Text>
pdf/src/campPrint/tableOfContents/entry/Summary.vue (1)

9-10: Ensure config prop is always provided to Summary.vue

  • Static searches did not reveal any direct <Summary> usages without a config attribute; both Story.vue and SafetyConsiderations.vue extend Summary and reference config.options.pageNumbers.
  • However, static regexes can miss dynamic registrations or programmatic renders—please manually verify every call site (including any <component :is="…"> or render functions) passes the config prop to these entry components.

Optional fallback default for backward compatibility:

-    config: { type: Object, required: true },
+    config: { type: Object, required: false, default: () => ({ options: {} }) },
pdf/src/campPrint/picasso/Picasso.vue (1)

2-10: Add a :key on the v-for to prevent vnode reuse issues across periods

Without a key, Vue may reuse ProgramPeriod instances across iterations, which can subtly break per-page capture or TOC mapping when period counts change.

Apply:

   <PicassoPeriod
     v-for="period in periods"
+    :key="period.id"
     :id="id"
     :config="config"
     :content="content"
     :period="period"
   >
pdf/src/renderer/vueRenderer.js (1)

19-20: Use a null-prototype object for $toc to avoid prototype key collisions

Safer if any id ever equals "proto", "constructor", etc.

-        app.config.globalProperties.$toc = {}
+        // Use a null-prototype object to avoid accidental collisions with Object.prototype keys.
+        app.config.globalProperties.$toc = Object.create(null)
frontend/src/views/camp/Story.vue (1)

65-71: Centralize options.pageNumbers default across print configurations

We’ve identified multiple components explicitly setting options: { pageNumbers: false }. To reduce duplication and keep defaults in sync with repairPrintConfig, consider extracting this into a shared constant.
Locations found:

  • frontend/src/views/camp/Story.vue (line 70)
  • frontend/src/views/camp/CampProgram.vue (line 210)
  • frontend/src/components/print/PrintConfigurator.vue (line 183)
  • frontend/src/components/activity/ScheduleEntry.vue (line 413)

Suggested steps:

  1. Create a module, e.g. frontend/src/utils/printDefaults.js:
    export const defaultPrintOptions = { pageNumbers: false };
  2. In each component, replace the inline object with a spread of the shared default:
    - options: { pageNumbers: false },
    + import { defaultPrintOptions } from '@/utils/printDefaults';
    + options: { ...defaultPrintOptions },
  3. Update any related tests (e.g., in repairPrintConfig.spec.js) to import and reference defaultPrintOptions instead of hardcoding.

This will keep your defaults DRY and ensure consistency whenever the default behavior needs to change.

pdf/src/campPrint/tableOfContents/entry/Program.vue (1)

2-8: Add a :key on the v-for to stabilize ProgramPeriod instances

This avoids vnode reuse pitfalls that could desync captured page numbers for periods and schedule entries.

   <ProgramPeriod
     v-for="period in periods"
+    :key="period.id"
     :id="id"
     :period="period"
     :filter="entry.options.filter"
     :config="config"
   />
pdf/src/campPrint/summary/Story.vue (1)

3-3: Pass id via slot props to make projections self-contained

Projectors might need the page id without coupling to parent scope. Expose it as a slot prop; keeps the pattern consistent with other Page wrappers and helps future consumers.

Apply this diff:

-    <slot></slot>
+    <slot :id="id"></slot>
pdf/src/campPrint/program/Program.vue (1)

3-3: Mirror Story/Cover slot API: pass id through the slot

Same rationale: let injected content consume the page anchor without reaching up the component tree. This also future‑proofs any page-number probes injected via the slot.

Apply this diff:

-    <slot></slot>
+    <slot :id="id"></slot>
pdf/src/renderer/__tests__/vueRenderer.spec.js (1)

58-58: Add at least one positive test with options.pageNumbers: true

All updated fixtures set pageNumbers: false. We should cover the enabled path to lock in the TOC number rendering and renderer plumbing.

Proposed addition (append near the existing “table of contents” test):

+  it('renders the table of contents with page numbers enabled', async () => {
+    // given
+    const store = wrap(fullCampStoreContent)
+
+    // when
+    const result = renderVueToPdfStructure(CampPrint, {
+      store,
+      $tc: tcMock,
+      locale: 'de',
+      config: {
+        language: 'de',
+        documentName: 'Pfila 2023.pdf',
+        options: { pageNumbers: true },
+        camp: store.get('/camps/c4cca3a51342'),
+        contents: [
+          {
+            type: 'Toc',
+            options: {},
+          },
+        ],
+      },
+    })
+
+    // then
+    expect(result).toMatchFileSnapshot(
+      './__snapshots__/table_of_contents_with_page_numbers.spec.json.snap'
+    )
+  })

If you prefer parameterization, I can refactor these cases into it.each([{pageNumbers:false},{pageNumbers:true}], ...).

Also applies to: 95-95, 134-134, 163-163, 192-192, 222-222, 272-272

pdf/src/campPrint/tableOfContents/entry/Picasso.vue (1)

2-5: TOC entry robustness: add :key and null-safe guard for config.options

  • Add :key on the v-for Link for stable VDOM.
  • Guard the template against missing options (defensive; repair should set it, but this costs nothing).

Apply this diff:

-  <Link v-for="period in periods" class="toc-entry" :href="`#${id}-${period.id}`">
+  <Link
+    v-for="period in periods"
+    :key="period.id"
+    class="toc-entry"
+    :href="`#${id}-${period.id}`"
+  >
     <Text>{{ $tc('print.picasso.title', { period: period.description }) }}</Text>
-    <TocEntryPageNumber v-if="config.options.pageNumbers" :id="`${id}-${period.id}`" />
+    <TocEntryPageNumber
+      v-if="config?.options?.pageNumbers"
+      :id="`${id}-${period.id}`"
+    />

Also applies to: 9-9, 13-13, 17-17

frontend/src/views/camp/CampProgram.vue (1)

210-211: Defaulting options.pageNumbers here is fine; consider centralizing defaults

No functional issues. For consistency and to reduce duplication across views, consider centralizing print-config defaults (e.g., a buildBasePrintConfig({ documentName, contents, camp, language }) helper that injects options: { pageNumbers: false }) and reuse it here and in other views.

pdf/src/campPrint/tableOfContents/entry/SafetyConsiderations.vue (1)

2-8: Add a key on v-for list items to prevent Vue warnings and improve patching

Add a stable key on the Link rendered in the v-for.

Apply:

-  <Link v-for="period in periods" class="toc-entry" :href="`#${id}-${period.id}`">
+  <Link v-for="period in periods" :key="period.id" class="toc-entry" :href="`#${id}-${period.id}`">
pdf/src/campPrint/tableOfContents/entry/Story.vue (1)

2-5: Add a key on the v-for

Mirror the key addition to avoid potential Vue list-diffing warnings.

-  <Link v-for="period in periods" class="toc-entry" :href="`#${id}-${period.id}`">
+  <Link v-for="period in periods" :key="period.id" class="toc-entry" :href="`#${id}-${period.id}`">
pdf/src/campPrint/tableOfContents/entry/Activity.vue (1)

8-11: DRY the anchor id to prevent divergence with the Link’s href.

The id string is duplicated here and on Line 2. Prefer a single computed anchorId to avoid future drift.

Suggested changes within this block:

-    <TocEntryPageNumber
-      v-if="config.options.pageNumbers"
-      :id="`${id}-${scheduleEntry.id}`"
-    />
+    <TocEntryPageNumber
+      v-if="config.options.pageNumbers"
+      :id="anchorId"
+    />

And add (outside the selected lines) to <script>:

computed: {
  // ...
  anchorId() {
    return `${this.id}-${this.scheduleEntry?.id}`
  },
}

Optionally, update the Link (Line 2) to :href="\#${anchorId}`"`.

pdf/src/campPrint/tableOfContents/entry/ProgramPeriod.vue (2)

5-5: Guard against missing options and centralize the flag.

Use optional chaining to avoid runtime errors if config.options is absent in legacy configs, and prefer a computed showPageNumbers for clarity and reuse.

-      <TocEntryPageNumber v-if="config.options.pageNumbers" :id="`${id}-${period.id}`" />
+      <TocEntryPageNumber v-if="showPageNumbers" :id="`${id}-${period.id}`" />
...
-      <TocEntryPageNumber
-        v-if="config.options.pageNumbers"
-        :id="`${id}-${period.id}-${scheduleEntry.id}`"
-      />
+      <TocEntryPageNumber
+        v-if="showPageNumbers"
+        :id="`${id}-${period.id}-${scheduleEntry.id}`"
+      />

Add this computed in the script block:

 export default {
   name: 'Program',
   components: { TocEntryPageNumber },
   extends: PdfComponent,
   props: {
     period: { type: Object, required: true },
     filter: { type: Object, default: () => ({}) },
     config: { type: Object, required: true },
   },
   computed: {
+    showPageNumbers() {
+      return this.config?.options?.pageNumbers ?? false
+    },
     anyScheduleEntries() {
       return this.period.scheduleEntries().items.length
     },

Also applies to: 17-20


27-37: Wiring looks good; tiny naming nit.

Import/registration of TocEntryPageNumber and the new config prop are correct. Minor nit: the component is named 'Program' while the file is ProgramPeriod.vue. Consider renaming to improve DX when debugging.

-export default {
-  name: 'Program',
+export default {
+  name: 'ProgramPeriod',
pdf/src/campPrint/tableOfContents/TableOfContents.vue (2)

3-5: Capture TOC page number — prefer a block body for readability.

The render-prop approach is correct and ensures the TOC entry can reference its own page number. Slight readability win by using a block body.

-    <slot></slot>
-    <View :render="({ pageNumber }) => ($toc[id] = pageNumber)" />
+    <slot></slot>
+    <View :render="({ pageNumber }) => { $toc[id] = pageNumber }" />

33-35: Component name likely incorrect.

name: 'Cover' in TableOfContents is misleading. Consider renaming to 'TableOfContents' for clarity in devtools and error logs.

 export default {
-  name: 'Cover',
+  name: 'TableOfContents',
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 91a69eb and 0dd0d74.

⛔ Files ignored due to path filters (7)
  • pdf/src/renderer/__tests__/__snapshots__/cover_page.spec.json.snap is excluded by !**/*.snap
  • pdf/src/renderer/__tests__/__snapshots__/picasso.spec.json.snap is excluded by !**/*.snap
  • pdf/src/renderer/__tests__/__snapshots__/program.spec.json.snap is excluded by !**/*.snap
  • pdf/src/renderer/__tests__/__snapshots__/single_activity.spec.json.snap is excluded by !**/*.snap
  • pdf/src/renderer/__tests__/__snapshots__/single_activity_with_special_text_with_empty_lists.spec.json.snap is excluded by !**/*.snap
  • pdf/src/renderer/__tests__/__snapshots__/story_overview.spec.json.snap is excluded by !**/*.snap
  • pdf/src/renderer/__tests__/__snapshots__/table_of_contents.spec.json.snap is excluded by !**/*.snap
📒 Files selected for processing (38)
  • e2e/specs/nuxtPrint.cy.js (1 hunks)
  • frontend/src/components/activity/ScheduleEntry.vue (1 hunks)
  • frontend/src/components/print/PrintConfigurator.vue (2 hunks)
  • frontend/src/components/print/__tests__/repairPrintConfig.spec.js (179 hunks)
  • frontend/src/components/print/repairPrintConfig.js (1 hunks)
  • frontend/src/locales/de.json (1 hunks)
  • frontend/src/views/camp/CampProgram.vue (1 hunks)
  • frontend/src/views/camp/Story.vue (1 hunks)
  • pdf/src/CampPrint.vue (2 hunks)
  • pdf/src/campPrint/activity/Activity.vue (1 hunks)
  • pdf/src/campPrint/activityList/ActivityList.vue (1 hunks)
  • pdf/src/campPrint/activityList/ActivityListScheduleEntry.vue (1 hunks)
  • pdf/src/campPrint/cover/Cover.vue (1 hunks)
  • pdf/src/campPrint/picasso/Picasso.vue (1 hunks)
  • pdf/src/campPrint/picasso/PicassoPage.vue (1 hunks)
  • pdf/src/campPrint/picasso/PicassoPeriod.vue (1 hunks)
  • pdf/src/campPrint/program/Program.vue (1 hunks)
  • pdf/src/campPrint/program/ProgramPeriod.vue (1 hunks)
  • pdf/src/campPrint/scheduleEntry/ScheduleEntry.vue (1 hunks)
  • pdf/src/campPrint/scheduleEntry/ScheduleEntryTitle.vue (1 hunks)
  • pdf/src/campPrint/summary/SafetyConsiderations.vue (1 hunks)
  • pdf/src/campPrint/summary/Story.vue (1 hunks)
  • pdf/src/campPrint/summary/SummaryPeriod.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/TableOfContents.vue (4 hunks)
  • pdf/src/campPrint/tableOfContents/TocEntryPageNumber.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/entry/Activity.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/entry/ActivityList.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/entry/Cover.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/entry/Picasso.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/entry/Program.vue (2 hunks)
  • pdf/src/campPrint/tableOfContents/entry/ProgramPeriod.vue (2 hunks)
  • pdf/src/campPrint/tableOfContents/entry/SafetyConsiderations.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/entry/Story.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/entry/Summary.vue (1 hunks)
  • pdf/src/campPrint/tableOfContents/entry/Toc.vue (1 hunks)
  • pdf/src/renderer/__tests__/vueRenderer.spec.js (7 hunks)
  • pdf/src/renderer/vueRenderer.js (1 hunks)
  • print/server/api/pdf.js (1 hunks)
🔇 Additional comments (25)
pdf/src/CampPrint.vue (1)

10-17: No server-side PDF footer logic found in this repo
I ran a global search for both pageNumbers and footer across the entire repository and didn’t find any server-side PDF generation code (e.g. no server/pdf.js or similar files) that conditionally renders a footer. As a result, there is no risk of duplicate page numbers within this codebase.

If your application’s PDF footer is implemented in a separate service or repository, please verify the gating there—but within this PR, no additional changes are needed.

Likely an incorrect or invalid review comment.

frontend/src/components/print/__tests__/repairPrintConfig.spec.js (1)

118-142: LGTM: defaulting now includes options.pageNumbers consistently.

The assertions clearly encode the new default shape, which will protect against regressions in repairPrintConfig.

e2e/specs/nuxtPrint.cy.js (1)

21-21: Good: config now explicitly sets pageNumbers=false in E2E preview

This aligns the preview with the new option and avoids accidental regressions from defaults.

pdf/src/campPrint/picasso/PicassoPeriod.vue (1)

13-15: Slot forwarding looks correct and matches the new PicassoPage default slot

The projection enables consumers to inject per-page content (e.g., page-number overlays) cleanly.

pdf/src/campPrint/activityList/ActivityListScheduleEntry.vue (1)

2-6: Id contract verified end-to-end – approved
PdfComponent declares an id prop, ScheduleEntryTitle inherits and forwards it into the rendered <Text :id="id" …> binding, and all TOCEntryPageNumber usages (including Activity.vue’s :id="${id}-${scheduleEntry.id}") consume the same identifier patterns for deterministic page-number mapping.

pdf/src/campPrint/picasso/Picasso.vue (2)

8-10: Enabling a slot per period is the right hook for capturing page numbers

Switching from self-closing to a block element with a default slot lets downstream components inject the page-number capture without changing data flow. Looks good.


2-10: Sanity check: no slot projections found in scanned components—manual verification still recommended

The recent ripgrep scan across all .vue files in pdf/src and frontend/src did not locate any <Picasso> or <PicassoPeriod> usages wrapping projected slot content. This suggests no callers are currently injecting heavier markup into the slot, but to be safe, please manually verify any of the following:

  • Dynamic or programmatic usages of the Picasso/PicassoPeriod components (e.g. in render functions, other directories, or via plugin registration) only project the minimal capture node.
  • Ensure no unexpected <template v-slot> or scoped‐slot patterns around Picasso in other parts of the codebase.
pdf/src/renderer/vueRenderer.js (1)

11-23: LGTM: per-app $toc initialization is scoped and reset per render

The $toc map lives on the app instance, so each render gets a fresh map; avoids cross-render leakage.

pdf/src/campPrint/tableOfContents/entry/Program.vue (1)

7-8: No missing :config propagation for Program.vue

All instantiations of Program.vue occur via the dynamic entry‐component loop in TableOfContents.vue, which unconditionally passes the parent config prop to every entry component (including Program) here:

<template v-for="(entry, index) in config.contents">
  <component
    :is="entryComponents[entry.type]"
    v-if="entry.type in entryComponents"
    :id="`entry-${index}`"
    :entry="entry"
    :config="config"
  />
  …
</template>

Since that binding covers all entry types—Program included—there are no call sites that omit :config. The requirement for config in Program.vue and downstream in ProgramPeriod.vue is fully satisfied by this single, global template. You can safely ignore the original warning.

Likely an incorrect or invalid review comment.

print/server/api/pdf.js (1)

135-135: Confirm product intent: header always shown even when footer is empty

displayHeaderFooter: true keeps the header visible regardless of pageNumbers. If the intent is “numbers off, header still on,” this is correct. If “no page adornments at all,” consider toggling displayHeaderFooter alongside the footer.

If “no adornments” is desired, replace with:

-      displayHeaderFooter: true,
+      displayHeaderFooter: footerTemplate.includes('pageNumber') || true, // keep true if header must stay

Or better, gate with a config flag e.g., options.header.

pdf/src/campPrint/scheduleEntry/ScheduleEntry.vue (1)

2-2: No changes needed: id prop is correctly defined and inherited

Verified that pdf/src/PdfComponent.js declares an id prop and that both ScheduleEntry.vue and ScheduleEntryTitle.vue extend PdfComponent, inheriting this prop. Passing :id="id" into ScheduleEntryTitle is therefore safe and will not be undefined at runtime.

PdfComponent.js exports a component with

props: {
  id: { type: String, default: '' },
},

confirming id is defined
ScheduleEntry.vue extends PdfComponent, so id is available in its template
ScheduleEntryTitle.vue also extends PdfComponent (in addition to its own props), so it accepts the id prop as expected

No further action required.

pdf/src/campPrint/tableOfContents/entry/SafetyConsiderations.vue (1)

7-7: No null guard needed for config
The Summary.vue component (pdf/src/campPrint/tableOfContents/entry/Summary.vue) declares

props: {
  entry: { type: Object, required: true },
  config: { type: Object, required: true },
},

so config is guaranteed to be present. The existing usage in SafetyConsiderations.vue:

<TocEntryPageNumber
  v-if="config.options.pageNumbers"
  :id="`${id}-${period.id}`"
/>

is safe and requires no additional guard.

pdf/src/campPrint/tableOfContents/entry/Story.vue (1)

4-4: Resolved – config is guaranteed by Summary.vue
I’ve confirmed in pdf/src/campPrint/tableOfContents/entry/Summary.vue that config is declared as a required prop (type Object, required: true). Therefore, it can never be undefined at runtime, and using v-if="config.options.pageNumbers" safely guards rendering.

pdf/src/campPrint/tableOfContents/entry/Cover.vue (2)

4-4: LGTM: conditional page number rendering on Cover

The conditional <TocEntryPageNumber> render under config.options.pageNumbers is consistent with the rest of the TOC entries.


15-17: Prop contract consistency: config is correctly forwarded

Verified that in pdf/src/campPrint/tableOfContents/TableOfContents.vue, the dynamic entry renderer includes :config="config" (line 15), ensuring that the Cover component receives the config prop. No changes required.

pdf/src/campPrint/picasso/PicassoPage.vue (1)

3-3: Good addition: slot improves extensibility of page layout.

Exposes a content hook without affecting existing structure. No issues spotted.

pdf/src/campPrint/tableOfContents/entry/Toc.vue (1)

4-4: TOC entry integration looks correct and consistent.

Conditional page-number rendering, proper import/registration, and new config prop are all in line with the broader change.

Also applies to: 9-9, 13-13, 15-17

pdf/src/campPrint/tableOfContents/entry/Activity.vue (1)

8-11: Activity TOC: page-number wiring is correct.

  • Uses the same anchor id format as the Link.
  • Properly gated by config.options.pageNumbers.
  • Import/registration are minimal and localized.

Also applies to: 16-16, 20-20, 24-24

pdf/src/campPrint/tableOfContents/entry/ActivityList.vue (1)

1-5: ActivityList TOC entry: straightforward and consistent with the pattern.

  • Correct conditional rendering via config.options.pageNumbers.
  • Proper import/registration and required config prop.

Also applies to: 8-17

frontend/src/components/print/PrintConfigurator.vue (3)

61-64: v-model on a nested computed field — confirm stability of object identity.

Binding to cnf.options.pageNumbers is fine, but since cnf is a computed getter, ensure it returns a stable object identity (until store changes) so nested mutations aren’t lost on re-computation. Your @input="onChange" mitigates this by persisting to the store.


183-183: Good defaulting for backward compatibility.

Adding options: { pageNumbers: false } to the last-config fallback is the right call. Ensure repairPrintConfig.js also normalizes config.options.pageNumbers when absent to protect server-side renders that bypass the UI.


54-67: Verify that both translation keys exist under components.print.printConfigurator in every locale

Ripgrep confirms the printConfigurator object is defined in all locale files (including de.json, en.json, fr.json, it.json, rm.json and their -CH-scout variants). However, we weren’t able to programmatically confirm the presence of the options and pageNumbers properties in each block due to environment permission issues. Please manually verify that each of these locale JSON files includes both keys:

  • frontend/src/locales/en.json
  • frontend/src/locales/de.json
  • frontend/src/locales/fr.json
  • frontend/src/locales/it.json
  • frontend/src/locales/rm.json
  • And the corresponding *-CH-scout.json files

If any locale is missing either key, add entries for

"components": {
  "print": {
    "printConfigurator": {
      "options": "...",
      "pageNumbers": "..."
    }
  }
}

in that file.

pdf/src/campPrint/tableOfContents/TableOfContents.vue (3)

15-16: Passing config down is necessary and correct.

This enables conditional page-number rendering in entry components without duplicating global wiring.


31-31: ActivityList inclusion in TOC components looks consistent.

Importing and registering ActivityList aligns the TOC with supported content types.

Also applies to: 50-51


70-76: TOC layout tweaks — verify dotted leaders/spacing remain aligned.

Switching .toc-entry to width: 100% and using padding on .toc-sub-entry should improve alignment, but double-check that leader dots and page numbers still line up across wrapped titles.

@carlobeltrame carlobeltrame added this pull request to the merge queue Aug 25, 2025
Merged via the queue into ecamp:devel with commit 5a7a5ee Aug 25, 2025
30 checks passed
@carlobeltrame carlobeltrame deleted the page-numbers branch August 25, 2025 20:13
@BacLuc BacLuc mentioned this pull request Sep 2, 2025
@github-actions github-actions bot mentioned this pull request Sep 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deploy! Creates a feature branch deployment for this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants