From adecb66fa896835bc52a564a840a155ca1980426 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 15:04:21 -0800 Subject: [PATCH 01/12] docs: Replace placeholder content with comprehensive documentation - Replace Lorem Ipsum home.md with proper welcome page - Add getting-started.md: New user onboarding guide - Add faq.md: FAQ and troubleshooting for offline use - Add use-cases.md: Use case examples (emergency prep, homeschool, etc.) Documentation written with non-technical users in mind, focusing on clarity and self-sufficiency when offline. Co-Authored-By: Claude Opus 4.5 --- admin/docs/faq.md | 168 ++++++++++++++++++++++++++++++ admin/docs/getting-started.md | 185 +++++++++++++++++++++++++++++++++ admin/docs/home.md | 110 ++++++++++---------- admin/docs/use-cases.md | 190 ++++++++++++++++++++++++++++++++++ 4 files changed, 596 insertions(+), 57 deletions(-) create mode 100644 admin/docs/faq.md create mode 100644 admin/docs/getting-started.md create mode 100644 admin/docs/use-cases.md diff --git a/admin/docs/faq.md b/admin/docs/faq.md new file mode 100644 index 0000000..050d903 --- /dev/null +++ b/admin/docs/faq.md @@ -0,0 +1,168 @@ +# Frequently Asked Questions + +## General Questions + +### What is N.O.M.A.D.? +N.O.M.A.D. (Node for Offline Media, Archives, and Data) is a personal server that gives you access to knowledge, education, and AI assistance without requiring an internet connection. It runs on your own hardware, keeping your data private and accessible anytime. + +### Do I need internet to use N.O.M.A.D.? +No — that's the whole point. Once your content is downloaded, everything works offline. You only need internet to: +- Download new content +- Update the software +- Sync the latest versions of Wikipedia, maps, etc. + +### What hardware do I need? +N.O.M.A.D. is designed for capable hardware, especially if you want to use the AI features. Recommended: +- Modern multi-core CPU +- 16GB+ RAM (32GB+ for best AI performance) +- SSD storage (size depends on content — 500GB minimum, 2TB+ recommended) +- GPU recommended for faster AI responses + +### How much storage do I need? +It depends on what you download: +- Full Wikipedia: ~95GB +- Khan Academy courses: ~50GB +- Medical references: ~500MB +- US state maps: ~2-3GB each +- AI models: 10-40GB depending on model + +Start with essentials and add more as needed. + +--- + +## Content Questions + +### How do I add more Wikipedia content? +1. Go to **Settings** (hamburger menu → Settings) +2. Click **ZIM Manager** +3. Browse available content +4. Click Download on items you want + +### How do I add more educational courses? +1. Open **Kolibri** +2. Sign in as an admin +3. Go to **Device → Channels** +4. Browse and import available channels + +### How current is the content? +Content is as current as when it was last downloaded. Wikipedia snapshots are typically updated monthly. Check the file names or descriptions for dates. + +### Can I add my own files? +Currently, N.O.M.A.D. uses standard content formats (ZIM files for Kiwix, Kolibri channels for education). Custom content support may be added in future versions. + +--- + +## Troubleshooting + +### A feature isn't loading or shows a blank page + +**Try these steps:** +1. Wait 30 seconds — some features take time to start +2. Refresh the page (Ctrl+R or Cmd+R) +3. Go back to the Command Center and try again +4. Check Settings → System to see if the service is running +5. Try restarting the service (Stop, then Start in Apps manager) + +### Maps show a gray/blank area + +The Maps feature requires downloaded map data. If you see a blank area: +1. Go to **Settings → Maps Manager** +2. Download map regions for your area +3. Wait for downloads to complete +4. Return to Maps and refresh + +### AI responses are slow + +Local AI requires significant computing power. To improve speed: +- Close other applications on the server +- Ensure adequate cooling (overheating causes throttling) +- Consider using a smaller/faster AI model if available +- Add a GPU if your hardware supports it + +### "Service unavailable" or connection errors + +The service might still be starting up. Wait 1-2 minutes and try again. + +If the problem persists: +1. Go to **Settings → Apps** +2. Find the problematic service +3. Click **Restart** +4. Wait 30 seconds, then try again + +### Downloads are stuck or failing + +1. Check your internet connection +2. Go to **Settings** and check available storage +3. If storage is full, delete unused content +4. Cancel the stuck download and try again + +### The server won't start + +If you can't access the Command Center at all: +1. Verify the server hardware is powered on +2. Check network connectivity +3. Try accessing directly via the server's IP address +4. Check server logs if you have console access + +### I forgot my Kolibri password + +Kolibri passwords are managed separately: +1. If you're an admin, you can reset user passwords in Kolibri's user management +2. If you forgot the admin password, you may need to reset it via command line (contact your administrator) + +--- + +## Updates and Maintenance + +### How do I update N.O.M.A.D.? +1. Go to **Settings → Check for Updates** +2. If an update is available, click to install +3. The system will download updates and restart automatically +4. This typically takes 2-5 minutes + +### Should I update regularly? +Yes, while you have internet access. Updates include: +- Bug fixes +- New features +- Security improvements +- Performance enhancements + +### How do I update content (Wikipedia, etc.)? +Content updates are separate from software updates: +1. Go to **Settings → ZIM Manager** +2. Check for newer versions of your installed content +3. Download updated versions as needed + +Tip: New Wikipedia snapshots are released approximately monthly. + +### What happens if an update fails? +The system is designed to recover gracefully. If an update fails: +1. The previous version should continue working +2. Try the update again later +3. Check Settings → System for error messages + +--- + +## Privacy and Security + +### Is my data private? +Yes. N.O.M.A.D. runs entirely on your hardware. Your searches, AI conversations, and usage data never leave your server. + +### Can others access my server? +By default, N.O.M.A.D. is accessible on your local network. Anyone on the same network can access it. For public networks, consider additional security measures. + +### Does the AI send data anywhere? +No. The AI runs completely locally. Your conversations are not sent to any external service. + +--- + +## Getting More Help + +### The AI can help +Try asking Open WebUI for help. The local AI can answer questions about many topics, including technical troubleshooting. + +### Check the documentation +You're in the docs now. Use the menu to find specific topics. + +### Release Notes +See what's changed in each version: **[Release Notes](/docs/release-notes)** diff --git a/admin/docs/getting-started.md b/admin/docs/getting-started.md new file mode 100644 index 0000000..ff1ab01 --- /dev/null +++ b/admin/docs/getting-started.md @@ -0,0 +1,185 @@ +# Getting Started with N.O.M.A.D. + +This guide will help you set up your N.O.M.A.D. server and start using it effectively. + +## First Steps + +### 1. Run the Easy Setup Wizard + +If this is your first time using N.O.M.A.D., the Easy Setup wizard will help you: +- Choose which apps to enable +- Download map regions for your area +- Select knowledge collections (Wikipedia, medical references, etc.) + +**[Launch Easy Setup →](/easy-setup)** + +The wizard walks you through four simple steps: +1. **Apps** — Choose additional tools like CyberChef or FlatNotes +2. **Maps** — Select geographic regions for offline maps +3. **ZIM Files** — Choose reference collections (Wikipedia, medical, survival guides) +4. **Review** — Confirm your selections and start downloading + +### 2. Wait for Downloads to Complete + +Depending on what you selected, downloads may take a while. You can: +- Monitor progress in the Settings area +- Continue using features that are already installed +- Leave your server running overnight for large downloads + +### 3. Explore Your Content + +Once downloads complete, you're ready to go. Your content works offline whenever you need it. + +--- + +## Understanding Your Tools + +### Kiwix — Your Offline Library + +Kiwix stores compressed versions of websites and references that work without internet. + +**What's included:** +- Full Wikipedia (millions of articles) +- Medical references and first aid guides +- How-to guides and survival information +- Classic books from Project Gutenberg + +**How to use it:** +1. Click **Kiwix** from the Command Center +2. Choose a collection (like Wikipedia) +3. Search or browse just like the regular website + +**[Open Kiwix →](/kiwix)** + +--- + +### Kolibri — Offline Education + +Kolibri provides complete educational courses that work offline. + +**What's included:** +- Khan Academy video courses +- Math, science, reading, and more +- Progress tracking for learners +- Works for all ages + +**How to use it:** +1. Click **Kolibri** from the Command Center +2. Sign in or create a learner account +3. Browse courses and start learning + +**Tip:** Kolibri supports multiple users. Create accounts for each family member to track individual progress. + +**[Open Kolibri →](/kolibri)** + +--- + +### Open WebUI — Your AI Assistant + +Chat with a local AI that runs entirely on your server — no internet needed. + +**What can it do:** +- Answer questions on any topic +- Explain complex concepts simply +- Help with writing and editing +- Brainstorm ideas +- Assist with problem-solving + +**How to use it:** +1. Click **Open WebUI** from the Command Center +2. Type your question or request +3. The AI responds in conversational style + +**Tip:** Be specific in your questions. Instead of "tell me about plants," try "what vegetables grow well in shade?" + +**[Open AI Chat →](/openwebui)** + +--- + +### Maps — Offline Navigation + +View maps without internet. Download the regions you need before going offline. + +**How to use it:** +1. Click **Maps** from the Command Center +2. Navigate by dragging and zooming +3. Search for locations using the search bar + +**To add more map regions:** +1. Go to **Settings → Maps Manager** +2. Select the regions you need +3. Click Download + +**Tip:** Download maps for areas you travel to frequently, plus neighboring regions just in case. + +**[Open Maps →](/maps)** + +--- + +## Managing Your Server + +### Adding More Content + +As your needs change, you can add more content anytime: + +- **More apps:** Settings → Apps +- **More references:** Settings → ZIM Manager +- **More map regions:** Settings → Maps Manager +- **More educational content:** Through Kolibri's built-in content browser + +### Keeping Things Updated + +While you have internet, periodically check for updates: + +1. Go to **Settings → Check for Updates** +2. If updates are available, click to install +3. Wait for the update to complete (your server will restart) + +Content updates (Wikipedia, maps, etc.) can be managed separately from software updates. + +### Monitoring System Health + +Check on your server anytime: + +1. Go to **Settings → System** +2. View CPU, memory, and storage usage +3. Check system uptime and status + +--- + +## Tips for Best Results + +### Before Going Offline + +- **Update everything** — Run software and content updates +- **Download what you need** — Maps, references, educational content +- **Test it** — Make sure features work while you still have internet to troubleshoot + +### Storage Management + +Your server has limited storage. Prioritize: +- Content you'll actually use +- Critical references (medical, survival) +- Maps for your region +- Educational content matching your needs + +Check storage usage in **Settings → System**. + +### Getting Help + +- **In-app docs:** You're reading them now +- **AI assistant:** Ask Open WebUI for help with almost anything +- **Release notes:** See what's new in each version + +--- + +## Next Steps + +You're ready to use N.O.M.A.D. Here are some things to try: + +1. **Look something up** — Search for a topic in Kiwix +2. **Learn something** — Start a Khan Academy course in Kolibri +3. **Ask a question** — Chat with the AI in Open WebUI +4. **Explore maps** — Find your neighborhood in the Maps viewer + +Enjoy your offline knowledge server! diff --git a/admin/docs/home.md b/admin/docs/home.md index 1516eac..cc83b86 100644 --- a/admin/docs/home.md +++ b/admin/docs/home.md @@ -1,71 +1,67 @@ -# Lorem Ipsum Markdown Showcase +# Welcome to Project N.O.M.A.D. + +Your personal offline knowledge server is ready to use. + +## What is N.O.M.A.D.? + +**N.O.M.A.D.** stands for **Node for Offline Media, Archives, and Data**. It's your personal server for accessing knowledge, education, and AI assistance — even when you have no internet connection. + +Think of it as having Wikipedia, Khan Academy, an AI assistant, and offline maps all in one place, running on hardware you control. + +## What Can You Do? + +### Browse Offline Knowledge +Access millions of Wikipedia articles, medical references, how-to guides, and ebooks — all stored locally on your server. No internet required. + +**[Open Kiwix →](/kiwix)** + +### Learn Something New +Khan Academy courses covering math, science, economics, and more. Complete with videos and exercises, all available offline. + +**[Open Kolibri →](/kolibri)** + +### Chat with AI +Ask questions, get explanations, brainstorm ideas, or get help with writing. Your local AI assistant works completely offline. + +**[Open AI Chat →](/openwebui)** + +### View Offline Maps +Navigate and explore maps without an internet connection. Download regions you need before going offline. + +**[Open Maps →](/maps)** --- -## Introduction +## Getting Started -This document serves as a comprehensive example of **Markdown's various formatting possibilities**, using the classic *Lorem Ipsum* text as its content. From basic text styling to lists, code blocks, and tables, you'll find a demonstration of common Markdown features here. +**New to N.O.M.A.D.?** Use the Easy Setup wizard to configure your server and download content collections. + +**[Run Easy Setup →](/easy-setup)** + +Or explore the **[Getting Started Guide](/docs/getting-started)** for a walkthrough of all features. --- -## Basic Text Formatting +## Quick Links -Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. - -* This text is **bold**. -* This text is *italic*. -* This text is ***bold and italic***. -* This text is ~~struck through~~. -* You can also use `backticks` for `inline code`. +| I want to... | Go here | +|--------------|---------| +| Download more content | [Install Apps](/apps) | +| Add Wikipedia/reference content | [ZIM Manager](/settings/zim-manager) | +| Download map regions | [Maps Manager](/settings/maps-manager) | +| Check for updates | [System Update](/settings/updates) | +| View system status | [Settings](/settings) | --- -## Headers +## Keeping Your Server Updated -Markdown supports up to six levels of headers. +N.O.M.A.D. works best when kept up to date while you have internet access. This ensures you have the latest: +- Software features and bug fixes +- Wikipedia and reference content +- Educational materials +- AI model improvements -# Header 1 -## Header 2 -### Header 3 -#### Header 4 -##### Header 5 -###### Header 6 +When you go offline, you'll have everything you need — the last synced versions of all your content. ---- - -## Lists - -### Unordered List - -* Lorem ipsum dolor sit amet. - * Consectetur adipiscing elit. - * Sed do eiusmod tempor. -* Incididunt ut labore et dolore magna. -* Aliqua ut enim ad minim veniam. - -### Ordered List - -1. Quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. -2. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. -3. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - ---- - -## Blockquotes - -> "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." -> -> — John Doe, *Lorem Ipsum Anthology* - ---- - -## Code Blocks - -```python -def fibonacci(n): - a, b = 0, 1 - for i in range(n): - print(a, end=" ") - a, b = b, a + b - -fibonacci(10) \ No newline at end of file +**[Check for Updates →](/settings/updates)** diff --git a/admin/docs/use-cases.md b/admin/docs/use-cases.md new file mode 100644 index 0000000..6f69192 --- /dev/null +++ b/admin/docs/use-cases.md @@ -0,0 +1,190 @@ +# What Can You Do With N.O.M.A.D.? + +N.O.M.A.D. is designed to be your information lifeline when internet isn't available. Here's how different people use it. + +--- + +## Emergency Preparedness + +When disasters strike, internet and cell service often go down first. N.O.M.A.D. keeps critical information at your fingertips. + +**What you can do:** +- Look up first aid and emergency medical procedures +- Access survival guides and emergency protocols +- Find information about water purification, food storage, shelter building +- Use offline maps to navigate when GPS services are degraded +- Research plant identification, weather patterns, radio frequencies + +**Recommended content:** +- Medical Library ZIM collection +- Survival/Prepper reference guides +- Maps for your region and evacuation routes +- Wikipedia (searchable for almost any topic) + +--- + +## Homeschooling and Education + +Teach your children anywhere, with or without internet. Complete curriculum available offline. + +**What you can do:** +- Access Khan Academy's full course library (math, science, reading, history) +- Track progress for multiple students +- Supplement with Wikipedia for research projects +- Use the AI as a patient tutor for any subject +- Access classic literature through Project Gutenberg + +**Recommended content:** +- Khan Academy courses via Kolibri +- Wikipedia for Schools (curated for younger learners) +- Project Gutenberg (classic books) +- Educational ZIM collections + +**Tip:** Create separate Kolibri accounts for each child to track their individual progress. + +--- + +## Off-Grid Living + +Living away from reliable internet doesn't mean living without information. + +**What you can do:** +- Research DIY projects and repairs +- Look up gardening, animal husbandry, food preservation +- Access medical references for remote healthcare +- Learn new skills through educational videos +- Get AI help with planning and problem-solving + +**Recommended content:** +- How-to and DIY reference collections +- Medical and first aid guides +- Agricultural and homesteading references +- Maps for your rural area +- Practical skills courses in Kolibri + +--- + +## Remote Work Sites + +Construction sites, research stations, ships, and remote facilities often lack reliable internet. + +**What you can do:** +- Access technical references and documentation +- Use AI for writing assistance and analysis +- Look up regulations, standards, and procedures +- Provide educational resources for workers +- Maintain communication records with note-taking apps + +**Recommended content:** +- Industry-specific technical references +- Relevant Wikipedia categories +- Maps of work areas +- Documentation and compliance guides + +--- + +## Travel and Expeditions + +International travel, cruises, camping trips — stay informed anywhere. + +**What you can do:** +- Access maps without expensive roaming data +- Research destinations, history, and culture +- Translate concepts with AI assistance +- Identify plants, animals, and geological features +- Access travel health information + +**Recommended content:** +- Maps for destination countries/regions +- Wikipedia in relevant languages +- Medical/health references +- Cultural and historical content + +--- + +## Privacy-Conscious Users + +Some people simply prefer to keep their searches and questions private. + +**What you can do:** +- Search Wikipedia without being tracked +- Ask AI questions that stay on your own hardware +- Learn about sensitive topics privately +- Keep your intellectual curiosity to yourself + +**How it works:** +- All data stays on your server +- No search history sent to companies +- No AI conversations leave your network +- You control your own information + +--- + +## Medical Reference + +When you can't reach a doctor, having reliable medical information can be critical. + +**What you can access:** +- NHS Medicines A-Z (drug information and interactions) +- Medical Library (field medicine, emergency procedures) +- First aid guides +- Anatomy and physiology references +- Disease and symptom information + +**Important:** Medical references are for information only. They don't replace professional medical care. In emergencies, always seek professional help when possible. + +**Recommended content:** +- Medical Essentials ZIM collection +- NHS Medicines reference +- First aid and emergency medicine guides + +--- + +## Academic Research + +Students and researchers can work without depending on university networks. + +**What you can do:** +- Access Wikipedia's extensive article database +- Use AI for research assistance and summarization +- Work on papers and projects offline +- Cross-reference multiple sources +- Take notes with built-in tools + +**Recommended content:** +- Full Wikipedia +- Academic and educational references +- Subject-specific ZIM collections +- Note-taking apps (FlatNotes) + +--- + +## Setting Up for Your Use Case + +### Step 1: Identify Your Needs +What situations might you face without internet? What information would you need? + +### Step 2: Prioritize Content +Storage is limited. Focus on: +1. Critical safety information (medical, emergency) +2. Content matching your primary use case +3. General reference (Wikipedia) +4. Nice-to-have additions + +### Step 3: Download While You Can +Keep your server updated while you have internet. You never know when you'll need to go offline. + +### Step 4: Practice +Try using N.O.M.A.D. before you need it. Familiarity with the tools makes them more useful in a crisis. + +--- + +## Need Something Specific? + +N.O.M.A.D. content is customizable. If you don't see what you need: + +1. **Check ZIM Remote Explorer** — Thousands of ZIM files are available +2. **Browse Kolibri channels** — Educational content for many subjects +3. **Request features** — Let us know what content would help you + +Your offline server, your content choices. From 109bad9b6e1897d71e980ce0b15e30a0ee0a0dd9 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 15:13:54 -0800 Subject: [PATCH 02/12] docs: Add installation instructions and CLI maintenance commands - Add Installation section to getting-started.md with system requirements - Add install commands, post-install access info - Add privacy and security notes - Add Command-Line Maintenance section to FAQ with helper scripts Co-Authored-By: Claude Opus 4.5 --- admin/docs/faq.md | 27 +++++++++++++++++ admin/docs/getting-started.md | 57 +++++++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/admin/docs/faq.md b/admin/docs/faq.md index 050d903..4e5d8a1 100644 --- a/admin/docs/faq.md +++ b/admin/docs/faq.md @@ -141,6 +141,33 @@ The system is designed to recover gracefully. If an update fails: 2. Try the update again later 3. Check Settings → System for error messages +### Command-Line Maintenance + +For advanced troubleshooting or when you can't access the web interface, N.O.M.A.D. includes helper scripts in `/opt/project-nomad`: + +**Start all services:** +```bash +sudo bash /opt/project-nomad/start_nomad.sh +``` + +**Stop all services:** +```bash +sudo bash /opt/project-nomad/stop_nomad.sh +``` + +**Update Command Center:** +```bash +sudo bash /opt/project-nomad/update_nomad.sh +``` +*Note: This updates the Command Center only, not individual apps. Update apps through the web interface.* + +**Uninstall N.O.M.A.D.:** +```bash +curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/master/install/uninstall_nomad.sh -o uninstall_nomad.sh +sudo bash uninstall_nomad.sh +``` +*Warning: This cannot be undone. All data will be deleted.* + --- ## Privacy and Security diff --git a/admin/docs/getting-started.md b/admin/docs/getting-started.md index ff1ab01..6ad7245 100644 --- a/admin/docs/getting-started.md +++ b/admin/docs/getting-started.md @@ -1,8 +1,61 @@ # Getting Started with N.O.M.A.D. -This guide will help you set up your N.O.M.A.D. server and start using it effectively. +This guide will help you install and set up your N.O.M.A.D. server. -## First Steps +--- + +## Installation + +### System Requirements + +N.O.M.A.D. runs on any **Debian-based Linux** system (Ubuntu recommended). The installation is terminal-based, and everything is accessed through a web browser — no desktop environment needed. + +**Minimum Specs** (Command Center only): +- 2 GHz dual-core processor +- 4 GB RAM +- 5 GB free storage +- Internet connection (for initial install) + +**Recommended Specs** (with AI features): +- AMD Ryzen 7 / Intel Core i7 or better +- 32 GB RAM +- NVIDIA RTX 3060 or better (more VRAM = larger AI models) +- 250 GB+ free storage (SSD preferred) + +The Command Center itself is lightweight — your hardware requirements depend on which tools and content you choose to install. + +### Install N.O.M.A.D. + +Open a terminal and run these two commands: + +```bash +curl -fsSL https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/refs/heads/master/install/install_nomad.sh -o install_nomad.sh +``` + +```bash +sudo bash install_nomad.sh +``` + +That's it. Once the install finishes, open a browser and go to: + +- **Same machine:** `http://localhost:8080` +- **Other devices on your network:** `http://YOUR_SERVER_IP:8080` + +### About Internet & Privacy + +N.O.M.A.D. is designed for offline use. Internet is only needed: +- During initial installation +- When downloading additional content + +There is **zero telemetry** — your data stays on your device. + +### About Security + +N.O.M.A.D. has no built-in authentication — it's designed to be open and accessible. If you expose it on a network, consider using firewall rules to control which ports are accessible. + +--- + +## After Installation ### 1. Run the Easy Setup Wizard From 6f0c829d36671293c15074095d5366eb6f97521b Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 15:43:48 -0800 Subject: [PATCH 03/12] fix: Notification auto-dismiss not working due to stale closure The removeNotification function was using a stale reference to the notifications array from the closure scope, causing the setTimeout callback to filter against an outdated state. Changed to use functional update pattern (prev => prev.filter(...)) which correctly references the current state when the timeout fires. Co-Authored-By: Claude Opus 4.5 --- admin/inertia/providers/NotificationProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/inertia/providers/NotificationProvider.tsx b/admin/inertia/providers/NotificationProvider.tsx index aa5d888..a8314ea 100644 --- a/admin/inertia/providers/NotificationProvider.tsx +++ b/admin/inertia/providers/NotificationProvider.tsx @@ -22,7 +22,7 @@ const NotificationsProvider = ({ children }: { children: React.ReactNode }) => { } const removeNotification = (id: string) => { - setNotifications(notifications.filter((n) => n.id !== id)) + setNotifications((prev) => prev.filter((n) => n.id !== id)) } const removeAllNotifications = () => { From 3cb5dceb1d3906298ba8051393d39528e86cce78 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 15:52:47 -0800 Subject: [PATCH 04/12] feat: Add tiered collection categories UI - Add kiwix-categories.json with Medicine category and 3 tiers - Create CategoryCard component for displaying category cards - Create TierSelectionModal for tier selection UI - Integrate categories into Easy Setup wizard (Step 3) - Add TypeScript types for categories and tiers - Fallback to legacy flat collections if categories unavailable Co-Authored-By: Claude Opus 4.5 --- admin/inertia/components/CategoryCard.tsx | 88 +++++++ .../inertia/components/TierSelectionModal.tsx | 205 +++++++++++++++++ admin/inertia/pages/easy-setup/index.tsx | 214 +++++++++++++++--- admin/types/downloads.ts | 30 +++ collections/kiwix-categories.json | 85 +++++++ 5 files changed, 589 insertions(+), 33 deletions(-) create mode 100644 admin/inertia/components/CategoryCard.tsx create mode 100644 admin/inertia/components/TierSelectionModal.tsx create mode 100644 collections/kiwix-categories.json diff --git a/admin/inertia/components/CategoryCard.tsx b/admin/inertia/components/CategoryCard.tsx new file mode 100644 index 0000000..2f70c33 --- /dev/null +++ b/admin/inertia/components/CategoryCard.tsx @@ -0,0 +1,88 @@ +import { formatBytes } from '~/lib/util' +import DynamicIcon, { DynamicIconName } from './DynamicIcon' +import { CuratedCategory, CategoryTier } from '../../types/downloads' +import classNames from 'classnames' +import { IconChevronRight, IconCircleCheck } from '@tabler/icons-react' + +export interface CategoryCardProps { + category: CuratedCategory + selectedTier?: CategoryTier | null + onClick?: (category: CuratedCategory) => void +} + +const CategoryCard: React.FC = ({ category, selectedTier, onClick }) => { + // Calculate total size range across all tiers + const getTierTotalSize = (tier: CategoryTier, allTiers: CategoryTier[]): number => { + let total = tier.resources.reduce((acc, r) => acc + r.size_mb * 1024 * 1024, 0) + + // Add included tier sizes recursively + if (tier.includesTier) { + const includedTier = allTiers.find(t => t.slug === tier.includesTier) + if (includedTier) { + total += getTierTotalSize(includedTier, allTiers) + } + } + + return total + } + + const minSize = getTierTotalSize(category.tiers[0], category.tiers) + const maxSize = getTierTotalSize(category.tiers[category.tiers.length - 1], category.tiers) + + return ( +
onClick?.(category)} + > +
+
+
+ +

