Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .idx/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Expert AI Engineering Protocol

## Clean Code & TypeScript Principles
When modifying or extending this framework, strictly adhere to these principles:

### 1. Robust Type Safety
- **No 'any'**: Avoid the use of 'any' at all costs. Use Zod schemas for all external data sources and configuration files.
- **Inference where possible**: Trust TypeScript's inference for local variables, but explicitly type exported constants and function parameters.
- **Exhaustive Checks**: Use discriminating unions and exhaustive 'switch' checks for themes, routes, and layout types.

### 2. Framework Modularity
- **Source of Truth**: The centralized 'src/config.ts' is the single source of truth for site-wide settings. Do not hardcode strings (site names, social URLs, paths) in components.
- **Feature Flags**: Respect all feature flags defined in 'CONFIG.features'. If a feature is disabled, the corresponding components should not render and routes should be guarded via middleware.
- **CSS Variable Overrides**: Design changes should primarily be handled through HSL variable overrides defined in 'CONFIG.theme.overrides'.

### 3. File Operations
- **Atomic Writes**: Always ensure configuration files are well-formed before writing.
- **Asset Integrity**: When changing PWA settings or site metadata, verify that the referenced assets (favicons, manifest icons) exist in the 'public/' directory.

### 4. Verification Workflow
- Every change must pass a full build ('npm run build') and all unit tests ('npm test').
- Frontend changes require visual verification via Playwright, ensuring consistent layout across different configured themes.
103 changes: 103 additions & 0 deletions FRAMEWORK_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Framework Conversion Plan (v2)

This document outlines the comprehensive strategy for transforming this repository into a reusable Astro blog framework, enhanced with senior-level architectural considerations for long-term maintainability.

## 1. Centralized Configuration & Validation

To ensure the framework is robust, we will implement a centralized configuration system using **Zod** for schema validation. This provides both TypeScript types and runtime error checking.

### Configuration Schema (`src/schemas/config.ts`)
```typescript
import { z } from 'zod';

export const FrameworkConfigSchema = z.object({
site: z.object({
title: z.string(),
description: z.string(),
author: z.string(),
logoText: z.string().optional(),
siteUrl: z.string().url(),
lang: z.string().default('en'),
social: z.record(z.string()).optional(),
}),
navigation: z.object({
header: z.array(z.object({ label: z.string(), href: z.string() })),
footer: z.array(z.object({ label: z.string(), href: z.string() })),
}),
features: z.object({
enableSearch: z.boolean().default(true),
enablePWA: z.boolean().default(true),
enableToolsPage: z.boolean().default(false),
enableRSS: z.boolean().default(true),
}),
theme: z.object({
active: z.enum(['newspaper', 'minimal', 'modern']).default('newspaper'),
overrides: z.record(z.string()).optional(),
}),
routing: z.object({
homepage: z.enum(['blog', 'tools']).default('blog'),
}),
assets: z.object({
favicon: z.string().default('/favicon.svg'),
defaultHeroImage: z.string().default('/placeholder-social.jpg'),
pwaIconsDir: z.string().default('/icons'),
})
});

export type FrameworkConfig = z.infer<typeof FrameworkConfigSchema>;
```

## 2. Dynamic Routing & Feature Toggles

### Homepage Selection
- **Refactor `src/pages/index.astro`**: It will serve as a dynamic router. Based on `config.routing.homepage`, it will import and render the appropriate template (Blog or Tools).

### Disabling Routes
- **Astro Middleware**: Use middleware to redirect requests for disabled features (e.g., `/tools`) back to the homepage if `enableToolsPage` is false. This ensures a clean user experience even if URLs are directly accessed.

## 3. Advanced Theming System

- **Tailwind v4 Integration**: Use `@theme` blocks in `src/styles/global.css` to define base variables.
- **Dynamic CSS Injection**: In `PageLayout.astro`, inject a `<style>` block that maps `config.theme.overrides` to HSL variables. This allows users to tweak the theme without touching the CSS files.
- **Component-Level Theming**: Use `class-variance-authority` (CVA) in components to support different styles for different themes.

## 4. Asset Management

- Centralize all asset paths in the configuration.
- Provide a clear directory structure for user-provided assets (e.g., `public/user-assets/`) to avoid merge conflicts with framework updates.

## 5. Content Bootstrapping & "Clean" Script

