Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
278d175
fix(desktop): polish Projects navigation and detail presentation
thomaspblock Aug 17, 2026
d1d54c9
chore(desktop): keep Tauri entrypoint within size limit
thomaspblock Aug 17, 2026
04ca3eb
fix(desktop): refine Projects overview and list presentation
thomaspblock Aug 18, 2026
3823617
polish(desktop): detail-page layout cleanup for readme and commit views
thomaspblock Aug 19, 2026
4ee8a2f
refactor(desktop): extract ImageMosaic from markdown.tsx
thomaspblock Aug 19, 2026
4cac268
feat(desktop): open latest project conversation from workspace Channe…
thomaspblock Aug 19, 2026
e9bd8cd
refactor(desktop): extract the latest repository commit row
thomaspblock Aug 19, 2026
e4f53a0
polish(desktop): simplify Projects context chrome and list layout
thomaspblock Aug 20, 2026
c463bc6
Merge origin/main into projects-v6-pt5-visual-cleanups
thomaspblock Aug 20, 2026
bd540d0
fix(ci): align Projects smoke specs with context chrome
thomaspblock Aug 20, 2026
5c6d86b
fix(desktop): align review identity and Channels click coverage (#6429)
thomaspblock Aug 20, 2026
bb565f5
fix(desktop): retain one selected review across Projects chrome (#6429)
thomaspblock Aug 20, 2026
570b198
fix(desktop): keep retained review body visible during refetch (#6429)
thomaspblock Aug 20, 2026
255c8b9
fix(desktop): render real review panel in refetch regression (#6429)
thomaspblock Aug 20, 2026
09e6ca2
Merge remote-tracking branch 'origin/main' into projects-v6-pt5-visua…
thomaspblock Aug 20, 2026
7712bbc
test(desktop): isolate ForumComposer stub from the shared test loader
thomaspblock Aug 20, 2026
71520a8
test(desktop): drive retained review through WorkspaceTabs
thomaspblock Aug 21, 2026
733380e
Merge origin/main into projects-v6-pt5-visual-cleanups
thomaspblock Aug 21, 2026
b8df8c4
fix(desktop): keep mention caret and channel tooltip just-now
thomaspblock Aug 21, 2026
9a98601
fix(desktop): expect composer mention chips to stay inline
thomaspblock Aug 21, 2026
a5eb269
fix(desktop): keep typing after mention chips
thomaspblock Aug 21, 2026
49afb82
fix(desktop): fence mention caret after autocomplete insert
thomaspblock Aug 21, 2026
0071d6f
fix(desktop): settle mention caret in the inserting editor
thomaspblock Aug 21, 2026
ca61bd6
fix(desktop): stop mention settlement from stealing ArrowLeft
thomaspblock Aug 21, 2026
35fc1b6
Merge origin/main into projects-v6-pt5-visual-cleanups
thomaspblock Aug 21, 2026
208209f
fix(desktop): honor ArrowLeft and chip clicks after mentions
thomaspblock Aug 21, 2026
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
1 change: 0 additions & 1 deletion desktop/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,6 @@ pub fn run() {
RunEvent::Exit => {
shut_down_app(app_handle, &run_shutdown_done);
app_handle.state::<ClipboardState>().release();

#[cfg(all(feature = "mesh-llm", target_os = "macos"))]
if restart_requested.load(Ordering::SeqCst) {
relaunch_after_mesh_shutdown(app_handle);
Expand Down
24 changes: 14 additions & 10 deletions desktop/src/features/messages/ui/MessageThreadTranscript.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type MessageThreadTranscriptProps = {
remove: boolean,
) => Promise<void>;
profiles?: UserProfileLookup;
renderAfterMessage?: (message: TimelineMessage) => React.ReactNode;
testId?: string;
};

Expand All @@ -36,6 +37,7 @@ export function MessageThreadTranscript({
messages,
onToggleReaction,
profiles,
renderAfterMessage,
testId = "message-thread-transcript",
}: MessageThreadTranscriptProps) {
const renderItems = React.useMemo(() => {
Expand All @@ -59,16 +61,18 @@ export function MessageThreadTranscript({
data-testid={testId}
>
{renderItems.map(({ isContinuation, message }) => (
<MessageThreadRow
channelId={channelId}
currentPubkey={currentPubkey}
isContinuation={isContinuation}
key={message.renderKey ?? message.id}
message={message}
onToggleReaction={onToggleReaction}
profiles={profiles}
showDepthGuides={false}
/>
<React.Fragment key={message.renderKey ?? message.id}>
<MessageThreadRow
channelId={channelId}
currentPubkey={currentPubkey}
isContinuation={isContinuation}
message={message}
onToggleReaction={onToggleReaction}
profiles={profiles}
showDepthGuides={false}
/>
{renderAfterMessage?.(message)}
</React.Fragment>
))}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
buildProjectSelectionAgentContext,
buildProjectsOverviewAgentContext,
projectDetailAgentContextBlock,
splitProjectDetailAgentContext,
stripProjectDetailAgentContext,
untrustedPromptValue,
withProjectSelectionAgentContext,
Expand Down Expand Up @@ -215,8 +216,44 @@ test("selected project context enforces a final serialization budget", () => {
});

test("strips hidden page context from the displayed user message", () => {
const content = `Explain this file${projectDetailAgentContextBlock(
const payload = projectDetailAgentContextBlock(
buildProjectDetailAgentContext(base),
)}`;
);
const content = `Explain this file${payload}`;
assert.equal(stripProjectDetailAgentContext(content), "Explain this file");
assert.deepEqual(splitProjectDetailAgentContext(content), {
context: payload.trim(),
message: "Explain this file",
});
});

test("leaves ordinary messages unchanged without inventing context", () => {
assert.deepEqual(splitProjectDetailAgentContext("A normal message"), {
context: null,
message: "A normal message",
});
});

test("splits only the final appended context marker", () => {
const userMessage =
"Discuss this literal example:\n---\nCurrent Buzz project page:\nnot appended";
const payload = projectDetailAgentContextBlock(
buildProjectDetailAgentContext(base),
);
assert.deepEqual(splitProjectDetailAgentContext(`${userMessage}${payload}`), {
context: payload.trim(),
message: userMessage,
});
});

test("splits workspace repository context for the shared conversation view", () => {
const payload =
'\n---\nWorkspace repositories:\n- "Buzz" (address: "owner:buzz")';
assert.deepEqual(
splitProjectDetailAgentContext(`Compare the repos${payload}`),
{
context: payload.trim(),
message: "Compare the repos",
},
);
});
28 changes: 25 additions & 3 deletions desktop/src/features/projects/lib/projectDetailAgentContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ import {
} from "./projectSelection.ts";

const PROJECT_PAGE_CONTEXT_MARKER = "Current Buzz project page:";
/** Marker for the repository set appended by the full Projects agent page. */
export const PROJECT_WORKSPACE_CONTEXT_MARKER = "Workspace repositories:";
const PROJECT_AGENT_CONTEXT_MARKERS = [
PROJECT_PAGE_CONTEXT_MARKER,
PROJECT_WORKSPACE_CONTEXT_MARKER,
];
const MAX_OVERVIEW_CONTEXT_ITEMS = 200;
const MAX_OVERVIEW_CONTEXT_FIELD_LENGTH = 180;
const MAX_SELECTION_CONTEXT_ITEMS = 100;
Expand Down Expand Up @@ -323,8 +329,24 @@ function overviewContextField(value: string | null | undefined) {
return normalizedPromptValue(value, MAX_OVERVIEW_CONTEXT_FIELD_LENGTH);
}

export function splitProjectDetailAgentContext(content: string): {
context: string | null;
message: string;
} {
const markerIndex = Math.max(
...PROJECT_AGENT_CONTEXT_MARKERS.map((marker) =>
content.lastIndexOf(`---\n${marker}`),
),
);
if (markerIndex === -1) {
return { context: null, message: content };
}
return {
context: content.slice(markerIndex).trim(),
message: content.slice(0, markerIndex).replace(/\n+$/, ""),
};
}

export function stripProjectDetailAgentContext(content: string) {
const markerIndex = content.indexOf(`---\n${PROJECT_PAGE_CONTEXT_MARKER}`);
if (markerIndex === -1) return content;
return content.slice(0, markerIndex).replace(/\n+$/, "");
return splitProjectDetailAgentContext(content).message;
}
181 changes: 181 additions & 0 deletions desktop/src/features/projects/lib/projectReviewDisplay.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import assert from "node:assert/strict";
import test from "node:test";

import {
currentPullRequestForSelection,
projectReviewFilesChangedBody,
retainLatestByKey,
reviewDiffWorkspaceBranch,
shouldReplaceRetainedPullRequest,
} from "./projectReviewDisplay.ts";

test("retainLatestByKey keeps the previous value when shouldReplace is false", () => {
const cache = { current: { key: "pr-1", value: { files: [1] } } };

const retained = retainLatestByKey(
cache,
"pr-1",
{ files: [] },
(next, previous) =>
next.files.length > 0 ? true : previous.files.length === 0,
);

assert.deepEqual(retained, { files: [1] });
assert.deepEqual(cache.current.value, { files: [1] });
});

test("retainLatestByKey takes a new key immediately", () => {
const cache = { current: { key: "pr-1", value: { files: [1] } } };

const retained = retainLatestByKey(
cache,
"pr-2",
{ files: [] },
(next) => next.files.length > 0,
);

assert.deepEqual(retained, { files: [] });
});

test("an explicit selected review does not fall back to another identity", () => {
const selected = { id: "pr-a" };
const branchReview = { id: "pr-branch" };

assert.equal(
currentPullRequestForSelection({
fallback: branchReview,
pullRequests: [selected, branchReview],
selectedPullRequestId: "pr-a",
}),
selected,
);
assert.equal(
currentPullRequestForSelection({
fallback: branchReview,
pullRequests: [branchReview],
selectedPullRequestId: "pr-a",
}),
null,
);
assert.equal(
currentPullRequestForSelection({
fallback: branchReview,
pullRequests: [branchReview],
selectedPullRequestId: null,
}),
branchReview,
);
});

test("retained review identity stays aligned with the diff-query identity across fetch phases", () => {
const reviewA = { id: "pr-a" };
const renderedCache = { current: { key: "repo:pr-a", value: reviewA } };
const diffQueryCache = { current: { key: "repo:pr-a", value: reviewA } };

const renderedDuringFetch = retainLatestByKey(
renderedCache,
"repo:pr-a",
currentPullRequestForSelection({
fallback: { id: "pr-branch" },
pullRequests: [],
selectedPullRequestId: "pr-a",
}),
(next) => shouldReplaceRetainedPullRequest(next, true),
);
const diffDuringFetch = retainLatestByKey(
diffQueryCache,
"repo:pr-a",
currentPullRequestForSelection({
fallback: { id: "pr-branch" },
pullRequests: [],
selectedPullRequestId: "pr-a",
}),
(next) => shouldReplaceRetainedPullRequest(next, true),
);
assert.equal(renderedDuringFetch, reviewA);
assert.equal(diffDuringFetch, reviewA);
assert.equal(renderedDuringFetch.id, diffDuringFetch.id);

const renderedAfterComplete = retainLatestByKey(
renderedCache,
"repo:pr-a",
currentPullRequestForSelection({
fallback: { id: "pr-branch" },
pullRequests: [],
selectedPullRequestId: "pr-a",
}),
(next) => shouldReplaceRetainedPullRequest(next, false),
);
const diffAfterComplete = retainLatestByKey(
diffQueryCache,
"repo:pr-a",
currentPullRequestForSelection({
fallback: { id: "pr-branch" },
pullRequests: [],
selectedPullRequestId: "pr-a",
}),
(next) => shouldReplaceRetainedPullRequest(next, false),
);
assert.equal(renderedAfterComplete, null);
assert.equal(diffAfterComplete, null);
});

test("review files stay mounted when a populated diff races an unavailable snapshot", () => {
assert.equal(
projectReviewFilesChangedBody({
hasPopulatedDiff: true,
hasSelectedPullRequest: true,
repositoryUnavailable: true,
}),
"files",
);
});

test("review files can show unavailable before a diff exists", () => {
assert.equal(
projectReviewFilesChangedBody({
hasPopulatedDiff: false,
hasSelectedPullRequest: true,
repositoryUnavailable: true,
}),
"unavailable",
);
});

test("review files render the panel for a selected review when the repo is available", () => {
assert.equal(
projectReviewFilesChangedBody({
hasPopulatedDiff: false,
hasSelectedPullRequest: true,
repositoryUnavailable: false,
}),
"files",
);
});

test("review diffs stay on the target branch, not the head or picker branch", () => {
assert.equal(
reviewDiffWorkspaceBranch({
activeBranch: "variation/bees",
defaultBranch: "main",
pullRequest: { targetBranch: "main" },
}),
"main",
);
assert.equal(
reviewDiffWorkspaceBranch({
activeBranch: "variation/bees",
defaultBranch: "main",
pullRequest: { targetBranch: null },
}),
"main",
);
assert.equal(
reviewDiffWorkspaceBranch({
activeBranch: "variation/bees",
defaultBranch: "main",
pullRequest: null,
}),
"variation/bees",
);
});
Loading
Loading