Skip to content

feat(devtools): add local mode application#167

Open
lourencorodrigo wants to merge 2 commits intomainfrom
feat/devtools-server
Open

feat(devtools): add local mode application#167
lourencorodrigo wants to merge 2 commits intomainfrom
feat/devtools-server

Conversation

@lourencorodrigo
Copy link
Contributor

@lourencorodrigo lourencorodrigo commented Feb 26, 2026

Summary

  • Add Local Mode popup and devtools page that lets developers point the extension to a local application server by entering the app URL, checking its availability, and connecting
  • Add page-storage utilities to read/write the active tab's sessionStorage from the extension context
  • Add popup entry point (popup.html), footer component, and inject-extension-flag content script

Screenshots

Captura de Tela 2026-02-26 às 16 49 29 Captura de Tela 2026-02-26 às 16 49 14

Summary by CodeRabbit

  • New Features

    • Local Mode: full UI to connect/manage local app scripts (pages, quick tips, status messages, copyable IDs)
    • DevTools popup: status-aware popup, popup HTML entry, footer, and toast support
    • New native select and CopyableValue components
    • Page/session storage helpers for tab-based state; tab-focus status reporting and extension badge updates
  • Style

    • Centralized popup theming, dark-mode visuals, and scrollbar/style refinements
  • Chores

    • Version bumped to 1.3.0; updated @radix-ui/react-tabs; removed fmt script and Prettier devDependency

@snyk-io-us
Copy link

snyk-io-us bot commented Feb 26, 2026

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@lourencorodrigo lourencorodrigo changed the title feat(devtools): add application server configuration and local storage utilities feat(devtools): add loca mode application Feb 26, 2026
@lourencorodrigo lourencorodrigo force-pushed the feat/devtools-server branch 2 times, most recently from c8fc351 to 58d2fde Compare February 26, 2026 19:51
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: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/nube-devtools/src/manifest.ts (1)

33-44: ⚠️ Potential issue | 🔴 Critical

Empty matches array prevents script injection.

The web_accessible_resources.matches is an empty array, which blocks all websites from accessing inject-extension-flag.js. However, chrome.scripting.registerContentScripts in background/index.ts registers this script to run in the MAIN world on all ["http://*/*", "https://*/*"] pages. When a content script in the MAIN world injects a .js file, the web page must be able to access that resource—the empty matches array contradicts this requirement and will cause the injection to fail at runtime.

Specify the appropriate match patterns to allow web pages to access the injected script:

🔧 Suggested fix
 	web_accessible_resources: [
 		{
 			resources: [
 				"img/logo-16.png",
 				"img/logo-32.png",
 				"img/logo-48.png",
 				"img/logo-128.png",
 				"inject-extension-flag.js",
 			],
-			matches: [],
+			matches: ["http://*/*", "https://*/*"],
 		},
 	],
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/manifest.ts` around lines 33 - 44, The
web_accessible_resources entry currently has an empty matches array which
prevents the page from loading inject-extension-flag.js when the background
script (see background/index.ts registerContentScripts) injects it into the MAIN
world; update the web_accessible_resources object in manifest.ts (the
web_accessible_resources property that lists "inject-extension-flag.js") to
include appropriate host match patterns (e.g. "http://*/*" and "https://*/*" or
the equivalent allowed patterns) so pages can access the injected script at
runtime.
🧹 Nitpick comments (6)
packages/nube-devtools/src/hooks/use-nube-status.ts (1)

3-14: Consider adding a guard for chrome.tabs consistency.

Line 5 checks typeof chrome !== "undefined" before accessing chrome.devtools, but line 11 directly calls chrome.tabs.query without a similar guard. While this is unlikely to fail in an extension context, adding consistency would improve robustness.

♻️ Optional: Add consistent guard
 const getTabId = (callback: (tabId: number | null) => void) => {
 	if (
 		typeof chrome !== "undefined" &&
 		chrome.devtools?.inspectedWindow?.tabId != null
 	) {
 		callback(chrome.devtools.inspectedWindow.tabId);
 		return;
 	}
+	if (typeof chrome === "undefined" || !chrome.tabs) {
+		callback(null);
+		return;
+	}
 	chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
 		callback(tabs[0]?.id ?? null);
 	});
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/hooks/use-nube-status.ts` around lines 3 - 14, The
getTabId helper accesses chrome.tabs.query without checking chrome or
chrome.tabs; update getTabId to first guard that chrome and chrome.tabs exist
(similar to the existing check for chrome.devtools.inspectedWindow.tabId) before
calling chrome.tabs.query, and if absent call callback(null) (or otherwise
return) so the function safely handles non-extension environments; ensure
references to chrome.devtools.inspectedWindow.tabId, chrome.tabs.query, and the
callback parameter remain intact.
packages/nube-devtools/src/popup/app.tsx (1)

6-14: Consider a loading state to avoid flash of Unavailable page.

Since useNubeStatus initializes to false and updates asynchronously, users may briefly see the Unavailable page before the status check completes. Consider adding a loading state for better UX.

This is a minor UX polish that can be addressed later if desired.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/popup/app.tsx` around lines 6 - 14, AppContent
currently renders Unavailable immediately because useNubeStatus initializes to
false; change the render logic to handle a loading/unknown state so the
Unavailable screen doesn't flash: update AppContent to treat a third "loading"
value (e.g., undefined or null) from useNubeStatus or add a local loading
boolean and render a Loading/Spinner or nothing while status is pending, then
render Unavailable only when status is definitively false and Popup when true;
refer to AppContent, useNubeStatus, Unavailable, and Popup when making the
change.
packages/nube-devtools/src/background/index.ts (1)

21-24: Broad error suppression may hide real issues.

The .catch(() => {}) silently ignores all errors, not just the expected "already registered" error. This could mask genuine issues like permission problems or invalid script paths.

♻️ Consider checking for expected error
 chrome.scripting
 	.registerContentScripts([
 		{
 			id: "nube-devtools-extension-flag",
 			matches: ["http://*/*", "https://*/*"],
 			js: ["inject-extension-flag.js"],
 			runAt: "document_start",
 			world: "MAIN",
 		},
 	])
-	.catch(() => {
-		// Already registered (e.g. service worker restart) — safe to ignore.
-	});
+	.catch((err) => {
+		// Duplicate ID error is expected on service worker restart — safe to ignore.
+		if (!err?.message?.includes("Duplicate script ID")) {
+			console.error("Failed to register extension flag script:", err);
+		}
+	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/background/index.ts` around lines 21 - 24, Replace
the broad silent .catch(() => {}) on the service worker registration promise
with a handler that inspects the thrown error (capture it as err) and only
suppresses the known harmless "already registered" condition (e.g., check
err.message or err.name for the expected text), otherwise surface unexpected
failures by logging or rethrowing (for example: if err.message.includes('already
registered') return; else console.error('Service worker registration failed',
err)). This targets the registration promise/error handling and avoids hiding
real issues while preserving the original intent to ignore benign "already
registered" errors.
packages/nube-devtools/src/pages/local-mode-content.tsx (1)

24-25: Extract the storage key into a shared constant module.

PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION is duplicated across modules (including content script). Centralizing it avoids silent drift bugs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/pages/local-mode-content.tsx` around lines 24 -
25, Extract the duplicated PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION constant into a
single shared constants module and import it where needed instead of redefining
it; update the declaration in local-mode-content.tsx (the const
PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION) to import the shared constant, and remove
the duplicate definitions from other modules (including the content script) so
all code references use the centralized export (e.g., export const
PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION) to prevent drift.
packages/nube-devtools/src/utils/page-storage.ts (1)

18-22: Consider extracting active-tab resolution to a shared helper.

The same active-tab query/guard pattern appears three times; pulling it into one helper will reduce duplication and keep behavior consistent.

Refactor sketch
+async function getActiveTabId(): Promise<number | null> {
+	const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
+	return tab?.id ?? null;
+}
+
 export async function setPageSessionStorage(
 	key: string,
 	value: string,
 ): Promise<boolean> {
-	const [tab] = await chrome.tabs.query({
-		active: true,
-		currentWindow: true,
-	});
-	if (!tab?.id) return false;
+	const tabId = await getActiveTabId();
+	if (tabId == null) return false;
 
 	try {
 		await chrome.scripting.executeScript({
-			target: { tabId: tab.id },
+			target: { tabId },
 			world: "MAIN",
 			func: (k: string, v: string) => {
 				sessionStorage.setItem(k, v);
 			},
 			args: [key, value],
 		});

Also applies to: 46-50, 76-80

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/utils/page-storage.ts` around lines 18 - 22,
Extract the repeated active-tab resolution into a shared helper (e.g., async
function getActiveTabId()) that wraps chrome.tabs.query({ active: true,
currentWindow: true }) and returns the tab id or null/false; replace the three
duplicate patterns (the one that queries chrome.tabs in page-storage.ts at the
three locations) with calls to getActiveTabId() and guard early when it returns
no id. Ensure the helper lives alongside other utilities in the same module and
update all call sites to use the helper instead of duplicating the
chrome.tabs.query + if (!tab?.id) return false pattern.
packages/nube-devtools/src/contentScript/index.ts (1)

34-37: Remove the commented-out initialization block.

Line [34] through Line [37] are dead commented code and make the focus handler noisier than needed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/contentScript/index.ts` around lines 34 - 37,
Remove the dead commented initialization block for the runtime message — delete
the commented chrome.runtime.sendMessage({...}) lines so the focus handler in
index.ts is not cluttered; specifically remove the commented block containing
"chrome.runtime.sendMessage({ action: \"nube-devtools-initialize-sdk\" });" near
the end of the focus handler/closing brace to leave only active code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/nube-devtools/src/devtools/app.tsx`:
- Line 13: The Unavailable guard currently returns <Unavailable /> whenever
nubeStatus is falsy, which prevents the Local Mode flow; update the guard logic
so LocalModePage (route PAGES.LOCAL_MODE) is allowed through even if nubeStatus
is falsy — e.g., check the current route or page first and render LocalModePage
when PAGES.LOCAL_MODE before the nubeStatus short-circuit, or change the
condition to if (!nubeStatus && currentPage !== PAGES.LOCAL_MODE) return
<Unavailable /> so LocalModePage (LocalModePage / PAGES.LOCAL_MODE) is not
blocked.

In `@packages/nube-devtools/src/devtools/index.css`:
- Around line 129-137: Add a blank line before the scrollbar-width declaration
to satisfy Stylelint formatting rules: locate the base layer rule where the
universal selector (*) sets properties (see the block containing `@apply`
border-border outline-ring/50 and the scrollbar-width declaration) and insert an
empty line before the scrollbar-width: thin; declaration (and mirror formatting
consistency for the .dark * block with scrollbar-color if needed).

In `@packages/nube-devtools/src/pages/local-mode-content.tsx`:
- Around line 40-57: The async checkBundle function can suffer from stale
responses overwriting current state; introduce a per-request identity (e.g., a
requestId counter stored in a useRef) that you increment and capture at the
start of checkBundle, then before any state updates (setStatus, setIsChecking)
verify the captured id still matches the latest ref value—only then apply the
state changes; implement the same request-id guard for the other async
bundle/dev-server check in this file that also does a fetch so older responses
cannot override newer URL checks.
- Around line 139-153: The code currently ignores the boolean result from
setPageSessionStorage(PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION,
JSON.stringify(data)); — update the flow in the function that calls
setPageSessionStorage so you await the call, check its returned success value,
and only proceed to setIsConnecting(false), setStatus("connected"),
chrome.runtime.sendMessage({ action: "nube-devtools-app-server-status", payload:
{ connected: true } }) and await reloadCurrentTab() when the write succeeded; on
failure, log or surface the error, set an error/failed status (e.g.,
setStatus("error") or keep disconnected), setIsConnecting(false), and send an
appropriate disconnected/error runtime message instead of declaring connected.
- Around line 157-169: The stop flow currently calls JSON.parse on persisted
data (via getPageSessionStorage and PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION)
without guarding against malformed JSON; wrap the parse in a try/catch inside
the handleStop logic so a JSON parse error doesn’t abort cleanup: if parsing
fails, treat the stored value as absent or create a fresh object, ensure you
still call setPageSessionStorage to clear or set connected=false as appropriate,
and swallow/log the parse error (use the same logger/context used elsewhere) so
the rest of the stop/reload sequence proceeds reliably.

In `@packages/nube-devtools/src/popup/index.css`:
- Around line 138-141: The Biome formatter failed on the CSS block containing
the "body" selector (the rule with "min-width: 23rem;" and "margin: 0;"); run
the Biome formatter (biome format) or adjust spacing so the block follows
project style (proper indentation and spacing) and re-run CI. Specifically, open
the popup index.css rule for body, ensure consistent whitespace and newline
placement around the declarations, save, and commit the formatted file so the
Biome check passes.
- Around line 69-84: There are duplicate sidebar CSS variable declarations: the
oklch set (--sidebar, --sidebar-foreground, --sidebar-primary,
--sidebar-primary-foreground, --sidebar-accent, --sidebar-accent-foreground,
--sidebar-border, --sidebar-ring) is declared first and then redeclared in HSL
later causing unintended overrides; remove the second HSL block so each
--sidebar* variable is defined only once (keep the oklch definitions) and verify
no duplicate --sidebar* variables remain in the file.
- Around line 111-126: The dark theme block duplicates sidebar CSS custom
properties (e.g., --sidebar, --sidebar-foreground, --sidebar-primary,
--sidebar-primary-foreground, --sidebar-accent, --sidebar-accent-foreground,
--sidebar-border, --sidebar-ring, --sidebar-background) using HSL-like values
that override the earlier OKLCH declarations; remove the duplicate HSL-style
declarations (the second set) so only the intended OKLCH sidebar variables
remain, mirroring the fix applied to the light theme and ensuring no duplicate
custom-property declarations exist for those --sidebar* names.

In `@packages/nube-devtools/src/popup/popup.tsx`:
- Around line 22-25: The main element wrapping LocalModeContent is described as
"scrollable" but lacks an overflow class, so content can be clipped; update the
<main> element's className (the one containing "flex-1 flex justify-center p-3")
to include an appropriate overflow utility (e.g., overflow-auto or
overflow-y-auto) so the LocalModeContent can scroll when it exceeds popup height
constraints.

In `@packages/nube-devtools/src/utils/page-storage.ts`:
- Around line 11-12: The JSDoc for setPageSessionStorage claims non-strings are
stringified but the function signature and implementation only accept and pass a
string; update the contract by either (A) changing the JSDoc to state value must
be a string, or (B) change setPageSessionStorage to accept value: unknown (or
any) and stringify non-string values before sending to the page script (ensure
the injected script uses sessionStorage.setItem with the stringified value).
Locate the setPageSessionStorage function and the code that constructs the
script payload to implement the chosen fix so docs and runtime behavior match.

---

Outside diff comments:
In `@packages/nube-devtools/src/manifest.ts`:
- Around line 33-44: The web_accessible_resources entry currently has an empty
matches array which prevents the page from loading inject-extension-flag.js when
the background script (see background/index.ts registerContentScripts) injects
it into the MAIN world; update the web_accessible_resources object in
manifest.ts (the web_accessible_resources property that lists
"inject-extension-flag.js") to include appropriate host match patterns (e.g.
"http://*/*" and "https://*/*" or the equivalent allowed patterns) so pages can
access the injected script at runtime.

---

