Skip to content

Commit ce063e4

Browse files
authored
Merge branch 'main' into matt/migrate-docs-to-mintlify
2 parents 002200b + 47c2da0 commit ce063e4

38 files changed

+983
-708
lines changed

bun.lock

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"@mozilla/readability": "^0.6.0",
1515
"@openrouter/ai-sdk-provider": "^1.2.5",
1616
"@orpc/client": "^1.11.3",
17+
"@orpc/openapi": "^1.12.2",
1718
"@orpc/server": "^1.11.3",
1819
"@orpc/zod": "^1.11.3",
1920
"@radix-ui/react-checkbox": "^1.3.3",
@@ -53,7 +54,7 @@
5354
"rehype-harden": "^1.1.5",
5455
"shescape": "^2.1.6",
5556
"source-map-support": "^0.5.21",
56-
"streamdown": "^1.4.0",
57+
"streamdown": "1.6.10",
5758
"trpc-cli": "^0.12.1",
5859
"turndown": "^7.2.2",
5960
"undici": "^7.16.0",
@@ -3143,6 +3144,8 @@
31433144

31443145
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
31453146

3147+
"remend": ["remend@1.0.1", "", {}, "sha512-152puVH0qMoRJQFnaMG+rVDdf01Jq/CaED+MBuXExurJgdbkLp0c3TIe4R12o28Klx8uyGsjvFNG05aFG69G9w=="],
3148+
31463149
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
31473150

31483151
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
@@ -3295,7 +3298,7 @@
32953298

