setNewMapUrl(e.target.value)}
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- handleAddMap();
- }
- }}
- />
-
-
You haven't added any learning maps yet.
-
Paste a learning map URL above to get started!
+
+
+
Add a New Learning Map
+
+ setNewMapUrl(e.target.value)}
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ handleAddMap();
+ }
+ }}
+ />
+
+
+ {error &&
{error}
}
- ) : (
-
- {allMaps.map((map) => {
+
+ {allMaps.length === 0 ? (
+
+
You haven't added any learning maps yet.
+
Paste a learning map URL above to get started!
+
+ ) : (
+
+ {allMaps.map((map) => {
const completed = Object.values(map.state?.nodes || {}).filter(
(node) => node.state === 'completed' || node.state === 'mastered'
).length;
@@ -237,6 +244,7 @@ function Learn() {
})}
)}
+
);
}
diff --git a/platforms/web/src/logo.svg b/platforms/web/src/logo.svg
new file mode 100644
index 0000000..a1c2ef5
--- /dev/null
+++ b/platforms/web/src/logo.svg
@@ -0,0 +1,159 @@
+
+
From 75693bd7f0ce69e554e489dcb729ef3b240591c6 Mon Sep 17 00:00:00 2001
From: Mike Barkmin
Date: Wed, 15 Oct 2025 09:19:02 +0200
Subject: [PATCH 07/12] Remove cleanUrls property from vercel.json
Removed the 'cleanUrls' property from the configuration.
---
vercel.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/vercel.json b/vercel.json
index bd6fd8f..0db3279 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,3 +1,3 @@
{
- "cleanUrls": true
+
}
From 3f5b9ca1a531ada07150d4a12e1501614124b341 Mon Sep 17 00:00:00 2001
From: Mike Barkmin
Date: Wed, 15 Oct 2025 10:04:27 +0200
Subject: [PATCH 08/12] update vercel
---
vercel.json | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/vercel.json b/vercel.json
index 0db3279..1323cda 100644
--- a/vercel.json
+++ b/vercel.json
@@ -1,3 +1,8 @@
{
-
+ "rewrites": [
+ {
+ "source": "/(.*)",
+ "destination": "/index.html"
+ }
+ ]
}
From c651635f8d0abaff4ce6df71d61cd4b29baec2ff Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 15 Oct 2025 08:18:22 +0000
Subject: [PATCH 09/12] Apply brand colors and remove x-overflow on learning
maps overview
Co-authored-by: mikebarkmin <2592379+mikebarkmin@users.noreply.github.com>
---
platforms/web/src/Learn.css | 38 +++++++++++++++++++------------------
1 file changed, 20 insertions(+), 18 deletions(-)
diff --git a/platforms/web/src/Learn.css b/platforms/web/src/Learn.css
index c3321b3..adaccc8 100644
--- a/platforms/web/src/Learn.css
+++ b/platforms/web/src/Learn.css
@@ -59,8 +59,8 @@
}
.toolbar-button:hover {
- background: #f3f4f6;
- border-color: #3b82f6;
+ background: var(--learningmap-color-whitesmoke, #f5f5f5);
+ border-color: var(--learningmap-color-openpatch, #007864);
}
.learn-loading,
@@ -77,8 +77,8 @@
.learn-spinner {
width: 40px;
height: 40px;
- border: 4px solid #f3f3f3;
- border-top: 4px solid #3498db;
+ border: 4px solid var(--learningmap-color-whitesmoke, #f5f5f5);
+ border-top: 4px solid var(--learningmap-color-openpatch, #007864);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@@ -95,7 +95,7 @@
.learn-error button {
padding: 0.5rem 1rem;
- background: #007bff;
+ background: var(--learningmap-color-openpatch, #007864);
color: white;
border: none;
border-radius: 4px;
@@ -105,7 +105,7 @@
}
.learn-error button:hover {
- background: #0056b3;
+ background: var(--learningmap-color-dark-forest, #004c45);
}
/* List view styles */
@@ -123,10 +123,11 @@
margin: 0 auto;
padding: 2rem;
flex: 1;
+ overflow-x: hidden;
}
.add-map-section {
- background: #f8f9fa;
+ background: var(--learningmap-color-whitesmoke, #f5f5f5);
padding: 2rem;
border-radius: 8px;
margin-bottom: 2rem;
@@ -154,7 +155,7 @@
.add-map-form button {
padding: 0.75rem 1.5rem;
- background: #007bff;
+ background: var(--learningmap-color-openpatch, #007864);
color: white;
border: none;
border-radius: 4px;
@@ -166,7 +167,7 @@
}
.add-map-form button:hover {
- background: #0056b3;
+ background: var(--learningmap-color-dark-forest, #004c45);
}
.error-message {
@@ -178,7 +179,7 @@
.empty-state {
text-align: center;
padding: 3rem;
- color: #6c757d;
+ color: var(--learningmap-color-quicksilver, #a4a4a4);
}
.empty-state p {
@@ -190,6 +191,7 @@
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
+ overflow-x: hidden;
}
.map-card {
@@ -206,7 +208,7 @@
.map-card-header {
padding: 1rem;
- background: #f8f9fa;
+ background: var(--learningmap-color-whitesmoke, #f5f5f5);
border-bottom: 1px solid #dee2e6;
display: flex;
justify-content: space-between;
@@ -225,7 +227,7 @@
background: none;
border: none;
font-size: 1.5rem;
- color: #6c757d;
+ color: var(--learningmap-color-quicksilver, #a4a4a4);
cursor: pointer;
padding: 0;
width: 24px;
@@ -239,7 +241,7 @@
}
.remove-button:hover {
- background: #e9ecef;
+ background: var(--learningmap-color-whitesmoke, #f5f5f5);
color: #dc3545;
}
@@ -262,18 +264,18 @@
.progress-fill {
height: 100%;
- background: linear-gradient(90deg, #28a745 0%, #20c997 100%);
+ background: linear-gradient(90deg, var(--learningmap-color-openpatch, #007864) 0%, var(--learningmap-color-freshmint, #b5e3d8) 100%);
transition: width 0.3s ease;
}
.progress-text {
font-size: 0.875rem;
- color: #6c757d;
+ color: var(--learningmap-color-quicksilver, #a4a4a4);
}
.map-meta {
font-size: 0.875rem;
- color: #6c757d;
+ color: var(--learningmap-color-quicksilver, #a4a4a4);
margin-top: 0.5rem;
}
@@ -285,7 +287,7 @@
.continue-button {
width: 100%;
padding: 0.75rem;
- background: #007bff;
+ background: var(--learningmap-color-openpatch, #007864);
color: white;
border: none;
border-radius: 4px;
@@ -297,5 +299,5 @@
}
.continue-button:hover {
- background: #0056b3;
+ background: var(--learningmap-color-dark-forest, #004c45);
}
From 1f905d0df285acfe402377327b669aea529eb631 Mon Sep 17 00:00:00 2001
From: Mike Barkmin
Date: Wed, 15 Oct 2025 12:38:27 +0200
Subject: [PATCH 10/12] fix overflow
---
platforms/web/src/Learn.css | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/platforms/web/src/Learn.css b/platforms/web/src/Learn.css
index adaccc8..9ec468b 100644
--- a/platforms/web/src/Learn.css
+++ b/platforms/web/src/Learn.css
@@ -1,6 +1,4 @@
.learn-container {
- width: 100%;
- height: 100vh;
display: flex;
flex-direction: column;
font-family: "Concert One", sans-serif;
@@ -8,7 +6,6 @@
/* Toolbar matching EditorToolbar design */
.learn-toolbar {
- width: 100%;
background: #ffffff;
border-bottom: 1px solid #e5e7eb;
padding: 12px 24px;
@@ -84,8 +81,13 @@
}
@keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
}
.learn-error h2 {
@@ -110,8 +112,6 @@
/* List view styles */
.learn-list-container {
- width: 100%;
- min-height: 100vh;
display: flex;
flex-direction: column;
font-family: "Concert One", sans-serif;
@@ -122,6 +122,7 @@
width: 100%;
margin: 0 auto;
padding: 2rem;
+ box-sizing: border-box;
flex: 1;
overflow-x: hidden;
}
From 0dd11aa314d4af97b6468bb2c1fc7611b83d7501 Mon Sep 17 00:00:00 2001
From: Mike Barkmin
Date: Wed, 15 Oct 2025 12:40:38 +0200
Subject: [PATCH 11/12] fix style
---
platforms/web/src/Learn.css | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/platforms/web/src/Learn.css b/platforms/web/src/Learn.css
index 9ec468b..1724d26 100644
--- a/platforms/web/src/Learn.css
+++ b/platforms/web/src/Learn.css
@@ -1,4 +1,6 @@
.learn-container {
+ width: 100%;
+ height: 100vh;
display: flex;
flex-direction: column;
font-family: "Concert One", sans-serif;
@@ -6,6 +8,7 @@
/* Toolbar matching EditorToolbar design */
.learn-toolbar {
+ width: 100%;
background: #ffffff;
border-bottom: 1px solid #e5e7eb;
padding: 12px 24px;
@@ -112,6 +115,8 @@
/* List view styles */
.learn-list-container {
+ width: 100%;
+ min-height: 100vh;
display: flex;
flex-direction: column;
font-family: "Concert One", sans-serif;
@@ -122,9 +127,9 @@
width: 100%;
margin: 0 auto;
padding: 2rem;
- box-sizing: border-box;
flex: 1;
overflow-x: hidden;
+ box-sizing: border-box;
}
.add-map-section {
From 149c0077bdfcc2fc5e7884f511c924bd298f3d2a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Thu, 16 Oct 2025 07:54:05 +0000
Subject: [PATCH 12/12] Fix viewport behavior: use defaultViewport, add
translateExtent, persist viewport state
Co-authored-by: mikebarkmin <2592379+mikebarkmin@users.noreply.github.com>
---
packages/learningmap/src/LearningMap.tsx | 52 ++++++++++++++++++++----
1 file changed, 44 insertions(+), 8 deletions(-)
diff --git a/packages/learningmap/src/LearningMap.tsx b/packages/learningmap/src/LearningMap.tsx
index 282fc66..3e4f963 100644
--- a/packages/learningmap/src/LearningMap.tsx
+++ b/packages/learningmap/src/LearningMap.tsx
@@ -68,7 +68,7 @@ export function LearningMap({
const updateNodesStates = useViewerStore(state => state.updateNodesStates);
const updateNodeState = useViewerStore(state => state.updateNodeState);
- const { fitView, getViewport, setViewport } = useReactFlow();
+ const { fitView, getViewport } = useReactFlow();
// Use language from settings if available, otherwise use prop
const effectiveLanguage = settings?.language || language;
@@ -78,14 +78,40 @@ export function LearningMap({
const parsedRoadmap = parseRoadmapData(roadmapData);
+ // Calculate translateExtent to ensure at least one node is always visible
+ const calculateTranslateExtent = useCallback(() => {
+ if (nodes.length === 0) return [[-Infinity, -Infinity], [Infinity, Infinity]] as [[number, number], [number, number]];
+
+ const padding = 200; // Add padding so nodes aren't at the very edge
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
+
+ nodes.forEach(node => {
+ if (node.position) {
+ // Estimate node size (approximate, could be refined)
+ const nodeWidth = node.width || 200;
+ const nodeHeight = node.height || 100;
+
+ minX = Math.min(minX, node.position.x - padding);
+ minY = Math.min(minY, node.position.y - padding);
+ maxX = Math.max(maxX, node.position.x + nodeWidth + padding);
+ maxY = Math.max(maxY, node.position.y + nodeHeight + padding);
+ }
+ });
+
+ return [[minX, minY], [maxX, maxY]] as [[number, number], [number, number]];
+ }, [nodes]);
+
useEffect(() => {
loadRoadmapData(parsedRoadmap, initialState);
- setViewport({
- x: initialState?.x || settings?.viewport?.x || 0,
- y: initialState?.y || settings?.viewport?.y || 0,
- zoom: initialState?.zoom || settings?.viewport?.zoom || 1,
- });
- }, [roadmapData, initialState]);
+
+ // Only use fitView if there's no saved state
+ if (!initialState) {
+ // Use setTimeout to ensure nodes are rendered before fitView
+ setTimeout(() => {
+ fitView({ duration: 0, padding: 0.2 });
+ }, 0);
+ }
+ }, [roadmapData, initialState, loadRoadmapData, fitView]);
const onNodeClick = useCallback((_: any, node: Node, focus: boolean = false) => {
if (!isInteractableNode(node)) return;
@@ -135,6 +161,15 @@ export function LearningMap({
type: "default",
};
+ // Determine default viewport (only used if no saved state exists)
+ const defaultViewport = {
+ x: initialState?.x || settings?.viewport?.x || 0,
+ y: initialState?.y || settings?.viewport?.y || 0,
+ zoom: initialState?.zoom || settings?.viewport?.zoom || 1,
+ };
+
+ const translateExtent = calculateTranslateExtent();
+
return (