-
-
Notifications
You must be signed in to change notification settings - Fork 9
Fix namespaces issues #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
optimize namespace selector optimize push behavior
WalkthroughThis PR refactors namespace selection to use an AutocompleteSelect component instead of a modal, adds namespace-aware API queries, updates the push workflow to respect namespace settings, improves error handling, and introduces hidden-layer child traversal logic for node filtering. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Push View
participant Hook as useHasNamespacesEnabled
participant Query as useQueryApi
participant API as API Client
participant Compute as Diff Computation
UI->>Hook: Check if namespaces enabled
activate Hook
Hook->>Query: Fetch /v2/api-keys/current
Query->>API: GET /v2/api-keys/current
API-->>Query: Return projectId from API key
Query-->>Hook: Return projectId
Hook->>Query: Fetch /v2/projects/{projectId}
Query->>API: GET /v2/projects/{projectId}
API-->>Query: Return project with useNamespaces flag
Query-->>Hook: Return useNamespaces flag
deactivate Hook
Hook-->>UI: hasNamespacesEnabled
alt Namespaces Enabled
UI->>Compute: Load translations (with namespaces)
Compute->>Query: Fetch translations by namespace
Query-->>Compute: Return namespaced translations
else Namespaces Disabled
UI->>Compute: Load translations (default namespace only)
Compute->>Query: Fetch default translations
Query-->>Compute: Return default translations
end
UI->>Compute: Compute diff with node changes
Compute->>Compute: getDiffChanges(nodes, translations, hasNamespacesEnabled)
Compute-->>UI: Return KeyChanges with conditional ns field
UI->>UI: Deduplicate nodes (Set-based O(n))
UI->>UI: Prepare push payload (keys, screenshots, tags)
UI->>API: POST /v2/projects/{id}/import (with translations)
API-->>UI: Return result with imported key IDs
UI->>UI: Process each key: resolve NEW vs OVERRIDE
UI->>API: PUT /v2/keys/{keyId}/update-translations
API-->>UI: Acknowledge update
opt Tags enabled
UI->>API: PUT /v2/keys/{keyId}/big-meta/related-keys
API-->>UI: Confirm tag update
end
opt Screenshots enabled
UI->>API: Upload screenshots with ArrayBuffer
API-->>UI: Confirm upload
end
UI->>Query: Invalidate /getConnectedNodes cache
Query-->>UI: Cache cleared
UI->>UI: Show success screen with pushed key counts
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cypress/e2e/index.cy.ts (1)
32-53: Test logic is inconsistent after removingnamespacesDisabled.After removing
namespacesDisabled: truefrom the config (line 35), this test now has identical configuration to the first test ({ ...SIGNED_IN }), yet expects opposite behavior:
- Test 1 (line 8):
config: SIGNED_IN→ expects namespace selector to exist (lines 25-29)- Test 2 (line 35):
config: { ...SIGNED_IN }→ expects namespace selector to NOT exist (line 52)Without a config setting to hide the namespace selector, the expectation on line 52 will fail. Please either:
- Update the assertion to expect the selector to exist, or
- Add a config setting that actually hides the namespace selector, or
- Remove this test if it's no longer relevant after the namespace refactor.
🧹 Nitpick comments (5)
src/ui/hooks/useAllTranslations.ts (1)
39-43: Consider simplifying the empty namespace handling logic.When
namespacesis provided (truthy), line 33 assignsnsNamesdirectly fromnamespaces. This means ifnamespacesincludes"", thennsNameswill already include it, making the check on line 41 (!nsNames.includes("")) always true when needed. The defensive code appears correct but may be overly cautious.Consider this simplification if the intent is to handle only the explicit
namespacesparameter case:- // Ensure empty string is included if namespaces is provided and contains empty string - // This handles the default namespace case - if (namespaces && namespaces.includes("") && !nsNames.includes("")) { - nsNames.push(""); - } + // Ensure empty string is included when explicitly requested + if (namespaces?.includes("") && !nsNames.includes("")) { + nsNames.push(""); + }src/ui/components/NamespaceSelect/NamespaceSelect.tsx (1)
51-58: Consider simplifying the nested setTimeout pattern.The double
focus()calls with nested timeouts (100ms then 50ms) appear fragile and may not reliably restore focus across different browsers or system loads.Consider a single timeout with a reasonable delay, or use a more deterministic approach like listening for a state update callback:
if (wasFocused && inputRef.current) { - setTimeout(() => { - inputRef.current?.focus(); - // Small delay to ensure the options are updated - setTimeout(() => { - inputRef.current?.focus(); - }, 50); - }, 100); + // Allow time for options to update before refocusing + setTimeout(() => inputRef.current?.focus(), 150); }src/ui/client/useQueryApi.ts (1)
51-65: Consider extending MutationProps with customPaths for consistency.The
useApiQueryhook'sPathsgeneric now includescustomPaths(line 32), butMutationPropsstill defaults to onlypaths(line 54). If mutations need to support custom endpoints, update line 54 to match:export type MutationProps< Url extends keyof Paths, Method extends keyof Paths[Url], - Paths = paths + Paths = paths & customPaths > = {If mutations are intentionally restricted to generated paths only, this is acceptable as-is.
src/ui/components/AutocompleteSelect/AutocompleteSelect.tsx (1)
176-191: Consider reducing the blur delay timeout.The 200ms delay in
handleBlur(line 185) allows dropdown item clicks to register before closing, but it also delays the UI response. Consider:
- Reducing to 100-150ms if 200ms feels sluggish
- Using
onMouseDownfor dropdown items (which you already do) to avoid needing such a long delaysrc/ui/views/Push/Push.tsx (1)
82-92: Consider including empty namespace in the dependency key.The
requiredNamespacesKeyfilters out empty/falsy namespaces on line 88, but the actual namespace computation inside the effect (line 127) includes empty strings vianode.ns ?? "". While this works because the key is only for dependency tracking, aligning them would make the logic easier to follow.const uniqueNamespaces = Array.from( new Set( deduplicatedNodes - .map((node) => node.ns) - .filter((ns): ns is string => Boolean(ns)) + .map((node) => node.ns ?? "") ) );
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (36)
cypress/e2e/index.cy.ts(2 hunks)cypress/e2e/push.cy.ts(1 hunks)package.json(1 hunks)src/main/utils/nodeTools.ts(1 hunks)src/main/utils/settingsTools.ts(2 hunks)src/tools/getPushChanges.ts(3 hunks)src/types.ts(1 hunks)src/ui/client/apiSchema.custom.ts(1 hunks)src/ui/client/client.ts(3 hunks)src/ui/client/useQueryApi.ts(2 hunks)src/ui/components/ActionsBottom/ActionsBottom.css(1 hunks)src/ui/components/AutocompleteSelect/AutocompleteSelect.css(1 hunks)src/ui/components/AutocompleteSelect/AutocompleteSelect.css.d.ts(1 hunks)src/ui/components/AutocompleteSelect/AutocompleteSelect.tsx(1 hunks)src/ui/components/InfoTooltip/InfoTooltip.css(1 hunks)src/ui/components/NamespaceSelect/NamespaceSelect.css(1 hunks)src/ui/components/NamespaceSelect/NamespaceSelect.css.d.ts(1 hunks)src/ui/components/NamespaceSelect/NamespaceSelect.tsx(1 hunks)src/ui/components/NodeList/NodeRow.css(1 hunks)src/ui/components/NodeList/NodeRow.tsx(1 hunks)src/ui/hooks/useAllTranslations.ts(2 hunks)src/ui/hooks/useHasNamespacesEnabled.ts(1 hunks)src/ui/hooks/useSetNodesDataMutation.ts(1 hunks)src/ui/icons/SvgIcons.tsx(1 hunks)src/ui/styles.css(1 hunks)src/ui/views/Index/Index.css(0 hunks)src/ui/views/Index/Index.css.d.ts(0 hunks)src/ui/views/Index/Index.tsx(4 hunks)src/ui/views/Index/ListItem.tsx(3 hunks)src/ui/views/Pull/Pull.tsx(1 hunks)src/ui/views/Push/Push.tsx(10 hunks)src/ui/views/Settings/ProjectSettings.tsx(6 hunks)src/ui/views/Settings/PushSection.tsx(3 hunks)src/ui/views/Settings/Settings.tsx(1 hunks)src/ui/views/Settings/StringsEditor.css(1 hunks)src/ui/views/Settings/StringsSection.tsx(4 hunks)
💤 Files with no reviewable changes (2)
- src/ui/views/Index/Index.css.d.ts
- src/ui/views/Index/Index.css
🧰 Additional context used
🧬 Code graph analysis (14)
src/ui/hooks/useSetNodesDataMutation.ts (3)
src/main/endpoints/setNodesData.ts (2)
SetNodesDataProps(5-7)setNodesDataEndpoint(9-28)src/main/utils/delayed.ts (1)
delayed(1-6)src/main/endpoints/getConnectedNodes.ts (1)
getConnectedNodesEndpoint(9-22)
cypress/e2e/push.cy.ts (2)
src/web/urlConfig.ts (2)
createTestNode(82-93)SIGNED_IN(39-45)cypress/common/tools.ts (1)
visitWithState(5-9)
src/ui/hooks/useHasNamespacesEnabled.ts (1)
src/ui/client/useQueryApi.ts (1)
useApiQuery(29-49)
cypress/e2e/index.cy.ts (2)
src/web/urlConfig.ts (2)
createTestNode(82-93)SIGNED_IN(39-45)cypress/common/tools.ts (1)
visitWithState(5-9)
src/ui/client/useQueryApi.ts (2)
src/ui/client/apiSchema.generated.ts (1)
paths(7-895)src/ui/client/apiSchema.custom.ts (1)
customPaths(6-14)
src/ui/views/Settings/StringsSection.tsx (1)
src/ui/components/InfoTooltip/InfoTooltip.tsx (1)
InfoTooltip(15-33)
src/ui/views/Index/ListItem.tsx (1)
src/ui/components/NamespaceSelect/NamespaceSelect.tsx (1)
NamespaceSelect(15-96)
src/ui/views/Settings/PushSection.tsx (1)
src/ui/components/AutocompleteSelect/AutocompleteSelect.tsx (1)
AutocompleteSelect(39-327)
src/ui/client/apiSchema.custom.ts (1)
src/ui/client/apiSchema.generated.ts (2)
operations(3321-11264)components(899-3315)
src/ui/views/Push/Push.tsx (5)
src/tools/getPushChanges.ts (2)
KeyChanges(17-22)getPushChanges(24-105)src/types.ts (1)
NodeInfo(74-86)src/ui/hooks/useAllTranslations.ts (1)
useAllTranslations(12-108)src/ui/hooks/useHasNamespacesEnabled.ts (1)
useHasNamespacesEnabled(3-35)src/main/endpoints/getScreenshots.ts (1)
getScreenshotsEndpoint(9-54)
src/ui/views/Index/Index.tsx (2)
src/ui/hooks/useHasNamespacesEnabled.ts (1)
useHasNamespacesEnabled(3-35)src/ui/hooks/useConnectedNodes.ts (1)
useConnectedNodes(8-16)
src/tools/getPushChanges.ts (1)
src/tools/compareNs.ts (1)
compareNs(1-3)
src/ui/views/Settings/ProjectSettings.tsx (3)
src/ui/hooks/useHasNamespacesEnabled.ts (1)
useHasNamespacesEnabled(3-35)src/ui/components/InfoTooltip/InfoTooltip.tsx (1)
InfoTooltip(15-33)src/ui/client/decodeApiKey.ts (1)
getProjectIdFromApiKey(57-74)
src/ui/client/client.ts (2)
src/ui/client/apiSchema.generated.ts (1)
paths(7-895)src/ui/client/apiSchema.custom.ts (1)
customPaths(6-14)
🪛 GitHub Check: Static check 🪲
src/ui/views/Settings/Settings.tsx
[warning] 23-23:
'getProjectIdFromApiKey' is defined but never used
[warning] 15-15:
'useApiQuery' is defined but never used
⏰ 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: Cypress
🔇 Additional comments (52)
src/ui/views/Pull/Pull.tsx (1)
56-59: LGTM! Clear error handling for image upload limits.The error handling logic is correct and the error message provides clear, actionable guidance to users.
src/ui/styles.css (1)
39-39: LGTM!The addition of the dark mode text color variable is appropriate. White text (
#ffffff) provides good contrast on dark backgrounds.src/ui/views/Settings/StringsEditor.css (1)
162-166: LGTM!The dark mode override for placeholder text improves readability. Using
--figma-color-text-primary(white) instead of grey text provides better contrast on dark backgrounds.cypress/e2e/index.cy.ts (1)
25-29: LGTM! Selector and assertion correctly updated.The migration from a select dropdown to an input-based namespace selector is properly reflected here. The functional expectation checking for an empty value is appropriate.
src/ui/components/ActionsBottom/ActionsBottom.css (1)
10-10: LGTM! Z-index adjustment aligns with UI layering updates.The z-index increase from 1 to 2 ensures proper stacking with other overlay components introduced in this PR.
src/ui/components/InfoTooltip/InfoTooltip.css (1)
6-6: LGTM! Z-index addition ensures proper tooltip layering.The added z-index coordinates with other UI stacking adjustments across the PR.
src/ui/hooks/useSetNodesDataMutation.ts (1)
9-20: LGTM! Query invalidation pattern ensures data freshness.The onSuccess handler properly invalidates the connected nodes query after mutation succeeds, ensuring the UI displays up-to-date data.
src/ui/components/NodeList/NodeRow.tsx (1)
88-88: LGTM! Improved label clarity.Changing "ns:" to "Namespace:" makes the UI more user-friendly and explicit.
src/types.ts (1)
118-118: LGTM! Settings type extension for hidden layer handling.The new
ignoreHiddenLayersIncludingChildrenproperty appropriately extends the GlobalSettings type for parent-chain visibility checks.src/ui/components/NodeList/NodeRow.css (1)
41-43: LGTM! CSS changes support improved namespace display.The overflow and text-overflow adjustments allow the full namespace text to be visible, complementing the "Namespace:" label change.
package.json (1)
9-9: LGTM! Schema generation script updated for new API structure.The updated endpoint URL and output path align with the UI-centric API schema organization introduced in this PR.
src/ui/hooks/useAllTranslations.ts (1)
101-103: LGTM!The
clearCache()method provides a clean way to reset translations data, which aligns with the broader namespace refresh functionality introduced in this PR.src/ui/components/NamespaceSelect/NamespaceSelect.css (1)
11-63: LGTM!The styling for the NamespaceSelect component is well-structured with:
- Proper positioning and layout for the input wrapper and refresh button
- Clear interaction states (hover, disabled)
- Smooth rotation animation for the refresh action
src/main/utils/settingsTools.ts (1)
76-76: LGTM!The new
ignoreHiddenLayersIncludingChildrensetting is properly integrated into the plugin data flow, being destructured from input and persisted to document settings. This aligns with the PR's objective to refine hidden layer detection logic.Also applies to: 95-95
src/ui/icons/SvgIcons.tsx (1)
187-201: LGTM!The new
Refreshicon component follows the established pattern of other icon components in this file, usingcurrentColorfor flexible theming and spreading props for maximum reusability.src/ui/views/Index/Index.tsx (3)
67-87: LGTM!The
allAvailableNamespacescomputation efficiently combines API and node-based namespaces with proper deduplication and sorting. The sorting logic correctly places empty string at the end while maintaining alphabetical order for other namespaces.
90-92: LGTM!The
handleRefreshNamespacesfunction properly refreshes both the API namespaces and connected nodes data sources usingPromise.all, ensuring the UI stays in sync with the latest namespace state.
241-246: LGTM!The updated
ListItemprops correctly wire up the namespace enablement flag, dynamically computed namespace list, and refresh callback, enabling the component to conditionally render namespace selection UI and trigger refreshes when needed.cypress/e2e/push.cy.ts (2)
82-115: LGTM!The test properly verifies that pushing new keys displays the correct count in the success message. The test flow is clear: setup test data → verify diff display → submit push → assert success count.
117-150: LGTM!The test properly verifies that pushing changed keys displays the correct count in the success message, following the same reliable pattern as the new keys test.
src/ui/components/AutocompleteSelect/AutocompleteSelect.css (1)
1-61: LGTM!The AutocompleteSelect styling is well-structured with:
- Proper positioning variants for dropdown direction (up/down)
- Clear visual hierarchy with headers and items
- Appropriate interaction states (hover, cursor)
- Themed colors using Figma CSS variables
src/tools/getPushChanges.ts (3)
38-50: LGTM on conditional namespace comparison.The logic correctly skips namespace comparison when
hasNamespacesEnabledis false, using short-circuit evaluation withcompareNs.
85-93: LGTM on KeyChangeValue construction.Setting
nstoundefinedwhen namespaces are disabled ensures consistent downstream handling.
24-30: LGTM on the function signature change.The parameter change to
hasNamespacesEnabledcorrectly controls namespace behavior in the push logic. The implementation properly guards namespace comparisons (line 42) and conditionally sets thensfield (line 87). All callers have been updated—the single caller in Push.tsx correctly passes the new parameter.src/main/utils/nodeTools.ts (1)
34-59: LGTM on the hidden layer visibility logic.The refactored logic correctly:
- Checks node's own visibility first
- Optionally traverses the parent chain when
ignoreHiddenLayersIncludingChildrenis enabled- Uses defensive programming with try-catch around parent traversal
One consideration: the catch block logs the error but allows the function to continue (potentially returning
true). This is a reasonable fallback—if we can't determine parent visibility, we include the node rather than silently exclude it.src/ui/client/client.ts (1)
124-127: LGTM on the extended Paths type.The intersection
paths & customPathscorrectly extends the API client to support both generated and custom endpoint definitions.src/ui/components/AutocompleteSelect/AutocompleteSelect.css.d.ts (1)
1-12: LGTM!Standard CSS module type declaration file with correct readonly properties and CommonJS-style export.
src/ui/components/NamespaceSelect/NamespaceSelect.tsx (1)
65-95: LGTM on the component structure.The integration with
AutocompleteSelectand conditional refresh button rendering is clean. TheisRefreshingstate correctly disables the button and applies the rotating animation.src/ui/components/NamespaceSelect/NamespaceSelect.css.d.ts (1)
1-12: LGTM!The CSS type definitions correctly expose the new class names needed for the enhanced NamespaceSelect UI (container, refresh button, animations, etc.).
src/ui/views/Settings/StringsSection.tsx (3)
82-98: LGTM!The help text clearly explains the behavior of the "Including all child texts" option and how it differs from the basic hidden layers setting.
139-142: LGTM!State management for
ignoreHiddenLayersIncludingChildrenfollows the existing patterns consistently, with proper initialization from config and handler to update both local state and config.Also applies to: 203-211
308-329: LGTM!The UI properly implements the nested checkbox pattern with appropriate indentation and conditional rendering. The InfoTooltip provides helpful context for users.
src/ui/views/Index/ListItem.tsx (3)
17-29: LGTM!The props interface correctly extends to support namespace enablement detection and refresh behavior. The function signature properly destructures the new props.
101-111: LGTM!The NamespaceSelect rendering is now properly gated by
hasNamespacesEnabledand theonRefreshcallback is correctly passed through for refresh functionality.
125-136: LGTM!The simplified InsertLink rendering based on
node.connectedis cleaner and maintains the same visual behavior (secondary color for unconnected state).src/ui/views/Settings/ProjectSettings.tsx (2)
136-142: LGTM!The namespace sorting logic correctly places empty strings at the end while maintaining alphabetical order for named namespaces.
20-77: LGTM!The dynamic help text functions properly generate project-specific documentation links. The dual functions (
namespaceHelpTextandnamespaceHelpTextSetUp) provide appropriate guidance for both enabled and disabled namespace states.src/ui/client/apiSchema.custom.ts (1)
1-68: LGTM!The custom API schema is well-structured and follows the same patterns as the generated schema. It properly:
- References existing
components["schemas"]types for consistency- Includes comprehensive response types for success and error cases
- Provides clear documentation comments
- Types the
projectIdparameter correctly as a numbersrc/ui/views/Settings/PushSection.tsx (2)
25-30: LGTM!The tag options are efficiently computed with
useMemoand properly sorted alphabetically for a better user experience.
88-97: LGTM!The
AutocompleteSelectintegration simplifies the tag management UI significantly. The configuration is appropriate with multi-select mode enabled and descriptive placeholders for existing and new tags.src/ui/components/AutocompleteSelect/AutocompleteSelect.tsx (5)
13-37: LGTM!The discriminated union for
Propscorrectly types single-select vs multi-select modes, ensuring type safety forvalueandonChangeat compile time.
73-99: LGTM!The display logic and sync effect correctly handle the single-select mode behavior:
- Shows the selected value when not focused
- Shows the input value when focused
- Syncs back to the selected value when focus is lost
101-129: LGTM!The filtering and validation logic correctly:
- Filters options based on input
- Excludes selected values in multi-select mode
- Detects exact matches
- Prevents duplicate additions
130-166: LGTM!Event handlers properly manage both single-select and multi-select flows with appropriate type guards and state updates.
193-326: LGTM!The rendering logic is comprehensive:
- Displays badges for multi-select values with remove functionality
- Provides accessible input with proper ARIA attributes
- Renders dropdown with dynamic positioning
- Highlights matching text segments
- Shows "Add" option for new values
- Handles empty states appropriately
src/ui/views/Push/Push.tsx (7)
44-50: LGTM!The new state variables (
isPushing,pushedKeysCount,screenshotCount) appropriately support the enhanced push progress tracking and success screen functionality.
56-68: Good performance optimization.The O(n²) to O(n) deduplication improvement using a Set is a solid change. The composite key correctly handles both key and namespace for uniqueness.
111-145: Well-structured effect with proper cancellation handling.The cancellation pattern using a
cancelledflag correctly prevents state updates after unmount or dependency changes. The early return whensuccessis true is a good optimization. The use of primitive keys as dependencies to avoid object reference issues is a valid pattern.
352-379: Good resilience pattern for non-critical operations.Wrapping tag addition in a separate try-catch allows the push to succeed even if tagging fails. The error message correctly indicates that translations were still updated.
412-419: Cache invalidation correctly preserves pushed count.Storing
keysPushedbefore clearing cache ensures the success screen displays accurate counts. The cache clear prevents stale translation data on subsequent operations.
474-488: Clean success screen implementation.The success UI correctly uses preserved counts and provides clear feedback to the user with the option to return to the main view.
489-490: LGTM!Including
isPushingin the loading condition correctly shows the loading state during the push operation.
| const allNamespaces = [ | ||
| ...new Set([ | ||
| ...namespaces, | ||
| ...(value && !namespaces.includes(value) ? [value] : []), | ||
| ]), | ||
| ] | ||
| .filter(Boolean) | ||
| .sort((a, b) => { | ||
| // Sort alphabetically, but put empty string at the end | ||
| if (!a) return 1; | ||
| if (!b) return -1; | ||
| return a.localeCompare(b); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: .filter(Boolean) removes empty strings that the sort logic expects to handle.
Line 31 filters out all falsy values including empty strings (""), but the sort comparator on lines 33-36 explicitly handles empty strings to "put empty string at the end." Since empty strings are filtered out before sorting, this code path is unreachable.
If empty string represents a valid "no namespace" option, preserve it:
]
- .filter(Boolean)
+ .filter((ns) => ns !== undefined && ns !== null)
.sort((a, b) => {Alternatively, if empty strings should be excluded, remove the dead code in the sort comparator.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const allNamespaces = [ | |
| ...new Set([ | |
| ...namespaces, | |
| ...(value && !namespaces.includes(value) ? [value] : []), | |
| ]), | |
| ] | |
| .filter(Boolean) | |
| .sort((a, b) => { | |
| // Sort alphabetically, but put empty string at the end | |
| if (!a) return 1; | |
| if (!b) return -1; | |
| return a.localeCompare(b); | |
| }); | |
| const allNamespaces = [ | |
| ...new Set([ | |
| ...namespaces, | |
| ...(value && !namespaces.includes(value) ? [value] : []), | |
| ]), | |
| ] | |
| .filter((ns) => ns !== undefined && ns !== null) | |
| .sort((a, b) => { | |
| // Sort alphabetically, but put empty string at the end | |
| if (!a) return 1; | |
| if (!b) return -1; | |
| return a.localeCompare(b); | |
| }); |
🤖 Prompt for AI Agents
In src/ui/components/NamespaceSelect/NamespaceSelect.tsx around lines 25 to 37,
the current .filter(Boolean) removes empty strings but the sort comparator
expects to handle empty string specially; replace .filter(Boolean) with a filter
that only removes null/undefined (e.g., .filter(x => x !== undefined && x !==
null)) so empty string values are preserved for sorting and the "put empty
string at the end" logic remains reachable.
| export const useHasNamespacesEnabled = () => { | ||
| try { | ||
| // First, get the project ID from the API key info (relevant for legacy api keys) | ||
| const apiKeyInfo = useApiQuery({ | ||
| url: "/v2/api-keys/current", | ||
| method: "get", | ||
| options: { | ||
| cacheTime: 60000, | ||
| staleTime: 60000, | ||
| }, | ||
| }); | ||
|
|
||
| // Then get the project info if we have a project ID | ||
| // eslint-disable-next-line react-hooks/rules-of-hooks | ||
| const projectQuery = useApiQuery({ | ||
| url: "/v2/projects/{projectId}", | ||
| method: "get", | ||
| path: { | ||
| projectId: apiKeyInfo.data?.projectId ?? 0, | ||
| }, | ||
| options: { | ||
| cacheTime: 60000, | ||
| staleTime: 60000, | ||
| enabled: apiKeyInfo.data?.projectId !== undefined, | ||
| }, | ||
| }); | ||
|
|
||
| return projectQuery.data?.useNamespaces ?? false; | ||
| } catch (error) { | ||
| console.error(error); | ||
| return false; | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove try/catch wrapper around hook calls.
Wrapping useApiQuery calls in try/catch violates React's Rules of Hooks. Hooks must be called unconditionally at the top level of a component or custom hook. The try/catch won't catch rendering errors from hooks, and the eslint-disable comment on line 16 suggests awareness of this issue.
The conditional enabling of projectQuery via options.enabled (line 26) is the correct approach for conditional data fetching.
Apply this diff to fix the issue:
export const useHasNamespacesEnabled = () => {
- try {
- // First, get the project ID from the API key info (relevant for legacy api keys)
- const apiKeyInfo = useApiQuery({
- url: "/v2/api-keys/current",
- method: "get",
- options: {
- cacheTime: 60000,
- staleTime: 60000,
- },
- });
+ // First, get the project ID from the API key info (relevant for legacy api keys)
+ const apiKeyInfo = useApiQuery({
+ url: "/v2/api-keys/current",
+ method: "get",
+ options: {
+ cacheTime: 60000,
+ staleTime: 60000,
+ },
+ });
- // Then get the project info if we have a project ID
- // eslint-disable-next-line react-hooks/rules-of-hooks
- const projectQuery = useApiQuery({
- url: "/v2/projects/{projectId}",
- method: "get",
- path: {
- projectId: apiKeyInfo.data?.projectId ?? 0,
- },
- options: {
- cacheTime: 60000,
- staleTime: 60000,
- enabled: apiKeyInfo.data?.projectId !== undefined,
- },
- });
+ // Then get the project info if we have a project ID
+ const projectQuery = useApiQuery({
+ url: "/v2/projects/{projectId}",
+ method: "get",
+ path: {
+ projectId: apiKeyInfo.data?.projectId ?? 0,
+ },
+ options: {
+ cacheTime: 60000,
+ staleTime: 60000,
+ enabled: apiKeyInfo.data?.projectId !== undefined,
+ },
+ });
- return projectQuery.data?.useNamespaces ?? false;
- } catch (error) {
- console.error(error);
- return false;
- }
+ return projectQuery.data?.useNamespaces ?? false;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export const useHasNamespacesEnabled = () => { | |
| try { | |
| // First, get the project ID from the API key info (relevant for legacy api keys) | |
| const apiKeyInfo = useApiQuery({ | |
| url: "/v2/api-keys/current", | |
| method: "get", | |
| options: { | |
| cacheTime: 60000, | |
| staleTime: 60000, | |
| }, | |
| }); | |
| // Then get the project info if we have a project ID | |
| // eslint-disable-next-line react-hooks/rules-of-hooks | |
| const projectQuery = useApiQuery({ | |
| url: "/v2/projects/{projectId}", | |
| method: "get", | |
| path: { | |
| projectId: apiKeyInfo.data?.projectId ?? 0, | |
| }, | |
| options: { | |
| cacheTime: 60000, | |
| staleTime: 60000, | |
| enabled: apiKeyInfo.data?.projectId !== undefined, | |
| }, | |
| }); | |
| return projectQuery.data?.useNamespaces ?? false; | |
| } catch (error) { | |
| console.error(error); | |
| return false; | |
| } | |
| }; | |
| export const useHasNamespacesEnabled = () => { | |
| // First, get the project ID from the API key info (relevant for legacy api keys) | |
| const apiKeyInfo = useApiQuery({ | |
| url: "/v2/api-keys/current", | |
| method: "get", | |
| options: { | |
| cacheTime: 60000, | |
| staleTime: 60000, | |
| }, | |
| }); | |
| // Then get the project info if we have a project ID | |
| const projectQuery = useApiQuery({ | |
| url: "/v2/projects/{projectId}", | |
| method: "get", | |
| path: { | |
| projectId: apiKeyInfo.data?.projectId ?? 0, | |
| }, | |
| options: { | |
| cacheTime: 60000, | |
| staleTime: 60000, | |
| enabled: apiKeyInfo.data?.projectId !== undefined, | |
| }, | |
| }); | |
| return projectQuery.data?.useNamespaces ?? false; | |
| }; |
🤖 Prompt for AI Agents
In src/ui/hooks/useHasNamespacesEnabled.ts around lines 3 to 35, remove the
outer try/catch and the eslint-disable comment so hooks are called
unconditionally at the top level; call useApiQuery for apiKeyInfo and
projectQuery directly (no try/catch), pass projectId as
apiKeyInfo.data?.projectId ?? undefined and keep options.enabled:
apiKeyInfo.data?.projectId !== undefined so projectQuery is conditionally
active, then return projectQuery.data?.useNamespaces ?? false.
| {hasNamespacesEnabled ? ( | ||
| <InfoTooltip> | ||
| {namespaceHelpText({ | ||
| apiUrl, | ||
| projectId: getProjectIdFromApiKey(apiKey) ?? 0, | ||
| })} | ||
| </InfoTooltip> | ||
| ) : ( | ||
| <InfoTooltip> | ||
| {namespaceHelpTextSetUp({ | ||
| apiUrl, | ||
| projectId: getProjectIdFromApiKey(apiKey) ?? 0, | ||
| })} | ||
| </InfoTooltip> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for getProjectIdFromApiKey function definition
rg "getProjectIdFromApiKey" --type ts --type tsx -B 2 -A 10Repository: tolgee/figma-plugin
Length of output: 90
🏁 Script executed:
#!/bin/bash
# Search for getProjectIdFromApiKey function definition in the repository
rg "getProjectIdFromApiKey" -B 2 -A 10Repository: tolgee/figma-plugin
Length of output: 5358
🏁 Script executed:
#!/bin/bash
# Get the complete implementation of the getProjectIdFromApiKey function
cat src/ui/client/decodeApiKey.tsRepository: tolgee/figma-plugin
Length of output: 1890
🏁 Script executed:
#!/bin/bash
# Search for namespaceHelpText and namespaceHelpTextSetUp function definitions
rg "namespaceHelpText" -B 2 -A 15Repository: tolgee/figma-plugin
Length of output: 4073
🏁 Script executed:
#!/bin/bash
# Search for how apiKey is validated before being passed to these functions
rg "apiKey" src/ui/views/Settings/ProjectSettings.tsx -B 2 -A 2 | head -50Repository: tolgee/figma-plugin
Length of output: 738
Add validation before constructing help text URLs when API key cannot be parsed.
The code uses getProjectIdFromApiKey(apiKey) ?? 0 as a fallback, which produces broken links like /projects/0/manage/edit/advanced when the API key is empty, malformed, or lacks the required tgpak_ prefix. Consider either conditionally rendering the help text only when a valid project ID is available, or validating the API key earlier in the component lifecycle.
🤖 Prompt for AI Agents
In src/ui/views/Settings/ProjectSettings.tsx around lines 193 to 207, the
component forces a fallback projectId of 0 when getProjectIdFromApiKey(apiKey)
fails which produces invalid help links; change the rendering to validate the
parsed projectId first and only pass a valid numeric projectId into
namespaceHelpText/namespaceHelpTextSetUp (or omit/render a different tooltip)
when getProjectIdFromApiKey(apiKey) returns a truthy number — do not use ?? 0;
if the parsed id is falsy, render a generic help message or hide the link so
broken URLs like /projects/0/... are never generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/ui/views/Push/Push.tsx (1)
82-92: Consider extracting namespace computation to avoid duplication.This namespace extraction logic is duplicated in the useEffect (lines 125-129). While both implementations are correct, extracting it to a memoized value would improve maintainability.
Consider refactoring:
- const requiredNamespacesKey = useMemo(() => { - if (!hasNamespacesEnabled) return ""; - const uniqueNamespaces = Array.from( - new Set( - deduplicatedNodes - .map((node) => node.ns) - .filter((ns): ns is string => Boolean(ns)) - ) - ); - return uniqueNamespaces.sort().join(","); - }, [deduplicatedNodes, hasNamespacesEnabled]); + const requiredNamespaces = useMemo(() => { + if (!hasNamespacesEnabled || deduplicatedNodes.length === 0) return undefined; + return Array.from( + new Set(deduplicatedNodes.map((node) => node.ns ?? "")) + ); + }, [deduplicatedNodes, hasNamespacesEnabled]); + + const requiredNamespacesKey = useMemo(() => { + return requiredNamespaces ? requiredNamespaces.slice().sort().join(",") : ""; + }, [requiredNamespaces]);Then use
requiredNamespacesdirectly in the useEffect instead of recomputing it at lines 124-129.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/ui/views/Push/Push.tsx(10 hunks)
🔇 Additional comments (6)
src/ui/views/Push/Push.tsx (6)
56-68: LGTM! Efficient deduplication optimization.The Set-based approach correctly reduces complexity from O(n²) to O(n), and the composite key properly accounts for namespace differentiation.
111-193: LGTM! Well-implemented diff computation with cancellation.The centralized useEffect correctly handles async operations with proper cancellation to prevent stale state updates. The comprehensive dependency array ensures recomputation when relevant inputs change, and the early return when
successis true avoids unnecessary work.
398-398: LGTM! Stale closure issue resolved.The functional updater
setUploadedScreenshotCount((c) => c + 1)correctly addresses the stale closure bug mentioned in past review comments. This ensures the count increments properly across async iterations.
413-422: LGTM! Proper state management and cache invalidation.Storing the pushed keys count before clearing the cache and connecting nodes ensures accurate UI display, and clearing the translations cache ensures newly pushed keys are recognized on subsequent operations.
475-489: LGTM! Clear success feedback with accurate counts.The success screen provides immediate feedback using the stored
pushedKeysCountanduploadedScreenshotCount, avoiding the need to wait for diff recomputation. The conditional display based onuploadScreenshotsappropriately reflects the user's choice.
266-268: The ArrayBuffer cast is correct and safe.
screenshot.imageis typed asUint8Array(not Node.js Buffer), and accessing.bufferon aUint8Arrayreturns the underlyingArrayBuffer. The cast is redundant but harmless—the Blob constructor accepts bothArrayBufferand typed arrays likeUint8Arraydirectly.Likely an incorrect or invalid review comment.
There was a problem hiding this 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
♻️ Duplicate comments (1)
src/ui/views/Push/Push.tsx (1)
443-448: Reset uploadedScreenshotCount in handleRepeat.Similar to the past review comment about screenshot count, if a push partially completes (some screenshots uploaded) before failing,
uploadedScreenshotCountretains its value. On retry, the count will be incorrect since it starts from the previous partial value instead of zero.Apply this diff to reset the screenshot count:
const handleRepeat = () => { setError(false); setSuccess(false); setPushedKeysCount(0); + setUploadedScreenshotCount(0); setErrorMessage(undefined); };
🧹 Nitpick comments (1)
src/ui/views/Push/Push.tsx (1)
320-332: Optional: Simplify resolution for changedKeys.Since
changedKeysalways haveoldValue(by definition fromgetPushChanges), the conditional on line 328 will always evaluate to"OVERRIDE". Consider simplifying:translations: { [language]: { text: item.newValue, - resolution: item.oldValue ? "OVERRIDE" : "NEW", + resolution: "OVERRIDE", }, },This is defensive programming and not a bug, but simplification improves clarity.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
cypress/e2e/index.cy.ts(1 hunks)cypress/e2e/push.cy.ts(2 hunks)src/ui/views/Push/Push.tsx(10 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/ui/views/Push/Push.tsx (5)
src/tools/getPushChanges.ts (2)
KeyChanges(17-22)getPushChanges(24-105)src/ui/hooks/useConnectedNodes.ts (1)
useConnectedNodes(8-16)src/types.ts (1)
NodeInfo(74-86)src/ui/hooks/useHasNamespacesEnabled.ts (1)
useHasNamespacesEnabled(3-35)src/main/endpoints/getScreenshots.ts (1)
getScreenshotsEndpoint(9-54)
⏰ 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: Cypress
🔇 Additional comments (18)
cypress/e2e/push.cy.ts (3)
82-115: LGTM! Well-structured test for new keys.The test follows existing patterns, includes clear comments, and appropriately verifies that the success message displays the correct count of pushed keys.
117-150: LGTM! Well-structured test for changed keys.The test follows existing patterns, includes clear comments, and appropriately verifies that the success message displays the correct count of pushed keys.
154-154: Verify test independence.The text change from "On the road" to "Changed text 2" aligns with the new test at line 120 that uses the same key. However, Cypress tests should be isolated and independent. Please confirm that:
- Tests don't share state or depend on execution order
- This change is for clarity/consistency rather than to fix test interdependence
If these tests are truly independent (as they should be), the original text "On the road" should work just as well as "Changed text 2".
Also applies to: 164-164
cypress/e2e/index.cy.ts (2)
25-29: LGTM! Selector and assertion updated correctly for AutocompleteSelect.The change from a
selectdropdown togeneral_namespace_select_inputand the functional assertion with the|| ""fallback appropriately handle the new AutocompleteSelect component behavior, where the value might be null or undefined.
54-54: The selectors in lines 54 and 84-87 are correct and should not be changed.general_node_list_row_namespacetargets a read-only display element in the NodeRow component for connected nodes, whilegeneral_namespace_select_inputtargets an editable AutocompleteSelect input in the NamespaceSelect component for unconnected nodes. These serve different UI purposes and intentionally use different selectors.src/ui/views/Push/Push.tsx (13)
32-32: LGTM! Namespace detection and progress tracking state additions.The new state variables (
isPushing,pushedKeysCount,uploadedScreenshotCount) and theuseHasNamespacesEnabledhook properly support the namespace-aware push flow and progress tracking requirements.Also applies to: 44-44, 47-47, 50-50, 71-71
56-68: Excellent performance optimization!The deduplication logic correctly reduces time complexity from O(n²) to O(n) using a Set, and properly includes namespace in the composite key to avoid conflating identically named keys from different namespaces.
73-110: Well-designed memoization strategy.The memoized keys (
nodesKey,requiredNamespacesKey,screenshotsKey) and extracted primitives fromtolgeeConfigeffectively stabilize the useEffect dependencies, preventing unnecessary recomputations while capturing all relevant changes including namespace updates.
111-193: Robust async effect with proper cancellation.The refactored diff computation properly handles:
- Namespace-aware translation loading
- Cancellation to prevent race conditions
- Comprehensive error handling with specific messages for invalid API keys
- Stable dependencies via memoized keys
The effect safely uses
deduplicatedNodeswithout including it in the deps array because changes tonodesare captured bynodesKey.
195-197: LGTM! Appropriate memoization.The
totalScreenshotCountis correctly memoized from the changes state and used consistently throughout the UI.
255-256: LGTM! Proper counter initialization.The counters are correctly reset before starting the push operation.
266-268: LGTM! Correct ArrayBuffer cast.The cast of
screenshot.image.buffer as ArrayBufferis correct;Uint8Array.bufferreturns anArrayBuffer, which is the proper type forBlobconstruction.
353-411: Excellent error handling with partial success support.The granular error handling allows the push to succeed even if tag application or screenshot updates fail, with clear user-facing messages. Line 398 correctly uses a functional state updater
setUploadedScreenshotCount((c) => c + 1)to avoid stale closure bugs in the async loop.
413-420: LGTM! Proper cache management.The key count is correctly stored before clearing the translation cache, ensuring the success screen displays accurate counts while the cache is refreshed for subsequent operations.
423-436: LGTM! Comprehensive error handling.The enhanced error messages provide specific, actionable feedback for common failure scenarios including invalid API keys, too many images, and import errors.
456-460: LGTM! Correct changesSize computation.Using
pushedKeysCountwhen displaying the success state ensures the UI shows the actual number of keys that were pushed, regardless of any subsequent diff recomputations.
475-489: LGTM! Improved success screen UX.The immediate success screen provides clear feedback with accurate counts, and the
successstate in the useEffect dependencies (line 192) correctly prevents unnecessary recomputation while showing the success message.
490-490: LGTM! Consistent UI updates.The UI consistently uses
totalScreenshotCountand properly includesisPushingin the loading condition, ensuring accurate progress display throughout the push flow.Also applies to: 507-520, 531-532
|
@JanCizmar please merge this, if it's fine :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/ui/views/Push/Push.tsx (2)
147-152: Consider simplifying config reconstruction.The
configForDiffobject spreadstolgeeConfigand then re-assigns the same fields that are already present. You can passtolgeeConfigdirectly togetPushChangesor only override fields that differ from the source.🔎 Simplified approach
- // Reconstruct tolgeeConfig object for getPushChanges - const configForDiff = { - ...tolgeeConfig, - tags: tolgeeConfig?.tags, - updateScreenshots: tolgeeConfigUpdateScreenshots, - addTags: tolgeeConfigAddTags, - }; - setChanges( getPushChanges( deduplicatedNodes, translations, hasNamespacesEnabled, screenshots, - configForDiff + tolgeeConfig ) );
382-411: Functional state updater correctly prevents stale closure.Line 398 correctly uses
setUploadedScreenshotCount((c) => c + 1)to avoid the stale closure bug flagged in previous reviews. The individual try-catch blocks enable partial success when updating screenshots.Minor improvement: If multiple screenshots fail, only the last error message is preserved. Consider accumulating errors or showing a count of failures for better UX.
📜 Review details
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/ui/client/client.tssrc/ui/hooks/useHasNamespacesEnabled.tssrc/ui/views/Push/Push.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/ui/hooks/useHasNamespacesEnabled.ts
⏰ 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: Cypress
🔇 Additional comments (18)
src/ui/client/client.ts (3)
6-6: LGTM: Custom paths import added.The import of
customPathsis used appropriately to extend the API path definitions.
140-146: Critical issue from past review has been fixed!The unreachable condition issue has been correctly resolved. The code now checks if
pathParams.projectIdis undefined (line 142) before assigning to it (line 145), making the conditional logic for URL rewriting work as intended.The current flow is correct:
- Extract projectId from API key
- If projectId exists and wasn't already provided in pathParams, add the placeholder to the URL
- Assign projectId to pathParams for placeholder replacement
Note: Line 145 will overwrite any explicit
pathParams.projectIdwith the value from the API key, which appears intentional as the API key is the source of truth.
127-127: No type conflicts found.The intersection type
paths & customPathsis safe. ThecustomPathsinterface adds the single endpoint"/v2/projects/{projectId}"which does not exist in the generatedpaths, so there are no overlapping keys and no type conflicts.src/ui/views/Push/Push.tsx (15)
56-68: LGTM! Excellent deduplication optimization.The refactoring from O(n²) to O(n) using a Set is a solid performance improvement. The composite key properly accounts for both key and namespace.
71-71: Good addition for namespace-aware logic.The
useHasNamespacesEnabledhook integrates well with the namespace-aware refactoring mentioned in the PR objectives.
74-109: Excellent memoization strategy.The stable keys (nodesKey, requiredNamespacesKey, screenshotsKey) and extracted primitive values from tolgeeConfig are well-designed optimizations that prevent unnecessary recomputation while maintaining correctness.
195-197: LGTM!The memoized
totalScreenshotCountis a good optimization for UI consistency.
255-257: LGTM!Proper initialization of push state counters before starting the push operation.
328-328: LGTM!The conditional resolution logic correctly distinguishes between overriding existing translations and creating new ones based on the presence of
oldValue.
353-380: Good partial-success error handling.The try-catch around tagging allows the push to succeed even if tagging fails, with a clear message to the user. This graceful degradation improves UX when non-critical operations fail.
414-420: LGTM! Proper cache invalidation.Storing the pushed key count before clearing the cache and then calling
clearCache()ensures newly pushed keys will be recognized in subsequent operations.
427-435: LGTM! Enhanced error handling.The specific error cases for
too_many_uploaded_imagesandimport_keys_errorprovide better user feedback than generic error messages.
443-449: LGTM! Past review feedback addressed.The reset logic now correctly includes both
setPushedKeysCount(0)andsetUploadedScreenshotCount(0), addressing the issues flagged in previous reviews about incomplete state resets on retry.
476-490: LGTM! Improved success screen UX.The success screen displays immediately after push completion with clear metrics (pushed keys count and uploaded screenshots count), providing good user feedback without waiting for data recomputation.
491-491: LGTM!Adding
isPushingto the loading condition correctly prevents user interaction during the push operation.
508-521: LGTM!Consistent use of
totalScreenshotCountthroughout the UI improves code clarity and maintainability.
532-544: LGTM!The finish button logic correctly handles the case where there are no changes and either no screenshots or screenshots are not being uploaded.
266-268: The type system already guarantees thatscreenshot.image.bufferis anArrayBuffersincescreenshot.imageis typed asUint8Array. The cast is redundant but does not cause issues.
| useEffect(() => { | ||
| // Don't recompute if we're showing success screen | ||
| if (success) { | ||
| return; | ||
| } | ||
|
|
||
| let cancelled = false; | ||
|
|
||
| async function computeDiff() { | ||
| setLoadingStatus("Loading translations"); | ||
| try { | ||
| // Compute requiredNamespaces inside the effect to avoid dependency issues | ||
| // Include all namespaces from nodes, including empty string for default namespace | ||
| const requiredNamespaces = | ||
| hasNamespacesEnabled && deduplicatedNodes.length > 0 | ||
| ? Array.from( | ||
| new Set(deduplicatedNodes.map((node) => node.ns ?? "")) | ||
| ) | ||
| : undefined; | ||
|
|
||
| const translations = await allTranslationsLoadable.getData({ | ||
| language, | ||
| namespaces: requiredNamespaces, | ||
| }); | ||
|
|
||
| // Check if cancelled before expensive screenshot operation | ||
| if (cancelled) return; | ||
|
|
||
| const screenshots = tolgeeConfigUpdateScreenshots | ||
| ? await getScreenshotsEndpoint.call(nodes) | ||
| : []; | ||
|
|
||
| setChanges( | ||
| getPushChanges( | ||
| deduplicatedNodes, | ||
| translations, | ||
| language, | ||
| screenshots, | ||
| tolgeeConfig | ||
| ) | ||
| ); | ||
| } catch (e) { | ||
| if (e === "invalid_project_api_key") { | ||
| setErrorMessage("Invalid API key"); | ||
| } else { | ||
| setErrorMessage(`Cannot get translation data. ${e}`); | ||
| // Check if cancelled before setting state | ||
| if (cancelled) return; | ||
|
|
||
| // Reconstruct tolgeeConfig object for getPushChanges | ||
| const configForDiff = { | ||
| ...tolgeeConfig, | ||
| tags: tolgeeConfig?.tags, | ||
| updateScreenshots: tolgeeConfigUpdateScreenshots, | ||
| addTags: tolgeeConfigAddTags, | ||
| }; | ||
|
|
||
| setChanges( | ||
| getPushChanges( | ||
| deduplicatedNodes, | ||
| translations, | ||
| hasNamespacesEnabled, | ||
| screenshots, | ||
| configForDiff | ||
| ) | ||
| ); | ||
| } catch (e) { | ||
| if (cancelled) return; | ||
| if (e === "invalid_project_api_key") { | ||
| setErrorMessage("Invalid API key"); | ||
| } else { | ||
| setErrorMessage(`Cannot get translation data. ${e}`); | ||
| } | ||
| } finally { | ||
| if (!cancelled) { | ||
| setLoadingStatus(undefined); | ||
| } | ||
| } | ||
| } finally { | ||
| setLoadingStatus(undefined); | ||
| } | ||
| } | ||
|
|
||
| useEffect(() => { | ||
| computeDiff(); | ||
| }, [nodes.length]); | ||
|
|
||
| // Cleanup: cancel if dependencies change before async operations complete | ||
| return () => { | ||
| cancelled = true; | ||
| }; | ||
| }, [ | ||
| nodesKey, | ||
| screenshotsKey, | ||
| hasNamespacesEnabled, | ||
| language, | ||
| requiredNamespacesKey, | ||
| tolgeeConfigTags, | ||
| tolgeeConfigUpdateScreenshots, | ||
| tolgeeConfigAddTags, | ||
| success, // Include success to prevent recompute when showing success screen | ||
| ]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find the useAllTranslations hook definition
rg "export.*useAllTranslations|const useAllTranslations" --type tsx --type ts -A 10Repository: tolgee/figma-plugin
Length of output: 90
🏁 Script executed:
# Search for deduplicatedNodes definition in Push.tsx
rg "deduplicatedNodes" src/ui/views/Push/Push.tsx -B 3 -A 1Repository: tolgee/figma-plugin
Length of output: 1315
🏁 Script executed:
# Check imports at the top of Push.tsx to understand allTranslationsLoadable
head -50 src/ui/views/Push/Push.tsxRepository: tolgee/figma-plugin
Length of output: 2260
🏁 Script executed:
# Find where allTranslationsLoadable is defined/imported
rg "allTranslationsLoadable" src/ui/views/Push/Push.tsx -B 2 -A 2Repository: tolgee/figma-plugin
Length of output: 805
🏁 Script executed:
# Find useAllTranslations hook definition
rg "useAllTranslations" --type ts -lRepository: tolgee/figma-plugin
Length of output: 289
🏁 Script executed:
# Search for the hook implementation
find . -name "*.ts" -o -name "*.tsx" | xargs grep -l "export.*useAllTranslations" | head -5Repository: tolgee/figma-plugin
Length of output: 99
🏁 Script executed:
# Read the useAllTranslations hook implementation
cat -n src/ui/hooks/useAllTranslations.tsRepository: tolgee/figma-plugin
Length of output: 3741
🏁 Script executed:
# Also check what fields/methods allTranslationsLoadable has
rg "\.getData|\.isLoading|\.clearCache" src/ui/views/Push/Push.tsx | head -10Repository: tolgee/figma-plugin
Length of output: 344
🏁 Script executed:
# Verify if allTranslationsLoadable is used elsewhere and if including it in deps would cause issues
rg "allTranslationsLoadable" src/ui/views/Push/Push.tsx -A 1 -B 1 | head -30Repository: tolgee/figma-plugin
Length of output: 644
🏁 Script executed:
# Check if nodesKey is properly derived from nodes to verify dependency coverage
grep -n "nodesKey" src/ui/views/Push/Push.tsx | head -10Repository: tolgee/figma-plugin
Length of output: 111
🏁 Script executed:
# Check the full nodesKey definition and what it depends on
sed -n '74,85p' src/ui/views/Push/Push.tsxRepository: tolgee/figma-plugin
Length of output: 545
🏁 Script executed:
# Check the full useEffect dependency array
sed -n '180,195p' src/ui/views/Push/Push.tsxRepository: tolgee/figma-plugin
Length of output: 437
Address unstable allTranslationsLoadable dependency.
The effect uses allTranslationsLoadable.getData() (line 131) but allTranslationsLoadable is not in the dependency array. Since useAllTranslations() creates a new object on every render, this violates the exhaustive-deps rule and could cause stale closures. Adding it directly to dependencies would create an infinite loop due to the new object reference. Consider either:
- Memoizing the return value of
useAllTranslations()within the hook, or - Restructuring to avoid the object reference in the effect dependency.
Additionally, the configForDiff object (lines 147-152) redundantly spreads tolgeeConfig then re-adds the tags property that already exists in the spread.
Summary by CodeRabbit
New Features
Bug Fixes
UI/UX Improvements
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.