Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@
All notable changes to this project are recorded here. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and the versioning follows [Semantic Versioning](https://semver.org/).

## [0.11.2] - 2026-07-28

> Highlights of this release:
>
> - **Stale-review marker**: when a PR gets new commits, the review timeline draws a divider so reviews made against older code are clearly separated from newer ones — hover to see the commit.
> - **Commit-aware review**: continuing a review conversation after new commits land — the agent now knows earlier discussion may reference code that has since changed.
> - **Quick single-PR refresh**: refresh just the current PR from remote with F5, a new refresh button, or a command — instead of waiting for the periodic sync.
>
> Plus fixes for inline-comment editing being interrupted by a refresh, comment positioning when a narrow diff collapses to unified, and an unexpected macOS media-library prompt.

### ✨ Added

- The review timeline now marks a divider whenever the PR gains new commits, separating reviews made against older code from newer ones (hover shows the commit's message), so it's obvious when earlier review results are based on outdated code.
- The review agent is now aware of code changes across commits: when you continue a conversation after new commits land, it knows earlier discussion may reference code that has since changed.
- You can now refresh just the current PR from remote — press F5, click the new refresh button next to "open in browser", or run the "Refresh PR" command — instead of waiting for the periodic background sync.

### ♻️ Changed

- Auto review moved from F5 to Ctrl+F5 to avoid triggering it by accident; F5 now refreshes the current PR.

### 🔧 Fixed

- An in-progress inline comment or draft editor in the diff is no longer interrupted when the comment list refreshes — whether from the periodic sync or a new remote comment arriving while you're typing.
- Inline comments and their markers now appear in the right place when a side-by-side diff automatically switches to unified because the pane is too narrow (comments anchored to the deleted / base side previously went missing); jumping to such a comment's anchor also lands on the correct line.
- On macOS, the app no longer prompts for access to Apple Music / your media library on launch.

## [0.11.1] - 2026-07-14

> Highlights of this release:
Expand Down Expand Up @@ -488,6 +514,7 @@ and the versioning follows [Semantic Versioning](https://semver.org/).
License: [Apache-2.0](LICENSE). The package bundles third-party components (pr-agent, Electron, etc.), each distributed under its own license, see [NOTICE](NOTICE).

[Unreleased]: https://github.com/huhamhire/code-meeseeks/compare/v0.11.1...HEAD
[0.11.2]: https://github.com/huhamhire/code-meeseeks/compare/v0.11.1...v0.11.2
[0.11.1]: https://github.com/huhamhire/code-meeseeks/compare/v0.11.0...v0.11.1
[0.11.0]: https://github.com/huhamhire/code-meeseeks/compare/v0.10.0...v0.11.0
[0.10.0]: https://github.com/huhamhire/code-meeseeks/compare/v0.9.0...v0.10.0
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,32 @@
本项目所有重要变更记录于此。格式参考 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/),
版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。

## [0.11.2] - 2026-07-28

> 本次发布要点:
>
> - **评审过时标记**:当 PR 有新提交进来时,评审时间线会画一条分割线,把基于旧代码的评审与新代码的评审清晰分隔——悬停可见对应提交。
> - **感知提交变化的评审**:新提交进来后继续评审对话时,Agent 现在知道先前的讨论可能引用了已变更的代码。
> - **快速刷新单个 PR**:用 F5、新增的刷新按钮或命令,只从远端刷新当前 PR,无需等待周期性同步。
>
> 另修复:刷新时正在编写的内联评论被打断、窄面板下 side-by-side 折叠为 unified 时的评论定位、以及 macOS 上意外的媒体库权限申请。

### ✨ 新增

- 每当 PR 有新提交进来,评审时间线会标出一条分割线,将基于旧代码的评审与新代码的评审分隔开(悬停显示该提交的消息),让「先前评审结果基于过时代码」一目了然。
- 评审 Agent 现在能感知跨提交的代码变化:新提交进来后继续对话时,它知道先前的讨论可能引用了此后已变更的代码。
- 现在可只从远端刷新当前 PR——按 F5、点击「浏览器打开」旁新增的刷新按钮,或执行「刷新 PR」命令——无需等待周期性后台同步。

### ♻️ 变更

- 自动评审从 F5 改为 Ctrl+F5,避免误触;F5 现在用于刷新当前 PR。

### 🔧 修复

- diff 中正在编写的内联评论或草稿编辑,在评论列表刷新时不再被打断——无论是周期性同步,还是编写时有新的远端评论到达。
- 当面板过窄导致 side-by-side diff 自动切换为 unified 时,内联评论及其标记现在显示在正确位置(此前锚定在删除 / 基准侧的评论会丢失);跳转到这类评论的锚点也能定位到正确行。
- macOS 上,应用启动时不再申请 Apple Music / 媒体库访问权限。

## [0.11.1] - 2026-07-14

> 本次发布要点:
Expand Down Expand Up @@ -488,6 +514,7 @@
许可证:[Apache-2.0](LICENSE)。打包内含第三方组件(pr-agent、Electron 等),各按其许可证分发,见 [NOTICE](NOTICE)。

[Unreleased]: https://github.com/huhamhire/code-meeseeks/compare/v0.11.1...HEAD
[0.11.2]: https://github.com/huhamhire/code-meeseeks/compare/v0.11.1...v0.11.2
[0.11.1]: https://github.com/huhamhire/code-meeseeks/compare/v0.11.0...v0.11.1
[0.11.0]: https://github.com/huhamhire/code-meeseeks/compare/v0.10.0...v0.11.0
[0.10.0]: https://github.com/huhamhire/code-meeseeks/compare/v0.9.0...v0.10.0
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@meebox/desktop",
"version": "0.11.1",
"version": "0.11.2",
"private": true,
"description": "meebox Electron desktop app",
"author": {
Expand Down
6 changes: 6 additions & 0 deletions apps/desktop/src/main/bootstrap/os-startup-tweaks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,16 @@ function applyWindowsStartupTweaks(): void {
* real keychain. Cost: cookie encryption degrades to a static key, but the key was already stored in
* plaintext, so no real loss. Removable once there's a proper Developer ID signature. Must be before
* app.whenReady().
* - disable-features MediaSessionService/HardwareMediaKeyHandling: Chromium's macOS "Now Playing" /
* media-session integration queries the MediaPlayer framework, which makes macOS prompt for "access
* Apple Music / your media library" on launch. We play no media and expose no now-playing controls, so
* this permission is unexpected and confusing; disabling the features stops Chromium from ever touching
* the media library. Must be before app.whenReady().
* - Prepend common CLI dirs to PATH (see augmentMacPath): must be before pr-agent probing / running.
*/
function applyMacStartupTweaks(): void {
app.commandLine.appendSwitch('use-mock-keychain');
app.commandLine.appendSwitch('disable-features', 'MediaSessionService,HardwareMediaKeyHandling');
augmentMacPath();
}

Expand Down
59 changes: 59 additions & 0 deletions apps/desktop/src/main/controllers/pr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,65 @@ export const openPrByUrl: IpcController<'prs:openByUrl'> = async (_event, req) =
*/
export const refreshPrs: IpcController<'prs:refresh'> = () => getContext().poller.tick();

/**
* Refresh a SINGLE PR from remote (metadata + comments) without a whole-poller tick. Fetches just this PR via the
* adapter (bypassing the discovery list), recomputes localStatus from the current user's reviewer status (remote
* authoritative, same rule as the poll), persists the updated meta, and invalidates the comments cache (broadcasts
* comments:changed → the open comments / activity / inline-diff refetch). If the head sha advanced, best-effort ensure
* the mirror has the new commits so the diff renders the new code. Returns the updated PR; the renderer then reloads the
* list locally (no network poll of other PRs).
*/
export const refreshOnePr: IpcController<'prs:refreshOne'> = async (_event, req) => {
const ctx = getContext();
const existing = await ctx.pr.findPrOrThrow(req.localId);
const adapter = ctx.pr.adapterForOrThrow(existing);
// Remote fetch of just this PR. 403/404 normalize to error codes (matching openPrByUrl); other errors bubble up.
let fresh;
try {
fresh = await adapter.prs.getSinglePullRequest(
{ projectKey: existing.repo.projectKey, repoSlug: existing.repo.repoSlug },
existing.remoteId,
);
} catch (err) {
const status = (err as { status?: number } | null)?.status;
if (status === 403) throw new AppError(ERROR_CODES.PR_FORBIDDEN, undefined, 'forbidden');
if (status === 404) throw new AppError(ERROR_CODES.PR_NOT_FOUND, undefined, 'not found');
throw err;
}
// localStatus mirrors the remote current user's reviewer status (remote authoritative, same mapping as the poll);
// when the current user is unknown (ping incomplete) keep the recorded status rather than downgrading to pending.
const me = adapter.connection.getCurrentUser();
const mineStatus = me ? fresh.reviewers.find((r) => r.name === me.name)?.status : undefined;
const localStatus = !me
? existing.localStatus
: mineStatus === 'approved'
? 'approved'
: mineStatus === 'needsWork'
? 'needs_work'
: 'pending';
const stored: StoredPullRequest = {
...fresh,
localId: existing.localId,
platform: existing.platform,
connectionId: existing.connectionId,
localStatus,
// Preserve local-only bookkeeping (a single-PR refresh isn't a discovery pass).
discoveryFilters: existing.discoveryFilters,
discoveredAt: existing.discoveredAt,
lastSeenAt: new Date().toISOString(),
};
await writePrMeta(await ctx.pr.storeForPr(req.localId), req.localId, stored);
await ctx.pr.invalidateCommentsCache(req.localId);
if (fresh.sourceRef.sha !== existing.sourceRef.sha) {
try {
await ctx.pr.ensureMirrorReadyForPr(stored);
} catch {
/* non-fatal: the diff view self-heals / surfaces a readable error if the mirror still lacks the sha */
}
}
return stored;
};

/**
* The Poller's most recent completion time (used for startup initialization).
*/
Expand Down
1 change: 1 addition & 0 deletions apps/desktop/src/main/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export function registerIpcHandlers(deps: RegisterDeps): {
ipcMain.handle('prs:listArchived', pr.listArchivedPrs); // Closed (archived) PR list (read-only browsing)
ipcMain.handle('prs:openByUrl', pr.openPrByUrl); // Open a current-platform PR by URL (locate / fetch archive)
ipcMain.handle('prs:refresh', pr.refreshPrs); // Poll and refresh immediately
ipcMain.handle('prs:refreshOne', pr.refreshOnePr); // Refresh a single PR from remote (no whole-poller tick)
ipcMain.handle('prs:lastSync', pr.getLastSync); // Most recent sync time
ipcMain.handle('prs:setLocalStatus', pr.setPrStatus); // Set review status (remote first, then local)
ipcMain.handle('prs:markRead', pr.markRead); // Mark PR read (advance unread watermark)
Expand Down
17 changes: 15 additions & 2 deletions apps/desktop/src/main/services/agent/planning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,13 @@ export async function runPlanning(
deps.stateStore,
pr.localId,
// When a Diff selection reference is present, persist it alongside, for the UI to show the "referenced code" collapsed below the bubble.
{ role: 'user', content: userRequest, referencedContext: deps.referencedContext },
// headSha stamps the PR head this turn was made against, so a later turn on a newer commit gets a "code changed" marker in the planning context.
{
role: 'user',
content: userRequest,
referencedContext: deps.referencedContext,
headSha: pr.sourceRef.sha,
},
now,
);

Expand Down Expand Up @@ -161,6 +167,8 @@ export async function runPlanning(
summarySections: buildSummarySections(),
userRequest,
history,
// Current PR head, so the planning context can flag a code change since the newest historical turn.
currentHeadSha: pr.sourceRef.sha,
referencedContext: deps.referencedContext,
maxSteps: deps.maxSteps,
maxFollowupAsks: deps.maxFollowupAsks,
Expand All @@ -172,7 +180,12 @@ export async function runPlanning(
await appendAgentMessage(
deps.stateStore,
pr.localId,
{ role: 'assistant', content: result.finalText, recommendation: result.recommendation },
{
role: 'assistant',
content: result.finalText,
recommendation: result.recommendation,
headSha: pr.sourceRef.sha,
},
now,
);
}
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/main/services/notifications.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ function activateOnClick(e: PollNotificationEvent): () => void {
anchor:
// File-level comments (no line) aren't line-navigable → no anchor (clicking just opens the PR).
e.comment?.anchor && e.comment.anchor.line != null
? { path: e.comment.anchor.path, line: e.comment.anchor.line }
? {
path: e.comment.anchor.path,
line: e.comment.anchor.line,
side: e.comment.anchor.side,
}
: null,
});
};
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop/src/main/services/pr-agent/run-executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ export class RunExecutor {
origin: item.priority,
// Single-commit review scope persisted with the run: the result card uses it to show a scope badge.
scope: req.scope,
// PR head commit the run runs against: stamped so ChatPane can draw a commit divider when the code changes between runs.
headSha: pr.sourceRef.sha,
});
// Upgrade the info (startedAt=null at enqueue) to active form + broadcast (via the scheduling layer).
item.info = { ...item.info, startedAt: run.startedAt };
Expand Down
22 changes: 18 additions & 4 deletions apps/desktop/src/renderer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,25 @@ export default function App() {
selectedId,
setSelectedId,
refreshing,
refreshingPr,
merging,
reloadPrs,
triggerRefresh,
refreshPr,
setSelectedPrStatus,
mergeSelectedPr,
markRead,
} = usePullRequests({ notifyError });
// App startup / global lifecycle (boot load, language, poll / focus refresh, wizard completion, connection hot-apply)
const { boot, fatalError, lastSyncAt, needsOnboarding, completeOnboarding, refreshBootAndPrs, patchConfig } =
useBootstrap({ setPrs, reloadPrs });
const {
boot,
fatalError,
lastSyncAt,
needsOnboarding,
completeOnboarding,
refreshBootAndPrs,
patchConfig,
} = useBootstrap({ setPrs, reloadPrs });
// Layout state (left/right column widths / collapse), version update notice, store wiring, external link guard — each its own app-level hook
const {
sidebarWidth,
Expand Down Expand Up @@ -128,12 +137,13 @@ export default function App() {
// "Can engage" determination for the closed scope: merged / still-open PRs allow adding comments + AI review; declined ones are browse-only. The active scope is always engageable.
const canEngage = !archived || (selectedPr ? selectedPr.state !== 'declined' : false);

// Window-level global shortcuts (F5 auto review / DevTools / view closed / Ctrl-Cmd+B·J layout toggles) — domain logic lives in useGlobalShortcuts.
// Window-level global shortcuts (F5 refresh / Ctrl+F5 auto review / DevTools / view closed / Ctrl-Cmd+B·J layout toggles) — domain logic lives in useGlobalShortcuts.
useGlobalShortcuts({
platform: boot?.info.platform,
selectedId,
canEngage,
viewArchived,
refreshPr: (localId) => void refreshPr(localId),
setSidebarCollapsed,
setChatCollapsed,
});
Expand Down Expand Up @@ -177,7 +187,8 @@ export default function App() {
const showDiscoveryFilter = availableDiscoveryFilters.length > 0;
// Whether the platform supports the needs_work ("needs changes") review state: GitHub / Bitbucket support it, GitLab (binary approval) does not.
// Determines whether the "pending" status filter is kept under discovery categories other than "awaiting my review" (see Sidebar.visibleFilters).
const supportsNeedsWork = activeConnSummary?.capabilities.reviewStatuses.includes('needsWork') ?? false;
const supportsNeedsWork =
activeConnSummary?.capabilities.reviewStatuses.includes('needsWork') ?? false;
// The selected category may be invalid for the current platform after switching connections → fall back to the first available.
const effectiveDiscoveryFilter = availableDiscoveryFilters.includes(discoveryFilter)
? discoveryFilter
Expand All @@ -204,6 +215,7 @@ export default function App() {
prStatusFilters={visibleStatusFilters}
setPrStatusFilter={setStatusFilter}
viewArchived={viewArchived}
refreshPr={(localId) => void refreshPr(localId)}
openPrByUrl={openPrByUrl}
/>
<div className="app-body">
Expand Down Expand Up @@ -238,6 +250,8 @@ export default function App() {
onSetStatus={(s) => void setSelectedPrStatus(s)}
onMerge={() => void mergeSelectedPr()}
merging={merging}
onRefresh={() => void refreshPr(selectedPr.localId)}
refreshing={refreshingPr}
capabilities={selectedConn?.capabilities}
currentUserName={selectedConn?.user?.name ?? null}
// The closed scope hides PR lifecycle actions (merge / approve); declined / not-engageable further hides comment / draft writes.
Expand Down
Loading
Loading