PROJECT NAME PATH DUPLICATION FIX
Comprehensive documentation system guides
Project Name Path Duplication Fix
Date: October 29, 2025
Time: Evening Session
Status: ✅ FIXED
Purpose: Resolve path duplication issue where project name was being included twice in file paths
🐛 The Problem
Error Symptoms
HEAD http://localhost:5010/docs/english/Architecture/Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE.mdc 429 (Too Many Requests)
Root Cause
In the project-scoped architecture, URLs include the project name as the first segment:
- URL:
/Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE - Path after stripping
/:Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE
However, PathResolutionService.getEffectiveDocsPath() already includes the project name in the base path:
- Base path:
docs/english/Architecture
When MarkdownService.generatePossiblePaths() was called with the full path including project name, it resulted in:
- Final path:
docs/english/Architecture+Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE=docs/english/Architecture/Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE
This caused:
- 404 errors for non-existent files
- Circuit breaker activation due to too many failed requests
- Content not rendering because files couldn't be loaded
✅ The Solution
Implementation
Modified three methods in MarkdownService to strip the project name from the path before calling generatePossiblePaths():
loadContent()- Regular content loadingdetectFileType()- File type detection (.md vs .mdc)loadMDCContent()- MDC-specific content loading
File: apps/docs-angular/src/app/services/markdown.service.ts
loadContent(path: string, language: 'en' | 'es' = 'en'): Observable<MarkdownContent> {
// Remove leading slash if present
let cleanPath = path.startsWith('/') ? path.slice(1) : path;
// ... (special route checks) ...
// 🎯 CRITICAL FIX: Strip project name from path to prevent duplication
// In project-scoped architecture, URLs include project name (e.g., /Architecture/ecosystem/file)
// But getEffectiveDocsPath() already includes project name in base path (e.g., docs/english/Architecture)
// So we must strip project name from cleanPath before calling generatePossiblePaths
const currentProject = this.store.selectSnapshot(ProjectSelectionState.currentProjectMetadata);
if (currentProject && cleanPath.startsWith(`${currentProject.name}/`)) {
const pathWithoutProject = cleanPath.substring(currentProject.name.length + 1);
console.log(`🔧 Stripped project name from path: "${cleanPath}" → "${pathWithoutProject}"`);
cleanPath = pathWithoutProject;
}
// ... (rest of the method) ...
}
// 🎯 SAME FIX IN detectFileType()
private detectFileType(path: string, language: string): Observable<'md' | 'mdc'> {
let cleanPath = path.startsWith('/') ? path.slice(1) : path;
// 🎯 CRITICAL FIX: Strip project name from path to prevent duplication
// (Same logic as in loadContent method)
const currentProject = this.store.selectSnapshot(ProjectSelectionState.currentProjectMetadata);
if (currentProject && cleanPath.startsWith(`${currentProject.name}/`)) {
const pathWithoutProject = cleanPath.substring(currentProject.name.length + 1);
console.log(`🔧 detectFileType: Stripped project name from path: "${cleanPath}" → "${pathWithoutProject}"`);
cleanPath = pathWithoutProject;
}
// ... (rest of the method) ...
}
// 🎯 SAME FIX IN loadMDCContent()
private loadMDCContent(path: string, language: string): Observable<MDCContentResult> {
let cleanPath = path.startsWith('/') ? path.slice(1) : path;
// 🎯 CRITICAL FIX: Strip project name from path to prevent duplication
// (Same logic as in loadContent and detectFileType methods)
const currentProject = this.store.selectSnapshot(ProjectSelectionState.currentProjectMetadata);
if (currentProject && cleanPath.startsWith(`${currentProject.name}/`)) {
const pathWithoutProject = cleanPath.substring(currentProject.name.length + 1);
console.log(`🔧 loadMDCContent: Stripped project name from path: "${cleanPath}" → "${pathWithoutProject}"`);
cleanPath = pathWithoutProject;
}
// ... (rest of the method) ...
}
Why All Three Methods Need the Fix
The path resolution happens in three different code paths, each calling generatePossiblePaths():
loadContent()- Called when loading regular Markdown filesdetectFileType()- Called independently to determine file extension before loadingloadMDCContent()- Called specifically when loading.mdcfiles (component-rich documentation)
All three must strip the project name because they all call generatePossiblePaths(), which combines the path with the already-project-scoped basePath.
How It Works
- URL Parsing:
/Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE→Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE - Project Detection: Get current project metadata from NGXS store →
{ name: 'Architecture', ... } - Project Name Stripping: Check if path starts with
Architecture/→ strip it →ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE - Path Resolution:
docs/english/Architecture+ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE=docs/english/Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE✅
Before and After
Before (❌ Duplicated):
URL: /Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE
↓
cleanPath: Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE
↓
basePath: docs/english/Architecture
↓
finalPath: docs/english/Architecture/Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE ❌
After (✅ Correct):
URL: /Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE
↓
cleanPath: Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE
↓
Strip project name: ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE
↓
basePath: docs/english/Architecture
↓
finalPath: docs/english/Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE ✅
🧪 Testing
Expected Results
- ✅ No path duplication in file requests
- ✅ Files load correctly from proper paths
- ✅ No circuit breaker activations
- ✅ Content renders successfully
- ✅ Console logs show correct path stripping in all three methods:
🔧 Stripped project name from path: "Architecture/..." → "..."🔧 detectFileType: Stripped project name from path: "Architecture/..." → "..."🔧 loadMDCContent: Stripped project name from path: "Architecture/..." → "..."
Test Cases
// Test 1: Architecture project file
URL: /Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE
Expected: docs/english/Architecture/ecosystem/ECOSYSTEM-INTEGRATION-COMPLETE.mdc ✅
// Test 2: GoGrab-Space project file
URL: /GoGrab-Space/libs/icons/index
Expected: docs/english/GoGrab-Space/libs/icons/index.mdc ✅
// Test 3: Public project file
URL: /getting-started
Expected: docs/english/public/getting-started.md ✅
// Test 4: Root index
URL: /Architecture
Expected: docs/english/Architecture/index.mdc ✅
🔗 Related Issues
Previous Fix Attempts
- Path Duplication Fix v1 (
INFINITE_LOOP_PATH_DUPLICATION_FIX.md): Fixed duplication inSearchServiceduring indexing - Extension Detection Fix (
LOADCONTENT_EXTENSION_DETECTION_FIX.md): Fixed.mdvs.mdcextension handling - Circuit Breaker Fix (
CIRCUIT_BREAKER_FIX.md): Added emergency circuit breaker for runaway requests
Why This Fix Was Needed
The previous fixes addressed search indexing and file type detection, but didn't address the root cause in MarkdownService.loadContent() where the path was being passed with the project name included.
This fix is the final piece of the path resolution puzzle, ensuring that:
- URLs can include project names for routing
- Base paths include project context for scoping
- File paths are correctly constructed without duplication
📚 Architecture Context
Project-Scoped URL Architecture
In the new architecture:
- URL structure:
/{projectName}/{path}(e.g.,/Architecture/ecosystem/file) - Base path structure:
docs/{language}/{projectName}(e.g.,docs/english/Architecture) - File path structure:
{basePath}/{relativePath}(e.g.,docs/english/Architecture/ecosystem/file.mdc)
Key Insight
URLs and base paths both include the project name, so we must strip it from the URL path before combining with the base path.
🎯 Impact
Before Fix
- ❌ 429 errors (Too Many Requests)
- ❌ Circuit breaker activations
- ❌ Content not rendering
- ❌ Path duplication:
docs/english/Architecture/Architecture/...
After Fix
- ✅ Correct file paths
- ✅ Successful content loading
- ✅ No circuit breaker activations
- ✅ Content renders properly
- ✅ Clean console logs with path stripping confirmation
📝 Next Steps
- Hard refresh browser to clear caches
- Reset circuit breaker if needed (see below)
- Navigate to Architecture project file
- Verify console logs show path stripping:
🔧 Stripped project name from path: "Architecture/..." → "..." - Verify file loads successfully with correct path
- Test other projects (GoGrab-Space, GoGrab-Pulse, etc.)
🔧 Circuit Breaker Reset (if needed)
If you see Status: 429 (Too Many Requests) errors, the circuit breaker is blocking requests due to previous failures. To reset it:
In Browser Console:
window.resetCircuitBreaker()
This will clear all circuit breaker counters and request caches, allowing requests to proceed normally.
✅ Status
COMPLETE - This fix resolves the path duplication issue in the content loading pipeline by applying the same path stripping logic to all three content loading methods.
NOTE: This fix was necessary but not sufficient on its own. After implementing this fix, we discovered that HTTP HEAD requests were being rate-limited (429 errors). The final solution was to use manifest-based file type detection instead of HTTP requests. See MANIFEST_BASED_FILE_TYPE_DETECTION.md for the complete solution.
Implementation Details
- ✅ Added NGXS Store injection to
MarkdownService - ✅ Added project name stripping in
loadContent()method (regular Markdown loading) - ✅ Added project name stripping in
detectFileType()method (file type detection) - ✅ Added project name stripping in
loadMDCContent()method (MDC-specific loading) - ✅ All three methods now correctly strip project name before calling
generatePossiblePaths() - ✅ Console logging confirms path stripping in all three code paths