Skip to content

feat(desktop): setup/teardown scripts editor for v2 projects#4090

Merged
Kitenite merged 3 commits into
mainfrom
setup-teardown-scripts
May 5, 2026
Merged

feat(desktop): setup/teardown scripts editor for v2 projects#4090
Kitenite merged 3 commits into
mainfrom
setup-teardown-scripts

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented May 5, 2026

Summary

  • Brings back the project-settings UI for editing the .superset/config.json setup and teardown scripts for v2 projects (the editor existed for v1 only).
  • Generalizes the config.getConfigContent / updateConfig tRPC routes to accept either { projectId } (v1 local-db lookup) or { mainRepoPath } (v2 host project), so a single ScriptsEditor works in both surfaces.
  • Reworks editor behavior: save only on blur, trim only on blur (so pressing Enter during editing no longer trims trailing whitespace/newlines), and skip the network call when the trimmed value matches the last saved snapshot.
  • Refreshes the section UI to match the v2 settings style — shared <Textarea>, tighter heading + description pair, smaller h-7 action buttons, subtler drag-import overlay.
  • Run tab is hidden for now while v2 host-side runner support is still pending; existing run values are preserved through load/save.

Test plan

  • Open a v2 project's settings → "Scripts" section appears between Appearance and Delete; edits to Setup/Teardown persist to <repoPath>/.superset/config.json.
  • Open a v1 project's settings → editor still works exactly as before; existing config files are untouched.
  • Type into a textarea, press Enter to add a newline at the end → blur the textarea → newline is trimmed and a save fires once.
  • Type a change and immediately switch tabs → the change saves on blur (not while typing).
  • Type and revert to original → blur fires no network request.
  • Drag a .sh file onto a textarea → contents replace the value; blur saves it.
  • Open a v2 project whose config already has a non-empty run array → run value is preserved on subsequent saves of setup/teardown (not zeroed out by the hidden tab).

Summary by CodeRabbit

  • Refactor

    • Scripts editor redesigned into a unified view with focus-aware syncing, save-on-blur, simplified tabs and improved file-import UX.
  • New Features

    • Two editor variants supported (local and host-service); settings surfaces render the appropriate editor.
    • Dismissible "Setup scripts" sidebar card that links to project settings.
    • Backend config API to read/update project script configuration on disk.
  • Chores

    • Persistent store added to track dismissed setup cards.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 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
📝 Walkthrough

Walkthrough

Refactors the scripts editor into a shared view with two wrappers (V1 for electron TRPC, V2 for host-service client), adds a host-service TRPC router to read/write .superset/config.json, introduces V2 setup-card UI in the dashboard sidebar, and adds a persisted dismissal store for that card.

Changes

Versioned Scripts Editor & Host Config Router

Layer / File(s) Summary
Data Shape / Helpers
apps/.../ScriptsEditor/ScriptsEditor.tsx, packages/host-service/src/runtime/setup/config.ts
Adds ScriptKind/ScriptValues, EMPTY, parseContentFromConfig(), toArrayCommand(); introduces SetupConfig / Local overlay types and helpers to read/merge/validate config JSON from repo, local overlay, and per-user paths.
Editor Core Implementation
apps/.../ScriptsEditor/ScriptsEditor.tsx
Adds ScriptsEditorView with unified values: Record<ScriptKind,string>, focus-aware server syncing, commit() on blur, saveStatus handling, and rewrites ScriptTextarea to use UI Textarea with explicit focus/blur callbacks and file import behavior.
UI Tabing & File Import
apps/.../ScriptsEditor/ScriptsEditor.tsx
Simplifies tabs to render setup and teardown only (keeps run in state), restyles drag/drop overlay, clears file input value after import.
Backends / Wrappers
apps/.../ScriptsEditor/ScriptsEditor.tsx
Adds V1ScriptsEditor (electron TRPC: config.getConfigContent/config.updateConfig) and V2ScriptsEditor (host-service client via React Query) that delegate to ScriptsEditorView.
Re-exports & Integrations
apps/.../ScriptsEditor/index.ts, ProjectSettings.tsx, V2ProjectSettings.tsx
Re-exports now expose V1ScriptsEditor/V2ScriptsEditor. ProjectSettings switched to <V1ScriptsEditor />. V2ProjectSettings conditionally renders <V2ScriptsEditor /> when activeHostUrl is present.
Host Service Router
packages/host-service/src/trpc/router/config/config.ts, .../index.ts, .../router.ts
Adds configRouter with protected procedures: shouldShowSetupCard(projectId), getConfigContent(projectId), and updateConfig({projectId, setup, teardown, run}); reads/creates/merges/writes .superset/config.json and registers under config namespace.

