Migrate task persistence to SQLite and remove legacy JSON store

This commit is contained in:
OpenClaw Bot 2026-02-19 23:34:46 -06:00
parent eaf01cf634
commit 301156a4ea
8 changed files with 782 additions and 634 deletions

3
.gitignore vendored
View File

@ -23,6 +23,9 @@
# misc # misc
.DS_Store .DS_Store
*.pem *.pem
data/*.db
data/*.db-shm
data/*.db-wal
# debug # debug
npm-debug.log* npm-debug.log*

View File

@ -1,6 +1,6 @@
# Gantt Board # Gantt Board
Task and sprint board built with Next.js + Zustand and file-backed API persistence. Task and sprint board built with Next.js + Zustand and SQLite-backed API persistence.
## Current Product Behavior ## Current Product Behavior
@ -45,7 +45,7 @@ Status behavior on drop:
- Drop into backlog section: `status -> open` - Drop into backlog section: `status -> open`
- `sprintId` is set/cleared based on destination - `sprintId` is set/cleared based on destination
Changes persist through store sync to `data/tasks.json`. Changes persist through store sync to SQLite.
### Kanban drag and drop ### Kanban drag and drop
@ -75,7 +75,7 @@ During drag, the active target column shows expanded status drop zones for clari
- Client state is managed with Zustand. - Client state is managed with Zustand.
- Persistence is done via `/api/tasks`. - Persistence is done via `/api/tasks`.
- API reads/writes `data/tasks.json` (single-file storage). - API reads/writes `data/tasks.db` (SQLite).
## Run locally ## Run locally

View File

@ -1,509 +0,0 @@
{
"projects": [
{
"id": "1",
"name": "OpenClaw iOS",
"description": "Main iOS app development",
"color": "#8b5cf6",
"createdAt": "2026-02-18T17:01:23.109Z"
},
{
"id": "2",
"name": "Web Projects",
"description": "Web tools and dashboards",
"color": "#3b82f6",
"createdAt": "2026-02-18T17:01:23.109Z"
},
{
"id": "3",
"name": "Research",
"description": "Experiments and learning",
"color": "#10b981",
"createdAt": "2026-02-18T17:01:23.109Z"
}
],
"tasks": [
{
"id": "1",
"title": "Redesign Gantt Board",
"description": "Make it actually work with proper notes system",
"type": "task",
"status": "archived",
"priority": "high",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-20T05:14:09.324Z",
"comments": [
{
"id": "c1",
"text": "Need 1-to-many notes, not one big text field",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "user"
},
{
"id": "c2",
"text": "Agreed - will rebuild with proper comment threads",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"ui",
"rewrite",
"Web Projects"
]
},
{
"id": "2",
"title": "MoodWeave App Idea - UPDATED",
"projectId": "1",
"status": "open",
"priority": "high",
"type": "idea",
"comments": [],
"tags": [
"ios",
"social",
"OpenClaw iOS"
],
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-20T05:02:47.264Z"
},
{
"id": "3",
"title": "Set up Gitea integration for code pushes",
"description": "Create bot account on Gitea (192.168.1.128:3000) and configure git remotes for all OpenClaw projects.",
"type": "task",
"status": "done",
"priority": "medium",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c3",
"text": "User has local Gitea at http://192.168.1.128:3000",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
},
{
"id": "c7",
"text": "✅ All 3 repos created and pushed to Gitea: gantt-board, blog-backup, heartbeat-monitor",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"gitea",
"git",
"automation",
"infrastructure",
"Web Projects"
]
},
{
"id": "4",
"title": "Redesign Heartbeat Monitor to match UptimeRobot",
"description": "Completely redesign the Heartbeat Monitor website to be a competitor to https://uptimerobot.com.",
"type": "task",
"status": "done",
"priority": "high",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c29",
"text": "COMPLETED: Full rebuild with Next.js + shadcn/ui + Framer Motion. Dark OLED theme, glass-morphism cards, animated status indicators, sparkline visualizations, grid/list views, tooltips, progress bars. Production-grade at http://localhost:3005",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"ui",
"ux",
"redesign",
"dashboard",
"monitoring",
"Web Projects"
]
},
{
"id": "5",
"title": "Fix Blog Backup links to be clickable",
"description": "Make links in the Daily Digest clickable in the blog backup UI.",
"type": "task",
"status": "done",
"priority": "medium",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c41",
"text": "COMPLETED: Fixed parseDigest to extract URLs from markdown links [Title](url) in title lines",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
},
{
"id": "c42",
"text": "COMPLETED: Title is now the clickable link with external link icon on hover",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"blog",
"ui",
"markdown",
"links",
"Web Projects"
]
},
{
"id": "6",
"title": "Fix monitoring schedule - 2 of 3 sites are down",
"description": "The cron job running every 10 minutes to check heartbeat website is failing.",
"type": "bug",
"status": "done",
"priority": "urgent",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c16",
"text": "FIXED: Updated cron job with pkill cleanup before restart + 2s delay. Created backup script: monitor-restart.sh",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"monitoring",
"cron",
"bug",
"infrastructure",
"urgent",
"Web Projects"
]
},
{
"id": "7",
"title": "Investigate root cause - why are websites dying?",
"description": "Currently monitoring only treats the symptom (restart when down). Need to investigate what is actually killing the Next.js dev servers.",
"type": "research",
"status": "done",
"priority": "high",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c27",
"text": "COMPLETED: Root cause analysis done. Primary suspect: Next.js dev server memory leaks. Secondary: SSH timeout, OOM killer, power mgmt. Full report: root-cause-analysis.md. Monitoring script deployed.",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"debugging",
"research",
"infrastructure",
"root-cause",
"Web Projects"
]
},
{
"id": "8",
"title": "Fix Kanban board - dynamic sync without hard refresh",
"description": "Current board uses localStorage persistence which requires hard refresh (Cmd+Shift+R) to see task updates from code changes.",
"type": "task",
"status": "done",
"priority": "medium",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c44",
"text": "COMPLETED: Added /api/tasks endpoint with file-based storage",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
},
{
"id": "c45",
"text": "COMPLETED: Store now syncs from server on load and auto-syncs changes",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"ui",
"sync",
"localstorage",
"real-time",
"Web Projects"
]
},
{
"id": "9",
"title": "Add ability to edit task priority in Kanban board",
"description": "Currently users cannot change task priority (Low/Medium/High/Urgent) from the UI.",
"type": "task",
"status": "done",
"priority": "high",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c28",
"text": "COMPLETED: Added priority buttons to task detail dialog. Click any task to see Low/Medium/High/Urgent buttons with color coding. Changes apply immediately.",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"ui",
"kanban",
"feature",
"priority",
"Web Projects"
]
},
{
"id": "10",
"title": "RESEARCH: Find viable screenshot solution for OpenClaw on macOS",
"description": "INVESTIGATION NEEDED: Find a reliable, persistent way for OpenClaw AI to capture screenshots of local websites running on macOS.",
"type": "research",
"status": "done",
"priority": "high",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c39",
"text": "SUCCESS: Playwright + Google Chrome works! Successfully captured screenshot of http://localhost:3005 using headless Chrome",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
},
{
"id": "c47",
"text": "COMPLETED: Playwright installed globally. Screenshots now work anytime without setup.",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"research",
"screenshot",
"macos",
"openclaw",
"investigation",
"Web Projects"
]
},
{
"id": "11",
"title": "RESEARCH: Find iOS side projects with MRR potential",
"description": "Research and identify iOS app ideas that have strong Monthly Recurring Revenue (MRR) opportunities.",
"type": "research",
"status": "done",
"priority": "low",
"projectId": "3",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-20T05:02:42.002Z",
"comments": [
{
"id": "c52",
"text": "COMPLETED: Full research report saved to memory/ios-mrr-opportunities.md",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
},
{
"id": "c53",
"text": "TOP 10 IDEAS: (1) AI Translator Keyboard - $15K/mo potential, (2) Finance Widget Suite, (3) Focus Timer with Live Activities - RECOMMENDED, (4) AI Photo Enhancer, (5) Habit Tracker with Social, (6) Local Business Review Widget, (7) Audio Journal with Voice-to-Text, (8) Plant Care Tracker, (9) Sleep Sounds with HomeKit, (10) Family Password Manager",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"ios",
"mrr",
"research",
"side-project",
"entrepreneurship",
"app-ideas"
]
},
{
"id": "12",
"title": "Add markdown rendering to Blog Backup",
"description": "The blog backup page currently shows raw markdown text instead of rendered HTML.",
"type": "task",
"status": "done",
"priority": "high",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-18T17:01:23.109Z",
"updatedAt": "2026-02-18T17:01:23.109Z",
"comments": [
{
"id": "c58",
"text": "COMPLETED: Installed react-markdown and remark-gfm",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
},
{
"id": "c61",
"text": "COMPLETED: Links now open in new tab with blue styling and hover effects",
"createdAt": "2026-02-18T17:01:23.109Z",
"author": "assistant"
}
],
"tags": [
"blog",
"ui",
"markdown",
"frontend",
"Web Projects"
]
},
{
"id": "13",
"title": "Research TTS options for Daily Digest podcast",
"description": "Research free text-to-speech (TTS) tools to convert the daily digest blog posts into an audio podcast format.",
"type": "research",
"status": "open",
"priority": "medium",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-19T17:01:23.109Z",
"updatedAt": "2026-02-19T17:01:23.109Z",
"comments": [
{
"id": "c62",
"text": "Goal: Convert daily digest text to audio for dog walks",
"createdAt": "2026-02-19T17:01:23.109Z",
"author": "user"
},
{
"id": "c63",
"text": "Requirement: Free or very low cost solution",
"createdAt": "2026-02-19T17:01:23.109Z",
"author": "user"
}
],
"tags": [
"research",
"tts",
"podcast",
"audio",
"digest",
"accessibility",
"Web Projects"
]
},
{
"id": "14",
"title": "Implement daily data backup system",
"description": "Create automated daily backup of all web app data to Git. All 3 apps (gantt-board, blog-backup, heartbeat-monitor) now have data persistence with JSON files in data/ directories. Need daily backup cron job that commits data to Gitea to prevent data loss on server restarts.",
"type": "task",
"status": "done",
"priority": "high",
"projectId": "2",
"sprintId": "sprint-1",
"createdAt": "2026-02-19T13:00:00.000Z",
"updatedAt": "2026-02-19T13:00:00.000Z",
"comments": [
{
"id": "c66",
"text": "All 3 apps verified to have data/ directories with JSON persistence",
"createdAt": "2026-02-19T13:00:00.000Z",
"author": "assistant"
},
{
"id": "c67",
"text": "Created daily-backup.sh script to commit data to Git",
"createdAt": "2026-02-19T13:00:00.000Z",
"author": "assistant"
},
{
"id": "c68",
"text": "Added cron job for 11:00 PM CST daily backups",
"createdAt": "2026-02-19T13:00:00.000Z",
"author": "assistant"
},
{
"id": "c69",
"text": "Backup logs to memory/backup.log for monitoring",
"createdAt": "2026-02-19T13:00:00.000Z",
"author": "assistant"
}
],
"tags": [
"backup",
"infrastructure",
"data-persistence",
"automation",
"Web Projects"
]
},
{
"id": "15",
"priority": "urgent",
"title": "Add Sprint functionality to Gantt Board",
"projectId": "2",
"sprintId": "sprint-1",
"updatedAt": "2026-02-20T05:24:24.353Z",
"tags": [
"Web Projects"
],
"status": "todo"
}
],
"lastUpdated": 1771565064468,
"sprints": [
{
"name": "Sprint 1",
"goal": "Foundation and core features",
"startDate": "2026-02-16",
"endDate": "2026-02-22",
"status": "active",
"projectId": "2",
"sprintId": "sprint-1",
"id": "sprint-1",
"createdAt": "2026-02-16T00:00:00.000Z"
},
{
"name": "Sprint 2",
"goal": "",
"startDate": "2026-02-23",
"endDate": "2026-03-01",
"status": "planning",
"projectId": "1",
"id": "1771551323429",
"createdAt": "2026-02-20T01:35:23.429Z"
},
{
"name": "Sprint 3",
"goal": "",
"startDate": "2026-03-02",
"endDate": "2026-03-08",
"status": "planning",
"projectId": "1",
"id": "1771551465241",
"createdAt": "2026-02-20T01:37:45.241Z"
}
]
}

428
package-lock.json generated
View File

@ -16,11 +16,13 @@
"@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8", "@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6", "@radix-ui/react-select": "^2.2.6",
"better-sqlite3": "^12.6.2",
"firebase": "^12.9.0" "firebase": "^12.9.0"
}, },
"devDependencies": { "devDependencies": {
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/better-sqlite3": "^7.6.13",
"@types/frappe-gantt": "^0.9.0", "@types/frappe-gantt": "^0.9.0",
"@types/node": "^20.19.33", "@types/node": "^20.19.33",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",
@ -3663,6 +3665,16 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@types/better-sqlite3": {
"version": "7.6.13",
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/d3-array": { "node_modules/@types/d3-array": {
"version": "3.2.2", "version": "3.2.2",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz",
@ -4700,6 +4712,26 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.9.19", "version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
@ -4710,6 +4742,40 @@
"baseline-browser-mapping": "dist/cli.js" "baseline-browser-mapping": "dist/cli.js"
} }
}, },
"node_modules/better-sqlite3": {
"version": "12.6.2",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz",
"integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
},
"engines": {
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
}
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"license": "MIT",
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@ -4768,6 +4834,30 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@ -4866,6 +4956,12 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/class-variance-authority": { "node_modules/class-variance-authority": {
"version": "0.7.1", "version": "0.7.1",
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
@ -5193,6 +5289,30 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -5240,7 +5360,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@ -5294,6 +5413,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.19.0", "version": "5.19.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
@ -5997,6 +6125,15 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
},
"node_modules/fast-deep-equal": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -6083,6 +6220,12 @@
"node": ">=16.0.0" "node": ">=16.0.0"
} }
}, },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"license": "MIT"
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -6235,6 +6378,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@ -6399,6 +6548,12 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
} }
}, },
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@ -6585,6 +6740,26 @@
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -6633,6 +6808,18 @@
"node": ">=0.8.19" "node": ">=0.8.19"
} }
}, },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/internal-slot": { "node_modules/internal-slot": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@ -7624,6 +7811,18 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -7641,12 +7840,17 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/motion-dom": { "node_modules/motion-dom": {
"version": "12.34.1", "version": "12.34.1",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.1.tgz", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.34.1.tgz",
@ -7690,6 +7894,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
"node_modules/napi-postinstall": { "node_modules/napi-postinstall": {
"version": "0.3.4", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
@ -7795,6 +8005,30 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/node-abi": {
"version": "3.87.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-abi/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-exports-info": { "node_modules/node-exports-info": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz", "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
@ -7944,6 +8178,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@ -8118,6 +8361,33 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -8171,6 +8441,16 @@
"node": ">=12.0.0" "node": ">=12.0.0"
} }
}, },
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -8202,6 +8482,30 @@
], ],
"license": "MIT" "license": "MIT"
}, },
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/rc/node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.2.4", "version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
@ -8324,6 +8628,20 @@
} }
} }
}, },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/recharts": { "node_modules/recharts": {
"version": "3.7.0", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz", "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz",
@ -8807,6 +9125,51 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -8838,6 +9201,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@ -9088,6 +9460,34 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tiny-invariant": { "node_modules/tiny-invariant": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
@ -9221,6 +9621,18 @@
"fsevents": "~2.3.3" "fsevents": "~2.3.3"
} }
}, },
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@ -9504,6 +9916,12 @@
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/victory-vendor": { "node_modules/victory-vendor": {
"version": "37.3.6", "version": "37.3.6",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz",
@ -9688,6 +10106,12 @@
"url": "https://github.com/chalk/wrap-ansi?sponsor=1" "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
} }
}, },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/y18n": { "node_modules/y18n": {
"version": "5.0.8", "version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -16,11 +16,13 @@
"@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.8", "@radix-ui/react-label": "^2.1.8",
"@radix-ui/react-select": "^2.2.6", "@radix-ui/react-select": "^2.2.6",
"better-sqlite3": "^12.6.2",
"firebase": "^12.9.0" "firebase": "^12.9.0"
}, },
"devDependencies": { "devDependencies": {
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/better-sqlite3": "^7.6.13",
"@types/frappe-gantt": "^0.9.0", "@types/frappe-gantt": "^0.9.0",
"@types/node": "^20.19.33", "@types/node": "^20.19.33",
"@types/react": "^19.2.14", "@types/react": "^19.2.14",

View File

@ -1,129 +1,36 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"; import { getData, saveData, type DataStore, type Task } from "@/lib/server/taskDb";
import { join } from "path";
const DATA_FILE = join(process.cwd(), "data", "tasks.json"); export const runtime = "nodejs";
console.log('>>> API ROUTE: module loaded, process.cwd():', process.cwd());
console.log('>>> API ROUTE: DATA_FILE:', DATA_FILE);
interface Task {
id: string;
title: string;
description?: string;
type: 'idea' | 'task' | 'bug' | 'research' | 'plan';
status: 'open' | 'todo' | 'blocked' | 'in-progress' | 'review' | 'validate' | 'archived' | 'canceled' | 'done';
priority: 'low' | 'medium' | 'high' | 'urgent';
projectId: string;
sprintId?: string;
createdAt: string;
updatedAt: string;
dueDate?: string;
comments: { id: string; text: string; createdAt: string; author: 'user' | 'assistant' }[];
tags: string[];
}
interface Project {
id: string;
name: string;
description?: string;
color: string;
createdAt: string;
}
interface Sprint {
id: string;
name: string;
goal?: string;
startDate: string;
endDate: string;
status: 'planning' | 'active' | 'completed';
projectId: string;
createdAt: string;
}
interface DataStore {
projects: Project[];
tasks: Task[];
sprints: Sprint[];
lastUpdated: number;
}
const defaultData: DataStore = {
projects: [
{ id: '1', name: 'OpenClaw iOS', description: 'Main iOS app development', color: '#8b5cf6', createdAt: new Date().toISOString() },
{ id: '2', name: 'Web Projects', description: 'Web tools and dashboards', color: '#3b82f6', createdAt: new Date().toISOString() },
{ id: '3', name: 'Research', description: 'Experiments and learning', color: '#10b981', createdAt: new Date().toISOString() },
],
tasks: [],
sprints: [],
lastUpdated: Date.now(),
};
function getData(): DataStore {
console.log('>>> getData: checking file:', DATA_FILE);
console.log('>>> getData: exists?', existsSync(DATA_FILE));
if (!existsSync(DATA_FILE)) {
console.log('>>> getData: file not found, returning defaultData');
return defaultData;
}
try {
const rawData = readFileSync(DATA_FILE, "utf-8");
console.log('>>> getData: read file, length:', rawData.length);
const data = JSON.parse(rawData);
console.log('>>> getData: parsed data has', data.tasks?.length, 'tasks');
return data;
} catch (err) {
console.error('>>> getData: error reading/parsing file:', err);
return defaultData;
}
}
function saveData(data: DataStore) {
const dir = join(process.cwd(), "data");
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
data.lastUpdated = Date.now();
writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
}
// GET - fetch all tasks, projects, and sprints // GET - fetch all tasks, projects, and sprints
export async function GET() { export async function GET() {
console.log('>>> API GET: fetching data'); try {
console.log('>>> API GET: DATA_FILE path:', DATA_FILE); const data = getData();
const data = getData(); return NextResponse.json(data);
console.log('>>> API GET: returning data with', data.tasks?.length, 'tasks,', data.projects?.length, 'projects,', data.sprints?.length, 'sprints'); } catch (error) {
console.log('>>> API GET: lastUpdated:', data.lastUpdated); console.error(">>> API GET: database error:", error);
return NextResponse.json(data); return NextResponse.json({ error: "Failed to fetch data" }, { status: 500 });
}
} }
// POST - create or update tasks, projects, or sprints // POST - create or update tasks, projects, or sprints
export async function POST(request: Request) { export async function POST(request: Request) {
try { try {
const body = await request.json(); const body = await request.json();
console.log('>>> API POST: received body keys:', Object.keys(body)); const { task, tasks, projects, sprints } = body as {
console.log('>>> API POST: has task?', !!body.task, '| has tasks?', !!body.tasks, '| has projects?', !!body.projects, '| has sprints?', !!body.sprints); task?: Task;
console.log('>>> API POST: tasks array length:', body.tasks?.length); tasks?: Task[];
projects?: DataStore["projects"];
sprints?: DataStore["sprints"];
};
const { task, tasks, projects, sprints } = body;
const data = getData(); const data = getData();
console.log('>>> API POST: current data in file - tasks:', data.tasks?.length, 'projects:', data.projects?.length, 'sprints:', data.sprints?.length);
// Update projects if provided if (projects) data.projects = projects;
if (projects) { if (sprints) data.sprints = sprints;
console.log('>>> API POST: updating projects:', projects.length);
data.projects = projects;
}
// Update sprints if provided
if (sprints) {
console.log('>>> API POST: updating sprints:', sprints.length);
data.sprints = sprints;
}
// Update or add single task
if (task) { if (task) {
console.log('>>> API POST: updating single task:', task.id);
const existingIndex = data.tasks.findIndex((t) => t.id === task.id); const existingIndex = data.tasks.findIndex((t) => t.id === task.id);
if (existingIndex >= 0) { if (existingIndex >= 0) {
data.tasks[existingIndex] = { ...task, updatedAt: new Date().toISOString() }; data.tasks[existingIndex] = { ...task, updatedAt: new Date().toISOString() };
@ -137,17 +44,14 @@ export async function POST(request: Request) {
} }
} }
// Update all tasks if tasks array provided
if (tasks && Array.isArray(tasks)) { if (tasks && Array.isArray(tasks)) {
console.log('>>> API POST: updating ALL tasks array:', tasks.length);
data.tasks = tasks; data.tasks = tasks;
} }
saveData(data); const saved = saveData(data);
console.log('>>> API POST: saved! New task count:', data.tasks.length); return NextResponse.json({ success: true, data: saved });
return NextResponse.json({ success: true, data });
} catch (error) { } catch (error) {
console.error('>>> API POST: error:', error); console.error(">>> API POST: database error:", error);
return NextResponse.json({ error: "Failed to save" }, { status: 500 }); return NextResponse.json({ error: "Failed to save" }, { status: 500 });
} }
} }
@ -155,12 +59,14 @@ export async function POST(request: Request) {
// DELETE - remove a task // DELETE - remove a task
export async function DELETE(request: Request) { export async function DELETE(request: Request) {
try { try {
const { id } = await request.json(); const { id } = (await request.json()) as { id: string };
const data = getData(); const data = getData();
data.tasks = data.tasks.filter((t) => t.id !== id); data.tasks = data.tasks.filter((t) => t.id !== id);
saveData(data); saveData(data);
return NextResponse.json({ success: true }); return NextResponse.json({ success: true });
} catch { } catch (error) {
console.error(">>> API DELETE: database error:", error);
return NextResponse.json({ error: "Failed to delete" }, { status: 500 }); return NextResponse.json({ error: "Failed to delete" }, { status: 500 });
} }
} }

View File

@ -185,13 +185,12 @@ function KanbanTaskCard({
onOpen: () => void onOpen: () => void
onDelete: () => void onDelete: () => void
}) { }) {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useDraggable({ const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
id: task.id, id: task.id,
}) })
const style = { const style = {
transform: CSS.Translate.toString(transform), transform: CSS.Translate.toString(transform),
transition,
opacity: isDragging ? 0.6 : 1, opacity: isDragging ? 0.6 : 1,
} }

323
src/lib/server/taskDb.ts Normal file
View File

@ -0,0 +1,323 @@
import Database from "better-sqlite3";
import { mkdirSync } from "fs";
import { join } from "path";
export interface Task {
id: string;
title: string;
description?: string;
type: "idea" | "task" | "bug" | "research" | "plan";
status: "open" | "todo" | "blocked" | "in-progress" | "review" | "validate" | "archived" | "canceled" | "done";
priority: "low" | "medium" | "high" | "urgent";
projectId: string;
sprintId?: string;
createdAt: string;
updatedAt: string;
dueDate?: string;
comments: { id: string; text: string; createdAt: string; author: "user" | "assistant" }[];
tags: string[];
}
export interface Project {
id: string;
name: string;
description?: string;
color: string;
createdAt: string;
}
export interface Sprint {
id: string;
name: string;
goal?: string;
startDate: string;
endDate: string;
status: "planning" | "active" | "completed";
projectId: string;
createdAt: string;
}
export interface DataStore {
projects: Project[];
tasks: Task[];
sprints: Sprint[];
lastUpdated: number;
}
const DATA_DIR = join(process.cwd(), "data");
const DB_FILE = join(DATA_DIR, "tasks.db");
const defaultData: DataStore = {
projects: [
{ id: "1", name: "OpenClaw iOS", description: "Main iOS app development", color: "#8b5cf6", createdAt: new Date().toISOString() },
{ id: "2", name: "Web Projects", description: "Web tools and dashboards", color: "#3b82f6", createdAt: new Date().toISOString() },
{ id: "3", name: "Research", description: "Experiments and learning", color: "#10b981", createdAt: new Date().toISOString() },
],
tasks: [],
sprints: [],
lastUpdated: Date.now(),
};
type SqliteDb = InstanceType<typeof Database>;
let db: SqliteDb | null = null;
function safeParseArray<T>(value: string | null, fallback: T[]): T[] {
if (!value) return fallback;
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? (parsed as T[]) : fallback;
} catch {
return fallback;
}
}
function normalizeTask(task: Partial<Task>): Task {
return {
id: String(task.id ?? Date.now()),
title: String(task.title ?? ""),
description: task.description || undefined,
type: (task.type as Task["type"]) ?? "task",
status: (task.status as Task["status"]) ?? "open",
priority: (task.priority as Task["priority"]) ?? "medium",
projectId: String(task.projectId ?? "2"),
sprintId: task.sprintId || undefined,
createdAt: task.createdAt || new Date().toISOString(),
updatedAt: task.updatedAt || new Date().toISOString(),
dueDate: task.dueDate || undefined,
comments: Array.isArray(task.comments) ? task.comments : [],
tags: Array.isArray(task.tags) ? task.tags.filter((tag): tag is string => typeof tag === "string") : [],
};
}
function setLastUpdated(database: SqliteDb, value: number) {
database
.prepare(`
INSERT INTO meta (key, value)
VALUES ('lastUpdated', ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value
`)
.run(String(value));
}
function getLastUpdated(database: SqliteDb): number {
const row = database.prepare("SELECT value FROM meta WHERE key = 'lastUpdated'").get() as { value?: string } | undefined;
const parsed = Number(row?.value ?? Date.now());
return Number.isFinite(parsed) ? parsed : Date.now();
}
function replaceAllData(database: SqliteDb, data: DataStore) {
const write = database.transaction((payload: DataStore) => {
database.exec("DELETE FROM projects;");
database.exec("DELETE FROM sprints;");
database.exec("DELETE FROM tasks;");
const insertProject = database.prepare(`
INSERT INTO projects (id, name, description, color, createdAt)
VALUES (@id, @name, @description, @color, @createdAt)
`);
const insertSprint = database.prepare(`
INSERT INTO sprints (id, name, goal, startDate, endDate, status, projectId, createdAt)
VALUES (@id, @name, @goal, @startDate, @endDate, @status, @projectId, @createdAt)
`);
const insertTask = database.prepare(`
INSERT INTO tasks (id, title, description, type, status, priority, projectId, sprintId, createdAt, updatedAt, dueDate, comments, tags)
VALUES (@id, @title, @description, @type, @status, @priority, @projectId, @sprintId, @createdAt, @updatedAt, @dueDate, @comments, @tags)
`);
for (const project of payload.projects) {
insertProject.run({
id: project.id,
name: project.name,
description: project.description ?? null,
color: project.color,
createdAt: project.createdAt,
});
}
for (const sprint of payload.sprints) {
insertSprint.run({
id: sprint.id,
name: sprint.name,
goal: sprint.goal ?? null,
startDate: sprint.startDate,
endDate: sprint.endDate,
status: sprint.status,
projectId: sprint.projectId,
createdAt: sprint.createdAt,
});
}
for (const task of payload.tasks.map(normalizeTask)) {
insertTask.run({
...task,
sprintId: task.sprintId ?? null,
dueDate: task.dueDate ?? null,
comments: JSON.stringify(task.comments ?? []),
tags: JSON.stringify(task.tags ?? []),
});
}
setLastUpdated(database, payload.lastUpdated || Date.now());
});
write(data);
}
function seedIfEmpty(database: SqliteDb) {
const counts = database
.prepare(
`
SELECT
(SELECT COUNT(*) FROM projects) AS projectsCount,
(SELECT COUNT(*) FROM sprints) AS sprintsCount,
(SELECT COUNT(*) FROM tasks) AS tasksCount
`
)
.get() as { projectsCount: number; sprintsCount: number; tasksCount: number };
if (counts.projectsCount > 0 || counts.sprintsCount > 0 || counts.tasksCount > 0) return;
replaceAllData(database, defaultData);
}
function getDb(): SqliteDb {
if (db) return db;
mkdirSync(DATA_DIR, { recursive: true });
const database = new Database(DB_FILE);
database.pragma("journal_mode = WAL");
database.exec(`
CREATE TABLE IF NOT EXISTS projects (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
description TEXT,
color TEXT NOT NULL,
createdAt TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS sprints (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
goal TEXT,
startDate TEXT NOT NULL,
endDate TEXT NOT NULL,
status TEXT NOT NULL,
projectId TEXT NOT NULL,
createdAt TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
type TEXT NOT NULL,
status TEXT NOT NULL,
priority TEXT NOT NULL,
projectId TEXT NOT NULL,
sprintId TEXT,
createdAt TEXT NOT NULL,
updatedAt TEXT NOT NULL,
dueDate TEXT,
comments TEXT NOT NULL DEFAULT '[]',
tags TEXT NOT NULL DEFAULT '[]'
);
CREATE TABLE IF NOT EXISTS meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
`);
seedIfEmpty(database);
db = database;
return database;
}
export function getData(): DataStore {
const database = getDb();
const projects = database.prepare("SELECT * FROM projects ORDER BY createdAt ASC").all() as Array<{
id: string;
name: string;
description: string | null;
color: string;
createdAt: string;
}>;
const sprints = database.prepare("SELECT * FROM sprints ORDER BY startDate ASC").all() as Array<{
id: string;
name: string;
goal: string | null;
startDate: string;
endDate: string;
status: Sprint["status"];
projectId: string;
createdAt: string;
}>;
const tasks = database.prepare("SELECT * FROM tasks ORDER BY createdAt ASC").all() as Array<{
id: string;
title: string;
description: string | null;
type: Task["type"];
status: Task["status"];
priority: Task["priority"];
projectId: string;
sprintId: string | null;
createdAt: string;
updatedAt: string;
dueDate: string | null;
comments: string | null;
tags: string | null;
}>;
return {
projects: projects.map((project) => ({
id: project.id,
name: project.name,
description: project.description ?? undefined,
color: project.color,
createdAt: project.createdAt,
})),
sprints: sprints.map((sprint) => ({
id: sprint.id,
name: sprint.name,
goal: sprint.goal ?? undefined,
startDate: sprint.startDate,
endDate: sprint.endDate,
status: sprint.status,
projectId: sprint.projectId,
createdAt: sprint.createdAt,
})),
tasks: tasks.map((task) => ({
id: task.id,
title: task.title,
description: task.description ?? undefined,
type: task.type,
status: task.status,
priority: task.priority,
projectId: task.projectId,
sprintId: task.sprintId ?? undefined,
createdAt: task.createdAt,
updatedAt: task.updatedAt,
dueDate: task.dueDate ?? undefined,
comments: safeParseArray(task.comments, []),
tags: safeParseArray(task.tags, []),
})),
lastUpdated: getLastUpdated(database),
};
}
export function saveData(data: DataStore): DataStore {
const database = getDb();
const payload: DataStore = {
...data,
projects: data.projects ?? [],
sprints: data.sprints ?? [],
tasks: (data.tasks ?? []).map(normalizeTask),
lastUpdated: Date.now(),
};
replaceAllData(database, payload);
return getData();
}