Skip to content

FIX: Deduplicate getExplorer and getDbExplorer API calls via Zustand store#71

Merged
wicky-zipstack merged 5 commits intomainfrom
fix/dedupe-get-explorer-api-call
Apr 25, 2026
Merged

FIX: Deduplicate getExplorer and getDbExplorer API calls via Zustand store#71
wicky-zipstack merged 5 commits intomainfrom
fix/dedupe-get-explorer-api-call

Conversation

@tahierhussain
Copy link
Copy Markdown
Contributor

@tahierhussain tahierhussain commented Apr 22, 2026

What

  • Added a new Zustand store frontend/src/store/explorer-store.js that holds the shared responses of both explorerSvc.getExplorer(projectId) and explorerSvc.getDbExplorer(projectId).
  • Updated frontend/src/ide/explorer/explorer-component.jsx to become the single writer for the store — it writes on every successful fetch of either API, and clears both slices on project switch through one clearExplorerData() call.
  • Updated frontend/src/ide/chat-ai/Body.jsx to stop calling either explorer API directly. It now subscribes to explorerData and dbExplorerData from the store and mirrors them into the existing promptAutoComplete shape used by chat autocomplete.
  • Removed the now-unused explorerService import and explorerSvc ref from Body.jsx (chat-ai body no longer touches explorerService at all).

Why

  • explorer-component.jsx and Body.jsx were both independently calling the same two explorer endpoints with the same arguments, causing duplicate GET /explorer and GET /db_explorer requests every time the chat drawer was opened.
  • The two responses were identical per-endpoint; only the downstream consumption differed — the left-side explorer tree on one hand, and the chat prompt autocomplete (@models, @seeds, DB schema/tables) on the other.
  • Deduplicating both fetches reduces unnecessary network traffic and backend load, removes any chance of inconsistency between what the explorer tree shows and what chat autocomplete suggests, and establishes a single source of truth for explorer data in the frontend.

How

  • New store (frontend/src/store/explorer-store.js): a minimal Zustand store with two fields — explorerData (= res.data.children from /explorer, array where [0]=models, [1]=seeds) and dbExplorerData (= res.data from /db_explorer, a single DB tree object). Setters setExplorerData / setDbExplorerData, and a single clearExplorerData action that resets both fields (intentional — both slices belong to the same project and always reset together on project switch). Intentionally no persist middleware — the payloads are refetched on every mount, can be large, and persisting only increases the risk of stale cross-session data.
  • explorer-component.jsx:
    • Imported useExplorerStore and pulled setExplorerData, setDbExplorerData, clearExplorerData alongside the existing useProjectStore selectors.
    • Inside the existing getExplorer wrapper, added setExplorerData(treeData) right after setTreeData(treeData). A single write site covers every refresh trigger — mount, schema switch, model create/rename/delete, run completion, explorer refresh events, etc.
    • Inside the existing getDbEXplorer wrapper, added setDbExplorerData(treeData) right after setDBExplorer(mappedData). A single write site covers both callers — the [projectId] mount/project-switch effect and the user-triggered refresh debounce.
    • Added a small useEffect([projectId]) that calls clearExplorerData() so the store doesn't briefly serve the previous project's data to Body.jsx during a project switch.
  • Body.jsx:
    • Imported useExplorerStore and subscribed to both explorerData and dbExplorerData.
    • Removed the explorerSvc.getExplorer(projectId) and explorerSvc.getDbExplorer(projectId) fetch blocks entirely, including the entire useEffect that housed them (previously gated on isChatDrawerOpen).
    • Added two small mirror effects that sync explorerData[0]/[1] into promptAutoComplete.modelsData/seedsData and dbExplorerData into promptAutoComplete.dbData. The promptAutoComplete shape consumed downstream by NewChatInputPrompt is unchanged.
    • Removed the now-unused import { explorerService }, the useRef import, and the const explorerSvc = useRef(explorerService()).current line — Body.jsx no longer has any direct dependency on explorerService.

