Skip to content

fix: address review feedback from PRs #69 and #70 - #73

Merged
Oaklight merged 1 commit into
masterfrom
fix/review-feedback-pr69-pr70
Sep 13, 2026
Merged

Oaklight merged 1 commit into
masterfrom
fix/review-feedback-pr69-pr70

Conversation

@Oaklight

Copy link
Copy Markdown
Owner

Summary

Follow-up fixes for review comments from PRs #69 and #70:

Test plan

  • Verify deleting a branch with / in the name (e.g. feature/foo) works via the API
  • Verify switching branches works normally and with force (dirty working tree → "Switch anyway")
  • Verify dirty dialog shows distinct heading and body text
  • Verify pressing Escape closes all dialogs (dirty, create branch, add worktree, remove worktree)
  • Verify branch dropdown outside-click closes properly without listener leaks
  • Verify clicking a remote branch in the dropdown triggers a switch to the local tracking branch

@clementine-oaklight clementine-oaklight Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM ✅ — 七个 fix 全部正确,没有 blocking issue。

逐项过:

  • <path:branch_name> 路由 ✅ — 修了 #69 review 的 feature/foo 404 问题,Bottle 的 path filter 匹配含 / 的段。
  • -- separator ✅ — switch_branch 放在 ["--", branch],正确保护 branch name。create_branch 放在 ["-c", name, "--"] + optional start_point——-c 已经把 name 消费为 operand,-- 保护的是后面的 start_point,逻辑对。
  • --discard-changes force switch ✅ — 完整链路:showDirtyDialog → "Switch anyway" → doSwitchBranch(branch, true) → { branch, force } → handler → switch_branch(force=True) → git switch --discard-changes -- branch。老代码的 "Switch anyway" 实际上没 force,会再次被 dirty state 拦住,现在修好了。
  • Dialog heading/body 去重 ✅ — 新增 dirty_working_tree_desc i18n key,heading 用标题,body 用描述,en/zh 都有。
  • Escape key ✅ — 四个 dialog 都加了 keydown listener + closeDialog() cleanup helper,不会泄漏。removeWorktree 的 Promise 模式也对——多次 resolve 被 JS 引擎忽略,不会 double-fire。
  • Dropdown listener cleanup ✅ — _branchDropdownCloser module-level 变量跟踪 stale listener,showBranchDropdown 开头先 remove 旧的再 attach 新的,toggleBranchDropdown 关闭时也清理。
  • Remote branch 可点击 ✅ — b.substring(b.indexOf("/") + 1) 正确剥离 origin/ 前缀(origin/feature/foo → feature/foo),调 switchBranch() 让 git switch 自动创建 local tracking branch。

两个 non-blocking:

  1. switchWorktree 的 unsaved dialog 没加 Escape——PR #72 新增的那个 dialog 不在本 PR scope 内,但现在是唯一一个不支持 Escape 关闭的 dialog,下次顺手补上。

  2. showDirtyDialog 仍用 innerHTML——内容全是 t() i18n 静态字符串,没有用户输入,不存在 XSS 风险。但 codebase 其余 dialog 已经迁到 createElement(PR #72),风格不统一。不影响合并,有空可以统一。

可以合。

@milo-oaklight milo-oaklight Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

PR #73 Review — Fix: address review feedback from PRs #69 and #70

CI is green. Each original finding checked against the diff below.


PR #69 Findings

✅ DELETE route won't match slashed branch names

server.py: <branch_name> → <path:branch_name>. Correct fix — Sanic's path type matches segments containing /, so feature/foo now routes properly.

✅ Missing -- separator in switch_branch

git_ops.py switch_branch: branch name is now placed after --:

args.extend(["--", branch])

A branch name like --orphan can no longer be interpreted as a flag. Good.

✅ Missing -- separator in create_branch

git_ops.py create_branch: -- is now placed between the -c <name> option and the optional start_point positional arg:

args = ["switch", "-c", name, "--"]
if start_point:
    args.append(start_point)

name is already validated by check-ref-format above, so it can't start with --. The -- protects start_point from flag interpretation. When no start_point is given, the trailing -- is harmless (git ignores it and defaults to HEAD). Correct.

📝 Stash list/apply/drop — not addressed

Noted as out-of-scope in this PR. Still a future improvement.


PR #70 Findings

✅ "Switch anyway" doesn't force-switch

Full chain is wired up:

  1. git_ops.py: switch_branch(project_dir, branch, force=False) — when force=True, adds --discard-changes before -- branch.
  2. handlers.py: reads force from the request body.
  3. app.js: doSwitchBranch(branch, force = false) sends { branch, force } in the POST body.
  4. Dialog: the "Switch anyway" / "dirty-discard" button now calls doSwitchBranch(targetBranch, true).

One behavioral note: --discard-changes discards both staged and unstaged modifications. The dialog description ("Choose how to handle your changes before switching") doesn't explicitly warn that staged changes are also lost. For a v1 this is fine — just something to keep in mind if users report surprise data loss.

✅ Dialog heading/body duplication

New dirty_working_tree_desc i18n key added in both en and zh. Heading uses dirty_working_tree ("You have uncommitted changes"), body uses dirty_working_tree_desc ("Choose how to handle your changes before switching."). No longer identical.

✅ Escape key for all dialogs

keydown listener for Escape added to all four dialogs:

  • showDirtyDialog()
  • createBranchDialog()
  • addWorktreeDialog()
  • removeWorktree() (confirmation dialog)

Each uses a consistent closeDialog helper that removes the overlay AND calls removeEventListener to clean up the keydown handler. No listener leaks. Clean pattern.

✅ Outside-click listener leak on branch dropdown

Module-level _branchDropdownCloser tracks the active listener reference:

  • toggleBranchDropdown() removes it on close.
  • showBranchDropdown() removes any stale listener before attaching a new one.
  • The closer itself nulls the reference on fire.

This covers the accumulation scenario (rapid open/close cycles). Resolved.

✅ Remote branches clickable

Remote branch items now get cursor: pointer and an onclick that calls switchBranch(localName). The local name is extracted by stripping everything up to and including the first /:

const localName = b.includes("/") ? b.substring(b.indexOf("/") + 1) : b;

This correctly handles origin/feature/foo → feature/foo (strips only the remote prefix, preserves nested slashes). git switch feature/foo will auto-create a local tracking branch if one doesn't exist — correct behavior.

Minor note for multi-remote setups: if both origin/main and upstream/main appear, clicking either would try switchBranch("main"), and git picks the tracking remote by its own heuristics (typically the first match). Unlikely to bite anyone in practice, but worth keeping in mind.

📝 CSS variable rename: --text-dim → --fg-muted

The diff also changes the remote branch name color from var(--text-dim) to var(--fg-muted). Presumably --fg-muted is the correct design token in the current stylesheet — just flagging for awareness.

📝 Items not addressed in this PR

  • Dropdown z-index (PR #70) — still open.
  • Loading state indicator (PR #70) — still open.
  • Stash list/apply/drop (PR #69) — still open.

These can be tracked separately.


New Issues

None found. The fixes are clean, consistent in pattern, and don't introduce regressions. The closeDialog pattern used across all four dialogs is a good refactor — uniform and easy to maintain.


Summary

Finding Source Status
DELETE route for slashed branches PR #69 🔴 ✅ Fixed
-- separator in switch/create PR #69 🟡 ✅ Fixed
Force-switch with --discard-changes PR #70 🔴 ✅ Fixed
Dialog heading/body dedup PR #70 🟡 ✅ Fixed
Escape key for all dialogs PR #70 🟡 ✅ Fixed
Dropdown listener cleanup PR #70 🟡 ✅ Fixed
Remote branches clickable PR #70 🟡 ✅ Fixed
Stash list/apply/drop PR #69 🟡 📝 Out of scope
Dropdown z-index PR #70 🟡 📝 Not addressed
Loading state indicator PR #70 🟡 📝 Not addressed

All critical and high-priority findings from PRs #69 and #70 are resolved. The remaining items are enhancements that can be tracked separately. No new issues introduced.

LGTM — this is ready to merge.

— Milo 👻

@elena-oaklight elena-oaklight Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: fix: address review feedback from PRs #69 and #70

CI is green (lint ✅, GitGuardian ✅). Single commit, well-scoped. Overall this is a solid batch of fixes — nice work. One real bug and one consistency nit below.

✅ DELETE route for slashed branch names (server.py)

<branch_name> → <path:branch_name> is the correct fix. The vendored httpserver maps path to the (.+) regex (vs ([^/]+) for the default str type), so feature/login is now fully captured. Verified by reading _PARAM_CONVERTERS in httpserver.py.

✅ -- separator in switch_branch (git_ops.py)

args.extend(["--", branch])

Correctly placed. Prevents branch names from being misinterpreted as flags.

✅ -- separator in create_branch (git_ops.py)

args = ["switch", "-c", name, "--"]
if start_point:
    args.append(start_point)

I initially worried that -- before start_point would cause git to treat it as a pathspec, but tested and confirmed that git switch -c foo -- HEAD~1 correctly interprets HEAD~1 as the start-point. The branch name itself is also protected by the check-ref-format --branch validation above, which rejects names starting with -. Belt and suspenders — fine.

✅ Force-switch with --discard-changes (git_ops.py + handlers.py + app.js)

Clean plumbing: force bool flows from the "Switch anyway" button → doSwitchBranch(branch, true) → { branch, force } in request body → handlers.py → git switch --discard-changes -- branch. The user must explicitly click through the dirty-working-tree dialog to trigger this, so it's safe.

✅ Dialog heading/body dedup (app.js)

Good fix. dirty_working_tree_desc is a new distinct i18n key used in the <p> body, while dirty_working_tree stays as the <h3> heading. Both en and zh translations added.

✅ Escape key for all dialogs (app.js)

The pattern is clean and consistent across all four dialogs:

const escHandler = (e) => { if (e.key === "Escape") closeDialog(); };
const closeDialog = () => { overlay.remove(); document.removeEventListener("keydown", escHandler); };
document.addEventListener("keydown", escHandler);

Listener is removed on every close path (Escape, cancel, outside click, action button). The removeWorktree dialog correctly wires resolve(false) through the Escape handler too. No leaks.

✅ Dropdown listener cleanup (app.js)

_branchDropdownCloser module-level var properly tracks and removes stale click listeners. Both showBranchDropdown() (removes old before adding new) and toggleBranchDropdown() (removes on hide) handle cleanup.

✅ Remote branches clickable (app.js)

b.substring(b.indexOf("/") + 1) strips only the remote name prefix (first segment), so origin/feature/login → feature/login. Passing this to switchBranch() lets git switch auto-create a local tracking branch. Correct.


🐛 Bug: undefined CSS variable --fg-muted

In showBranchDropdown(), the remote branch name color was changed from var(--text-dim) to var(--fg-muted):

nameSpan.style.color = "var(--fg-muted)";  // ← this variable doesn't exist

The codebase defines and uses --text-dim throughout app.css (18+ occurrences). There is no --fg-muted defined anywhere. This means the color property resolves to an invalid value and falls back to inherited color, which may or may not look correct depending on context.

Fix: revert to var(--text-dim), or define --fg-muted in the CSS if a new token is intended.

📝 Nit: delete_branch still lacks -- separator

While this PR adds -- to switch_branch and create_branch, delete_branch still runs:

rc, out, err = _run_git(project_dir, "branch", flag, name)

Same class of vulnerability. In practice it's low-risk since branch names are validated elsewhere and the route now uses <path:branch_name> (not user-crafted flags), but for consistency it should be _run_git(project_dir, "branch", flag, "--", name). Fine to address in a follow-up.

@Oaklight
Oaklight merged commit ad68e86 into master Sep 13, 2026
2 checks passed
@Oaklight
Oaklight deleted the fix/review-feedback-pr69-pr70 branch September 13, 2026 19:31
@Oaklight

Copy link
Copy Markdown
Owner Author

Addressing remaining feedback

Thanks for the clean reviews. One fix needed:

  • 🐛 CSS variable --fg-muted undefined: Will revert to var(--text-dim) ✅

clementine's non-blocking items (switchWorktree Escape key, showDirtyDialog innerHTML) noted for follow-up.
elena's nit on delete_branch -- separator noted for follow-up.

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