To help users start fresh:
- **`npm run framework:clean`**: A script that removes the "Giwan" demo content (blog posts in `src/pages/blog` and data in `src/data`) while keeping the directory structure intact.
- **Placeholder Content**: Provide a "Hello World" template for the first blog post and a basic configuration.

## 6. Maintainability & Upstream Sync

- **Template Repository Strategy**: Mark the repo as a GitHub Template.
- **Upstream Sync Guide**: Provide documentation on how users can pull updates from the core framework into their forks using `git remote add framework`.
- **Decoupling Data**: Ensure all user-specific data (blog posts, tools data) is isolated from framework logic to minimize merge conflicts.

## 7. Implementation Roadmap

### Phase 1: Core Refactoring (Validation & Config)
- Implement Zod schemas.
- Update all components to pull from the validated config.

### Phase 2: Routing & Logic (Features & Homepage)
- Implement the dynamic `index.astro`.
- Add middleware for route guarding.

### Phase 3: Theming & Assets
- Finalize the multi-theme CSS architecture.
- Centralize asset handling.

### Phase 4: Developer Experience (Scripts & Docs)
- Create the `framework:clean` script.
- Write comprehensive "Quick Start" and "Upstream Sync" documentation.

## 8. Senior-Level Verification

- **Edge Case Testing**: Verify build behavior when features are toggled off.
- **Schema Validation**: Ensure the app fails gracefully with descriptive errors if the config is invalid.
- **Performance Audit**: Use Lighthouse to ensure that adding framework layers doesn't impact the current high performance (especially PWA and SSR).
215 changes: 111 additions & 104 deletions astro.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import tailwindcss from "@tailwindcss/vite";
import sitemap from "@astrojs/sitemap";
import headersIntegration from "./src/integrations/headers";
import pwaValidationIntegration from "./src/integrations/pwaValidation";
import { SITE_CONFIG, PWA_CONFIG } from "./src/config";
import { SITE_CONFIG, PWA_CONFIG, CONFIG } from "./src/config";

// https://astro.build/config
// https://docs.astro.build/en/guides/server-side-rendering/#enabling-ssr-in-your-project
Expand All @@ -16,116 +16,123 @@ export default defineConfig({
vite: {
plugins: [tailwindcss()],
},
integrations: [mdx(), react(), sitemap(), headersIntegration(), VitePWA({
scope: '/',
registerType: 'prompt',
manifestFilename: 'manifest.json',
minify: false,
manifest: {
name: PWA_CONFIG.name,
short_name: PWA_CONFIG.shortName,
description: PWA_CONFIG.description,
lang: PWA_CONFIG.lang,
dir: PWA_CONFIG.dir,
icons: PWA_CONFIG.icons,
start_url: PWA_CONFIG.startUrl,
scope: PWA_CONFIG.scope,
display: PWA_CONFIG.display,
orientation: PWA_CONFIG.orientation,
theme_color: PWA_CONFIG.themeColor,
background_color: PWA_CONFIG.backgroundColor,
categories: PWA_CONFIG.categories,
prefer_related_applications: false
},
workbox: {
mode: 'development',
disableDevLogs: true,
navigateFallback: '/offline.html',
globPatterns: ['**/*.{js,css,html,ico,png,svg,webp,woff,woff2}'],
maximumFileSizeToCacheInBytes: 3 * 1024 * 1024, // 3MB limit
// Cache cleanup and management policies
cleanupOutdatedCaches: true, // Remove old caches automatically
skipWaiting: true, // Activate new service worker immediately
clientsClaim: true, // Take control of all clients immediately
runtimeCaching: [
// Blog posts - stale-while-revalidate, cache last 3 articles visited
{
urlPattern: /\/blog\/.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'blog-posts',
expiration: {
maxEntries: 3, // Store only last 3 articles visited
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days (1 month)
integrations: [
mdx(),
react(),
sitemap(),
headersIntegration(),
CONFIG.features.enablePWA ? VitePWA({
scope: '/',
registerType: 'prompt',
manifestFilename: 'manifest.json',
minify: false,
manifest: {
name: PWA_CONFIG.name,
short_name: PWA_CONFIG.shortName,
description: PWA_CONFIG.description,
lang: PWA_CONFIG.lang,
dir: PWA_CONFIG.dir,
icons: PWA_CONFIG.icons,
start_url: PWA_CONFIG.startUrl,
scope: PWA_CONFIG.scope,
display: PWA_CONFIG.display,
orientation: PWA_CONFIG.orientation,
theme_color: PWA_CONFIG.themeColor,
background_color: PWA_CONFIG.backgroundColor,
categories: PWA_CONFIG.categories,
prefer_related_applications: false
},
workbox: {
mode: 'development',
disableDevLogs: true,
navigateFallback: '/offline.html',
globPatterns: ['**/*.{js,css,html,ico,png,svg,webp,woff,woff2}'],
maximumFileSizeToCacheInBytes: 3 * 1024 * 1024, // 3MB limit
// Cache cleanup and management policies
cleanupOutdatedCaches: true, // Remove old caches automatically
skipWaiting: true, // Activate new service worker immediately
clientsClaim: true, // Take control of all clients immediately
runtimeCaching: [
// Blog posts - stale-while-revalidate, cache last 3 articles visited
{
urlPattern: /\/blog\/.*/,
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'blog-posts',
expiration: {
maxEntries: 3, // Store only last 3 articles visited
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days (1 month)
}
}
}
},
// Images - cache-first with 1-month expiration and size limits
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp|ico)$/,
handler: 'CacheFirst',
options: {
cacheName: 'images',
expiration: {
maxEntries: 50, // Reasonable limit for images
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days (1 month)
},
// Images - cache-first with 1-month expiration and size limits
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp|ico)$/,
handler: 'CacheFirst',
options: {
cacheName: 'images',
expiration: {
maxEntries: 50, // Reasonable limit for images
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days (1 month)
}
}
}
},
// Static assets - cache-first with optimized limits
{
urlPattern: /\.(?:js|css|woff|woff2|ttf|eot)$/,
handler: 'CacheFirst',
options: {
cacheName: 'static-assets',
expiration: {
maxEntries: 30, // Reduced for better cache management
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days (1 month)
},
// Static assets - cache-first with optimized limits
{
urlPattern: /\.(?:js|css|woff|woff2|ttf|eot)$/,
handler: 'CacheFirst',
options: {
cacheName: 'static-assets',
expiration: {
maxEntries: 30, // Reduced for better cache management
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 days (1 month)
}
}
}
},
// Google Fonts stylesheets - stale while revalidate
{
urlPattern: ({ url }) => url.origin === 'https://fonts.googleapis.com',
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'google-fonts-stylesheets',
expiration: {
maxEntries: 10,
maxAgeSeconds: 30 * 24 * 60 * 60
},
// Google Fonts stylesheets - stale while revalidate
{
urlPattern: ({ url }) => url.origin === 'https://fonts.googleapis.com',
handler: 'StaleWhileRevalidate',
options: {
cacheName: 'google-fonts-stylesheets',
expiration: {
maxEntries: 10,
maxAgeSeconds: 30 * 24 * 60 * 60
}
}
}
},
// Google Fonts files - cache first
{
urlPattern: ({ url }) => url.origin === 'https://fonts.gstatic.com',
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts-webfonts',
expiration: {
maxEntries: 10,
maxAgeSeconds: 365 * 24 * 60 * 60
},
cacheableResponse: {
statuses: [0, 200]
},
// Google Fonts files - cache first
{
urlPattern: ({ url }) => url.origin === 'https://fonts.gstatic.com',
handler: 'CacheFirst',
options: {
cacheName: 'google-fonts-webfonts',
expiration: {
maxEntries: 10,
maxAgeSeconds: 365 * 24 * 60 * 60
},
cacheableResponse: {
statuses: [0, 200]
}
}
}
},
// API calls - network-first
{
urlPattern: /\/api\/.*/,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 24 * 60 * 60 // 24 hours
},
// API calls - network-first
{
urlPattern: /\/api\/.*/,
handler: 'NetworkFirst',
options: {
cacheName: 'api-cache',
expiration: {
maxEntries: 10,
maxAgeSeconds: 24 * 60 * 60 // 24 hours
}
}
}
}
]
}
}), pwaValidationIntegration()],
]
}
}) : null,
CONFIG.features.enablePWA ? pwaValidationIntegration() : null
].filter(Boolean) as any[],
output: "static",
outDir: "./docs",
markdown: {
Expand Down
Loading
Loading