Skip to content

Explicit state and event id minification - #6100

Open
benedikt-bartscher wants to merge 163 commits into
reflex-dev:mainfrom
benedikt-bartscher:explicit-event-id-minification
Open

benedikt-bartscher wants to merge 163 commits into
reflex-dev:mainfrom
benedikt-bartscher:explicit-event-id-minification

Conversation

@benedikt-bartscher

@benedikt-bartscher benedikt-bartscher commented Jan 24, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add state and event name minification with CLI management tools.

  • Introduces minify.json config file to map state/event names to short IDs (e.g., myapp.state.AppState → "a")
  • Adds reflex minify CLI command group for managing minification
  • Reduces frontend bundle size and WebSocket payload size by replacing long state/event paths with compact identifiers

CLI Commands

# Initialize minify.json with IDs for all states and events
reflex minify init

# Sync minify.json after code changes
reflex minify sync
reflex minify sync --prune --reassign-deleted

# Validate config against current codebase
reflex minify validate

# List state tree with minified names
reflex minify list
reflex minify list --json

# Lookup a minified path
reflex minify lookup "a.bU"

based on #6098

@codspeed

codspeed Bot commented Jan 24, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 5.62%

2 warnings

⚠️ 15 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
✅ 146 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ test_hydration_snapshot[20] 959.2 µs 904.9 µs +5.99%
⚡ test_hydration_snapshot[200] 8.8 ms 8.4 ms +5.25%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing benedikt-bartscher:explicit-event-id-minification (1b8deac) with main (05329d0)

Open in CodSpeed

@benedikt-bartscher benedikt-bartscher changed the title Explicit event id minification Explicit state and event id minification Jan 25, 2026
Comment thread reflex/reflex.py Outdated
@benedikt-bartscher

benedikt-bartscher commented Jan 28, 2026 •

Copy link
Copy Markdown
Contributor Author

TODO: best-effort-mode

  • walk trough the state id registry after the app module get's imported
  • filter out all states with explicit state id
  • sort the remaining states without state id by full name
  • assign state ids - start with the highest user defined state id - users might have fragmentation by choice

a similar thing can be implemented for event handler id's

edit: not that easy, as state names are baked into to many things (events, vars etc). we would need to defer init_subclass and possibly parts of @rx.var and @rx.event

edit2: i have found a better approach with a minify.json which makes this obsolete

@benedikt-bartscher

benedikt-bartscher commented Jan 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Open for discussion: Should reflex reserve the first 5/10 state id's for internal states? Current reflex uses 0-3, but that might change in the future.

edit: with sibling uniqueness we would just need to reserve the state id's on the first rx.State subclass level.

edit2: i have found a better approach with a minify.json which makes this obsolete

@benedikt-bartscher

benedikt-bartscher commented Jan 28, 2026 •

Copy link
Copy Markdown
Contributor Author

TODO: think about sibling uniqueness for states. currently using global uniqueness

edit: adjusted in 6b4c5fa

@benedikt-bartscher
benedikt-bartscher force-pushed the explicit-event-id-minification branch from 35ad1c6 to 1647c1e Compare February 1, 2026 10:25
@benedikt-bartscher

Copy link
Copy Markdown
Contributor Author

Idea: store ids in a minify.json file

  • allows overwriting state ids for upstream dependencies and reflex core
  • one centralized place for the ids
  • keep track of reserved numbers
  • better automatic instrumentation - state and event ids can all be managed with cli - without manual code changes
  • can be loaded once on startup and is frozen - probably faster
  • no best effort mode needed

When a parent and child state have the same minified name, substate
resolution can fail because the leading segment is stripped incorrectly. This
change adds a flag to skip stripping only on the initial recursive call, ensuring
correct resolution even in name collision scenarios.
Comment thread reflex/reflex.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 11 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread reflex/app.py
Comment thread news/6100.feature.md Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 5 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tests/units/test_app.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tests/units/test_event.py

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 10 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread tests/units/test_minify.py
@masenf masenf added the perf Performance-improving changes label Sep 29, 2026
…inification

- state.js: keep the main_state_name prefix check in isStateful, with main's
  non-string event name guard and nested event normalization.
