All files / web/src/app/create/worksheets/utils extractConfigFields.ts

0% Statements 0/101
0% Branches 0/1
0% Functions 0/1
0% Lines 0/101

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102                                                                                                                                                                                                           
import type { AdditionConfigV4 } from '../config-schemas'
import type { WorksheetFormState } from '../types'

/**
 * Extract only the persisted config fields from formState
 *
 * ## Architecture: Blacklist Approach
 *
 * This function uses a **blacklist approach** to extract config fields:
 * - Automatically includes ALL fields from formState
 * - Only excludes specific derived/ephemeral fields: rows, total, date
 *
 * ### Why Blacklist Instead of Whitelist?
 *
 * **Previous approach (FRAGILE):**
 * - Manually listed every field to include
 * - Adding new config fields required updating this function
 * - Forgetting to update caused shared worksheets to break
 * - Multiple incidents where new fields weren't shared correctly
 *
 * **Current approach (ROBUST):**
 * - New config fields automatically work in shared worksheets
 * - Only need to update if adding new DERIVED fields
 * - Much harder to accidentally break sharing
 *
 * ### Field Categories
 *
 * **PRIMARY STATE** (persisted):
 * - problemsPerPage, cols, pages - define worksheet structure
 * - digitRange, operator - define problem space
 * - pAnyStart, pAllStart, interpolate - control regrouping distribution
 * - mode, displayRules, difficultyProfile, etc. - control display behavior
 * - seed, prngAlgorithm - ensure exact problem reproduction when shared
 *
 * **DERIVED STATE** (excluded):
 * - `total` = problemsPerPage × pages (recalculated on load)
 * - `rows` = Math.ceil((problemsPerPage / cols)) (recalculated on load)
 *
 * **EPHEMERAL STATE** (excluded):
 * - `date` - generated fresh at render time, not persisted
 *
 * ### Usage
 *
 * This function ensures consistent extraction across:
 * - Auto-save to localStorage (useWorksheetAutoSave hook)
 * - Share link creation (POST /api/worksheets/share)
 * - Settings persistence (POST /api/worksheets/settings)
 *
 * ### Critical for Sharing
 *
 * The `seed` and `prngAlgorithm` fields are CRITICAL - they ensure that
 * shared worksheets generate the exact same problems when opened by others.
 * Without these, each person would see different random problems.
 *
 * @param formState - The current form state (may be partial during editing)
 * @returns Clean config object ready for serialization (JSON.stringify)
 *
 * @example
 * // In ShareModal component
 * const config = extractConfigFields(formState)
 * await fetch('/api/worksheets/share', {
 *   method: 'POST',
 *   body: JSON.stringify({ worksheetType: 'addition', config })
 * })
 */
export function extractConfigFields(formState: WorksheetFormState): Omit<
  AdditionConfigV4,
  'version'
> & {
  seed?: number
  prngAlgorithm?: string
} {
  // Blacklist approach: Exclude only derived/ephemeral fields
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const { rows, total, date, ...persistedFields } = formState

  // Ensure prngAlgorithm has a default (critical for reproducibility)
  const config = {
    ...persistedFields,
    prngAlgorithm: persistedFields.prngAlgorithm ?? 'mulberry32',
  }

  console.log('[extractConfigFields] Extracted config:', {
    fieldCount: Object.keys(config).length,
    seed: config.seed,
    prngAlgorithm: config.prngAlgorithm,
    pages: config.pages,
    problemsPerPage: config.problemsPerPage,
    mode: (config as any).mode,
    operator: (config as any).operator,
    displayRules: (config as any).displayRules,
    additionDisplayRules: (config as any).additionDisplayRules,
    subtractionDisplayRules: (config as any).subtractionDisplayRules,
    excludedFields: ['rows', 'total', 'date'],
  })

  return config as Omit<AdditionConfigV4, 'version'> & {
    seed?: number
    prngAlgorithm?: string
  }
}