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:

  1. Check species you want to update
  2. Click "Bulk Edit Points" button (appears when species selected)
  3. Select point value (5/10/15/20) in dialog
  4. 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:

  1. Open species to be deleted (defunct)
  2. Click "Merge Into Another Species"
  3. 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
  4. Select canonical species and confirm
  5. All names and submissions move to canonical species
  6. 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 key
  • canonical_genus / canonical_species_name - Official names
  • species_type - Fish/Plant/Invert/Coral
  • program_class - BAP/HAP/CAP class
  • base_points - Default points (NULL if unassigned)
  • is_cares_species - Conservation flag (0/1)
  • external_references - JSON array of URLs
  • image_links - JSON array of URLs

species_common_name (Common Name Variants)

  • common_name_id - Primary key
  • group_id - Foreign key to species_name_group
  • common_name - Name variant (e.g., "Guppy", "Fancy Guppy")

species_scientific_name (Scientific Name Variants)

  • scientific_name_id - Primary key
  • group_id - Foreign key to species_name_group
  • scientific_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:

  1. Find species in list (use filters/search)
  2. Click name → Edit page
  3. Select points in dropdown
  4. Save changes

Bulk assignment (recommended for many species):

  1. Filter species (e.g., "Without Points" + "Cichlids")
  2. Check all species on page (or select individually)
  3. Click "Bulk Edit Points"
  4. Select point value
  5. Apply to all selected

Cleaning Up Duplicates

  1. Find duplicate species (search by name)
  2. Decide which is canonical (most complete, most submissions)
  3. Open the duplicate/defunct species → Edit
  4. Scroll to Danger Zone → "Merge Into Another Species"
  5. Search for and select canonical species
  6. Confirm merge
  7. 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:

  1. Open species → Edit
  2. Scroll to Name Variants section
  3. Click "+ Add" for Common or Scientific names
  4. Enter name in inline form
  5. Name is added to list immediately

Marking CARES Species

  1. Open species → Edit
  2. Check "CARES Conservation Species" checkbox
  3. Save changes
  4. 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 beforeend swap
  • 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 validation
  • bulkSetPointsSchema - Bulk points validation
  • mergeSpeciesSchema - Merge validation with self-merge prevention

Database Functions

Key functions in src/db/species.ts:

  • getSpeciesForAdmin() - Paginated list with filters
  • getSpeciesDetail() - Single species with metadata
  • getNamesForGroup() - Get common + scientific names
  • updateSpeciesGroup() - Update species metadata
  • addCommonName() / addScientificName() - Add names
  • deleteCommonName() / deleteScientificName() - Delete names
  • bulkSetPoints() - Update points for multiple species
  • mergeSpecies() - Merge defunct into canonical species

Security & Safety

Authentication

  • All routes require admin authentication via requireAdmin middleware
  • 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