Usable Docs Auto-Migration Template - Fragment Type to Docs Path Mapping
Simple, deterministic template for automatically publishing Usable fragments to the docs site. 1:1 mapping between Usable and docs for intuitive navigation.
๐ฏ Core Concept
Fragment Type Name = Docs Directory Name
No translation, no mapping - what you see in Usable is what you get in the docs.
๐ Fragment Type โ Directory (1:1 Mapping)
| Fragment Type | Docs Directory | URL Path |
|---|---|---|
| Knowledge | /knowledge/ | /knowledge/... |
| Recipe | /recipe/ | /recipe/... |
| Solution | /solution/ | /solution/... |
| Template | /template/ | /template/... |
๐ท๏ธ Required Tags
Publish Control
status:published # Must have to publish to docsrepo:usable-docs # Associates with docs projectPath Structure (using first non-system tag)
discord-bots # First tag becomes subdirectoryOr use a dedicated slug tag for full control:
slug:discord-bots/build-your-first-bot๐ Path Generation Logic (N8N JS)
// Simple 1:1 path generationfunction generateDocsPath(fragment) { const { type, title, tags } = fragment;
// 1. Check if should publish const shouldPublish = tags.includes('status:published') && tags.includes('repo:usable-docs'); if (!shouldPublish) return null;
// 2. Use fragment type name directly as directory const baseDir = type.toLowerCase(); // "Recipe" โ "recipe"
// 3. Extract slug or build from tags const slugTag = tags.find(t => t.startsWith('slug:')); let path;
if (slugTag) { // Use explicit slug: "slug:discord-bots/build-bot" path = slugTag.replace('slug:', ''); } else { // Build from first non-system tag + title const nonSystemTags = tags.filter(t => !t.startsWith('status:') && !t.startsWith('repo:') && !t.startsWith('slug:') && !['experimental', 'deprecated'].includes(t) );
const subdirectory = nonSystemTags[0] || ''; const slug = title .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '');
path = subdirectory ? `${subdirectory}/${slug}` : slug; }
// 4. Generate full file path return `src/content/docs/${baseDir}/${path}.md`;}๐ Examples
Example 1: Recipe
Fragment:
- Type: Recipe
- Title: โHow to Build a Discord Botโ
- Tags:
["discord-bots", "status:published", "repo:usable-docs"]
Generated Path: /recipe/discord-bots/how-to-build-a-discord-bot.md
URL: /recipe/discord-bots/how-to-build-a-discord-bot
Example 2: Knowledge
Fragment:
- Type: Knowledge
- Title: โWhat is MCP?โ
- Tags:
["mcp", "status:published", "repo:usable-docs"]
Generated Path: /knowledge/mcp/what-is-mcp.md
URL: /knowledge/mcp/what-is-mcp
Example 3: Solution
Fragment:
- Type: Solution
- Title: โFix CORS Errors in Next.jsโ
- Tags:
["nextjs", "status:published", "repo:usable-docs"]
Generated Path: /solution/nextjs/fix-cors-errors-in-nextjs.md
URL: /solution/nextjs/fix-cors-errors-in-nextjs
Example 4: Template
Fragment:
- Type: Template
- Title: โReact Authentication Hookโ
- Tags:
["react", "status:published", "repo:usable-docs"]
Generated Path: /template/react/react-authentication-hook.md
URL: /template/react/react-authentication-hook
Example 5: Custom Slug
Fragment:
- Type: Recipe
- Title: โComplete MCP Setup Guideโ
- Tags:
["slug:mcp/quickstart", "status:published", "repo:usable-docs"]
Generated Path: /recipe/mcp/quickstart.md
URL: /recipe/mcp/quickstart
๐จ Badge Calculation
function calculateBadges(fragment) { const badges = []; const { tags, createdAt, updatedAt } = fragment;
// Manual badges from tags if (tags.includes('experimental')) badges.push('experimental'); if (tags.includes('deprecated')) badges.push('deprecated');
// Auto-calculated time-based badges const now = Date.now(); const daysSinceCreation = (now - new Date(createdAt)) / (1000 * 60 * 60 * 24); const daysSinceUpdate = (now - new Date(updatedAt)) / (1000 * 60 * 60 * 24);
if (daysSinceCreation <= 30) badges.push('new'); if (daysSinceUpdate <= 7 && daysSinceCreation > 7) badges.push('updated');
return badges;}๐ Frontmatter Generation
function generateFrontmatter(fragment, badges) { return `---title: "${fragment.title}"description: "${fragment.summary || ''}"fragmentId: "${fragment.id}"fragmentType: "${fragment.type}"author: "${fragment.author}"createdAt: "${fragment.createdAt}"updatedAt: "${fragment.updatedAt}"badges: ${JSON.stringify(badges)}---
${fragment.content}`;}๐ Quick Guide for Authors
Minimum Required Tags
["your-topic", "status:published", "repo:usable-docs"]Fragment Type = Directory
- Create a Recipe in Usable โ appears in
/recipe/in docs - Create Knowledge in Usable โ appears in
/knowledge/in docs - Create a Solution in Usable โ appears in
/solution/in docs - Create a Template in Usable โ appears in
/template/in docs
What you see in Usable is what you get in docs!
๐ฏ Path Examples
Recipe + ["discord-bots"] + โBuild a Botโ
โ /recipe/discord-bots/build-a-bot.md
Knowledge + ["mcp"] + โWhat is MCPโ
โ /knowledge/mcp/what-is-mcp.md
Solution + ["nextjs"] + โFix CORSโ
โ /solution/nextjs/fix-cors.md
Template + ["react"] + โAuth Hookโ
โ /template/react/auth-hook.md
โจ Benefits of 1:1 Mapping
โ
Intuitive - Same names everywhere
โ
No mental mapping - Recipe in Usable = /recipe/ in docs
โ
Consistent experience - Users donโt get confused
โ
Simple code - Just type.toLowerCase()
โ
Easy to explain - โFragment types become directoriesโ
๐ Astro Config Update
Update sidebar in astro.config.mjs:
sidebar: [ { label: 'Knowledge', autogenerate: { directory: 'knowledge' }, }, { label: 'Recipe', autogenerate: { directory: 'recipe' }, }, { label: 'Solution', autogenerate: { directory: 'solution' }, }, { label: 'Template', autogenerate: { directory: 'template' }, },]1:1 mapping = zero mental overhead! ๐ฏ