{category.name}

+
+ {selectedTier ? ( +
+ + {selectedTier.name} +
+ ) : ( + + )} +
+
+ +

{category.description}

+ +
+

+ {category.tiers.length} tiers available +

+
+ {category.tiers.map((tier) => ( + + {tier.name} + {tier.recommended && ' *'} + + ))} +
+

+ Size: {formatBytes(minSize, 1)} - {formatBytes(maxSize, 1)} +

+
+
+ ) +} + +export default CategoryCard diff --git a/admin/inertia/components/TierSelectionModal.tsx b/admin/inertia/components/TierSelectionModal.tsx new file mode 100644 index 0000000..9603627 --- /dev/null +++ b/admin/inertia/components/TierSelectionModal.tsx @@ -0,0 +1,205 @@ +import { Fragment } from 'react' +import { Dialog, Transition } from '@headlessui/react' +import { IconX, IconCheck, IconInfoCircle } from '@tabler/icons-react' +import { CuratedCategory, CategoryTier, CategoryResource } from '../../types/downloads' +import { formatBytes } from '~/lib/util' +import classNames from 'classnames' +import DynamicIcon, { DynamicIconName } from './DynamicIcon' + +interface TierSelectionModalProps { + isOpen: boolean + onClose: () => void + category: CuratedCategory | null + selectedTierSlug?: string | null + onSelectTier: (category: CuratedCategory, tier: CategoryTier) => void +} + +const TierSelectionModal: React.FC = ({ + isOpen, + onClose, + category, + selectedTierSlug, + onSelectTier, +}) => { + if (!category) return null + + // Get all resources for a tier (including inherited resources) + const getAllResourcesForTier = (tier: CategoryTier): CategoryResource[] => { + const resources = [...tier.resources] + + if (tier.includesTier) { + const includedTier = category.tiers.find(t => t.slug === tier.includesTier) + if (includedTier) { + resources.unshift(...getAllResourcesForTier(includedTier)) + } + } + + return resources + } + + const getTierTotalSize = (tier: CategoryTier): number => { + return getAllResourcesForTier(tier).reduce((acc, r) => acc + r.size_mb * 1024 * 1024, 0) + } + + return ( + + + +
+ + +
+
+ + + {/* Header */} +
+
+
+ +
+ + {category.name} + +

{category.description}

+
+
+ +
+
+ + {/* Content */} +
+

+ Select a tier based on your storage capacity and needs. Higher tiers include all content from lower tiers. +

+ +
+ {category.tiers.map((tier, index) => { + const allResources = getAllResourcesForTier(tier) + const totalSize = getTierTotalSize(tier) + const isSelected = selectedTierSlug === tier.slug + + return ( +
onSelectTier(category, tier)} + className={classNames( + 'border-2 rounded-lg p-5 cursor-pointer transition-all', + isSelected + ? 'border-desert-green bg-desert-green/5 shadow-md' + : 'border-gray-200 hover:border-desert-green/50 hover:shadow-sm', + tier.recommended && !isSelected && 'border-lime-500/50' + )} + > +
+
+
+

+ {tier.name} +

+ {tier.recommended && ( + + Recommended + + )} + {tier.includesTier && ( + + (includes {category.tiers.find(t => t.slug === tier.includesTier)?.name}) + + )} +
+

{tier.description}

+ + {/* Resources preview */} +
+

+ {allResources.length} resources included: +

+
+ {allResources.map((resource, idx) => ( +
+ +
+ {resource.title} + + ({formatBytes(resource.size_mb * 1024 * 1024, 0)}) + +
+
+ ))} +
+
+
+ +
+
+ {formatBytes(totalSize, 1)} +
+
+ {isSelected && } +
+
+
+
+ ) + })} +
+ + {/* Info note */} +
+ +

