diff --git a/backend/src/Infrastructure/MathComps.Infrastructure/Storage/IFileUploader.cs b/backend/src/Infrastructure/MathComps.Infrastructure/Storage/IFileUploader.cs
index ba795f3..769e3b7 100644
--- a/backend/src/Infrastructure/MathComps.Infrastructure/Storage/IFileUploader.cs
+++ b/backend/src/Infrastructure/MathComps.Infrastructure/Storage/IFileUploader.cs
@@ -9,7 +9,7 @@ public interface IFileUploader
/// Uploads a local file to remote storage at the specified key.
///
/// Absolute path to the local file to upload.
- /// The storage key (e.g., "handouts/factorization/factorization.sk.pdf").
+ /// The storage key (e.g., "handouts/pdfs/factorization.sk.pdf").
/// A task representing the asynchronous upload operation.
Task UploadAsync(string localFilePath, string key);
}
diff --git a/backend/src/Tools/MathComps.Cli.Handouts/BuildCommand.cs b/backend/src/Tools/MathComps.Cli.Handouts/BuildCommand.cs
index 061596d..a0ade85 100644
--- a/backend/src/Tools/MathComps.Cli.Handouts/BuildCommand.cs
+++ b/backend/src/Tools/MathComps.Cli.Handouts/BuildCommand.cs
@@ -451,8 +451,8 @@ private static void CompileTexFile(
}
///
- /// Uploads a compiled handout PDF (main or skeleton) to remote storage, nesting it
- /// under the handout's slug folder so every artefact for one handout sits together.
+ /// Uploads a compiled handout PDF (main or skeleton) to remote storage under
+ /// the flat handouts/pdfs/ folder shared by every handout.
///
/// The .tex file whose corresponding PDF should be uploaded.
/// The directory containing the compiled PDFs.
@@ -471,8 +471,8 @@ private static async Task UploadHandoutPdfAsync(FileInfo texFile, DirectoryInfo
return;
}
- // Build the R2 key so every artefact for one handout lands in the same folder
- var r2Key = ToHandoutR2Key($"{ToHandoutSlug(texFile.Name)}/{pdfFileName}");
+ // All handout PDFs share the flat handouts/pdfs/ folder
+ var r2Key = ToHandoutR2Key($"pdfs/{pdfFileName}");
await fileUploader.UploadAsync(sourcePdfPath, r2Key);
AnsiConsole.MarkupLine($" [green]✓ PDF uploaded:[/] {Markup.Escape(pdfFileName)}");
}
diff --git a/backend/src/Tools/MathComps.Cli.Handouts/README.md b/backend/src/Tools/MathComps.Cli.Handouts/README.md
index 5703775..bbf9b01 100644
--- a/backend/src/Tools/MathComps.Cli.Handouts/README.md
+++ b/backend/src/Tools/MathComps.Cli.Handouts/README.md
@@ -10,7 +10,7 @@ For each matched `.tex` file, the tool runs these steps in order:
2. **Compile TeX** — runs the configured compiler (2 passes) on both main + skeleton files
3. **Parse to JSON** — converts the TeX document structure into `RawContentBlock[]` JSON (saved locally to `web/src/content/handouts/`)
4. **Upload images** — processes SVG images and uploads them to R2 under `handouts//.svg`, where `` is the language-stripped handout id (so all language variants share one image set)
-5. **Upload PDFs** — uploads compiled main + skeleton PDFs to R2 under `handouts//.pdf` (same folder as the images)
+5. **Upload PDFs** — uploads compiled main + skeleton PDFs to R2 under `handouts/pdfs/.pdf` (flat layout; every handout's PDFs share one folder)
## Prerequisites
diff --git a/web/src/components/features/problems/services/problem-api-urls.ts b/web/src/components/features/problems/services/problem-api-urls.ts
index 9211952..eed0096 100644
--- a/web/src/components/features/problems/services/problem-api-urls.ts
+++ b/web/src/components/features/problems/services/problem-api-urls.ts
@@ -24,17 +24,14 @@ export function getProblemImageUrl(contentId: string, type: ImageType): string {
}
/**
- * Builds a public URL to a handout PDF by its filename. The handout's
- * language-stripped slug is derived from the filename — both `..pdf`
- * and `.-skeleton.pdf` collapse to the same slug so every artefact
- * lives in one folder on R2.
+ * Builds a public URL to a handout PDF by its filename. All handout PDFs live
+ * together in the flat `handouts/pdfs/` folder on R2.
*
* @param filename - The PDF filename (e.g., "factorization.sk.pdf")
* @returns The public URL to the PDF on R2
*/
export function getHandoutPdfUrl(filename: string): string {
- const slug = filename.replace(/\.[a-z]{2}(-skeleton)?\.pdf$/i, '')
- return `${getR2BaseUrl()}/handouts/${slug}/${filename}`
+ return `${getR2BaseUrl()}/handouts/pdfs/${filename}`
}
/**