diff --git a/ROADMAP.md b/ROADMAP.md
index 8ab4142..060b945 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -21,22 +21,22 @@
---
-## 🔥 Phase 1: Essential UX Foundations
-*Timeline: 1-2 days | Priority: CRITICAL*
+## ✅ Phase 1: Essential UX Foundations
+*Timeline: 1-2 days | Priority: CRITICAL | **COMPLETED***
These features make the current experience sticky and shareable.
| Feature | Status | Priority | Effort | Value | Notes |
|---------|--------|----------|--------|-------|-------|
-| **URL-Based Coordinate Sharing** | 📋 | 🔥 | ⚡ | ⭐⭐⭐⭐⭐ | Encode coords in URL for instant sharing |
-| **Temporal Journal (Visit History)** | 📋 | 🔥 | ⚡⚡ | ⭐⭐⭐⭐⭐ | localStorage history of visited coords |
-| **Image Gallery & Export** | 📋 | 🔥 | ⚡⚡ | ⭐⭐⭐⭐ | Save images to IndexedDB, download PNG |
+| **URL-Based Coordinate Sharing** | ✅ | 🔥 | ⚡ | ⭐⭐⭐⭐⭐ | Encode coords in URL for instant sharing |
+| **Temporal Journal (Visit History)** | ✅ | 🔥 | ⚡⚡ | ⭐⭐⭐⭐⭐ | localStorage history of visited coords |
+| **Image Gallery & Export** | ✅ | 🔥 | ⚡⚡ | ⭐⭐⭐⭐ | Save images to IndexedDB, download PNG |
**Deliverables**:
-- Share button with copy-to-clipboard
-- Journal panel in left sidebar
-- Gallery modal with grid view
-- Download as PNG feature
+- ✅ Share button with copy-to-clipboard
+- ✅ Journal panel in left sidebar
+- ✅ Gallery modal with grid view
+- ✅ Download as PNG feature
---
@@ -111,31 +111,31 @@ Features that don't align with the app's vision:
## Current Sprint
-**Active Sprint**: Phase 1 - Essential UX Foundations
-**Start Date**: TBD
-**Target Completion**: 1-2 days
+**Active Sprint**: Phase 2 - Temporal Navigation
+**Status**: Ready to start
+**Previous Sprint**: Phase 1 - Essential UX Foundations ✅ COMPLETED
-### Sprint Backlog
+### Phase 1 Sprint Summary (Completed)
-- [ ] 1.1 URL-Based Coordinate Sharing (2 hours)
- - [ ] Create urlManager utility
- - [ ] Integrate with ChronoscopeContext
- - [ ] Add Share button to header
- - [ ] Test with all coordinate types
+- [x] 1.1 URL-Based Coordinate Sharing
+ - [x] Create urlManager utility
+ - [x] Integrate with ChronoscopeContext
+ - [x] Add Share button to header
+ - [x] Auto-update URL on scene render
-- [ ] 1.2 Temporal Journal (4 hours)
- - [ ] Create temporalJournal utility
- - [ ] Build Journal component
- - [ ] Add to left sidebar
- - [ ] Implement export/import
- - [ ] Test localStorage limits
+- [x] 1.2 Temporal Journal
+ - [x] Create temporalJournal utility
+ - [x] Build Journal component
+ - [x] Add to left sidebar
+ - [x] Implement export/import
+ - [x] Auto-save on scene render
-- [ ] 1.3 Image Gallery (6 hours)
- - [ ] Set up IndexedDB schema
- - [ ] Create imageGallery utility
- - [ ] Build Gallery modal component
- - [ ] Add download functionality
- - [ ] Test storage limits
+- [x] 1.3 Image Gallery
+ - [x] Set up IndexedDB schema with idb
+ - [x] Create galleryService utility
+ - [x] Build Gallery modal component
+ - [x] Add download functionality
+ - [x] Auto-save generated images
---
@@ -196,7 +196,7 @@ Track these KPIs to guide future development:
| Version | Date | Changes |
|---------|------|---------|
| 1.0.0 | 2025-11-28 | Initial app launch with core features |
-| 1.1.0 | TBD | Phase 1: Essential UX (sharing, history, gallery) |
+| 1.1.0 | 2025-11-28 | Phase 1: Essential UX (sharing, history, gallery) ✅ |
| 1.2.0 | TBD | Phase 2: Temporal navigation (slider, chat) |
| 2.0.0 | TBD | Phase 3: Enhanced discovery |
diff --git a/package-lock.json b/package-lock.json
index b898daa..b871d9b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "chronoscope",
"version": "1.0.0",
"dependencies": {
+ "idb": "^8.0.3",
"lucide-react": "^0.555.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
@@ -1772,6 +1773,12 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/idb": {
+ "version": "8.0.3",
+ "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.3.tgz",
+ "integrity": "sha512-LtwtVyVYO5BqRvcsKuB2iUMnHwPVByPCXFXOpuU96IZPPoPN6xjOGxZQ74pgSVVLQWtUOYgyeL4GE98BY5D3wg==",
+ "license": "ISC"
+ },
"node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
diff --git a/package.json b/package.json
index edcef8d..7e5fe54 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "idb": "^8.0.3",
"lucide-react": "^0.555.0",
"react": "^19.2.0",
"react-dom": "^19.2.0"
diff --git a/src/App.tsx b/src/App.tsx
index 4435471..a4da7dd 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,18 +1,21 @@
-import { useState, useCallback } from 'react';
+import { useState, useCallback, useEffect } from 'react';
import {
PanelLeftClose,
PanelLeftOpen,
PanelRightClose,
PanelRightOpen,
} from 'lucide-react';
-import { ChronoscopeProvider } from './context/ChronoscopeContext';
+import { ChronoscopeProvider, useChronoscope } from './context/ChronoscopeContext';
import {
Header,
ControlPlane,
Viewport,
DataStream,
Waypoints,
+ TemporalJournal,
} from './components';
+import { getCoordinatesFromUrl, updateUrlWithCoordinates } from './utils/urlManager';
+import { addJournalEntry } from './utils/temporalJournal';
interface ChronoscopeAppProps {
onApiKeyChange: () => void;
@@ -22,6 +25,36 @@ function ChronoscopeApp({ onApiKeyChange }: ChronoscopeAppProps) {
const [leftPanelOpen, setLeftPanelOpen] = useState(true);
const [rightPanelOpen, setRightPanelOpen] = useState(true);
const [mobileTab, setMobileTab] = useState<'controls' | 'viewport' | 'data'>('viewport');
+ const { state, setCoordinates, renderScene } = useChronoscope();
+
+ // Read URL coordinates on mount and auto-render
+ useEffect(() => {
+ const urlCoords = getCoordinatesFromUrl();
+ if (urlCoords) {
+ setCoordinates(urlCoords);
+ // Small delay to ensure state is set before rendering
+ setTimeout(() => {
+ renderScene();
+ }, 100);
+ }
+ }, []); // eslint-disable-line react-hooks/exhaustive-deps
+
+ // Update URL and save to journal when scene is rendered
+ useEffect(() => {
+ if (state.currentScene) {
+ updateUrlWithCoordinates(state.currentScene.coordinates);
+
+ // Save to journal
+ addJournalEntry(
+ state.currentScene.coordinates,
+ state.currentScene.locationName,
+ !!state.generatedImage
+ );
+
+ // Notify journal component to refresh
+ window.dispatchEvent(new Event('journalUpdated'));
+ }
+ }, [state.currentScene]); // eslint-disable-line react-hooks/exhaustive-deps
return (
@@ -54,6 +87,7 @@ function ChronoscopeApp({ onApiKeyChange }: ChronoscopeAppProps) {
+
)}
@@ -134,6 +168,7 @@ function ChronoscopeApp({ onApiKeyChange }: ChronoscopeAppProps) {
+
)}
{mobileTab === 'viewport' && }
diff --git a/src/components/Header.tsx b/src/components/Header.tsx
index 281c4ec..5a0645d 100644
--- a/src/components/Header.tsx
+++ b/src/components/Header.tsx
@@ -5,9 +5,16 @@ import {
Info,
X,
Github,
+ Share2,
+ Check,
+ Images,
} from 'lucide-react';
import { Settings } from './Settings';
+import { ImageGallery } from './ImageGallery';
import { isGeminiConfigured } from '../services/geminiService';
+import { getGalleryCount } from '../services/galleryService';
+import { useChronoscope } from '../context/ChronoscopeContext';
+import { copyShareableUrl } from '../utils/urlManager';
interface HeaderProps {
onApiKeyChange?: () => void;
@@ -17,7 +24,20 @@ export function Header({ onApiKeyChange }: HeaderProps) {
const [currentTime, setCurrentTime] = useState(new Date());
const [showInfo, setShowInfo] = useState(false);
const [showSettings, setShowSettings] = useState(false);
+ const [showGallery, setShowGallery] = useState(false);
const [apiConfigured, setApiConfigured] = useState(isGeminiConfigured());
+ const [galleryCount, setGalleryCount] = useState(0);
+ const [copied, setCopied] = useState(false);
+ const { state } = useChronoscope();
+
+ const handleShare = async () => {
+ if (!state.currentScene) return;
+ const success = await copyShareableUrl(state.currentScene.coordinates);
+ if (success) {
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ }
+ };
// Update real-world time every second
useEffect(() => {
@@ -27,6 +47,19 @@ export function Header({ onApiKeyChange }: HeaderProps) {
return () => clearInterval(timer);
}, []);
+ // Load gallery count on mount and listen for updates
+ useEffect(() => {
+ const loadCount = async () => {
+ const count = await getGalleryCount();
+ setGalleryCount(count);
+ };
+ loadCount();
+
+ // Listen for gallery update events
+ window.addEventListener('galleryUpdated', loadCount);
+ return () => window.removeEventListener('galleryUpdated', loadCount);
+ }, []);
+
return (
<>
@@ -75,6 +108,32 @@ export function Header({ onApiKeyChange }: HeaderProps) {
{/* Actions */}
+
+