+ You can change your selection at any time. Downloads will begin when you complete the setup wizard. +

+
+
+ + {/* Footer */} +
+ +
+
+
+
+
+
+
+ ) +} + +export default TierSelectionModal diff --git a/admin/inertia/pages/easy-setup/index.tsx b/admin/inertia/pages/easy-setup/index.tsx index 28bcd8a..33b4305 100644 --- a/admin/inertia/pages/easy-setup/index.tsx +++ b/admin/inertia/pages/easy-setup/index.tsx @@ -6,17 +6,35 @@ import StyledButton from '~/components/StyledButton' import api from '~/lib/api' import { ServiceSlim } from '../../../types/services' import CuratedCollectionCard from '~/components/CuratedCollectionCard' +import CategoryCard from '~/components/CategoryCard' +import TierSelectionModal from '~/components/TierSelectionModal' import LoadingSpinner from '~/components/LoadingSpinner' import Alert from '~/components/Alert' import { IconCheck } from '@tabler/icons-react' import { useNotifications } from '~/context/NotificationContext' import useInternetStatus from '~/hooks/useInternetStatus' import classNames from 'classnames' +import { CuratedCategory, CategoryTier, CategoryResource } from '../../../types/downloads' type WizardStep = 1 | 2 | 3 | 4 const CURATED_MAP_COLLECTIONS_KEY = 'curated-map-collections' const CURATED_ZIM_COLLECTIONS_KEY = 'curated-zim-collections' +const CURATED_CATEGORIES_KEY = 'curated-categories' +const CATEGORIES_URL = + 'https://github.com/Crosstalk-Solutions/project-nomad/raw/refs/heads/feature/tiered-collections/collections/kiwix-categories.json' + +// Helper to get all resources for a tier (including inherited resources) +const getAllResourcesForTier = (tier: CategoryTier, allTiers: CategoryTier[]): CategoryResource[] => { + const resources = [...tier.resources] + if (tier.includesTier) { + const includedTier = allTiers.find((t) => t.slug === tier.includesTier) + if (includedTier) { + resources.unshift(...getAllResourcesForTier(includedTier, allTiers)) + } + } + return resources +} export default function EasySetupWizard(props: { system: { services: ServiceSlim[] } }) { const [currentStep, setCurrentStep] = useState(1) @@ -25,6 +43,11 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim const [selectedZimCollections, setSelectedZimCollections] = useState([]) const [isProcessing, setIsProcessing] = useState(false) + // Category/tier selection state + const [selectedTiers, setSelectedTiers] = useState>(new Map()) + const [tierModalOpen, setTierModalOpen] = useState(false) + const [activeCategory, setActiveCategory] = useState(null) + const { addNotification } = useNotifications() const { isOnline } = useInternetStatus() const queryClient = useQueryClient() @@ -32,7 +55,8 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim const anySelectionMade = selectedServices.length > 0 || selectedMapCollections.length > 0 || - selectedZimCollections.length > 0 + selectedZimCollections.length > 0 || + selectedTiers.size > 0 const { data: mapCollections, isLoading: isLoadingMaps } = useQuery({ queryKey: [CURATED_MAP_COLLECTIONS_KEY], @@ -46,6 +70,20 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim refetchOnWindowFocus: false, }) + // Fetch curated categories with tiers + const { data: categories, isLoading: isLoadingCategories } = useQuery({ + queryKey: [CURATED_CATEGORIES_KEY], + queryFn: async () => { + const response = await fetch(CATEGORIES_URL) + if (!response.ok) { + throw new Error('Failed to fetch categories') + } + const data = await response.json() + return data.categories as CuratedCategory[] + }, + refetchOnWindowFocus: false, + }) + const availableServices = props.system.services.filter( (service) => !service.installed && service.installation_status !== 'installing' ) @@ -68,6 +106,44 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim ) } + // Category/tier handlers + const handleCategoryClick = (category: CuratedCategory) => { + if (!isOnline) return + setActiveCategory(category) + setTierModalOpen(true) + } + + const handleTierSelect = (category: CuratedCategory, tier: CategoryTier) => { + setSelectedTiers((prev) => { + const newMap = new Map(prev) + // If same tier is selected, deselect it + if (prev.get(category.slug)?.slug === tier.slug) { + newMap.delete(category.slug) + } else { + newMap.set(category.slug, tier) + } + return newMap + }) + } + + const closeTierModal = () => { + setTierModalOpen(false) + setActiveCategory(null) + } + + // Get all resources from selected tiers for downloading + const getSelectedTierResources = (): CategoryResource[] => { + if (!categories) return [] + const resources: CategoryResource[] = [] + selectedTiers.forEach((tier, categorySlug) => { + const category = categories.find((c) => c.slug === categorySlug) + if (category) { + resources.push(...getAllResourcesForTier(tier, category.tiers)) + } + }) + return resources + } + const canProceedToNextStep = () => { if (!isOnline) return false // Must be online to proceed if (currentStep === 1) return true // Can skip app installation @@ -105,9 +181,12 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim await Promise.all(installPromises) + // Download collections and individual tier resources + const tierResources = getSelectedTierResources() const downloadPromises = [ ...selectedMapCollections.map((slug) => api.downloadMapCollection(slug)), ...selectedZimCollections.map((slug) => api.downloadZimCollection(slug)), + ...tierResources.map((resource) => api.downloadRemoteZimFile(resource.url)), ] await Promise.all(downloadPromises) @@ -359,44 +438,79 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim const renderStep3 = () => (
-

