Rastrum

Detailed tasks

Per-item roadmap breakdown. Subtasks with status and spec references.

Updated: 2026-05-12 · View roadmap

✓ Done ~ In progress ○ Planned ! Blocked

v0.1 Alpha MVP (online-first) Shipped

astro-skeleton Astro site skeleton + Tailwind + i18n
6/6

subtasks

Astro 5 project with output: 'static'
Tailwind 3 + @astrojs/tailwind integration
EN/ES locales via astro/i18n + prefixDefaultLocale
BaseLayout + DocLayout shared shells
Header + Footer with theme toggle, lang switcher, mobile menu
Sitemap (@astrojs/sitemap)
supabase-schema Supabase schema with PostGIS + RLS

02-observation.md · 05-map.md · 06-darwin-core.md · 07-licensing.md

8/8

subtasks

6 core tables (users, taxa, taxon_usage_history, observations, identifications, media_files)
PostGIS geography columns + GIST indexes
RLS owner + public-read + credentialed-read
3 triggers (auth-user-created, obs-count, sync-primary-id)
obscure_point() helper + obscure_level enum
Idempotent — replayable via make db-apply
Storage bucket 'media' + policies
Role-level grants (anon SELECT, authenticated CRUD)
auth-magic-link Magic-link auth + guest mode

04-auth.md

6/7

subtasks

supabase.ts singleton client
auth.ts helpers (sendMagicLink, exchangeCode, signOut)
Sign-in pages in EN + ES
Callback handles PKCE and implicit flows
Header avatar dropdown via onAuthStateChange
ObserverRef discriminated union
Hard-cap UI nudge after 3rd guest observation
auth-multi Google + GitHub OAuth, OTP code, passkey, sign-out-everywhere

04-auth.md

6/7

subtasks

signInWithGoogle / signInWithGitHub (with user:email scope)
Email OTP code path (verifyOtp)
Passkey enrol + verify (b64url ↔ ArrayBuffer)
WebAuthn feature detection
signOutEverywhere() global revocation
SignInForm shared component used by both locales
!
Operator: enable WebAuthn MFA toggle in dashboard

Blocked by: Manual dashboard step

ci-cd CI/CD via GitHub Actions

infra/testing.md · infra/github-actions.yml

5/5

subtasks

ci.yml — typecheck + test + build on PRs
deploy.yml — typecheck + test + build + deploy
deploy-functions.yml — manual Edge Function deploys
PUBLIC_* secrets pushed via gh secret set
Vitest job runs in CI
profile-basics Profile page + edit + avatar dropdown

08-profile-activity-gamification.md

7/7

subtasks

ALTER TABLE users with 11 profile/credential columns
ProfileView + ProfileEditForm shared components
Pages in EN /profile/* and ES /perfil/*
Initials-SVG avatar fallback
Header avatar dropdown (View / Edit / Sign out)
Activity feed section (renders v0.3+ events)
Badges section (renders when gamification opted in)
gps-observation GPS observation form with EXIF auto-fill

02-observation.md

9/9

subtasks

Multi-image capture/upload
Live GPS via getCurrentPosition
EXIF GPS fallback via exifr
Manual coords input
Habitat / weather / evidence_type dropdowns
Notes textarea (≤2000 chars)
NOM-059 / CITES privacy notice
Audio capture (MediaRecorder ≤30s)
Saves to Dexie outbox + immediate sync attempt
plantnet-id PlantNet photo ID integration

01-photo-id.md · 13-identifier-registry.md

7/7

subtasks

Edge Function with PlantNet → Claude waterfall
force_provider field for cascade routing
client_keys.plantnet BYO key support
0.7 confidence threshold
Plugin wrapper in identifiers/plantnet.ts
testConnection() probe
Operator: deploy + set secret
claude-haiku-id Claude Haiku 4.5 vision cascade

01-photo-id.md

4/5

subtasks

Cached system prompt (90% token savings)
JSON response parsing with code-fence stripping
client_anthropic_key BYO support
Cost-tracking probe in testConnection
Operator: optional ANTHROPIC_API_KEY secret
map-view MapLibre map with clustered observation pins

05-map.md

6/6

subtasks

OpenFreeMap Liberty style (free, IPv4)
GeoJSON source from observations table
Clustering at low zoom + pins at high zoom
Kingdom-coloured pins
Click pin → popup with thumbnail + link
pmtiles offline layer (handed off to v0.3 offline-maps)

→ tracked under offline-maps

darwin-core-csv Darwin Core CSV export

06-darwin-core.md

5/5

subtasks

darwin-core.ts mapping + CSV generation
Three column presets: DwC / SNIB / CONANP
Obscuration tier handling
ExportView component with format selector
9 Vitest unit tests
pwa-manifest PWA manifest + service worker shell cache

03-offline.md

4/4

subtasks

manifest.webmanifest with display:standalone
sw.js cache-first for same-origin GETs
Production-only registration (skip localhost)
Apple-mobile-web-app meta tags
offline-queue Dexie IndexedDB outbox + sync engine + identify trigger

03-offline.md · 10-media-storage.md

6/6

subtasks

RastrumDB with observations / mediaBlobs / idQueue
syncOutbox + registerSyncTriggers
R2 + Supabase Storage dual upload paths
Client-side image resize before upload
Cascade engine integration
Audio blob support
unit-tests Vitest unit-test scaffold

infra/testing.md

4/6

subtasks

Vitest 4.1 + happy-dom env
darwin-core.test.ts (9 tests)
byo-keys.test.ts (10 tests)
Make targets + CI integration
pgTAP RLS test suite
Playwright E2E suite

v0.3 Offline intelligence + activity Shipped

activity-feed Activity feed + server-side triggers

08-profile-activity-gamification.md

6/6

subtasks

activity_events table with kind enum (12 values)
RLS self/public + visibility tiers
fire_observation_created trigger
fire_research_grade trigger (auto-public)
Profile-page rendering with i18n labels
Auto-mark-as-read on view
unread-badge Unread-count badge on avatar dropdown

08-profile-activity-gamification.md

4/6

subtasks

Red dot with count when unread > 0
Refresh on every onAuthStateChange
99+ overflow text
Supabase RPC or client query for unread activity_events count
CSS pulse animation on new unread arrival
Accessible aria-label announcing unread count to screen readers
sensitive-privacy NOM-059 / CITES obscuration warning

02-observation.md

4/5

subtasks

Amber notice always visible in form
Schema obscure_level enum
sync_primary_identification trigger applies obscuration
location_obscured separate column for public reads
Per-species look-up at form-submit time
exif-extraction EXIF/XMP/ID3 metadata auto-extraction

02-observation.md

3/7

subtasks

exifr library wired in observation form
GPS extracted with EXIF source flag
DateTimeOriginal → observation timestamp
XMP sidecar parsing for drone/camera-trap metadata
ID3 tag extraction from audio recordings (MediaRecorder output)
Camera make/model stored on media_files for device analytics
Orientation tag auto-rotation before upload
webllm-text WebLLM Llama-3.2-1B for translation + field notes

11-in-browser-ai.md

5/6

subtasks

@mlc-ai/web-llm installed
loadTextEngine + translateNote + generateFieldNote
Profile/edit AI download card with consent modal
Cache management (probe / delete / re-download)
requestPersistentStorage() to prevent iOS eviction
Translate + Auto-narrative buttons in observation form
onnx-base EfficientNet-Lite0 ONNX base fallback (~18 MB INT8 hosted on R2)

13-identifier-registry.md

6/6

subtasks

Convert model to ONNX (TF Model Garden EfficientNet-Lite0)
Bundle ImageNet labels JSON
src/lib/identifiers/onnx-base.ts implementation
Image preprocessing (224x224 cover crop + ImageNet mean/std)
On-device inference via onnxruntime-web (cached in IndexedDB)
Profile/edit AI download card with consent + cache management
offline-maps pmtiles offline map download (Mexico zoom 0–10, ~48 MB)

05-map.md

5/6

subtasks

Mexico-bounded pmtiles archive hosted on R2
PUBLIC_PMTILES_MX_URL env var wired into the build
src/lib/offline-map.ts download + Cache API persistence
MapLibre pmtiles:// protocol registered for offline reads
Profile/edit download card with cell-conn warning
Per-region zoom 11–14 chunks on-demand
byo-anthropic-key BYO Anthropic key (client-set, never persisted)

01-photo-id.md · 13-identifier-registry.md

5/5

subtasks

byo-keys.ts central store backed by localStorage
client_keys.anthropic forwarded per call to Edge Function
Edge Function never logs or persists the key
Profile/edit Configure UI with Save/Test/Clear
testConnection() probe verifies a live key
webllm-default WebLLM as default AI fallback (download warning on first use)

11-in-browser-ai.md · 13-identifier-registry.md

5/5

subtasks

Phi-3.5-vision plugin registered in bootstrapIdentifiers()
Cascade automatically falls back to WebLLM when network plugins fail
First-use download dialog with size + persistence consent
Cache management (probe/delete/re-download) in profile
0.35 confidence cap to prevent low-quality auto-promote
identification-block Visible identification block in observation form

02-observation.md · 01-photo-id.md

5/5

subtasks

Inline ID block under photo grid (spinner + result chip)
Auto-fires on first photo + manual re-run button
Manual scientific-name input overrides cascade
Confidence + source surfaced (PlantNet, Claude, …)
Low-confidence amber notice when conf < 0.4
gps-two-pass Two-pass GPS: fast coarse fix then high-accuracy refinement

02-observation.md

5/5

subtasks

First call: enableHighAccuracy=false, ~1 s timeout
Second call: enableHighAccuracy=true, ~10 s timeout
Form preview updates live as fix improves
Cancel logic on form submit / reset
Falls back to EXIF GPS on timeout

v0.5 Beta

byo-keys-platform Per-plugin BYO API keys with guided setup

13-identifier-registry.md

6/6

subtasks

KeySpec + SetupStep + testConnection contract
byo-keys.ts central store
One-time legacy migration
Per-plugin Configure disclosure UI
Live save + Test + Clear buttons
10 unit tests
webllm-vision WebLLM Phi-3.5-vision fallback ID

11-in-browser-ai.md

5/5

subtasks

Phi-3.5-vision plugin in identifiers/phi-vision.ts
Confidence hard-cap 0.35 (DB trigger blocks <0.4)
Disclaimer in profile/edit + plugin description
Same consent + cache + delete flow as Llama
Cascade integration as last-resort opt-in
discovery-badges 39 seed badges + nightly evaluator

08-profile-activity-gamification.md

6/7

subtasks

badges + user_badges tables with RLS
39 badge seed (5 categories × 4 tiers)
Badge eligibility SQL functions
award-badges nightly Edge Function
Profile-page tier-coloured pills
Cron schedule 30 7 * * *
Activity-feed badge_earned event integration
quality-gates Confidence ≥ 0.4 enforcement on research-grade

08-profile-activity-gamification.md

2/6

subtasks

enforce_research_grade_quality trigger
Plugin-level confidence_ceiling cap
UI warning banner when identification confidence < 0.4
Admin dashboard widget showing research-grade rejection rate
Vitest coverage for trigger edge cases (null confidence, exact 0.4 boundary)
Documentation in module spec for quality-gate thresholds and rationale
consensus-workflow Research-grade 2/3 consensus + anti-sybil + expert weight

08-profile-activity-gamification.md

2/4

subtasks

prevent_self_validation trigger
recompute_consensus with 3× expert weight
Validator UI for community input
AFTER INSERT/UPDATE auto-recompute trigger
multi-image Multi-image observations

02-observation.md

4/4

subtasks

Gallery <input multiple>
Photo grid with primary indicator + remove
All blobs saved to Dexie + uploaded to R2
media_files inserted with sort_order + is_primary
eco-evidence Ecological evidence fields

02-observation.md

3/6

subtasks

evidence_type enum on observations
Form dropdown with 9 values
i18n labels for all 9 evidence_type enum values in EN + ES
Darwin Core mapping of evidence_type to DwC basisOfRecord
Filter by evidence_type on /explore/recent grid
Icon set for each evidence type in observation detail view
birdnet-audio BirdNET-Lite audio ID (Cornell Lab CC BY-NC-SA 4.0, ONNX hosted on R2)

12-birdnet-audio.md

9/9

subtasks

Audio capture in observation form (≤30s)
Sync engine routes audio to R2 + cascade
Module 12 spec written
BirdNET-Lite v2.4 ONNX hosted on R2 + PUBLIC_BIRDNET_WEIGHTS_URL
Audio decode + 48 kHz resample + 3 s window pre-processing
onnxruntime-web inference in plugin (top-K species)
Bundle species labels JSON (~6,000 species)
Profile/edit download card with consent + cache management
Cornell Lab CC BY-NC-SA 4.0 attribution in UI + DwC export
scout-v0 Rastrum Scout v0 (conversational ID, pgvector RAG)
0/6

subtasks

!
Enable pgvector extension

Blocked by: Deferred per future-migrations.md

!
Embedding pipeline (Voyage-3 over ~50K Mexican taxa)

Blocked by: Embedding budget (~$50)

taxon_embeddings table + HNSW index
scout Edge Function (retrieval + Claude composition)
Chat UI in app
chat_sessions table + history
onnx-regional Regional ONNX packs (Oaxaca, Yucatán)
0/5

subtasks

!
Training pipeline (TFLite Model Maker / PyTorch+Optimum)

Blocked by: ML training infra

Curated training set per region (~1K species each)
Convert to ONNX int8
Download cards in profile/edit
Region-aware cascade routing (lat/lng → pack)
gbif-ipt GBIF IPT pilot publish (DwC-A ZIP)

06-darwin-core.md

3/6

subtasks

!
GBIF publisher account application

Blocked by: ~2-week ed-org review

DwC-A ZIP generator (meta.xml + eml.xml + csvs)
export-dwca Edge Function (on-demand export)
publish-to-ipt.sh operator script
gbif-publish Edge Function (monthly cron)
DOI tracking in dataset_versions table
local-contexts Local Contexts BC/TK Notice integration
0/4

subtasks

!
Community consent (Zapoteco partnership)

Blocked by: Multi-month governance work

Local Contexts Hub API v2 integration
observation_bc_notices link table
BC/TK label rendering on observations + DwC
user-api-tokens User API tokens (rst_*, scoped, SHA-256 hashed)

14-user-api-tokens.md

5/5

subtasks

user_api_tokens table with hashed token + scopes[]
Crypto-strong rst_<base32> generation client-side
RLS owner-only on user_api_tokens
Plaintext shown to user exactly once on creation
last_used_at fire-and-forget update on each call
token-rest-api Token-authenticated REST API

14-user-api-tokens.md

7/7

subtasks

supabase/functions/api Edge Function deployed
/api/observe POST creates a row in observations
/api/identify POST runs the cascade with the user's BYO keys
/api/observations GET (paginated, RLS-scoped to caller)
/api/export GET returns Darwin Core CSV for caller
Scope enforcement (observe/identify/export) per route
Deployed --no-verify-jwt; the function validates rst_* itself
token-ui Token management UI at /profile/tokens (EN) and /perfil/tokens (ES)

14-user-api-tokens.md

6/6

subtasks

Locale-paired routes /profile/tokens and /perfil/tokens
Create form (label + scopes checkboxes) + plaintext modal
List existing tokens (label, scopes, last used, created)
Revoke action with confirm
i18n strings under tr.profile.tokens.*
Copy-to-clipboard button on creation

v1.0 Public Launch

streaks Opt-in streaks + grace window

08-profile-activity-gamification.md

7/8

subtasks

user_streaks table (current_days, longest_days, last_qualifying_day, grace_used_at) + RLS self-read + public-when-opted-in
recompute_streak(p_user_id) PL/pgSQL function with single-grace-per-30-days window logic
Quality gate: only identifications with confidence ≥ 0.4 count toward a qualifying day
supabase/functions/recompute-streaks Edge Function — iterates gamification_opt_in users, RPCs recompute_streak per user
pg_cron schedule (nightly 07:05 UTC) firing the Edge Function via net.http_post
StreakCard.astro on profile page — opt-in CTA, current/longest/last-active display, flame state (active/at_risk/broken/none)
summarizeStreak() helper in src/lib/social.ts derives state from row + today's date
Inbox-digest email for milestone streaks (7/30/100 days)
shareable-cards Observation share OG cards
7/7

subtasks

supabase/functions/share-card Edge Function — accepts ?obs_id=, format=html|svg, joins observations + identifications + users
1200×630 SVG renderer with brand stripe, gradient bg, italic species name, date + region + habitat meta
obscure_level='full' returns 404 — fully-withheld species cannot be share-card scraped
Sensitive-species visual badge when obscure_level > 'none' (still surfaces region, hides precise locale)
Public Astro page src/pages/share/obs/[id].astro renders observation with OG/Twitter meta tags pointing to share-card endpoint
Anonymous fallback when profile_public is false — observer rendered as 'Anonymous observer'
XML-escape all user-provided strings to prevent SVG injection
social-features Follows + comments + watchlists schema

08-profile-activity-gamification.md

8/8

subtasks

public.follows (follower_id, followee_id) PRIMARY KEY composite + RLS follows_self_manage + follows_public_read
Anti-self-follow CHECK constraint (follower_id ≠ followee_id) on follows
public.observation_comments with parent_id self-FK (single-level threading) + soft-delete via deleted_at + idx_comments_obs
comments RLS triplet: comments_authenticated_insert + comments_self_update + comments_public_read
public.watchlists (user_id, taxon_id, radius_km, region_geojson) + watchlists_self RLS
Comments.astro component — list + composer + reply (one-level) + edit/delete owned, 2000-char limit
FollowButton.astro — optimistic toggle, hidden when self-target, sign-in CTA on RLS-rejected writes
WatchlistView.astro — surfaces activity_events feed via RLS visibility=followers/public, mark-all-read
expert-system Expert taxonomic 3× weight

08-profile-activity-gamification.md

6/7

subtasks

users.is_expert (boolean) + users.expert_taxa (text[] e.g. ARRAY['Aves','Plantae']) columns on the users table
recompute_consensus(p_observation_id) PL/pgSQL — SUM weight 3.0 when u.is_expert AND t.kingdom = ANY(u.expert_taxa) else 1.0
public.expert_applications table (user_id, taxa[], credentials, institution, orcid, status enum) + indices for pending queue
expert_applications RLS — user reads/inserts own, UPDATE admin-only (withdraw via fresh row keeps audit trail)
ExpertApplyView.astro — taxa multi-select (11 kingdoms), credentials textarea, ORCID validation, pending/approved state UI
Already-expert short-circuit: ExpertApplyView reads users.is_expert and shows thank-you instead of form
Expert badges surfaced on profile page (insignia visual when is_expert + per-kingdom chips)
bioblitz-events-schema Events table + RLS (schema)

08-profile-activity-gamification.md

7/7

subtasks

public.events table — id, slug UNIQUE, name, description_md, organiser_id FK→users, starts_at, ends_at, kind
region_geojson geography(Polygon, 4326) NOT NULL — defines the spatial bounds for in-event observations
Three event kinds via CHECK (kind IN ('bioblitz','survey','challenge'))
Composite/temporal index idx_events_time on (starts_at, ends_at)
Spatial GIST index idx_events_region on region_geojson for fast obs-within-region queries
RLS enabled + events_public_read policy (anyone can SELECT — events are publicly browsable)
Idempotent schema (CREATE TABLE IF NOT EXISTS, DROP POLICY IF EXISTS … ; CREATE POLICY) — replayable via make db-apply
institutional-export DwC + SNIB + CONANP CSV presets

06-darwin-core.md

7/7

subtasks

DWC_COLUMNS — full Darwin Core column set in src/lib/darwin-core.ts (toCSV() default)
SNIB_COLUMNS subset — 14 columns aligned with CONABIO SNIB ingest schema (toCsvSnib())
CONANP_COLUMNS subset — 9 columns for ANP report format including informationWithheld (toCsvConanp())
ExportView.astro <select id="format"> with options dwc / snib / conanp
Switch in download handler picks the right serializer per fmt value
Filename suffixes per format — rastrum-{dwc|snib|conanp}-YYYY-MM-DD.csv via downloadCSV()
Unit tests for column subsets and CSV header order in src/lib/darwin-core.test.ts
credentialed-access credentialed_researcher RLS gate

07-licensing.md

5/6

subtasks

users.credentialed_researcher (boolean DEFAULT false) — admin-set after ID verification, no self-serve
users.credentialed_at (timestamptz) + users.credentialed_by (uuid FK→users) for audit trail
obs_credentialed_read RLS policy — SELECT bypasses obscure_level when caller's credentialed_researcher=true
Policy still respects BC/TK Notice withholding (community-level overrides remain in effect)
Idempotent ALTER TABLE … ADD COLUMN IF NOT EXISTS for the three columns
Application flow page (UI for self-serve credential request — admin reviews via dashboard)
env-enrichment Lunar phase + OpenMeteo weather Edge Function
7/7

subtasks

supabase/functions/enrich-environment Edge Function (Deno) — POST { observation_id }, service-role client
Conway lunar-phase algorithm — computes phase ('new'..'waning_crescent') + illumination 0..1 from observed_at UTC
OpenMeteo historical archive fetch (CC BY 4.0) — daily precipitation_sum + temperature_2m_mean for the obs lat/lng/date
UPDATE observations SET moon_phase, moon_illumination, precipitation_24h_mm, temp_celsius, post_rain_flag (>5mm) — 5 columns
Auto-fires from src/lib/sync.ts after observation sync — fire-and-forget (failures don't block the sync flow)
Idempotent — safe to invoke repeatedly on the same observation_id (UPSERT semantics via update + observation PK)
Partial-enrichment tolerance — OpenMeteo failure still writes lunar phase; lunar failure leaves columns NULL but doesn't 500
video-support Video support ≤30s (H.265/AV1)

02-observation.md

3/6

subtasks

Video capture in observation form (MediaRecorder ≤30s)
media_files row with media_type='video' + R2 upload
Video preview tile in form grid (autoplay muted)
Server-side ffmpeg transcoding pipeline
Frame extraction for ID + audio split for BirdNET
Video player on share/obs page
camera-trap-ingest Camera trap ingestion (on-device MegaDetector v5a YOLOv5 ONNX)

09-camera-trap.md · 19-batch-photo-importer.md

4/7

subtasks

Bulk-upload UI at /profile/import/camera-trap (drag-drop)
Shared trap location (single GPS) + per-photo EXIF timestamp
Plugin runtime:'client' running MegaDetector v5a via onnxruntime-web (megadetector-yolo.ts letterbox+NMS, megadetector-cache.ts download/cache)
FilteredFrameError short-circuits the cascade for empty/human/vehicle frames (cascade.ts + errors.ts)
!
Operator hosts the ~134 MB ONNX behind PUBLIC_MEGADETECTOR_WEIGHTS_URL

Blocked by: Operator action: convert v5a checkpoint and upload to R2

camera_trap_deployments + processing_queue tables (deferred)
Edge Function: motion → ID → research-grade pipeline
capacitor-ios Capacitor iOS App Store wrapper (v1.2)
0/5

subtasks

!
Apple Developer Program ($99/yr)

Blocked by: Subscription required

Capacitor scaffolding + platform/ios
Native plugins (Camera, Geo, FS, Network)
App Store metadata + screenshots
First TestFlight build
follows-comments-ui UI for follows + threaded comments + watchlists

08-profile-activity-gamification.md

3/5

subtasks

Follow button on public profile pages (FollowButton.astro)
Threaded comments component on share/obs page (Comments.astro)
Watchlist add/edit view in profile (WatchlistView.astro)
Followers' research-grade events in activity feed
Watchlist alerts (inbox digest, daily cron)
map-location-picker Interactive map picker for observation location

15-map-location-picker.md · 02-observation.md

5/5

subtasks

Drag-pin + tap-to-place on MapLibre
Locality search (geocoder) with EN/ES results
Reverse-geocode to populate locality string
Mobile bottom-sheet UX for the picker
Round-trip integration with ObservationForm fields
my-observations Personal observation list/history page

16-my-observations.md

6/6

subtasks

Route /profile/observations (EN) / /perfil/observaciones (ES)
MyObservationsView component (shared between locales)
Thumbnail grid with status pill (synced/pending/failed)
Click row → /share/obs/<id> for detail
Includes outbox-pending rows (unsynced)
Pagination + newest-first sort
camera-getUserMedia In-app camera via getUserMedia API with file-input fallback

17-in-app-camera.md · 02-observation.md

5/5

subtasks

navigator.mediaDevices.getUserMedia({video:{facingMode:'environment'}})
Live <video> preview + capture-to-canvas → Blob
Permission denied → fallback to <input type=file capture=environment>
Stream cleanup on close to avoid camera-on indicator
Identify + Observe surfaces both wire to it
batch-exif-importer Batch photo importer with EXIF GPS/datetime extraction

19-batch-photo-importer.md

6/6

subtasks

Drag-drop / file-input multi-photo intake
exifr extraction of GPS + DateTimeOriginal per file
Per-row review table (include/exclude, edit fields)
Bulk insert into Dexie outbox + queued sync
EN /profile/import/, ES /perfil/importar/ pages
Camera-trap deep-link (subroute) for staged uploads
oauth-custom-domain Custom auth domain on Supabase OAuth (auth.rastrum.org)

04-auth.md

0/4

subtasks

!
Supabase Pro plan ($25/mo) — required by Supabase for custom auth domain

Blocked by: Deferred for the zero-cost target; default Supabase callback host is fine for v1.0

DNS CNAME for auth.rastrum.org
Update Site URL + redirect allow-list in Supabase Auth
Re-test Google + GitHub OAuth flows on custom domain
mcp-server MCP server for AI agents (JSON-RPC over HTTP at /functions/v1/mcp)

15-mcp-server.md · 14-user-api-tokens.md

6/6

subtasks

supabase/functions/mcp Edge Function with JSON-RPC 2.0 transport
tools/list and tools/call backed by user_api_tokens scopes
5 tools: identify_species, submit_observation, list_observations, get_observation, export_darwin_core
initialize + ping unauthenticated for capability probing
Deployed --no-verify-jwt; the function validates rst_* itself
Documented Claude Desktop, Cursor, VS Code, Copilot Coding Agent integrations
rastrum-org-domain Migrate canonical domain to rastrum.org
6/6

subtasks

Register rastrum.org + DNS via Cloudflare
Update Astro site config + sitemap host
Migrate media bucket hostname to media.rastrum.org
Service worker pass-through host list updated
Old domain (rastrum.artemiop.com) 301-redirects
Documentation updated to reference rastrum.org
ux-revamp-pr1-ia-chrome UX revamp PR 1: IA + chrome rebuild

00-index.md

8/8

subtasks

chrome-mode helper + chrome-helpers module + routeTree (TDD)
i18n strings for tagline, explore dropdown, docs groups, drawer, bottom bar
Explore subroute placeholder pages (/explore/recent, /explore/watchlist, /explore/species)
301 redirect for /profile/watchlist to /explore/watchlist
MegaMenu, MobileBottomBar, MobileDrawer components
Header.astro rewrite with verb-first copy, mobile-friendly layout, accent-color classnames
BaseLayout pb-20 sm:pb-0 for mobile bottom-bar clearance
e2e coverage: active rail highlight, mega-menu renders, FAB targets /observe, drawer toggle, watchlist 301
bioblitz-events-ui Bioblitz events

08-profile-activity-gamification.md

0/6

subtasks

Bioblitz events — UI (event detail page, live aggregates, participation badges)
Event detail page with title, description, date range, and region polygon on map
Live observation count and species tally aggregated from event polygon
Participation badge awarded on bioblitz completion via award-badges function
Event listing page at /explore/events with upcoming/active/past filters
Join event button with auth check and participant count display

v1.1 UX polish (post-launch brainstorm) Planned

chat-improvements-v1-1 Chat v1.1: Gemma 4 text + entity context + 5 typed tools

20-chat

15/15

subtasks

loadGemmaTextEngine: reuse Gemma 4 E2B vision weights via transformers.js for text-only generation (no second download)
chat-engine: streaming + 1-round tool-call loop, Llama fallback on Gemma load failure
Generic EntitySpec registry mirroring src/lib/identifiers/ — 6 built-ins (observation/species/project/camera_station/observer/self_profile)
5 SECURITY INVOKER chat_find_* RPCs + chat_entity_card dispatcher + 6 per-kind card builders
chat-tools.ts: typed JSON tool layer with hand-rolled validators (no Zod), {ok|unknown_tool|invalid_args|network|offline} discriminated union
Two-button consent gate: side-by-side Gemma 4 (recommended) vs Llama 3.2 1B (lighter); refreshState checks both caches
AskRastrumButton + ?attach=kind:id deep-link parser; mounted on share-obs, public profile, project detail, species profile
ChatEntityPicker popover with 6 tabs and search; opens from composer's Context button
Empty-state suggestion chips (Near me, Review observation, How it works, NOM-059) seed input + auto-submit
Header model badge resolved client-side from cache status (Gemma 4 / Llama 1B / On-device)
Chat link added to MobileDrawer (was missing on <sm viewports)
tests/sql/chat.sql regression suite (10 assertions) + db-validate workflow wiring
Privacy invariants: obscure-coords on observation card, self-only on self_profile_card, no-centroid on observer_card
Telemetry events on rastrum:onboarding-event bus (chat.entity.attached, chat.tool.called, chat.tool.failed, chat.engine.fallback)
Runbook: docs/runbooks/chat-improvements.md + index link
community-validation Community validation queue (Module 22)

22-community-validation

7/7

subtasks

Spec v1.0 → v1.3 (Copilot review × 2 + schema audit)
SQL: validation_queue view + 3 RLS policies + UNIQUE index + tie-handling patch on recompute_consensus
Components: ValidationQueueView, SuggestIdModal, ValidationDashboardView
4 routes: /{en,es}/{explore,explorar}/{validate,validar}/ + profile counterparts
Research-grade chip on MyObservationsView + ExploreRecentView
Suggest CTA on every /share/obs/?id= page
Validate card on /explorar/ index
owner-observation-crud Owner CRUD on synced observations

02-observation · 07-licensing

4/4

subtasks

Manage panel on share/obs (visible only to owner)
Edit notes + obscure_level + scientific name override
delete-observation Edge Function — atomic R2 + DB delete
Client wired to delete-observation instead of direct DELETE (no orphan blobs)
og-pipeline Open Graph pipeline (zero per-request compute)

10-media-storage

5/5

subtasks

satori + resvg-js for build-time static-page PNGs
Client-side renderer in src/lib/og-card.ts (lazy import)
Hook into syncOne — per-observation OG card uploaded next to photo
Hook into ProfileEditForm — per-profile OG card on save
BaseLayout meta-tag mapping (path → /og/<slug>.png with override)
karma-phase-1-foundation Karma engine — schema, computation, basic UI (Phase 1)

23-karma-expertise-rarity

11/11

subtasks

user_expertise + taxon_rarity + karma_events tables
taxa.parent_id + ancestor_path trigger + name-rank backfill
is_expert → user_expertise migration shim
refresh_taxon_rarity() + nightly pg_cron
award_karma() helper
recompute_consensus calls award_karma + uses user_expertise weights
src/lib/karma.ts pure helpers + Vitest
Suggest modal pre-vote rarity + magnitudes microcopy
Profile karma section + top-5 expertise
Pokédex page (/profile/dex, /perfil/dex)
Apply schema via make db-apply (operator)
ai-sponsorships AI sponsorships — operator/community-sponsored Anthropic budget
14/15

subtasks

Schema + RLS (sponsorships table, monthly USD budget, owner/public read policies)
_shared/sponsorship.ts helper + unit tests (budget check, charge accounting)
sponsorships Edge Function (CRUD + budget reads, JWT-gated)
identify Edge Function modified to use sponsored key when within budget, BYOK fallback otherwise
UI + i18n + routes (sponsor settings page, profile widget, EN/ES paired)
Smoke tests + lint in CI (Edge Function smoke, type/lint gates)
Rollout + remove server-side ANTHROPIC_API_KEY (operator action)
M27 polish — 9 UX gaps (PR #84)
M27 complete coverage (PR #94)
Resend SMTP integration (active)
Sponsorship request flow
Public docs page /docs/sponsorships
Onboarding tour + replay
AI mode selector in ObserveView2 (Sponsored / Own key / Local toggle with localStorage persistence)

→ Shipped in ObserveView2; closes #328

Optional: delete ANTHROPIC_API_KEY env
admin-console-foundation Admin/Moderator/Expert console — PR1 foundation

24-admin-console.md

7/7

subtasks

Schema migration: user_roles + admin_audit + has_role + sync trigger + RLS
Console chrome mode + ConsoleLayout + sidebar + i18n
admin Edge Function dispatcher + 3 handlers (role.grant/revoke + user_audit)
Three tabs ship: Overview, Experts (moved), Audit log
Old /profile/admin/experts/ → 308 redirect
Module spec 24, runbooks (bootstrap, role-model, audit, ops), CLAUDE.md update
Vitest (chrome-mode, console-tabs, admin-client) + Playwright smoke
admin-console-pr2-users-credentials Admin/Moderator/Expert console — PR2 (Users + Credentials tabs, role grants UI)

24-admin-console.md

5/5

subtasks

ConsoleUsersView: search, role chips, grant/revoke slide-over
ConsoleCredentialsView: researcher role grants with notes field
Expert console tabs declared (5 tabs, all stub initially)
Role pill + sidebar routing for moderator + expert
E2E smoke for users + credentials not-auth banners
admin-console-pr3-ops-tabs Admin console — PR3 (Sync + API + Cron read-only ops tabs)

24-admin-console.md

5/5

subtasks

ConsoleSyncFailuresView: error groups table with 7-day filter
ConsoleApiQuotasView: daily quota burn-down + 30-day chart
ConsoleCronRunsView: pg_cron job status table
Moderator console foundation: ConsoleModOverviewView + 4 tabs
E2E smoke for sync + api + cron not-auth banners
admin-console-pr4-observations Admin console — PR4 (Observations admin tab + 4 obs handlers)

24-admin-console.md

5/5

subtasks

ConsoleObservationsView: global obs table with hide/obscure/license chips
Handler: observation.hide + observation.unhide
Handler: observation.obscure + observation.license_override
obs_public_read RLS excludes hidden observations
E2E smoke for observations not-auth banner
admin-console-pr5-moderator Console — PR5 (Moderator console: Flag queue, Comments, Soft-bans + 9 handlers)

24-admin-console.md

7/7

subtasks

ConsoleFlagQueueView: report triage/resolve/dismiss with slide-over
ConsoleCommentsView: hide/unhide/lock/unlock comments
ConsoleBansView: issue + lift soft-bans
Handlers: report.triage/resolve/dismiss (3)
Handlers: comment.hide/unhide/lock/unlock (4)
Handlers: user.ban + user.unban (2)
E2E smoke for flag-queue + comments + bans not-auth banners
admin-console-pr6-expert Console — PR6 (Expert console: Overview, Validation queue, Your expertise)

24-admin-console.md

7/7

subtasks

ConsoleExpertOverviewView: KPI counters + action items + recent contributions
ConsoleExpertValidationView: queue table filtered to expert_taxa + all-taxa toggle
ConsoleExpertExpertiseView: per-taxon scores + search/filter + SVG kingdom chart
Stub: ConsoleExpertOverridesView + ConsoleExpertTaxonNotesView
8 new EN/ES route pages + expert dispatch on /console/ root
Remove stub flag from 3 functional expert tabs in console-tabs.ts
E2E smoke for validation + expertise not-auth banners
admin-overview-real-kpis Admin overview — real platform-state KPIs + alerts + recent activity

24-admin-console.md

3/5

subtasks

Real KPI cards (user count, obs count, sync failures, cron health)
Alert panel (elevated sync failure rate, stale cron run)
Recent activity feed from admin_audit
7-day trend sparklines on each KPI card
Export KPI snapshot as CSV for monthly reporting
admin-console-pr7-remaining-admin Admin console PR7 — Badges + Taxa + Karma + Features + Bioblitz tabs

24-admin-console.md

6/6

subtasks

ConsoleBadgesView: list + detail + tier chart (stub write buttons)
ConsoleTaxaView: list + detail + rarity map (stub write buttons)
ConsoleKarmaView: config table + rarity multipliers + recent events
ConsoleFeatureFlagsView: flag grid grouped by category (stub toggles)
ConsoleBioblitzView: stub with upcoming feature notice
karma_events admin RLS fix (PR #88)
admin-console-pr8-hardening Admin console PR8 — CORS tighten + rate limit + pgTAP RLS suite + 5 write handlers + DB-backed config

24-admin-console.md

18/18

subtasks

CORS: restrict Access-Control-Allow-Origin to rastrum.org + localhost dev/preview ports
Token-bucket rate limiter (30 req/min/actor, cost 3 for writes) — in-memory per isolate
tests/pgtap/rls.sql — 24 TAP assertions covering has_role + 6 table RLS policies
db-validate.yml: install pgTAP + run RLS suite step
badge.award_manual handler — upsert user_badges + audit row
badge.revoke handler — delete user_badges + audit row
taxon.recompute_rarity handler — call refresh_taxon_rarity() RPC
taxon.toggle_conservation handler — update nom059_status/cites_appendix/iucn_category with taxon_conservation_set audit op
feature_flag.toggle handler — update app_feature_flags.value + updated_by
app_feature_flags table + RLS policies + seed from FEATURE_FLAGS
karma_config + karma_rarity_multipliers tables + RLS policies + seed from TS modules
ConsoleFeatureFlagsView: DB-backed reads + live toggle wired to adminClient.featureFlag.toggle
ConsoleBadgesView: Award + Revoke buttons live with slide-over forms
ConsoleTaxaView: Recompute Rarity + Toggle Conservation buttons live
ConsoleKarmaView: DB-backed reads (karma_config + karma_rarity_multipliers)
adminClient SDK: badge.{awardManual,revoke} + taxon.{recomputeRarity,toggleConservation} + featureFlag.toggle
4 new unit tests for badge + featureFlag SDK methods
Doc sync: progress.json + tasks.json + module 24 + admin-ops.md + CLAUDE.md
admin-console-pr9-quick-actions Admin console PR9 — admin/mod UX (reason templates + URL filters + g-prefix keybindings + mobile sticky bottom-sheet)

24-admin-console.md

5/5

subtasks

ConsoleSlideOver: pre-translated quick reason pills via data attributes
console-filter-state.ts: read/write/applyFilterState helpers (URLSearchParams + history.replaceState)
console-keybindings.ts: g-prefix Vim chord (g a / g u / g r…) + ? help overlay + Escape close
ConsoleSlideOver: sticky bottom-sheet on mobile (env(safe-area-inset-bottom))
i18n parity for all reason templates (EN/ES)
admin-console-pr10-subject-ux Admin console PR10 — subject UX (ban banner + appeals workflow + comment lock + hidden-obs owner badge)

24-admin-console.md

6/6

subtasks

BanBanner.astro mounted in BaseLayout for active bans
ban_appeals table + RLS + appeal.accept/reject handlers
AppealView.astro + /profile/appeal/ pages (EN/ES)
ConsoleAppealsView moderator queue + appeal lifecycle UI
Comments.astro: locked-thread banner + read-only mode
MyObservationsView: hidden-obs owner badge
admin-console-pr11-engineering Admin console PR11 — engineering hygiene (Postgres rate-limit RPC + flash polish + handler-registry test)

24-admin-console.md

4/4

subtasks

rate_limit_buckets table + consume_rate_limit_token() SECURITY DEFINER RPC
_shared/rate-limit.ts: replace in-memory Map with async RPC call (fail-open)
/console/* flash polish: hidden gate div + 1500ms fallback (static-only mode)
tests/unit/admin-handlers-registry.test.ts: regex-based consistency check
admin-console-pr12-observability Admin console PR12 — observability (anomaly detection + weekly health digest + forensics CSV + function_errors sink)

24-admin-console.md

7/7

subtasks

admin_anomalies + admin_health_digests + function_errors tables + RLS
detect_admin_anomalies() — high_rate / bulk_delete / off_hours rules + hourly cron
compute_admin_health_digest() weekly Mondays 09:00 UTC
anomaly.acknowledge + audit.export Edge Function handlers
_shared/csv.ts pure helper + 17 unit tests for CSV escape edge cases
_shared/error-reporter.ts wired into admin dispatcher catch
ConsoleAnomaliesView + ConsoleForensicsView admin tabs (EN/ES)
admin-console-pr13-future-proofing Admin console PR13 — future-proofing (time-bounded roles + two-person rule + webhooks + trust score primitive)

24-admin-console.md

9/9

subtasks

user_roles.expires_at + auto_revoke_expired_roles() daily 08:15 UTC cron
admin_action_proposals state machine + expire_stale_proposals() hourly cron
proposal.create / approve / reject handlers + IRREVERSIBLE_OPS registry
Self-approval guard in _shared/proposal-guards.ts (testable)
admin_webhooks + admin_webhook_deliveries tables + dispatch_admin_webhooks() trigger
HMAC-SHA256 signing helper + webhook.create/update/delete/test handlers
compute_moderator_trust_score() v1 placeholder + moderator_trust_scores view
ConsoleProposalsView + ConsoleWebhooksView admin tabs (EN/ES)
4 new runbooks: admin-time-bounded-roles, admin-two-person-rule, admin-webhooks, admin-trust-scores
admin-console-pr14-deferred-cleanup Admin console PR14 — close v1.1 deferred items (per-admin tz + webhook _meta replay + reconcile cron + real trust formula + irreversible enforcement gate + durable observability dry-run)

24-admin-console.md

9/9

subtasks

users.timezone column + per-admin off_hours rule (LEFT JOIN + COALESCE → UTC)
Profile → Edit timezone picker (9 IANA zones, canonical America/Argentina/Buenos_Aires)
admin_webhook_deliveries.nonce + .request_id columns + signed _meta envelope
reconcile_webhook_deliveries() SECURITY DEFINER + 2-min cron (writes back async pg_net status)
compute_moderator_trust_score() v1.1 formula (anomaly + overturn + active_days + recency, clamped 0-100)
checkIrreversibleEnforcement() pure function + enforce_two_person_irreversible feature flag (default off)
ConsoleSlideOver irreversible prop + 'Require approval' toggle on Users + Observations slide-overs
.github/workflows/admin-observability-dryrun.yml — one-shot 2026-05-06 + weekly Mondays + red badge if reconcile stuck
Doc sync: 4 runbooks updated, CLAUDE.md status line bumped, sentinel asserts +4
admin-console-pr15-observability-ui Admin console PR15 — observability UI (Health digest cards + sparklines + Errors browser with bulk ack + per-webhook deliveries drilldown + replay)

24-admin-console.md

10/10

subtasks

function_errors.acknowledged_at + acknowledged_by + ack_notes columns + partial unack index
audit_op enum +4: health_recompute, error_acknowledge, error_acknowledge_bulk, webhook_replay
Edge Function handlers: health.recompute + error.acknowledge[_bulk] + webhook.replay_delivery
ConsoleHealthView — hero card + 12-week sparklines + manual recompute (EN/ES)
ConsoleErrorsView — function_errors browser with URL filters + severity pills + auto-refresh + single + bulk ack (EN/ES)
ConsoleWebhooksView — per-webhook deliveries drilldown + filter chips + replay last + nonce copy
Pure helpers: src/lib/health-delta.ts + src/lib/error-severity.ts (10 + 8 unit assertions)
Console keybindings: g h → health, g e → errors (g x reclaimed for experts), help overlay updated
rls.sql 32nd assertion + sentinel +3 columns; admin-handlers-registry test bumps to 36 actions
Runbooks: admin-function-errors.md (new) + admin-health-digest UI section + admin-webhooks PR15 section + admin-ops 4 new entries
admin-console-pr16-entity-browsers Admin console PR16 — read-only entity browsers (Identifications, Notifications, Media, Follows, Watchlists, Projects, Taxon changes) on shared template + runtime

24-admin-console.md

13/13

subtasks

Hot-path indexes (filter_field, created_at DESC) on identifications, notifications, media_files, follows, projects, watchlists + admin-SELECT policies on notifications + watchlists
ConsoleEntityBrowser.astro shared template + entity-browser.ts runtime (server-side pagination, URL-driven filters, FK lookups, auto-populated dropdowns, drilldown expansion, rowIdFromRow for composite-PK tables)
ConsoleIdentificationsView — filter by identifier/taxon/source/validated/research-grade; drill-down full row + raw_response + obs deep-link
ConsoleNotificationsView — filter by recipient/kind (auto-populated)/read state/from-date; drill-down full payload jsonb
ConsoleMediaView — filter by type/state/observation; thumbnail with loading=lazy; drill-down full preview + R2 URL + EXIF
ConsoleFollowsView — filter by follower/followee (autocomplete)/status/tier; rowIdFromRow synthesises stable id from (follower_id, followee_id)
ConsoleWatchlistsView — filter by user/taxon/scientific_name (ilike); drill-down full row JSON. Schema note: no `region` column in v1; uses radius_km + digest_only as visible facets
ConsoleProjectsView — backed by projects_with_geojson view (security_invoker); filter by owner/visibility/name; drill-down GeoJSON + species_list + lazy obs count via head:true count(*)
ConsoleTaxonChangesView — backed by admin_audit filtered to taxon_* ops via baseline-filter sentinel; filter by taxon/action/actor/date; drill-down before/after diff JSON
EN+ES page wrappers for all 7 entity browsers (14 pages); console-tabs.ts registers taxon-changes; routes.consoleTaxonChanges + routeTree label pair
i18n: console.{identifications,notifications,media,follows,watchlists,projects,taxonChanges}View namespaces EN+ES + tab labels + entityBrowser shared strings
Tests: console-tabs.test.ts bumped to 39 tabs (32 + 7 PR16); entity-browser.test.ts (212 LOC: filter predicates + applyFilters + render helpers); 681 tests pass
Runbook: docs/runbooks/admin-entity-browsers.md (template pattern, performance characteristics, recipe for adding a new entity browser, composite-PK handling, lazy aggregate facets)
community-observers-pr17-ux-fixes Community observers UX fixes — privacy explainer + country CTA + GPS-based Nearby

28-community-discovery.md

10/10

subtasks

Privacy-default explainer banner — viewer-state-aware copy (private vs public profile); renders on 0 rows + signed in
Set-country inline CTA — only when viewer is signed in AND country_code IS NULL; links to /profile/edit/
community_observers_nearby_at(lat, lng, radius_m, …) SECURITY INVOKER RPC; granted to authenticated only; reuses GiST index on centroid_geog
📍 Use my location pill via navigator.geolocation; coords stored in sessionStorage (key rastrum.community.gps), NEVER URL — Referer-leak prevention
Fallback to centroid Nearby on geolocation deny; friendly toast
loadGps / saveGps / clearGps helpers in community-url.ts (testable, dependency-injected)
Regression-guard test: 'NEVER serializes GPS coords into the URL' + 9 sessionStorage round-trip / malformed / out-of-range cases
loadViewerCommunityMeta() — single query for {signedIn, profilePublic, countryCode}; no N+1
Sentinel verify entries for both nearby RPCs
CLAUDE.md M28 4th load-bearing rule + docs/runbooks/community-discovery.md UX clarifications section
console-chrome-rendering-fix Console chrome rendering fix — restore sidebar + role pills (70 console pages were using BaseLayout, ConsoleLayout was dead code)

24-admin-console.md

10/10

subtasks

Diagnose: ConsoleLayout existed but unused; all 70 /console + /consola pages imported BaseLayout, leaving the chrome unrendered
Refactor ConsoleLayout to be SSG-safe: props (allRoles, activeRole, currentPath, identity) made optional with sensible defaults
Server-render all 3 role tab lists with data-role attributes, all initially hidden; client script reveals based on getUserRoles()
Same pattern for role pills: all 3 ship hidden, JS reveals only those held by the viewer; active role styled with role accent
Active tab highlighting via currentPath = Astro.url.pathname (works at build time since each route has its own static HTML)
Swap BaseLayout → ConsoleLayout in all 35 EN console pages + 35 ES consola pages (70 total)
BaseLayout: console.warn at build time if a /console/* or /consola/* path slips through using BaseLayout (regression guard)
Verification: grep dist/ confirms <aside + data-console-pill + data-role admin/moderator/expert present on all sampled pages
New runbook: docs/runbooks/admin-chrome-rendering.md (the ConsoleLayout-only invariant)
CLAUDE.md Console section: 4th load-bearing rule documenting the ConsoleLayout-only contract + chrome-rendering runbook cross-link
ux-confidence-ring Confidence ring (SVG arc graded emerald→amber→red) instead of percentage pill

13-identifier-registry.md

6/6

subtasks

Confidence ring (SVG arc graded emerald→amber→red) instead of percentage pill — faster comprehension for non-technical users
SVG arc component with graded color stops (emerald > 0.7, amber 0.4-0.7, red < 0.4)
Animated arc fill on mount for visual engagement
Accessible aria-label with numeric confidence for screen readers
Replace percentage pill on observation detail and share card views
Vitest snapshot test for ring rendering at boundary values (0, 0.4, 0.7, 1.0)
ux-quick-taxon-chips Quick-taxon icons under photo upload (🌿 plant · 🐦 bird · 🐾 mammal · 🐛 insect · 🍄 fungus)

02-observation-form.md

6/6

subtasks

Quick-taxon icons under photo upload (🌿 plant · 🐦 bird · 🐾 mammal · 🐛 insect · 🍄 fungus) — primes the cascade and sets honest user expectations
Chip row component with 5 kingdom icons below photo upload area
Tap chip sets taxon_hint field on observation for cascade priming
Visual feedback: selected chip gets ring highlight + deselect on second tap
i18n labels for all 5 chip tooltips in EN + ES
Mobile-friendly touch targets (min 44px) with horizontal scroll on narrow screens
ux-photo-compression Auto-compress photos > 4 MP to 4 MP via canvas before upload

02-observation-form.md

6/6

subtasks

Auto-compress photos > 4 MP to 4 MP via canvas before upload — cuts R2 footprint 4×, speeds sync on slow networks, no ID quality loss
Canvas-based resize to 4 MP max before upload (preserves aspect ratio)
JPEG quality parameter tuned for ID accuracy vs file size tradeoff
Skip compression for images already under 4 MP threshold
EXIF orientation preserved through compression pipeline
File size reduction logged for sync performance analytics
ux-share-button Share button on result card → /share/obs/<id> with existing OG card; critical for viral pi

03-observation-detail.md

6/6

subtasks

Share button on result card → /share/obs/<id> with existing OG card; critical for viral pickup during family launch
Web Share API integration with fallback to clipboard copy
Share URL points to /share/obs/<id> with pre-rendered OG card
Share button positioned on observation result card after identification
Toast confirmation after successful share or clipboard copy
Analytics event tracking for share button taps
ux-save-as-draft Save observation as draft without GPS

02-observation-form.md

6/6

subtasks

Save observation as draft without GPS — current form blocks submit on missing location, breaking the flow for users in cell-dead zones
Draft status on Dexie outbox entries when GPS is missing
Resume draft flow: list drafts on observe page with edit/delete actions
Visual indicator distinguishing drafts from pending-sync observations
Auto-save to draft on page navigation away from incomplete form
Draft count badge on observe nav item when drafts exist
ux-onboarding-tour First-signup onboarding
6/6

subtasks

First-signup onboarding — 3 dismissible cards (install PWA → take a photo → see your profile); skippable; never repeats
3 dismissible onboarding cards (install PWA, take a photo, see your profile)
Cards shown only on first sign-up via localStorage flag
Skip button to dismiss all remaining cards at once
Never repeats after dismissal (localStorage persistence)
Bilingual card content in EN + ES matching user locale
ux-chat-suggestions Chat follow-up suggestion chips below each AI reply (¿Es venenosa?, ¿Cómo distingo de X?,

13-identifier-registry.md

6/6

subtasks

Chat follow-up suggestion chips below each AI reply (¿Es venenosa?, ¿Cómo distingo de X?, Hábitat) — reduce cold-start friction
Suggestion chip row rendered below each AI reply in chat
Context-aware chips based on identified species (toxicity, habitat, lookalikes)
Tap chip auto-fills chat input and sends follow-up question
Bilingual chip labels in EN + ES
Chips hidden after one is selected to avoid clutter
ux-chat-persistence Persist chat conversation history per device in Dexie
6/6

subtasks

Persist chat conversation history per device in Dexie — currently lost on reload
Dexie table for chat conversation history (per-device, per-user)
Restore previous conversation on /chat page mount
Clear conversation button with confirmation dialog
Conversation auto-pruned after 50 messages to limit IndexedDB size
Scroll-to-bottom on page load when restoring conversation
ux-explore-time-slider Time-slider on /explore map

12-explore-map.md

6/6

subtasks

Time-slider on /explore map — drag months to see phenological patterns; visually striking for first-time visitors
Range slider component for month selection on /explore map
GeoJSON source filtered by observation date range on slider change
Animated transition when filtering observations by time window
Month labels displayed below slider with locale-aware formatting
Reset button to clear time filter and show all observations
ux-skeleton-screens Replace bare spinners with skeleton screens on /observations, /explore, profile
6/6

subtasks

Replace bare spinners with skeleton screens on /observations, /explore, profile — cumulatively makes the platform feel snappier
Skeleton placeholder components for observation cards, profile sections, explore grid
CSS shimmer animation on skeleton elements for loading perception
Skeleton dimensions match real content to prevent layout shift (CLS)
Graceful transition from skeleton to real content on data load
Dark mode compatible skeleton colors via CSS custom properties
ux-voice-chat-input SpeechRecognition voice input in /chat

13-identifier-registry.md

6/6

subtasks

SpeechRecognition voice input in /chat — accessibility win, especially for indigenous-language native speakers
SpeechRecognition API integration with microphone permission handling
Voice input button in /chat with recording indicator animation
Transcript auto-fills chat input field on speech end
Language detection matching current locale (EN or ES) for recognition
Graceful fallback message when SpeechRecognition is not supported
ux-first-observation-celebration First-observation confetti + 'Bienvenido a Rastrum 🌱' banner on the user's literal first s

08-profile-activity-gamification.md

6/6

subtasks

First-observation confetti + 'Bienvenido a Rastrum 🌱' banner on the user's literal first synced observation
Confetti animation triggered on first synced observation completion
Bilingual welcome banner with species illustration
First-observation flag stored in user profile to prevent repeat
Banner auto-dismisses after 5 seconds with manual close button
Activity feed event logged for first observation milestone
ux-indigenous-taxa-search Indigenous-language taxon search (Zapoteco / Náhuatl / Maya / Mixteco / Tseltal → scientif

07-licensing.md

0/6

subtasks

Indigenous-language taxon search (Zapoteco / Náhuatl / Maya / Mixteco / Tseltal → scientific name); requires corpus + governance per local-contexts
Corpus partnership with CONABIO and community Co-PIs for indigenous name data
Indigenous name lookup table: Zapoteco, Nahuatl, Maya, Mixteco, Tseltal to scientific name
Search autocomplete supporting indigenous language input alongside Spanish/Latin
Governance review and community consent protocol before deployment
Local Contexts BC/TK notice integration for culturally sensitive taxa
ux-photo-dedupe Image deduplication on submit

02-observation-form.md

6/6

subtasks

Image deduplication on submit — perceptual hash warns when the same photo is being re-uploaded
Perceptual hash computation (pHash) on image upload in observation form
Duplicate detection warning when hash matches an existing upload in session
User can override warning and submit duplicate intentionally
Hash comparison runs client-side with no server round-trip
Performance budget: hash computation under 100ms per image on mid-range mobile
ux-streak-push Web Push notification at 8 PM local when a streak is 1 day from breaking

08-profile-activity-gamification.md

6/6

subtasks

Web Push notification at 8 PM local when a streak is 1 day from breaking — opt-in only, single nightly notification
Web Push subscription opt-in UI in profile notification preferences
Edge Function to check streaks at risk (1 day from breaking) nightly
Push notification payload with streak count and motivational message (EN + ES)
Respect user timezone for 8 PM local delivery via timezone column on users table
Rate limit: maximum one push notification per user per day
delete-observation-atomic Atomic delete-observation Edge Function

03-observation-detail.md

6/6

subtasks

Atomic delete-observation Edge Function — wipes R2 photos + OG card alongside DB rows; no orphan blobs
Edge Function that deletes observation + all media_files + R2 blobs in one transaction
R2 object deletion via presigned DELETE or service binding
OG card cleanup alongside media deletion (no orphan share cards)
RLS check ensuring only observation owner or admin can trigger delete
Confirmation dialog in UI before triggering irreversible delete
suggest-from-share-page Suggest identification from any /share/obs page

22-community-validation.md

6/6

subtasks

Suggest identification from any /share/obs page — community-suggestions list visible to all viewers
Suggest identification modal on /share/obs page for community input
Community suggestions list visible to all viewers below primary ID
Auth check: only signed-in users can submit suggestions
Suggestion includes taxon name, confidence, and optional notes
Consensus recomputation triggered when new suggestion is added
karma-phase-2-engagement Karma engagement layers

23-karma.md

0/6

subtasks

Karma engagement layers — toast, digest, leaderboards (Phase 2)
Toast notification on karma milestone achievements (100, 500, 1000 points)
Weekly karma digest email summarizing points earned and rank change
Community leaderboard page at /explore/leaderboard with opt-in visibility
Karma breakdown tooltip on profile showing points by category
Seasonal karma challenges with time-limited bonus multipliers
karma-phase-3-conservation-bonuses Karma conservation bonuses

23-karma.md

0/6

subtasks

Karma conservation bonuses — IUCN/NOM-059 multipliers (Phase 3)
IUCN Red List status multiplier (Vulnerable 1.5x, Endangered 2x, CR 3x)
NOM-059 category multiplier for Mexican endemic threatened species
First-observation-of-species bonus for new taxa records in a region
Protected area observation bonus when GPS falls within ANP polygon (M29)
Conservation karma leaderboard filtered by region and time period

v1.2 Profile privacy & public profile

profile-privacy-matrix Profile privacy matrix (module 25)

25-profile-privacy

15/15

subtasks

Schema: users.profile_privacy JSONB (19-key matrix) + dismissed_privacy_intro_at + GIN index
can_see_facet(target, facet, viewer) STABLE SQL function
can_see_facets() batch RPC (jsonb result)
Migration backfill from module 08's profile_public boolean
users_update_self_privacy RLS policy
Add Privacy as 5th tab in SettingsShell.astro (/profile/settings/privacy/) with 3 presets + 19-row matrix + live preview
Widen SettingsShell activeTab union: 'profile'|'preferences'|'data'|'developer'|'privacy' + add settings.tabs.privacy i18n keys (EN+ES)
Add 4 entries to PR-4 command-palette index: Privacy settings, Pokédex, View my public profile, Make profile private (action)
Register routeTree entries: profileSettingsPrivacy, publicProfile, publicProfileDex (PR-3 breadcrumbs/footer pickup)
Insert 6th onboarding step (PR-5 spotlight tour): Pick your privacy preset
Module 23 hand-off: replace user_expertise_public_read with user_expertise_facet_read
Onboarding step (module 18): Pick your privacy preset
Module 22 voter-link gating (anonymous expert when profile=private)
Intro banner + dismissed_privacy_intro_at
pgTAP test: 9-cell matrix coverage of can_see_facet()
public-profile-route Public profile at /u/<username>/

25-profile-privacy

14/14

subtasks

Astro routes: /en/u/[username]/index.astro + /es/u/[username]/index.astro
PublicProfileView orchestrator with batched can_see_facets() call
ProfileHero + ProfileObservationMap + ProfileTopSpecies + ProfileValidationReputation components
ProfileEmptyState (404-equivalent for profile=private)
OG card gating (public/signed_in/private renders)
Sitemap filter: exclude profile_privacy.profile <> 'public'
noindex + nofollow meta on non-public profiles
Person JSON-LD entity on /u/<username>/ when profile=public (PR-C/E SEO bar)
Per-page metadata for /u/<username>/: canonical, hreflang en↔es pair, OG/Twitter (PR-B parity)
Sitemap enumeration of public profiles (filter profile_privacy.profile = 'public', emit hreflang pairs)
301 redirect from legacy /{lang}/profile/u/?username=… to /u/<username>/
Update internal links: MyObservationsView, ProfileView, ExploreRecentView, ValidationQueueView, /share/obs
Rewrite PublicProfileView to use can_see_facets() instead of profile_public
Remove profile_public checkbox from ProfileEditForm; StreakCard sets profile_privacy.profile instead
profile-widgets-richer Richer profile widgets (heatmap, donut, streak, top species, mini-map)

25-profile-privacy · 08-profile-activity-gamification

5/5

subtasks

ProfileCalendarHeatmap (12-month SVG grid, click → filtered list)
ProfileTaxonomicDonut (kingdom/phylum/family breakdown)
ProfileStreakRing (current arc + longest faded outer arc)
Facet-gated views: profile_calendar_buckets, profile_taxonomic_donut, profile_top_species, profile_karma, profile_pokedex
ProfilePokedexLink + visitor route /u/<username>/dex/
social-graph-m26 Social graph + reactions (Module 26)

26-social-graph.md

13/13

subtasks

follows table + counters trigger + privacy helpers (social_visible_to, is_collaborator_of) + collaborator coord-precision unlock
per-target reaction tables: observation_reactions, photo_reactions, identification_reactions (+ RLS, kind enums, unique constraints)
blocks (read-symmetric) + reports (queue with reason enum) + notifications (kind enum + 90-day prune cron)
fan-out triggers: follow → notification, observation_reactions → notification (skip blocked recipients)
Edge Functions: follow (request/accept/reject + rate-limit), react (idempotent toggle), report (with operator email)
TypeScript types (types.social.ts) + social client (followUser, react, reportTarget, blockUser) + 5 unit tests
i18n: socialgraph.* namespace EN+ES (sidesteps existing flat social.* namespace) + new routes (inbox, profileFollowers, profileFollowing)
UI: ReactionStrip, FollowersView, FollowingView, InboxView, BlockedUsersList + EN/ES paired pages
tests: tests/sql/social-rls.sql regression + tests/e2e/social.spec.ts smoke
schema applied to prod (auto-fired by db-apply.yml on merge of #43); MIN(uuid) view bug fixed in #62
Edge Functions deployed (follow/react/report) — auto-deployed by deploy-functions.yml after CI/CD revamp in #62
UI integration (#63 + #64): BellIcon→/inbox + unread badge, rich notification cards (avatars + thumbnails + type-coded icons + skeletons + empty state), profile follower/following pills, profile overflow ⋮ menu (Block + Report), shared ReportDialog modal in BaseLayout (focus trap + Esc + backdrop close), FollowButton swapped to follow Edge Function with Requested state, ReactionStrip wired interactive on /share/obs/
v1.1 follow-ups (DELIVERED in PR #97 + #100 + #101): see social-graph-m26-v11, visitor-pokedex-route, deploy-functions-resilience entries below
social-graph-m26-v11 M26 v1.1 polish — reactions on feed + Block/Report on cards/comments + ARIA

26-social-graph.md

6/6

subtasks

observation_reaction_summary view (security_invoker = true) for batched reaction counts without N+1
❤ N reaction count chip on ExploreRecentView + MyObservationsView feed cards
Overflow ⋮ menu on observation cards (Block + Report) — same pattern as PublicProfileViewV2; hidden on owner cards
Block/Report per-comment in Comments.astro (lazy import of blockUser, ReportDialog with target='comment')
ARIA: aria-live region on ReactionStrip toggle, role=menu/menuitem on overflows, aria-label per InboxView row
i18n EN/ES under socialgraph.cards.* + socialgraph.reactions.* (12 new keys)
visitor-pokedex-route Public visitor Pokédex route /u/dex/?username=

25-profile-privacy.md

6/6

subtasks

Parametrize PokedexView.astro with visitor mode prop (owner self-view fallback when prop absent)
Privacy gate via can_see_facet(target, 'pokedex', viewer) RPC; private fallback when false
EN + ES paired pages at /u/dex/ with noindex,nofollow
PublicProfileViewV2.astro — un-hide Pokédex link for visitors (owner → /profile/dex/, visitor → /u/dex/?username=)
OG card: profile-dex-visitor slug in generate-og.ts + ogSlugForPath() in BaseLayout (EN + ES)
i18n: pokedex.visitor_* keys (title, subtitle, empty, private, not_found, not_found_cta, back_to_profile)
deploy-functions-resilience Pin esm.sh imports across Edge Functions for deploy resilience

13-identifier-registry.md

3/4

subtasks

Pin @supabase/supabase-js@2 → @2.39.7 across 24 Edge Function files
Verify all other esm.sh imports already pinned (aws-sdk@3.658.1, jszip@3.10.1, zod@3.23.8)
Inline pin during merge cascade for new file recompute-user-stats/index.ts that landed mid-PR
Follow-up: pin imports in PR #99's 5 new admin handlers once #99 merges (~5 LOC patch)
m26-v1-1-review-followups M26 v1.1 review follow-ups — minor polish from PR #100/#101 reviewer notes

26-social-graph.md

6/6

subtasks

?u= alias on readUsername() — removed (audit showed zero references outside docs across PublicProfileViewV2, FollowingView, FollowersView, PokedexView)
document.title in JS → use i18n key for consistency (PublicProfileViewV2, ExploreSpeciesView, PokedexView)
runVisitor catch-all → distinguish error vs not-found state (Pokedex visitor mode now shows visitor_load_error for network/RPC errors, visitor_not_found only for 0-row PGRST116)
ConfirmDialog component — replaces confirm()/alert() in Block flow + delete-photo flow (matches ReportDialog pattern; closes notes from PR #101 + #125 reviews)
Extract overflow-menu logic into lib/overflow-menu.ts (replaces 3× duplication across PublicProfileViewV2, ExploreRecentView, Comments — wireOverflowMenu(wrap, trigger, menu) returns teardown)
Standardize fave_count_one vs fave_aria_one data-attr naming — both consumers now use data-label-fave-aria-one/other
community-discovery-m28 Community discovery (Module 28)

28-community-discovery.md

5/5

subtasks

PR1 — schema deltas: 6 new users columns + 7 partial indexes scoped to (NOT hide_from_leaderboards AND profile_public) + iso_countries reference table + 28-country bilingual seed + normalize_country_code() + dual views (community_observers anon-safe / community_observers_with_centroid auth-only)
PR2 — recompute-user-stats Edge Function + nightly cron at 08:00 UTC + SECURITY DEFINER recompute_user_stats() wrapper
PR3 — backfill recompute-user-stats once after deploy to seed counters/centroids/country_code for existing users. Automated as a workflow_dispatch button at .github/workflows/community-backfill.yml (POSTs the EF, surfaces rows_updated/elapsed_ms in the Actions UI, idempotent)
PR4 — Profile → Edit: country picker + 'Show me in community discovery' toggle (UI-true → DB-false hide_from_leaderboards) + country_code_source 'auto'/'user' for the 'inferred from your region' badge + buildCommunityDiscoveryPayload() pure helper + 9 unit tests
PR5+PR6 — atomic landing: /community/observers/ CSR page + composable filter chips (sort, country, taxon, experts-only, nearby) + sign-in gate on Nearby + URL-state serializer + community_observers_nearby RPC + MegaMenu split (Biodiversity / Community columns) + MobileDrawer subheading + i18n rewrite of the two shipped 'no leaderboards' strings + OG card + roadmap + module spec status → 'shipped'
obs-detail-redesign Observation detail page redesign — viewer + owner edit
6/7

subtasks

PR1 — extract reusable MapPicker.astro from ObservationForm. Per-instance HTML IDs (-${pickerId} suffix). Native modal a11y preserved. Playwright regression test for the observe-form flow
PR2 — schema: observations.last_material_edit_at + media_files.deleted_at + idx_media_files_active + observations_material_edit_check_trg trigger (location > 1 km, observed_at > 24 h, primary_taxon_id change). Extract observation-enums.ts + obs_detail.* i18n namespace EN+ES
PR3 — PhotoGallery.astro (native lightbox + keyboard + swipe + canShare probe + dynamic 'Photo N of M' aria-labels) + ShareObsView.astro extraction to two-column desktop / stacked mobile + share/obs/index.astro slimmed to ~15 LOC + coords readout + 'Edited after IDs' badge + Playwright e2e (5 cases) + ShareObsView ternary strings migrated to obs_detail.view.*
PR4 — ObsManagePanel.astro Details tab: editable date/time + habitat + weather + establishment_means + scientific-name override + notes + obscure-level. Material-edit trigger fires automatically on save
PR5 — ObsManagePanel.astro Location tab: read-only view picker (pickerId='obs-detail-loc-view') + edit-modal picker (pickerId='obs-detail-edit') wired by wireManagePanelLocation; pointGeographyLiteral helper + 5 vitest cases + 5 Playwright e2e cases
PR6 — Photos tab + delete-photo Edge Function (atomic transaction via delete_photo_atomic SECURITY DEFINER RPC: media_files.deleted_at + identifications demote clearing validated_by/validated_at/is_research_grade + observations.last_material_edit_at). Photo add via R2 upload flow. Cascade-photo confirm dialog driven by willDemote() helper (covered by vitest)
v1.1 follow-ups: gc-orphan-media cron (R2 GC of soft-deleted blobs); native vs PhotoSwipe lightbox decision (was native ≤150 lines per spec)
projects-anp-m29 Projects (Module 29) — ANP polygon protocols

29-projects-anp.md

3/4

subtasks

Schema: projects (slug UNIQUE, polygon geography(MultiPolygon, 4326), visibility public/private) + project_members + RLS + GIST index
BEFORE INSERT/UPDATE OF location trigger on observations: ST_Covers(polygon, NEW.location) → auto-tag NEW.project_id (manual override respected, ORDER BY created_at ASC tiebreaker)
upsert_project SECURITY DEFINER RPC + projects_with_geojson view WITH (security_invoker = true) + UI (List/New/Detail) at /{en,es}/{projects,proyectos}/
v1.1 follow-up: surface Projects link from chrome (Header MegaMenu Biodiversity column) — currently only reachable via direct URL
cli-batch-import-m30 CLI batch import for camera-trap memory cards (Module 30)

30-cli-batch-import.md

4/4

subtasks

cli/ Node 20+ TypeScript package (rastrum-import) — walker + EXIF reader + orchestrator
POST /api/upload-url Edge Function endpoint — issues presigned R2 PUT URLs scoped by rst_* token
--project-slug flag auto-tags every observation into the M29 polygon. State file makes the import resumable across SD recoveries
Built for the CONANP-Oaxaca / DRFSIPS / PROREST 2026 workflow (500–2000 images per camera deployment)
camera-stations-m31 Camera stations + sampling-effort tracking (Module 31)

31-camera-stations.md

3/4

subtasks

Schema: camera_stations (project_id FK + station_key + coords + camera_model)
Schema: camera_station_active_periods (start/end) — multiple per station for redeployments across a year
Foundation for downstream wildlife indices: Relative Abundance Index (RAI), detection rate per 100 trap-nights, species richness — all blocked on knowing how long the camera was sampling
v1.1 follow-up: UI to capture active periods (CRUD form + station picker on observations + station map layer)
multi-provider-vision-m32 Multi-provider vision + per-sponsor model + platform pool (Module 32)

32-multi-provider-vision.md

4/4

subtasks

_shared/vision-provider.ts — VisionProvider interface implemented by 6 providers (Anthropic api_key + oauth_token, Bedrock with AWS Sig V4, OpenAI, Azure OpenAI, Gemini)
Per-sponsor preferred_model column on ai_credentials — beneficiaries can request Haiku/Sonnet/Opus or Bedrock equivalents (closes #116)
OpenAI / Azure OpenAI / Google Gemini / Vertex AI providers (closes #118)
Platform-wide call pool: sponsor_pools table + consume_pool_slot RPC (closes #115)
social-graph-m26-ui M26 UI integration

26-social-graph.md

7/7

subtasks

M26 UI integration — bell→inbox + unread badge, rich notification cards, profile follower/following pills + Block/Report overflow, shared ReportDialog with focus trap, ReactionStrip on /share/obs/, FollowButton on the new follow Edge Function (Requested state for private profiles)
Bell icon repointed to /inbox with unread badge from notifications table
Rich notification cards with avatars, thumbnails, type-coded icons, skeletons
Profile follower/following pills + overflow menu (Block + Report)
Shared ReportDialog modal with focus trap, Esc close, reason radio + optional note
FollowButton calling m26 follow Edge Function with Requested state for private profiles
ReactionStrip rewritten to self-hydrate and wired interactive on /share/obs/
ci-cd-edge-auto-deploy CI/CD
5/6

subtasks

CI/CD — auto-deploy Edge Functions on path-filtered push (mirrors db-apply.yml). Filesystem-derived function list eliminates drift; manual workflow_dispatch preserved for surgical rollback
Path-filtered GitHub Actions workflow triggering on supabase/functions/** changes
Filesystem-derived function list eliminates drift between UI enum and deploy loop
Manual workflow_dispatch preserved for surgical rollback of individual functions
Deploy job uses pinned supabase CLI version for reproducibility
Slack/email notification on deploy failure with function name and error log
ci-rls-presence-check CI gate
6/6

subtasks

CI gate — db-validate.yml fails if any public.* table lacks ENABLE ROW LEVEL SECURITY. Closes the loop on the Supabase rls_disabled_in_public lint by catching missing RLS at PR review time instead of via the after-the-fact security alert email
db-validate.yml workflow checking all public.* tables have ENABLE ROW LEVEL SECURITY
SQL query against pg_catalog to detect tables missing RLS
CI job fails PR if any public table lacks RLS (non-zero exit code)
Clear error message listing specific tables missing RLS in CI output
Runs on every PR that touches supabase/ or docs/specs/infra/ paths

v1.5 Territory Layer Planned

biodiversity-trails Biodiversity Trails with GPS waypoints + diversity metrics
0/5

subtasks

trails table (start/end, route LineString, observer)
Trail recording UI (start/stop, waypoints)
Per-trail observation linking (FK trail_id)
Diversity metrics per trail (S, H', D)
Trail detail page + map render
pits-qr PITs + QR/NFC anchors
0/5

subtasks

pits table (slug, polygon, install_location)
/{lang}/pit/<slug>/ page (obs in polygon)
QR generator for printing
NFC tag write protocol docs
Stats: visitors + species recorded at PIT
spatial-analysis Spatial analysis: ANP/INEGI/INAH GeoJSON layers
0/5

subtasks

Static GeoJSON for ANP boundaries (CONANP)
INEGI municipal boundaries
INAH archaeological-zone polygons
Layer toggle in MapLibre
PostGIS ST_Within queries (obs within ANP X)
diversity-indices Diversity indices: S, H', D, Chao1, Pielou J
0/4

subtasks

diversity-stats Edge Function (bbox + window → JSON)
Math: Shannon-Wiener, Simpson, Chao1, Pielou
Rarefaction curves
Render in trail / event / region pages
trail-pdf-export Trail PDF export (field guide style)
0/10

subtasks

Server-side PDF rendering (Puppeteer or react-pdf)
Cover map, species list, diversity stats
Bilingual templates
PDF layout engine selection (Puppeteer headless vs react-pdf/pdfkit)
Cover page with trail map snapshot (MapLibre static image API)
Species checklist table with thumbnails, scientific + common names, confidence
Diversity statistics section (species richness, Shannon index, observation count)
Bilingual EN/ES PDF templates with locale-aware date formatting
Edge Function endpoint for on-demand PDF generation with auth check
Download button in ExportView component alongside CSV options

v2.0 Institutional Planned

camera-trap-advanced Camera trap: occupancy modelling, activity histograms
0/9

subtasks

R or Python statistical pipeline
Activity histograms by species + time-of-day
Multi-camera grid analysis
Occupancy modelling pipeline (R or Python) with detection/non-detection matrices
Activity histogram visualization by species and time-of-day (24h polar chart)
Multi-camera grid analysis aggregating detections across camera_stations
Trap-night effort calculation from camera_station_active_periods (M31 dependency)
RAI (Relative Abundance Index) per species per station with confidence intervals
Export occupancy + activity results as CSV and integration with DwC-A
gbif-publisher GBIF dataset publisher + DOI generation
0/10

subtasks

Continuous DwC-A export
Versioned dataset releases with DOIs (Zenodo/DataCite)
Citation generator
Automated DwC-A ZIP generation via export-dwca Edge Function on schedule
Dataset metadata EML file with contact, citation, and license fields
Versioned dataset releases with semantic versioning and changelog
DOI minting via Zenodo or DataCite API integration
Citation generator producing BibTeX + APA + RIS formats
GBIF IPT registration and endpoint configuration
Operator runbook for initial GBIF publisher account setup
regional-ml Regional ML training pipeline

07-licensing.md

0/4

subtasks

Continuous fine-tuning on validated observations
Federated learning across institutional partners
Model card publication per release
License gate: only CC BY / CC0 enter training
b2g-dashboard B2G SaaS dashboard for CONANP / state agencies
0/5

subtasks

Separate Astro+React app at b2g.rastrum.app
Per-agency report templates
Stripe subscriptions
Audit logs + SLA monitoring
!
Cornell BirdNET commercial license signed

Blocked by: Cornell licensing process

inat-bridge iNaturalist import/export bridge
0/4

subtasks

iNat OAuth flow
Import: pull user's iNat observations
Export: push verified data back with attribution
Rate-limit compatibility

v2.5 AI + AR Planned

scout-full Rastrum Scout — full conversational field AI
0/4

subtasks

Multi-turn conversation refinement
Region-aware retrieval (lat/lng filtering)
Citation rendering for sources
Builds on scout-v0
ar-overlay AR species overlay in camera viewfinder
0/9

subtasks

WebXR or Capacitor ARKit/ARCore
Real-time camera frame → ID → 3D label
Performance budget: 30fps mobile
WebXR Device API feature detection with Capacitor ARKit/ARCore fallback path
Real-time camera frame capture and resize to model input dimensions
On-frame species identification via lightweight ONNX model or server cascade
3D label overlay anchored to detected organism bounding box
Performance budget enforcement: 30fps on mid-range mobile (Snapdragon 7xx)
Tap-to-capture flow: freeze frame + save observation from AR view
voice-indigenous Indigenous language voice I/O
0/9

subtasks

Whisper-tiny via transformers.js for STT
!
Custom-trained TTS for Zapoteco/Mixteco

Blocked by: No off-the-shelf TTS exists

Conversation flow in Zapoteco for Sierra Norte pilot
Whisper-tiny ONNX via transformers.js for client-side STT
Language detection and routing for Zapoteco, Mixteco, Nahuatl, Maya, Tseltal
!
Custom-trained TTS model for Zapoteco (Sierra Norte pilot)

Blocked by: Requires community partnership and voice corpus

Conversational flow in Zapoteco for observation form field-by-field input
Fallback to Spanish STT when indigenous language model confidence is low
Community governance review and consent protocol before deployment
conabio-api Formal CONABIO/CONANP/INAH partnership APIs
0/9

subtasks

MOUs with each agency
Whitelisted API endpoints with audit logs
Bidirectional sync agreements
Draft MOUs with CONABIO, CONANP, and INAH legal teams
Whitelisted REST API endpoints with rst_* token authentication
Structured audit log for all agency API calls (request/response metadata)
Bidirectional sync protocol: push observations to SNIB, pull taxa updates
Rate limiting and quota management per agency partner
Data format adapters: Rastrum JSON to SNIB CSV and CONANP XML schemas

Edit docs/tasks.json to update. The page rebuilds on every push.

Report an issue

Show diagnostics

Environment

 

Console errors

 

Failed requests