Integrate Concepts Collection into More-About Dynamic Routing

Summary

Implemented a unified dynamic routing system that renders both vocabulary and concepts collections through the same /more-about route, with dedicated index pages and consistent styling. Further refactored the implementation to use component-based architecture and semantic CSS classes.

Why Care

This enhancement provides a more comprehensive reference library for users, allowing them to access both vocabulary terms and conceptual frameworks through a consistent interface. The implementation follows a modular approach that can be extended to support additional content collections in the future. The component-based refactoring improves code maintainability and establishes patterns for future development.

Implementation

Changes Made

We implemented the integration of the concepts collection into the more-about routing system in four main commits:
  1. Initial setup and configuration:
text
15af080 - prerun(concepts): add concepts to dyanamic routing. (41 minutes ago)
M       src/components/admin/RouteManager.astro
M       src/components/basics/Header.astro
M       src/content.config.ts
A       src/pages/more-about/[content-item].astro
A       src/pages/more-about/concepts.astro
A       src/pages/more-about/index.astro
A       src/pages/more-about/vocabulary.astro
M       src/utils/routing/routeManager.ts
  1. Improved routing with catch-all pattern:
text
0cc76d9 - iterate(concepts): second iteration fixes page names (27 minutes ago)
M       src/layouts/Layout.astro
R055    src/pages/more-about/[content-item].astro       src/pages/more-about/[...slug].astro
  1. UI refinements and styling improvements:
text
4ddb89b - works(reference): reference page now working. (10 minutes ago)
M       src/pages/more-about/concepts.astro
M       src/pages/more-about/index.astro
M       src/pages/more-about/vocabulary.astro
  1. Component refactoring and CSS improvements:
text
f8e2c7d - refactor(components): move functionality to dedicated components (5 minutes ago)
A       src/components/reference/ConceptPreviewCard.astro
A       src/components/reference/VocabularyPreviewCard.astro
M       src/pages/more-about/concepts.astro
M       src/pages/more-about/index.astro
M       src/pages/more-about/vocabulary.astro

Technical Details

Content Configuration

Added the concepts collection to src/content.config.ts to make Astro aware of the content in the /content/concepts/ directory:
typescript
// src/content.config.ts
const conceptsCollection = defineCollection({
  loader: glob({pattern: "**/*.md", base: "../content/concepts"}),
  schema: z.object({
    aliases: z.union([
      z.string().transform(str => [str]),
      z.array(z.string())
    ]).optional().default([])
  }).passthrough().transform((data, context) => {
    // Transform logic for generating titles and slugs
  })
});

// Add to collections export
export const collections = {
  // existing collections...
  'concepts': conceptsCollection
};

Dynamic Routing

Implemented a flexible catch-all route handler in src/pages/more-about/[...slug].astro that can handle both vocabulary and concepts entries:
javascript
// src/pages/more-about/[...slug].astro
// Helper function to convert filename to proper case
function toProperCase(str) {
  return str
    .replace(/[-_]/g, ' ')
    .split(' ')
    .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
    .join(' ');
}

// Function to find an entry by slug in a collection
async function findEntryBySlug(collection, slug) {
  const entries = await getCollection(collection);
  return entries.find(entry => {
    const entrySlug = entry.data.slug || entry.id.replace(/\.md$/, '').toLowerCase().replace(/\s+/g, '-');
    return entrySlug === slug;
  });
}

// Try to find the entry in both collections
let entry;
let collection;

// First check vocabulary, then check concepts
// ...

Index Pages

Created three index pages with consistent styling and navigation:
  1. Main index page (src/pages/more-about/index.astro) showing both collections
  2. Vocabulary index (src/pages/more-about/vocabulary.astro)
  3. Concepts index (src/pages/more-about/concepts.astro)
All pages use a consistent card-based layout with compact styling:
javascript
// Styling improvements in all index pages
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
  {entries.map(entry => (
    <div class="bg-gray-800 p-4 rounded-lg">
      <h3 class="text-sm font-bold mb-2">
        <a href={`/more-about/${entry.data.slug || entry.id.replace(/\.md$/, '').toLowerCase().replace(/\s+/g, '-')}`} class="text-blue-400 hover:underline">
          {entry.data.title}
        </a>
      </h3>
      <p class="text-xs text-gray-300">
        {entry.data.description || "Learn more about this concept..."}
      </p>
    </div>
  ))}
</div>

Component Refactoring

Refactored the implementation to use component-based architecture, introducing dedicated components for concept and vocabulary preview cards:
javascript
// src/components/reference/VocabularyPreviewCard.astro
<div class="vocabulary-card">
  <h3 class="vocabulary-card__title">
    <a href={url} class="vocabulary-card__link">
      {entry.data.title}
    </a>
  </h3>
  {entry.data.aliases && entry.data.aliases.length > 0 && (
    <p class="vocabulary-card__aliases">
      Also known as: {entry.data.aliases.join(', ')}
    </p>
  )}
</div>

// src/components/reference/ConceptPreviewCard.astro
<div class="concept-card">
  <h3 class="concept-card__title">
    <a href={url} class="concept-card__link">
      {entry.data.title}
    </a>
  </h3>
  <p class="concept-card__description">
    {entry.data.description || "Learn more about this concept..."}
  </p>
</div>

CSS Improvements

Improved CSS architecture by replacing Tailwind utility classes with semantic BEM-style classes and leveraging project CSS variables:
css
/* VocabularyPreviewCard.astro <style> section */
.vocabulary-card {
  background-color: var(--clr-lossless-primary-dark, #19141D);
  padding: 1rem;
  border-radius: 0.5rem;
}

.vocabulary-card__title {
  font-size: 1rem; /* text-base */
  font-weight: 400;
  margin-bottom: 0.5rem;
}

.vocabulary-card__link {
  color: var(--clr-lossless-accent--brightest, rgb(4, 229, 229));
}

.vocabulary-card__link:hover {
  text-decoration: underline;
}

.vocabulary-card__aliases {
  font-size: 0.75rem;
  font-weight: 300;
  color: var(--white--pure--40p, hsla(0, 0%, 100%, .4));
  margin-bottom: 0.5rem;
}
This approach:
  • Improves code readability with semantic class names
  • Establishes a pattern for component-specific styling
  • Leverages the project's CSS variable system
  • Provides comprehensive comments for future developers
  • Creates a single source of truth for component styles

Integration Points

Title Generation

Implemented a robust title generation system that:
  • Extracts titles from frontmatter when available
  • Falls back to generating titles from filenames using proper case formatting
  • Handles hyphens, underscores, and other special characters in filenames
javascript
// Title generation logic in all index pages
if (!entry.data.title) {
  const filename = entry.id.replace(/\.md$/, '');
  const filenameParts = filename.split('/');
  const baseFilename = filenameParts[filenameParts.length - 1];
  entry.data.title = toProperCase(baseFilename);
}

URL Generation

Added fallback mechanisms for entries without explicit slugs, using the filename as the basis for the URL path:
javascript
href={`/more-about/${entry.data.slug || entry.id.replace(/\.md$/, '').toLowerCase().replace(/\s+/g, '-')}`}

Type Safety

Used type assertions to handle TypeScript errors until Astro regenerates types for new collections:
javascript
// Type assertion in index.astro
const conceptsEntries = await getCollection('concepts' as any) as any[];

Documentation

The implementation is documented in the updated prompt: /content/lost-in-public/prompts/render-logic/Integrate-Concepts-into-More-About.md
This prompt now includes:
  • Detailed implementation insights
  • Code examples for each component
  • Data flow diagrams
  • Implementation architecture
  • Summary of implementation steps