@molecule/app-ide-react 1.9.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -20
- package/dist/components/ChatPanel.d.ts +11 -5
- package/dist/components/ChatPanel.d.ts.map +1 -1
- package/dist/components/ChatPanel.js +123 -41
- package/dist/components/ChatPanel.js.map +1 -1
- package/dist/components/PreviewPanel.d.ts +1 -1
- package/dist/components/PreviewPanel.d.ts.map +1 -1
- package/dist/components/PreviewPanel.js +286 -32
- package/dist/components/PreviewPanel.js.map +1 -1
- package/dist/components/chat-models-utilities.d.ts +7 -3
- package/dist/components/chat-models-utilities.d.ts.map +1 -1
- package/dist/components/chat-models-utilities.js +65 -9
- package/dist/components/chat-models-utilities.js.map +1 -1
- package/dist/types.d.ts +40 -19
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-08-
|
|
6
|
+
Generated: 2026-08-31T12:50:49.416Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/app-ide-react
|
|
@@ -266,22 +266,22 @@ interface ChatPanelProps {
|
|
|
266
266
|
*/
|
|
267
267
|
onRenderError?: (error: Error, info: ErrorInfo) => void
|
|
268
268
|
/**
|
|
269
|
-
* Called when a user avatar in the chat timeline is clicked —
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
274
|
-
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
269
|
+
* Called when a user avatar or author name in the chat timeline is clicked —
|
|
270
|
+
* the host opens THAT user's profile (e.g. molecule.dev's profile modal).
|
|
271
|
+
* Receives the clicked author's {@link ChatUserIdentity}: a teammate's click
|
|
272
|
+
* carries their persisted author fields, the signed-in user's own click
|
|
273
|
+
* carries their own — the host compares `id` against the signed-in user to
|
|
274
|
+
* decide editable-own vs view-only-teammate. Omit it (the default) to render
|
|
275
|
+
* the avatars/names non-interactive (static, exactly as before). Only human
|
|
276
|
+
* authors are clickable — the molecule glyph on auto-sent messages is not.
|
|
277
277
|
*/
|
|
278
278
|
onProfileClick?: (user: ChatUserIdentity) => void
|
|
279
279
|
/**
|
|
280
|
-
* The signed-in user's id
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
* user. Omit in a solo host —
|
|
284
|
-
*
|
|
280
|
+
* The signed-in user's id (a message's persisted `author.id`). Used to stamp
|
|
281
|
+
* the identity handed to {@link onProfileClick} for AUTHOR-LESS messages (the
|
|
282
|
+
* local optimistic echo, legacy solo rows), which are always the signed-in
|
|
283
|
+
* user's own. Omit in a solo host — those clicks then carry no `id`, which
|
|
284
|
+
* hosts treat as "the signed-in user".
|
|
285
285
|
*/
|
|
286
286
|
currentUserId?: string
|
|
287
287
|
/** Called when the server signals (via the `ready_to_build` stream event) that discovery is complete and the sandbox should boot. */
|
|
@@ -543,16 +543,24 @@ interface ChatPanelProps {
|
|
|
543
543
|
|
|
544
544
|
#### `ChatUserIdentity`
|
|
545
545
|
|
|
546
|
-
Identity of the user whose avatar was clicked in the chat timeline,
|
|
547
|
-
{@link ChatPanelProps.onProfileClick} so the host can open that
|
|
546
|
+
Identity of the user whose avatar or name was clicked in the chat timeline,
|
|
547
|
+
passed to {@link ChatPanelProps.onProfileClick} so the host can open that
|
|
548
|
+
user's profile.
|
|
548
549
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
550
|
+
The identity is always the clicked MESSAGE AUTHOR's — a teammate's click
|
|
551
|
+
carries their persisted `author` fields, never the viewer's. An author-less
|
|
552
|
+
message (the local optimistic echo, legacy solo rows) is the signed-in user's
|
|
553
|
+
own, so it carries {@link ChatPanelProps.currentUserId} + the viewer's avatar.
|
|
554
|
+
The host compares `id` against the signed-in user to pick the surface: own →
|
|
555
|
+
editable profile, someone else → view-only. A missing `id` means "the
|
|
556
|
+
signed-in user" (author-less rows in a host that passes no `currentUserId`).
|
|
553
557
|
|
|
554
558
|
```typescript
|
|
555
559
|
interface ChatUserIdentity {
|
|
560
|
+
/** The clicked user's stable id (a persisted `author.id`), when known. */
|
|
561
|
+
id?: string
|
|
562
|
+
/** The clicked user's display name (a persisted `author.name`), when known. */
|
|
563
|
+
name?: string
|
|
556
564
|
/** The clicked user's avatar value (data-URI / URL), if any. */
|
|
557
565
|
avatar?: string | null
|
|
558
566
|
}
|
|
@@ -959,6 +967,19 @@ interface PreviewPanelProps {
|
|
|
959
967
|
* fix request. The argument is optional for backward compatibility with no-arg callers.
|
|
960
968
|
*/
|
|
961
969
|
onPreviewStuck?: (report?: PreviewStuckReport) => void
|
|
970
|
+
/**
|
|
971
|
+
* Escalation hook: restart the BACKING dev server. The panel calls this at most
|
|
972
|
+
* once per broken episode (long cooldown, never during a build) when client-side
|
|
973
|
+
* recovery is provably useless: the server answers probes, at least one automatic
|
|
974
|
+
* reload already ran, and the app has still never confirmed a render for minutes.
|
|
975
|
+
* Document reloads cannot heal that class — e.g. a poisoned immutable module cache
|
|
976
|
+
* keyed to the dev server's version hash (observed live 2026-08-31); only a server
|
|
977
|
+
* restart mints new module URLs. Return (or resolve) `false` to DECLINE (wrong
|
|
978
|
+
* role, hidden tab, sandbox not running, a turn active) — the panel then burns no
|
|
979
|
+
* budget and may ask again later; any other result counts as "restart initiated".
|
|
980
|
+
* Omit the prop to disable escalation entirely (the default for generic hosts).
|
|
981
|
+
*/
|
|
982
|
+
onRestartBackend?: () => Promise<boolean | void> | boolean | void
|
|
962
983
|
/**
|
|
963
984
|
* Called when the preview's render verdict changes ({@link PreviewRenderState}) — and
|
|
964
985
|
* with the current location so the host can report WHERE. The host forwards this to the
|
|
@@ -2291,6 +2312,7 @@ function PreviewPanel({
|
|
|
2291
2312
|
className,
|
|
2292
2313
|
onPreviewError,
|
|
2293
2314
|
onPreviewStuck,
|
|
2315
|
+
onRestartBackend,
|
|
2294
2316
|
onRenderState,
|
|
2295
2317
|
uiCommand,
|
|
2296
2318
|
onUiResult,
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import type { JSX } from 'react';
|
|
19
19
|
import type { ChatMessage } from '@molecule/app-ai-chat';
|
|
20
|
-
import type { ChatPanelProps, IdeClientAction } from '../types.js';
|
|
20
|
+
import type { ChatPanelProps, ChatUserIdentity, IdeClientAction } from '../types.js';
|
|
21
21
|
import type { Activity } from './activity-utilities.js';
|
|
22
22
|
import { type CommandDef } from './chat-commands.js';
|
|
23
23
|
import type { ModelMode } from './chat-model-mode-utilities.js';
|
|
@@ -82,8 +82,14 @@ export interface MessageItemProps {
|
|
|
82
82
|
chatMode: 'plan' | 'execute';
|
|
83
83
|
/** Signed-in user's avatar shown beside their own messages (SOC1); icon fallback when absent/unsafe. */
|
|
84
84
|
userAvatar?: string | null;
|
|
85
|
-
/**
|
|
86
|
-
|
|
85
|
+
/**
|
|
86
|
+
* When set, the author's avatar AND name become clickable and fire this with
|
|
87
|
+
* the clicked AUTHOR's identity — a teammate's message opens THEIR profile,
|
|
88
|
+
* the signed-in user's own opens theirs (C5). See {@link ChatPanelProps.onProfileClick}.
|
|
89
|
+
*/
|
|
90
|
+
onProfileClick?: (user: ChatUserIdentity) => void;
|
|
91
|
+
/** Signed-in user's id — stamps the identity for author-less rows (the local echo, legacy solo rows). */
|
|
92
|
+
currentUserId?: string;
|
|
87
93
|
/** Discovery phase — gives consecutive question/answer cards roomier, uncollapsed spacing (B3). */
|
|
88
94
|
discovery?: boolean;
|
|
89
95
|
/**
|
|
@@ -133,9 +139,9 @@ export interface ChatInnerProps {
|
|
|
133
139
|
onActivityClick?: (activity: Activity) => void;
|
|
134
140
|
/** See {@link ChatPanelProps.onRenderError}. */
|
|
135
141
|
onRenderError?: ChatPanelProps['onRenderError'];
|
|
136
|
-
/** Called when a user avatar in the chat timeline is clicked — see {@link ChatPanelProps.onProfileClick}. */
|
|
142
|
+
/** Called when a user avatar or author name in the chat timeline is clicked — see {@link ChatPanelProps.onProfileClick}. */
|
|
137
143
|
onProfileClick?: ChatPanelProps['onProfileClick'];
|
|
138
|
-
/** The signed-in user's id —
|
|
144
|
+
/** The signed-in user's id — stamps author-less rows' identity. See {@link ChatPanelProps.currentUserId}. */
|
|
139
145
|
currentUserId?: string;
|
|
140
146
|
/** Called on the `ready_to_build` stream event — discovery is done; boot the sandbox. */
|
|
141
147
|
onReadyToBuild?: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChatPanel.d.ts","sourceRoot":"","sources":["../../src/components/ChatPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAa,MAAM,OAAO,CAAA;AAY3C,OAAO,KAAK,EAAa,WAAW,EAAmB,MAAM,uBAAuB,CAAA;AAuCpF,OAAO,KAAK,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"ChatPanel.d.ts","sourceRoot":"","sources":["../../src/components/ChatPanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -935,6 +935,36 @@ export function CommitCardItem({ card, onRevert, }) {
|
|
|
935
935
|
return _jsx("div", { children: path }, path);
|
|
936
936
|
}) }) }))] }) }));
|
|
937
937
|
}
|
|
938
|
+
/**
|
|
939
|
+
* The clickable author-name in a message header (C5) — a reset button that
|
|
940
|
+
* renders exactly like the bold name span it replaces, with an underline on
|
|
941
|
+
* hover/focus as the affordance, and opens the author's profile like their
|
|
942
|
+
* avatar does. Its own component so the hover state never re-renders the
|
|
943
|
+
* (memoized, heavy) message row.
|
|
944
|
+
*
|
|
945
|
+
* @param props - Click handler + the name text.
|
|
946
|
+
* @param props.onClick - Opens the author's profile.
|
|
947
|
+
* @param props.children - The author's display name.
|
|
948
|
+
* @returns The name button.
|
|
949
|
+
*/
|
|
950
|
+
function AuthorNameButton({ onClick, children, }) {
|
|
951
|
+
const [hover, setHover] = useState(false);
|
|
952
|
+
return (_jsx("button", { type: "button", onClick: onClick, onMouseEnter: () => setHover(true), onMouseLeave: () => setHover(false), onFocus: () => setHover(true), onBlur: () => setHover(false), "data-mol-id": "chat-user-name-button",
|
|
953
|
+
// Full button reset so it is byte-identical to the former bold span at
|
|
954
|
+
// rest — the underline affordance is the only visual change on hover.
|
|
955
|
+
style: {
|
|
956
|
+
padding: 0,
|
|
957
|
+
margin: 0,
|
|
958
|
+
border: 'none',
|
|
959
|
+
background: 'transparent',
|
|
960
|
+
font: 'inherit',
|
|
961
|
+
color: 'inherit',
|
|
962
|
+
fontWeight: 600,
|
|
963
|
+
cursor: 'pointer',
|
|
964
|
+
textDecoration: hover ? 'underline' : 'none',
|
|
965
|
+
}, children: children }));
|
|
966
|
+
}
|
|
967
|
+
AuthorNameButton.displayName = 'AuthorNameButton';
|
|
938
968
|
/**
|
|
939
969
|
* Renders a single message (user or assistant) in the chat timeline.
|
|
940
970
|
* Wrapped in React.memo so unchanged messages skip re-rendering when
|
|
@@ -943,7 +973,7 @@ export function CommitCardItem({ card, onRevert, }) {
|
|
|
943
973
|
* @returns The rendered message item.
|
|
944
974
|
*/
|
|
945
975
|
const MessageItem = memo(function MessageItem(props) {
|
|
946
|
-
const { msg, sendMessage, handleAskUserResponse, isLoading, streamingStatus, onNavigatePreview, undoneTcIds, handleUndoToggle, onFileOpen, onFileDoubleClick, onFileDiff, handleFileRevert, setInputAndCursorEnd, setModelPicker, chatMode, userAvatar,
|
|
976
|
+
const { msg, sendMessage, handleAskUserResponse, isLoading, streamingStatus, onNavigatePreview, undoneTcIds, handleUndoToggle, onFileOpen, onFileDoubleClick, onFileDiff, handleFileRevert, setInputAndCursorEnd, setModelPicker, chatMode, userAvatar, onProfileClick, currentUserId, discovery, buildUpgradeCta, agentName, canEdit, } = props;
|
|
947
977
|
const cm = getClassMap();
|
|
948
978
|
const themeMode = useThemeMode();
|
|
949
979
|
const isLight = themeMode === 'light';
|
|
@@ -959,6 +989,15 @@ const MessageItem = memo(function MessageItem(props) {
|
|
|
959
989
|
// A real, user-typed message (the only one styled with the blue border + the
|
|
960
990
|
// user's own avatar).
|
|
961
991
|
const isUser = msg.role === 'user' && !isAutomatic;
|
|
992
|
+
// Who this message belongs to — the identity a profile click hands the host.
|
|
993
|
+
// An AUTHORED message is that author's (a teammate's click opens THEIR
|
|
994
|
+
// profile, never the viewer's); an author-less one (the local optimistic
|
|
995
|
+
// echo, legacy solo rows) is the signed-in user's own, stamped with
|
|
996
|
+
// currentUserId + the viewer's avatar so the host still knows who it is.
|
|
997
|
+
const authorIdentity = msg.author
|
|
998
|
+
? { id: msg.author.id, name: msg.author.name, avatar: msg.author.avatar ?? null }
|
|
999
|
+
: { id: currentUserId, avatar: userAvatar ?? null };
|
|
1000
|
+
const openAuthorProfile = onProfileClick ? () => onProfileClick(authorIdentity) : undefined;
|
|
962
1001
|
// Spacing follows the one timeline convention (see TIMELINE_ITEM_GAP above): a
|
|
963
1002
|
// single bottom margin, no top margin, no negatives — so a message can never
|
|
964
1003
|
// pull itself up over the previous item's spacing. Discovery is roomier but
|
|
@@ -980,11 +1019,16 @@ const MessageItem = memo(function MessageItem(props) {
|
|
|
980
1019
|
// A real user message keeps its classic look: a gray surface with the
|
|
981
1020
|
// molecule brand's ANIMATED blue gradient stripe (3px) on the left edge,
|
|
982
1021
|
// drawn by the `::before` injected via USER_ACCENT_STYLE + gated on the
|
|
983
|
-
// data-mol-id below,
|
|
984
|
-
//
|
|
985
|
-
//
|
|
986
|
-
//
|
|
987
|
-
//
|
|
1022
|
+
// data-mol-id below, PLUS a solid blue outline ring (host-tunable via
|
|
1023
|
+
// `--mol-chat-accent-border`) so a sent-to-Synthase message reads bordered
|
|
1024
|
+
// the same way a team note does, and the user's full-size avatar. An
|
|
1025
|
+
// auto-sent message is instead a green (success) tinted card — same chrome
|
|
1026
|
+
// as the info cards — so it's unmistakably agent-sent, not user-typed (C2).
|
|
1027
|
+
// A team note keeps the user-message look but swaps the blue chrome (its
|
|
1028
|
+
// data-mol-id differs, so USER_ACCENT_STYLE never applies) for the gold
|
|
1029
|
+
// team-only border — with its own solid 3px left band (an inset box-shadow,
|
|
1030
|
+
// which starts at the border's inner edge exactly like the stripe's
|
|
1031
|
+
// `inset: 0` ::before, so the two rows' left accents match in geometry).
|
|
988
1032
|
...(isAutomatic
|
|
989
1033
|
? chatCardStyle(AUTO_SENT_ACCENT)
|
|
990
1034
|
: {
|
|
@@ -993,7 +1037,12 @@ const MessageItem = memo(function MessageItem(props) {
|
|
|
993
1037
|
paddingTop: '10px',
|
|
994
1038
|
paddingBottom: '10px',
|
|
995
1039
|
paddingRight: '10px',
|
|
996
|
-
|
|
1040
|
+
border: isTeamNote
|
|
1041
|
+
? `1px solid ${NOTICE_TONE.gold.accent}`
|
|
1042
|
+
: '1px solid var(--mol-chat-accent-border, var(--mol-color-primary, #3060c0))',
|
|
1043
|
+
...(isTeamNote
|
|
1044
|
+
? { boxShadow: `inset 3px 0 0 0 ${NOTICE_TONE.gold.accent}` }
|
|
1045
|
+
: {}),
|
|
997
1046
|
}),
|
|
998
1047
|
}, "data-mol-id": isTeamNote
|
|
999
1048
|
? 'chat-team-message'
|
|
@@ -1004,20 +1053,24 @@ const MessageItem = memo(function MessageItem(props) {
|
|
|
1004
1053
|
// when they have none — NEVER the viewing user's picture (which made
|
|
1005
1054
|
// a teammate's avatar-less message wear the viewer's own face). Only
|
|
1006
1055
|
// an author-less message (the local optimistic echo, legacy rows) is
|
|
1007
|
-
// the signed-in user's own and uses their avatar.
|
|
1056
|
+
// the signed-in user's own and uses their avatar. Clicking opens the
|
|
1057
|
+
// AUTHOR's profile (own and teammate alike — the host decides which
|
|
1058
|
+
// surface from the identity's id).
|
|
1008
1059
|
, {
|
|
1009
1060
|
// An AUTHORED message shows that author's avatar — or their initial
|
|
1010
1061
|
// when they have none — NEVER the viewing user's picture (which made
|
|
1011
1062
|
// a teammate's avatar-less message wear the viewer's own face). Only
|
|
1012
1063
|
// an author-less message (the local optimistic echo, legacy rows) is
|
|
1013
|
-
// the signed-in user's own and uses their avatar.
|
|
1014
|
-
|
|
1064
|
+
// the signed-in user's own and uses their avatar. Clicking opens the
|
|
1065
|
+
// AUTHOR's profile (own and teammate alike — the host decides which
|
|
1066
|
+
// surface from the identity's id).
|
|
1067
|
+
userAvatar: authorIdentity.avatar, name: msg.author?.name ?? undefined, size: 36, onClick: openAuthorProfile })), _jsxs("div", { style: { flex: 1, minWidth: 0, marginTop: 1 }, children: [isAutomatic ? null : (_jsxs("div", { style: {
|
|
1015
1068
|
display: 'flex',
|
|
1016
1069
|
alignItems: 'baseline',
|
|
1017
1070
|
gap: 8,
|
|
1018
1071
|
lineHeight: 1.3,
|
|
1019
1072
|
marginBottom: 1,
|
|
1020
|
-
}, children: [_jsx("span", { style: { fontWeight: 600 }, children: msg.author?.name ?? t('ide.chat.you', undefined, { defaultValue: 'You' }) }), isTeamNote && (_jsx("span", { title: t('ide.chat.teamOnly.badge', { agentName: agentName ?? 'the assistant' }, {
|
|
1073
|
+
}, children: [openAuthorProfile ? (_jsx(AuthorNameButton, { onClick: openAuthorProfile, children: msg.author?.name ?? t('ide.chat.you', undefined, { defaultValue: 'You' }) })) : (_jsx("span", { style: { fontWeight: 600 }, children: msg.author?.name ?? t('ide.chat.you', undefined, { defaultValue: 'You' }) })), isTeamNote && (_jsx("span", { title: t('ide.chat.teamOnly.badge', { agentName: agentName ?? 'the assistant' }, {
|
|
1021
1074
|
defaultValue: 'Team only — visible to your team; {{agentName}} will ignore it',
|
|
1022
1075
|
}), "aria-label": t('ide.chat.teamOnly.badge', { agentName: agentName ?? 'the assistant' }, {
|
|
1023
1076
|
defaultValue: 'Team only — visible to your team; {{agentName}} will ignore it',
|
|
@@ -1241,15 +1294,6 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
1241
1294
|
// visible; 420px bounds it on small tablets.
|
|
1242
1295
|
const popupMaxHeight = isNarrow ? 'min(50dvh, 420px)' : '70vh';
|
|
1243
1296
|
const http = useHttpClient();
|
|
1244
|
-
// Bind the host's profile-click callback to the SIGNED-IN user's identity once.
|
|
1245
|
-
// Hosts open the *own*-profile surface from this, so it is only ever attached to
|
|
1246
|
-
// the signed-in user's OWN messages (see the per-message gate at the MessageItem
|
|
1247
|
-
// call site — a teammate's avatar must never open the viewer's profile). Stable
|
|
1248
|
-
// so MessageItem's memo isn't broken; `undefined` when the host opts out, which
|
|
1249
|
-
// keeps every avatar non-interactive.
|
|
1250
|
-
const onUserAvatarClick = useMemo(() => onProfileClick
|
|
1251
|
-
? () => onProfileClick({ avatar: userAvatar })
|
|
1252
|
-
: undefined, [onProfileClick, userAvatar]);
|
|
1253
1297
|
// If there's already a conversation (conversationId in the URL), always load
|
|
1254
1298
|
// history — even when initialMessage is set. This prevents a refresh from
|
|
1255
1299
|
// re-sending the initial prompt instead of restoring the existing conversation.
|
|
@@ -1458,6 +1502,13 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
1458
1502
|
const sendMessageRef = useRef(() => { });
|
|
1459
1503
|
useEffect(() => {
|
|
1460
1504
|
if (autoFixCountdown && autoFixCountdown.secondsLeft === 0 && !autoFixCountdown.paused) {
|
|
1505
|
+
// Defense-in-depth: arming is sender-only (a viewer never owns a stream),
|
|
1506
|
+
// but an autonomous agent-turn dispatch must never fire from a read-only
|
|
1507
|
+
// client regardless of how the countdown came to exist.
|
|
1508
|
+
if (canEdit === false) {
|
|
1509
|
+
setAutoFixCountdown(null);
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1461
1512
|
const msg = `Fix these issues:\n\n${autoFixCountdown.output}`;
|
|
1462
1513
|
setAutoFixCountdown(null);
|
|
1463
1514
|
// Auto-sent on the user's behalf — flag it so the chat renders it in the
|
|
@@ -1465,7 +1516,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
1465
1516
|
// user typed it (C2).
|
|
1466
1517
|
sendMessageRef.current(msg, undefined, { automatic: true });
|
|
1467
1518
|
}
|
|
1468
|
-
}, [autoFixCountdown]);
|
|
1519
|
+
}, [autoFixCountdown, canEdit]);
|
|
1469
1520
|
// Auto-pause countdown when user starts typing
|
|
1470
1521
|
const handleAutoFixPauseOnInput = useCallback(() => {
|
|
1471
1522
|
setAutoFixCountdown((prev) => (prev && !prev.paused ? { ...prev, paused: true } : prev));
|
|
@@ -1483,6 +1534,10 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
1483
1534
|
// overwrite it), and `…PersistedRef` tracks the value we believe is on the
|
|
1484
1535
|
// server so we only PATCH genuine changes (never the value we just hydrated).
|
|
1485
1536
|
const [autoCommitLoaded, setAutoCommitLoaded] = useState(false);
|
|
1537
|
+
// Live canEdit for async callbacks (the settings hydrate) that must not
|
|
1538
|
+
// capture a stale role from mount time.
|
|
1539
|
+
const canEditRef = useRef(canEdit);
|
|
1540
|
+
canEditRef.current = canEdit;
|
|
1486
1541
|
const autoCommitPersistedRef = useRef(0);
|
|
1487
1542
|
const autoCommitPatchTimerRef = useRef(null);
|
|
1488
1543
|
// Set after debouncedFetchPendingFiles is defined below (it depends on state
|
|
@@ -2701,11 +2756,15 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
2701
2756
|
// Restore the persisted auto-commit cadence in the paused state (it
|
|
2702
2757
|
// re-arms on the next file change). Auto-commit is ON by default: a
|
|
2703
2758
|
// project that never set `autoCommitSeconds` resolves to the default
|
|
2704
|
-
// cadence; an explicit 0 (the user turned it off) stays off.
|
|
2759
|
+
// cadence; an explicit 0 (the user turned it off) stays off. Never for
|
|
2760
|
+
// a read-only VIEWER: /commit is an editor action, so arming the
|
|
2761
|
+
// countdown on a viewer's client only produces a doomed dispatch (and
|
|
2762
|
+
// the denial card it minted) when it lapses.
|
|
2705
2763
|
const savedAutoCommit = resolveAutoCommitSeconds(s?.autoCommitSeconds);
|
|
2706
2764
|
autoCommitPersistedRef.current = savedAutoCommit;
|
|
2707
|
-
if (savedAutoCommit > 0)
|
|
2765
|
+
if (savedAutoCommit > 0 && canEditRef.current !== false) {
|
|
2708
2766
|
dispatchAutoCommit({ type: 'hydrate', seconds: savedAutoCommit });
|
|
2767
|
+
}
|
|
2709
2768
|
setAutoCommitLoaded(true);
|
|
2710
2769
|
})
|
|
2711
2770
|
.catch(() => {
|
|
@@ -2788,6 +2847,10 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
2788
2847
|
useEffect(() => {
|
|
2789
2848
|
if (!autoCommitLoaded)
|
|
2790
2849
|
return;
|
|
2850
|
+
// A viewer never mirrors auto-commit state back to the project — the PATCH
|
|
2851
|
+
// is editor-gated server-side and their reducer stays disarmed anyway.
|
|
2852
|
+
if (canEdit === false)
|
|
2853
|
+
return;
|
|
2791
2854
|
const seconds = autoCommit.intervalSeconds;
|
|
2792
2855
|
if (autoCommitPersistedRef.current === seconds)
|
|
2793
2856
|
return;
|
|
@@ -2806,7 +2869,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
2806
2869
|
if (autoCommitPatchTimerRef.current)
|
|
2807
2870
|
clearTimeout(autoCommitPatchTimerRef.current);
|
|
2808
2871
|
};
|
|
2809
|
-
}, [autoCommit.intervalSeconds, autoCommitLoaded, http, projectId]);
|
|
2872
|
+
}, [autoCommit.intervalSeconds, autoCommitLoaded, http, projectId, canEdit]);
|
|
2810
2873
|
// ── Removed-model recovery ──────────────────────────────────────────────────
|
|
2811
2874
|
// If the saved chatModel is no longer in the catalog (a provider retired it,
|
|
2812
2875
|
// or we pruned the entry), notify once and fall back to the free-tier model.
|
|
@@ -3028,13 +3091,18 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
3028
3091
|
useEffect(() => {
|
|
3029
3092
|
if (initialMessage && !hasConversation && sentInitialRef.current !== initialMessage) {
|
|
3030
3093
|
sentInitialRef.current = initialMessage;
|
|
3031
|
-
//
|
|
3032
|
-
//
|
|
3094
|
+
// A read-only VIEWER never auto-sends the initial prompt: the host sources
|
|
3095
|
+
// it from PROJECT-keyed localStorage, which survives a role demotion and a
|
|
3096
|
+
// shared browser — a stale entry would auto-POST an agent turn the server
|
|
3097
|
+
// 403s, surfacing a spurious red error on mount. Consume the ref (above)
|
|
3098
|
+
// so it is never retried either.
|
|
3099
|
+
if (canEdit === false)
|
|
3100
|
+
return;
|
|
3033
3101
|
const sideChannel = matchesSideChannelCommand(extraCommands?.length ? [...COMMANDS, ...extraCommands] : COMMANDS, initialMessage);
|
|
3034
3102
|
sendMessage(initialMessage, undefined, sideChannel ? { suppressUserMessage: true } : undefined);
|
|
3035
3103
|
onInitialMessageSent?.();
|
|
3036
3104
|
}
|
|
3037
|
-
}, [initialMessage, hasConversation, sendMessage, onInitialMessageSent, extraCommands]);
|
|
3105
|
+
}, [initialMessage, hasConversation, sendMessage, onInitialMessageSent, extraCommands, canEdit]);
|
|
3038
3106
|
// ── Auto-send pending message (e.g. "Fix with AI", preview errors) ──────
|
|
3039
3107
|
// Defers sending while the AI is streaming to avoid queueing up auto-fix
|
|
3040
3108
|
// messages during active work. Messages are sent once streaming ends.
|
|
@@ -4151,11 +4219,17 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
4151
4219
|
// the exact render the countdown hits zero): never commit mid-turn — the
|
|
4152
4220
|
// effect re-runs when the hold clears and fires then.
|
|
4153
4221
|
useEffect(() => {
|
|
4154
|
-
|
|
4222
|
+
// A read-only VIEWER never fires auto-commit: /commit is an editor action,
|
|
4223
|
+
// and the client-side dispatch here surfaced the "view-only access, so this
|
|
4224
|
+
// command is unavailable" denial card out of nowhere on viewers whenever a
|
|
4225
|
+
// synced countdown lapsed (observed 2026-08-31, right after a watched turn
|
|
4226
|
+
// settled and released the hold). The hydrate below is also viewer-gated,
|
|
4227
|
+
// so this is the belt for a mid-session demotion.
|
|
4228
|
+
if (canEdit === false || !isAutoCommitDue(autoCommit) || autoCommitHeld)
|
|
4155
4229
|
return;
|
|
4156
4230
|
void executeCommand('commit');
|
|
4157
4231
|
dispatchAutoCommit({ type: 'fired' });
|
|
4158
|
-
}, [autoCommit, autoCommitHeld, executeCommand]);
|
|
4232
|
+
}, [autoCommit, autoCommitHeld, executeCommand, canEdit]);
|
|
4159
4233
|
// ── Submit ─────────────────────────────────────────────────────────────────
|
|
4160
4234
|
const handleSubmit = useCallback(async () => {
|
|
4161
4235
|
// Stop voice recognition on submit
|
|
@@ -4537,7 +4611,11 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
4537
4611
|
setInputAndCursorEnd('/teamsay ');
|
|
4538
4612
|
else
|
|
4539
4613
|
setInputValue('');
|
|
4540
|
-
|
|
4614
|
+
// sideChannel: a team note is a human-to-human message, not a turn — it
|
|
4615
|
+
// goes out immediately even while a turn streams (it used to queue
|
|
4616
|
+
// silently behind the sender's own active turn, and a viewer's note
|
|
4617
|
+
// used to tear down their remote-turn tracking).
|
|
4618
|
+
sendMessage(trimmed, undefined, { suppressUserMessage: true, sideChannel: true });
|
|
4541
4619
|
return;
|
|
4542
4620
|
}
|
|
4543
4621
|
// Rewrite /explain with attachments into a proper prompt (attachments processed below)
|
|
@@ -5550,14 +5628,11 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
5550
5628
|
}, onRevert: canEdit === false ? undefined : handleRevertCommit }, msg.id));
|
|
5551
5629
|
}
|
|
5552
5630
|
return (_jsx(MessageItem, { msg: msg, sendMessage: sendMessage, handleAskUserResponse: handleAskUserResponse, isLoading: isLoading, streamingStatus: streamingStatus, onNavigatePreview: onNavigatePreview, undoneTcIds: undoneTcIds, handleUndoToggle: handleUndoToggle, onFileOpen: onFileOpen, onFileDoubleClick: onFileDoubleClick, onFileDiff: onFileDiff, handleFileRevert: handleFileRevert, setInputAndCursorEnd: setInputAndCursorEnd, setModelPicker: setModelPicker, chatMode: liveModelMode, userAvatar: userAvatar,
|
|
5553
|
-
// Avatar clicks open the
|
|
5554
|
-
//
|
|
5555
|
-
//
|
|
5556
|
-
//
|
|
5557
|
-
|
|
5558
|
-
onAvatarClick: !msg.author?.id || (currentUserId != null && msg.author.id === currentUserId)
|
|
5559
|
-
? onUserAvatarClick
|
|
5560
|
-
: undefined, discovery: discovery, buildUpgradeCta: buildUpgradeCta, agentName: agentName, canEdit: canEdit }, msg.id));
|
|
5631
|
+
// Avatar/name clicks open the clicked AUTHOR's profile —
|
|
5632
|
+
// MessageItem builds the identity from the message's own
|
|
5633
|
+
// author (teammates included), so the host shows their
|
|
5634
|
+
// view-only profile and the viewer's editable own.
|
|
5635
|
+
onProfileClick: onProfileClick, currentUserId: currentUserId, discovery: discovery, buildUpgradeCta: buildUpgradeCta, agentName: agentName, canEdit: canEdit }, msg.id));
|
|
5561
5636
|
} }, item.kind === 'message' ? item.msg.id : item.card.id))), error &&
|
|
5562
5637
|
!isStaleAnonymousLimit &&
|
|
5563
5638
|
(errorMeta?.limitType ? (_jsx(ResourceLimitBanner, { message: error, action: buildUpgradeCta?.({ requiresSignup: errorMeta.requiresSignup }) })) : isViewerAccessDenied ? (
|
|
@@ -5578,7 +5653,14 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
5578
5653
|
// continuation) shows the same live activity indicator as an own send —
|
|
5579
5654
|
// watchers see Synthase working, not a frozen transcript.
|
|
5580
5655
|
const showActivity = isLoading || awaitingSandboxBoot || isRemoteStreaming;
|
|
5581
|
-
|
|
5656
|
+
// A remote turn streams real messages into the same store, so it gets the
|
|
5657
|
+
// SAME activity treatment as an own send. Only the no-turn-at-all case
|
|
5658
|
+
// (awaitingSandboxBoot alone) is a sandbox-boot wait — labeling every
|
|
5659
|
+
// !isLoading render with the boot copy showed viewers "Waiting for the
|
|
5660
|
+
// development environment to finish starting…" over a running sandbox
|
|
5661
|
+
// for the whole remote turn (observed 2026-08-31).
|
|
5662
|
+
const streamingLike = isLoading || isRemoteStreaming;
|
|
5663
|
+
const streamingMsg = streamingLike
|
|
5582
5664
|
? [...visibleMessages].reverse().find((m) => m.isStreaming)
|
|
5583
5665
|
: undefined;
|
|
5584
5666
|
// Turn start = the last genuine user message, so the elapsed timer counts up across
|
|
@@ -5595,7 +5677,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
5595
5677
|
// matches the elapsed timer's span: it climbs monotonically and only plateaus during
|
|
5596
5678
|
// tool-execution gaps, rather than vanishing/restarting at each new assistant message.
|
|
5597
5679
|
const turnTokens = estimateTurnTokens(messages);
|
|
5598
|
-
const label =
|
|
5680
|
+
const label = streamingLike
|
|
5599
5681
|
? (streamingStatus ?? (streamingMsg ? streamingActivityLabel(streamingMsg) : undefined))
|
|
5600
5682
|
: t('ide.chat.awaitingSandbox', undefined, {
|
|
5601
5683
|
defaultValue: 'Waiting for the development environment to finish starting…',
|
|
@@ -5605,7 +5687,7 @@ function ChatInner({ projectId, endpoint, initialMessage, onInitialMessageSent,
|
|
|
5605
5687
|
opacity: showActivity ? 1 : 0,
|
|
5606
5688
|
pointerEvents: showActivity ? 'auto' : 'none',
|
|
5607
5689
|
transition: 'opacity 0.18s ease-out',
|
|
5608
|
-
}, children: showActivity && (_jsx(StreamingIndicator, { label: label, tokens:
|
|
5690
|
+
}, children: showActivity && (_jsx(StreamingIndicator, { label: label, tokens: streamingLike ? turnTokens : undefined, startedAt: streamingLike ? turnStartedAt : undefined })) }));
|
|
5609
5691
|
})(), _jsx("div", { ref: messagesEndRef })] }), autoFixCountdown && (_jsxs("div", { className: cm.cn(cm.shrink0, cm.borderT), style: {
|
|
5610
5692
|
display: 'flex',
|
|
5611
5693
|
alignItems: 'center',
|