Skip to content

Commit 6b2e005

Browse files
iammukeshmclaude
andcommitted
feat(docs): FolderTree MDX component; use it on modular-monolith
Replaces the ASCII folder-structure code block with a themed, responsive tree: indent guides, folder/file icons, dotted leaders to annotations, brand-tinted badges and library chips. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 5723bea commit 6b2e005

5 files changed

Lines changed: 292 additions & 55 deletions

File tree

CLAUDE.md

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
55
## What this is
66

77
The documentation site for the FullStackHero .NET starter kit, served at `fullstackhero.net`.
8-
It is **not** the starter kit itself only its docs. Built with Astro 6, Tailwind 4, MDX, and
8+
It is **not** the starter kit itself - only its docs. Built with Astro 6, Tailwind 4, MDX, and
99
deployed as a Cloudflare Worker that wraps a static build.
1010

1111
## Commands
@@ -14,13 +14,13 @@ deployed as a Cloudflare Worker that wraps a static build.
1414
npm run dev # astro dev on http://localhost:4321 (hot-reload of MDX)
1515
npm run build # astro build && pagefind --site dist (produces dist/)
1616
npm run preview # serve dist/ locally
17-
npm run check # astro check type + content-schema validation
17+
npm run check # astro check - type + content-schema validation
1818
```
1919

20-
- There is **no test suite**. `npm run check` is the closest thing to a CI gate run it after
20+
- There is **no test suite**. `npm run check` is the closest thing to a CI gate - run it after
2121
touching `.astro`, `.ts`, layouts, or content schema.
2222
- Search (Pagefind) only works after `npm run build`. In `dev`, the search modal reports
23-
"index not available" this is expected, not a bug.
23+
"index not available" - this is expected, not a bug.
2424

2525
### Cloudflare / Worker (deploy-time)
2626

@@ -37,7 +37,7 @@ binding IDs live in `wrangler.toml`.
3737

3838
This is the single most important thing to understand.
3939

40-
1. **Astro builds fully static** to `dist/`. There is no SSR adapter in `astro.config.mjs`
40+
1. **Astro builds fully static** to `dist/`. There is no SSR adapter in `astro.config.mjs` -
4141
every page, including the `llms-full.txt` endpoint, is prerendered at build time.
4242
2. **`src/worker.ts` is a standalone Cloudflare Worker** (not an Astro adapter). It serves
4343
`dist/` through the `ASSETS` binding and adds one dynamic surface: `/api/views/*`. Static
@@ -50,7 +50,7 @@ Consequences:
5050
- The page-view counter (`/api/views`) writes to D1 (`views` table, see `migrations/0001_init.sql`)
5151
and dedups per IP+UA via KV with a 1h TTL. Slugs are whitelisted to `/docs/*` only.
5252
- Anything dynamic must go through the worker; you cannot add an SSR Astro route and expect it
53-
to run the build is static.
53+
to run - the build is static.
5454

5555
## Architecture: content → routes → sidebar
5656

@@ -87,13 +87,14 @@ BaseLayout.astro <head>: SEO meta, OG/Twitter, JSON-LD graph, fonts, them
8787

8888
## MDX authoring
8989

90-
- Frontmatter shape is enforced by the schema in `src/content.config.ts` `check` fails the build
90+
- Frontmatter shape is enforced by the schema in `src/content.config.ts` - `check` fails the build
9191
on violations. `sidebar.order` is ascending (lower = higher in the list); ties break alphabetically.
9292
- **Only components registered in `src/components/mdx.ts` are usable inside MDX**: `Callout`,
93-
`CategoryIndex`, `CodeGroup`, `Screenshot`, `SectionIndex`. To expose a new one in MDX, add it there.
93+
`CategoryIndex`, `CodeGroup`, `Faq`, `FolderTree`, `Screenshot`, `SectionIndex`. To expose a new
94+
one in MDX, add it there.
9495
- The docs `<article>` carries `data-pagefind-body`, which scopes the search index to docs content only.
9596

96-
## SEO surface (this site invests heavily here don't regress it)
97+
## SEO surface (this site invests heavily here - don't regress it)
9798

9899
- `astro.config.mjs` `sitemap.serialize()` assigns per-path `priority`/`changefreq` by URL prefix.
99100
- `public/robots.txt` explicitly allow-lists AI + search crawlers (GPTBot, ClaudeBot, PerplexityBot, …).
@@ -104,7 +105,7 @@ BaseLayout.astro <head>: SEO meta, OG/Twitter, JSON-LD graph, fonts, them
104105
## Styling
105106

106107
- Tailwind 4 via the Vite plugin (no `tailwind.config.js`). Tokens, prose, and code-block themes
107-
in `src/styles/*.css` are **forked verbatim from `codewithmukesh/blog`** preserve the provenance
108+
in `src/styles/*.css` are **forked verbatim from `codewithmukesh/blog`** - preserve the provenance
108109
comments when editing.
109110
- Brand colors are split deliberately: primary is `#15803d` (green-700); the brighter brand green
110111
`#16a34a` lives in `--primary-soft` for accents/gradients. Don't collapse the two.
@@ -113,7 +114,7 @@ BaseLayout.astro <head>: SEO meta, OG/Twitter, JSON-LD graph, fonts, them
113114

114115
## Client scripts & Astro view transitions
115116

116-
- Inline `<script is:inline>` (e.g. theme bootstrap, Analytics) is shipped verbatim Astro does not
117+
- Inline `<script is:inline>` (e.g. theme bootstrap, Analytics) is shipped verbatim - Astro does not
117118
bundle or typecheck it. Bundled client logic uses `<script>import '...'</script>`.
118119
- The site uses view transitions, so interactive scripts must be **idempotent and re-bind on
119120
`astro:page-load` / `astro:after-swap`** (see `views-counter.ts`, the mobile sheet in
@@ -122,6 +123,6 @@ BaseLayout.astro <head>: SEO meta, OG/Twitter, JSON-LD graph, fonts, them
122123
## Conventions
123124

124125
- Formatting is Prettier with `prettier-plugin-astro` + `prettier-plugin-tailwindcss` (Tailwind class
125-
sorting is automatic). No standalone config file plugin defaults apply.
126+
sorting is automatic). No standalone config file - plugin defaults apply.
126127
- TypeScript extends `astro/tsconfigs/strict`; React JSX is enabled for `.tsx` islands.
127128
- `superpowers/` is gitignored and not part of the site.
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
/**
3+
* FolderTree - a styled repository/folder-structure explorer for MDX.
4+
*
5+
* Usage in MDX:
6+
* <FolderTree
7+
* root="src/"
8+
* caption="One repo · one process"
9+
* tree={[
10+
* { name: 'Modules/', note: 'Ten bounded contexts', children: [
11+
* { name: 'Identity/', children: [
12+
* { name: 'Modules.Identity.Contracts/', note: 'Public surface', highlight: true },
13+
* ]},
14+
* { name: 'Billing/', badge: '+ Contracts' },
15+
* ]},
16+
* { name: 'BuildingBlocks/', chips: ['Core', 'Web', 'Persistence'] },
17+
* ]}
18+
* />
19+
*
20+
* Replaces ASCII-art code blocks (├── └──) with a themed, responsive tree:
21+
* indent guides, folder/file icons, dotted leaders to annotations, and
22+
* brand-tinted badges/highlights. Node shape is documented in
23+
* FolderTreeNode.astro.
24+
*/
25+
import FolderTreeNode from './FolderTreeNode.astro';
26+
27+
interface TreeNode {
28+
name: string;
29+
note?: string;
30+
badge?: string;
31+
chips?: string[];
32+
highlight?: boolean;
33+
file?: boolean;
34+
children?: TreeNode[];
35+
}
36+
37+
interface Props {
38+
/** Root label shown in the header bar, e.g. "src/". */
39+
root?: string;
40+
/** Optional mono eyebrow on the right of the header bar (hidden on mobile). */
41+
caption?: string;
42+
tree: TreeNode[];
43+
}
44+
45+
const { root = '/', caption, tree } = Astro.props as Props;
46+
---
47+
48+
<figure class="not-prose my-8 overflow-hidden rounded-xl border border-border bg-card shadow-sm">
49+
<figcaption
50+
class="flex items-center justify-between gap-3 border-b border-border bg-subtle/50 px-4 py-2.5"
51+
>
52+
<span class="flex items-center gap-2 font-mono text-[12px] font-semibold text-foreground">
53+
<svg
54+
class="h-4 w-4 text-primary"
55+
width="16"
56+
height="16"
57+
viewBox="0 0 24 24"
58+
fill="none"
59+
stroke="currentColor"
60+
stroke-width="1.75"
61+
stroke-linecap="round"
62+
stroke-linejoin="round"
63+
aria-hidden="true"
64+
>
65+
<path
66+
d="m6 14 1.45-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6a2 2 0 0 1-1.94 1.5H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H18a2 2 0 0 1 2 2v2"
67+
/>
68+
</svg>
69+
{root}
70+
</span>
71+
{caption && (
72+
<span
73+
class="hidden font-mono text-[10.5px] font-medium uppercase tracking-[0.16em] text-muted-foreground sm:block"
74+
>
75+
{caption}
76+
</span>
77+
)}
78+
</figcaption>
79+
<ul class="m-0 list-none space-y-0.5 p-2 sm:p-3">
80+
{tree.map((node) => (
81+
<FolderTreeNode node={node} />
82+
))}
83+
</ul>
84+
</figure>
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
---
2+
/**
3+
* FolderTreeNode - one row of a <FolderTree>, recursing into children.
4+
* Internal to FolderTree.astro; not registered in mdx.ts directly.
5+
*/
6+
interface TreeNode {
7+
/** Display name, e.g. "Modules.Identity/". Trailing slash is cosmetic. */
8+
name: string;
9+
/** Muted annotation, right-aligned behind a dotted leader on sm+. */
10+
note?: string;
11+
/** Small brand-tinted pill after the name, e.g. "+ Contracts". */
12+
badge?: string;
13+
/** Compact pill list instead of full child rows (e.g. 11 libraries). */
14+
chips?: string[];
15+
/** Emphasize this row in brand green. */
16+
highlight?: boolean;
17+
/** Render a file icon instead of a folder icon. */
18+
file?: boolean;
19+
children?: TreeNode[];
20+
}
21+
22+
interface Props {
23+
node: TreeNode;
24+
}
25+
26+
const { node } = Astro.props as Props;
27+
const isFolder = !node.file;
28+
---
29+
30+
<li class="min-w-0">
31+
<div
32+
class:list={[
33+
'flex min-w-0 flex-wrap items-center gap-x-2 gap-y-0.5 rounded-md px-2 py-[5px] transition-colors hover:bg-subtle/60',
34+
node.highlight && 'bg-primary/5',
35+
]}
36+
>
37+
{isFolder ? (
38+
<svg
39+
class:list={['h-4 w-4 shrink-0', node.highlight ? 'text-primary' : 'text-primary/70']}
40+
width="16"
41+
height="16"
42+
viewBox="0 0 24 24"
43+
fill="none"
44+
stroke="currentColor"
45+
stroke-width="1.75"
46+
stroke-linecap="round"
47+
stroke-linejoin="round"
48+
aria-hidden="true"
49+
>
50+
<path
51+
d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"
52+
/>
53+
</svg>
54+
) : (
55+
<svg
56+
class="h-4 w-4 shrink-0 text-muted-foreground/70"
57+
width="16"
58+
height="16"
59+
viewBox="0 0 24 24"
60+
fill="none"
61+
stroke="currentColor"
62+
stroke-width="1.75"
63+
stroke-linecap="round"
64+
stroke-linejoin="round"
65+
aria-hidden="true"
66+
>
67+
<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" />
68+
<path d="M14 2v4a2 2 0 0 0 2 2h4" />
69+
</svg>
70+
)}
71+
<span
72+
class:list={[
73+
'font-mono text-[13px] font-medium',
74+
node.highlight ? 'text-primary' : 'text-foreground',
75+
]}
76+
>
77+
{node.name}
78+
</span>
79+
{node.badge && (
80+
<span
81+
class="rounded-full border border-primary/25 bg-primary/10 px-2 py-px font-mono text-[10px] font-semibold text-primary"
82+
>
83+
{node.badge}
84+
</span>
85+
)}
86+
{node.note && (
87+
<>
88+
<span
89+
class="hidden min-w-6 flex-1 border-t border-dashed border-border/80 sm:block"
90+
aria-hidden="true"></span>
91+
<span
92+
class="min-w-0 basis-full pl-6 text-[12px] leading-snug text-muted-foreground sm:basis-auto sm:pl-0 sm:text-right"
93+
>
94+
{node.note}
95+
</span>
96+
</>
97+
)}
98+
</div>
99+
100+
{node.chips && (
101+
<div class="mt-1 mb-1.5 ml-8 flex flex-wrap gap-1.5">
102+
{node.chips.map((chip) => (
103+
<span
104+
class="rounded-md border border-border bg-subtle/60 px-2 py-0.5 font-mono text-[11px] text-muted-foreground"
105+
>
106+
{chip}
107+
</span>
108+
))}
109+
</div>
110+
)}
111+
112+
{node.children && node.children.length > 0 && (
113+
<ul class="m-0 ml-4 list-none border-l border-border/70 pl-2.5">
114+
{node.children.map((child) => (
115+
<Astro.self node={child} />
116+
))}
117+
</ul>
118+
)}
119+
</li>

src/components/mdx.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Callout from './docs/Callout.astro';
22
import CategoryIndex from './docs/CategoryIndex.astro';
33
import CodeGroup from './docs/CodeGroup.astro';
44
import Faq from './docs/Faq.astro';
5+
import FolderTree from './docs/FolderTree.astro';
56
import Screenshot from './docs/Screenshot.astro';
67
import SectionIndex from './docs/SectionIndex.astro';
78

@@ -10,6 +11,7 @@ export const mdxComponents = {
1011
CategoryIndex,
1112
CodeGroup,
1213
Faq,
14+
FolderTree,
1315
Screenshot,
1416
SectionIndex,
1517
};

0 commit comments

Comments
 (0)