Skip to content

Update version to 0.39.0, add OverviewView component, and adjust navi…#26

Merged
AboMeezO merged 2 commits into
mainfrom
OverviewTab
Mar 19, 2026
Merged

Update version to 0.39.0, add OverviewView component, and adjust navi…#26
AboMeezO merged 2 commits into
mainfrom
OverviewTab

Conversation

@AboMeezO
Copy link
Copy Markdown
Member

@AboMeezO AboMeezO commented Mar 19, 2026

…gation paths in Sidebar and ViewTabs components to reflect new project structure.

Summary by CodeRabbit

  • New Features

    • Added Overview dashboard with a customizable, draggable grid of tiles (Console, Tunnel, Tunnel Logs, Files, CPU, RAM, PIDs)
    • Console supports live logs, command input, and history navigation
    • Tunnel controls with start/stop, open/copy URL, and status indicators
    • Compact charts/sparklines and stat cards for CPU/RAM; file preview and PID copy actions
    • Per-project layout persistence and a layout reset control
  • UI/UX

    • Overview is now the default project view; navigation and tabs updated to include Overview

…gation paths in Sidebar and ViewTabs components to reflect new project structure.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

Adds a new per-project Overview dashboard route and UI: a draggable/resizable grid of mini-tiles (console, tunnel, logs, files, CPU, RAM, PIDs), persisted per-project layout, routing/nav updates, and a runtime dependency on @eleung/react-grid-layout.

Changes

Cohort / File(s) Summary
Dependency & Version
package.json
Bumped version 0.38.0 → 0.39.0; added runtime dep @eleung/react-grid-layout (^1.8.3).
Routing & Navigation
src/App.jsx, src/components/Sidebar.jsx, src/components/ViewTabs.jsx
Added overview child route and made Overview the default project tab; updated project navigation and tab UI to include Overview and route tab clicks under /project/:projectId/<tab>.
Overview View & Grid
src/components/OverviewView.jsx, src/components/overview/OverviewGrid.jsx, src/components/overview/TileShell.jsx, src/components/overview/useProjectLayout.js
New OverviewView and OverviewGrid implementing a 12-column draggable/resizable grid using the new grid dependency; TileShell wrapper and useProjectLayout hook persist per-project layouts (localStorage, debounced).
Tiles & Tile Features
src/components/overview/tiles/...
src/components/overview/tiles/ConsoleMiniTile.jsx, TunnelMiniTile.jsx, TunnelLogsMiniTile.jsx, MiniFileExhibitorTile.jsx, CpuTile.jsx, RamTile.jsx, ProcessIdsTile.jsx
Added tile components: embedded xterm console with input/history, tunnel controls (start/stop/open/copy), tunnel logs wrapper, file tree preview (limited depth/count), CPU/RAM metric tiles with sparklines, and PID list with copy actions.
Charts & Small UI
src/components/overview/charts/...
CompactStatCard.jsx, MiniSparkline.jsx, PremiumSparklineChart.jsx
New compact stat and sparkline chart components used by metric tiles.
State Signal
src/store/atoms.js
Added exported overviewLayoutResetSignalAtom (atom(0)) used to trigger layout resets from UI.

Sequence Diagram

sequenceDiagram
    participant User
    participant Router
    participant OverviewView
    participant GridLayout
    participant localStorage
    participant Atoms as StateAtoms
    participant Tiles as TileComponents
    participant API as ExternalAPIs

    User->>Router: navigate /project/:id/overview
    Router->>OverviewView: mount
    OverviewView->>localStorage: load saved layout (key per project)
    localStorage-->>OverviewView: layout or default
    OverviewView->>GridLayout: init with layout
    OverviewView->>Atoms: subscribe (stats, logs, tunnel, history)
    Atoms-->>OverviewView: push state updates
    OverviewView->>Tiles: render tiles with props
    Tiles->>API: init (xterm, tunnel start/stop, openExternal)
    User->>Tiles: interact (send command, start tunnel, open file)
    Tiles->>API: call window.api / clipboard
    API-->>Tiles: responses/logs
    Tiles->>Atoms: update state (logs, status)
    GridLayout->>localStorage: debounced save on layout change
    localStorage-->>GridLayout: persisted
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐇 I hopped into the project view today,
Tiles that drag and tiles that play,
Consoles hum and tunnels peep,
Sparkling charts that never sleep,
A tiny dashboard — hooray! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately reflects the main changes: version bump to 0.39.0, addition of OverviewView component, and navigation path adjustments in Sidebar/ViewTabs.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch OverviewTab
📝 Coding Plan
  • Generate coding plan for human review comments

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

❤️ Share

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

Copy link
Copy Markdown
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: 2

🧹 Nitpick comments (2)
src/components/OverviewView.jsx (1)

835-849: Consider simplifying the nested ternary with an object lookup.

The deeply nested ternary for tile titles is functional but hard to read and maintain.

♻️ Suggested simplification
+const TILE_TITLES = {
+  console: "Console",
+  tunnel: "Tunnel",
+  tunnelLogs: "Tunnel Logs",
+  files: "Files",
+  cpu: "CPU",
+  ram: "RAM",
+  pids: "PIDs",
+};
+
 {tiles.map((t) => (
   <div key={t.i} className="h-full w-full">
     <TileShell
-      title={
-        t.i === "console"
-          ? "Console"
-          : t.i === "tunnel"
-            ? "Tunnel"
-            : t.i === "tunnelLogs"
-              ? "Tunnel Logs"
-              : t.i === "files"
-                ? "Files"
-                : t.i === "cpu"
-                  ? "CPU"
-                  : t.i === "ram"
-                    ? "RAM"
-                    : "PIDs"
-      }
+      title={TILE_TITLES[t.i] ?? t.i}
       right={t.headerRight ?? null}
     >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/OverviewView.jsx` around lines 835 - 849, Replace the nested
ternary used for the title prop in OverviewView.jsx with a simple lookup: define
a mapping object (e.g., titleMap = { console: "Console", tunnel: "Tunnel",
tunnelLogs: "Tunnel Logs", files: "Files", cpu: "CPU", ram: "RAM" }) near the
render/functional component and then set title to titleMap[t.i] || "PIDs";
update the title prop usage that currently references t.i to use this lookup and
remove the nested ternary to improve readability and maintainability.
package.json (1)

118-118: Consider using the original react-grid-layout package instead of this fork.

While @eleung/react-grid-layout is a legitimate npm package, the original react-grid-layout (v2.2.2) is significantly more actively maintained and widely adopted (22k GitHub stars, 1.6M weekly downloads vs. ~310), with full TypeScript support and React 18+ compatibility. The fork is limited to React 16 & 17 and receives infrequent updates. Unless there is a specific reason this fork is required, switching to the original package would reduce maintenance risk and improve long-term compatibility.

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

In `@package.json` at line 118, Replace the forked dependency
"@eleung/react-grid-layout" in package.json with the upstream
"react-grid-layout" (suggested version v2.2.2), update your lockfile (npm
install / yarn install), and run the app/tests to catch any API differences;
then verify and, if necessary, update imports/usages (e.g., any references to
"@eleung/react-grid-layout" to "react-grid-layout" and components like
GridLayout/Responsive) so TypeScript picks up the upstream types and React 18+
compatibility.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/OverviewView.jsx`:
- Around line 89-105: The debounce timer set in onLayoutChange using
saveTimerRef.current may fire after the component unmounts; add a cleanup to
clear the pending timeout when the component unmounts by adding a useEffect with
a cleanup that checks saveTimerRef.current and calls
clearTimeout(saveTimerRef.current) (and optionally sets saveTimerRef.current =
null). Update the component that defines onLayoutChange/ layout/ saveTimerRef
(the OverviewView component) to ensure the timeout is cleared on unmount to
prevent the delayed localStorage write from running after unmount.
- Around line 108-109: The current logs assignment (const allLogs =
useAtomValue(logsAtom); const logs = allLogs?.[projectId] || []) creates a new
empty array each render when allLogs?.[projectId] is undefined, causing the
downstream useEffect to rerun; change this to memoize a stable fallback by
deriving logs with useMemo (depend on allLogs and projectId) so logs only
changes when the actual data changes (refer to logsAtom, allLogs, logs, and the
useEffect in OverviewView.jsx).

---

Nitpick comments:
In `@package.json`:
- Line 118: Replace the forked dependency "@eleung/react-grid-layout" in
package.json with the upstream "react-grid-layout" (suggested version v2.2.2),
update your lockfile (npm install / yarn install), and run the app/tests to
catch any API differences; then verify and, if necessary, update imports/usages
(e.g., any references to "@eleung/react-grid-layout" to "react-grid-layout" and
components like GridLayout/Responsive) so TypeScript picks up the upstream types
and React 18+ compatibility.

In `@src/components/OverviewView.jsx`:
- Around line 835-849: Replace the nested ternary used for the title prop in
OverviewView.jsx with a simple lookup: define a mapping object (e.g., titleMap =
{ console: "Console", tunnel: "Tunnel", tunnelLogs: "Tunnel Logs", files:
"Files", cpu: "CPU", ram: "RAM" }) near the render/functional component and then
set title to titleMap[t.i] || "PIDs"; update the title prop usage that currently
references t.i to use this lookup and remove the nested ternary to improve
readability and maintainability.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 627c2587-aaff-4c64-924a-410bd1fec58b

📥 Commits

Reviewing files that changed from the base of the PR and between 352e0c7 and ae3eb98.

📒 Files selected for processing (5)
  • package.json
  • src/App.jsx
  • src/components/OverviewView.jsx
  • src/components/Sidebar.jsx
  • src/components/ViewTabs.jsx

Comment on lines +89 to +105
const onLayoutChange = (nextLayout) => {
setLayout(nextLayout);

if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
saveTimerRef.current = setTimeout(() => {
if (projectId == null) return;
const key = `selfhost-overview-grid:${projectId}`;
try {
localStorage.setItem(key, JSON.stringify(nextLayout));
} catch {
// Ignore storage errors (private mode / quota).
}
}, 250);
};

return { layout, onLayoutChange };
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Missing cleanup for debounce timer on unmount.

If the component unmounts before the 250ms debounce completes, saveTimerRef.current callback may still execute. Clear the timer in a cleanup function.

🛠️ Proposed fix
+  useEffect(() => {
+    return () => {
+      if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
+    };
+  }, []);
+
   const onLayoutChange = (nextLayout) => {
     setLayout(nextLayout);
📝 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
const onLayoutChange = (nextLayout) => {
setLayout(nextLayout);
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
saveTimerRef.current = setTimeout(() => {
if (projectId == null) return;
const key = `selfhost-overview-grid:${projectId}`;
try {
localStorage.setItem(key, JSON.stringify(nextLayout));
} catch {
// Ignore storage errors (private mode / quota).
}
}, 250);
};
return { layout, onLayoutChange };
}
useEffect(() => {
return () => {
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
};
}, []);
const onLayoutChange = (nextLayout) => {
setLayout(nextLayout);
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
saveTimerRef.current = setTimeout(() => {
if (projectId == null) return;
const key = `selfhost-overview-grid:${projectId}`;
try {
localStorage.setItem(key, JSON.stringify(nextLayout));
} catch {
// Ignore storage errors (private mode / quota).
}
}, 250);
};
return { layout, onLayoutChange };
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/OverviewView.jsx` around lines 89 - 105, The debounce timer
set in onLayoutChange using saveTimerRef.current may fire after the component
unmounts; add a cleanup to clear the pending timeout when the component unmounts
by adding a useEffect with a cleanup that checks saveTimerRef.current and calls
clearTimeout(saveTimerRef.current) (and optionally sets saveTimerRef.current =
null). Update the component that defines onLayoutChange/ layout/ saveTimerRef
(the OverviewView component) to ensure the timeout is cleared on unmount to
prevent the delayed localStorage write from running after unmount.

Comment on lines +108 to +109
const allLogs = useAtomValue(logsAtom);
const logs = allLogs?.[projectId] || [];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Unstable logs reference may cause unnecessary effect re-runs.

The || [] fallback creates a new array instance on every render when allLogs?.[projectId] is undefined, causing the useEffect at line 184 to re-execute unnecessarily.

🛠️ Proposed fix using useMemo
+const EMPTY_LOGS = [];
+
 function ConsoleMiniTile({ projectId, status, onSendInput }) {
   const allLogs = useAtomValue(logsAtom);
-  const logs = allLogs?.[projectId] || [];
+  const logs = useMemo(() => allLogs?.[projectId] ?? EMPTY_LOGS, [allLogs, projectId]);
🧰 Tools
🪛 GitHub Check: build-and-verify

[warning] 109-109:
The 'logs' logical expression could make the dependencies of useEffect Hook (at line 202) change on every render. To fix this, wrap the initialization of 'logs' in its own useMemo() Hook

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

In `@src/components/OverviewView.jsx` around lines 108 - 109, The current logs
assignment (const allLogs = useAtomValue(logsAtom); const logs =
allLogs?.[projectId] || []) creates a new empty array each render when
allLogs?.[projectId] is undefined, causing the downstream useEffect to rerun;
change this to memoize a stable fallback by deriving logs with useMemo (depend
on allLogs and projectId) so logs only changes when the actual data changes
(refer to logsAtom, allLogs, logs, and the useEffect in OverviewView.jsx).

…agement and integrate reset layout functionality in ViewTabs. Introduce overviewLayoutResetSignalAtom for state management of the grid layout.
Copy link
Copy Markdown
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)
src/components/OverviewView.jsx (1)

22-702: Remove the stale inline overview implementation.

OverviewView now just delegates to OverviewGrid, and src/components/overview/OverviewGrid.jsx already imports the standalone TileShell, useProjectLayout, and tile components. Keeping a second copy here means fixes can land in the wrong implementation. Delete the in-file versions and keep/move only the CSS side-effect imports that the extracted components still need.

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

In `@src/components/OverviewView.jsx` around lines 22 - 702, This file contains a
stale inline implementation of many overview pieces (MiniSparkline, TileShell,
useProjectLayout, ConsoleMiniTile, TunnelMiniTile, TunnelLogsMiniTile,
MiniFileExhibitorTile, ProcessIdsTile, CpuTile, RamTile) that now live in
src/components/overview/OverviewGrid.jsx; delete these in-file component and
hook definitions and any related helper duplicates, leaving only the CSS
side-effect imports (if any) required by the extracted components and a simple
OverviewView that imports/delegates to OverviewGrid; ensure you keep references
to unique symbols (MiniSparkline, TileShell, useProjectLayout, ConsoleMiniTile,
TunnelMiniTile, TunnelLogsMiniTile, MiniFileExhibitorTile, ProcessIdsTile,
CpuTile, RamTile) removed here so future fixes land in the single canonical
implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/overview/OverviewGrid.jsx`:
- Around line 65-77: The ConsoleMiniTile instance is reused across projects
because the tile key/identifier is static ("console"), causing its local state
(input, history, historyIndex) to persist; update the tiles entry that renders
ConsoleMiniTile so its key depends on the current project (e.g., include
project.id in the tile key or use a composite key like `console-${project.id}`)
so React will unmount and remount ConsoleMiniTile when project changes, ensuring
its internal state is reset; locate the tiles array and the object with i:
"console" and change its key/identifier to incorporate project.id.

In `@src/components/overview/tiles/ConsoleMiniTile.jsx`:
- Around line 1-18: The logs variable in ConsoleMiniTile is recreating an empty
array each render (const logs = allLogs?.[projectId] || []) causing effects that
depend on logs to rerun; fix by defining a stable EMPTY_LOGS constant and use
useMemo or reuse that reference so logs becomes const logs =
allLogs?.[projectId] ?? EMPTY_LOGS (or memoize via useMemo(() =>
allLogs?.[projectId] ?? EMPTY_LOGS, [allLogs, projectId])) so the identity is
stable and the effect that depends on logs (the effect around line 93) only runs
when actual log data changes.

In `@src/components/overview/tiles/MiniFileExhibitorTile.jsx`:
- Around line 27-38: Replace the non-interactive clickable divs used for
directory toggles and file rows with proper interactive controls: change the row
container (the element with onClick that calls setExpanded((prev) => ({ ...prev,
[path]: !prev[path] })) and the child file row rendering in renderNode) to a
real button (type="button") or make them keyboard operable by adding tabIndex,
onKeyDown handling for Enter/Space, and appropriate ARIA (e.g., aria-expanded
for directory rows bound to isOpen). Ensure the label uses the existing
node.name, keep the visual styling, and preserve the children rendering logic
(children.map(renderNode, level + 1)) while updating those rows the same way so
they are focusable and usable by keyboard and assistive tech.

In `@src/components/overview/tiles/ProcessIdsTile.jsx`:
- Around line 24-30: The empty-state conditional uses stats?.activeProcesses
directly instead of the normalized value computed earlier
(activeProcesses/processCount), so replace the condition (pids.length === 0 &&
(stats?.activeProcesses ?? 0) > 0) with a check that uses the normalized process
count variable you already compute (activeProcesses or processCount) to
determine if active processes exist; update the JSX branch to reference that
normalized variable (activeProcesses) so the fallback path is honored when
stats.activeProcesses is missing but processCount/activeProcesses is present.

In `@src/components/overview/tiles/TunnelMiniTile.jsx`:
- Line 123: In TunnelMiniTile.jsx, update the status detail rendering that
currently uses the template fragment `: localhost:${portNum}` (inside the
TunnelMiniTile component where portNum is used) to remove the extra leading
colon so it reads `localhost:${portNum}`; locate the JSX expression that builds
the status/detail string and remove the stray ":" before "localhost" to fix the
formatting.

In `@src/components/overview/useProjectLayout.js`:
- Around line 39-49: Add an unmount cleanup that clears the pending debounce
timer so the queued save cannot run after the component unmounts: ensure the
effect or hook that sets saveTimerRef.current (the function containing the
setTimeout and localStorage.setItem) returns a cleanup which checks
saveTimerRef.current and calls clearTimeout(saveTimerRef.current) and resets it
(e.g., saveTimerRef.current = null). Reference the existing saveTimerRef.current
and the debounce block that calls setTimeout to locate where to add the cleanup.

---

Nitpick comments:
In `@src/components/OverviewView.jsx`:
- Around line 22-702: This file contains a stale inline implementation of many
overview pieces (MiniSparkline, TileShell, useProjectLayout, ConsoleMiniTile,
TunnelMiniTile, TunnelLogsMiniTile, MiniFileExhibitorTile, ProcessIdsTile,
CpuTile, RamTile) that now live in src/components/overview/OverviewGrid.jsx;
delete these in-file component and hook definitions and any related helper
duplicates, leaving only the CSS side-effect imports (if any) required by the
extracted components and a simple OverviewView that imports/delegates to
OverviewGrid; ensure you keep references to unique symbols (MiniSparkline,
TileShell, useProjectLayout, ConsoleMiniTile, TunnelMiniTile,
TunnelLogsMiniTile, MiniFileExhibitorTile, ProcessIdsTile, CpuTile, RamTile)
removed here so future fixes land in the single canonical implementation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eaed5e90-4c6d-4f33-8d6b-5044402cab16

📥 Commits

Reviewing files that changed from the base of the PR and between ae3eb98 and 38633af.

📒 Files selected for processing (16)
  • src/components/OverviewView.jsx
  • src/components/ViewTabs.jsx
  • src/components/overview/OverviewGrid.jsx
  • src/components/overview/TileShell.jsx
  • src/components/overview/charts/CompactStatCard.jsx
  • src/components/overview/charts/MiniSparkline.jsx
  • src/components/overview/charts/PremiumSparklineChart.jsx
  • src/components/overview/tiles/ConsoleMiniTile.jsx
  • src/components/overview/tiles/CpuTile.jsx
  • src/components/overview/tiles/MiniFileExhibitorTile.jsx
  • src/components/overview/tiles/ProcessIdsTile.jsx
  • src/components/overview/tiles/RamTile.jsx
  • src/components/overview/tiles/TunnelLogsMiniTile.jsx
  • src/components/overview/tiles/TunnelMiniTile.jsx
  • src/components/overview/useProjectLayout.js
  • src/store/atoms.js
✅ Files skipped from review due to trivial changes (3)
  • src/store/atoms.js
  • src/components/overview/TileShell.jsx
  • src/components/overview/tiles/TunnelLogsMiniTile.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/ViewTabs.jsx

Comment on lines +65 to +77
const tiles = [
{
i: "console",
headerRight: (
<span
className={`w-1.5 h-1.5 rounded-full shrink-0 ${
project.status === "running" ? "bg-green-500" : "bg-destructive"
}`}
/>
),
render: (
<ConsoleMiniTile projectId={project.id} status={project.status} onSendInput={onSendInput} />
),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the OverviewGrid.jsx file around the specified lines
cat -n src/components/overview/OverviewGrid.jsx | sed -n '60,80p'

Repository: DevRoots-Studio/SelfHost-Helper

Length of output: 702


🏁 Script executed:

# Let's also see the grid rendering section (lines 140-163 mentioned in the comment)
cat -n src/components/overview/OverviewGrid.jsx | sed -n '135,165p'

Repository: DevRoots-Studio/SelfHost-Helper

Length of output: 1337


🏁 Script executed:

# Now let's examine the ConsoleMiniTile component to understand its state
cat -n src/components/overview/tiles/ConsoleMiniTile.jsx

Repository: DevRoots-Studio/SelfHost-Helper

Length of output: 7783


Remount the console tile per project to prevent command history leak.

ConsoleMiniTile stores local command state (input, history, historyIndex), but because the tile is keyed only by its type ("console" at line 141), the same component instance persists when projectId changes. The terminal itself is properly disposed and recreated via the projectId dependency (line 91), but the state variables are not reset. This allows command history from one project to be recalled and sent to another via ArrowUp/ArrowDown navigation.

Key the console tile by project.id to force unmount and remount when switching projects:

Suggested fix
       render: (
-        <ConsoleMiniTile projectId={project.id} status={project.status} onSendInput={onSendInput} />
+        <ConsoleMiniTile
+          key={`console:${project.id}`}
+          projectId={project.id}
+          status={project.status}
+          onSendInput={onSendInput}
+        />
       ),
📝 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
const tiles = [
{
i: "console",
headerRight: (
<span
className={`w-1.5 h-1.5 rounded-full shrink-0 ${
project.status === "running" ? "bg-green-500" : "bg-destructive"
}`}
/>
),
render: (
<ConsoleMiniTile projectId={project.id} status={project.status} onSendInput={onSendInput} />
),
const tiles = [
{
i: "console",
headerRight: (
<span
className={`w-1.5 h-1.5 rounded-full shrink-0 ${
project.status === "running" ? "bg-green-500" : "bg-destructive"
}`}
/>
),
render: (
<ConsoleMiniTile
key={`console:${project.id}`}
projectId={project.id}
status={project.status}
onSendInput={onSendInput}
/>
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/overview/OverviewGrid.jsx` around lines 65 - 77, The
ConsoleMiniTile instance is reused across projects because the tile
key/identifier is static ("console"), causing its local state (input, history,
historyIndex) to persist; update the tiles entry that renders ConsoleMiniTile so
its key depends on the current project (e.g., include project.id in the tile key
or use a composite key like `console-${project.id}`) so React will unmount and
remount ConsoleMiniTile when project changes, ensuring its internal state is
reset; locate the tiles array and the object with i: "console" and change its
key/identifier to incorporate project.id.

Comment on lines +1 to +18
import React, { useEffect, useRef, useState } from "react";
import { useAtomValue } from "jotai";
import { Terminal as TerminalIcon, Send } from "lucide-react";
import { toast } from "react-toastify";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { ClipboardAddon } from "@xterm/addon-clipboard";
import { WebLinksAddon } from "@xterm/addon-web-links";

import { Button } from "@/components/ui/button";
import { logsAtom } from "@/store/atoms";

const API = window.api;

export default function ConsoleMiniTile({ projectId, status, onSendInput }) {
const allLogs = useAtomValue(logsAtom);
const logs = allLogs?.[projectId] || [];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get its size
ls -la src/components/overview/tiles/ConsoleMiniTile.jsx && wc -l src/components/overview/tiles/ConsoleMiniTile.jsx

Repository: DevRoots-Studio/SelfHost-Helper

Length of output: 227


🏁 Script executed:

# Read the entire file to understand the context
cat -n src/components/overview/tiles/ConsoleMiniTile.jsx

Repository: DevRoots-Studio/SelfHost-Helper

Length of output: 7783


🏁 Script executed:

# Look for useEffect hooks and their dependencies
rg "useEffect" src/components/overview/tiles/ConsoleMiniTile.jsx -A 10

Repository: DevRoots-Studio/SelfHost-Helper

Length of output: 1106


Memoize the empty log fallback to prevent unnecessary effect reruns.

Line 17 creates a new [] on every render when allLogs?.[projectId] is missing. Since the effect at line 93 depends on logs, this causes unnecessary reruns even when no data has changed. Use useMemo with a stable EMPTY_LOGS reference to preserve identity.

🛠️ Minimal change
-import React, { useEffect, useRef, useState } from "react";
+import React, { useEffect, useMemo, useRef, useState } from "react";
 import { useAtomValue } from "jotai";
 import { Terminal as TerminalIcon, Send } from "lucide-react";
 import { toast } from "react-toastify";
 import { Terminal } from "@xterm/xterm";
 import { FitAddon } from "@xterm/addon-fit";
 import { ClipboardAddon } from "@xterm/addon-clipboard";
 import { WebLinksAddon } from "@xterm/addon-web-links";
@@
 const API = window.api;
+const EMPTY_LOGS = [];
 
 export default function ConsoleMiniTile({ projectId, status, onSendInput }) {
   const allLogs = useAtomValue(logsAtom);
-  const logs = allLogs?.[projectId] || [];
+  const logs = useMemo(() => allLogs?.[projectId] ?? EMPTY_LOGS, [allLogs, projectId]);
📝 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
import React, { useEffect, useRef, useState } from "react";
import { useAtomValue } from "jotai";
import { Terminal as TerminalIcon, Send } from "lucide-react";
import { toast } from "react-toastify";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { ClipboardAddon } from "@xterm/addon-clipboard";
import { WebLinksAddon } from "@xterm/addon-web-links";
import { Button } from "@/components/ui/button";
import { logsAtom } from "@/store/atoms";
const API = window.api;
export default function ConsoleMiniTile({ projectId, status, onSendInput }) {
const allLogs = useAtomValue(logsAtom);
const logs = allLogs?.[projectId] || [];
import React, { useEffect, useMemo, useRef, useState } from "react";
import { useAtomValue } from "jotai";
import { Terminal as TerminalIcon, Send } from "lucide-react";
import { toast } from "react-toastify";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
import { ClipboardAddon } from "@xterm/addon-clipboard";
import { WebLinksAddon } from "@xterm/addon-web-links";
import { Button } from "@/components/ui/button";
import { logsAtom } from "@/store/atoms";
const API = window.api;
const EMPTY_LOGS = [];
export default function ConsoleMiniTile({ projectId, status, onSendInput }) {
const allLogs = useAtomValue(logsAtom);
const logs = useMemo(() => allLogs?.[projectId] ?? EMPTY_LOGS, [allLogs, projectId]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/overview/tiles/ConsoleMiniTile.jsx` around lines 1 - 18, The
logs variable in ConsoleMiniTile is recreating an empty array each render (const
logs = allLogs?.[projectId] || []) causing effects that depend on logs to rerun;
fix by defining a stable EMPTY_LOGS constant and use useMemo or reuse that
reference so logs becomes const logs = allLogs?.[projectId] ?? EMPTY_LOGS (or
memoize via useMemo(() => allLogs?.[projectId] ?? EMPTY_LOGS, [allLogs,
projectId])) so the identity is stable and the effect that depends on logs (the
effect around line 93) only runs when actual log data changes.

Comment on lines +27 to +38
<div key={path} className="select-none">
<div
className="flex items-center gap-2 px-2 py-1 hover:bg-white/5 cursor-pointer rounded-md"
style={{ paddingLeft: indent }}
onClick={() => setExpanded((prev) => ({ ...prev, [path]: !prev[path] }))}
>
<span className="text-muted-foreground/70">{isOpen ? "▾" : "▸"}</span>
<span className="text-xs text-foreground/85 truncate">{node.name}</span>
</div>
{isOpen && children.length > 0 && (
<div className="pt-0.5">{children.map((child) => renderNode(child, level + 1))}</div>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use real controls for the file tree rows.

Both the directory toggles and file rows are clickable <div>s. They aren't focusable or keyboard-operable, so the preview can't be used without a mouse. Replace them with <button type="button"> elements, or add equivalent semantics and keyboard handling (aria-expanded for directories, Enter/Space activation, focusability).

Also applies to: 45-54

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

In `@src/components/overview/tiles/MiniFileExhibitorTile.jsx` around lines 27 -
38, Replace the non-interactive clickable divs used for directory toggles and
file rows with proper interactive controls: change the row container (the
element with onClick that calls setExpanded((prev) => ({ ...prev, [path]:
!prev[path] })) and the child file row rendering in renderNode) to a real button
(type="button") or make them keyboard operable by adding tabIndex, onKeyDown
handling for Enter/Space, and appropriate ARIA (e.g., aria-expanded for
directory rows bound to isOpen). Ensure the label uses the existing node.name,
keep the visual styling, and preserve the children rendering logic
(children.map(renderNode, level + 1)) while updating those rows the same way so
they are focusable and usable by keyboard and assistive tech.

Comment on lines +24 to +30
if (pids.length === 0 && (stats?.activeProcesses ?? 0) > 0) {
return (
<div className="h-full p-4 overflow-auto">
<div className="text-xs text-muted-foreground/60">
PID enumeration not available for this project (recovered Job Object). Active processes:{" "}
<span className="text-foreground font-mono">{activeProcesses}</span>
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use the normalized process count in the empty-state condition.

Line 24 bypasses the fallback you already computed at Line 10. If activeProcesses is absent but processCount is present, this can incorrectly fall through to “No processes detected.”

🔧 Proposed fix
-  if (pids.length === 0 && (stats?.activeProcesses ?? 0) > 0) {
+  if (pids.length === 0 && activeProcesses > 0) {
📝 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
if (pids.length === 0 && (stats?.activeProcesses ?? 0) > 0) {
return (
<div className="h-full p-4 overflow-auto">
<div className="text-xs text-muted-foreground/60">
PID enumeration not available for this project (recovered Job Object). Active processes:{" "}
<span className="text-foreground font-mono">{activeProcesses}</span>
</div>
if (pids.length === 0 && activeProcesses > 0) {
return (
<div className="h-full p-4 overflow-auto">
<div className="text-xs text-muted-foreground/60">
PID enumeration not available for this project (recovered Job Object). Active processes:{" "}
<span className="text-foreground font-mono">{activeProcesses}</span>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/overview/tiles/ProcessIdsTile.jsx` around lines 24 - 30, The
empty-state conditional uses stats?.activeProcesses directly instead of the
normalized value computed earlier (activeProcesses/processCount), so replace the
condition (pids.length === 0 && (stats?.activeProcesses ?? 0) > 0) with a check
that uses the normalized process count variable you already compute
(activeProcesses or processCount) to determine if active processes exist; update
the JSX branch to reference that normalized variable (activeProcesses) so the
fallback path is honored when stats.activeProcesses is missing but
processCount/activeProcesses is present.

? url
: error
? String(error)
: `: localhost:${portNum}`}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix localhost fallback text formatting.

Line 123 renders an extra leading colon (: localhost:...). This appears to be a typo in the status detail line.

✏️ Proposed fix
-                    : `: localhost:${portNum}`}
+                    : `localhost:${portNum}`}
📝 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
: `: localhost:${portNum}`}
: `localhost:${portNum}`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/overview/tiles/TunnelMiniTile.jsx` at line 123, In
TunnelMiniTile.jsx, update the status detail rendering that currently uses the
template fragment `: localhost:${portNum}` (inside the TunnelMiniTile component
where portNum is used) to remove the extra leading colon so it reads
`localhost:${portNum}`; locate the JSX expression that builds the status/detail
string and remove the stray ":" before "localhost" to fix the formatting.

Comment on lines +39 to +49
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
saveTimerRef.current = setTimeout(() => {
if (projectId == null) return;
const key = `selfhost-overview-grid:${projectId}`;
try {
localStorage.setItem(key, JSON.stringify(nextLayout));
} catch {
// Ignore storage errors (private mode / quota).
}
}, 250);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Pending debounce timer should be cleaned up on unmount.

A queued save can still fire after unmount, causing stale writes. Add an unmount cleanup to clear saveTimerRef.current.

🧹 Proposed fix
 export default function useProjectLayout({ projectId, defaultLayout }) {
   const [layout, setLayout] = useState(defaultLayout);
   const saveTimerRef = useRef(null);
   const defaultLayoutRef = useRef(defaultLayout);
   const projectIdRef = useRef(projectId);

+  useEffect(() => {
+    return () => {
+      if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
+    };
+  }, []);
+
   useEffect(() => {
     defaultLayoutRef.current = defaultLayout;
   }, [defaultLayout]);
📝 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
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
saveTimerRef.current = setTimeout(() => {
if (projectId == null) return;
const key = `selfhost-overview-grid:${projectId}`;
try {
localStorage.setItem(key, JSON.stringify(nextLayout));
} catch {
// Ignore storage errors (private mode / quota).
}
}, 250);
};
export default function useProjectLayout({ projectId, defaultLayout }) {
const [layout, setLayout] = useState(defaultLayout);
const saveTimerRef = useRef(null);
const defaultLayoutRef = useRef(defaultLayout);
const projectIdRef = useRef(projectId);
useEffect(() => {
return () => {
if (saveTimerRef.current) clearTimeout(saveTimerRef.current);
};
}, []);
useEffect(() => {
defaultLayoutRef.current = defaultLayout;
}, [defaultLayout]);
// ... rest of the hook implementation including the debounceSave logic
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/overview/useProjectLayout.js` around lines 39 - 49, Add an
unmount cleanup that clears the pending debounce timer so the queued save cannot
run after the component unmounts: ensure the effect or hook that sets
saveTimerRef.current (the function containing the setTimeout and
localStorage.setItem) returns a cleanup which checks saveTimerRef.current and
calls clearTimeout(saveTimerRef.current) and resets it (e.g.,
saveTimerRef.current = null). Reference the existing saveTimerRef.current and
the debounce block that calls setTimeout to locate where to add the cleanup.

@AboMeezO AboMeezO merged commit 3025934 into main Mar 19, 2026
2 checks passed
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