Nitpick comments:
In `@packages/nube-devtools/src/background/index.ts`:
- Around line 21-24: Replace the broad silent .catch(() => {}) on the service
worker registration promise with a handler that inspects the thrown error
(capture it as err) and only suppresses the known harmless "already registered"
condition (e.g., check err.message or err.name for the expected text), otherwise
surface unexpected failures by logging or rethrowing (for example: if
err.message.includes('already registered') return; else console.error('Service
worker registration failed', err)). This targets the registration promise/error
handling and avoids hiding real issues while preserving the original intent to
ignore benign "already registered" errors.

In `@packages/nube-devtools/src/contentScript/index.ts`:
- Around line 34-37: Remove the dead commented initialization block for the
runtime message — delete the commented chrome.runtime.sendMessage({...}) lines
so the focus handler in index.ts is not cluttered; specifically remove the
commented block containing "chrome.runtime.sendMessage({ action:
\"nube-devtools-initialize-sdk\" });" near the end of the focus handler/closing
brace to leave only active code.

In `@packages/nube-devtools/src/hooks/use-nube-status.ts`:
- Around line 3-14: The getTabId helper accesses chrome.tabs.query without
checking chrome or chrome.tabs; update getTabId to first guard that chrome and
chrome.tabs exist (similar to the existing check for
chrome.devtools.inspectedWindow.tabId) before calling chrome.tabs.query, and if
absent call callback(null) (or otherwise return) so the function safely handles
non-extension environments; ensure references to
chrome.devtools.inspectedWindow.tabId, chrome.tabs.query, and the callback
parameter remain intact.

In `@packages/nube-devtools/src/pages/local-mode-content.tsx`:
- Around line 24-25: Extract the duplicated
PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION constant into a single shared constants
module and import it where needed instead of redefining it; update the
declaration in local-mode-content.tsx (the const
PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION) to import the shared constant, and remove
the duplicate definitions from other modules (including the content script) so
all code references use the centralized export (e.g., export const
PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION) to prevent drift.

In `@packages/nube-devtools/src/popup/app.tsx`:
- Around line 6-14: AppContent currently renders Unavailable immediately because
useNubeStatus initializes to false; change the render logic to handle a
loading/unknown state so the Unavailable screen doesn't flash: update AppContent
to treat a third "loading" value (e.g., undefined or null) from useNubeStatus or
add a local loading boolean and render a Loading/Spinner or nothing while status
is pending, then render Unavailable only when status is definitively false and
Popup when true; refer to AppContent, useNubeStatus, Unavailable, and Popup when
making the change.

In `@packages/nube-devtools/src/utils/page-storage.ts`:
- Around line 18-22: Extract the repeated active-tab resolution into a shared
helper (e.g., async function getActiveTabId()) that wraps chrome.tabs.query({
active: true, currentWindow: true }) and returns the tab id or null/false;
replace the three duplicate patterns (the one that queries chrome.tabs in
page-storage.ts at the three locations) with calls to getActiveTabId() and guard
early when it returns no id. Ensure the helper lives alongside other utilities
in the same module and update all call sites to use the helper instead of
duplicating the chrome.tabs.query + if (!tab?.id) return false pattern.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27f6920 and 4d48a7b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (29)
  • packages/nube-devtools/package.json
  • packages/nube-devtools/popup.html
  • packages/nube-devtools/public/inject-extension-flag.js
  • packages/nube-devtools/src/background/index.ts
  • packages/nube-devtools/src/background/nube-dev-tools.ts
  • packages/nube-devtools/src/background/scripts/index.ts
  • packages/nube-devtools/src/background/scripts/inject-window-variable.ts
  • packages/nube-devtools/src/components/copyable-value.tsx
  • packages/nube-devtools/src/components/footer.tsx
  • packages/nube-devtools/src/contentScript/index.ts
  • packages/nube-devtools/src/contexts/navigation-context.tsx
  • packages/nube-devtools/src/devtools/app.tsx
  • packages/nube-devtools/src/devtools/components/app-detail-panel.tsx
  • packages/nube-devtools/src/devtools/components/app-sidebar.tsx
  • packages/nube-devtools/src/devtools/index.css
  • packages/nube-devtools/src/devtools/pages/LocalMode.tsx
  • packages/nube-devtools/src/devtools/pages/errors.tsx
  • packages/nube-devtools/src/devtools/pages/events.tsx
  • packages/nube-devtools/src/devtools/pages/storages.tsx
  • packages/nube-devtools/src/devtools/pages/svg-converter.tsx
  • packages/nube-devtools/src/hooks/use-nube-status.ts
  • packages/nube-devtools/src/manifest.ts
  • packages/nube-devtools/src/pages/local-mode-content.tsx
  • packages/nube-devtools/src/popup/app.tsx
  • packages/nube-devtools/src/popup/index.css
  • packages/nube-devtools/src/popup/index.tsx
  • packages/nube-devtools/src/popup/popup.tsx
  • packages/nube-devtools/src/utils/index.ts
  • packages/nube-devtools/src/utils/page-storage.ts
💤 Files with no reviewable changes (4)
  • packages/nube-devtools/src/background/scripts/index.ts
  • packages/nube-devtools/src/background/nube-dev-tools.ts
  • packages/nube-devtools/src/background/scripts/inject-window-variable.ts
  • packages/nube-devtools/package.json

@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e85b3415-99fd-4e7d-8285-9c92c64b2a7e

📥 Commits

Reviewing files that changed from the base of the PR and between e7960d4 and 7d1c717.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (1)
  • packages/nube-devtools/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/nube-devtools/package.json

Walkthrough

Adds a Local Mode UI and popup, introduces page-sessionStorage utilities, replaces executeScript-based window-variable injection with a registered content script flag, updates background messaging and badge logic, and introduces multiple new UI components and styling changes across the devtools package.

Changes

Cohort / File(s) Summary
Package & Manifest
packages/nube-devtools/package.json, packages/nube-devtools/src/manifest.ts, packages/nube-devtools/popup.html
Bumped package version to 1.3.0; removed scripts.fmt and devDependencies.prettier; updated @radix-ui/react-tabs. Added action popup config and included inject-extension-flag.js as a web_accessible_resource; added popup.html.
Extension flag & background
packages/nube-devtools/public/inject-extension-flag.js, packages/nube-devtools/src/background/index.ts, packages/nube-devtools/src/background/nube-dev-tools.ts, packages/nube-devtools/src/background/scripts/*
Added content script inject-extension-flag.js and register it via chrome.scripting.registerContentScripts. Removed injectWindowVariable and handleDevToolsInjectWindowVariable re-exports/usage. Added badge update logic responding to nube-devtools-app-server-status.
Content script & status flow
packages/nube-devtools/src/contentScript/index.ts, packages/nube-devtools/src/hooks/use-nube-status.ts
Shifted to focus-driven status reporting (onTabFocus) and added PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION/sendAppServerStatusToBadge. Refactored status hook to obtain tabId via helper with chrome.tabs.query fallback and null checks.
Popup entry & UI
packages/nube-devtools/src/popup/index.tsx, packages/nube-devtools/src/popup/app.tsx, packages/nube-devtools/src/popup/popup.tsx, packages/nube-devtools/src/popup/index.css, packages/nube-devtools/popup.html, packages/nube-devtools/src/popup/components/popup-footer.tsx
Added React popup bootstrap, App entry using useNubeStatus, themed popup CSS (light/dark variables and animations), popup UI component, and footer displaying extension version.
Local Mode feature
packages/nube-devtools/src/components/local-mode-content.tsx, packages/nube-devtools/src/devtools/pages/LocalMode.tsx, packages/nube-devtools/src/devtools/app.tsx, packages/nube-devtools/src/contexts/navigation-context.tsx, packages/nube-devtools/src/devtools/components/app-sidebar.tsx
Introduced Local Mode page, registered PAGES.LOCAL_MODE, added sidebar entry, and implemented Local Mode content (connect/disconnect, script polling, persisted state).
Local Mode support components
packages/nube-devtools/src/components/local-mode-card-header.tsx, .../local-mode-quick-tips.tsx, .../local-mode-status-messages.tsx
Added Local Mode header, quick tips, and status messages (including ScriptAvailability type and App ID copy UI).
Copy & form UI
packages/nube-devtools/src/components/copyable-value.tsx, packages/nube-devtools/src/components/ui/native-select.tsx
Added CopyableValue component (clipboard + toast) and NativeSelect family (NativeSelect, NativeSelectOption, NativeSelectOptGroup) for native form controls.
SessionStorage utilities
packages/nube-devtools/src/utils/page-storage.ts, packages/nube-devtools/src/utils/index.ts
Added setPageSessionStorage, getPageSessionStorage, removePageSessionStorage that execute MAIN-world scripts on the active tab; re-exported them from utils index.
Devtools panels & props
packages/nube-devtools/src/devtools/components/app-detail-panel.tsx, packages/nube-devtools/src/devtools/pages/apps.tsx
Replaced inline copy with CopyableValue, added getScriptStatusLabel, extended AppDetailPanelProps with script and scriptStatus, and updated Apps page to read local-mode storage and render Local Mode / Replaced Script / script-status badges.
Styling & scrollbars
packages/nube-devtools/src/devtools/index.css, packages/nube-devtools/src/devtools/pages/errors.tsx, .../events.tsx, .../storages.tsx, .../svg-converter.tsx
Centralized scrollbar styling in global CSS and removed per-page WebKit scrollbar overrides; adjusted SyntaxHighlighter styling in svg-converter.
Miscellaneous
packages/nube-devtools/src/components/copyable-value.tsx, packages/nube-devtools/src/popup/index.tsx
Added popup bootstrap and reusable small components referenced by popup and devtools UI.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.71% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main feature addition: a local mode application for the devtools. It matches the substantial changes across popup, pages, and utility components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/devtools-server

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: 6

🧹 Nitpick comments (1)
packages/nube-devtools/src/contentScript/index.ts (1)

7-8: Extract the storage key to a shared constant module.

This key is duplicated in multiple files; a future rename in one place can silently break sync between UI and content script.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/contentScript/index.ts` around lines 7 - 8, The
constant PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION is duplicated; extract it into a
single shared constants module (e.g., export a named constant like
PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION from a new shared/constants or similar
module) and replace the local declaration in contentScript/index.ts with an
import of that shared constant; also update the other places that currently
duplicate the string to import the same exported constant so all code references
use the single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/nube-devtools/src/background/index.ts`:
- Around line 20-22: The current blanket catch on the registerContentScripts
call hides all failures; change the catch to inspect the thrown error from
registerContentScripts (e.g., error.message or error.code) and only suppress /
ignore the specific duplicate-registration error (match text like "already
registered" or the platform duplicate-id code), while logging and rethrowing (or
at least not swallowing) all other errors so missing files/permission issues
surface and the flag injection can proceed; update the catch block adjacent to
the registerContentScripts invocation in
packages/nube-devtools/src/background/index.ts to implement this selective
filtering and add a clear error log for non-duplicate failures.

In `@packages/nube-devtools/src/devtools/index.css`:
- Around line 130-134: Add a blank line before the declaration inside the
universal selector to satisfy Stylelint's declaration-empty-line-before rule:
inside the "*" rule block (the selector "*"), insert an empty line before the
"scrollbar-width: thin;" declaration so there's a separation between the "@apply
border-border outline-ring/50;" line and the "scrollbar-width" property.

In `@packages/nube-devtools/src/pages/local-mode-content.tsx`:
- Around line 40-57: The checkBundle function (and the similar check at lines
73-76) can suffer from out-of-order async responses overwriting state; fix by
stamping each check with a unique identifier (e.g., capture the url or
incrementing requestId in a ref) before the fetch, store that identifier as the
"latest" on a ref (like latestCheckUrlRef or latestRequestIdRef), and only call
setStatus / setIsChecking in the try/catch/finally if the identifier still
matches the latest; this ensures stale responses from earlier checkBundle
invocations do not override the current status.
- Around line 161-163: Wrap the JSON.parse(data) call in a try/catch inside the
stop/disconnect flow so a malformed session payload doesn't throw and abort the
disconnect; when parsing fails, log or handle the error and continue with
disconnect logic (e.g., treat as not connected), otherwise proceed to modify
dataObj.connected = false and continue the existing flow — update the block
around JSON.parse(data) and dataObj to use this safe parsing pattern referencing
JSON.parse and dataObj.
- Around line 139-148: The code marks the UI as connected regardless of whether
setPageSessionStorage succeeded; update the flow in the block that calls
setPageSessionStorage (using PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION) to check its
return value and only proceed to setIsConnecting(false) and
setStatus("connected") (and any reload) when setPageSessionStorage returns
truthy; if it returns false, setIsConnecting(false), setStatus("disconnected")
(or an error state/message) and abort the connected flow so the UI does not
display a false connected state.

In `@packages/nube-devtools/src/popup/index.css`:
- Around line 69-84: There are duplicate CSS custom properties for the sidebar
palette (--sidebar, --sidebar-foreground, --sidebar-primary,
--sidebar-primary-foreground, --sidebar-accent, --sidebar-accent-foreground,
--sidebar-border, --sidebar-ring, --sidebar-background) which override the
intended values and trigger Stylelint duplicate-property errors; remove the
repeated declarations so each custom property is defined only once per scope
(keep the intended palette set you want and delete the later conflicting sets),
ensuring the remaining declarations for the identifiers above reflect the
correct color format (oklch vs HSL/percent) and consistent naming so no
duplicate-property lint warnings occur.

---

Nitpick comments:
In `@packages/nube-devtools/src/contentScript/index.ts`:
- Around line 7-8: The constant PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION is
duplicated; extract it into a single shared constants module (e.g., export a
named constant like PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION from a new
shared/constants or similar module) and replace the local declaration in
contentScript/index.ts with an import of that shared constant; also update the
other places that currently duplicate the string to import the same exported
constant so all code references use the single source of truth.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d48a7b and f6c6dca.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (29)
  • packages/nube-devtools/package.json
  • packages/nube-devtools/popup.html
  • packages/nube-devtools/public/inject-extension-flag.js
  • packages/nube-devtools/src/background/index.ts
  • packages/nube-devtools/src/background/nube-dev-tools.ts
  • packages/nube-devtools/src/background/scripts/index.ts
  • packages/nube-devtools/src/background/scripts/inject-window-variable.ts
  • packages/nube-devtools/src/components/copyable-value.tsx
  • packages/nube-devtools/src/components/footer.tsx
  • packages/nube-devtools/src/contentScript/index.ts
  • packages/nube-devtools/src/contexts/navigation-context.tsx
  • packages/nube-devtools/src/devtools/app.tsx
  • packages/nube-devtools/src/devtools/components/app-detail-panel.tsx
  • packages/nube-devtools/src/devtools/components/app-sidebar.tsx
  • packages/nube-devtools/src/devtools/index.css
  • packages/nube-devtools/src/devtools/pages/LocalMode.tsx
  • packages/nube-devtools/src/devtools/pages/errors.tsx
  • packages/nube-devtools/src/devtools/pages/events.tsx
  • packages/nube-devtools/src/devtools/pages/storages.tsx
  • packages/nube-devtools/src/devtools/pages/svg-converter.tsx
  • packages/nube-devtools/src/hooks/use-nube-status.ts
  • packages/nube-devtools/src/manifest.ts
  • packages/nube-devtools/src/pages/local-mode-content.tsx
  • packages/nube-devtools/src/popup/app.tsx
  • packages/nube-devtools/src/popup/index.css
  • packages/nube-devtools/src/popup/index.tsx
  • packages/nube-devtools/src/popup/popup.tsx
  • packages/nube-devtools/src/utils/index.ts
  • packages/nube-devtools/src/utils/page-storage.ts
💤 Files with no reviewable changes (4)
  • packages/nube-devtools/src/background/scripts/index.ts
  • packages/nube-devtools/package.json
  • packages/nube-devtools/src/background/nube-dev-tools.ts
  • packages/nube-devtools/src/background/scripts/inject-window-variable.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • packages/nube-devtools/src/popup/popup.tsx
  • packages/nube-devtools/src/components/copyable-value.tsx
  • packages/nube-devtools/src/hooks/use-nube-status.ts
  • packages/nube-devtools/src/popup/index.tsx
  • packages/nube-devtools/src/manifest.ts
  • packages/nube-devtools/src/contexts/navigation-context.tsx
  • packages/nube-devtools/src/devtools/pages/svg-converter.tsx
  • packages/nube-devtools/src/utils/page-storage.ts
  • packages/nube-devtools/src/devtools/pages/events.tsx
  • packages/nube-devtools/popup.html
  • packages/nube-devtools/public/inject-extension-flag.js
  • packages/nube-devtools/src/components/footer.tsx

@lourencorodrigo lourencorodrigo changed the title feat(devtools): add loca mode application feat(devtools): add local mode application Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 26, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 27, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Feb 27, 2026
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: 6

🧹 Nitpick comments (2)
packages/nube-devtools/src/devtools/pages/svg-converter.tsx (1)

154-154: Remove the empty className prop from SyntaxHighlighter.

Line 154 contains className="", which is a no-op and non-idiomatic. Omit the prop entirely unless applying an actual CSS class.

Suggested diff
-									className=""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/devtools/pages/svg-converter.tsx` at line 154,
Remove the no-op empty className attribute from the SyntaxHighlighter usage in
svg-converter.tsx: locate the JSX element <SyntaxHighlighter ... className="">
and delete the className="" prop entirely (leave other props intact) so the
component is not passed an empty string class.
packages/nube-devtools/src/components/ui/native-select.tsx (1)

10-29: className currently styles the wrapper, not the <select> control.

This makes consumer styling for the actual input element harder/unpredictable. Consider splitting wrapper/select class props.

♻️ Suggested refactor
 type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
 	size?: "sm" | "default";
+	wrapperClassName?: string;
 };
 
 function NativeSelect({
 	className,
+	wrapperClassName,
 	size = "default",
 	...props
 }: NativeSelectProps) {
 	return (
 		<div
 			className={cn(
 				"group/native-select relative w-fit has-[select:disabled]:opacity-50",
-				className,
+				wrapperClassName,
 			)}
 			data-slot="native-select-wrapper"
 			data-size={size}
 		>
 			<select
 				data-slot="native-select"
 				data-size={size}
-				className="border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 w-full min-w-0 appearance-none rounded-lg border bg-transparent py-1 pr-8 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-[size=sm]:py-0.5 outline-none disabled:pointer-events-none disabled:cursor-not-allowed"
+				className={cn(
+					"border-input placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-8 w-full min-w-0 appearance-none rounded-lg border bg-transparent py-1 pr-8 pl-2.5 text-sm transition-colors select-none focus-visible:ring-3 aria-invalid:ring-3 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] data-[size=sm]:py-0.5 outline-none disabled:pointer-events-none disabled:cursor-not-allowed",
+					className,
+				)}
 				{...props}
 			/>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/components/ui/native-select.tsx` around lines 10 -
29, The current NativeSelect component applies the incoming className to the
wrapper div instead of the <select>, which prevents consumers from styling the
actual control; update the component API and implementation so className targets
the <select> (or add a new prop like wrapperClassName for the outer div and keep
className for the select). Concretely, change NativeSelectProps to include
wrapperClassName?: string and className?: string (or swap semantics so className
is applied to the select), then move the cn(...) usage: apply wrapperClassName
to the div's className and apply the composed select classes plus className to
the <select> element (ensure data-size handling and existing default select
classes in NativeSelect are preserved).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/nube-devtools/src/components/copyable-value.tsx`:
- Around line 9-13: The handleCopy function (navigator.clipboard.writeText call
inside handleCopy) lacks error handling and can produce unhandled Promise
rejections; update handleCopy to handle both success and failure by adding a
.catch (or try/catch if converting to async) to call toast.error with a helpful
message on failure and optionally implement a fallback copy approach (e.g.,
legacy document.execCommand or creating a temporary textarea) for environments
where navigator.clipboard is unavailable; ensure the change references the
handleCopy callback in copyable-value.tsx so the Promise is always handled.

In `@packages/nube-devtools/src/components/local-mode-content.tsx`:
- Around line 300-325: markStoredAsDisconnected can throw on JSON.parse which
will abort handleStop before UI/state cleanup; wrap the read/parse/update
sequence in markStoredAsDisconnected with a try/catch that handles malformed
JSON (e.g., treat as absent or overwrite with a safe disconnected payload) and
never rethrow so callers like handleStop always continue. Update
markStoredAsDisconnected to catch parsing errors, optionally remove or overwrite
the PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION entry with a known disconnected
object, and ensure await setPageSessionStorage is still called when appropriate;
leave handleStop unchanged so its subsequent state resets always run even if
stored data was malformed.
- Around line 229-235: reloadCurrentTab currently reloads the active tab via
chrome.tabs.query which can target the wrong tab in DevTools; change
reloadCurrentTab to first check for chrome.devtools?.inspectedWindow?.tabId and
call chrome.tabs.reload with that id if present, otherwise fall back to the
existing chrome.tabs.query active/currentWindow logic and reload the found tab;
update references in reloadCurrentTab (chrome.devtools.inspectedWindow.tabId,
chrome.tabs.query, chrome.tabs.reload) accordingly to preserve behavior when
devtools isn't available.

In `@packages/nube-devtools/src/devtools/index.css`:
- Around line 130-137: Stylelint flagged a missing empty line before the
scrollbar-width declaration in the global rule; open the rule for the universal
selector (*) where you set `@apply` border-border outline-ring/50 and insert a
single blank line immediately before the scrollbar-width: thin; declaration (the
declaration within the "*" rule that precedes scrollbar-color), then save/format
so the same spacing is applied consistently for the scrollbar-color lines
(including the .dark * block) to satisfy Stylelint.

In `@packages/nube-devtools/src/popup/index.css`:
- Around line 69-84: Duplicate CSS custom properties for the sidebar are defined
twice (the oklch() set and a later HSL-like numeric set) causing silent
overrides and Stylelint failures; remove the redundant redefinitions so each
--sidebar custom property is declared only once (keep either the oklch(...)
definitions or the numeric HSL-like definitions consistently), e.g., remove the
second group of --sidebar, --sidebar-foreground, --sidebar-primary,
--sidebar-primary-foreground, --sidebar-accent, --sidebar-accent-foreground,
--sidebar-border, and --sidebar-ring declarations in
packages/nube-devtools/src/popup/index.css so each variable is uniquely defined.
- Around line 4-6: Stylelint is flagging Tailwind v4 directives (`@custom-variant`
and `@theme`) as unknown; update the Stylelint config (.stylelintrc.json) to allow
these at-rules by adding an ignoreAtRules entry to the at-rule-no-unknown
rule—specifically modify the at-rule-no-unknown configuration to include
"custom-variant", "theme" (and "tailwind" if desired) in ignoreAtRules so the
directives used in index.css are accepted.

---

Nitpick comments:
In `@packages/nube-devtools/src/components/ui/native-select.tsx`:
- Around line 10-29: The current NativeSelect component applies the incoming
className to the wrapper div instead of the <select>, which prevents consumers
from styling the actual control; update the component API and implementation so
className targets the <select> (or add a new prop like wrapperClassName for the
outer div and keep className for the select). Concretely, change
NativeSelectProps to include wrapperClassName?: string and className?: string
(or swap semantics so className is applied to the select), then move the cn(...)
usage: apply wrapperClassName to the div's className and apply the composed
select classes plus className to the <select> element (ensure data-size handling
and existing default select classes in NativeSelect are preserved).

In `@packages/nube-devtools/src/devtools/pages/svg-converter.tsx`:
- Line 154: Remove the no-op empty className attribute from the
SyntaxHighlighter usage in svg-converter.tsx: locate the JSX element
<SyntaxHighlighter ... className=""> and delete the className="" prop entirely
(leave other props intact) so the component is not passed an empty string class.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f6c6dca and 6623dc1.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (34)
  • packages/nube-devtools/package.json
  • packages/nube-devtools/popup.html
  • packages/nube-devtools/public/inject-extension-flag.js
  • packages/nube-devtools/src/background/index.ts
  • packages/nube-devtools/src/background/nube-dev-tools.ts
  • packages/nube-devtools/src/background/scripts/index.ts
  • packages/nube-devtools/src/background/scripts/inject-window-variable.ts
  • packages/nube-devtools/src/components/copyable-value.tsx
  • packages/nube-devtools/src/components/local-mode-card-header.tsx
  • packages/nube-devtools/src/components/local-mode-content.tsx
  • packages/nube-devtools/src/components/local-mode-quick-tips.tsx
  • packages/nube-devtools/src/components/local-mode-status-messages.tsx
  • packages/nube-devtools/src/components/ui/native-select.tsx
  • packages/nube-devtools/src/contentScript/index.ts
  • packages/nube-devtools/src/contexts/navigation-context.tsx
  • packages/nube-devtools/src/devtools/app.tsx
  • packages/nube-devtools/src/devtools/components/app-detail-panel.tsx
  • packages/nube-devtools/src/devtools/components/app-sidebar.tsx
  • packages/nube-devtools/src/devtools/index.css
  • packages/nube-devtools/src/devtools/pages/LocalMode.tsx
  • packages/nube-devtools/src/devtools/pages/apps.tsx
  • packages/nube-devtools/src/devtools/pages/errors.tsx
  • packages/nube-devtools/src/devtools/pages/events.tsx
  • packages/nube-devtools/src/devtools/pages/storages.tsx
  • packages/nube-devtools/src/devtools/pages/svg-converter.tsx
  • packages/nube-devtools/src/hooks/use-nube-status.ts
  • packages/nube-devtools/src/manifest.ts
  • packages/nube-devtools/src/popup/app.tsx
  • packages/nube-devtools/src/popup/components/popup-footer.tsx
  • packages/nube-devtools/src/popup/index.css
  • packages/nube-devtools/src/popup/index.tsx
  • packages/nube-devtools/src/popup/popup.tsx
  • packages/nube-devtools/src/utils/index.ts
  • packages/nube-devtools/src/utils/page-storage.ts
💤 Files with no reviewable changes (3)
  • packages/nube-devtools/src/background/scripts/inject-window-variable.ts
  • packages/nube-devtools/src/background/nube-dev-tools.ts
  • packages/nube-devtools/src/background/scripts/index.ts
🚧 Files skipped from review as they are similar to previous changes (17)
  • packages/nube-devtools/src/contexts/navigation-context.tsx
  • packages/nube-devtools/src/popup/app.tsx
  • packages/nube-devtools/src/utils/page-storage.ts
  • packages/nube-devtools/src/devtools/pages/errors.tsx
  • packages/nube-devtools/src/hooks/use-nube-status.ts
  • packages/nube-devtools/src/popup/popup.tsx
  • packages/nube-devtools/package.json
  • packages/nube-devtools/src/contentScript/index.ts
  • packages/nube-devtools/src/devtools/pages/LocalMode.tsx
  • packages/nube-devtools/src/manifest.ts
  • packages/nube-devtools/src/popup/index.tsx
  • packages/nube-devtools/src/devtools/pages/storages.tsx
  • packages/nube-devtools/popup.html
  • packages/nube-devtools/src/utils/index.ts
  • packages/nube-devtools/src/devtools/components/app-detail-panel.tsx
  • packages/nube-devtools/src/devtools/app.tsx
  • packages/nube-devtools/src/background/index.ts

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

♻️ Duplicate comments (4)
packages/nube-devtools/src/devtools/index.css (1)

131-133: ⚠️ Potential issue | 🟡 Minor

Fix Stylelint spacing before scrollbar-width.

At Line 132, add a blank line between @apply and scrollbar-width to satisfy declaration-empty-line-before.

🧹 Proposed fix
 	* {
 		`@apply` border-border outline-ring/50;
+
 		scrollbar-width: thin;
 		scrollbar-color: oklch(0.7 0 0 / 30%) transparent;
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/devtools/index.css` around lines 131 - 133, Add a
single blank line before the declaration "scrollbar-width: thin;" so there is an
empty line between the "@apply border-border outline-ring/50;" rule and the
"scrollbar-width" declaration to satisfy the declaration-empty-line-before
Stylelint rule; update the CSS block containing "@apply border-border
outline-ring/50;", "scrollbar-width: thin;", and "scrollbar-color: oklch(0.7 0 0
/ 30%) transparent;" by inserting one empty line between the `@apply` line and the
scrollbar-width line.
packages/nube-devtools/src/components/copyable-value.tsx (1)

9-13: ⚠️ Potential issue | 🟡 Minor

Handle clipboard write failures to prevent unhandled Promise rejections.

The navigator.clipboard.writeText() call lacks error handling. If the operation fails (e.g., due to permissions or unavailable context), the Promise rejection goes unhandled and the user receives no feedback.

🛡️ Proposed fix
-	const handleCopy = useCallback(() => {
-		navigator.clipboard.writeText(value).then(() => {
-			toast.success("Copied to clipboard");
-		});
-	}, [value]);
+	const handleCopy = useCallback(async () => {
+		try {
+			await navigator.clipboard.writeText(value);
+			toast.success("Copied to clipboard");
+		} catch {
+			toast.error("Unable to copy to clipboard");
+		}
+	}, [value]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/components/copyable-value.tsx` around lines 9 -
13, The handleCopy callback currently calls navigator.clipboard.writeText(value)
without handling rejections; update handleCopy (the useCallback function) to
catch errors from navigator.clipboard.writeText(value) and provide user feedback
and logging on failure (e.g., call toast.error with a helpful message and
optionally console.error the error), ensuring both the success path still calls
toast.success and the failure path prevents unhandled Promise rejections.
packages/nube-devtools/src/components/local-mode-content.tsx (2)

290-304: ⚠️ Potential issue | 🟠 Major

Malformed stored JSON can break Stop flow before state cleanup runs.

JSON.parse at line 296 can throw on malformed data, preventing handleStop from completing its state reset. The loadStoredServer function (lines 215-220) correctly wraps its JSON.parse in try-catch, but this function doesn't.

🛡️ Suggested hardening
 const markStoredAsDisconnected = async () => {
 	const stored = await getPageSessionStorage(
 		PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION,
 	);
 	if (!stored) return;

-	const data = JSON.parse(stored) as StoredApplicationServerData;
-	if (!data.connected) return;
-
-	data.connected = false;
-	await setPageSessionStorage(
-		PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION,
-		JSON.stringify(data),
-	);
+	try {
+		const data = JSON.parse(stored) as StoredApplicationServerData;
+		if (!data.connected) return;
+		data.connected = false;
+		await setPageSessionStorage(
+			PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION,
+			JSON.stringify(data),
+		);
+	} catch {
+		// Ignore malformed payload; caller cleanup continues.
+	}
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/components/local-mode-content.tsx` around lines
290 - 304, The markStoredAsDisconnected handler can throw on malformed stored
JSON because it calls JSON.parse directly; wrap the parse in a try-catch inside
markStoredAsDisconnected (the function using getPageSessionStorage, JSON.parse,
and setPageSessionStorage) so parse failures do not abort the stop flow—on
error, treat it as already-disconnected (or remove/clear
PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION) and continue, ensuring data.connected is
only updated when parse succeeds and the function always returns without
throwing.

225-231: ⚠️ Potential issue | 🟠 Major

Reload should prioritize the inspected tab, not only the active tab query.

In DevTools workflows, the active tab may differ from the inspected tab. Use chrome.devtools.inspectedWindow.tabId as the primary target, consistent with the pattern used in fetchApps (lines 108-119).

🔧 Suggested fix
 const reloadCurrentTab = async () => {
+	const inspectedTabId = chrome.devtools?.inspectedWindow?.tabId;
+	if (inspectedTabId != null) {
+		chrome.tabs.reload(inspectedTabId);
+		return;
+	}
 	const [tab] = await chrome.tabs.query({
 		active: true,
 		currentWindow: true,
 	});
-	if (tab?.id) chrome.tabs.reload(tab.id);
+	if (tab?.id != null) chrome.tabs.reload(tab.id);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/components/local-mode-content.tsx` around lines
225 - 231, The reloadCurrentTab function currently queries the active tab but
should prioritize the DevTools inspected tab; update reloadCurrentTab to first
read chrome.devtools.inspectedWindow.tabId and, if it's a number, call
chrome.tabs.reload with that id, otherwise fall back to the existing
chrome.tabs.query active/currentWindow lookup and reload the resulting tab id;
keep the existing null/undefined checks (tab?.id) and ensure usage mirrors the
pattern in fetchApps by treating inspectedWindow.tabId as the primary target.
🧹 Nitpick comments (4)
packages/nube-devtools/src/components/local-mode-content.tsx (2)

270-281: Connection data persists connected: true before script verification completes.

The storage is set to connected: true (line 272) before checkScriptAvailability runs. If the script is unavailable, the stored state indicates "connected" while the UI shows "unavailable". This inconsistency could cause confusion on page reload. Consider updating the stored state after verification completes, or clarifying that "connected" means "connection initiated" rather than "script verified".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/components/local-mode-content.tsx` around lines
270 - 281, The code is persisting connected: true before script verification
completes; update the flow so persistConnectionData(...) is called only after
checkScriptAvailability(...) confirms availability (or persist with connected:
false initially and then update it after verification). Locate the calls to
persistConnectionData, checkScriptAvailability, setIsConnecting, setStatus, and
notifyConnectionStatus (and the variables trimmedUrl, resolvedAppId,
selectedTab) and either move the successful-persist call to after the
verification step or perform an initial persist with connected: false and then a
second persist/update to connected: true when verification succeeds; ensure
notifyConnectionStatus and setStatus reflect the verified state.

63-67: Consider validating localStorage value for selectedTab.

If localStorage contains an unexpected value (e.g., corrupted or manually edited), the type cast assumes it's valid. A safer approach would validate the value.

🛡️ Suggested improvement
-	const [selectedTab, setSelectedTab] = useState<"new" | "existing">(
-		(localStorage.getItem(LOCAL_STORAGE_KEY_DEVTOOLS_LOCAL_MODE_SELECTED_TAB) as
-			| "new"
-			| "existing") || "new",
-	);
+	const [selectedTab, setSelectedTab] = useState<"new" | "existing">(() => {
+		const stored = localStorage.getItem(LOCAL_STORAGE_KEY_DEVTOOLS_LOCAL_MODE_SELECTED_TAB);
+		return stored === "new" || stored === "existing" ? stored : "new";
+	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/components/local-mode-content.tsx` around lines 63
- 67, The current initialization of selectedTab in local-mode-content.tsx
blindly casts the localStorage value to "new" | "existing", which can break if
the stored value is unexpected; update the initialization (or add a small helper
like getInitialSelectedTab) to read
LOCAL_STORAGE_KEY_DEVTOOLS_LOCAL_MODE_SELECTED_TAB, check whether the value
strictly equals "new" or "existing", and only then return it, otherwise return
the safe default "new" and ensure setSelectedTab continues to persist only valid
values.
packages/nube-devtools/src/components/local-mode-status-messages.tsx (1)

43-50: Consider adding dark mode text styling for consistency.

The "available" state (line 38) has dark:text-green-400 for dark mode support, but the "unavailable" state only uses text-destructive. If the destructive color isn't already dark-mode aware via CSS variables, this could look inconsistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/components/local-mode-status-messages.tsx` around
lines 43 - 50, The "unavailable" branch lacks a dark-mode text variant; update
the element(s) that use text-destructive (the <p> with class "text-destructive"
and optionally the AlertCircle icon with "text-destructive") to include a
dark-mode class (e.g., dark:text-destructive or a specific dark variant like
dark:text-destructive-400) so the destructive text/icon color matches dark-mode
styling similar to the "available" branch which uses dark:text-green-400; modify
the paragraph and icon class lists in the component where scriptAvailability ===
"unavailable" to add the appropriate dark: class names.
packages/nube-devtools/src/contentScript/index.ts (1)

34-36: Remove commented-out initialization message block.

Please drop the dead commented code (or gate it with a real feature flag) to avoid stale behavior hints in runtime code.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/contentScript/index.ts` around lines 34 - 36,
Remove the dead commented initialization message block in
packages/nube-devtools/src/contentScript/index.ts: delete the commented
chrome.runtime.sendMessage({ action: "nube-devtools-initialize-sdk" }) lines
(or, if this behavior is still required, reintroduce it behind a real feature
flag check such as isSdkInitEnabled and call chrome.runtime.sendMessage from the
initialization flow). Ensure you update any related comments to reflect the
change and keep the contentScript initialization logic (index.ts) free of stale
commented-out runtime code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/nube-devtools/src/components/local-mode-content.tsx`:
- Around line 354-362: The two Input elements both use id="app-server-url",
violating unique-ID rules; change them to distinct IDs (e.g.,
"app-server-url-new" and "app-server-url-existing") and update their matching
<label> htmlFor attributes so labels point to the correct input; also search for
any JS that references "app-server-url" (e.g., getElementById or tests) and
update those references to the new IDs, leaving handlers like handleUrlChange,
and state variables appUrl/isConnecting/status unchanged.

In `@packages/nube-devtools/src/hooks/use-nube-status.ts`:
- Around line 27-30: The hook in use-nube-status ignores falsy boolean responses
because it only updates state when response.status is truthy; change the update
logic in the response handler used by setNubeStatus so it sets nubeStatus to
response.status whenever response has a status property (including false) —
e.g., check for response !== undefined or 'status' in response and call
setNubeStatus(() => response.status) so false values propagate properly.
- Around line 3-13: getTabId currently calls chrome.tabs.query unguarded which
can throw when chrome or chrome.tabs is undefined and also ignores
chrome.runtime.lastError; update getTabId to first check for typeof chrome !==
"undefined" and chrome.tabs before calling chrome.tabs.query, and if chrome or
chrome.tabs is missing call callback(null) immediately; when using
chrome.tabs.query, inspect chrome.runtime.lastError in its callback and if an
error exists pass null (or handle appropriately) to the provided callback,
otherwise pass tabs[0]?.id; reference getTabId,
chrome.devtools?.inspectedWindow?.tabId, chrome.tabs.query and
chrome.runtime.lastError when making the change.

In `@packages/nube-devtools/src/utils/page-storage.ts`:
- Around line 18-22: Wrap all chrome.tabs.query calls in a try/catch and extract
a new helper named getActiveTabId() that returns the active tab id or null/false
on failure; replace the inline queries (the one currently assigning const [tab]
= await chrome.tabs.query({ active: true, currentWindow: true }); plus the
similar occurrences around the other two blocks) with calls to getActiveTabId(),
and ensure getActiveTabId() handles exceptions from chrome.tabs.query() and
returns the documented fallback (false or null) so the surrounding functions
preserve their contract.

---

Duplicate comments:
In `@packages/nube-devtools/src/components/copyable-value.tsx`:
- Around line 9-13: The handleCopy callback currently calls
navigator.clipboard.writeText(value) without handling rejections; update
handleCopy (the useCallback function) to catch errors from
navigator.clipboard.writeText(value) and provide user feedback and logging on
failure (e.g., call toast.error with a helpful message and optionally
console.error the error), ensuring both the success path still calls
toast.success and the failure path prevents unhandled Promise rejections.

In `@packages/nube-devtools/src/components/local-mode-content.tsx`:
- Around line 290-304: The markStoredAsDisconnected handler can throw on
malformed stored JSON because it calls JSON.parse directly; wrap the parse in a
try-catch inside markStoredAsDisconnected (the function using
getPageSessionStorage, JSON.parse, and setPageSessionStorage) so parse failures
do not abort the stop flow—on error, treat it as already-disconnected (or
remove/clear PAGE_STORAGE_KEY_DEVTOOLS_APPLICATION) and continue, ensuring
data.connected is only updated when parse succeeds and the function always
returns without throwing.
- Around line 225-231: The reloadCurrentTab function currently queries the
active tab but should prioritize the DevTools inspected tab; update
reloadCurrentTab to first read chrome.devtools.inspectedWindow.tabId and, if
it's a number, call chrome.tabs.reload with that id, otherwise fall back to the
existing chrome.tabs.query active/currentWindow lookup and reload the resulting
tab id; keep the existing null/undefined checks (tab?.id) and ensure usage
mirrors the pattern in fetchApps by treating inspectedWindow.tabId as the
primary target.

In `@packages/nube-devtools/src/devtools/index.css`:
- Around line 131-133: Add a single blank line before the declaration
"scrollbar-width: thin;" so there is an empty line between the "@apply
border-border outline-ring/50;" rule and the "scrollbar-width" declaration to
satisfy the declaration-empty-line-before Stylelint rule; update the CSS block
containing "@apply border-border outline-ring/50;", "scrollbar-width: thin;",
and "scrollbar-color: oklch(0.7 0 0 / 30%) transparent;" by inserting one empty
line between the `@apply` line and the scrollbar-width line.

---

Nitpick comments:
In `@packages/nube-devtools/src/components/local-mode-content.tsx`:
- Around line 270-281: The code is persisting connected: true before script
verification completes; update the flow so persistConnectionData(...) is called
only after checkScriptAvailability(...) confirms availability (or persist with
connected: false initially and then update it after verification). Locate the
calls to persistConnectionData, checkScriptAvailability, setIsConnecting,
setStatus, and notifyConnectionStatus (and the variables trimmedUrl,
resolvedAppId, selectedTab) and either move the successful-persist call to after
the verification step or perform an initial persist with connected: false and
then a second persist/update to connected: true when verification succeeds;
ensure notifyConnectionStatus and setStatus reflect the verified state.
- Around line 63-67: The current initialization of selectedTab in
local-mode-content.tsx blindly casts the localStorage value to "new" |
"existing", which can break if the stored value is unexpected; update the
initialization (or add a small helper like getInitialSelectedTab) to read
LOCAL_STORAGE_KEY_DEVTOOLS_LOCAL_MODE_SELECTED_TAB, check whether the value
strictly equals "new" or "existing", and only then return it, otherwise return
the safe default "new" and ensure setSelectedTab continues to persist only valid
values.

In `@packages/nube-devtools/src/components/local-mode-status-messages.tsx`:
- Around line 43-50: The "unavailable" branch lacks a dark-mode text variant;
update the element(s) that use text-destructive (the <p> with class
"text-destructive" and optionally the AlertCircle icon with "text-destructive")
to include a dark-mode class (e.g., dark:text-destructive or a specific dark
variant like dark:text-destructive-400) so the destructive text/icon color
matches dark-mode styling similar to the "available" branch which uses
dark:text-green-400; modify the paragraph and icon class lists in the component
where scriptAvailability === "unavailable" to add the appropriate dark: class
names.

In `@packages/nube-devtools/src/contentScript/index.ts`:
- Around line 34-36: Remove the dead commented initialization message block in
packages/nube-devtools/src/contentScript/index.ts: delete the commented
chrome.runtime.sendMessage({ action: "nube-devtools-initialize-sdk" }) lines
(or, if this behavior is still required, reintroduce it behind a real feature
flag check such as isSdkInitEnabled and call chrome.runtime.sendMessage from the
initialization flow). Ensure you update any related comments to reflect the
change and keep the contentScript initialization logic (index.ts) free of stale
commented-out runtime code.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6623dc1 and e7960d4.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (34)
  • packages/nube-devtools/package.json
  • packages/nube-devtools/popup.html
  • packages/nube-devtools/public/inject-extension-flag.js
  • packages/nube-devtools/src/background/index.ts
  • packages/nube-devtools/src/background/nube-dev-tools.ts
  • packages/nube-devtools/src/background/scripts/index.ts
  • packages/nube-devtools/src/background/scripts/inject-window-variable.ts
  • packages/nube-devtools/src/components/copyable-value.tsx
  • packages/nube-devtools/src/components/local-mode-card-header.tsx
  • packages/nube-devtools/src/components/local-mode-content.tsx
  • packages/nube-devtools/src/components/local-mode-quick-tips.tsx
  • packages/nube-devtools/src/components/local-mode-status-messages.tsx
  • packages/nube-devtools/src/components/ui/native-select.tsx
  • packages/nube-devtools/src/contentScript/index.ts
  • packages/nube-devtools/src/contexts/navigation-context.tsx
  • packages/nube-devtools/src/devtools/app.tsx
  • packages/nube-devtools/src/devtools/components/app-detail-panel.tsx
  • packages/nube-devtools/src/devtools/components/app-sidebar.tsx
  • packages/nube-devtools/src/devtools/index.css
  • packages/nube-devtools/src/devtools/pages/LocalMode.tsx
  • packages/nube-devtools/src/devtools/pages/apps.tsx
  • packages/nube-devtools/src/devtools/pages/errors.tsx
  • packages/nube-devtools/src/devtools/pages/events.tsx
  • packages/nube-devtools/src/devtools/pages/storages.tsx
  • packages/nube-devtools/src/devtools/pages/svg-converter.tsx
  • packages/nube-devtools/src/hooks/use-nube-status.ts
  • packages/nube-devtools/src/manifest.ts
  • packages/nube-devtools/src/popup/app.tsx
  • packages/nube-devtools/src/popup/components/popup-footer.tsx
  • packages/nube-devtools/src/popup/index.css
  • packages/nube-devtools/src/popup/index.tsx
  • packages/nube-devtools/src/popup/popup.tsx
  • packages/nube-devtools/src/utils/index.ts
  • packages/nube-devtools/src/utils/page-storage.ts
💤 Files with no reviewable changes (3)
  • packages/nube-devtools/src/background/nube-dev-tools.ts
  • packages/nube-devtools/src/background/scripts/inject-window-variable.ts
  • packages/nube-devtools/src/background/scripts/index.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • packages/nube-devtools/src/devtools/pages/storages.tsx
  • packages/nube-devtools/src/popup/components/popup-footer.tsx
  • packages/nube-devtools/src/popup/popup.tsx
  • packages/nube-devtools/package.json
  • packages/nube-devtools/src/components/ui/native-select.tsx
  • packages/nube-devtools/src/popup/app.tsx
  • packages/nube-devtools/src/components/local-mode-quick-tips.tsx
  • packages/nube-devtools/src/devtools/components/app-detail-panel.tsx
  • packages/nube-devtools/src/devtools/pages/apps.tsx
  • packages/nube-devtools/src/popup/index.tsx
  • packages/nube-devtools/src/devtools/components/app-sidebar.tsx
  • packages/nube-devtools/popup.html
  • packages/nube-devtools/src/components/local-mode-card-header.tsx
  • packages/nube-devtools/src/devtools/pages/errors.tsx
  • packages/nube-devtools/src/devtools/pages/svg-converter.tsx
  • packages/nube-devtools/src/background/index.ts
  • packages/nube-devtools/src/devtools/pages/LocalMode.tsx
  • packages/nube-devtools/public/inject-extension-flag.js
  • packages/nube-devtools/src/manifest.ts

Comment on lines 27 to 30
(response) => {
if (response?.status) {
setNubeStatus(() => response.status);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Propagate false status values instead of treating them as no-op.

Line 28 only updates for truthy values; when background returns status: false, the hook does not clear nubeStatus.

Proposed fix
 				(response) => {
-					if (response?.status) {
-						setNubeStatus(() => response.status);
+					if (typeof response?.status === "boolean") {
+						setNubeStatus(response.status);
 					}
 				},
📝 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.

Suggested change
(response) => {
if (response?.status) {
setNubeStatus(() => response.status);
}
(response) => {
if (typeof response?.status === "boolean") {
setNubeStatus(response.status);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/nube-devtools/src/hooks/use-nube-status.ts` around lines 27 - 30,
The hook in use-nube-status ignores falsy boolean responses because it only
updates state when response.status is truthy; change the update logic in the
response handler used by setNubeStatus so it sets nubeStatus to response.status
whenever response has a status property (including false) — e.g., check for
response !== undefined or 'status' in response and call setNubeStatus(() =>
response.status) so false values propagate properly.

@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 2026
@TiendaNube TiendaNube deleted a comment from coderabbitai bot Mar 2, 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.

1 participant