Status: Partially Complete (Frontend Shell Ready, Backend Integration Missing) Priority: High (blocks public launch) Date: January 2026
The gemmology.dev website is a well-designed Astro/React application with professional UI/UX. However, it is NOT PRODUCTION-READY due to missing backend integration, stub implementations, and incomplete content.
What Works:
- Gallery with 50+ mineral presets (sql.js database)
- Landing page with professional design
- CDL playground editor with syntax highlighting
- Responsive design & deployment pipeline
What's Missing:
- Backend API integration (CDL parsing, geometry, rendering)
- 3D WebGL viewer
- 14 documentation/learn pages
- Export functionality
- Automated tests
Status: No backend exists
Current State:
- CDL validation uses regex patterns only
- Preview shows placeholder SVG octahedron
- Export buttons show "would trigger here" alerts
Required Endpoints:
| Endpoint | Purpose | Backend Package |
|---|---|---|
POST /api/parse |
Parse CDL string | cdl-parser |
POST /api/geometry |
Generate 3D geometry | crystal-geometry |
POST /api/render/svg |
Render to SVG | crystal-renderer |
POST /api/render/stl |
Export to STL | crystal-renderer |
POST /api/render/gltf |
Export to glTF | crystal-renderer |
GET /api/presets |
List mineral presets | mineral-database |
GET /api/presets/:id |
Get preset details | mineral-database |
Implementation Options:
-
Cloudflare Workers + Python (via HTTP)
Frontend → Cloudflare Worker → Python API (Railway/Fly.io) -
Pyodide/WASM (client-side)
Frontend → Pyodide → cdl-parser/crystal-geometry (in browser) -
Pre-generated Static Assets
Build time → Generate all preset SVGs/geometries → Static hosting
Recommended: Option 1 for dynamic CDL, Option 3 for presets.
Status: Not implemented
Current State:
- Uses 2D SVG with CSS 3D transforms
transform: perspective(800px) rotateX(...) rotateY(...)- No actual 3D geometry rendering
Proposed Implementation:
// src/components/crystal/Crystal3DViewer.tsx
import { Canvas } from '@react-three/fiber';
import { OrbitControls, Stage } from '@react-three/drei';
interface Crystal3DViewerProps {
geometry: {
vertices: number[][];
faces: number[][];
faceColors?: string[];
};
}
export function Crystal3DViewer({ geometry }: Crystal3DViewerProps) {
return (
<Canvas camera={{ position: [0, 0, 5], fov: 50 }}>
<Stage environment="studio" intensity={0.5}>
<CrystalMesh geometry={geometry} />
</Stage>
<OrbitControls enableZoom={true} enablePan={false} />
</Canvas>
);
}
function CrystalMesh({ geometry }) {
const { vertices, faces, faceColors } = geometry;
// Convert to Three.js BufferGeometry
// Apply per-face colors
// Return mesh with proper materials
}Dependencies to Add:
{
"@react-three/fiber": "^8.15.0",
"@react-three/drei": "^9.92.0",
"three": "^0.160.0"
}Status: Links exist, content missing
| Page | Route | Priority |
|---|---|---|
| Installation | /docs/installation |
High |
| Quick Start | /docs/quickstart |
High |
| CDL Specification | /docs/cdl |
High |
| CLI Reference | /docs/cli |
Medium |
| API Reference | /docs/api |
Medium |
| Configuration | /docs/config |
Low |
| Troubleshooting | /docs/troubleshooting |
Medium |
| Contributing | /docs/contributing |
Low |
Proposed Structure (src/pages/docs/):
docs/
├── index.astro # Hub (exists)
├── installation.astro # Getting started
├── quickstart.astro # First crystal in 5 min
├── cdl/
│ ├── index.astro # CDL overview
│ ├── syntax.astro # Full syntax reference
│ ├── systems.astro # Crystal systems
│ ├── forms.astro # Named forms
│ └── modifications.astro
├── cli.astro # Command reference
├── api.astro # REST API docs
└── troubleshooting.astro
Status: Links exist, content missing
| Topic | Route | Content Source |
|---|---|---|
| Crystal Systems | /learn/crystal-systems |
gemmology-knowledge |
| Physical Properties | /learn/physical-properties |
gemmology-knowledge |
| Optical Properties | /learn/optical-properties |
gemmology-knowledge |
| Inclusions | /learn/inclusions |
gemmology-knowledge |
| Treatments | /learn/treatments |
gemmology-knowledge |
| Synthetics | /learn/synthetics |
gemmology-knowledge |
Content Strategy:
- Import markdown from
gemmology-knowledgerepository - Use Astro content collections
- Add interactive elements (quizzes, diagrams)
Status: Stub implementation
Current (src/components/playground/ExportPanel.tsx):
const handleExport = (format: string) => {
alert(`Export to ${format} would trigger here`); // STUB
};Required Implementation:
// src/lib/export.ts
export async function exportCrystal(
cdl: string,
format: 'svg' | 'stl' | 'gltf' | 'gemcad'
): Promise<Blob> {
const response = await fetch(`/api/render/${format}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cdl }),
});
if (!response.ok) {
throw new Error(`Export failed: ${response.statusText}`);
}
return response.blob();
}
export function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}Status: Zero test coverage
Proposed Test Structure:
src/
├── __tests__/
│ ├── components/
│ │ ├── playground/
│ │ │ ├── CDLEditor.test.tsx
│ │ │ ├── CDLPreview.test.tsx
│ │ │ └── ExportPanel.test.tsx
│ │ ├── gallery/
│ │ │ ├── Gallery.test.tsx
│ │ │ └── FilterBar.test.tsx
│ │ └── ui/
│ │ ├── Button.test.tsx
│ │ └── Card.test.tsx
│ ├── hooks/
│ │ ├── useCDLValidation.test.ts
│ │ └── useCrystalDB.test.ts
│ └── lib/
│ ├── db.test.ts
│ └── monaco-cdl.test.ts
e2e/
├── playground.spec.ts
├── gallery.spec.ts
└── navigation.spec.ts
Configuration (vitest.config.ts):
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test/setup.ts',
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
},
},
});Dependencies to Add:
{
"vitest": "^1.2.0",
"@testing-library/react": "^14.1.0",
"@testing-library/user-event": "^14.5.0",
"@playwright/test": "^1.41.0"
}Status: Regex-based only
Current (src/hooks/useCDLValidation.ts):
// Simple regex validation
const systemPattern = /^(cubic|hexagonal|trigonal|...)/;Proposed: Use cdl-parser via API or WASM
// src/lib/cdl-validator.ts
export async function validateCDL(cdl: string): Promise<ValidationResult> {
try {
const response = await fetch('/api/parse', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ cdl }),
});
const result = await response.json();
if (result.error) {
return {
valid: false,
error: result.error,
position: result.position,
};
}
return {
valid: true,
parsed: result.description,
};
} catch (error) {
return { valid: false, error: 'Validation service unavailable' };
}
}Status: Modal exists, dedicated pages missing
Current: /gallery shows modal on click
Needed: /gallery/[mineral] dedicated pages
Proposed (src/pages/gallery/[mineral].astro):
---
import { getMineral, getAllMinerals } from '../../lib/db';
import BaseLayout from '../../layouts/BaseLayout.astro';
import CrystalViewer from '../../components/crystal/CrystalViewer';
import FGAInfoPanel from '../../components/crystal/FGAInfoPanel';
export async function getStaticPaths() {
const minerals = await getAllMinerals();
return minerals.map(m => ({
params: { mineral: m.id },
props: { mineral: m },
}));
}
const { mineral } = Astro.props;
---
<BaseLayout title={mineral.name}>
<article class="max-w-4xl mx-auto py-12 px-4">
<h1 class="text-4xl font-bold">{mineral.name}</h1>
<p class="text-xl text-gray-600">{mineral.chemistry}</p>
<div class="grid md:grid-cols-2 gap-8 mt-8">
<CrystalViewer client:load cdl={mineral.cdl} />
<FGAInfoPanel mineral={mineral} />
</div>
<section class="mt-12">
<h2>CDL Definition</h2>
<pre><code>{mineral.cdl}</code></pre>
</section>
</article>
</BaseLayout>Missing:
robots.txtsitemap.xml- JSON-LD structured data
- Canonical URLs for dynamic pages
Proposed (public/robots.txt):
User-agent: *
Allow: /
Sitemap: https://gemmology.dev/sitemap.xml
Sitemap Generation (astro.config.mjs):
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://gemmology.dev',
integrations: [
react(),
tailwind(),
sitemap(),
],
});JSON-LD (src/layouts/BaseLayout.astro):
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Gemmology.dev",
"url": "https://gemmology.dev",
"description": "Crystal visualization and gemstone expertise",
"potentialAction": {
"@type": "SearchAction",
"target": "https://gemmology.dev/gallery?q={search_term_string}",
"query-input": "required name=search_term_string"
}
}
</script>Missing:
- ARIA labels on interactive elements
- Skip navigation link
- Focus management in modals
- Screen reader announcements
Proposed Fixes:
// Skip link
<a href="#main-content" class="sr-only focus:not-sr-only">
Skip to main content
</a>
// Modal focus trap
import { FocusTrap } from '@headlessui/react';
// ARIA labels
<button aria-label="Export crystal as SVG">
<DownloadIcon />
</button>
// Live regions
<div role="status" aria-live="polite">
{isLoading ? 'Loading crystals...' : `${count} crystals found`}
</div>Status: Not implemented
Proposed Implementation:
// src/hooks/useTheme.ts
export function useTheme() {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
useEffect(() => {
const stored = localStorage.getItem('theme');
const system = window.matchMedia('(prefers-color-scheme: dark)').matches;
setTheme(stored || (system ? 'dark' : 'light'));
}, []);
const toggle = () => {
const next = theme === 'light' ? 'dark' : 'light';
setTheme(next);
localStorage.setItem('theme', next);
document.documentElement.classList.toggle('dark', next === 'dark');
};
return { theme, toggle };
}Tailwind Config:
module.exports = {
darkMode: 'class',
// ...
};Current Issues:
- Monaco editor loaded on page load (large bundle)
- sql.js WASM loaded synchronously
- No image lazy loading
Proposed Fixes:
// Lazy load Monaco
const CDLEditor = dynamic(() => import('./CDLEditor'), {
loading: () => <div class="skeleton h-64" />,
ssr: false,
});
// Lazy load sql.js
const db = await import('sql.js').then(m => m.default());
// Image lazy loading
<img loading="lazy" src={crystalSvg} alt={name} />Not Started
Recommended Approach:
- Use Astro i18n integration
- Priority languages: English, German, Japanese, Chinese
Not Started
Proposed Features:
- User-submitted crystal designs
- OAuth login (GitHub, Google)
- Like/favorite system
- Share with URL
Not Started
Options:
- React Native with shared components
- PWA with offline support
| Priority | Task | Effort | Impact |
|---|---|---|---|
| P0 | Backend API for CDL/render | 2 weeks | Core functionality |
| P0 | Connect playground to API | 1 week | Core functionality |
| P1 | Create documentation pages | 1 week | User adoption |
| P1 | Create learn pages | 1 week | FGA curriculum |
| P1 | Add Three.js 3D viewer | 2 weeks | Visual impact |
| P1 | Implement export endpoints | 1 week | Core functionality |
| P2 | Add automated tests | 1 week | Quality assurance |
| P2 | Individual mineral pages | 3 days | SEO/navigation |
| P2 | SEO enhancements | 2 days | Discoverability |
| P3 | Accessibility audit | 3 days | Compliance |
| P3 | Dark mode | 2 days | UX |
| P3 | Performance optimization | 3 days | Speed |
| Phase | Duration | Deliverables |
|---|---|---|
| Phase 1 | 2-3 weeks | Documentation pages, learn pages, tests |
| Phase 2 | 3-4 weeks | Backend API, playground integration |
| Phase 3 | 4-6 weeks | Three.js 3D viewer, export functionality |
| Phase 4 | 2-3 weeks | Polish, SEO, accessibility, launch |
Estimated Total: 11-16 weeks to production-ready state
Before launch:
- All 8 documentation pages complete
- All 6 learn pages complete
- Backend API deployed and functional
- Playground generates real SVG from CDL
- Export to SVG/STL/glTF works
- 3D WebGL viewer implemented
- Individual mineral pages exist
- Vitest test suite passes (80%+ coverage)
- E2E tests pass
- Lighthouse score > 90 (all categories)
- WCAG 2.1 AA compliance
- robots.txt and sitemap.xml present
- SSL certificate active
- Analytics configured
Document created: 2026-01-20