Files
mcpedia/.hermes/plans/hierarchical-folders.md
T
asepharyana ab3a2dc456
CI / typecheck + build (turbo) (push) Canceled after 0s
feat: Phase 14 — hierarchical folder structure (GitHub-style nested folders)
- Section index pages ([section]/page.tsx): shows folder tree + flat doc list
- Folder index pages: [section]/[...slug]/page.tsx detects folder paths
  and renders subfolder + document listing instead of 404
- Sidebar tree: hierarchical grouping from flat doc slugs, folder icons (📁)
  with proper indentation per depth level
- DocForm v2: parent folder dropdown (populated from existing folders),
  slug input for leaf name, resolved path display
- Core helpers: extractFoldersForSection + classifyPath exported from @mcpedia/core
- CTF writeup reorganized: pwn-100 ret2win moved into pwn/ subfolder
- _index.md folder intro pages for ctf/ and defcon-quals-2024/
- STANDARD_KEYS includes extraFields to avoid badge duplication
2026-08-21 08:37:41 +07:00

6.5 KiB

Phase 14 — Hierarchical Folder Structure

User: "gk ada bedanya, maksud saya inginnya itu bisa yg bertingkat seperti github yg memiliki folder dalam folder" Context: after the full dynamic-custom-fields overhaul (Phase 13), the user wants document URLs/content organized in nested folders like GitHub — writeups/ctf/defcon-quals-2024/pwn-100/... with subfolders under subfolders, not just one level deep.

Problem

The current URL scheme is /<section>/<slug> where slug can contain / (e.g. writeups/ctf/defcon-quals-2024/pwn-100-ret2win-alignment → /writeups/ctf/defcon-quals-2024/pwn-100-ret2win-alignment). This works for files but there are no folder-level index pages — navigating to /writeups/ctf/defcon-quals-2024/ returns 404 because Next.js catch-all [section]/[...slug]/page.tsx requires at least one slug segment beyond the section, and the sidebar only shows flat doc titles (no folder tree).

GitHub's model: github.com/org/repo/tree/main/path/to/folder/file — every folder has an index page (/path/to/folder/) listing its contents.

Solution

1. Folder Index Pages

Create apps/web/app/[section]/[...slug]/folder.tsx (or a parallel route). Actually — cleaner approach per Next.js App Router: the catch-all [section]/[...slug]/page.tsx handles both. Add logic: if the slug resolves to an actual markdown file → doc page (existing behavior). If the slug resolves to a directory (folder of docs) → render a folder index listing all docs whose path starts with that prefix.

Mechanism:

  • Call listDocuments() to get all docs.
  • The incoming URL path is {section}/{...slug}.
  • Build the "folder prefix" = ${section}/${slug.join("/")}/ (with trailing /, or just ${section}/${slug.join("/")} if no slug segments).
  • Filter docs whose doc.path starts with that prefix.
  • If exactly one doc matches AND its path === prefix (trimmed .md) → it's a doc page (existing). If zero or multiple match and they all start with the prefix → it's a folder index.
  • Edge: a folder with exactly one doc whose path matches exactly — still a doc page. A folder is when there are docs at prefix/sub/....

Better heuristic: A slug path is a "folder" if there exist docs whose path is prefix/deep/... (i.e., the slug is a parent of other doc paths, not a leaf itself). A slug is a "leaf doc" if path === prefix + ".md".

2. Sidebar Tree

Update Sidebar.tsx:

  • listDocuments() already returns all docs with their full slug and path.
  • Build a tree from the flat list: split each slug by /, create nested folder nodes.
  • Render nested <ul> with indentation (already done via marginLeft based on depth).
  • Folder nodes (collapsed/expanded) get a folder icon 📁 and a CSS class.
  • Clicking a folder → navigates to the folder index page /{section}/{path}.
  • Leaf doc nodes → link to /{doc.slug} (existing behavior).
  • Group by the first segment after section too (e.g. ctf/defcon-quals-2024/ is a folder, then pwn-100-... are children).

Tree-building algorithm (from flat slugs):

For slug "writeups/ctf/defcon-quals-2024/pwn-100-ret2win-alignment":
  parts = ["writeups", "ctf", "defcon-quals-2024", "pwn-100-ret2win-alignment"]
  → tree: writeups → ctf → defcon-quals-2024 → pwn-100-ret2win-alignment (leaf)

3. DocForm / Folder Selection

Update DocForm.tsx:

  • Add a "Parent folder" input (autocomplete or text) that shows existing folders for the selected section. The slug field already supports / but the user experience is better with folder picker.
  • When creating, the slug becomes {parentFolder}/{slug} automatically.
  • Show existing folder structure as <select> or tree picker.

4. Example Hierarchy

Create a real hierarchical structure to demonstrate:

writeups/
  ctf/
    defcon-quals-2024/
      pwn/
        pwn-100-ret2win-alignment.md
        pwn-200-bof-heap.md
      crypto/
        crypto-100-xor.md
        crypto-200-rsa.md
      template/
        writeup-template.md
      _index.md              ← folder index (optional intro)
    hackthebox/
      machine-name/
        walkthrough.md

For now, reorganize the existing Defcon writeup into proper subfolders + add a folder index page. The existing content/writeups/ctf/defcon-quals-2024/ is already a folder — just need the folder index route to work.

5. Route Changes

Current: [section]/[...slug]/page.tsx — catch-all requires ≥1 slug segment.

  • /writeups → notFound() (no index for bare section unless we add one)
  • /writeups/ctf → catch-all gets slug=["ctf"] → currently treated as a doc (looks up writeups/ctf doc, 404 if none)
  • /writeups/ctf/defcon-quals-2024/ → catch-all slug=["ctf","defcon-quals-2024"]

Plan:

  1. Add /writeups/page.tsx (section index) — lists top-level folders + root docs in that section. (Currently /docs/page.tsx exists but /writeups/page.tsx doesn't.)
  2. In [section]/[...slug]/page.tsx: at the top of the page component, check if the slug path is a folder (has child docs). If so, render folder index instead of doc page.

Section index pages: Create [section]/page.tsx for all 4 sections, or a generic one. Currently only /docs/page.tsx exists. Add a shared SectionIndex component.

6. Files to Change

new:    apps/web/app/[section]/page.tsx               # generic section index (folder + doc listing)
mod:    apps/web/app/[section]/[...slug]/page.tsx     # add folder-index detection
mod:    apps/web/app/components/Sidebar.tsx            # tree from flat slugs
mod:    apps/web/app/components/DocForm.tsx            # parent folder picker
new:    content/writeups/ctf/defcon-quals-2024/_index.md  # folder intro (optional)
new:    content/writeups/ctf/_index.md                # CTF section intro
mod:    apps/web/app/docs/page.tsx                    # may need generic version

7. Verification

  • /writeups → 200, shows folders: ctf/, template/
  • /writeups/ctf → 200, folder index listing defcon-quals-2024/
  • /writeups/ctf/defcon-quals-2024 → 200, folder index listing pwn-100-...
  • /writeups/ctf/defcon-quals-2024/pwn-100-ret2win-alignment → 200, doc page
  • Sidebar shows nested tree with folder icons
  • DocForm parent folder picker works
  • bun run test green, turbo run typecheck green
  • Live verification via curl

Constraints

  • User: "pastikan semua dinamis dan rapih untuk banyak situasi jadi tergantung user bukan hardcode" — folder detection must be content-driven, not config.
  • No breaking existing flat slugs.
  • Section icons/labels stay the same.