Files
anthonyrawlins 5e0be60c30 Release v1.2.0: Newspaper-style layout with major UI refinements
This release transforms PING into a sophisticated newspaper-style digital
publication with enhanced readability and professional presentation.

Major Features:
- New FeaturedPostHero component with full-width newspaper design
- Completely redesigned homepage with responsive newspaper grid layout
- Enhanced PostCard component with refined typography and spacing
- Improved mobile-first responsive design (mobile → tablet → desktop → 2XL)
- Archive section with multi-column layout for deeper content discovery

Technical Improvements:
- Enhanced blog post validation and error handling in lib/blog.ts
- Better date handling and normalization for scheduled posts
- Improved Dockerfile with correct content volume mount paths
- Fixed port configuration (3025 throughout stack)
- Updated Tailwind config with refined typography and newspaper aesthetics
- Added getFeaturedPost() function for hero selection

UI/UX Enhancements:
- Professional newspaper-style borders and dividers
- Improved dark mode styling throughout
- Better content hierarchy and visual flow
- Enhanced author bylines and metadata presentation
- Refined color palette with newspaper sophistication

Documentation:
- Added DESIGN_BRIEF_NEWSPAPER_LAYOUT.md detailing design principles
- Added TESTING_RESULTS_25_POSTS.md with test scenarios

This release establishes PING as a premium publication platform for
AI orchestration and contextual intelligence thought leadership.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 00:23:51 +11:00

104 lines
3.8 KiB
TypeScript

import { getSortedPostsData } from '@/lib/blog'
import { NextResponse } from 'next/server'
// Force dynamic rendering to pick up volume-mounted content
export const dynamic = 'force-dynamic'
// RSS feed configuration
const SITE_URL = 'https://blog.chorus.services'
const SITE_TITLE = 'CHORUS Services Blog'
const SITE_DESCRIPTION = 'Contextual AI orchestration, on-premises infrastructure, and the future of intelligent systems.'
const SITE_LANGUAGE = 'en-us'
const AUTHOR_EMAIL = 'noreply@chorus.services'
const AUTHOR_NAME = 'CHORUS Services'
function escapeXml(text: string): string {
return text
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;')
}
function formatRssDate(dateString: string): string {
const date = new Date(dateString)
return date.toUTCString()
}
export async function GET() {
try {
// Get all published posts (includes scheduled posts that should be live)
const posts = getSortedPostsData()
// Generate RSS XML
const rssXml = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>${escapeXml(SITE_TITLE)}</title>
<link>${SITE_URL}</link>
<description>${escapeXml(SITE_DESCRIPTION)}</description>
<language>${SITE_LANGUAGE}</language>
<managingEditor>${AUTHOR_EMAIL} (${AUTHOR_NAME})</managingEditor>
<webMaster>${AUTHOR_EMAIL} (${AUTHOR_NAME})</webMaster>
<lastBuildDate>${formatRssDate(new Date().toISOString())}</lastBuildDate>
<atom:link href="${SITE_URL}/rss.xml" rel="self" type="application/rss+xml" />
<generator>CHORUS Blog RSS Generator</generator>
<ttl>60</ttl>
${posts.map(post => ` <item>
<title>${escapeXml(post.title)}</title>
<link>${SITE_URL}/posts/${post.slug}</link>
<description>${escapeXml(post.description)}</description>
<author>${AUTHOR_EMAIL} (${post.author.name})</author>
<category>${post.tags.map(tag => escapeXml(tag)).join(', ')}</category>
<guid isPermaLink="true">${SITE_URL}/posts/${post.slug}</guid>
<pubDate>${formatRssDate(post.date)}</pubDate>
<content:encoded><![CDATA[
<p><strong>Author:</strong> ${escapeXml(post.author.name)}${post.author.role ? `, ${escapeXml(post.author.role)}` : ''}</p>
<p><strong>Reading Time:</strong> ${post.readingTime} min</p>
<p><strong>Tags:</strong> ${post.tags.map(tag => escapeXml(tag)).join(', ')}</p>
<hr>
<div>${post.content}</div>
<hr>
<p><a href="${SITE_URL}/posts/${post.slug}">Read full post on CHORUS Blog</a></p>
]]></content:encoded>
</item>`).join('\n')}
</channel>
</rss>`
return new NextResponse(rssXml, {
status: 200,
headers: {
'Content-Type': 'application/rss+xml; charset=utf-8',
'Cache-Control': 'public, max-age=3600, s-maxage=3600', // Cache for 1 hour
},
})
} catch (error) {
console.error('Error generating RSS feed:', error)
// Return a minimal RSS feed with error info
const errorRss = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>${escapeXml(SITE_TITLE)} - Error</title>
<link>${SITE_URL}</link>
<description>Error generating RSS feed</description>
<language>${SITE_LANGUAGE}</language>
<lastBuildDate>${formatRssDate(new Date().toISOString())}</lastBuildDate>
<item>
<title>RSS Feed Error</title>
<link>${SITE_URL}</link>
<description>There was an error generating the RSS feed. Please try again later.</description>
<pubDate>${formatRssDate(new Date().toISOString())}</pubDate>
</item>
</channel>
</rss>`
return new NextResponse(errorRss, {
status: 500,
headers: {
'Content-Type': 'application/rss+xml; charset=utf-8',
},
})
}
}