Skip to content

Conversation

@DZakh
Copy link
Member

@DZakh DZakh commented Dec 19, 2025

You can see the gif here https://github.com/enviodev/hyperindex/tree/dz/tui-love

Should probably record dev console, since it looks cooler. But I'll do it after finishing all performance optimizations.

Summary by CodeRabbit

  • New Features

    • Added a terminal (TUI) for visualizing chain syncs: per-chain progress, buffered progress bars, total events, ETA, and info panels.
  • Refactor

    • Simplified state/refresh flow for the TUI for more direct and predictable updates.
    • Consolidated progress data shapes and formatting utilities used by the UI.
  • Chores

    • Added terminal UI-related dependencies to support rendering and resize handling.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 19, 2025

Walkthrough

Adds a React-based terminal UI (Ink) for envio, new TUI modules/types/bindings, updates ReScript build configs and package manifests, removes the stateUpdatedHook from GlobalStateManager, and refactors Index.res to use a direct getState callback for driving the TUI.

Changes

Cohort / File(s) Summary
Envio package manifests
codegenerator/cli/npm/envio/package.json, codegenerator/cli/npm/envio/package.json.tmpl
Added dependencies: @rescript/react (0.14.0), ink (6.5.1), ink-big-text (2.0.0), ink-spinner (5.0.0).
Rescript build configs
codegenerator/cli/npm/envio/rescript.json, codegenerator/cli/templates/static/codegen/rescript.json
Added/adjusted jsx config and bs-dependencies (added @rescript/react in envio config; codegen config reorganized and React dependency removed in favor of envio).
Template/test manifests
codegenerator/cli/templates/dynamic/codegen/package.json.hbs, codegenerator/cli/templates/dynamic/init_templates/shared/package.json.hbs, scenarios/test_codegen/package.json
Removed @rescript/react (and Ink-related deps in generated templates/tests) from template and scenario manifests.
GlobalStateManager API
codegenerator/cli/npm/envio/src/GlobalStateManager.res, codegenerator/cli/npm/envio/src/GlobalStateManager.resi
Removed stateUpdatedHook from the public make signature and internal invocation; make now takes only initial state and optional onError.
TUI entry & components
codegenerator/cli/npm/envio/src/tui/Tui.res
Added App, ChainLine, TotalEventsProcessed components and start(~getState) entrypoint; polling-based UI that derives chain progress and renders terminal UI.
Ink bindings & hooks
codegenerator/cli/npm/envio/src/tui/bindings/Ink.res
Converted Newline to local element, added Hooks externals and useStdoutColumns hook, and added Ink.Table binding.
TUI data/types
codegenerator/cli/npm/envio/src/tui/components/TuiData.res
New types: syncing, synced, progress, chain; utilities getNumberOfEventsProccessed, minOfOption, formatLocaleString and JS number bindings.
SyncETA type updates
codegenerator/cli/npm/envio/src/tui/components/SyncETA.res
Updated function signatures to accept TuiData.chain (replacing ChainData.chainData), minor JSX formatting tweaks.
Index entrypoint refactor
codegenerator/cli/templates/static/codegen/src/Index.res
Removed makeAppState; construct chains directly from chainFetchers; create gsManager without stateUpdatedHook and start TUI via Tui.start(~getState).

Sequence Diagram(s)

sequenceDiagram
    participant Index as Index.res
    participant GSM as GlobalStateManager
    participant TUI as Tui.App (React)
    participant Hook as useEffect (poll)

    Index->>GSM: create gsManager(globalState, ~onError)
    Index->>TUI: Tui.start(~getState)
    activate TUI
    TUI->>Hook: start polling (every 500ms)
    loop poll
        Hook->>TUI: trigger re-render
        TUI->>Index: getState()
        Index->>GSM: read current state
        GSM-->>Index: return GlobalState
        Index-->>TUI: return chains array
        TUI->>TUI: derive TuiData.progress per chain
        TUI->>TUI: render ChainLine/TotalEventsProcessed/UI
    end
    deactivate TUI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • GlobalStateManager.make signature removal — verify all callers and initialization paths.
    • Index.res refactor — correctness of derived per-chain fields (endBlock, first/latest event blocks, knownHeight, eventsProcessed).
    • Tui.res — complex rendering logic, terminal sizing, polling effects, and correctness of progress derivation.
    • Ink bindings interop (externals) and useStdoutColumns correctness.
    • Type migrations (SyncETA and other modules) to ensure all usages updated.

Possibly related PRs

Suggested reviewers

  • JonoPrest
  • JasoonS

Poem

🐰 I hopped through bytes and terminal light,

Ink-strokes paint chains in the night,
No hook to call, just getState's song,
Rerender hums and lines grow long,
A rabbit cheers — the TUI's strong! 🎨🫧

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'TUI love' is vague and does not clearly convey the substantive changes in the pull request, which involve adding a new React-based TUI layer, restructuring dependencies, and refactoring state management. Consider using a more descriptive title that captures the main objective, such as 'Add React-based TUI component system for chain visualizer' or 'Implement TUI layer with ink-based UI components'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dz/tui-love

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
codegenerator/cli/npm/envio/src/tui/Tui.res (1)

261-266: Consider whether the returned rerender function is needed.

The start function returns a rerender callback, but in Index.res (line 225), it's captured as _rerender (unused). If this is intentional for future use, consider adding a comment. Otherwise, simplify to return unit.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10a58de and 03a5b0d.

⛔ Files ignored due to path filters (2)
  • codegenerator/cli/npm/envio/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • scenarios/test_codegen/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • codegenerator/cli/npm/envio/src/GlobalStateManager.res
  • codegenerator/cli/npm/envio/src/GlobalStateManager.resi
  • codegenerator/cli/npm/envio/src/tui/Tui.res
  • codegenerator/cli/npm/envio/src/tui/bindings/Ink.res
  • codegenerator/cli/npm/envio/src/tui/components/SyncETA.res
  • codegenerator/cli/npm/envio/src/tui/components/TuiData.res
  • codegenerator/cli/templates/static/codegen/src/Index.res
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Always use ReScript 11 documentation
Never suggest ReasonML syntax
Never use [| item |] to create an array. Use [ item ] instead
Must always use = for setting value to a field. Use := only for ref values created using ref function
Use records when working with structured data, and objects to conveniently pass payload data between functions
Never use %raw to access object fields if you know the type

Files:

  • codegenerator/cli/npm/envio/src/GlobalStateManager.res
  • codegenerator/cli/npm/envio/src/GlobalStateManager.resi
  • codegenerator/cli/npm/envio/src/tui/components/SyncETA.res
  • codegenerator/cli/npm/envio/src/tui/components/TuiData.res
  • codegenerator/cli/npm/envio/src/tui/bindings/Ink.res
  • codegenerator/cli/npm/envio/src/tui/Tui.res
  • codegenerator/cli/templates/static/codegen/src/Index.res
codegenerator/cli/templates/static/**/*.res

📄 CodeRabbit inference engine (.cursor/rules/navigation.mdc)

Static ReScript files must be placed in codegenerator/cli/templates/static/

Files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
🧠 Learnings (9)
📚 Learning: 2025-12-04T12:13:23.246Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-12-04T12:13:23.246Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/{src/handlers,src/**}/*.ts : ALL external calls (RPC calls, API calls, blockchain state fetching) MUST use the Effect API with `context.effect()` to enable preload optimization and parallelization. Create Effect API functions in `src/effects/` folder for better organization

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
📚 Learning: 2025-12-04T12:12:49.547Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/hyperindex.mdc:0-0
Timestamp: 2025-12-04T12:12:49.547Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/*.{ts,js} : Import and use the Effect API (`S`, `createEffect`) from 'envio' package for external calls in HyperIndex handlers

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
📚 Learning: 2025-12-04T12:13:23.246Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-12-04T12:13:23.246Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/*.ts : For Step 6 - Final Migration Verification: Go through each handler and helper function systematically, comparing the logic line-by-line to the original subgraph implementation. Iterate multiple times until logic is completely correct, as first pass often misses subtle differences

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
📚 Learning: 2025-12-04T12:12:49.547Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/hyperindex.mdc:0-0
Timestamp: 2025-12-04T12:12:49.547Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/*.{ts,js} : In Envio HyperIndex, use `entity_id` fields (e.g., `token_id: string`) instead of direct object references for entity relationships

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
📚 Learning: 2025-12-04T12:13:23.246Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-12-04T12:13:23.246Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/src/utils/**/*.ts : When implementing helper functions with no entity or handler dependencies, implement the COMPLETE business logic from the original subgraph immediately, not just placeholder TODO comments

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
📚 Learning: 2025-12-04T12:13:23.246Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-12-04T12:13:23.246Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/src/**/*.ts : For Step 3 - Refactor file structure to mirror the original subgraph EXACTLY using the same filenames, create separate handler files by contract (e.g., contract1.ts, contract2.ts) instead of a single EventHandlers.ts file, and move handlers from EventHandlers.ts to contract-specific files

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
📚 Learning: 2025-12-04T12:13:23.246Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-12-04T12:13:23.246Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/src/effects/**/*.ts : When making external RPC calls, use viem with createPublicClient and batching for efficiency. Define Effect functions with proper schemas using the S module from 'envio': S.object(), S.string, S.number, etc.

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
📚 Learning: 2025-12-04T12:12:49.547Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/hyperindex.mdc:0-0
Timestamp: 2025-12-04T12:12:49.547Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/*.{ts,js} : When updating existing entities in HyperIndex handlers, always use the spread operator for immutability since returned objects are read-only

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
📚 Learning: 2025-12-04T12:13:23.246Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-12-04T12:13:23.246Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/EventHandlers.ts : For Step 1 - Clear all boilerplate code from EventHandlers.ts and replace with empty handlers containing TODO comments referencing the original subgraph implementation

Applied to files:

  • codegenerator/cli/templates/static/codegen/src/Index.res
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (17)
codegenerator/cli/npm/envio/src/tui/bindings/Ink.res (2)

219-234: LGTM! Well-implemented custom hook.

The useStdoutColumns hook correctly subscribes to stdout resize events, updates state accordingly, and properly cleans up the event listener. The external bindings for getColumns, on, and off follow the correct ReScript patterns.


373-376: LGTM! Clean table component binding.

The Table module binding is straightforward and follows the established patterns in this file. The type signatures for head and rows are appropriate for tabular data.

codegenerator/cli/npm/envio/src/GlobalStateManager.resi (1)

3-3: LGTM!

The simplified make signature removing stateUpdatedHook is a clean API improvement. The new TUI polling mechanism via getState is a more straightforward approach than hook-based notifications.

codegenerator/cli/npm/envio/src/tui/components/TuiData.res (1)

1-12: LGTM!

The type definitions are well-structured. The record spread syntax for synced extending syncing is clean ReScript 11 usage. The external bindings for number formatting are correctly defined.

Also applies to: 20-46

codegenerator/cli/npm/envio/src/tui/components/SyncETA.res (5)

4-11: LGTM!

The type signature update to array<TuiData.chain> and the reduction logic are correct.


13-26: LGTM!

The remaining blocks calculation correctly handles the endBlock option and all progress variants.


28-56: LGTM!

Type signature updates to array<TuiData.chain> are consistent. The timestamp and block processing calculations handle all progress variants correctly.


58-95: LGTM!

The useShouldDisplayEta hook correctly tracks whether enough batches have been fetched to display a meaningful ETA. The effect returns None appropriately as no cleanup is needed.


97-193: LGTM!

The ETA calculation logic is sound, and the component structure cleanly separates the syncing, synced, and calculating states. The interval-based timer with cleanup is properly implemented.

codegenerator/cli/npm/envio/src/tui/Tui.res (4)

22-83: LGTM!

The ChainLine rendering logic handles both the loading state and full progress display well. The adaptive layout based on fitsSameLine is a nice touch for terminal width considerations.


98-115: LGTM!

The 500ms polling interval is reasonable for TUI updates. The cleanup function properly clears the interval. The getState dependency is stable since it's passed from the parent.


117-197: LGTM!

The chain data transformation logic is comprehensive. The handling of edge cases (e.g., no events in block range resulting in firstEventBlockNumber = None) is well documented in the comment at lines 131-134.


211-258: LGTM!

The render tree is well-structured with appropriate components for header, chain lines, totals, sync status, and configuration display.

codegenerator/cli/npm/envio/src/GlobalStateManager.res (2)

14-25: LGTM!

The simplified record type and make function are cleaner. Removing stateUpdatedHook in favor of the polling-based TUI approach is a reasonable simplification.


27-65: LGTM!

The dispatch logic remains unchanged and continues to handle state updates, task scheduling, and error handling correctly.

codegenerator/cli/templates/static/codegen/src/Index.res (2)

165-196: LGTM!

The chain data construction is cleaner, computing values directly from cf objects. The logic correctly handles:

  • latestFetchedBlockNumber with a floor of 0
  • knownHeight based on whether endblock has been processed
  • latestProcessedBlock returning None when -1

223-228: LGTM!

The initialization flow is clean. The GlobalStateManager.make call without stateUpdatedHook aligns with the new TUI polling approach. The getState callback correctly provides access to the manager's state.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
codegenerator/cli/npm/envio/src/tui/Tui.res (1)

219-236: Using array index as React key.

Using i->Int.toString as the key works if the chain list order is stable and chains aren't dynamically added/removed during rendering. If chain ordering can change, consider using a stable identifier like chainData.chainId instead.

🔎 Proposed change
     {chains
-    ->Array.mapWithIndex((i, chainData) => {
+    ->Array.map(chainData => {
         <ChainLine
-          key={i->Int.toString}
+          key={chainData.chainId}
           chainId={chainData.chainId}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03a5b0d and 587e0dd.

⛔ Files ignored due to path filters (1)
  • sync.gif is excluded by !**/*.gif
📒 Files selected for processing (1)
  • codegenerator/cli/npm/envio/src/tui/Tui.res
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{res,resi}

📄 CodeRabbit inference engine (.cursor/rules/rescript.mdc)

**/*.{res,resi}: Always use ReScript 11 documentation
Never suggest ReasonML syntax
Never use [| item |] to create an array. Use [ item ] instead
Must always use = for setting value to a field. Use := only for ref values created using ref function
Use records when working with structured data, and objects to conveniently pass payload data between functions
Never use %raw to access object fields if you know the type

Files:

  • codegenerator/cli/npm/envio/src/tui/Tui.res
🧠 Learnings (1)
📚 Learning: 2025-12-04T12:13:23.246Z
Learnt from: CR
Repo: enviodev/hyperindex PR: 0
File: codegenerator/cli/templates/static/shared/.cursor/rules/subgraph-migration.mdc:0-0
Timestamp: 2025-12-04T12:13:23.246Z
Learning: Applies to codegenerator/cli/templates/static/shared/**/*.ts : Always verify entity type field names match exactly with the generated entity types. For example, use `token0_id` not `token0`, and `transaction_id` not `transaction`. Run `pnpm tsc --noEmit` to catch type mismatches

Applied to files:

  • codegenerator/cli/npm/envio/src/tui/Tui.res
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_and_test
🔇 Additional comments (5)
codegenerator/cli/npm/envio/src/tui/Tui.res (5)

49-55: Consider edge case where toBlock - firstEventBlock is zero or negative.

If firstEventBlock defaults to startBlock (line 28) and toBlock equals startBlock, then outOf becomes 0. Similarly, loaded or buffered could be negative if block ordering is unexpected. Verify that BufferedProgressBar handles these edge cases gracefully, or add guards here.


85-96: LGTM!

Clean and simple component with proper text styling and locale-aware number formatting.


105-115: LGTM - Proper polling with cleanup.

The interval-based polling with cleanup in the effect return is correctly implemented.


129-174: LGTM - Well-structured progress state derivation.

The progress state logic handles the various sync states clearly, with helpful comments explaining edge cases like chains with no events in the block range.


261-266: LGTM!

The start function correctly initializes the TUI and returns a rerender callback for external refresh control beyond the internal polling.

@DZakh DZakh requested a review from JonoPrest December 22, 2025 11:40
@DZakh
Copy link
Member Author

DZakh commented Jan 5, 2026

Merged as a part of #876

@DZakh DZakh closed this Jan 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants