This file provides context for AI coding agents working on this project.
Infrahub Q&A Viewer is a static web application for browsing and searching Discord forum threads. It displays threads from JSON files with search, tag filtering, and message formatting.
- Framework: SvelteKit 2 with Svelte 5 (using runes:
$state,$effect,$props) - Styling: Tailwind CSS 3
- Language: TypeScript
- Build: Vite 7
- Adapter:
@sveltejs/adapter-staticfor static site generation - Deployment: GitHub Pages, Cloudflare Pages, or any static host
svelte-app/
├── src/
│ ├── lib/
│ │ ├── data.ts # Data loading functions
│ │ └── types.ts # TypeScript interfaces
│ ├── routes/
│ │ ├── +layout.svelte # Root layout
│ │ ├── +layout.ts # SSR/prerender config
│ │ ├── +page.server.ts # Server-side data loading
│ │ └── +page.svelte # Main application page
│ ├── app.css # Global styles (Tailwind)
│ ├── app.d.ts # SvelteKit type declarations
│ └── app.html # HTML template
├── static/
│ ├── data/ # Thread JSON files
│ │ └── channel_data.json
│ ├── .nojekyll
│ └── favicon.svg
├── .github/workflows/
│ └── deploy.yml # GitHub Pages deployment
├── svelte.config.js
├── tailwind.config.js
├── vite.config.ts
├── tsconfig.json
└── package.json
Main application component containing:
- Thread list sidebar (resizable)
- Search input
- Tag filter
- Message display area
- Message formatting (code blocks, links, bold, italic)
Data loading utilities:
loadThreadsProgressively()- Loads threads in parallel with progress callbackloadAllThreads()- Loads all threads at oncesearchThreads()- Filters threads by search queryloadChannelData()- Loads channel metadata and tagsbuildTagMap()- Creates tag lookup map
TypeScript interfaces for:
ThreadData- Thread with messagesThread- Thread metadataMessage- Individual messageAuthor- Message authorChannelData- Channel with available tagsAvailableTag- Tag definition
Server-side file listing - reads static/data/ directory to get thread files dynamically.
This project uses Svelte 5 runes syntax:
<script lang="ts">
let count = $state(0); // Reactive state
let doubled = $derived(count * 2); // Derived values
let { data } = $props(); // Component props
$effect(() => { // Side effects
console.log(count);
});
</script>- Use Tailwind CSS utility classes
- Dark theme with gray-800/900 backgrounds
- Custom scrollbar styling in
+page.svelte<style>block - Tag colors generated from hash of tag ID
- Uses
$app/pathsbase for all fetch URLs to support deployment subpaths - Progressive loading pattern for better UX
- Files loaded in parallel with
Promise.all
- Update types in
src/lib/types.tsif needed - Add data loading logic to
src/lib/data.ts - Update UI in
src/routes/+page.svelte - Test with
npm run dev - Build with
npm run build
- Main layout is in
+page.svelte - Uses flexbox layout with resizable sidebar
- Responsive considerations: sidebar has min/max width constraints
- Update interfaces in
types.ts - Update data loading in
data.tsif processing needed - Display in
+page.svelte
# Development
npm run dev
# Production build
npm run build
# Preview production build
npm run preview
# Build for GitHub Pages (with base path)
BASE_PATH=/repo-name npm run build- Run dev server:
npm run dev - Check browser at
http://localhost:5173 - Verify build works:
npm run build - Preview build:
npm run preview
- Thread data is stored in
data.zipat the project root - The
prebuildscript extractsdata.ziptostatic/data/ - Each JSON file is an array with one ThreadData object
channel_data.jsoncontains tag definitions- Files are discovered dynamically at build time via
+page.server.ts static/data/is gitignored (extracted from zip during build)
The formatMessageContent() function in +page.svelte:
- Extracts code blocks and inline code first (protects from formatting)
- Escapes HTML
- Converts URLs to links
- Applies bold/italic formatting
- Restores code blocks
- Multiple tags can be selected (AND filter)
- Tags stored in
selectedTagsSet - Filtering happens in
$effectblock combining search and tags
loadThreadsProgressively()fires callback as each thread loads- Threads sorted by creation date (newest first)
- Loading spinner shown while
loadingstate is true