Choose ZIM Files

+

Choose Content Collections

- Select ZIM file collections for offline knowledge. You can always download more later. + Select content categories for offline knowledge. Click a category to choose your preferred tier based on storage capacity.

- {isLoadingZims ? ( + + {/* Curated Categories with Tiers */} + {isLoadingCategories ? (
- ) : zimCollections && zimCollections.length > 0 ? ( -
- {zimCollections.map((collection) => ( -
- isOnline && !collection.all_downloaded && toggleZimCollection(collection.slug) - } - className={classNames( - 'relative', - selectedZimCollections.includes(collection.slug) && - 'ring-4 ring-desert-green rounded-lg', - collection.all_downloaded && 'opacity-75', - !isOnline && 'opacity-50 cursor-not-allowed' - )} - > - - {selectedZimCollections.includes(collection.slug) && ( -
- -
- )} + ) : categories && categories.length > 0 ? ( + <> +
+ {categories.map((category) => ( + + ))} +
+ + {/* Tier Selection Modal */} + + + ) : null} + + {/* Legacy flat collections - show if available and no categories */} + {(!categories || categories.length === 0) && ( + <> + {isLoadingZims ? ( +
+
- ))} -
- ) : ( -
-

No ZIM collections available at this time.

-
+ ) : zimCollections && zimCollections.length > 0 ? ( +
+ {zimCollections.map((collection) => ( +
+ isOnline && !collection.all_downloaded && toggleZimCollection(collection.slug) + } + className={classNames( + 'relative', + selectedZimCollections.includes(collection.slug) && + 'ring-4 ring-desert-green rounded-lg', + collection.all_downloaded && 'opacity-75', + !isOnline && 'opacity-50 cursor-not-allowed' + )} + > + + {selectedZimCollections.includes(collection.slug) && ( +
+ +
+ )} +
+ ))} +
+ ) : ( +
+

No content collections available at this time.

+
+ )} + )}
) @@ -405,7 +519,8 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim const hasSelections = selectedServices.length > 0 || selectedMapCollections.length > 0 || - selectedZimCollections.length > 0 + selectedZimCollections.length > 0 || + selectedTiers.size > 0 return (
@@ -482,6 +597,39 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
)} + {selectedTiers.size > 0 && ( +
+

+ Content Categories ({selectedTiers.size}) +

+ {Array.from(selectedTiers.entries()).map(([categorySlug, tier]) => { + const category = categories?.find((c) => c.slug === categorySlug) + if (!category) return null + const resources = getAllResourcesForTier(tier, category.tiers) + return ( +
+
+ + + {category.name} - {tier.name} + + + ({resources.length} files) + +
+
    + {resources.map((resource, idx) => ( +
  • + {resource.title} +
  • + ))} +
+
+ ) + })} +
+ )} + Date: Sun, 18 Jan 2026 16:01:15 -0800 Subject: [PATCH 05/12] fix: Use jsDelivr CDN for categories JSON to avoid CORS issues GitHub raw URLs don't allow cross-origin requests from localhost. Using jsDelivr CDN which serves GitHub content with proper CORS headers. Co-Authored-By: Claude Opus 4.5 --- admin/inertia/pages/easy-setup/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/inertia/pages/easy-setup/index.tsx b/admin/inertia/pages/easy-setup/index.tsx index 33b4305..87f7298 100644 --- a/admin/inertia/pages/easy-setup/index.tsx +++ b/admin/inertia/pages/easy-setup/index.tsx @@ -22,7 +22,7 @@ const CURATED_MAP_COLLECTIONS_KEY = 'curated-map-collections' const CURATED_ZIM_COLLECTIONS_KEY = 'curated-zim-collections' const CURATED_CATEGORIES_KEY = 'curated-categories' const CATEGORIES_URL = - 'https://github.com/Crosstalk-Solutions/project-nomad/raw/refs/heads/feature/tiered-collections/collections/kiwix-categories.json' + 'https://cdn.jsdelivr.net/gh/Crosstalk-Solutions/project-nomad@feature/tiered-collections/collections/kiwix-categories.json' // Helper to get all resources for a tier (including inherited resources) const getAllResourcesForTier = (tier: CategoryTier, allTiers: CategoryTier[]): CategoryResource[] => { From b007f5e0fe14b94663e641196cbe01f323733311 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 16:05:33 -0800 Subject: [PATCH 06/12] feat: Add Survival & Preparedness category with 3 tiers Essential (~138 MB): - Food for Preppers, FOSS Cooking, Based.Cooking Standard (~3.6 GB, includes Essential): - Canadian Prepper: Winter Prepping & Bug Out Roll - Gardening Q&A, Cooking Q&A Comprehensive (~21 GB, includes Standard): - Urban Prepper, Canadian Prepper: Prepping Food & Bug Out Concepts - Learning Self-Reliance, iFixit Repair Guides, DIY Q&A Co-Authored-By: Claude Opus 4.5 --- collections/kiwix-categories.json | 111 ++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/collections/kiwix-categories.json b/collections/kiwix-categories.json index e6622d4..ced0588 100644 --- a/collections/kiwix-categories.json +++ b/collections/kiwix-categories.json @@ -80,6 +80,117 @@ ] } ] + }, + { + "name": "Survival & Preparedness", + "slug": "survival", + "icon": "IconShieldCheck", + "description": "Emergency preparedness, self-sufficiency, food storage, and practical survival skills.", + "language": "en", + "tiers": [ + { + "name": "Essential", + "slug": "survival-essential", + "description": "Core food preparation and cooking guides. Lightweight text-based resources to get started.", + "recommended": true, + "resources": [ + { + "title": "Food for Preppers", + "description": "Recipes and techniques for food preservation and preparation", + "url": "https://download.kiwix.org/zim/other/zimgit-food-preparation_en_2025-04.zim", + "size_mb": 98 + }, + { + "title": "FOSS Cooking", + "description": "Quick and easy cooking guides and recipes", + "url": "https://download.kiwix.org/zim/zimit/foss.cooking_en_all_2025-11.zim", + "size_mb": 24 + }, + { + "title": "Based.Cooking", + "description": "Simple, practical recipes from the community", + "url": "https://download.kiwix.org/zim/zimit/based.cooking_en_all_2025-11.zim", + "size_mb": 16 + } + ] + }, + { + "name": "Standard", + "slug": "survival-standard", + "description": "Video guides for winter survival, bug-out gear, plus gardening and cooking Q&A. Includes Essential.", + "includesTier": "survival-essential", + "resources": [ + { + "title": "Canadian Prepper: Winter Prepping", + "description": "Video guides for winter survival and cold weather emergencies", + "url": "https://download.kiwix.org/zim/videos/canadian_prepper_winterprepping_en_2025-11.zim", + "size_mb": 1340 + }, + { + "title": "Canadian Prepper: Bug Out Roll", + "description": "Essential gear selection for your bug-out bag", + "url": "https://download.kiwix.org/zim/videos/canadian_prepper_bugoutroll_en_2025-08.zim", + "size_mb": 975 + }, + { + "title": "Gardening Q&A", + "description": "Stack Exchange Q&A for growing your own food", + "url": "https://download.kiwix.org/zim/stack_exchange/gardening.stackexchange.com_en_all_2025-12.zim", + "size_mb": 923 + }, + { + "title": "Cooking Q&A", + "description": "Stack Exchange Q&A for cooking techniques and food safety", + "url": "https://download.kiwix.org/zim/stack_exchange/cooking.stackexchange.com_en_all_2025-12.zim", + "size_mb": 236 + } + ] + }, + { + "name": "Comprehensive", + "slug": "survival-comprehensive", + "description": "Full prepper video library plus repair guides and DIY skills. Includes Standard.", + "includesTier": "survival-standard", + "resources": [ + { + "title": "Urban Prepper", + "description": "Comprehensive urban emergency preparedness video series", + "url": "https://download.kiwix.org/zim/videos/urban-prepper_en_all_2025-11.zim", + "size_mb": 2240 + }, + { + "title": "Canadian Prepper: Prepping Food", + "description": "Long-term food storage and survival meal preparation", + "url": "https://download.kiwix.org/zim/videos/canadian_prepper_preppingfood_en_2025-09.zim", + "size_mb": 2160 + }, + { + "title": "Canadian Prepper: Bug Out Concepts", + "description": "Strategies and planning for emergency evacuation", + "url": "https://download.kiwix.org/zim/videos/canadian_prepper_bugoutconcepts_en_2025-11.zim", + "size_mb": 2890 + }, + { + "title": "Learning Self-Reliance", + "description": "Prepping, survival skills, beekeeping, and homesteading", + "url": "https://download.kiwix.org/zim/videos/lrnselfreliance_en_all_2025-12.zim", + "size_mb": 3970 + }, + { + "title": "iFixit Repair Guides", + "description": "Step-by-step repair guides for electronics, appliances, and vehicles", + "url": "https://download.kiwix.org/zim/ifixit/ifixit_en_all_2025-12.zim", + "size_mb": 3570 + }, + { + "title": "DIY & Home Improvement Q&A", + "description": "Stack Exchange Q&A for home repairs and construction", + "url": "https://download.kiwix.org/zim/stack_exchange/diy.stackexchange.com_en_all_2025-12.zim", + "size_mb": 1900 + } + ] + } + ] } ] } From c9c29955eebaf80443f0ca47f31243f7068a15a2 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 16:13:34 -0800 Subject: [PATCH 07/12] chore: Add cache-busting parameter to categories URL jsDelivr aggressively caches branch references. Adding version parameter ensures fresh data is fetched when categories are updated. Co-Authored-By: Claude Opus 4.5 --- admin/inertia/pages/easy-setup/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/inertia/pages/easy-setup/index.tsx b/admin/inertia/pages/easy-setup/index.tsx index 87f7298..4b24b07 100644 --- a/admin/inertia/pages/easy-setup/index.tsx +++ b/admin/inertia/pages/easy-setup/index.tsx @@ -22,7 +22,7 @@ const CURATED_MAP_COLLECTIONS_KEY = 'curated-map-collections' const CURATED_ZIM_COLLECTIONS_KEY = 'curated-zim-collections' const CURATED_CATEGORIES_KEY = 'curated-categories' const CATEGORIES_URL = - 'https://cdn.jsdelivr.net/gh/Crosstalk-Solutions/project-nomad@feature/tiered-collections/collections/kiwix-categories.json' + 'https://cdn.jsdelivr.net/gh/Crosstalk-Solutions/project-nomad@feature/tiered-collections/collections/kiwix-categories.json?v=2' // Helper to get all resources for a tier (including inherited resources) const getAllResourcesForTier = (tier: CategoryTier, allTiers: CategoryTier[]): CategoryResource[] => { From 05441bd6a23392b375d9e897c70b376a3f11f4d8 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 16:19:02 -0800 Subject: [PATCH 08/12] feat: Add Education & Reference category with 3 tiers Essential (~5 GB): - Wikipedia Top 45k articles (no images) - Wikibooks (no images) Standard (~19 GB, includes Essential): - TED-Ed educational videos - Wikiversity tutorials - LibreTexts STEM (Math, Physics, Chemistry, Biology) - Project Gutenberg Education Comprehensive (~59 GB, includes Standard): - Full Wikipedia (6M+ articles, no images) - Wikibooks with images - TED Conference talks - LibreTexts Humanities, Engineering, Geosciences, Business Co-Authored-By: Claude Opus 4.5 --- collections/kiwix-categories.json | 129 ++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/collections/kiwix-categories.json b/collections/kiwix-categories.json index ced0588..53ed8b3 100644 --- a/collections/kiwix-categories.json +++ b/collections/kiwix-categories.json @@ -191,6 +191,135 @@ ] } ] + }, + { + "name": "Education & Reference", + "slug": "education", + "icon": "IconSchool", + "description": "Encyclopedias, textbooks, tutorials, and educational videos for self-directed learning.", + "language": "en", + "tiers": [ + { + "name": "Essential", + "slug": "education-essential", + "description": "Core reference materials - Wikipedia's best articles and open textbooks. Lightweight, text-focused.", + "recommended": true, + "resources": [ + { + "title": "Wikipedia Top 45,000 Articles", + "description": "The 45,000 best Wikipedia articles, optimized for size (no images)", + "url": "https://download.kiwix.org/zim/wikipedia/wikipedia_en_top_nopic_2025-12.zim", + "size_mb": 1880 + }, + { + "title": "Wikibooks", + "description": "Open-content textbooks covering math, science, computing, and more", + "url": "https://download.kiwix.org/zim/wikibooks/wikibooks_en_all_nopic_2025-10.zim", + "size_mb": 3100 + } + ] + }, + { + "name": "Standard", + "slug": "education-standard", + "description": "Adds educational videos, university-level tutorials, and STEM textbooks. Includes Essential.", + "includesTier": "education-essential", + "resources": [ + { + "title": "TED-Ed", + "description": "Educational video lessons on science, history, literature, and more", + "url": "https://download.kiwix.org/zim/ted/ted_mul_ted-ed_2025-07.zim", + "size_mb": 5610 + }, + { + "title": "Wikiversity", + "description": "Tutorials, courses, and learning materials for all levels", + "url": "https://download.kiwix.org/zim/wikiversity/wikiversity_en_all_maxi_2025-11.zim", + "size_mb": 2370 + }, + { + "title": "LibreTexts Mathematics", + "description": "Open-source math textbooks from algebra to calculus", + "url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_math_2025-01.zim", + "size_mb": 831 + }, + { + "title": "LibreTexts Physics", + "description": "Physics courses and textbooks", + "url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_phys_2025-01.zim", + "size_mb": 560 + }, + { + "title": "LibreTexts Chemistry", + "description": "Chemistry courses and textbooks", + "url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_chem_2025-01.zim", + "size_mb": 2180 + }, + { + "title": "LibreTexts Biology", + "description": "Biology courses and textbooks", + "url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_bio_2025-01.zim", + "size_mb": 2240 + }, + { + "title": "Project Gutenberg: Education", + "description": "Classic educational texts and resources", + "url": "https://download.kiwix.org/zim/gutenberg/gutenberg_en_education_2025-12.zim", + "size_mb": 606 + } + ] + }, + { + "name": "Comprehensive", + "slug": "education-comprehensive", + "description": "Complete educational library with full Wikipedia, enhanced textbooks, and TED talks. Includes Standard.", + "includesTier": "education-standard", + "resources": [ + { + "title": "Wikipedia (Full, No Images)", + "description": "Complete English Wikipedia - over 6 million articles", + "url": "https://download.kiwix.org/zim/wikipedia/wikipedia_en_all_mini_2025-12.zim", + "size_mb": 11400 + }, + { + "title": "Wikibooks (With Images)", + "description": "Open textbooks with full illustrations and diagrams", + "url": "https://download.kiwix.org/zim/wikibooks/wikibooks_en_all_maxi_2025-10.zim", + "size_mb": 5400 + }, + { + "title": "TED Conference", + "description": "Main TED conference talks on ideas worth spreading", + "url": "https://download.kiwix.org/zim/ted/ted_mul_ted-conference_2025-08.zim", + "size_mb": 16500 + }, + { + "title": "LibreTexts Humanities", + "description": "Literature, philosophy, history, and social sciences", + "url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_human_2025-01.zim", + "size_mb": 3730 + }, + { + "title": "LibreTexts Geosciences", + "description": "Earth science, geology, and environmental studies", + "url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_geo_2025-01.zim", + "size_mb": 1190 + }, + { + "title": "LibreTexts Engineering", + "description": "Engineering courses and technical references", + "url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_eng_2025-01.zim", + "size_mb": 678 + }, + { + "title": "LibreTexts Business", + "description": "Business, economics, and management textbooks", + "url": "https://download.kiwix.org/zim/libretexts/libretexts.org_en_biz_2025-01.zim", + "size_mb": 840 + } + ] + } + ] } ] } From 1027bd8e0fd129e3907cc7d02c00469014489212 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 16:28:47 -0800 Subject: [PATCH 09/12] chore: Switch categories URL to raw GitHub for dev reliability jsDelivr CDN was aggressively caching old data during development. Raw GitHub URLs provide more immediate updates when pushing changes to the feature branch. Co-Authored-By: Claude Opus 4.5 --- admin/inertia/pages/easy-setup/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/inertia/pages/easy-setup/index.tsx b/admin/inertia/pages/easy-setup/index.tsx index 4b24b07..31c5d0d 100644 --- a/admin/inertia/pages/easy-setup/index.tsx +++ b/admin/inertia/pages/easy-setup/index.tsx @@ -22,7 +22,7 @@ const CURATED_MAP_COLLECTIONS_KEY = 'curated-map-collections' const CURATED_ZIM_COLLECTIONS_KEY = 'curated-zim-collections' const CURATED_CATEGORIES_KEY = 'curated-categories' const CATEGORIES_URL = - 'https://cdn.jsdelivr.net/gh/Crosstalk-Solutions/project-nomad@feature/tiered-collections/collections/kiwix-categories.json?v=2' + 'https://raw.githubusercontent.com/Crosstalk-Solutions/project-nomad/feature/tiered-collections/collections/kiwix-categories.json' // Helper to get all resources for a tier (including inherited resources) const getAllResourcesForTier = (tier: CategoryTier, allTiers: CategoryTier[]): CategoryResource[] => { From c03f2ae70267913e9d4780b3af6fc5cfb8ee3828 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 16:37:05 -0800 Subject: [PATCH 10/12] docs: Add categories to-do list for future expansion Tracks potential Kiwix categories to add to the tiered collections system, organized by priority level. Co-Authored-By: Claude Opus 4.5 --- collections/CATEGORIES-TODO.md | 107 +++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 collections/CATEGORIES-TODO.md diff --git a/collections/CATEGORIES-TODO.md b/collections/CATEGORIES-TODO.md new file mode 100644 index 0000000..28d3e72 --- /dev/null +++ b/collections/CATEGORIES-TODO.md @@ -0,0 +1,107 @@ +# Kiwix Categories To-Do List + +Potential categories to add to the tiered collections system in `kiwix-categories.json`. + +## Current Categories (Completed) +- [x] Medicine - Medical references, first aid, emergency care +- [x] Survival & Preparedness - Food prep, prepper videos, repair guides +- [x] Education & Reference - Wikipedia, textbooks, TED talks + +--- + +## High Priority + +### Technology & Programming +Stack Overflow, developer documentation, coding tutorials +- Stack Overflow (multiple tags available) +- DevDocs documentation +- freeCodeCamp +- Programming language references + +### Children & Family +Age-appropriate educational content for kids +- Wikipedia for Schools +- Wikibooks Children's Bookshelf +- Khan Academy Kids (via Kolibri - separate system) +- Storybooks, fairy tales + +### Trades & Vocational +Practical skills for building, fixing, and maintaining +- Electrical wiring guides +- Plumbing basics +- Automotive repair +- Woodworking +- Welding fundamentals + +### Agriculture & Gardening +Food production and farming (expand beyond what's in Survival) +- Practical Plants database +- Permaculture guides +- Seed saving +- Animal husbandry +- Composting and soil management + +--- + +## Medium Priority + +### Languages & Reference +Dictionaries, language learning, translation +- Wiktionary (multiple languages) +- Language learning resources +- Translation dictionaries +- Grammar guides + +### History & Culture +Historical knowledge and cultural encyclopedias +- Wikipedia History portal content +- Historical documents +- Cultural archives +- Biographies + +### Legal & Civic +Laws, rights, and civic procedures +- Legal references +- Constitutional documents +- Civic procedures +- Rights and responsibilities + +### Communications +Emergency and amateur radio, networking +- Ham radio guides +- Emergency communication protocols +- Basic networking/IT +- Signal procedures + +--- + +## Nice To Have + +### Entertainment +Recreational reading and activities +- Project Gutenberg (fiction categories) +- Chess tutorials +- Puzzles and games +- Music theory + +### Religion & Philosophy +Spiritual and philosophical texts +- Religious texts (various traditions) +- Philosophy references +- Ethics guides + +### Regional/Non-English Bundles +Content in other languages +- Spanish language bundle +- French language bundle +- Other major languages + +--- + +## Notes + +- Each category should have 3 tiers: Essential, Standard, Comprehensive +- Higher tiers include all content from lower tiers via `includesTier` +- Check Kiwix catalog for available ZIM files: https://download.kiwix.org/zim/ +- Consider storage constraints - Essential tiers should be <500MB ideally +- Mark one tier as `recommended: true` (usually Essential) From 7bf3f25c47c6b3ee6fb9a321c9858a167c813fed Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 17:06:16 -0800 Subject: [PATCH 11/12] feat: Add storage projection bar to easy setup wizard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a dynamic storage projection bar that shows users how their selections will impact disk space: - Displays current disk usage and projected usage after installation - Updates in real-time as users select maps, ZIM collections, and tiers - Color-coded warnings (green→tan→orange→red) based on projected usage - Shows "exceeds available space" warning if selections exceed capacity - Works on both Linux (disk array) and Windows (fsSize array) Co-Authored-By: Claude Opus 4.5 --- .../components/StorageProjectionBar.tsx | 122 ++++++++++++++++++ admin/inertia/pages/easy-setup/index.tsx | 55 +++++++- 2 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 admin/inertia/components/StorageProjectionBar.tsx diff --git a/admin/inertia/components/StorageProjectionBar.tsx b/admin/inertia/components/StorageProjectionBar.tsx new file mode 100644 index 0000000..36bb214 --- /dev/null +++ b/admin/inertia/components/StorageProjectionBar.tsx @@ -0,0 +1,122 @@ +import classNames from '~/lib/classNames' +import { formatBytes } from '~/lib/util' +import { IconAlertTriangle, IconServer } from '@tabler/icons-react' + +interface StorageProjectionBarProps { + totalSize: number // Total disk size in bytes + currentUsed: number // Currently used space in bytes + projectedAddition: number // Additional space that will be used in bytes +} + +export default function StorageProjectionBar({ + totalSize, + currentUsed, + projectedAddition, +}: StorageProjectionBarProps) { + const projectedTotal = currentUsed + projectedAddition + const currentPercent = (currentUsed / totalSize) * 100 + const projectedPercent = (projectedAddition / totalSize) * 100 + const projectedTotalPercent = (projectedTotal / totalSize) * 100 + const remainingAfter = totalSize - projectedTotal + const willExceed = projectedTotal > totalSize + + // Determine warning level based on projected total + const getProjectedColor = () => { + if (willExceed) return 'bg-desert-red' + if (projectedTotalPercent >= 90) return 'bg-desert-orange' + if (projectedTotalPercent >= 75) return 'bg-desert-tan' + return 'bg-desert-olive' + } + + const getProjectedGlow = () => { + if (willExceed) return 'shadow-desert-red/50' + if (projectedTotalPercent >= 90) return 'shadow-desert-orange/50' + if (projectedTotalPercent >= 75) return 'shadow-desert-tan/50' + return 'shadow-desert-olive/50' + } + + return ( +
+
+
+ + Storage +
+
+ {formatBytes(projectedTotal, 1)} / {formatBytes(totalSize, 1)} + {projectedAddition > 0 && ( + + (+{formatBytes(projectedAddition, 1)} selected) + + )} +
+
+ + {/* Progress bar */} +
+
+ {/* Current usage - darker/subdued */} +
+ {/* Projected addition - highlighted */} + {projectedAddition > 0 && ( +
+ )} +
+ + {/* Percentage label */} +
15 + ? 'left-3 text-desert-white drop-shadow-md' + : 'right-3 text-desert-green' + )} + > + {Math.round(projectedTotalPercent)}% +
+
+ + {/* Legend and warnings */} +
+
+
+
+ Current ({formatBytes(currentUsed, 1)}) +
+ {projectedAddition > 0 && ( +
+
+ + Selected (+{formatBytes(projectedAddition, 1)}) + +
+ )} +
+ + {willExceed ? ( +
+ + Exceeds available space by {formatBytes(projectedTotal - totalSize, 1)} +
+ ) : ( +
+ {formatBytes(remainingAfter, 1)} will remain free +
+ )} +
+
+ ) +} diff --git a/admin/inertia/pages/easy-setup/index.tsx b/admin/inertia/pages/easy-setup/index.tsx index 31c5d0d..f775398 100644 --- a/admin/inertia/pages/easy-setup/index.tsx +++ b/admin/inertia/pages/easy-setup/index.tsx @@ -1,6 +1,6 @@ import { Head, router } from '@inertiajs/react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' -import { useEffect, useState } from 'react' +import { useEffect, useState, useMemo } from 'react' import AppLayout from '~/layouts/AppLayout' import StyledButton from '~/components/StyledButton' import api from '~/lib/api' @@ -10,9 +10,11 @@ import CategoryCard from '~/components/CategoryCard' import TierSelectionModal from '~/components/TierSelectionModal' import LoadingSpinner from '~/components/LoadingSpinner' import Alert from '~/components/Alert' +import StorageProjectionBar from '~/components/StorageProjectionBar' import { IconCheck } from '@tabler/icons-react' import { useNotifications } from '~/context/NotificationContext' import useInternetStatus from '~/hooks/useInternetStatus' +import { useSystemInfo } from '~/hooks/useSystemInfo' import classNames from 'classnames' import { CuratedCategory, CategoryTier, CategoryResource } from '../../../types/downloads' @@ -51,6 +53,7 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim const { addNotification } = useNotifications() const { isOnline } = useInternetStatus() const queryClient = useQueryClient() + const { data: systemInfo } = useSystemInfo({ enabled: true }) const anySelectionMade = selectedServices.length > 0 || @@ -144,6 +147,47 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim return resources } + // Calculate total projected storage from all selections + const projectedStorageBytes = useMemo(() => { + let totalBytes = 0 + + // Add tier resources + const tierResources = getSelectedTierResources() + totalBytes += tierResources.reduce((sum, r) => sum + r.size_mb * 1024 * 1024, 0) + + // Add map collections + if (mapCollections) { + selectedMapCollections.forEach((slug) => { + const collection = mapCollections.find((c) => c.slug === slug) + if (collection) { + totalBytes += collection.resources.reduce((sum, r) => sum + r.size_mb * 1024 * 1024, 0) + } + }) + } + + // Add ZIM collections + if (zimCollections) { + selectedZimCollections.forEach((slug) => { + const collection = zimCollections.find((c) => c.slug === slug) + if (collection) { + totalBytes += collection.resources.reduce((sum, r) => sum + r.size_mb * 1024 * 1024, 0) + } + }) + } + + return totalBytes + }, [selectedTiers, selectedMapCollections, selectedZimCollections, categories, mapCollections, zimCollections]) + + // Get primary disk/filesystem info for storage projection + // Try disk array first (Linux/production), fall back to fsSize (Windows/dev) + const primaryDisk = systemInfo?.disk?.[0] + const primaryFs = systemInfo?.fsSize?.[0] + const storageInfo = primaryDisk + ? { totalSize: primaryDisk.totalSize, totalUsed: primaryDisk.totalUsed } + : primaryFs + ? { totalSize: primaryFs.size, totalUsed: primaryFs.used } + : null + const canProceedToNextStep = () => { if (!isOnline) return false // Must be online to proceed if (currentStep === 1) return true // Can skip app installation @@ -657,6 +701,15 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim
{renderStepIndicator()} + {storageInfo && ( +
+ +
+ )}
{currentStep === 1 && renderStep1()} {currentStep === 2 && renderStep2()} From f414d9e1c075cb3f195bab1fb3566d8ab81b0d90 Mon Sep 17 00:00:00 2001 From: Chris Sherwood Date: Sun, 18 Jan 2026 19:55:08 -0800 Subject: [PATCH 12/12] chore: Rename step 3 label from 'ZIM Files' to 'Content' More user-friendly terminology for non-technical users. Co-Authored-By: Claude Opus 4.5 --- admin/inertia/pages/easy-setup/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/inertia/pages/easy-setup/index.tsx b/admin/inertia/pages/easy-setup/index.tsx index f775398..a64ecc0 100644 --- a/admin/inertia/pages/easy-setup/index.tsx +++ b/admin/inertia/pages/easy-setup/index.tsx @@ -291,7 +291,7 @@ export default function EasySetupWizard(props: { system: { services: ServiceSlim const steps = [ { number: 1, label: 'Apps' }, { number: 2, label: 'Maps' }, - { number: 3, label: 'ZIM Files' }, + { number: 3, label: 'Content' }, { number: 4, label: 'Review' }, ]