Can this PR break any existing features. If yes, please list possible items. If no, please explain why. (PS: Admins do not merge the PR without this section filled)

  • Low risk. Neither the left-side explorer tree path nor its DB explorer tab is changed functionally — setTreeData, transformTree, setDBExplorer, mapIconsToTreeData, and all other callers of the internal wrappers in explorer-component.jsx continue to work exactly as before. The only new behavior there is an additional side-effect of writing the raw response into the shared store.
  • Chat autocomplete: the promptAutoComplete shape consumed by NewChat / InputPrompt is preserved. The only behavioral change is that models/seeds/DB autocomplete data appears when the explorer has fetched at least once (as part of the IDE's normal boot), rather than being refetched on chat-drawer open. Per the agreed design, the chat drawer is only expected to be opened after the IDE and explorer have mounted, so this is not a regression in practice.
  • Project switch: autocomplete will briefly show empty data (between clearExplorerData firing and the next fetches completing) instead of the previous project's entries. This is intentional and the safer behavior — it prevents cross-project data leakage in the autocomplete list.

Database Migrations

  • None

Env Config

  • None

Relevant Docs

  • None

Related Issues or PRs

  • None

Dependencies Versions

  • No new dependencies. Uses the already-installed zustand package, consistent with frontend/src/store/project-store.js, frontend/src/store/model-status-store.js, etc.

Notes on Testing

  • Deduplication check — open the IDE with DevTools → Network tab filtered to explorer. Reload the page, then open the chat drawer. Expect exactly one GET /explorer and one GET /db_explorer request on boot (previously each fired twice once the chat drawer was opened).
  • Chat autocomplete (models/seeds) — open the chat drawer, type @ in the prompt input. Verify that model and seed suggestions still populate identically to before.
  • Chat autocomplete (DB) — trigger the DB-specific autocomplete (schema/table suggestions) in the chat prompt. Verify it still populates identically to before.
  • Explorer trees still render — both the models/seeds tree and the DB explorer tab in the left panel render identically. Verify that model create / rename / delete / run completion / manual refresh still update the models tree, and that the DB refresh button still refreshes the DB explorer (via onRefreshDebouncegetDbEXplorer(projectId, hardReload)).
  • Project switch — switch projects via the project switcher with the chat drawer open. Verify:
    • Explorer tree swaps to the new project's models/seeds; DB explorer swaps to the new project's DB tree.
    • Chat autocomplete reflects the new project's models/seeds/DB data and does not momentarily show entries from the previous project.
  • Lint/build — run npm run lint in frontend/ — should pass (the 2 pre-existing import/no-unresolved warnings for ./ExistingChat and ./NewChat in Body.jsx are unrelated to this change). No new unused-import warnings should appear in Body.jsx after the explorerService / useRef / explorerSvc removal.

Screenshots

Note: The explorer APIs are not repeated multiple times
image

Checklist

I have read and understood the Contribution Guidelines.

tahierhussain and others added 2 commits April 22, 2026 14:53
Both explorer-component.jsx and chat-ai/Body.jsx were independently
calling explorerSvc.getExplorer(projectId), causing a duplicate
GET /explorer request every time the chat drawer opened.

Introduce a minimal Zustand store (explorer-store.js) that holds the
shared response. explorer-component.jsx becomes the single writer
(updates on every successful fetch, clears on project switch), and
Body.jsx reads explorerData from the store to populate prompt
autocomplete instead of issuing its own fetch. getDbExplorer in
Body.jsx is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Extend useExplorerStore with a dbExplorerData slice so chat-ai/Body.jsx
stops fetching /db_explorer independently. Previously, opening the chat
drawer triggered a second identical GET /db_explorer alongside the one
already issued by explorer-component.jsx on mount.

explorer-component.jsx becomes the single writer for both slices
(getExplorer and getDbExplorer wrappers), and the existing project-switch
clear effect now resets both slices via an expanded clearExplorerData.
Body.jsx subscribes to the store and mirrors dbExplorerData into the
existing promptAutoComplete.dbData shape — no direct explorerService
usage remains in chat-ai/Body.jsx (explorerService import, useRef import,
and the explorerSvc ref were dropped).

Also updated the PR description to cover both refactors in one PR.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@tahierhussain tahierhussain requested a review from a team as a code owner April 22, 2026 09:28
@tahierhussain tahierhussain self-assigned this Apr 22, 2026
@tahierhussain tahierhussain added the bug Something isn't working label Apr 22, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR deduplicates the GET /explorer and GET /db_explorer API calls that were previously fired independently by both explorer-component.jsx and Body.jsx. A new minimal Zustand store (explorer-store.js) acts as the single source of truth: the explorer component writes on every fetch, and Body.jsx passively mirrors the store data into promptAutoComplete. All concerns raised in prior review threads are addressed — the store correctly receives the pre-mutation deep clone (rawTreeDataRef.current) rather than the post-transformTree tree, the clearExplorerData dependency is present in the effect array, and the isChatDrawerOpen removal is documented with a clear inline comment explaining the mount-order assumption.

Confidence Score: 5/5

Safe to merge — all prior P1 concerns are addressed and no new issues were found.

All three previously-raised review concerns (mutated-tree store data, missing clearExplorerData dep, isChatDrawerOpen gate removal) are explicitly resolved in this revision. The schemaMenu null-init change avoids a redundant duplicate mount fetch while the previewTimeTravel effect still fires getExplorer on first render, keeping store population intact. No P0 or P1 findings remain.

No files require special attention.

Important Files Changed

Filename Overview
frontend/src/store/explorer-store.js New minimal Zustand store with explorerData, dbExplorerData, setters, and clearExplorerData — correct pattern, no persist middleware by design.
frontend/src/ide/explorer/explorer-component.jsx Stores pre-mutation deep clone (rawTreeDataRef.current) in explorerData and raw res.data in dbExplorerData; project-switch clear uses prevProjectIdRef pattern; schemaMenu init changed to null to eliminate duplicate mount-time getExplorer call (still fires from previewTimeTravel effect on mount).
frontend/src/ide/chat-ai/Body.jsx Removed direct explorerService calls; now passively mirrors explorerData/dbExplorerData from store into promptAutoComplete; explorerSvc ref and explorerService import cleaned up; comment documents the mount-order assumption.

Sequence Diagram

sequenceDiagram
    participant EC as explorer-component.jsx
    participant ES as explorer-store.js (Zustand)
    participant B as Body.jsx (chat-ai)
    participant API as Backend API

    EC->>API: GET /explorer (single fetch)
    API-->>EC: res.data.children
    EC->>EC: deep clone → rawTreeDataRef.current
    EC->>ES: setExplorerData(rawTreeDataRef.current)
    EC->>EC: mutate + transformTree (local only)
    EC->>API: GET /db_explorer (single fetch)
    API-->>EC: res.data
    EC->>ES: setDbExplorerData(res.data)

    ES-->>B: explorerData (reactive subscription)
    B->>B: mirror → promptAutoComplete.modelsData/seedsData
    ES-->>B: dbExplorerData (reactive subscription)
    B->>B: mirror → promptAutoComplete.dbData

    Note over EC,ES: On project switch
    EC->>ES: clearExplorerData() (prevProjectIdRef guard)
    ES-->>B: explorerData=null, dbExplorerData=null
    B->>B: promptAutoComplete reset to {}
Loading

Reviews (4): Last reviewed commit: "fix: only clear explorer store on actual..." | Re-trigger Greptile

Comment thread frontend/src/ide/explorer/explorer-component.jsx Outdated
Comment thread frontend/src/ide/explorer/explorer-component.jsx Outdated
Comment thread frontend/src/ide/chat-ai/Body.jsx
tahierhussain and others added 2 commits April 22, 2026 15:12
Three review-driven tweaks from the first round of review on this branch:

- explorer-component.jsx: publish the raw /explorer response to
  useExplorerStore (via rawTreeDataRef.current) before the in-place
  mutations by sortModels, applyModelDecorations, and transformTree
  begin. The store now matches its documented contract and Body.jsx's
  autocomplete receives the untransformed children shape it did before
  the dedup refactor.
- explorer-component.jsx: add clearExplorerData to the dependency array
  of the project-switch clear effect. Zustand action refs are stable in
  practice, so effect cadence is unchanged, but the deps array is now
  exhaustive and future-proof against a non-stable refactor.
- Body.jsx: expand the short comment above the mirror effects into a
  block that documents why this component is read-only and lists the
  escape hatches (fetch fallback here or a loading state in InputPrompt)
  to reach for if the "explorer mounts before chat drawer" invariant
  ever breaks.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The prior `schemaMenu?.length` guard on the schema/schema-change useEffect
conflated "schemas not loaded yet" (schemaMenu === []) with "loaded but
empty" (same []). A reviewer correctly flagged that a project with no DB
connection would have the fetch silently suppressed.

Fix:
- Initialise schemaMenu as null (sentinel for "not yet loaded") instead of [].
- Revert the effect guard to `if (schemaMenu)` so it fires the moment
  schemaMenu transitions from null to an array, regardless of whether that
  array has items.
- Add null-safe guards — `(schemaMenu || [])` — at the JSX consumers
  (`.map`, `.length` comparisons) that now need to handle the pre-load
  null state. During loading, seed-related actions remain disabled and
  the schema dropdown is empty, both the correct UX.
- Add an inline comment above the effect explaining the sentinel so this
  design choice is visible at the call site.

Net result: the original optimisation (skip the redundant mount-time
fetch while schemaMenu is still []) is preserved, and the reviewer's
no-DB regression is fixed.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@wicky-zipstack wicky-zipstack left a comment

Choose a reason for hiding this comment

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

LGTM.
Minor note: consider adding a null fallback in handleAddNewSchema when spreading prevMenu (purely defensive).

Copy link
Copy Markdown
Contributor

@abhizipstack abhizipstack left a comment

Choose a reason for hiding this comment

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

Good refactor — the single-writer Zustand store pattern is clean, and the PR description is thorough. Two issues to address before merge (details in inline comments).

Comment thread frontend/src/ide/explorer/explorer-component.jsx
Comment thread frontend/src/ide/explorer/explorer-component.jsx
The project-switch clear effect was also firing on initial mount (and
on any remount of explorer-component within the same session), which
briefly wiped valid store data and could leave chat autocomplete empty
until the next fetch resolved. Gate the clear with a prevProjectIdRef
so clearExplorerData() only fires when projectId actually changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@abhizipstack abhizipstack left a comment

Choose a reason for hiding this comment

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

LGTM

@wicky-zipstack wicky-zipstack merged commit fc00c46 into main Apr 25, 2026
8 checks passed
@wicky-zipstack wicky-zipstack deleted the fix/dedupe-get-explorer-api-call branch April 25, 2026 05:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants