Conversation
New plugin with three subsystems: - Action Logger: MST onPatch() interception, action classification, debounced ring buffer - RL Data Pipeline: state encoding, potential-based reward shaping, episode management - Scavenger Hunt Widget: guided task system with validation, MTurk completion codes Includes JSONL/webhook export, Python Gymnasium converter, and tests for ActionClassifier, RewardCalculator, EpisodeManager, and TaskValidator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Register plugin in jbrowse-web corePlugins and package.json - Attach onPatch listener to session (not rootModel) to avoid interfering with view init processing - Guard StateEncoder property access with try/catch for views that haven't been rendered yet (e.g. width not initialized) - Wrap PatchListener callbacks in try/catch for resilience - Use queueMicrotask for episodeManager.recordAction to avoid disrupting synchronous MST patch processing - Only extract state from LinearGenomeView (skip Dotplot, etc.) All 78 tests pass (36 plugin + 42 jbrowse-web integration). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add public accessors (getExportManager, getEpisodeManager, getPatchListener) to RLAnalyticsPlugin for testing and external integration - New RLAnalytics.test.tsx validates full pipeline: MST patches → action classification → episode recording → JSONL export with volvox data - Add volvox-specific scavenger hunt tasks (ctgA/ctgB coordinates) - Remove debug logging from PatchListener Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Experiment 2: UMD bundle build (47KB) - build_umd.sh uses esbuild + post-processing to create IIFE bundle - All JBrowse deps resolved via window.JBrowseExports at runtime - Icons gracefully degraded (null) since @mui/icons-material not in re-exports Experiment 3: Local parasitic test infrastructure - config_with_rl_analytics.json: volvox config with UMD plugin entry - UMD bundle in test_data/volvox/ for dev server testing Experiment 4: Config generator script - generate_parasitic_config.mjs: fetches any JBrowse config, resolves relative URIs to absolute, injects RL analytics plugin entry Experiment 6: Webhook receiver - webhook_receiver.mjs: lightweight Node.js HTTP server for real-time analytics collection (POST /ingest, GET /status, GET /episodes) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Collapse multi-line imports before processing
- Convert 'import {X as Y}' to 'var {X: Y}' (destructuring syntax)
- Extract default export name from 'export { X as default }' pattern
- Add globalThis fallback for Node.js compatibility
- Validated: parses as valid JS, instantiates with mock JBrowseExports
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
umdUrl resolves relative to page root; umdLoc with {uri: "..."} resolves
relative to the config file's directory, which is what we need.
Validated in browser: 46 steps collected, 5 action types, full pipeline
through to Gymnasium .npz conversion confirmed working.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Route episode recording through debounced buffer (onDebouncedAction) instead of raw onAction — trackpad swipe bursts now merge into single actions (37 PAN_RIGHT -> ~5 debounced pans) - Increase debounce window from 100ms to 500ms for trackpad momentum - Fix track ID capture in classifier (check configuration string ref) - Fix ScavengerHunt view validation: use getSession(model) to access the LinearGenomeView for position checking - Fix generateCompletionCode MST action error: call setCompletionCode action from async .then() instead of setting volatile directly - Remove RLAnalytics from corePlugins (load only via UMD for parasitic) - Use umdLoc (relative to config dir) instead of umdUrl Validated in browser: 6 tasks completed, 21 debounced steps captured, completion code 21F5C2FB2EB5. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comprehensive GDD for "JBrowse Navigator" — a structured game-like funnel from MTurk/Prolific entrants to qualified genome browser users. Key design principles: - Tutorial through play (no explicit instructions, learn by doing) - Anti-speedrun by design (tasks require browser controls, not search bar) - Award system gates tier progression (Explorer, Eagle Eye, Curator, etc.) - Soft failure handling (retry, auto-advance, never stuck) - Every game mechanic maps to RL data quality signal 5-tier funnel: Hook (30s) → Discovery (2-3min) → Competence (3-5min) → Expertise (5-10min) → Mastery (3-5min). ~4% graduation rate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comprehensive expansion of the game design covering:
Awards (40+): Navigation, track/display, view types, feature interaction,
menu exploration, and mastery — each mapping to an RL-instrumented behavior.
Every discoverable JBrowse feature gets an award.
Level packs:
- "The Toolkit": menu exploration (right-click, rubberband, bookmarks, undo)
- "New Perspectives": dotplot, circular, synteny, split-screen views
- "The Lab": color-by-strand, compact mode, mismatch highlighting, sequence export
- "Variant Analysis": SNP/indel classification, impact triage, deletions
New action types (20+): RUBBERBAND_ZOOM, CONTEXT_MENU_OPEN, DISPLAY_CONFIG_CHANGE,
COLOR_BY_CHANGE, SORT_ALIGNMENTS, GET_SEQUENCE, EXPORT_SVG, ADD_BOOKMARK, UNDO/REDO, etc.
Visual feedback "dream" features:
- Pulsing highlight rings on quest-relevant UI elements (achievable now via CSS)
- Floating arrows with gentle bob animation (achievable now via DOM portal)
- Award burst particle effect (CSS-only)
- Quest beacon after 30s inactivity
- Viewport target overlay on overview bar
- Semantic zoom level HUD ("Genome"/"Region"/"Gene"/"Exon"/"Sequence")
- Progress trail on overview bar (fog-of-war clearing)
- Achievement gallery widget (aspirational)
Extended instrumentation architecture: MST patches + DOM events + extension
point hooks, unified through ActionClassifier into debounced ActionBuffer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Award system: - AwardManager tracks earned awards from action + state triggers - AwardChips display earned awards in widget with pop-in animation - AwardSnackbar shows transient notification when award earned - TierGate blocks task progression until required awards earned - Awards defined in task set JSON (6 awards: first_steps, eagle_eye, curator, collector, cartographer, biologist) Task system extensions: - navigate_constrained: requires specific action types, rejects search-only - action_required: validates required tracks are open - Navigation constraints: requiredActionTypes, minActions, zoomRange - Answer validation modes: exact, fuzzy, keyword_set, any_nonempty - searchPenalty, awardOnComplete, coaching messages - Auto-advance after max retries (default 3) - Tier 0-4 support (was 1-3) - Combined submit button (no more separate "Submit Answer" + "Submit & Continue") Full volvox game task set (game_tasks_v2.json): - Tier 0: Zoom In, Look Around (action-gated, no search possible) - Tier 1: Read the Code, Reveal Hidden Data, Name That Feature, The Other Contig - Tier 2: Gene Anatomy, The Neighbor, Variant Spotting, attention check - Tier 3: Layers of Evidence, Free Exploration - Tier 4: Teach a Robot, Graduation Action log fed to widget for navigation constraint validation. 38 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Narrator system: - NARRATOR.md: design brief defining personality (enthusiastic, patient, philosophical scientific coach), idle behavior, fog of war, drift notifications - Centralized locale file (src/ScavengerHunt/locale/en.ts) with all player-facing text keyed by ID, ready for localization - Narrator intro/success lines for every task, idle escalation, validation feedback, encouragement, drift notifications Auto-validation: - No more "Check" button for navigation/action tasks — game watches continuously (500ms polling) and auto-advances when goal is met - Text-answer tasks keep a "Submit" button but also auto-validate once an answer is entered - Narrator success line shown briefly before advancing to next task Task flow improvements: - Task intro line (narrator voice) shown when each task begins - Narrator message area replaces validation error alerts - Success state shown with green background, info with grey Re-added RLAnalytics to corePlugins (needed for dev server + tests; UMD still available for parasitic deployment separately). 38 tests pass. 74KB UMD bundle. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes from playtesting:
- Fix track name: use 'gff3tabix_genes' not 'volvox-sorted-vcf' (doesn't exist)
- Fix zoom task: accept ZOOM_IN OR ZOOM_OUT (not both required), mention
zoom buttons and trackpad pinch as alternatives to scroll wheel
- Fix "Name That Gene": better framing ("zoom in to comfortable level where
you can read labels") instead of vague "pan until you find a feature"
- requiredActionTypes now uses ANY-of logic (not ALL-of) — matching the
design intent ("you need to zoom" = either direction works)
- Add explicit VCF track opening task (t2-open-vcf) before variant questions
New: NARRATOR.md design brief
- Defines narrator personality: enthusiastic, patient, philosophical coach
- Establishes auto-validation as core principle (no "Check" button)
- Documents fog-of-war, idle nudges, drift notifications
- Mandates centralized text assets for localization
New: src/ScavengerHunt/locale/en.ts
- All narrator text centralized by key
- Intro/success lines for every task
- Idle escalation (30s/60s/90s/120s/180s)
- Validation feedback, encouragement, drift notifications
- Ready for localization (create fr.ts, es.ts, etc.)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major UX changes from playtesting feedback: Floating panel: - Quest UI now renders as a fixed-position floating panel (React portal to document.body) instead of in JBrowse's widget drawer - Stays visible when track selector or other widgets open - Minimizable to a small "Q" tab, click header to toggle - 320px wide, right-aligned, with scroll Narrator log (chat-like history): - Messages accumulate in a scrollable list, never replaced - Auto-scrolls to bottom on new messages - Color-coded by type: intro (grey), success (green), hint (blue), award (gold), system (dim grey) - Fade-in animation on new entries Task card redesign: - Simplified: left border accent, no CardHeader overhead - Title + description inline - Hints shown below task card, expandable Removed "Check" button entirely for nav/action tasks. Text tasks keep a "Submit" button but also auto-validate. 38 tests pass. 79KB UMD. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Architecture rewrite based on code review findings: GameEngine (new, pure logic, no React): - Owns task lifecycle, award checking, narrator events, auto-validation - Driven by actions from PatchListener (onAction) and text answers - Widget is now a thin renderer of GameEngine state - No more setInterval polling — validation runs on each action Bugs fixed: - AwardManager now actually wired: actions feed into checkAction() with real BrowserState, awards fire reactively from state thresholds - prevState/nextState in EpisodeManager: cache previous state so reward shaping actually computes non-zero spatial rewards - completeCurrentTask dedup: check completedTaskIds.includes before push - zoomRange added to MST NavigationConstraintModel (was silently dropped) - No more render-during-render: graduation message handled by GameEngine - Module-level actionLogRef removed: action log lives in GameEngine Widget simplification (ScavengerHuntWidget.tsx): - Pure renderer of GameEngine + MST model state - No game logic, no validation, no polling - Floating panel with scrollable narrator history preserved - ~160 lines (was 318) 38 tests pass. 78KB UMD. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ility 1. Remove all scroll wheel / trackpad references from task descriptions, hints, coaching, and narrator lines. Only reference + / - buttons. 2. Revert to regular JBrowse widget drawer (remove FloatingPanel). The floating panel looked bad; widget drawer is the standard UX. 3. Basepairs task now requires volvox_refseq track to be visible (requiredTracks). The Eagle Eye award was firing when bases weren't visible because the reference track wasn't open. 4. Task description now tells user to open the reference sequence track first, then zoom in. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The scavenger hunt is now a JBrowse view (ScavengerHuntView) instead of a sidebar widget. Views live in the main content area and persist when other widgets (track selector, feature details) open. Architecture: - viewModel.ts: MST model composing BaseViewModel, with task state, awards, answers, hints. Has height property for resizing. - ScavengerHuntView.tsx: horizontal layout with narrator log on left, task panel on right. Scrollable within its height. - GameEngine wired via setGameEngine() module-level ref (same pattern) - Auto-loaded from ?scavengerTasks= URL param as a view The view appears alongside the LinearGenomeView, so both are visible simultaneously. Default height 250px. Old widget code preserved in model.ts/ScavengerHuntWidget.tsx but no longer registered — can be removed later. 38 tests pass. 74KB UMD. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Submit button: - submitValidation() now adds narrator feedback on failure (shows the validation reason in the narrator log as a hint) - tryAutoValidate() remains silent on failure (for action-triggered checks) - "Enter an answer first" shown if submit clicked without typing Gene name recognition: - Expanded keyword list to include ALL named features from volvox GFF3: EDEN, Apple1-4, seg01-15, b101, agt830, agt221, agt767, FakeSNP - Case variants included (Eden, eden, apple1, etc.) - Validation already case-insensitive (lowercases both sides) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Removed: entire ScavengerHunt game layer (tutorial, awards, narrator, GameEngine, TaskValidator, locale, all game components). Preserved on branch rl-analytics-tutorial-v1. Kept: ActionLogger, RLPipeline, Export — the core instrumentation. Enhanced BrowserState observation (for RL agent consumption): - zoomLevel: semantic classification (genome/region/gene/sequence/basepair) - Track type booleans: hasReferenceSequence, hasGeneTrack, hasAlignmentTrack, hasVariantTrack, hasQuantitativeTrack - activeTracks: full TrackInfo[] with trackId, trackType, displayType - visibleContentBlocks: count of rendered content blocks - Session-level features: sessionDurationMs, totalActionsThisSession, actionCountsByType, uniqueRefNamesVisited - Numeric encoding expanded from 11 to 17 dimensions New: RLObserverView — a logging console view showing what the RL system sees in real time. Opens via Add → RL Observer or ?rlObserver. Fixed: EpisodeManager caches prevState between steps. 30KB UMD. 26 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
MobX volatile plain arrays don't trigger re-renders on push(). Fix: reassign array on every addLogEntry() call. Removed debug console.log statements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When zoomTo() internally calls scrollTo(), onPatch sees ZOOM + PAN as separate events. Now using MST onAction() to annotate each patch with its sourceAction name. The ActionBuffer absorbs PAN patches that share the same sourceAction as a preceding ZOOM (e.g., both from 'zoomTo'). This means rubberband zoom on the navbar now produces a single ZOOM_OUT entry instead of ZOOM_OUT + PAN_LEFT + PAN_RIGHT. The sourceAction metadata is included in the observer log (shown in parentheses) and in the exported JSONL data. Fallback: <10ms gap detection still works for cases where sourceAction isn't available (e.g., UMD deployment without onAction support). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The old architecture classified actions by regex-matching on patch paths (e.g., /views/\d+/bpPerPx → ZOOM_IN). This was fragile, missed compound actions, and required heuristic absorption of sub-action patches. New architecture: MST onAction fires once per action call with the action name (zoomTo, horizontalScroll, navTo, showTrack, etc.) directly. No regex, no compound-action ambiguity. Sub-actions (scrollTo inside zoomTo) are filtered by simply not including them in the ACTION_MAP. Changes: - New ActionListener replaces PatchListener + ActionClassifier - ACTION_MAP maps MST action names → semantic ActionType enum - Only user-meaningful actions are captured (internal actions ignored) - Action args extracted as metadata (bpPerPx, distance, trackId, etc.) - Observer log now shows MST action name (zoomTo, horizontalScroll, etc.) instead of patch-derived classifications - Simplified ActionType enum: ZOOM (not IN/OUT), PAN (not LEFT/RIGHT) — direction is in metadata, the action type is the user intent - Removed PatchListener.ts, ActionClassifier.ts, ActionClassifier.test.ts - Updated all tests for new types 30KB UMD. 14 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
onAction only fires for actions on the target node itself, not child nodes. View-level actions (zoomTo, horizontalScroll) were missed when listening on the session. addMiddleware intercepts ALL actions in the entire MST tree. Sub-actions (scrollTo inside zoomTo) are filtered via call.parentActionEvent — only top-level user-initiated actions are captured. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
addMiddleware on session still only catches session-level actions. Attaching to rootModel (the true MST root) intercepts everything in the entire tree: views, tracks, widgets, session. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
From console debugging, rubberband zoom fires as 'moveTo' (not zoomTo) at the top level. Panning via click-drag fires 'horizontalScroll' which calls 'scrollTo'. Both now in the map. Sub-action filtering via parentActionEvent correctly handles: - moveTo → zoomTo → scrollTo (only moveTo captured) - horizontalScroll → scrollTo (only horizontalScroll captured) Removed debug console.log statements. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
moveTo now shows target coordinates, scrollTo shows offset. Temporary debug logging to identify the track-add MST action name. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Track selector uses 'toggleTrack' (not 'showTrack') as the MST action. Added to ACTION_MAP with metadata extraction for trackId. Removed debug logging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Monitor New instrumented actions: - moveTrackUp / moveTrackDown → REORDER_TRACK (with direction metadata) - addToHighlights / removeHighlight → BOOKMARK - undo / redo → UNDO View now displays as "Action Monitor" instead of "Untitled view". Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bookmarks: add 'addBookmark' (the actual widget action name) - Track reorder: add moveTrack, moveTrackToTop, moveTrackToBottom - View name: pass displayName in initial state (not setDisplayName after) - Metadata for moveTrack (movingId, targetId) To bookmark a region: rubberband-select on the genome view (click-drag while holding shift), then choose "Bookmark region" from the popup menu. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- moveTo args are BpOffset objects, not numbers — fixed metadata extraction - moveTrack now shows movingId → targetId in observer - Added addBookmark to ACTION_MAP (actual bookmark widget action name) - Bookmark: use rubberband select (click-drag without shift on overview bar, or right-click after selecting) → "Bookmark region" Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- View name: setDisplayName('Action Monitor') called on both new and
session-restored observer views. Also finds existing observer from
session restore instead of creating duplicates.
- moveTrack: show "reorder" instead of cryptic auto-generated instance IDs
- moveTo: show refName instead of NaN (args are BpOffset objects)
- Menu label changed from "RL Observer" to "Action Monitor"
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
moveTrack args are MST auto-generated instance IDs (e.g. B_cm5usGvr). Now resolved to config trackIds (e.g. volvox_gc) by looking up the instance ID in the view's track list at action time. Observer now shows: moveTrack volvox_gc → before volvox_refseq Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The track configuration can be a string reference (the trackId itself) or an object with a trackId property. Now handles both. Also searches through session.views if call.tree doesn't have tracks directly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The call.tree in middleware doesn't reliably expose the track list. Instead, resolve instance IDs to config trackIds using the live LinearGenomeView accessed via getView() at log-display time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ActionListener was trying to resolve IDs via call.tree which doesn't work reliably. Now stores raw instance IDs in metadata, and resolves them to config trackIds in logToObserver using the live view. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JBrowse's drag-and-drop calls moveTrack with the same ID for both moving and target on drag initiation. These are no-ops that don't actually reorder anything. Now filtered in the middleware. The actual track order is captured in the BrowserState observation (activeTracks array), which is what matters for RL training. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Revert the no-op filter — moveTrack(id, id) IS the reorder event from drag-and-drop. Show resolved track name without the cryptic raw IDs. Feature clicks are already captured as OPEN_WIDGET with widgetType 'BaseFeatureWidget' — no additional instrumentation needed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search bar: - navToLocString captures raw location string (e.g. "ctgA:1000-2000") - navToSearchString captures raw search input text (e.g. "EDEN") - Shown in observer as: navToLocString "ctgA:1000-2000" Display config (track-level): - setColorScheme: color-by changes (strand, insertSize, etc.) - setSortedBy / setSortedByAtPosition: read sorting - setFeatureHeight: track height changes - setDrawSingletons, setDrawProperPairs, setDrawInter, setDrawLongRange - setLineWidth: arc display line width SVG export: - exportSvg captured as CONFIG_CHANGE Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- navToLocString: capture both raw string (if string) and parsed target - setColorScheme: stringify the full colorBy arg when type field is absent (fixes empty display when track initially sets its color scheme) - JSONL verified: 9 steps, search queries, config changes all captured Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
addBookmark fires as a sub-action of menu click handlers, so it was filtered by the parentActionEvent check. Now whitelisted alongside addToHighlights. Confirmed working: bookmark creation, bookmark navigation (shows as navToLocString with JSON target). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New state fields: - viewportCenterBp: center of viewport in genomic coordinates - displayedRegions: all displayed regions (for multi-region views) - labelsVisible: heuristic — feature labels readable at current zoom - openWidgets: list of open widget types (feature details, bookmarks, etc.) Enriched TrackInfo: - height, colorScheme, sortedBy per track (display config state) Observation vector: 21 dimensions (was 17): +viewportCenterBp, +labelsVisible, +openWidgets count, +displayedRegions count Updated Python Gymnasium converter: - New ACTION_MAP matching current ActionType enum - 21-dim encode_state matching JS StateEncoder - Verified: (9, 21) observations from latest JSONL Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
For unsupervised exploration data collection, there is no meaningful in-situ reward. The placeholder (-0.01/step, -0.5 oscillation) was dishonest — it encoded assumptions about what "good" navigation is. Reward field is now 0 for all steps. Reward shaping is a post-processing step on the exported JSONL, defined by the RL researcher's objective. Removed: RewardCalculator.ts, RewardCalculator.test.ts Removed reward display from Action Monitor log lines. 11 tests pass. 36KB UMD. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Git hygiene: - Remove node_modules/ symlinks from git (9 files) - Remove dist/ build artifact from git - Add .gitignore for dist/ - Move design docs (GAME_DESIGN.md, etc.) to docs/ subdirectory Config schema: - Strip to 5 slots that are actually read at runtime - Wire all config values via getConf() with safe fallbacks - Remove 6 dead slots (scavengerTasksUrl, rewardShaping, etc.) Plugin lifecycle: - No more mutable state on plugin singleton (except _testRefs) - All subsystems created as local variables in configure(), wired via closures - Observer view found dynamically via getObserverView() with isAlive() check - No stale reference to destroyed view model Memory: - Episode eviction: maxEpisodes config (default 100), oldest evicted on overflow - Prevents unbounded memory growth from long sessions Dead code removed: - RewardCalculator (already removed) reference in EpisodeManager - resolveTrackId() in ActionListener (never called) - onAction() callback API in ActionListener (never used) - ActionCallback type export (unused) - episode.taskId in JSONLExporter (field doesn't exist on Episode) Session access: - Centralized getSessionSafe() helper with proper typing - setTimeout(0) instead of queueMicrotask (actually yields to renderer) View model: - Accepts pluginManager parameter (JBrowse convention) - JSDoc #property / #action annotations for API docs - menuItems() view (empty but present, standard for views) 11 tests pass. 35KB UMD. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. Middleware scoped to session (not rootModel) — no longer intercepts root-level config/assembly actions. Only session children (views, tracks, widgets) are monitored. 2. _testRefs moved to module-level WeakMap — no mutable class fields. 3. Tests moved to src/__tests__/ (JBrowse convention). EpisodeManager test updated with eviction test and reward=0 check. 4. viewModel: postProcessSnapshot strips volatile log entries. menuItems() returns "Clear log" action. Accepts pluginManager param. 5. ObserverView uses MUI theme (useTheme) instead of hardcoded colors. Adapts to dark/light mode. 6. scrollIntoView throttled via requestAnimationFrame — no more continuous smooth-scroll during rapid actions. 7. Log entries use MobX observable.array for O(1) push reactivity instead of O(n) spread-copy on every entry. 8. WebhookExporter wired to config: webhookUrl config slot, read at configure() time, configureWebhook() called if non-empty. 11 unit tests + 3 integration tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- README.md: usage docs including parasitic deployment workflow - CodeReview.md: second-pass JBrowse-maintainer-perspective review - package.json: add 'pnpm build:umd' script - viewModel: remove dead no-op postProcessSnapshot - Remove stale test/EpisodeManager.test.ts (was untracked on disk) The parasitic deployment workflow can now be run as: pnpm build:umd # produces dist/*.umd.js node scripts/generate_parasitic_config.mjs <args> # generates config node scripts/webhook_receiver.mjs # receives data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The webhook pipeline was cosmetic — configureWebhook() created the exporter and started its flush timer, but nothing called webhookExporter.push(). Webhook POSTs always sent empty batches. Fix: onDebouncedAction now calls exportManager.webhook?.push(step, episodeId) after recordAction succeeds. The episode ID is returned from EpisodeManager.recordAction() alongside the step. Also: - EpisodeManager exposes currentEpisodeId getter - WebhookExporter.push signature: removed unused taskId parameter - test_data/volvox/config_with_webhook.json: sample config for testing Tested end-to-end: webhook receiver collects steps with correct episode_id, timestamps, and full observation/nextState payloads. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
getConf(model, slot) expects model.configuration as the navigation root, but we were passing the config node itself. Switched to readConfObject which takes the config node directly. Also moved config reading BEFORE subsystem creation so config values actually affect construction. Tested end-to-end: live browser session → webhook receiver collects PAN/ZOOM/ADD_VIEW/OPEN_WIDGET steps with correct episode IDs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Type safety:
- Replace all `(rootModel as any)` casts with typed `RootModelWithSession`
interface
- Use AbstractSessionModel, AbstractViewModel from @jbrowse/core/util
- No more `as any` in index.ts
Tests (11 → 25):
- Add ActionBuffer.test.ts: debounce, merge, type separation, eviction,
drain, dispose (6 tests)
- Add StateEncoder.test.ts: shape, zoomLevel, labelsVisible, track flags,
encode(), recordAction, unique refNames, throwing getters (9 tests)
- Remove unused ClassifiedAction.path field
UI code quality:
- LogEntry type: {id, text} with stable monotonic IDs
- ObserverView uses entry.id as key (no more re-association on eviction)
MUI version: verified already matches monorepo (7.3.8)
25 tests pass. Clean typecheck.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All pass-2 blockers now addressed. Plugin is in shape for a draft PR to GMOD/jbrowse-components. Remaining items are lower-priority polish (state memoization, delta encoding, broader integration tests). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…oc generator - StateEncoder: within-frame memoization keyed on view inputs - JSONLExporter: delta-encode next_observation against observation - EpisodeManager: dynamic inactivity check interval - New WebhookExporter test suite (mocked fetch, sendBeacon, restore-on-failure) - New StateEncoder memoization tests - New integration tests for SHOW_TRACK and delta-encoded JSONL - scripts/generate_action_doc.mjs + docs/ACTIONS.md - Python converter handles delta-encoded JSONL - CodeReview.md rewritten as a current-state PR reference Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Member
Author
|
I don't know if this belongs on main or not tbh. Posting as a PR for your review @cmdcolin |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Code Review Reference: jbrowse-plugin-rl-analytics
Purpose
Captures user interactions with a JBrowse 2 session as RL-compatible
(state, action, reward, next_state)tuples and exports them as JSONL or streams them to a webhook. (rewardis a dummy zero field included for compatibility with RL training environments.) Designed to enable training and evaluation of agents that operate JBrowse on behalf of users.The commit history includes some abandoned attempts at a gamified tutorial for Mechanical Turks
(turns out to be much harder than I anticipated to make this polished and smooth).
Architecture
Source layout (
src/)index.tsgetExportManager()/getEpisodeManager()/getActionListener()for tests via a module-levelWeakMap<Plugin, refs>.config.tsreadConfObject:enabled,webhookUrl,webhookBatchSize,webhookFlushIntervalMs,inactivityTimeoutMs,maxEpisodes.ActionLogger/ActionListener.tsaddMiddlewarelistener. Filters sub-actions viaparentActionEvent. Maps MST action names → semanticActionTypeviaACTION_MAP. Extracts per-action metadata inextractMetadata().ActionLogger/ActionBuffer.tsonDebouncedActionwith the merged event.ActionLogger/ActionTypes.tsActionTypeenum +ClassifiedActionshape.RLPipeline/StateEncoder.tsBrowserState. Memoizes within a frame keyed on(bpPerPx, offsetPx, numTracks, firstRegion, lastActionTimestamp). Tracks action counts and unique refNames visited.encode()returns a fixed 21-dim numeric vector.RLPipeline/EpisodeManager.tsSteprecords from prev/nextBrowserState, manages episode lifecycle, inactivity timeout (dynamic check interval = clamp(1s, 30s, timeout/2)), bounded ring buffer of completed episodes viamaxEpisodes.RLPipeline/types.tsBrowserState,Step,Episodetypes.Export/ExportManager.tsgetJSONL()for tests/UI.Export/JSONLExporter.tsnext_observationagainstobservationto reduce file size.Export/WebhookExporter.tsnavigator.sendBeaconon dispose if available.ObserverView/viewModel.tsobservable.array<LogEntry>for O(1) push and stable React keys.ObserverView/ObserverView.tsxuseTheme()for dark/light.requestAnimationFrame-coalesced auto-scroll.Tests (
src/__tests__/)ActionBuffer.test.tsEpisodeManager.test.tsmaxEpisodeseviction, prevState caching.StateEncoder.test.tsencode()vector dims, action count tracking, unique refName tracking, throwing-getter resilience, frame memoization (hit + invalidation).WebhookExporter.test.tssendBeaconon dispose, empty-URL disabled — all via mockedglobal.fetch.Plus
products/jbrowse-web/src/tests/RLAnalytics.test.tsx:end-to-end integration through the real
LinearGenomeView. CoversMST
onActionpropagation, full pipeline → JSONL with enrichedstate, prevState caching across steps,
SHOW_TRACKcapture, anddelta-encoded JSONL output.
Total: 35 tests across 5 files.
Key design decisions
Middleware on session, not rootModel. Action capture is scoped
to a single session; this avoids capturing assembly-loading and
bootstrap actions that occur before the user takes control.
Plugin state in a
WeakMap. No mutable state on the plugininstance itself; refs (
actionListener,episodeManager,exportManager) live in a module-level WeakMap keyed by theplugin object. Accessor methods (
getExportManager()etc.) existfor the integration test.
parentActionEventfilter. Suppresses sub-actions likescrollToinsidezoomTo, so only top-level user-initiatedactions become Steps.
Frame-level state memoization.
StateEncoder.extractStatereturns the same object on repeat calls within a frame to avoid
re-walking MST getters. Invalidated on the next animation frame
or when any view input changes.
Delta-encoded JSONL.
next_observationis stored as the difffrom
observation, computed by shallow-equality on eachBrowserStatefield. The Python converter(
scripts/convert_to_gymnasium.py) reconstructs fullobservations.
isAlive()checks on the observer view ingetObserverView()to handle MST destruction races.Webhook export is fully optional and gated by config. If
webhookUrlis empty the exporter short-circuits and nevertouches
fetch.Generated docs
docs/ACTIONS.mdis auto-generated fromActionListener.tsbyscripts/generate_action_doc.mjs. It lists all 13 semantic actiontypes, the MST action names mapped to each, and the metadata fields
extracted per action. Regenerate with:
Type safety
as anyinindex.ts— usesAbstractSessionModelandAbstractViewModelfrom@jbrowse/core/util, plus a localRootModelWithSessioninterface.eslint-disableinindex.ts.as anycasts in tests are for constructing minimal mockviews/steps and are isolated to fixtures.
Metrics
"files": ["esm"]— excludesscripts/,docs/,dist/, tests@jbrowse/coreworkspace, MUI
^7.3.8, mobx 6.15, mobx-react 9.2)Known follow-ups (non-blocking)
extractMetadatafalls back to storing rawargsfor unknownaction names (
logOtherActionsopt-in). If an unknown actionpasses MST node references, JSON serialization could fail. Safer
to drop unknown args entirely.
BrowserStateschemawould let downstream RL training stay in sync with the plugin.
crypto.randomUUID()requires Node 18+ / jsdom 20+. Fine oncurrent CI; flag if the matrix widens.