Skip to content

feat(templates): Electrobun shell template for phenotype web apps#81

Open
KooshaPari wants to merge 3 commits into
mainfrom
feat/phenotype-diff
Open

feat(templates): Electrobun shell template for phenotype web apps#81
KooshaPari wants to merge 3 commits into
mainfrom
feat/phenotype-diff

Conversation

@KooshaPari
Copy link
Copy Markdown
Owner

@KooshaPari KooshaPari commented May 31, 2026

User description

Summary

  • Adds templates/electrobun-shell/ — parameterized Electrobun desktop shell template
  • adopt.sh generates a ready-to-run shell in one command (name, renderer URL, process-compose services, window size)
  • One-click service boot: on launch runs process-compose up -d
  • Reference impl: Tracera frontend/apps/desktop-electrobun

Usage

./templates/electrobun-shell/adopt.sh \
  --app-name "MyApp" --app-id "com.example.myapp" \
  --renderer-url "http://localhost:3000" \
  --views-entrypoint "../web/dist/index.html" \
  --compose "/path/to/process-compose.yml" \
  --out ./apps/desktop

Test plan

  • Run adopt.sh, verify substituted files correct
  • bun install && bun dev on macOS opens window
  • SERVICES_COMPOSE_FILE set → process-compose boots on launch

🤖 Generated with Claude Code


Note

Low Risk
New template and standalone library with no changes to existing runtime crates; desktop launch only runs optional local process-compose when configured.

Overview
Adds templates/electrobun-shell/ so Phenotype web apps can be wrapped as desktop apps via Electrobun: parameterized config, adopt.sh to copy the template and substitute app name/id, dev renderer URL, bundled views path, and optional process-compose path, plus generated .env.example and justfile. The main process optionally runs process-compose up -d on launch, opens a configurable webview URL or bundled views://app, and ships a default window and app menu.

Also introduces workspace crate phenotype-diff: line-level diff / apply on top of similar, serde-friendly UnifiedDiff models, and DiffError for context mismatch, out-of-range hunks, and malformed patches, with unit tests for round-trips. Cargo.toml / Cargo.lock register the crate and similar dependency.

Reviewed by Cursor Bugbot for commit 9cc5fca. Bugbot is set up for automated code reviews on this repo. Configure here.


CodeAnt-AI Description

Add a reusable desktop shell template and a unified diff library

What Changed

  • Added a ready-to-adopt Electrobun shell template for wrapping Phenotype web apps as desktop apps
  • The shell opens a window with your chosen renderer URL, can start local services on launch when configured, and includes a standard app menu and window setup
  • Added an adopt.sh script that copies the template, fills in app details, and writes startup settings for the new desktop app
  • Added a new phenotype-diff library that can create line-based unified diffs and apply them back to text, with clear errors for mismatched or invalid patches

Impact

✅ Faster desktop app setup
✅ One-command local service startup
✅ Safer patch application for text edits

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

KooshaPari and others added 2 commits May 29, 2026 19:33
Implements line-level unified diff + patch-apply wrapping `similar` 2.7;
exposes diff()/apply() with UnifiedDiff model + 12 TDD tests + 1 doctest.
Preserves intent of archived KooshaPari/Diffuse `patch` skeleton.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds templates/electrobun-shell — a parameterized Electrobun desktop shell
template. adopt.sh generates an app-specific shell (name, renderer URL,
process-compose services, window config) in one command.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 31, 2026 01:08
@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented May 31, 2026

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 31, 2026

Warning

Review limit reached

@KooshaPari, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 57 minutes and 41 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3e9205fe-4eb2-403a-b687-be1fe524bda8

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca6c61 and 9cc5fca.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • Cargo.toml
  • crates/phenotype-diff/Cargo.toml
  • crates/phenotype-diff/src/error.rs
  • crates/phenotype-diff/src/lib.rs
  • crates/phenotype-diff/src/model.rs
  • crates/phenotype-diff/src/ops.rs
  • templates/electrobun-shell/README.md
  • templates/electrobun-shell/adopt.sh
  • templates/electrobun-shell/electrobun.config.ts
  • templates/electrobun-shell/package.json
  • templates/electrobun-shell/src/main.ts
  • templates/electrobun-shell/tsconfig.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/phenotype-diff
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/phenotype-diff

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.

@codeant-ai codeant-ai Bot added the size:XL This PR changes 500-999 lines, ignoring generated files label May 31, 2026
@@ -0,0 +1,98 @@
#!/usr/bin/env bash
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Re-implement this adoption workflow in a Rust CLI (or route through an existing Rust binary) and keep shell usage to a thin invocation layer only, because project governance is Rust-first and disallows introducing new Bash implementations. [custom_rule]

Severity Level: Minor ⚠️

Why it matters? 🤔

The repository governance explicitly states "Rust-first" and the README says "Rust default; no new Bash." This file is a brand-new Bash implementation, so the suggestion correctly identifies a real rule violation in the existing code.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** templates/electrobun-shell/adopt.sh
**Line:** 1:1
**Comment:**
	*Custom Rule: Re-implement this adoption workflow in a Rust CLI (or route through an existing Rust binary) and keep shell usage to a thin invocation layer only, because project governance is Rust-first and disallows introducing new Bash implementations.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new Rust crate, phenotype-diff, for line-level unified diff and patch operations, alongside an Electrobun-based desktop shell template for wrapping web applications. The feedback suggests adding validation to prevent out-of-order or overlapping hunks in the diff application logic. For the desktop template, recommendations include removing an unused import, safely handling potential NaN values during window dimension parsing, generating a .gitignore file during adoption, correcting the TypeScript types reference for Bun, and replacing local absolute file paths in the documentation with relative ones.

Comment on lines +101 to +109
let hunk_old_start = hunk.header.old_start.saturating_sub(1); // convert to 0-based

// Validate the hunk start is reachable
if hunk_old_start > source_len {
return Err(DiffError::OutOfRange {
line: hunk.header.old_start,
source_len,
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

To prevent issues with malformed, out-of-order, or overlapping hunks in a patch, we should validate that the hunk's starting line is not before the current source cursor position.

Suggested change
let hunk_old_start = hunk.header.old_start.saturating_sub(1); // convert to 0-based
// Validate the hunk start is reachable
if hunk_old_start > source_len {
return Err(DiffError::OutOfRange {
line: hunk.header.old_start,
source_len,
});
}
let hunk_old_start = hunk.header.old_start.saturating_sub(1); // convert to 0-based
if hunk_old_start < src_cursor {
return Err(DiffError::Malformed(
"hunks are out of order or overlapping".to_string(),
));
}
// Validate the hunk start is reachable
if hunk_old_start > source_len {
return Err(DiffError::OutOfRange {
line: hunk.header.old_start,
source_len,
});
}

Comment on lines +10 to +12
import { BrowserWindow, ApplicationMenu } from "electrobun/bun";
import { $ } from "bun";
import { join } from "node:path";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The join function imported from node:path is not used anywhere in this file and can be safely removed.

Suggested change
import { BrowserWindow, ApplicationMenu } from "electrobun/bun";
import { $ } from "bun";
import { join } from "node:path";
import { BrowserWindow, ApplicationMenu } from "electrobun/bun";
import { $ } from "bun";

Comment on lines +50 to +51
width: parseInt(process.env.WINDOW_WIDTH ?? "1400"),
height: parseInt(process.env.WINDOW_HEIGHT ?? "900"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If WINDOW_WIDTH or WINDOW_HEIGHT environment variables are set to invalid non-numeric values, parseInt will return NaN, which could cause issues when creating the BrowserWindow. Adding a fallback using the logical OR operator ensures we always have valid dimensions.

Suggested change
width: parseInt(process.env.WINDOW_WIDTH ?? "1400"),
height: parseInt(process.env.WINDOW_HEIGHT ?? "900"),
width: parseInt(process.env.WINDOW_WIDTH ?? "1400") || 1400,
height: parseInt(process.env.WINDOW_HEIGHT ?? "900") || 900,

Comment on lines +55 to +67
# Write .env.example
cat > "$OUT_DIR/.env.example" <<EOF
# Override renderer URL (default: $RENDERER_URL)
RENDERER_URL=$RENDERER_URL

# Path to process-compose.yml for one-click service boot
# Leave unset to skip service boot
SERVICES_COMPOSE_FILE=${COMPOSE_FILE}

# Optional window size overrides
# WINDOW_WIDTH=1400
# WINDOW_HEIGHT=900
EOF
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

When adopting the desktop shell template, it is highly recommended to generate a .gitignore file in the output directory to prevent users from accidentally committing node_modules, build artifacts, or sensitive .env files.

# Write .env.example
cat > "$OUT_DIR/.env.example" <<EOF
# Override renderer URL (default: $RENDERER_URL)
RENDERER_URL=$RENDERER_URL

# Path to process-compose.yml for one-click service boot
# Leave unset to skip service boot
SERVICES_COMPOSE_FILE=${COMPOSE_FILE}

# Optional window size overrides
# WINDOW_WIDTH=1400
# WINDOW_HEIGHT=900
EOF

# Write .gitignore
cat > "$OUT_DIR/.gitignore" <<EOF
/node_modules
/dist
/.env
/.electrobun
EOF

"strict": true,
"noUncheckedIndexedAccess": true,
"lib": ["ESNext"],
"types": ["bun-types"],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Since @types/bun is installed as a devDependency in package.json, the corresponding TypeScript types should be referenced as "bun" instead of "bun-types". Referencing "bun-types" when it is not installed will cause tsc to fail with a missing type definition file error.

Suggested change
"types": ["bun-types"],
"types": ["bun"],


## Reference: Tracera migration

`E:/Dev/Tracera/frontend/apps/desktop-electrobun` is the production reference implementation,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Avoid referencing local absolute development paths (such as E:/Dev/Tracera/...) in the documentation. Use a relative path or a generic reference instead.

Suggested change
`E:/Dev/Tracera/frontend/apps/desktop-electrobun` is the production reference implementation,
`frontend/apps/desktop-electrobun` is the production reference implementation,

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.


while [[ $# -gt 0 ]]; do
case $1 in
--app-name) APP_NAME="$2"; shift 2 ;;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Each option branch reads $2 and does shift 2 without first verifying that a value is present. If a caller passes a flag at the end (for example --app-name with no value), set -u makes this crash with an unbound variable instead of a controlled error message. Add an explicit arity check before dereferencing $2 for value-taking flags. [incorrect condition logic]

Severity Level: Major ⚠️
- ❌ Adoption script crashes on missing option values.
- ⚠️ Users see cryptic unbound variable error message.
Steps of Reproduction ✅
1. From the repo root, run `bash templates/electrobun-shell/adopt.sh --app-name` with no
value for `--app-name`.

2. Argument parsing loop at `templates/electrobun-shell/adopt.sh:22-33` matches `$1` to
`--app-name` and executes the branch at line 24.

3. With only one argument, `$2` is unset; due to `set -u` at line 12, dereferencing
`APP_NAME="$2"` at line 24 triggers an "unbound variable" error and immediate exit.

4. The script terminates before reaching required-argument checks at lines 35-37, and the
user sees a cryptic shell error instead of a controlled "missing value" message.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** templates/electrobun-shell/adopt.sh
**Line:** 24:24
**Comment:**
	*Incorrect Condition Logic: Each option branch reads `$2` and does `shift 2` without first verifying that a value is present. If a caller passes a flag at the end (for example `--app-name` with no value), `set -u` makes this crash with an unbound variable instead of a controlled error message. Add an explicit arity check before dereferencing `$2` for value-taking flags.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +46 to +52
sed \
-e "s|__APP_NAME__|$APP_NAME|g" \
-e "s|__APP_ID__|$APP_ID|g" \
-e "s|__APP_VERSION__|$APP_VERSION|g" \
-e "s|__DEFAULT_DEV_URL__|$RENDERER_URL|g" \
-e "s|__VIEWS_ENTRYPOINT__|$VIEWS_ENTRYPOINT|g" \
"$TEMPLATE_DIR/$SRC" > "$DEST"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Raw user-provided strings are injected directly into sed replacement expressions. Values containing replacement metacharacters like &, \, or the chosen delimiter can corrupt substitutions and generate broken template files. Escape replacement strings (or use a safer templating approach) before passing them to sed. [logic error]

Severity Level: Major ⚠️
- ❌ Generated Electrobun config contains corrupted app name or URLs.
- ⚠️ Desktop shell may point at wrong renderer endpoint.
Steps of Reproduction ✅
1. Run `bash templates/electrobun-shell/adopt.sh --app-name 'Foo & Bar' --app-id
com.example.app --renderer-url 'http://localhost:3000/?foo=1&bar=2' --views-entrypoint
'../web/dist/index.html' --out /tmp/desktop`.

2. The loop at `templates/electrobun-shell/adopt.sh:42-53` copies each template file,
invoking `sed` at lines 46-52 with unescaped replacement values like `$APP_NAME` and
`$RENDERER_URL`.

3. Because the values contain `&`, `sed` treats `&` as "the matched text" in the
replacement section (e.g., lines 47-51), altering the intended strings when generating
files under `/tmp/desktop`.

4. Inspect a generated file such as `/tmp/desktop/electrobun.config.ts` or
`/tmp/desktop/package.json` and observe that the app name or renderer URL no longer
matches the original input, indicating corrupted templating due to unescaped `sed`
replacements.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** templates/electrobun-shell/adopt.sh
**Line:** 46:52
**Comment:**
	*Logic Error: Raw user-provided strings are injected directly into `sed` replacement expressions. Values containing replacement metacharacters like `&`, `\`, or the chosen delimiter can corrupt substitutions and generate broken template files. Escape replacement strings (or use a safer templating approach) before passing them to `sed`.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +58 to +62
RENDERER_URL=$RENDERER_URL

# Path to process-compose.yml for one-click service boot
# Leave unset to skip service boot
SERVICES_COMPOSE_FILE=${COMPOSE_FILE}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: Environment values are written unquoted into .env.example, so paths/URLs containing spaces or # will be parsed incorrectly when copied into .env and loaded by just/dotenv. Quote or escape these values when writing the file to avoid truncation and invalid env parsing. [logic error]

Severity Level: Major ⚠️
- ❌ Dotenv misparses URL or path containing `#` character.
- ⚠️ Desktop shell misconfigured when env copied from example.
Steps of Reproduction ✅
1. Run `bash templates/electrobun-shell/adopt.sh --app-name MyApp --app-id
com.example.myapp --renderer-url 'http://localhost:3000/#dashboard' --views-entrypoint
'../web/dist/index.html' --out /tmp/desktop`.

2. The `.env.example` block at `templates/electrobun-shell/adopt.sh:55-67` writes
`RENDERER_URL=$RENDERER_URL` at line 58 and `SERVICES_COMPOSE_FILE=${COMPOSE_FILE}` at
line 62 without any quoting or escaping.

3. Copy `/tmp/desktop/.env.example` to `/tmp/desktop/.env` and run `just dev` in
`/tmp/desktop`, which uses `set dotenv-load` at line 72 of the generated `justfile` to
load `.env`.

4. The dotenv loader parses `RENDERER_URL=http://localhost:3000/#dashboard` and treats the
`#dashboard` fragment as a comment, so the effective value is truncated to
`http://localhost:3000/`, causing the desktop shell to use an unintended renderer URL;
similarly, spaces in `SERVICES_COMPOSE_FILE` paths would be parsed incorrectly.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** templates/electrobun-shell/adopt.sh
**Line:** 58:62
**Comment:**
	*Logic Error: Environment values are written unquoted into `.env.example`, so paths/URLs containing spaces or `#` will be parsed incorrectly when copied into `.env` and loaded by `just`/dotenv. Quote or escape these values when writing the file to avoid truncation and invalid env parsing.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +95 to +96
let source_lines: Vec<&str> = source.split_inclusive('\n').collect();
let source_len = source_lines.len();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: source.split_inclusive('\n') treats an empty source as one pseudo-line ([""]), so source_len becomes 1 instead of 0. This makes out-of-range checks too permissive for empty inputs and can allow invalid hunk start positions to pass validation. Handle the empty-source case explicitly so line indexing reflects real line count. [off-by-one]

Severity Level: Major ⚠️
- ❌ Empty-file patches can declare impossible line numbers undetected.
- ⚠️ OutOfRange error reports wrong line count for empty sources.
Steps of Reproduction ✅
1. Add a new unit test in `crates/phenotype-diff/src/ops.rs` alongside
`diff_preserves_empty_string` (lines 27-33) that calls `apply` with an empty source string
(`let src = \"\";`).

2. Construct a `UnifiedDiff` manually using `HunkHeader`/`Hunk` from
`crates/phenotype-diff/src/model.rs` (lines 23-41), e.g. `UnifiedDiff { hunks: vec![Hunk {
header: HunkHeader { old_start: 2, old_lines: 0, new_start: 2, new_lines: 1 }, lines:
vec![DiffLine::Added(\"new\n\".into())] }] }`.

3. Call `apply(src, &patch)` which executes `source.split_inclusive('\n')` and
`source_lines.len()` at `crates/phenotype-diff/src/ops.rs:95-96`, producing `source_len ==
1` for the empty string.

4. Observe that `hunk.header.old_start == 2` yields `hunk_old_start == 1`, which passes
the `hunk_old_start > source_len` check at `ops.rs:103-108`, allowing a hunk that refers
to line 2 of a logically zero-line source to be treated as in-range instead of returning
`DiffError::OutOfRange { line: 2, source_len: 0 }` as the API contract suggests.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/phenotype-diff/src/ops.rs
**Line:** 95:96
**Comment:**
	*Off By One: `source.split_inclusive('\n')` treats an empty source as one pseudo-line (`[""]`), so `source_len` becomes `1` instead of `0`. This makes out-of-range checks too permissive for empty inputs and can allow invalid hunk start positions to pass validation. Handle the empty-source case explicitly so line indexing reflects real line count.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +101 to +118
let hunk_old_start = hunk.header.old_start.saturating_sub(1); // convert to 0-based

// Validate the hunk start is reachable
if hunk_old_start > source_len {
return Err(DiffError::OutOfRange {
line: hunk.header.old_start,
source_len,
});
}

// Copy source lines that precede this hunk
while src_cursor < hunk_old_start {
output.push(source_lines[src_cursor].to_owned());
src_cursor += 1;
}

// Apply each diff line in the hunk
for dl in &hunk.lines {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The apply logic never validates that hunk.header.old_lines/new_lines match the actual number of removed/context/added lines in the hunk body. As a result, malformed patches with inconsistent headers are silently accepted or misapplied instead of being rejected (despite having a Malformed error variant). Add a pre-check per hunk to enforce header/body consistency before applying. [incomplete implementation]

Severity Level: Major ⚠️
- ⚠️ Malformed patches may apply and silently change extra lines.
- ⚠️ DiffError::Malformed is unused, reducing validation guarantees.
Steps of Reproduction ✅
1. In a new test in `crates/phenotype-diff/src/ops.rs` (near the existing apply tests at
lines 232-273), manually construct a `UnifiedDiff` using `HunkHeader`/`Hunk` from
`crates/phenotype-diff/src/model.rs:23-41` where `header.old_lines` and `header.new_lines`
do not match the number of `Removed`/`Context` or `Added` lines in `hunk.lines`.

2. For example, set `old_start: 1, old_lines: 1, new_start: 1, new_lines: 0` but put two
`DiffLine::Removed` entries in `lines`, targeting a two-line source string like
`"a\nb\n"`.

3. Call `apply("a\nb\n", &patch)` to execute the hunk logic starting at
`crates/phenotype-diff/src/ops.rs:100-118`; note that only `hunk.header.old_start` is used
for positioning and `old_lines`/`new_lines` are ignored.

4. Observe that `apply` removes both lines based solely on `hunk.lines` and returns
`Ok("")` without ever emitting `DiffError::Malformed` (defined but unused in
`crates/phenotype-diff/src/error.rs:4-12`), meaning malformed header/body combinations are
silently accepted and can misrepresent what the patch claims to do.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** crates/phenotype-diff/src/ops.rs
**Line:** 101:118
**Comment:**
	*Incomplete Implementation: The apply logic never validates that `hunk.header.old_lines`/`new_lines` match the actual number of removed/context/added lines in the hunk body. As a result, malformed patches with inconsistent headers are silently accepted or misapplied instead of being rejected (despite having a `Malformed` error variant). Add a pre-check per hunk to enforce header/body consistency before applying.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +36 to +41
} catch (err) {
console.warn(
`[${APP_NAME}] process-compose boot skipped (not found or services already running):`,
(err as Error).message
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: The catch block swallows every process-compose failure and always logs it as a benign skip. This hides real startup errors (invalid compose file, bad config, permission issues) and launches the app in a broken state. Only suppress expected "command not found/already running" cases and rethrow unexpected failures. [logic error]

Severity Level: Major ⚠️
- ❌ Desktop shell can start without required backend services running.
- ⚠️ Serious boot errors mislabeled as benign "skipped" conditions.
Steps of Reproduction ✅
1. Set `SERVICES_COMPOSE_FILE` to a path with an invalid or unreadable
`process-compose.yml` in the environment used to run the Electrobun shell
(`templates/electrobun-shell/src/main.ts`).

2. Launch the app so `main()` at `main.ts:112-119` runs and calls `bootServices()` at
`main.ts:27-42` before creating the window.

3. Inside `bootServices`, the `process-compose up -d --config ${SERVICES_COMPOSE_FILE}`
command at `main.ts:34` fails (e.g., non-zero exit code or spawn error), causing Bun's `$`
helper to throw into the `catch (err)` block at `main.ts:36-41`.

4. Observe that the catch block logs a generic "boot skipped (not found or services
already running)" warning and then continues without rethrowing, so the main window is
created via `createMainWindow()` at `main.ts:45-55` even though the configured services
failed to start, leaving the desktop shell running in a broken state with only console
diagnostics.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** templates/electrobun-shell/src/main.ts
**Line:** 36:41
**Comment:**
	*Logic Error: The catch block swallows every `process-compose` failure and always logs it as a benign skip. This hides real startup errors (invalid compose file, bad config, permission issues) and launches the app in a broken state. Only suppress expected "command not found/already running" cases and rethrow unexpected failures.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Comment on lines +50 to +51
width: parseInt(process.env.WINDOW_WIDTH ?? "1400"),
height: parseInt(process.env.WINDOW_HEIGHT ?? "900"),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Suggestion: parseInt output is used directly for window dimensions without validating the result. If WINDOW_WIDTH or WINDOW_HEIGHT contains a non-numeric value, NaN is passed into BrowserWindow frame sizing, which can cause window creation failures. Validate parsed values and fallback to defaults when parsing fails. [type error]

Severity Level: Major ⚠️
- ❌ Non-numeric env values can break BrowserWindow creation or layout.
- ⚠️ Misconfigured deployment environment destabilizes desktop shell startup.
Steps of Reproduction ✅
1. Start the Electrobun shell from `templates/electrobun-shell/src/main.ts` with
`WINDOW_WIDTH` or `WINDOW_HEIGHT` set to a non-numeric value (for example,
`WINDOW_WIDTH=wide`).

2. When `main()` at `main.ts:112-119` executes, it calls `createMainWindow()` at
`main.ts:45-55` to construct a new `BrowserWindow`.

3. Inside `createMainWindow`, the `frame.width` and `frame.height` properties are computed
via `parseInt(process.env.WINDOW_WIDTH ?? "1400")` and `parseInt(process.env.WINDOW_HEIGHT
?? "900")` at `main.ts:50-51`, which yield `NaN` for non-numeric environment values.

4. Observe that these `NaN` values are passed directly into the `BrowserWindow`
constructor (imported from `"electrobun/bun"` at `main.ts:10`), leading to invalid window
sizing and potentially causing window creation to fail or behave unpredictably depending
on Electrobun's handling of `NaN`.

Fix in Cursor | Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** templates/electrobun-shell/src/main.ts
**Line:** 50:51
**Comment:**
	*Type Error: `parseInt` output is used directly for window dimensions without validating the result. If `WINDOW_WIDTH` or `WINDOW_HEIGHT` contains a non-numeric value, `NaN` is passed into `BrowserWindow` frame sizing, which can cause window creation failures. Validate parsed values and fallback to defaults when parsing fails.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@codeant-ai
Copy link
Copy Markdown

codeant-ai Bot commented May 31, 2026

CodeAnt AI finished reviewing your PR.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Autofix Details

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Sed substitution corrupts values containing special characters
    • Added escape_sed_replace to escape backslashes, ampersands, and pipe delimiters before sed substitution of user-provided values.
  • ✅ Fixed: Hunk type missing from crate root re-exports
    • Added Hunk to the crate root pub use model re-exports alongside DiffLine, HunkHeader, and UnifiedDiff.

Create PR

Or push these changes by commenting:

@cursor push 7b51a6749e
Preview (7b51a6749e)
diff --git a/crates/phenotype-diff/src/lib.rs b/crates/phenotype-diff/src/lib.rs
--- a/crates/phenotype-diff/src/lib.rs
+++ b/crates/phenotype-diff/src/lib.rs
@@ -28,5 +28,5 @@
 pub mod ops;
 
 pub use error::DiffError;
-pub use model::{DiffLine, HunkHeader, UnifiedDiff};
+pub use model::{DiffLine, Hunk, HunkHeader, UnifiedDiff};
 pub use ops::{apply, diff};

diff --git a/templates/electrobun-shell/adopt.sh b/templates/electrobun-shell/adopt.sh
--- a/templates/electrobun-shell/adopt.sh
+++ b/templates/electrobun-shell/adopt.sh
@@ -36,6 +36,15 @@
 [[ -z "$APP_ID"   ]] && { echo "ERROR: --app-id required";   exit 1; }
 [[ -z "$OUT_DIR"  ]] && { echo "ERROR: --out required";       exit 1; }
 
+# Escape &, \, and | for sed replacement (delimiter is |).
+escape_sed_replace() {
+  local value="$1"
+  value="${value//\\/\\\\}"
+  value="${value//&/\\&}"
+  value="${value//|/\\|}"
+  printf '%s' "$value"
+}
+
 TEMPLATE_DIR="$(cd "$(dirname "$0")" && pwd)"
 mkdir -p "$OUT_DIR/src"
 
@@ -44,11 +53,11 @@
   DEST="$OUT_DIR/$SRC"
   mkdir -p "$(dirname "$DEST")"
   sed \
-    -e "s|__APP_NAME__|$APP_NAME|g" \
-    -e "s|__APP_ID__|$APP_ID|g" \
-    -e "s|__APP_VERSION__|$APP_VERSION|g" \
-    -e "s|__DEFAULT_DEV_URL__|$RENDERER_URL|g" \
-    -e "s|__VIEWS_ENTRYPOINT__|$VIEWS_ENTRYPOINT|g" \
+    -e "s|__APP_NAME__|$(escape_sed_replace "$APP_NAME")|g" \
+    -e "s|__APP_ID__|$(escape_sed_replace "$APP_ID")|g" \
+    -e "s|__APP_VERSION__|$(escape_sed_replace "$APP_VERSION")|g" \
+    -e "s|__DEFAULT_DEV_URL__|$(escape_sed_replace "$RENDERER_URL")|g" \
+    -e "s|__VIEWS_ENTRYPOINT__|$(escape_sed_replace "$VIEWS_ENTRYPOINT")|g" \
     "$TEMPLATE_DIR/$SRC" > "$DEST"
 done

You can send follow-ups to the cloud agent here.

-e "s|__APP_VERSION__|$APP_VERSION|g" \
-e "s|__DEFAULT_DEV_URL__|$RENDERER_URL|g" \
-e "s|__VIEWS_ENTRYPOINT__|$VIEWS_ENTRYPOINT|g" \
"$TEMPLATE_DIR/$SRC" > "$DEST"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sed substitution corrupts values containing special characters

Medium Severity

The sed replacements interpolate user-provided values ($APP_NAME, $RENDERER_URL, etc.) directly into the replacement string without escaping sed metacharacters. An & in any value (e.g., an app name like "Track & Field") is interpreted by sed as "the entire matched text," corrupting the output. Similarly, | (the chosen delimiter) or \ in any value would break the command entirely or produce garbled results.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6a9a6f7. Configure here.

pub mod ops;

pub use error::DiffError;
pub use model::{DiffLine, HunkHeader, UnifiedDiff};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hunk type missing from crate root re-exports

Low Severity

The crate root re-exports DiffLine, HunkHeader, and UnifiedDiff but omits Hunk, even though UnifiedDiff exposes pub hunks: Vec<Hunk>. External consumers who need Hunk as a type annotation (e.g., in function signatures or variable bindings) must reach into phenotype_diff::model::Hunk rather than importing it from the crate root like all other public types.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6a9a6f7. Configure here.

description = "Line-level unified diff and patch-apply library (migrated from KooshaPari/Diffuse); wraps `similar`"

[dependencies]
similar = "2.6"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CRITICAL: Version mismatch between Cargo.toml and Cargo.lock.

Cargo.toml specifies similar = "2.6" but Cargo.lock shows similar version 2.7.0. This inconsistency will cause build determinism issues.

*/
import { BrowserWindow, ApplicationMenu } from "electrobun/bun";
import { $ } from "bun";
import { join } from "node:path";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Unused import - join is imported from node:path but never used in this file.

@kilo-code-bot
Copy link
Copy Markdown

kilo-code-bot Bot commented May 31, 2026

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 1
Issue Details (click to expand)

CRITICAL

File Line Issue
crates/phenotype-diff/Cargo.toml 10 Version mismatch: Cargo.toml specifies similar = "2.6" but Cargo.lock shows 2.7.0

WARNING

File Line Issue
templates/electrobun-shell/src/main.ts 12 Unused import join from node:path
Other Observations (not in diff)
  • lib.rs:31 - Hunk type should be re-exported for public usability; users may need to construct patches programmatically
  • ops.rs:109 - Missing hunk order validation: the apply function doesn't verify hunks are in ascending line order, which could lead to incorrect patch application with out-of-order hunks
Files Reviewed (5 files)
  • crates/phenotype-diff/Cargo.toml - 1 issue
  • crates/phenotype-diff/src/ops.rs - no issues
  • crates/phenotype-diff/src/lib.rs - no issues
  • crates/phenotype-diff/src/model.rs - no issues
  • templates/electrobun-shell/src/main.ts - 1 issue
  • templates/electrobun-shell/adopt.sh - no issues
  • templates/electrobun-shell/package.json - no issues
  • templates/electrobun-shell/electrobun.config.ts - no issues
  • templates/electrobun-shell/tsconfig.json - no issues
  • templates/electrobun-shell/README.md - no issues

Reviewed by laguna-m.1-20260312:free · 490,488 tokens

)

Empirically verified on Windows 11 (RTX 3090 Ti host): `electrobun build`
succeeds and produces a dev-win-x64/ bundle with launcher.exe, bun.exe,
libNativeWrapper.dll, WebView2Loader.dll — no cl.exe or MSVC required.
The CLI downloads pre-built platform-native binaries from GitHub Releases
(electrobun-core-win-x64.tar.gz) at first-run; the compiler is only needed
to build Electrobun itself, not to use it.

Correct the misleading comment from "macOS only" to the accurate per-platform
note so Windows and Linux consumers are not blocked by a wrong assumption.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

There are 4 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

Bugbot Autofix prepared fixes for both issues found in the latest run.

  • ✅ Fixed: Unused import of join in template
    • Removed the unused join import from node:path in templates/electrobun-shell/src/main.ts.
  • ✅ Fixed: Local filesystem path committed in README
    • Replaced the Windows-local E:/Dev/Tracera/... path with the monorepo-relative apps/desktop-electrobun reference in the README.

Create PR

Or push these changes by commenting:

@cursor push e42bbad110
Preview (e42bbad110)
diff --git a/templates/electrobun-shell/README.md b/templates/electrobun-shell/README.md
--- a/templates/electrobun-shell/README.md
+++ b/templates/electrobun-shell/README.md
@@ -43,5 +43,5 @@
 
 ## Reference: Tracera migration
 
-`E:/Dev/Tracera/frontend/apps/desktop-electrobun` is the production reference implementation,
+`apps/desktop-electrobun` in the Tracera monorepo is the production reference implementation,
 migrated from Electron + electron-vite to this template.

diff --git a/templates/electrobun-shell/src/main.ts b/templates/electrobun-shell/src/main.ts
--- a/templates/electrobun-shell/src/main.ts
+++ b/templates/electrobun-shell/src/main.ts
@@ -9,7 +9,6 @@
  */
 import { BrowserWindow, ApplicationMenu } from "electrobun/bun";
 import { $ } from "bun";
-import { join } from "node:path";
 
 // ── Config ────────────────────────────────────────────────────────────────────
 const APP_NAME = process.env.APP_NAME ?? "__APP_NAME__";

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit 9cc5fca. Configure here.

*/
import { BrowserWindow, ApplicationMenu } from "electrobun/bun";
import { $ } from "bun";
import { join } from "node:path";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Unused import of join in template

Low Severity

join is imported from node:path but never used anywhere in main.ts. Since this is a template file, every project generated via adopt.sh inherits this dead import.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9cc5fca. Configure here.

## Reference: Tracera migration

`E:/Dev/Tracera/frontend/apps/desktop-electrobun` is the production reference implementation,
migrated from Electron + electron-vite to this template.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Local filesystem path committed in README

Low Severity

The README references E:/Dev/Tracera/frontend/apps/desktop-electrobun, a developer-local Windows path. This is meaningless to other contributors and exposes internal development setup details in a reusable template.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9cc5fca. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants