Origin of idea
Mod list is a flat ListView. Mods are ordered by ModsConfig.xml load order. This is correct for load-order editing, but awful for browsing a 300+ mod collection. You can't see that 12 mods are "Vanilla Factions Expanded" family, you can't bulk-toggle them, you can't organize by theme.
Needs #28 (plugin system) first
Suggestion / proposed solution
A plugin that adds a new view to the icon rail (like Steam Workshop is a separate view). It presents a virtual tree of all mods — purely for organization, navigation, and toggling. It does not affect load order in any way.
┌──────────┐ ┌────────────────────────────────────────────┐
│ Sidebar │ │ ▼ Vanilla Factions Expanded (12) [✓] │
│ │ │ ├─ Vanilla Factions Expanded - Core [✓]│
│ ──────── │ │ ├─ Vanilla Factions Expanded - Settler [✓]│
│ All │ │ ├─ Vanilla Factions Expanded - Medieval[✓]│
│ Active │ │ └─ ... │
│ ──────── │ │ │
│ Core │ │ ▼ Combat (8) [◐] │
│ Local │ │ ├─ Combat Extended [✓] │
│ Steam │ │ ├─ Combat Extended: Armory [✓] │
│ ──────── │ │ ├─ Yayo's Combat 3 [☐] │
│ Factions │ │ └─ ... │
│ Combat │ │ │
│ Graphics │ │ Ungrouped (3) │
│ ──────── │ │ ├─ EdB Prepare Carefully [✓] │
│ Inactive │ │ ├─ RimHUD [✓] │
│ Errors │ │ └─ ... │
└──────────┘ └────────────────────────────────────────────┘
Core concepts:
- Folders — each mod belongs to at most one folder (enforced). A folder maps a subset of mods to a named group. In the tree, folders appear as parent nodes with a group-level checkbox (checked / partially-checked / unchecked). Toggling the folder checkbox toggles all child mods.
- Tags — each mod can have many tags. Tags appear in the sidebar (like provider filters) and can be used to quickly highlight/filter mods across folders. Tags are independent of folder assignment.
- Auto-folder rules — optional patterns that auto-assign mods to folders. E.g. packageId starts with oskarpotocki.vanillafactionsexpanded. → folder "Vanilla Factions Expanded". Rules are evaluated in order, first match wins.
- Ungrouped — mods not in any folder appear under an "Ungrouped" node at the bottom (toggleable).
- Tree does NOT touch load order. The flat ModListModel / ModList.qml remains the canonical order. The tree is purely a parallel view for browsing and bulk-toggle. Toggling a folder or a mod in the tree toggles the same ModItem.checked in the flat model.
Nested folders:
Support one additional nesting level (subfolder within a folder). Not arbitrary depth — keeps the tree manageable. Parent folder checkbox controls all descendants.
Plugin architecture:
Implemented as a plugin using the extension system (see issue #7 or the plugin's own entry_points). Consists of:
Component Type
ModTreeModel QAbstractItemModel
ModTree.qml QML
ModTreePanel Python
Folder/Tag config msgspec structs
Registers as a view (like Steam Workshop) via the plugin entry point, appears in the icon rail.
Sidebar behavior:
In the organizer view, the sidebar shows:
- Standard filters (All, Active, Inactive, providers, errors, warnings) — works as before, filters the visible tree
- Tag list — each user-defined tag becomes a filter entry. Click "Combat" → tree shows only mods tagged "Combat"
Operations supported:
- Right-click mod → "Move to folder..." / "Remove from folder"
- Right-click mod → "Manage tags..."
- Right-click folder → "Rename", "Delete folder" (mods become ungrouped), "Sort by name"
- Drag mod from one folder to another (within tree)
- Create folder from selection
- Checkbox on folder: bulk enable/disable all children
- Search filters both the tree and the flat list identically
Storage format:
SQLite database at config_dir()/organizer.db, reusing the pattern from core/services/startup_impact_service/db.py (aiosqlite + async lock).
CREATE TABLE folders (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
sort_order INTEGER NOT NULL DEFAULT 0,
collapsed INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE folder_mods (
folder_id INTEGER NOT NULL REFERENCES folders(id) ON DELETE CASCADE,
uuid TEXT NOT NULL,
PRIMARY KEY (folder_id, uuid)
);
CREATE TABLE mod_tags (
uuid TEXT NOT NULL,
tag TEXT NOT NULL,
PRIMARY KEY (uuid, tag)
);
Alternatives considered
Built-in feature instead of plugin — tied to core release cycle, harder to iterate. Plugin allows independent versioning and optional install.
- Folders-as-tags (just tags, no exclusive folder) — loses the "one clear place" semantics that users expect from a file-tree mental model.
- Affecting load order from the tree — conflicts with RimWorld's flat ModsConfig.xml, complicates the plugin scope. Keep load order editing in the existing flat list view.
Additional context
Depends on issue #28 (plugin/extension system) so the plugin can register a view and sidebar entries.
Tags/folders could sync with Steam collections in the future, but that's a separate enhancement.
Origin of idea
Mod list is a flat ListView. Mods are ordered by ModsConfig.xml load order. This is correct for load-order editing, but awful for browsing a 300+ mod collection. You can't see that 12 mods are "Vanilla Factions Expanded" family, you can't bulk-toggle them, you can't organize by theme.
Needs #28 (plugin system) first
Suggestion / proposed solution
A plugin that adds a new view to the icon rail (like Steam Workshop is a separate view). It presents a virtual tree of all mods — purely for organization, navigation, and toggling. It does not affect load order in any way.
Core concepts:
Nested folders:
Support one additional nesting level (subfolder within a folder). Not arbitrary depth — keeps the tree manageable. Parent folder checkbox controls all descendants.
Plugin architecture:
Implemented as a plugin using the extension system (see issue #7 or the plugin's own entry_points). Consists of:
Component Type
ModTreeModel QAbstractItemModel
ModTree.qml QML
ModTreePanel Python
Folder/Tag config msgspec structs
Registers as a view (like Steam Workshop) via the plugin entry point, appears in the icon rail.
Sidebar behavior:
In the organizer view, the sidebar shows:
Operations supported:
Storage format:
SQLite database at config_dir()/organizer.db, reusing the pattern from core/services/startup_impact_service/db.py (aiosqlite + async lock).
Alternatives considered
Built-in feature instead of plugin — tied to core release cycle, harder to iterate. Plugin allows independent versioning and optional install.
Additional context
Depends on issue #28 (plugin/extension system) so the plugin can register a view and sidebar entries.
Tags/folders could sync with Steam collections in the future, but that's a separate enhancement.