This project is uv based, it is a reflex wrapper for mui x-data-grid UI component
-
State var mixin classes MUST use
rx.Statewithmixin=True: Reflex provides a built-in mixin mechanism. Declare your mixin asclass MyMixin(rx.State, mixin=True)so that the vars are not registered on the mixin itself but are injected into each concrete subclass by Reflex's metaclass. Each subclass then gets its own independent set of reactive vars. Subclasses must also inherit fromrx.State(or another non-mixin state class) so the mixin flag is cleared and vars become reactive:# CORRECT — mixin=True, each child gets independent vars class MyMixin(rx.State, mixin=True): my_count: int = 0 class GridA(MyMixin, rx.State): ... class GridB(MyMixin, rx.State): ... # GridA.my_count and GridB.my_count are INDEPENDENT rx.Var objects # WRONG — without mixin=True, all children share the SAME vars class MyMixin(rx.State): my_count: int = 0 class GridA(MyMixin): ... class GridB(MyMixin): ... # GridA.my_count and GridB.my_count point to the SAME reactive var # ALSO WRONG — plain Python mixin without rx.State, vars stay as raw types class MyMixin: my_count: int = 0 class AppState(MyMixin, rx.State): ... # AppState.my_count is just int(0), .to(str) crashes
-
No keyword-only arguments in mixin event handler methods: Reflex's
BaseState._copy_fncopies event handler methods from mixin classes to concrete subclasses usingFunctionType(..., argdefs=fn.__defaults__). This copies__defaults__(positional defaults) but not__kwdefaults__(keyword-only defaults). If a mixin method uses*,to define keyword-only arguments with defaults, those defaults are silently lost when the method is copied to the child class, causingTypeError: missing required keyword-only arguments. Always use regular positional arguments with defaults instead of keyword-only arguments in mixin methods that will be used as event handlers. -
pagination=Falsefor scrollable grids: TheWrappedDataGriddefaults topagination=Trueandauto_page_size=True. You MUST explicitly passpagination=Falseandhide_footer=Trueto get a continuously scrollable grid. Without this, rows are silently paginated and only the first page is visible. -
Column definitions stored in state vars MUST be JSON-serializable: Storing
ColumnDefobjects orrx.Var-based renderers in state vars (e.g.prs_columns) fails because Reflex cannot serialize them. Usecell_renderer_type+cell_renderer_config(plain strings/dicts) for columns that go through state; keeprx.Var-based renderers only for static, compile-time column definitions. -
New public APIs must be exported in
__init__.py: When adding new classes (e.g.BadgeCellRenderer,ProgressBarCellRenderer), they must be added toreflex_mui_datagrid/__init__.pyso they can be imported by users. Missing exports causeImportError.
- Truly lazy behavior:
set_lazyframeand all grid operations MUST be memory-safe. NEVER collect the entire LazyFrame. Every operation (row count, value options inference, page slicing) must use lazy queries that Polars can push down into the scan. If a full-dataset scan is unavoidable (e.g. counting rows on a format without metadata), it must be a streaming count — never materialise all rows into a DataFrame. - Hybrid value options strategy: Value options for filter dropdowns (the "is" dropdown with singleSelect) use a two-tier approach:
- Small datasets (row count <=
eager_value_options_row_limit, default 50k): value options are computed eagerly atset_lazyframeinit for all string-like columns. Each column is scanned independently with projection pushdown — the full dataset is never materialised. Columns that qualify are markedsingleSelectimmediately so the "is" dropdown is available from the start. - Large datasets: value options are deferred and computed on demand when the user clicks the filter icon on a column header. The JS
_AlwaysVisibleFilterIconButtondispatches a_requestValueOptionscustom event which theUnlimitedDataGridwrapper forwards tohandle_lf_grid_request_value_options(field). This upgrades the column tosingleSelectwithvalueOptionsand pushes updated column defs to the frontend. - The
_ensure_value_options_for_filterfallback still runs on filter apply as a safety net for columns not yet computed.
- Small datasets (row count <=
- Always-visible filter buttons in column headers: Every column header must have a clickable filter icon/button on the right side of the header text. Clicking it opens the filter panel for that column. These buttons must always be visible (not hidden behind a hover or menu). This is a core UX requirement — users must see at a glance that columns are filterable and be able to filter with one click.
- Memory safety: The grid must never hold more rows in memory (in
lf_grid_rows) than what has been scrolled to. Each scroll chunk appends only the new slice. Filter/sort resets must clear accumulated rows and start fresh from offset 0.
- Apply button pattern: When
filterMode="server", the grid uses a custom_FilterPanelWithApplyslot that adds Apply/Reset buttons below the standardGridFilterPanel. TheUnlimitedDataGridwrapper interceptsonFilterModelChange— user edits update a local React state only (no Python call), and the real Python callback is only invoked when Apply is clicked (or Enter is pressed). This prevents expensive server queries on every keystroke. - Local filter model: In server filter mode, the controlled
filterModelprop from Python is replaced with a local React state (localFilterModel). This local state syncs from the Python prop only when the prop genuinely changes (detected viaJSON.stringifycomparison). This prevents MUI from resetting the user's in-progress edits when unrelated state vars (likelf_grid_loading) cause re-renders. - Custom event dispatch: The Apply button dispatches a
_applyFilterCustomEvent (withbubbles: true) on the MUI grid root element. TheUnlimitedDataGridwrapper listens for this event on its container div and forwards the filter model to the real PythononFilterModelChangecallback. - Operator preservation in merge_filter_model: When MUI sends a filter item with a changed operator but no value (user changed the operator dropdown),
merge_filter_modelupdates the operator on the existing accumulated filter item instead of ignoring the change. This prevents the operator from "snapping back" to the previous value. - Filter panel must close on Apply/Reset: When the user clicks Apply or Reset in the filter panel, the panel must close automatically. Use
apiRef.current.hideFilterPanel()after dispatching the filter event. - Default operator for singleSelect columns: For enum-like (
singleSelect) columns (e.g. chromosome), default the filter operator to"is"unless the user changes it. Avoid leaving the operator empty. - Case-insensitive field name resolution (CRITICAL): Reflex's serialisation layer may convert column names to different cases (e.g.
DP→dp,MIN_DP→min_dp). All filter/sort/value-options code MUST use_resolve_field_name(raw_field, schema)to resolve field names case-insensitively against the schema before using them in Polars expressions. Never doif field not in schemadirectly — always resolve first. The canonical column name from the schema must be used inpl.col(field)calls to avoid DataFusion predicate pushdown errors.
setPanelsexists in the Community edition virtualizer: ThesetPanelsAPI lives in@mui/x-virtualizer(the shared virtualizer package used by all editions), not gated behind a Pro license. It is a ReactuseStatesetter acceptingMap<GridRowId, ReactNode>. After rendering each row, the virtualizer checkspanels.get(id)and appends the panel element. Access it viaapiRef.current.virtualizer.api.getters.setPanels. This is how MUI Pro implements detail panels — the Pro package only addsGridDetailPanelswhich callssetPanels.rowSelectionModelrequires Set conversion: MUI DataGrid v8 expectsrowSelectionModel.idsas aSet<GridRowId>, but Python/JSON sends arrays. The JS wrapper must convert arrays toSetbefore passing to MUI.row_id_fieldwith spaces needs bracket notation: Whenrow_id_fieldcontains spaces (e.g."PGS ID"), userow["PGS ID"]in JS instead of dot notation to avoid invalid JavaScript.
- CSS-only fixes for column header icon alignment DO NOT WORK: Adding
flex: 1 1 autoto.MuiDataGrid-columnHeaderTitleContainerContentvia MUIsxprop, global<style>injection with!important, or generic CSS selectors (:first-child:not(...),:not(...)) all fail to push filter/sort icons to the right edge whenrenderHeaderproduces two-line headers (name + description). MUI v8's styled-component output overridessxregardless of specificity. The working approach is arefcallback on therenderHeaderdiv that imperatively setsflex: 1 1 autoon the parentcolumnHeaderTitleContainerContentDOM element (see_forceParentFlex). - Synthetic detail rows injected into the rows array DO NOT WORK: Injecting
__is_detail_row__synthetic rows into the grid'srowsarray and overridinggetRowHeight/getRowClassName/renderCellto render detail content causes severe performance issues and visual overlay glitches. MUI's virtualizer fights the injected rows. The correct approach is to use thesetPanelsAPI from@mui/x-virtualizer(see MUI X Internals above).
- Avoid nested try-catch: try catch often just hide errors, put them only when errors is what we consider unavoidable in the use-case.
- Type hints: Mandatory for all Python code.
- Pathlib: Always use for all file paths.
- No relative imports: Always use absolute imports.
- No placeholders: Never use
/my/custom/path/in code. - No legacy support: Refactor aggressively; do not keep old API functions.
- Publishing to PyPI: The PyPI publish token is stored in
.envasPYPI_TOKEN. Source the file before publishing:set -a && source .env && set +a && uv publish --token "$PYPI_TOKEN" dist/PACKAGE_FILES. Note that.envvalues are quoted — you mustsourcethe file (not just export the raw string) so the shell strips the quotes. - Dependency Management: Use
uv syncanduv add. NEVER useuv pip install. - Versions: Do not hardcode versions in
__init__.py; usepyproject.toml. - Avoid all: Avoid
__init__.pywith__all__as it confuses where things are located. - Pay attention to terminal warnings: Always check terminal output for warnings, especially deprecation ones. AI knowledge of APIs can be outdated; these warnings are critical hints to update code to the current version.
- Typer CLI: Mandatory for all CLI tools.
- Pydantic 2: Mandatory for data classes.
- Self-Correction: If you make an API mistake that leads to a system error (e.g. a crash or a major logic failure due to outdated knowledge), you MUST update this file (
AGENTS.md) with the correct API usage or pattern. This ensures future agents don't repeat the same mistake. - Docs: Put all new markdown files (except README/AGENTS) in
docs/.
- Real data + ground truth: Use actual source data, auto-download if needed, and compute expected values at runtime.
- Deterministic coverage: Use fixed seeds or explicit filters; include representative and edge cases.
- Meaningful assertions: Prefer relationships and aggregates over existence-only checks.
- Verbosity: Run
pytest -vvv.
- Counts & aggregates: Row counts, sums/min/max/means, distinct counts, and distributions.
- Joins: Pre/post counts, key coverage, cardinality expectations, nulls introduced by outer joins, and a few spot-checks.
- Transformations: Round-trip survival, subset/superset semantics, value mapping, key preservation.
- Data quality: Format/range checks, outliers, malformed entries, duplicates, referential integrity.
- Runtime ground truth: Query source data at test time instead of hardcoding expectations.
- Seeded sampling: Validate random records with a fixed seed, not just known examples.
- Negative & boundary tests: Ensure invalid inputs fail; probe min/max, empty, unicode.
- Derived assertions: Test relationships (e.g., input vs output counts), not magic numbers.
- Allow expected failures: Use
pytest.mark.xfailfor known data quality issues with a clear reason.
- Parameterize over duplicate: If testing the same logic on multiple outputs, use
@pytest.mark.parametrizeinstead of copy-pasting tests. - Set equality over counts: Prefer
assert set_a == set_boverassert len(set_a) == 270- set comparison catches both missing and extra values. - Delete redundant tests: If test A (e.g., set equality) fully covers test B (e.g., count check), keep only test A.
- Domain constants are OK: Hardcoding expected enum values or well-known constants from specs is fine; hardcoding row counts or unique counts derived from data inspection is not.
When claiming a test "would have caught" a bug, demonstrate it:
- Isolate the buggy logic in a test or script
- Run it and show failure against correct expectations
- Then show the fix passes the same test
Never claim "tests would have caught this" without running the buggy code against the test.
- Testing only "happy path" with trivial data
- Hardcoding expected values that drift from source (use derived ground truth)
- Mocking data transformations instead of running real pipelines
- Ignoring edge cases (nulls, empty strings, boundary values, unicode, malformed data)
- Claiming tests "would catch bugs" without demonstrating failure on buggy code
Meaningless Tests to Avoid (common AI-generated anti-patterns):
- Never assume the user didn't rebuild: When a fix doesn't appear to work in the browser, do not suggest the user forgot to rebuild or update the code. Investigate the actual problem instead.
- Trust user-provided DOM structure: When the user provides DOM paths or structure from their browser, prefer that information over automated browser inspection which can be unreliable.
- Precise scope when removing code: When asked to remove a specific feature, remove only what was asked. Do not remove adjacent related functionality unless explicitly requested.
- Extend the library rather than working around it: If a feature exists in the TypeScript MUI DataGrid but not in the Reflex wrapper, extend the library to support it rather than building hacky workarounds.
- Provide demos for new features: New features should be demonstrated in the existing demo app (e.g. genetic variants/PRS tab) rather than only documented.
- Never revert always-visible filter icons to MUI default: The custom always-visible filter icon buttons in column headers are a core UX requirement. Never replace them with MUI's default hover-only behavior.
- Document failed hypotheses immediately: When an approach fails, document it in the Failed Hypotheses section of AGENTS.md to prevent future agents from retrying the same approach.
- Demo app: Run with
uv syncthenuv run demofrom the project root. The demo usesworkspace = trueinpyproject.tomlto depend on the local repo version. - Filter JSON
idfield: Theidin MUI filter items is MUI-internal and should be stripped from filter JSON output sent to the user. - Dynamic scrolling monkey-patching: Dynamic scrolling with
pagination=Falseoriginally used monkey-patching of MUI's pagination logic; this broke when Reflex switched from Next.js to Vite/ESM (CommonJSrequire()no longer available).