32963299
"storybook": ["storybook@10.1.4", "", { "dependencies": { "@storybook/global": "^5.0.0", "@storybook/icons": "^2.0.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/user-event": "^14.6.1", "@vitest/expect": "3.2.4", "@vitest/spy": "3.2.4", "esbuild": "^0.18.0 || ^0.19.0 || ^0.20.0 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0 || ^0.26.0 || ^0.27.0", "recast": "^0.23.5", "semver": "^7.6.2", "use-sync-external-store": "^1.5.0", "ws": "^8.18.0" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": "./dist/bin/dispatcher.js" }, "sha512-FrBjm8I8O+pYEOPHcdW9xWwgXSZxte7lza9q2lN3jFN4vuW79m5j0OnTQeR8z9MmIbBTvkIpp3yMBebl53Yt5Q=="],
32973300

3298-
"streamdown": ["streamdown@1.6.9", "", { "dependencies": { "clsx": "^2.1.1", "hast": "^1.0.0", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "katex": "^0.16.22", "lucide-react": "^0.542.0", "marked": "^16.2.1", "mermaid": "^11.11.0", "rehype-harden": "^1.1.6", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", "remark-cjk-friendly": "^1.2.3", "remark-cjk-friendly-gfm-strikethrough": "^1.2.3", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "shiki": "^3.12.2", "tailwind-merge": "^3.3.1", "unified": "^11.0.5", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-rtUZcRvDYNEgduq1OxNJzuYYmchZVXq+1Pw3T445RrYwrT+SGNK1drtt1eaqC4HaD8YYIscdtMSlZFaNM+yYGA=="],
3301+
"streamdown": ["streamdown@1.6.10", "", { "dependencies": { "clsx": "^2.1.1", "hast": "^1.0.0", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "katex": "^0.16.22", "lucide-react": "^0.542.0", "marked": "^16.2.1", "mermaid": "^11.11.0", "rehype-harden": "^1.1.6", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", "remark-cjk-friendly": "^1.2.3", "remark-cjk-friendly-gfm-strikethrough": "^1.2.3", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.0.1", "shiki": "^3.12.2", "tailwind-merge": "^3.3.1", "unified": "^11.0.5", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-B4Y3Z/qiXl1Dc+LzAB5c52Cd1QGRiFjaDwP+ERoj1JtCykdRDM8X6HwQnn3YkpkSk0x3R7S/6LrGe1nQiElHQQ=="],
32993302

33003303
"string-length": ["string-length@6.0.0", "", { "dependencies": { "strip-ansi": "^7.1.0" } }, "sha512-1U361pxZHEQ+FeSjzqRpV+cu2vTzYeWeafXFLykiFlv4Vc0n3njgU8HrMbyik5uwm77naWMuVG8fhEF+Ovb1Kg=="],
33013304

docs/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
- [Prompting Tips](./prompting-tips.md)
3131
- [System Prompt](./system-prompt.md)
3232
- [Telemetry](./telemetry.md)
33+
- [Mux Codes](./mux-codes.md)
3334

3435
# Development
3536

docs/mux-codes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ title: Mux Codes
33
description: Redeem free LLM token credits for evaluating Mux
44
---
55

6+
67
Mux codes are free LLM token credits for evaluating Mux. Coder employees disburse them at events to users we think will enjoy Mux. If you want to get your hands on a code, ask the nearest Coder employee.
78

89
A code looks like `ABC-123`.

jest.config.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ module.exports = {
1818
transform: {
1919
"^.+\\.(ts|tsx|js|mjs)$": ["babel-jest"],
2020
},
21-
// Transform ESM modules (like shiki, @orpc) to CommonJS for Jest
22-
transformIgnorePatterns: ["node_modules/(?!(@orpc|shiki)/)"],
21+
// Transform ESM modules to CommonJS for Jest
22+
transformIgnorePatterns: ["node_modules/(?!(@orpc|shiki|json-schema-typed|rou3)/)"],
2323
// Run tests in parallel (use 50% of available cores, or 4 minimum)
2424
maxWorkers: "50%",
2525
// Force exit after tests complete to avoid hanging on lingering handles

mobile/src/utils/slashCommandHelpers.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ describe("buildMobileCompactionPayload", () => {
5454
const payload = buildMobileCompactionPayload(parsed, baseOptions);
5555

5656
expect(payload.messageText).toContain("approximately 615 words");
57+
5758
expect(payload.messageText).toContain(parsed.continueMessage);
5859
expect(payload.metadata.type).toBe("compaction-request");
5960
expect(payload.metadata.rawCommand).toContain("/compact -t 800 -m anthropic:claude-opus-4-1");

mobile/src/utils/slashCommandHelpers.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,17 @@ import type { MuxFrontendMetadata } from "@/common/types/message";
22
import type { ParsedCommand, SlashSuggestion } from "@/browser/utils/slashCommands/types";
33
import type { InferClientInputs } from "@orpc/client";
44
import type { ORPCClient } from "../orpc/client";
5+
import {
6+
DEFAULT_COMPACTION_WORD_TARGET,
7+
WORDS_TO_TOKENS_RATIO,
8+
buildCompactionPrompt,
9+
} from "@/common/constants/ui";
510

611
type SendMessageOptions = NonNullable<
712
InferClientInputs<ORPCClient>["workspace"]["sendMessage"]["options"]
813
>;
914

1015
export const MOBILE_HIDDEN_COMMANDS = new Set(["telemetry", "vim"]);
11-
const WORDS_PER_TOKEN = 1.3;
12-
const DEFAULT_WORD_TARGET = 2000;
1316

1417
export function extractRootCommand(replacement: string): string | null {
1518
if (typeof replacement !== "string") {
@@ -44,12 +47,10 @@ export function buildMobileCompactionPayload(
4447
baseOptions: SendMessageOptions
4548
): MobileCompactionPayload {
4649
const targetWords = parsed.maxOutputTokens
47-
? Math.round(parsed.maxOutputTokens / WORDS_PER_TOKEN)
48-
: DEFAULT_WORD_TARGET;
50+
? Math.round(parsed.maxOutputTokens / WORDS_TO_TOKENS_RATIO)
51+
: DEFAULT_COMPACTION_WORD_TARGET;
4952

50-
let messageText =
51-
`Summarize this conversation into a compact form for a new Assistant to continue helping the user. ` +
52-
`Use approximately ${targetWords} words.`;
53+
let messageText = buildCompactionPrompt(targetWords);
5354

5455
if (parsed.continueMessage) {
5556
messageText += `\n\nThe user wants to continue with: ${parsed.continueMessage}`;

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"@mozilla/readability": "^0.6.0",
5656
"@openrouter/ai-sdk-provider": "^1.2.5",
5757
"@orpc/client": "^1.11.3",
58+
"@orpc/openapi": "^1.12.2",
5859
"@orpc/server": "^1.11.3",
5960
"@orpc/zod": "^1.11.3",
6061
"@radix-ui/react-checkbox": "^1.3.3",
@@ -94,7 +95,7 @@
9495
"rehype-harden": "^1.1.5",
9596
"shescape": "^2.1.6",
9697
"source-map-support": "^0.5.21",
97-
"streamdown": "^1.4.0",
98+
"streamdown": "1.6.10",
9899
"trpc-cli": "^0.12.1",
99100
"turndown": "^7.2.2",
100101
"undici": "^7.16.0",

src/browser/components/AIView.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ const AIViewInner: React.FC<AIViewProps> = ({
209209
// Use auto-scroll hook for scroll management
210210
const {
211211
contentRef,
212+
innerRef,
212213
autoScroll,
213214
setAutoScroll,
214215
performAutoScroll,
@@ -500,7 +501,10 @@ const AIViewInner: React.FC<AIViewProps> = ({
500501
data-testid="message-window"
501502
className="h-full overflow-y-auto p-[15px] leading-[1.5] break-words whitespace-pre-wrap"
502503
>
503-
<div className={cn("max-w-4xl mx-auto", mergedMessages.length === 0 && "h-full")}>
504+
<div
505+
ref={innerRef}
506+
className={cn("max-w-4xl mx-auto", mergedMessages.length === 0 && "h-full")}
507+
>
504508
{mergedMessages.length === 0 ? (
505509
<div className="text-placeholder flex h-full flex-1 flex-col items-center justify-center text-center [&_h3]:m-0 [&_h3]:mb-2.5 [&_h3]:text-base [&_h3]:font-medium [&_p]:m-0 [&_p]:text-[13px]">
506510
<h3>No Messages Yet</h3>

src/browser/components/ChatInput/CreationCenterContent.tsx

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,38 +3,36 @@ import React from "react";
33
interface CreationCenterContentProps {
44
projectName: string;
55
isSending: boolean;
6-
inputPreview?: string;
6+
/** The confirmed workspace name (null while name generation is in progress) */
7+
workspaceName?: string | null;
78
}
89

910
/**
1011
* Center content displayed during workspace creation
11-
* Shows either a loading state with the user's prompt or welcome message
12+
* Shows either a loading state with the workspace name or welcome message
1213
*/
1314
export function CreationCenterContent(props: CreationCenterContentProps) {
14-
// Truncate long prompts for preview display
15-
const truncatedPreview =
16-
props.inputPreview && props.inputPreview.length > 150
17-
? props.inputPreview.slice(0, 150) + "..."
18-
: props.inputPreview;
19-
2015
return (
2116
<div className="flex flex-1 items-center justify-center">
2217
{props.isSending ? (
2318
<div className="max-w-xl px-8 text-center">
2419
<div className="bg-accent mb-4 inline-block h-8 w-8 animate-spin rounded-full border-4 border-solid border-current border-r-transparent"></div>
2520
<h2 className="text-foreground mb-2 text-lg font-medium">Creating workspace</h2>
26-
{truncatedPreview && (
27-
<p className="text-muted text-sm leading-relaxed">
28-
Generating name for &ldquo;{truncatedPreview}&rdquo;
29-
</p>
30-
)}
21+
<p className="text-muted text-sm leading-relaxed">
22+
{props.workspaceName ? (
23+
<>
24+
<code className="bg-separator rounded px-1">{props.workspaceName}</code>
25+
</>
26+
) : (
27+
"Generating name…"
28+
)}
29+
</p>
3130
</div>
3231
) : (
3332
<div className="max-w-2xl px-8 text-center">
3433
<h1 className="text-foreground mb-4 text-2xl font-semibold">{props.projectName}</h1>
3534
<p className="text-muted text-sm leading-relaxed">
36-
Describe what you want to build. A new workspace will be created with an automatically
37-
generated branch name. Configure runtime and model options below.
35+
Describe what you want to build and a workspace will be created.
3836
</p>
3937
</div>
4038
)}
Lines changed: 120 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import React from "react";
1+
import React, { useCallback } from "react";
22
import { RUNTIME_MODE, type RuntimeMode } from "@/common/types/runtime";
33
import { Select } from "../Select";
44
import { RuntimeIconSelector } from "../RuntimeIconSelector";
5+
import { Loader2, Wand2 } from "lucide-react";
6+
import { cn } from "@/common/lib/utils";
7+
import { Tooltip, TooltipWrapper } from "../Tooltip";
8+
import type { WorkspaceNameState } from "@/browser/hooks/useWorkspaceName";
59

610
interface CreationControlsProps {
711
branches: string[];
@@ -10,68 +14,144 @@ interface CreationControlsProps {
1014
runtimeMode: RuntimeMode;
1115
defaultRuntimeMode: RuntimeMode;
1216
sshHost: string;
13-
/** Called when user clicks a runtime icon to select it (does not persist) */
1417
onRuntimeModeChange: (mode: RuntimeMode) => void;
15-
/** Called when user checks "Default for project" checkbox (persists) */
1618
onSetDefaultRuntime: (mode: RuntimeMode) => void;
17-
/** Called when user changes SSH host */
1819
onSshHostChange: (host: string) => void;
1920
disabled: boolean;
21+
/** Workspace name generation state and actions */
22+
nameState: WorkspaceNameState;
2023
}
2124

2225
/**
2326
* Additional controls shown only during workspace creation
2427
* - Trunk branch selector (which branch to fork from) - hidden for Local runtime
2528
* - Runtime mode (Local, Worktree, SSH)
29+
* - Workspace name (auto-generated with manual override)
2630
*/
2731
export function CreationControls(props: CreationControlsProps) {
2832
// Local runtime doesn't need a trunk branch selector (uses project dir as-is)
2933
const showTrunkBranchSelector =
3034
props.branches.length > 0 && props.runtimeMode !== RUNTIME_MODE.LOCAL;
3135

32-
return (
33-
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
34-
{/* Runtime Selector - icon-based with tooltips */}
35-
<RuntimeIconSelector
36-
value={props.runtimeMode}
37-
onChange={props.onRuntimeModeChange}
38-
defaultMode={props.defaultRuntimeMode}
39-
onSetDefault={props.onSetDefaultRuntime}
40-
disabled={props.disabled}
41-
/>
36+
const { nameState } = props;
37+
38+
const handleNameChange = useCallback(
39+
(e: React.ChangeEvent<HTMLInputElement>) => {
40+
nameState.setName(e.target.value);
41+
},
42+
[nameState]
43+
);
44+
45+
// Clicking into the input disables auto-generation so user can edit
46+
const handleInputFocus = useCallback(() => {
47+
if (nameState.autoGenerate) {
48+
nameState.setAutoGenerate(false);
49+
}
50+
}, [nameState]);
51+
52+
// Toggle auto-generation via wand button
53+
const handleWandClick = useCallback(() => {
54+
nameState.setAutoGenerate(!nameState.autoGenerate);
55+
}, [nameState]);
4256

43-
{/* Trunk Branch Selector - hidden for Local runtime */}
44-
{showTrunkBranchSelector && (
45-
<div
46-
className="flex items-center gap-1"
47-
data-component="TrunkBranchGroup"
48-
data-tutorial="trunk-branch"
49-
>
50-
<label htmlFor="trunk-branch" className="text-muted text-xs">
51-
From:
52-
</label>
53-
<Select
54-
id="trunk-branch"
55-
value={props.trunkBranch}
56-
options={props.branches}
57-
onChange={props.onTrunkBranchChange}
57+
return (
58+
<div className="flex flex-col gap-2">
59+
{/* First row: Workspace name with magic wand toggle */}
60+
<div className="flex items-center gap-2" data-component="WorkspaceNameGroup">
61+
<label htmlFor="workspace-name" className="text-muted text-xs whitespace-nowrap">
62+
Name:
63+
</label>
64+
<div className="relative max-w-xs flex-1">
65+
<input
66+
id="workspace-name"
67+
type="text"
68+
value={nameState.name}
69+
onChange={handleNameChange}
70+
onFocus={handleInputFocus}
71+
placeholder={nameState.isGenerating ? "Generating..." : "workspace-name"}
5872
disabled={props.disabled}
59-
className="max-w-[120px]"
73+
className={cn(
74+
"bg-separator text-foreground border-border-medium focus:border-accent h-6 w-full rounded border px-2 pr-6 text-xs focus:outline-none disabled:opacity-50",
75+
nameState.error && "border-red-500"
76+
)}
6077
/>
78+
{/* Magic wand / loading indicator - vertically centered */}
79+
<div className="absolute inset-y-0 right-0 flex items-center pr-1.5">
80+
{nameState.isGenerating ? (
81+
<Loader2 className="text-accent h-3.5 w-3.5 animate-spin" />
82+
) : (
83+
<TooltipWrapper inline>
84+
<button
85+
type="button"
86+
onClick={handleWandClick}
87+
disabled={props.disabled}
88+
className="flex h-full items-center disabled:opacity-50"
89+
aria-label={nameState.autoGenerate ? "Disable auto-naming" : "Enable auto-naming"}
90+
>
91+
<Wand2
92+
className={cn(
93+
"h-3.5 w-3.5 transition-colors",
94+
nameState.autoGenerate
95+
? "text-accent"
96+
: "text-muted-foreground opacity-50 hover:opacity-75"
97+
)}
98+
/>
99+
</button>
100+
<Tooltip className="tooltip" align="center">
101+
{nameState.autoGenerate ? "Auto-naming enabled" : "Click to enable auto-naming"}
102+
</Tooltip>
103+
</TooltipWrapper>
104+
)}
105+
</div>
61106
</div>
62-
)}
107+
{/* Error display - inline */}
108+
{nameState.error && <span className="text-xs text-red-500">{nameState.error}</span>}
109+
</div>
63110

64-
{/* SSH Host Input - after From selector */}
65-
{props.runtimeMode === RUNTIME_MODE.SSH && (
66-
<input
67-
type="text"
68-
value={props.sshHost}
69-
onChange={(e) => props.onSshHostChange(e.target.value)}
70-
placeholder="user@host"
111+
{/* Second row: Runtime, Branch, SSH */}
112+
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
113+
{/* Runtime Selector - icon-based with tooltips */}
114+
<RuntimeIconSelector
115+
value={props.runtimeMode}
116+
onChange={props.onRuntimeModeChange}
117+
defaultMode={props.defaultRuntimeMode}
118+
onSetDefault={props.onSetDefaultRuntime}
71119
disabled={props.disabled}
72-
className="bg-separator text-foreground border-border-medium focus:border-accent w-32 rounded border px-1 py-0.5 text-xs focus:outline-none disabled:opacity-50"
73120
/>
74-
)}
121+
122+
{/* Trunk Branch Selector - hidden for Local runtime */}
123+
{showTrunkBranchSelector && (
124+
<div
125+
className="flex h-6 items-center gap-1"
126+
data-component="TrunkBranchGroup"
127+
data-tutorial="trunk-branch"
128+
>
129+
<label htmlFor="trunk-branch" className="text-muted text-xs">
130+
From:
131+
</label>
132+
<Select
133+
id="trunk-branch"
134+
value={props.trunkBranch}
135+
options={props.branches}
136+
onChange={props.onTrunkBranchChange}
137+
disabled={props.disabled}
138+
className="h-6 max-w-[120px]"
139+
/>
140+
</div>
141+
)}
142+
143+
{/* SSH Host Input - after From selector */}
144+
{props.runtimeMode === RUNTIME_MODE.SSH && (
145+
<input
146+
type="text"
147+
value={props.sshHost}
148+
onChange={(e) => props.onSshHostChange(e.target.value)}
149+
placeholder="user@host"
150+
disabled={props.disabled}
151+
className="bg-separator text-foreground border-border-medium focus:border-accent h-6 w-32 rounded border px-1 text-xs focus:outline-none disabled:opacity-50"
152+
/>
153+
)}
154+
</div>
75155
</div>
76156
);
77157
}

0 commit comments

Comments
 (0)