V2 Setup Card & Dismissals

Layer / File(s) Summary
Store
apps/.../stores/v2-setup-card-dismissals/store.ts, index.ts
Adds persisted Zustand store useV2SetupCardDismissalsStore storing dismissedProjectIds and dismiss(projectId), persistence key "v2-setup-card-dismissals".
Sidebar Card Component
apps/.../V2SetupScriptCard/V2SetupScriptCard.tsx, index.ts
New V2SetupScriptCard component queries client.config.shouldShowSetupCard (enabled when hostUrl/projectId present and not dismissed) and renders a dismissible SidebarCard that navigates to project settings.
Sidebar Integration
apps/.../DashboardSidebar/DashboardSidebar.tsx
Derives active V2 workspace → projectId/name and activeHostUrl, inserts V2SetupScriptCard into the sidebar when applicable and not collapsed.

Sequence Diagram(s)

sequenceDiagram
    participant UI as ProjectSettings UI
    participant V1 as V1ScriptsEditor
    participant TRPC as Electron TRPC
    participant HostFS as Host Service / FS

    UI->>V1: mount(projectId)
    V1->>TRPC: config.getConfigContent({ projectId })
    TRPC->>HostFS: resolve repoPath & read .superset/config.json
    HostFS-->>TRPC: { content, exists }
    TRPC-->>V1: return config
    V1->>V1: render ScriptsEditorView(serverContent)

    UI->>V1: edit textarea
    V1->>V1: update local state
    UI->>V1: blur
    V1->>V1: commit() -> prepare arrays
    V1->>TRPC: config.updateConfig({ projectId, setup, teardown, run })
    TRPC->>HostFS: merge & write .superset/config.json
    HostFS-->>TRPC: success
    TRPC-->>V1: success
Loading
sequenceDiagram
    participant UI as V2ProjectSettings UI
    participant V2 as V2ScriptsEditor
    participant RQ as React Query (host-service)
    participant HostAPI as Host Service API
    participant FS as File System

    UI->>V2: mount(hostUrl, projectId)
    V2->>RQ: client.config.getConfigContent({ projectId })
    RQ->>HostAPI: request
    HostAPI->>FS: read .superset/config.json
    FS-->>HostAPI: content
    HostAPI-->>RQ: { content, exists }
    RQ-->>V2: return config
    V2->>V2: render ScriptsEditorView(serverContent)

    UI->>V2: edit textarea
    V2->>V2: update local state
    UI->>V2: blur
    V2->>V2: commit()
    V2->>RQ: client.config.updateConfig({ projectId, setup, teardown, run })
    RQ->>HostAPI: request
    HostAPI->>FS: write .superset/config.json
    FS-->>HostAPI: success
    HostAPI-->>RQ: ok
    RQ-->>V2: success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • superset-sh/superset#3606: touches V2 project settings surface and scripts editor wiring; likely related at the code-level.

Poem

🐰 I nibble lines where configs hide,

Two editors share one tidy stride,
V1 hums TRPC, V2 calls the host,
Blur saves gently, no data lost,
Hopping home where scripts abide.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: introducing a setup/teardown scripts editor UI for v2 projects, which aligns with the primary objective.
Description check ✅ Passed The description is comprehensive and well-structured, covering summary, implementation details, and a detailed test plan. It follows the repository's template with sections for description, type of change, and testing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch setup-teardown-scripts

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 5, 2026

Greptile Summary

  • Generalizes getConfigContent / updateConfig tRPC routes to accept either { projectId } (v1 local-DB lookup) or { mainRepoPath } (v2 host project) via a shared configTargetSchema, and wires the ScriptsEditor into the v2 project settings page.
  • Reworks editor to save on blur only, skip the network call when the trimmed value matches the last saved snapshot, and guard against server data overwriting in-flight edits via isFocusedRef.
  • Multi-element run arrays are silently collapsed to a single element on the first setup/teardown save because parseContentFromConfig joins elements with \ but toArrayCommand does not split on \ when writing back.

Confidence Score: 3/5

Not safe to merge as-is — the run array corruption bug permanently changes config data for any v2 project that stores multiple run commands.

One P1 defect (multi-element run arrays silently rewritten as a single-element array on every save) and one P2 (async side-effect in setState updater). The P1 is a data-loss issue directly on the new v2 code path added by this PR.

ScriptsEditor.tsx — toArrayCommand / commit logic around the run field needs to either split on newlines when serializing back or preserve the original array when the run tab is hidden.

Important Files Changed

Filename Overview
apps/desktop/src/lib/trpc/routers/config/config.ts Generalizes getConfigContent and updateConfig to accept either projectId or mainRepoPath via a union schema and shared resolveMainRepoPath helper. Logic is clean; z.intersection with a z.union behaves correctly at runtime.
apps/desktop/src/renderer/lib/project-scripts.ts Updates invalidateProjectScriptQueries to accept ProjectScriptsTarget; conditionally skips shouldShowSetupCard invalidation for v2 (mainRepoPath) targets. Correct.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/project/$projectId/page.tsx Trivial call-site update to pass { projectId } object to invalidateProjectScriptQueries. No issues.
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx Updates ScriptsEditor prop from projectId string to target object. Single-line change, no issues.
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx Major refactor: save-on-blur, run tab hidden, generalized target prop. Contains a P1 bug where multi-element run arrays are silently collapsed to a single element on save, plus a P2 pattern of calling async commit from inside a setState updater.
apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx Adds ScriptsEditor to v2 settings page, only rendered when hostProject.repoPath is available. Guard is correct; imports are clean.

Sequence Diagram

sequenceDiagram
    participant UI as ScriptsEditor (React)
    participant tRPC as tRPC Router
    participant FS as Filesystem

    Note over UI: User blurs textarea
    UI->>UI: handleBlur(kind) — trim, set isFocusedRef=false
    UI->>UI: commit(next) — compare vs lastSavedRef
    alt values changed
        UI->>tRPC: updateConfig({ target | mainRepoPath, setup, teardown, run })
        tRPC->>tRPC: resolveMainRepoPath(input)
        alt mainRepoPath in input
            tRPC-->>tRPC: use input.mainRepoPath directly
        else projectId in input
            tRPC->>tRPC: localDb lookup → project.mainRepoPath
        end
        tRPC->>FS: read existing config.json
        tRPC->>FS: write merged config.json
        tRPC-->>UI: { success: true }
        UI->>tRPC: invalidateProjectScriptQueries(target)
        Note over UI: saveStatus = "saved"
    else no change
        Note over UI: skip network call
    end
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx:155-165
**Multi-element `run` array silently corrupted on save**

When `.superset/config.json` contains `"run": ["cmd1", "cmd2"]` (multiple elements), `parseContentFromConfig` joins them as `"cmd1\ncmd2"`, and `toArrayCommand` wraps the whole trimmed string into a single element `["cmd1\ncmd2"]`. Every save of setup/teardown therefore overwrites a multi-element `run` array with a single-element array, permanently changing the config. The test plan specifically promises run values are preserved for v2 projects, but this round-trip breaks multi-element arrays on the first edit of any other tab.

### Issue 2 of 2
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx:258-272
**Async side-effect inside `setState` updater**

`commit` (an async function) is called from within the `setValues` functional updater. React is permitted to call state updaters more than once (it does so intentionally in Strict Mode dev builds to detect impure updaters), which would fire two concurrent `mutateAsync` network requests on every blur. Extract the commit call outside the updater:

```
const handleBlur = useCallback(
  (kind: ScriptKind) => {
    isFocusedRef.current = false;
    const trimmed = values[kind].trim();
    const next =
      trimmed === values[kind]
        ? values
        : { ...values, [kind]: trimmed };
    if (next !== values) setValues(next);
    void commit(next);
  },
  [commit, values],
);
```

Reviews (1): Last reviewed commit: "feat(desktop): bring back setup/teardown..." | Re-trigger Greptile

Comment on lines +155 to +165
type="file"
accept=".sh,.bash,.zsh,.command"
onChange={handleFileInputChange}
className="hidden"
/>
</div>
</div>
);
}

type SaveStatus = "idle" | "saving" | "saved";
const TAB_DESCRIPTIONS: Record<ScriptKind, string> = {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Multi-element run array silently corrupted on save

When .superset/config.json contains "run": ["cmd1", "cmd2"] (multiple elements), parseContentFromConfig joins them as "cmd1\ncmd2", and toArrayCommand wraps the whole trimmed string into a single element ["cmd1\ncmd2"]. Every save of setup/teardown therefore overwrites a multi-element run array with a single-element array, permanently changing the config. The test plan specifically promises run values are preserved for v2 projects, but this round-trip breaks multi-element arrays on the first edit of any other tab.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx
Line: 155-165

Comment:
**Multi-element `run` array silently corrupted on save**

When `.superset/config.json` contains `"run": ["cmd1", "cmd2"]` (multiple elements), `parseContentFromConfig` joins them as `"cmd1\ncmd2"`, and `toArrayCommand` wraps the whole trimmed string into a single element `["cmd1\ncmd2"]`. Every save of setup/teardown therefore overwrites a multi-element `run` array with a single-element array, permanently changing the config. The test plan specifically promises run values are preserved for v2 projects, but this round-trip breaks multi-element arrays on the first edit of any other tab.

How can I resolve this? If you propose a fix, please make it concise.

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

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx (1)

29-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add Array.isArray guards so a malformed field doesn't wipe valid ones.

If .superset/config.json has e.g. setup: "echo hi" (string instead of array), (parsed.setup ?? []).join("\n") throws, the catch path runs, and the user sees EMPTY for setup, teardown, and run — losing visibility into otherwise-valid fields. The dashboard's parseConfigContent already uses Array.isArray for exactly this reason.

🛠️ Proposed fix
 function parseContentFromConfig(content: string | null): ScriptValues {
 	if (!content) return { ...EMPTY };
 	try {
 		const parsed = JSON.parse(content);
+		const toLines = (v: unknown): string =>
+			Array.isArray(v)
+				? v.filter((s): s is string => typeof s === "string").join("\n")
+				: "";
 		return {
-			setup: (parsed.setup ?? []).join("\n"),
-			teardown: (parsed.teardown ?? []).join("\n"),
-			run: (parsed.run ?? []).join("\n"),
+			setup: toLines(parsed?.setup),
+			teardown: toLines(parsed?.teardown),
+			run: toLines(parsed?.run),
 		};
 	} catch {
 		return { ...EMPTY };
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx
around lines 29 - 41, The parseContentFromConfig function currently JSON.parses
and assumes parsed.setup/teardown/run are arrays, so a malformed one causes the
catch branch and wipes all fields; update parseContentFromConfig to guard each
field using Array.isArray before calling .join (e.g., for setup, use
Array.isArray(parsed.setup) ? parsed.setup.join("\n") : ""), repeating the same
pattern for teardown and run so a single bad field doesn't clear the others
while still falling back to EMPTY when content is null or parsing fails.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/lib/trpc/routers/config/config.ts`:
- Around line 468-485: Tighten validation and clarify errors: make
configTargetSchema require a non-empty mainRepoPath (use z.string().min(1)) so
empty strings fail validation up-front, and in updateConfig split the failure
cases — if input.mainRepoPath is empty/invalid return a clear validation error
(e.g., "mainRepoPath must be a non-empty string"); keep the separate branch for
resolveMainRepoPath(input) returning falsy and throw "Project not found" (or
similar) so callers can distinguish an invalid input from a missing project;
ensure references in this logic point to configTargetSchema, updateConfig,
resolveMainRepoPath, getConfigContent and ensureConfigExists.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx:
- Around line 177-180: The toArrayCommand function currently collapses
newline-joined multi-command text into a single-string array element; change its
logic to mirror splitCommands by splitting the trimmed value on newlines,
trimming each line, filtering out empty lines, and returning that array so
multi-command script arrays (run/setup/teardown) are preserved; update any other
uses in this file (including the blur/commit path and the similar code
referenced around lines 209–242) to call the revised toArrayCommand so commit
sends back a proper string[] rather than one newline-containing string.

---

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx:
- Around line 29-41: The parseContentFromConfig function currently JSON.parses
and assumes parsed.setup/teardown/run are arrays, so a malformed one causes the
catch branch and wipes all fields; update parseContentFromConfig to guard each
field using Array.isArray before calling .join (e.g., for setup, use
Array.isArray(parsed.setup) ? parsed.setup.join("\n") : ""), repeating the same
pattern for teardown and run so a single bad field doesn't clear the others
while still falling back to EMPTY when content is null or parsing fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fa4c30ef-5374-4507-a5d2-8fc16c5994cd

📥 Commits

Reviewing files that changed from the base of the PR and between 52ab188 and bb3e222.

📒 Files selected for processing (6)
  • apps/desktop/src/lib/trpc/routers/config/config.ts
  • apps/desktop/src/renderer/lib/project-scripts.ts
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/project/$projectId/page.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx

Comment thread apps/desktop/src/lib/trpc/routers/config/config.ts Outdated
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

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.

♻️ Duplicate comments (1)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx (1)

43-46: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

toArrayCommand still collapses multi-line content into a single array element — corrupts existing run arrays on first save.

This was flagged on a previous commit but the implementation is unchanged. Because commit() always sends all three fields (including run, whose tab is now hidden), an existing config like run: ["bun run dev", "echo started"] round-trips as:

  1. parseContentFromConfig joins the array → values.run === "bun run dev\necho started".
  2. Any blur on Setup/Teardown calls commit(values).
  3. toArrayCommand(values.run) returns ["bun run dev\necho started"] (single element with embedded \n).
  4. Server persists the corrupted single-element array.

This silently breaks the PR's own test-plan assertion "Existing non-empty run arrays in v2 configs are preserved despite the hidden Run tab." The dashboard's splitCommands already does the right thing.

🛠️ Proposed fix
-function toArrayCommand(value: string): string[] {
-	const trimmed = value.trim();
-	return trimmed ? [trimmed] : [];
-}
+function toArrayCommand(value: string): string[] {
+	return value
+		.split("\n")
+		.map((line) => line.trim())
+		.filter((line) => line.length > 0);
+}

Also applies to: 234-238

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx
around lines 43 - 46, The toArrayCommand function currently returns a single
element containing embedded newlines; change it to split multi-line input into
separate command strings by splitting on newlines (handle \r\n and \n), trim
each line and filter out empty lines so an input like "bun run dev\necho
started" becomes ["bun run dev", "echo started"]; update the function named
toArrayCommand (and the duplicate implementation referenced in the same file) to
perform value.split(/\r?\n/).map(s => s.trim()).filter(Boolean) and return that
array.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx:
- Around line 43-46: The toArrayCommand function currently returns a single
element containing embedded newlines; change it to split multi-line input into
separate command strings by splitting on newlines (handle \r\n and \n), trim
each line and filter out empty lines so an input like "bun run dev\necho
started" becomes ["bun run dev", "echo started"]; update the function named
toArrayCommand (and the duplicate implementation referenced in the same file) to
perform value.split(/\r?\n/).map(s => s.trim()).filter(Boolean) and return that
array.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 57fffda9-2d56-422b-bea3-0f77e70bc915

📥 Commits

Reviewing files that changed from the base of the PR and between bb3e222 and e8fa37a.

📒 Files selected for processing (7)
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/ProjectSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx
  • packages/host-service/src/trpc/router/config/config.ts
  • packages/host-service/src/trpc/router/config/index.ts
  • packages/host-service/src/trpc/router/router.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/host-service/src/trpc/router/config/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/settings/v2-project/$projectId/components/V2ProjectSettings/V2ProjectSettings.tsx

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.

🧹 Nitpick comments (1)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/V2SetupScriptCard/V2SetupScriptCard.tsx (1)

48-73: 💤 Low value

exit animation on motion.div is unreachable

AnimatePresence lives inside V2SetupScriptCard, which returns null (not <AnimatePresence>) when the card should hide. Because the whole component unmounts before Framer Motion can orchestrate the exit, exit={{ opacity: 0, y: 10 }} never fires — the card disappears instantly on dismiss or when shouldShow flips to false.

To get the exit animation, AnimatePresence needs to wrap the conditional render from the outside (e.g., in DashboardSidebar):

♻️ Proposed fix — lift AnimatePresence to the caller

In DashboardSidebar.tsx, replace the bare <V2SetupScriptCard> with:

+<AnimatePresence>
   <V2SetupScriptCard
     hostUrl={activeHostUrl}
     projectId={activeWorkspaceProject?.id ?? null}
     projectName={activeWorkspaceProject?.name ?? null}
     isCollapsed={isCollapsed}
   />
+</AnimatePresence>

And in V2SetupScriptCard.tsx, drop the AnimatePresence wrapper but keep motion.div:

-return (
-  <AnimatePresence>
-    <motion.div
-      key={projectId}
-      ...
-    >
-      <SidebarCard ... />
-    </motion.div>
-  </AnimatePresence>
-);
+return (
+  <motion.div
+    key={projectId ?? "card"}
+    initial={{ opacity: 0, y: 10 }}
+    animate={{ opacity: 1, y: 0 }}
+    exit={{ opacity: 0, y: 10 }}
+    transition={{ duration: 0.2 }}
+    className="px-3 pb-2"
+  >
+    <SidebarCard ... />
+  </motion.div>
+);

And expose the inner conditional logic to the parent so AnimatePresence can track the child's lifecycle.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/V2SetupScriptCard/V2SetupScriptCard.tsx`
around lines 48 - 73, The exit animation never runs because AnimatePresence is
inside V2SetupScriptCard so the component unmounts before Framer Motion can play
the exit; move AnimatePresence out to the parent (DashboardSidebar) so it wraps
the conditional render of V2SetupScriptCard, remove the AnimatePresence wrapper
from V2SetupScriptCard and keep only the motion.div with its exit prop, and
expose the card's show/hide conditional (e.g., the shouldShow/visible logic) to
DashboardSidebar so AnimatePresence can track the child lifecycle and allow
exit={{ opacity: 0, y: 10 }} on the motion.div to run.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/V2SetupScriptCard/V2SetupScriptCard.tsx`:
- Around line 48-73: The exit animation never runs because AnimatePresence is
inside V2SetupScriptCard so the component unmounts before Framer Motion can play
the exit; move AnimatePresence out to the parent (DashboardSidebar) so it wraps
the conditional render of V2SetupScriptCard, remove the AnimatePresence wrapper
from V2SetupScriptCard and keep only the motion.div with its exit prop, and
expose the card's show/hide conditional (e.g., the shouldShow/visible logic) to
DashboardSidebar so AnimatePresence can track the child lifecycle and allow
exit={{ opacity: 0, y: 10 }} on the motion.div to run.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dd41ff2c-e3a7-4a2c-85ba-a9fe9dfc43ba

📥 Commits

Reviewing files that changed from the base of the PR and between e8fa37a and f4a9212.

📒 Files selected for processing (6)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/DashboardSidebar.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/V2SetupScriptCard/V2SetupScriptCard.tsx
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/V2SetupScriptCard/index.ts
  • apps/desktop/src/renderer/stores/v2-setup-card-dismissals/index.ts
  • apps/desktop/src/renderer/stores/v2-setup-card-dismissals/store.ts
  • packages/host-service/src/trpc/router/config/config.ts
✅ Files skipped from review due to trivial changes (2)
  • apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/V2SetupScriptCard/index.ts
  • apps/desktop/src/renderer/stores/v2-setup-card-dismissals/index.ts

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.

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx (1)

33-37: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid wiping all script fields when one parsed field is malformed.

(parsed.setup ?? []).join("\n") assumes array shape. If one field is not an array, parsing falls into catch and resets all values to empty, which can lead to destructive overwrite on next save.

Proposed fix
 function parseContentFromConfig(content: string | null): ScriptValues {
 	if (!content) return { ...EMPTY };
 	try {
 		const parsed = JSON.parse(content);
+		const toText = (value: unknown): string =>
+			Array.isArray(value)
+				? value
+						.filter((v): v is string => typeof v === "string")
+						.join("\n")
+				: "";
 		return {
-			setup: (parsed.setup ?? []).join("\n"),
-			teardown: (parsed.teardown ?? []).join("\n"),
-			run: (parsed.run ?? []).join("\n"),
+			setup: toText(parsed.setup),
+			teardown: toText(parsed.teardown),
+			run: toText(parsed.run),
 		};
 	} catch {
 		return { ...EMPTY };
 	}
 }

Also applies to: 39-40

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx
around lines 33 - 37, The current code blindly does (parsed.setup ??
[]).join("\n") (and same for teardown/run) which treats non-array values as
empty and can reset all fields if one parse error occurs; update ScriptsEditor
to check each parsed field with Array.isArray(parsed.setup) ?
parsed.setup.join("\n") : preserve the existing value (e.g., the current state
or original script string) for setup, and do the same for teardown and run so a
malformed field doesn't wipe the others — reference the parsed object and the
setup/teardown/run properties when making these checks and use safe fallbacks
instead of joining an assumed array.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/project/`$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx:
- Around line 33-37: The current code blindly does (parsed.setup ??
[]).join("\n") (and same for teardown/run) which treats non-array values as
empty and can reset all fields if one parse error occurs; update ScriptsEditor
to check each parsed field with Array.isArray(parsed.setup) ?
parsed.setup.join("\n") : preserve the existing value (e.g., the current state
or original script string) for setup, and do the same for teardown and run so a
malformed field doesn't wipe the others — reference the parsed object and the
setup/teardown/run properties when making these checks and use safe fallbacks
instead of joining an assumed array.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 931628f9-b098-4104-84f0-2cae037adc6d

📥 Commits

Reviewing files that changed from the base of the PR and between f4a9212 and 6c6d88f.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/routes/_authenticated/settings/project/$projectId/components/ProjectSettings/components/ScriptsEditor/ScriptsEditor.tsx

Plan-only. Code changes deferred until the plan is reviewed.

Scope is strictly v2 — no v1 ScriptsEditor / electronTrpc config router /
v1 SetupScriptCard touches. Per project rule: v1 desktop UI is sunset.
Adds the v2 equivalent of the v1 project-settings scripts editor and
makes v2 workspace creation honor the configured setup array (was only
running a literal `<worktree>/.superset/setup.sh`).

- host-service config loader resolves `<repoPath>/.superset/config.json`
  + `~/.superset/projects/<id>/config.json` user override + local
  `.superset/config.local.json` overlay; no worktree-level read so the
  main repo stays the single source of truth.
- new host-service `config` router: getConfigContent, updateConfig,
  shouldShowSetupCard.
- setup-terminal helper joins resolved setup commands with `&&` and
  falls back to `bash <repoPath>/.superset/setup.sh` against the main
  repo. Worktrees reach the canonical .superset/ via $SUPERSET_ROOT_PATH.
- V2ScriptsEditor mounts in V2ProjectSettings (Setup/Teardown tabs,
  blur-only save, multi-line→array, drag-drop import).
- V2SetupScriptCard sidebar CTA with a per-machine zustand+persist
  dismissals store, gated on the active v2 workspace route.

v1 paths intentionally untouched.
…solver

Adds integration tests for the v2 setup/teardown work:

- config loader: malformed JSON, mixed-type rejection, override
  precedence, before/after/replace overlay, three-layer stacking,
  empty-array override clearing base, partial-keys passthrough,
  path-traversal guard, no worktree consultation.
- config router (in-memory drizzle): getConfigContent / updateConfig
  (preserves run + unrelated keys, overwrites malformed) /
  shouldShowSetupCard (run-only, teardown-only, all-empty cases).
- resolveInitialCommand: && joining, fallback to setup.sh, single-quote
  escape, config-wins-over-fallback, whitespace filter.

Adds optional `homeDir` parameter to loadSetupConfig and
resolveInitialCommand to allow test sandboxes without process.env
mutation. Production callers don't pass it (defaults to os.homedir()).
@Kitenite Kitenite merged commit 4ee044c into main May 5, 2026
11 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