- vars/base.py, tests: keep both sides' imports and new tests.
- docs/api-reference/cli.md: keep compile and minify, drop the deleted
  component command.
- test_context_js_template.py: context_template no longer takes state_name.
@masenf masenf added the documentation Improvements or additions to documentation label Oct 6, 2026
…inification

Ported upstream changes into the resolver-aware code:

- Context template: hydrate_and_load (reflex-dev#7064) gets a resolved wire name in
  InternalEventNames (replacing hydrate), alongside upstream's state schema
  and initial state hashes. state.js reads app.hydrate_and_load and
  app.update_vars_internal instead of hardcoded default names, and the
  handshake query (token + scheme) is built for the warm-up socket too.
- State reads use upstream's useStateContext (reflex-dev#7441), keyed by the
  state's resolved full name, into the prefixed local.
- Upstream made unknown substates non-fatal (reflex-dev#7435) and dropped
  backend_state_mismatch; the fatal minification scheme mismatch keeps its
  own flag (backend_scheme_mismatch) gating events, updates and reconnects.
- The frontend state schema hash (reflex-dev#7553) hashes resolver-independent names;
  the scheme digest already covers the wire names.
- The processor's hydrate/hydrate_and_load/on_load_internal lookups cache
  handlers, not names, so they follow a resolver installed later.
- BaseStateToken._state_key keys by the resolver-independent name.
- get_substate keeps upstream's loop but only lets the first segment name
  the state itself; get_name/get_full_name stay function-wide caches so a
  resolver install clears every class, and also clears _get_substate_path.
- on_connect sets up per-SID state before upstream's new first await.
- pyStrAt avoids Array.prototype.at, which the state.js tests forbid.
benedikt-bartscher added a commit to benedikt-bartscher/reflex that referenced this pull request Oct 17, 2026
…into late-bound-wire-names

Brings in reflex-dev#6100's merge of upstream/main. Its conflicts here are the
reflex-dev#6100 spellings of code this branch already resolved (InternalEventNames
vs InternalNames, the hook's state name, tests of state/event-only
minification), so this branch's versions are kept and the tree is
unchanged.
Comment thread reflex/minify.py
Comment thread reflex/state.py
Comment on lines +3079 to +3083
state_cls_name = f"{cls.__name__}__{_state_key}"
# Report the key rather than letting the generated class name reach
# the sibling-shadowing guard, whose message names neither.
existing = getattr(reflex.istate.dynamic, state_cls_name, None)
if existing is not None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Different component keys collide

Distinct valid _state_key values can produce the same state name. For example, cart_item and cartItem pass this exact-name check, but default_state_name converts both generated class names to snake case. With minification off—the default—the second create() raises the sibling-shadowing error and stops app compilation.

Check uniqueness using the final state name, or encode keys without losing their spelling.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread reflex/minify.py
A handler with no minify.json entry keeps its Python name, which can be
the id another handler of its state was given, e.g. when it is added
before 'reflex minify sync' runs. Registering it overwrote the other,
so an event for one ran the other. Registration now raises when two
handlers of a state resolve to the same name.

refresh_keys rebuilt the substate sets from base_states, which still
holds a state a module reload removed, so installing a resolver after
such a reload put the removed state back into the tree. It now re-keys
the substate sets as they are.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/reflex-base/src/reflex_base/registry.py">

<violation number="1" location="packages/reflex-base/src/reflex_base/registry.py:394">
P2: This check compares only the leaf of each function’s `__qualname__`, so distinct same-named handlers from different modules or nested scopes bypass it and the later handler overwrites the first. Compare the module plus full qualname before replacing the registry entry.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

existing = self.event_handlers.get(full_name)
# An entry of another class is replaced, as on a module reload.
if existing is not None and existing.handler.state is handler.state:
existing_name = existing.handler.fn.__qualname__.rpartition(".")[2]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This check compares only the leaf of each function’s __qualname__, so distinct same-named handlers from different modules or nested scopes bypass it and the later handler overwrites the first. Compare the module plus full qualname before replacing the registry entry.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At packages/reflex-base/src/reflex_base/registry.py, line 394:

<comment>This check compares only the leaf of each function’s `__qualname__`, so distinct same-named handlers from different modules or nested scopes bypass it and the later handler overwrites the first. Compare the module plus full qualname before replacing the registry entry.</comment>

<file context>
@@ -380,10 +380,25 @@ def _register_event_handler(
+        existing = self.event_handlers.get(full_name)
+        # An entry of another class is replaced, as on a module reload.
+        if existing is not None and existing.handler.state is handler.state:
+            existing_name = existing.handler.fn.__qualname__.rpartition(".")[2]
+            name = handler.fn.__qualname__.rpartition(".")[2]
+            if existing_name != name:
</file context>

Comment thread reflex/reflex.py

# Build lookup: full_path -> minified_id (None if no entry).
path_to_id: dict[str, str | None] = {}
for s in collect_all_states(State):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Inspection misses separate state roots

minify list and minify lookup only walk the framework State tree, while init, sync, and validate cover every registered root. An app using an independent BaseState root, such as the existing App(_state=Base) test, therefore gets IDs that these inspection commands cannot show or resolve.

Use the same root inventory across the commands so developers can inspect all IDs the CLI assigns.

sync compared decoded ids, so a hand-edited "aa" counted as a collision
with "a" and got rehomed; validate and the wire compare the strings.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 2 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="reflex/minify.py">

<violation number="1" location="reflex/minify.py:736">
P2: This conversion reintroduces numeric aliasing while allocating new state IDs: a valid sibling ID `"aa"` reserves the unrelated wire ID `"a"`. Preserve raw IDs through the allocation check, or ignore noncanonical aliases when building numeric reservations, so sync can assign the free `"a"` slot.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

View guided diff | Re-trigger cubic

Comment thread reflex/minify.py
siblings, parent = _state_id_scope(states, parent_key, set(state_paths))
return _assign_next_ids(
state_paths,
{minified_name_to_int(i) for i in siblings},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This conversion reintroduces numeric aliasing while allocating new state IDs: a valid sibling ID "aa" reserves the unrelated wire ID "a". Preserve raw IDs through the allocation check, or ignore noncanonical aliases when building numeric reservations, so sync can assign the free "a" slot.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At reflex/minify.py, line 736:

<comment>This conversion reintroduces numeric aliasing while allocating new state IDs: a valid sibling ID `"aa"` reserves the unrelated wire ID `"a"`. Preserve raw IDs through the allocation check, or ignore noncanonical aliases when building numeric reservations, so sync can assign the free `"a"` slot.</comment>

<file context>
@@ -734,7 +732,11 @@ def _assign_state_ids(
     return _assign_next_ids(
-        state_paths, siblings, reassign_deleted, for_states=True, skip=parent
+        state_paths,
+        {minified_name_to_int(i) for i in siblings},
+        reassign_deleted,
+        for_states=True,
</file context>

…inification

The reconnect back-off from upstream counts a disconnect before the connect
handler runs as a failed attempt. A fatal scheme mismatch disconnects in
exactly that window, since socket.io replays it before "connect", so the
retry appended a non-fatal error and the connect handler then discarded the
out-of-date notice. A client-initiated disconnect no longer counts as a
failed attempt; the state.js runtime test mock now replays buffered events
and reports client disconnects like socket.io-client to cover it.

The hydration toast test now locates the error threshold through
has_too_many_connection_errors, which this branch extends with the fatal
check.
Comment thread reflex/reflex.py
Comment on lines +1583 to +1585
handlers = [name for name in holders if name in current.event_handlers] or (
[part] if part in current.event_handlers else []
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Lookup hides a valid handler

minify lookup hides a valid reading when a segment is both one live handler's Python name and another handler's ID. For example, with foo -> a and bar -> foo, looking up <state>.foo returns only bar. With event minification disabled, that same wire name calls foo.

This can send a developer debugging an event to the wrong handler. Since inspection accepts both modes without using the current flags, return both readings and explain the ambiguity instead of presenting one as certain.

@masenf masenf added the roadmap https://github.com/reflex-dev/reflex/issues/2727 label Oct 23, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation perf Performance-improving changes roadmap https://github.com/reflex-dev/reflex/issues/2727

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants