Skip to content

Commit bf64a1c

Browse files
authored
🤖 refactor: rework edit mode UI to match app aesthetic (#1143)
## Summary Reworks the editing message UI components to align with the rest of the app's visual language. ### Changes - **Color scheme**: Updated edit mode colors from green (`hsl(120)`) to warm amber (`hsl(38)`) across all themes - **EditCutoffBarrier**: New component following the app's barrier pattern (like `InterruptedBarrier`, `RetryBarrier`) with gradient lines and centered badge - **Storybook**: Added `EditingMessage` story demonstrating the editing state with amber input border and cutoff barrier ### Visual Changes - Textarea border when editing a message: now amber instead of orange - Edit cutoff barrier: now uses consistent barrier styling with gradient lines - Warning color language now consistent with `--color-interrupted` --- _Generated with `mux` • Model: `claude-sonnet-4-20250514` • Thinking: `low`_
1 parent 2e796d6 commit bf64a1c

File tree

4 files changed

+148
-32
lines changed

4 files changed

+148
-32
lines changed

src/browser/components/AIView.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import React, {
1010
import { cn } from "@/common/lib/utils";
1111
import { MessageRenderer } from "./Messages/MessageRenderer";
1212
import { InterruptedBarrier } from "./Messages/ChatBarrier/InterruptedBarrier";
13+
import { EditCutoffBarrier } from "./Messages/ChatBarrier/EditCutoffBarrier";
1314
import { StreamingBarrier } from "./Messages/ChatBarrier/StreamingBarrier";
1415
import { RetryBarrier } from "./Messages/ChatBarrier/RetryBarrier";
1516
import { PinnedTodoList } from "./PinnedTodoList";
@@ -657,11 +658,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
657658
}}
658659
/>
659660
)}
660-
{isAtCutoff && (
661-
<div className="edit-cutoff-divider text-edit-mode bg-edit-mode/10 my-5 px-[15px] py-3 text-center text-xs font-medium">
662-
⚠️ Messages below this line will be removed when you submit the edit
663-
</div>
664-
)}
661+
{isAtCutoff && <EditCutoffBarrier />}
665662
{shouldShowInterruptedBarrier(msg) && <InterruptedBarrier />}
666663
</React.Fragment>
667664
);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import React from "react";
2+
import { cn } from "@/common/lib/utils";
3+
4+
interface EditCutoffBarrierProps {
5+
className?: string;
6+
}
7+
8+
/**
9+
* Barrier shown when editing a message to indicate where the cutoff point is.
10+
* Messages below this barrier will be removed when the edit is submitted.
11+
*/
12+
export const EditCutoffBarrier: React.FC<EditCutoffBarrierProps> = ({ className }) => {
13+
return (
14+
<div className={cn("flex items-center gap-3 py-3 my-4", className)}>
15+
<div
16+
className="h-px flex-1"
17+
style={{
18+
background: `linear-gradient(to right, transparent, var(--color-edit-mode) 20%, var(--color-edit-mode) 80%, transparent)`,
19+
}}
20+
/>
21+
<div className="border-edit-mode/30 bg-edit-mode/10 text-edit-mode flex items-center gap-2 rounded-md border px-3 py-1.5 text-[11px] font-medium">
22+
<span className="text-sm">⚠️</span>
23+
<span>Messages below will be removed when you submit</span>
24+
</div>
25+
<div
26+
className="h-px flex-1"
27+
style={{
28+
background: `linear-gradient(to right, transparent, var(--color-edit-mode) 20%, var(--color-edit-mode) 80%, transparent)`,
29+
}}
30+
/>
31+
</div>
32+
);
33+
};

src/browser/stories/App.chat.stories.tsx

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from "./mockFactory";
1717
import { updatePersistedState } from "@/browser/hooks/usePersistedState";
1818
import { getModelKey } from "@/common/constants/storage";
19-
import { setupSimpleChatStory, setupStreamingChatStory } from "./storyHelpers";
19+
import { setupSimpleChatStory, setupStreamingChatStory, setWorkspaceInput } from "./storyHelpers";
2020
import { within, userEvent, waitFor } from "@storybook/test";
2121

2222
export default {
@@ -663,3 +663,95 @@ export const ModelSelectorPrettyWithGateway: AppStory = {
663663
},
664664
},
665665
};
666+
667+
/**
668+
* Editing message state - shows the edit cutoff barrier and amber-styled input.
669+
* Demonstrates the UI when a user clicks "Edit" on a previous message.
670+
*/
671+
export const EditingMessage: AppStory = {
672+
render: () => (
673+
<AppWithMocks
674+
setup={() => {
675+
const workspaceId = "ws-editing";
676+
677+
// Ensure a deterministic starting state (Chromatic/Storybook can preserve localStorage
678+
// across story runs in the same session).
679+
setWorkspaceInput(workspaceId, "");
680+
681+
return setupSimpleChatStory({
682+
workspaceId,
683+
messages: [
684+
createUserMessage("msg-1", "Add authentication to the user API endpoint", {
685+
historySequence: 1,
686+
timestamp: STABLE_TIMESTAMP - 300000,
687+
}),
688+
createAssistantMessage(
689+
"msg-2",
690+
"I'll help you add authentication. Let me check the current implementation and add JWT validation.",
691+
{
692+
historySequence: 2,
693+
timestamp: STABLE_TIMESTAMP - 290000,
694+
toolCalls: [
695+
createFileReadTool(
696+
"call-1",
697+
"src/api/users.ts",
698+
"export function getUser(req, res) {\n const user = db.users.find(req.params.id);\n res.json(user);\n}"
699+
),
700+
],
701+
}
702+
),
703+
createUserMessage("msg-3", "Actually, can you use a different approach?", {
704+
historySequence: 3,
705+
timestamp: STABLE_TIMESTAMP - 280000,
706+
}),
707+
createAssistantMessage(
708+
"msg-4",
709+
"Of course! I can use a different authentication approach. What would you prefer?",
710+
{
711+
historySequence: 4,
712+
timestamp: STABLE_TIMESTAMP - 270000,
713+
}
714+
),
715+
],
716+
});
717+
}}
718+
/>
719+
),
720+
play: async ({ canvasElement }) => {
721+
const storyRoot = document.getElementById("storybook-root") ?? canvasElement;
722+
const canvas = within(storyRoot);
723+
724+
// Wait for user message actions to render (Edit buttons only appear on user messages)
725+
const editButtons = await canvas.findAllByLabelText("Edit", {}, { timeout: 10000 });
726+
if (editButtons.length === 0) throw new Error("No edit buttons found");
727+
728+
// Click edit on the first user message
729+
await userEvent.click(editButtons[0]);
730+
731+
// Wait for the editing state to be applied
732+
await waitFor(
733+
() => {
734+
const textarea = canvas.getByLabelText("Edit your last message");
735+
if (!textarea.className.includes("border-editing-mode")) {
736+
throw new Error("Textarea not in editing state");
737+
}
738+
},
739+
{ timeout: 2000 }
740+
);
741+
742+
// Verify the edit cutoff barrier appears
743+
await canvas.findByText(
744+
"Messages below will be removed when you submit",
745+
{},
746+
{ timeout: 2000 }
747+
);
748+
},
749+
parameters: {
750+
docs: {
751+
description: {
752+
story:
753+
"Shows the editing message state with the amber-styled input border and edit cutoff barrier indicating messages that will be removed.",
754+
},
755+
},
756+
},
757+
};

src/browser/styles/globals.css

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,15 @@
5353
--color-exec-mode-hover: hsl(268.56 94.04% 67%);
5454
--color-exec-mode-light: hsl(268.56 94.04% 78%);
5555

56-
--color-edit-mode: hsl(120 50% 35%);
57-
--color-edit-mode-hover: hsl(120 50% 47%);
58-
--color-edit-mode-light: hsl(120 50% 62%);
56+
/* Edit mode: amber/gold for editing warnings and barriers */
57+
--color-edit-mode: hsl(38 80% 45%);
58+
--color-edit-mode-hover: hsl(38 80% 55%);
59+
--color-edit-mode-light: hsl(38 80% 65%);
5960

6061
--color-read: hsl(210 70% 40%);
61-
--color-editing-mode: hsl(30 100% 50%);
62-
--color-editing-mode-alpha: hsla(30 100% 50% / 0.1);
62+
/* Editing state: subtle amber border when editing a message */
63+
--color-editing-mode: hsl(38 70% 50%);
64+
--color-editing-mode-alpha: hsla(38 70% 50% / 0.08);
6365
--color-pending: hsl(30 100% 70%);
6466

6567
--color-debug-mode: hsl(214 100% 64%);
@@ -300,13 +302,6 @@
300302
--color-exec-mode-hover: hsl(268.56 94.04% 67%);
301303
--color-exec-mode-light: hsl(268.56 94.04% 78%);
302304

303-
--color-edit-mode: hsl(120 50% 35%);
304-
--color-edit-mode-hover: hsl(120 50% 45%);
305-
--color-edit-mode-light: hsl(120 50% 60%);
306-
307-
--color-read: hsl(210 70% 40%);
308-
--color-editing-mode: hsl(30 100% 50%);
309-
--color-editing-mode-alpha: hsla(30 100% 50% / 0.08);
310305
--color-pending: hsl(30 100% 64%);
311306

312307
--color-debug-mode: hsl(214 100% 56%);
@@ -508,6 +503,19 @@
508503
/* Theme override hook: redeclare tokens inside this block to swap palettes */
509504
}
510505

506+
/* Solarized themes share the same semantic/mode colors (only surfaces differ). */
507+
:root[data-theme="solarized-light"],
508+
:root[data-theme="solarized-dark"] {
509+
--color-edit-mode: hsl(38 80% 40%); /* amber */
510+
--color-edit-mode-hover: hsl(38 80% 50%);
511+
--color-edit-mode-light: hsl(38 80% 60%);
512+
513+
--color-read: hsl(205 69% 49%);
514+
--color-editing-mode: hsl(38 70% 45%); /* amber */
515+
--color-editing-mode-alpha: hsla(38 70% 45% / 0.1);
516+
}
517+
518+
511519
/* Solarized Light Theme */
512520
:root[data-theme="solarized-light"] {
513521
color-scheme: light;
@@ -532,13 +540,6 @@
532540
--color-exec-mode-hover: hsl(237 43% 70%);
533541
--color-exec-mode-light: hsl(237 43% 78%);
534542

535-
--color-edit-mode: hsl(68 100% 30%); /* green */
536-
--color-edit-mode-hover: hsl(68 100% 40%);
537-
--color-edit-mode-light: hsl(68 100% 52%);
538-
539-
--color-read: hsl(205 69% 49%);
540-
--color-editing-mode: hsl(45 100% 35%); /* yellow */
541-
--color-editing-mode-alpha: hsla(45 100% 35% / 0.1);
542543
--color-pending: hsl(18 89% 44%); /* orange */
543544

544545
--color-debug-mode: hsl(205 69% 49%);
@@ -747,13 +748,6 @@
747748
--color-exec-mode-hover: hsl(237 43% 70%);
748749
--color-exec-mode-light: hsl(237 43% 78%);
749750

750-
--color-edit-mode: hsl(68 100% 30%); /* green */
751-
--color-edit-mode-hover: hsl(68 100% 40%);
752-
--color-edit-mode-light: hsl(68 100% 52%);
753-
754-
--color-read: hsl(205 69% 49%);
755-
--color-editing-mode: hsl(45 100% 35%); /* yellow */
756-
--color-editing-mode-alpha: hsla(45 100% 35% / 0.1);
757751
--color-pending: hsl(18 89% 44%); /* orange */
758752

759753
--color-debug-mode: hsl(205 69% 49%);

0 commit comments

Comments
 (0)