Skip to content

Markdown parsing fails on Windows (CRLF) and slugify doesn't support non-Latin characters #38

@baslie

Description

@baslie

Problem

Product roadmap data from product-roadmap.md doesn't display on the site — shows "No roadmap defined yet" EmptyState even when the file contains valid content.

Root Causes

1. Windows CRLF line endings break regex matching

The regex patterns in markdown parsers use \n for line breaks, but files saved on Windows use \r\n:

// product-loader.ts:126
const sectionMatches = [...md.matchAll(/### (\d+)\.\s*(.+)\n+([\s\S]*?)(?=\n### |\n## |\n#[^#]|$)/g)]

This pattern fails to match sections when the file has CRLF line endings.

2. slugify() doesn't support Cyrillic (and other non-Latin) characters

// product-loader.ts:28-34
function slugify(str: string): string {
  return str
    .toLowerCase()
    .replace(/\s+&\s+/g, '-and-')
    .replace(/[^a-z0-9]+/g, '-')  // ← Removes ALL non-Latin characters!
    .replace(/(^-|-$)/g, '')
}

For Cyrillic input like "Главная страница", this returns an empty string "", breaking section IDs and routing.

Affected Files

  • src/lib/product-loader.tsparseProductOverview, parseProductRoadmap, slugify
  • src/lib/data-model-loader.tsparseDataModel
  • src/lib/shell-loader.tsparseShellSpec
  • src/lib/section-loader.tsparseSpec

Proposed Solution

1. Normalize line endings before parsing

function normalizeLineEndings(text: string): string {
  return text.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
}

Add md = normalizeLineEndings(md) at the start of each parse function.

2. Add transliteration to slugify()

const cyrillicToLatin = {
  'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd',
  'е': 'e', 'ё': 'yo', 'ж': 'zh', 'з': 'z', 'и': 'i',
  // ... full map
}

function transliterate(str: string): string {
  return str.split('').map(char => {
    const lower = char.toLowerCase()
    return cyrillicToLatin[lower] ?? char
  }).join('')
}

function slugify(str: string): string {
  return transliterate(str)
    .toLowerCase()
    .replace(/\s+&\s+/g, '-and-')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/(^-|-$)/g, '')
}

Expected Results

Input Output
"Главная страница" glavnaya-stranitsa
"Каталог проектов" katalog-proektov
"Invoice Management" invoice-management

Environment

  • OS: Windows 10
  • Node: v20+
  • Vite: 7.x

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions