Admin Species Management - jra3/mulm GitHub Wiki
Admin Species Management
Complete guide to the species database management interface at /admin/species.
Overview
The species management interface provides comprehensive tools for maintaining the 2,300+ species groups and 5,000+ name variants in the database. Admins can view, edit, merge, and bulk-update species data including points, CARES status, and taxonomic information.
Access: Admin authentication required
Route: /admin/species
Features
1. Species List View
Paginated table with 50 species per page showing:
- Canonical Name - Click to edit
- Species Type - Fish/Plant/Invert/Coral badge
- Program Class - Cichlids, Livebearers, etc.
- Synonym Count - Number of common + scientific name variants
- Points - Base points (or "—" if unassigned)
- CARES - ⭐ if conservation species
- Actions - Edit button
Sortable columns: Click headers to sort by name, type, class, or points
2. Filtering & Search
Filters:
- Type - Fish, Plant, Invert, Coral, or All
- Class - Context-aware based on selected type
- Points Status - All, With Points, Without Points
- Conservation - All, CARES Only, Non-CARES
- Search - Searches canonical names and all synonyms
Filter combinations are supported for precise queries
3. Bulk Point Assignment
Workflow:
- Check species you want to update
- Click "Bulk Edit Points" button (appears when species selected)
- Select point value (5/10/15/20) in dialog
- Apply to all selected species
Use case: Quickly assign points to the 1,663+ species that still need them
4. Species Edit Page
Click any species name to open the edit page with these sections:
Canonical Name (⚠️ Caution Zone)
- Genus and Species fields
- Warning: Changes affect all references
Classification
- Species Type (read-only - affects form fields)
- Program Class (dropdown with type-specific options)
Points & Conservation
- Base Points (5/10/15/20 or unassigned)
- CARES Species checkbox
External Resources
- Reference URLs (one per line)
- Image URLs (one per line)
Name Variants
Split schema - Common and scientific names managed separately:
Common Names:
- List of all common name variants
- Add new common names
- Delete individual names
- Empty state with "Add First Common Name" button
Scientific Names:
- List of all scientific name variants
- Add new scientific names
- Delete individual names
- Empty state with "Add First Scientific Name" button
Many-to-many flexibility: A species can have multiple common names AND multiple scientific names independently
5. Merge Species
Location: Danger Zone at bottom of edit page
Purpose: Combine duplicate species into one
Workflow:
- Open species to be deleted (defunct)
- Click "Merge Into Another Species"
- Dialog shows:
- Current species (will be deleted) in red
- Typeahead to search for canonical species (will be kept)
- Warning about permanent action
- Required confirmation checkbox
- Select canonical species and confirm
- All names and submissions move to canonical species
- Defunct species is deleted
What happens:
- All common names → moved to canonical
- All scientific names → moved to canonical
- All submissions → redirected to canonical
- Defunct species → permanently deleted
Safety: Cannot merge a species into itself, requires confirmation
6. Delete Species
Location: Danger Zone at bottom of edit page
Safety checks:
- Warns about number of submissions using this species
- Confirms deletion of all synonyms
- Cannot be undone
Data Model
species_name_group (Canonical Species)
group_id- Primary keycanonical_genus/canonical_species_name- Official namesspecies_type- Fish/Plant/Invert/Coralprogram_class- BAP/HAP/CAP classbase_points- Default points (NULL if unassigned)is_cares_species- Conservation flag (0/1)external_references- JSON array of URLsimage_links- JSON array of URLs
species_common_name (Common Name Variants)
common_name_id- Primary keygroup_id- Foreign key to species_name_groupcommon_name- Name variant (e.g., "Guppy", "Fancy Guppy")
species_scientific_name (Scientific Name Variants)
scientific_name_id- Primary keygroup_id- Foreign key to species_name_groupscientific_name- Name variant (e.g., "Poecilia reticulata")
Relationship: One species group has many common names AND many scientific names (split schema)
Common Workflows
Assigning Points to Species
Individual species:
- Find species in list (use filters/search)
- Click name → Edit page
- Select points in dropdown
- Save changes
Bulk assignment (recommended for many species):
- Filter species (e.g., "Without Points" + "Cichlids")
- Check all species on page (or select individually)
- Click "Bulk Edit Points"
- Select point value
- Apply to all selected
Cleaning Up Duplicates
- Find duplicate species (search by name)
- Decide which is canonical (most complete, most submissions)
- Open the duplicate/defunct species → Edit
- Scroll to Danger Zone → "Merge Into Another Species"
- Search for and select canonical species
- Confirm merge
- Defunct species is deleted, all data moves to canonical
Adding Name Variants
When to use:
- Species has multiple common names (e.g., "Guppy" and "Fancy Guppy")
- Scientific name has changed/has synonyms
- Regional name variations
How to add:
- Open species → Edit
- Scroll to Name Variants section
- Click "+ Add" for Common or Scientific names
- Enter name in inline form
- Name is added to list immediately
Marking CARES Species
- Open species → Edit
- Check "CARES Conservation Species" checkbox
- Save changes
- Species will show ⭐ in list and during approvals
Technical Details
HTMX Architecture
The interface is built with HTMX for server-driven interactions:
- List filtering submits form on change
- Edit page loads in full page (not sidebar despite issue naming)
- Name add/delete uses HTMX partials
- Dialogs (merge, bulk edit) use HTMX with
beforeendswap - No page reloads for most actions
Typeahead Initialization
Dialogs with typeaheads use inline initialization scripts:
(function() {
const typeahead = document.getElementById('canonical_species');
if (typeahead && typeof TomSelect !== 'undefined') {
const config = getTypeaheadConfig(typeahead);
const instance = new TomSelect(typeahead, buildTomSelectOptions(typeahead, config));
}
})();
This pattern ensures typeaheads work in HTMX-loaded content.
Validation
All forms use Zod schemas:
speciesEditForm- Edit page validationbulkSetPointsSchema- Bulk points validationmergeSpeciesSchema- Merge validation with self-merge prevention
Database Functions
Key functions in src/db/species.ts:
getSpeciesForAdmin()- Paginated list with filtersgetSpeciesDetail()- Single species with metadatagetNamesForGroup()- Get common + scientific namesupdateSpeciesGroup()- Update species metadataaddCommonName()/addScientificName()- Add namesdeleteCommonName()/deleteScientificName()- Delete namesbulkSetPoints()- Update points for multiple speciesmergeSpecies()- Merge defunct into canonical species
Security & Safety
Authentication
- All routes require admin authentication via
requireAdminmiddleware - Non-admin users get 403 Forbidden
Data Safety
- Deletes require confirmation dialogs
- Merge warns about permanent action
- Cannot delete last name variant (species must have at least one name)
- Bulk operations show count of affected species
- Transaction-based updates (all or nothing)
Validation
- Canonical names must be unique (genus + species combo)
- Points must be 0-100 or NULL
- Species type must be Fish/Plant/Invert/Coral
- Program class validated against type-specific options
Performance
- Pagination: 50 species per page prevents loading all 2,300 at once
- Indexed queries: Database indexes on canonical_genus, canonical_species_name, group_id
- Split schema: Separate tables for common/scientific names reduces JOIN complexity
- Lazy loading: Synonyms loaded per-species only when needed
Empty States
Helpful empty states guide users:
No species found: Shows icon, message, "Clear Filters" button
No common names: Shows icon, "Add First Common Name" button
No scientific names: Shows icon, "Add First Scientific Name" button
Future Enhancements
See separate issues:
- #150 - CSV Import/Export
- #151 - Duplicate Species Detection
#152- Inline Point Editing (closed - bulk works better)
Troubleshooting
Problem: Dialog opens but immediately closes
Solution: HTMX event bubbling - parent form's afterRequest handler catching child events. Use event.detail.elt === this to check event source.
Problem: Typeahead not working in dialog
Solution: Include inline initialization script or use HTMX htmx:afterSwap event.
Problem: Can't delete name Solution: Can't delete last name for a species. Add another name first, then delete.
Related Documentation
- Database Schema - Full database structure
- HTMX Patterns - HTMX best practices
- Admin Routes - All admin endpoints