@volter-ai-dev/supercode-ui 0.1.38 → 0.1.40

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 CHANGED
@@ -135,7 +135,7 @@ Hosts do not need to fork the messenger to connect their own object model:
135
135
  ```
136
136
 
137
137
  `navigation` is an event, not duplicated router state: change its `id` to open a conversation or
138
- prefill a new one, while `onViewChange` observes user navigation. Context candidates use the exact
138
+ prefill either an existing or new one, while `onViewChange` observes user navigation. Context candidates use the exact
139
139
  bounded `TranscriptAttachment` envelope sent to the harness; a custom candidate component changes
140
140
  presentation without inventing a second context protocol. `beforeSessions` and `afterSessions`
141
141
  place host-owned rows inside the same searchable scroller. `confirmIntent` runs before resume,
package/components.mjs CHANGED
@@ -2301,6 +2301,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2301
2301
  const rowScroller = useRef5(null);
2302
2302
  const rows = filterSessions(state.sessions, query);
2303
2303
  const Row = components.SessionRow ?? SessionRow;
2304
+ const ListHeader = slots.listHeader;
2304
2305
  const BeforeSessions = slots.beforeSessions;
2305
2306
  const AfterSessions = slots.afterSessions;
2306
2307
  useLayoutEffect3(() => {
@@ -2327,7 +2328,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2327
2328
  }
2328
2329
  };
2329
2330
  return /* @__PURE__ */ jsxs7("section", { className: "scui-list", ref: root, children: [
2330
- /* @__PURE__ */ jsxs7("header", { className: "scui-head", children: [
2331
+ ListHeader ? /* @__PURE__ */ jsx8(ListHeader, { state, adapter, value: { query, rows, onNew } }) : /* @__PURE__ */ jsxs7("header", { className: "scui-head", children: [
2331
2332
  /* @__PURE__ */ jsxs7("span", { className: "scui-head-copy", children: [
2332
2333
  /* @__PURE__ */ jsx8("strong", { children: labels.chats }),
2333
2334
  /* @__PURE__ */ jsxs7("small", { children: [
@@ -2627,7 +2628,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
2627
2628
  onClose ? /* @__PURE__ */ jsx10("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx10(UiIcon, { name: "close", size: 18 }) }) : null
2628
2629
  ] });
2629
2630
  }
2630
- function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates }) {
2631
+ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
2631
2632
  const Header = slots.header;
2632
2633
  const HeaderActions = slots.headerActions;
2633
2634
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
@@ -2637,6 +2638,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2637
2638
  const [settings, setSettings] = useState6(null);
2638
2639
  const restoreSequence = useRef7(0);
2639
2640
  const actionSequence = useRef7(0);
2641
+ const lastNavigation = useRef7(null);
2640
2642
  const acknowledged = useRef7(/* @__PURE__ */ new Set());
2641
2643
  const setPending = (update) => setPendingState((current) => {
2642
2644
  const next = typeof update === "function" ? update(current) : update;
@@ -2649,6 +2651,17 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2649
2651
  setPendingAction(null);
2650
2652
  setRestoreDraft(null);
2651
2653
  }, [memoryKey]);
2654
+ useEffect8(() => {
2655
+ if (!navigation || navigation.id === lastNavigation.current) return;
2656
+ lastNavigation.current = navigation.id;
2657
+ if (navigation.draft === void 0 && !navigation.context?.length && !navigation.images?.length) return;
2658
+ setRestoreDraft({
2659
+ id: `host:${String(navigation.id)}`,
2660
+ text: navigation.draft ?? "",
2661
+ context: navigation.context,
2662
+ images: navigation.images
2663
+ });
2664
+ }, [navigation]);
2652
2665
  useEffect8(() => {
2653
2666
  if (!pending) return;
2654
2667
  if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
@@ -2973,6 +2986,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2973
2986
  const [view, setViewState] = useState6(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2974
2987
  const [opening, setOpening] = useState6(null);
2975
2988
  const [newNavigation, setNewNavigation] = useState6(null);
2989
+ const [chatNavigation, setChatNavigation] = useState6(null);
2976
2990
  const [listFocus, setListFocus] = useState6(null);
2977
2991
  const lastNavigation = useRef7(null);
2978
2992
  const setView = (next) => {
@@ -2985,17 +2999,21 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2985
2999
  lastNavigation.current = navigation.id;
2986
3000
  if (navigation.view === "list") {
2987
3001
  setOpening(null);
3002
+ setChatNavigation(null);
2988
3003
  setView("list");
2989
3004
  return;
2990
3005
  }
2991
3006
  if (navigation.view === "new") {
2992
3007
  setListFocus("@new");
3008
+ setChatNavigation(null);
2993
3009
  setNewNavigation(navigation);
2994
3010
  setView("new");
2995
3011
  return;
2996
3012
  }
2997
3013
  if (navigation.view === "chat" && typeof navigation.sessionKey === "string" && navigation.sessionKey) {
2998
3014
  setListFocus(navigation.sessionKey);
3015
+ setNewNavigation(null);
3016
+ setChatNavigation(navigation);
2999
3017
  if (state.attached?.key === navigation.sessionKey) {
3000
3018
  setOpening(null);
3001
3019
  setView("chat");
@@ -3017,6 +3035,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
3017
3035
  }, [opening, state.attachError?.key, state.attached?.key, state.error, state.operation]);
3018
3036
  const open = (row) => {
3019
3037
  setListFocus(row.key);
3038
+ setChatNavigation(null);
3020
3039
  setOpening(row);
3021
3040
  adapter.onIntent({ action: "attach", key: row.key });
3022
3041
  };
@@ -3026,6 +3045,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
3026
3045
  return /* @__PURE__ */ jsxs9("main", { className: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
3027
3046
  view === "list" ? /* @__PURE__ */ jsx10(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
3028
3047
  setListFocus("@new");
3048
+ setChatNavigation(null);
3029
3049
  setNewNavigation(null);
3030
3050
  setView("new");
3031
3051
  }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
@@ -3035,9 +3055,10 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
3035
3055
  setView("list");
3036
3056
  }, onNew: () => {
3037
3057
  setListFocus("@new");
3058
+ setChatNavigation(null);
3038
3059
  setNewNavigation(null);
3039
3060
  setView("new");
3040
- }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates }) : null,
3061
+ }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
3041
3062
  opening ? /* @__PURE__ */ jsxs9("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
3042
3063
  /* @__PURE__ */ jsx10(HarnessLogo, { id: opening.harness, size: 34 }),
3043
3064
  /* @__PURE__ */ jsxs9("span", { children: [
package/embed.mjs CHANGED
@@ -2221,6 +2221,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2221
2221
  const rowScroller = useRef5(null);
2222
2222
  const rows = filterSessions(state.sessions, query);
2223
2223
  const Row = components.SessionRow ?? SessionRow;
2224
+ const ListHeader = slots.listHeader;
2224
2225
  const BeforeSessions = slots.beforeSessions;
2225
2226
  const AfterSessions = slots.afterSessions;
2226
2227
  useLayoutEffect3(() => {
@@ -2247,7 +2248,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2247
2248
  }
2248
2249
  };
2249
2250
  return /* @__PURE__ */ jsxs6("section", { className: "scui-list", ref: root, children: [
2250
- /* @__PURE__ */ jsxs6("header", { className: "scui-head", children: [
2251
+ ListHeader ? /* @__PURE__ */ jsx7(ListHeader, { state, adapter, value: { query, rows, onNew } }) : /* @__PURE__ */ jsxs6("header", { className: "scui-head", children: [
2251
2252
  /* @__PURE__ */ jsxs6("span", { className: "scui-head-copy", children: [
2252
2253
  /* @__PURE__ */ jsx7("strong", { children: labels.chats }),
2253
2254
  /* @__PURE__ */ jsxs6("small", { children: [
@@ -2547,7 +2548,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
2547
2548
  onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2548
2549
  ] });
2549
2550
  }
2550
- function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates }) {
2551
+ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
2551
2552
  const Header = slots.header;
2552
2553
  const HeaderActions = slots.headerActions;
2553
2554
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
@@ -2557,6 +2558,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2557
2558
  const [settings, setSettings] = useState6(null);
2558
2559
  const restoreSequence = useRef7(0);
2559
2560
  const actionSequence = useRef7(0);
2561
+ const lastNavigation = useRef7(null);
2560
2562
  const acknowledged = useRef7(/* @__PURE__ */ new Set());
2561
2563
  const setPending = (update) => setPendingState((current) => {
2562
2564
  const next = typeof update === "function" ? update(current) : update;
@@ -2569,6 +2571,17 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2569
2571
  setPendingAction(null);
2570
2572
  setRestoreDraft(null);
2571
2573
  }, [memoryKey]);
2574
+ useEffect8(() => {
2575
+ if (!navigation || navigation.id === lastNavigation.current) return;
2576
+ lastNavigation.current = navigation.id;
2577
+ if (navigation.draft === void 0 && !navigation.context?.length && !navigation.images?.length) return;
2578
+ setRestoreDraft({
2579
+ id: `host:${String(navigation.id)}`,
2580
+ text: navigation.draft ?? "",
2581
+ context: navigation.context,
2582
+ images: navigation.images
2583
+ });
2584
+ }, [navigation]);
2572
2585
  useEffect8(() => {
2573
2586
  if (!pending) return;
2574
2587
  if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
@@ -2893,6 +2906,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2893
2906
  const [view, setViewState] = useState6(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2894
2907
  const [opening, setOpening] = useState6(null);
2895
2908
  const [newNavigation, setNewNavigation] = useState6(null);
2909
+ const [chatNavigation, setChatNavigation] = useState6(null);
2896
2910
  const [listFocus, setListFocus] = useState6(null);
2897
2911
  const lastNavigation = useRef7(null);
2898
2912
  const setView = (next) => {
@@ -2905,17 +2919,21 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2905
2919
  lastNavigation.current = navigation.id;
2906
2920
  if (navigation.view === "list") {
2907
2921
  setOpening(null);
2922
+ setChatNavigation(null);
2908
2923
  setView("list");
2909
2924
  return;
2910
2925
  }
2911
2926
  if (navigation.view === "new") {
2912
2927
  setListFocus("@new");
2928
+ setChatNavigation(null);
2913
2929
  setNewNavigation(navigation);
2914
2930
  setView("new");
2915
2931
  return;
2916
2932
  }
2917
2933
  if (navigation.view === "chat" && typeof navigation.sessionKey === "string" && navigation.sessionKey) {
2918
2934
  setListFocus(navigation.sessionKey);
2935
+ setNewNavigation(null);
2936
+ setChatNavigation(navigation);
2919
2937
  if (state.attached?.key === navigation.sessionKey) {
2920
2938
  setOpening(null);
2921
2939
  setView("chat");
@@ -2937,6 +2955,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2937
2955
  }, [opening, state.attachError?.key, state.attached?.key, state.error, state.operation]);
2938
2956
  const open = (row) => {
2939
2957
  setListFocus(row.key);
2958
+ setChatNavigation(null);
2940
2959
  setOpening(row);
2941
2960
  adapter.onIntent({ action: "attach", key: row.key });
2942
2961
  };
@@ -2946,6 +2965,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2946
2965
  return /* @__PURE__ */ jsxs8("main", { className: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
2947
2966
  view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
2948
2967
  setListFocus("@new");
2968
+ setChatNavigation(null);
2949
2969
  setNewNavigation(null);
2950
2970
  setView("new");
2951
2971
  }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
@@ -2955,9 +2975,10 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2955
2975
  setView("list");
2956
2976
  }, onNew: () => {
2957
2977
  setListFocus("@new");
2978
+ setChatNavigation(null);
2958
2979
  setNewNavigation(null);
2959
2980
  setView("new");
2960
- }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates }) : null,
2981
+ }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
2961
2982
  opening ? /* @__PURE__ */ jsxs8("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
2962
2983
  /* @__PURE__ */ jsx9(HarnessLogo, { id: opening.harness, size: 34 }),
2963
2984
  /* @__PURE__ */ jsxs8("span", { children: [
package/index.d.ts CHANGED
@@ -455,6 +455,8 @@ export interface AgentActivityLauncherProps {
455
455
 
456
456
  export interface MessengerSlots {
457
457
  header?: ComponentType<ComponentOverrideProps>;
458
+ /** Replace the conversation-list header. A component returning `null` delegates all list chrome to its host. */
459
+ listHeader?: ComponentType<ComponentOverrideProps<{ query: string; rows: SessionRowModel[]; onNew?(): void }>>;
458
460
  /** Add a compact consumer control to every default messenger header. `value` is the current view. */
459
461
  headerActions?: ComponentType<ComponentOverrideProps<'list' | 'chat' | 'new'>>;
460
462
  /** Host-owned rows rendered inside the searchable conversation scroller. */
@@ -468,7 +470,7 @@ export interface MessengerSlots {
468
470
 
469
471
  export type MessengerNavigation =
470
472
  | { id: string | number; view: 'list' }
471
- | { id: string | number; view: 'chat'; sessionKey: string }
473
+ | { id: string | number; view: 'chat'; sessionKey: string; draft?: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> }
472
474
  | { id: string | number; view: 'new'; harness?: HarnessId; draft?: string; context?: TranscriptContext[]; images?: Array<TranscriptImage & { url: string }> };
473
475
 
474
476
  export interface MessengerProps {
package/messenger.mjs CHANGED
@@ -2218,6 +2218,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2218
2218
  const rowScroller = useRef5(null);
2219
2219
  const rows = filterSessions(state.sessions, query);
2220
2220
  const Row = components.SessionRow ?? SessionRow;
2221
+ const ListHeader = slots.listHeader;
2221
2222
  const BeforeSessions = slots.beforeSessions;
2222
2223
  const AfterSessions = slots.afterSessions;
2223
2224
  useLayoutEffect3(() => {
@@ -2244,7 +2245,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2244
2245
  }
2245
2246
  };
2246
2247
  return /* @__PURE__ */ jsxs6("section", { className: "scui-list", ref: root, children: [
2247
- /* @__PURE__ */ jsxs6("header", { className: "scui-head", children: [
2248
+ ListHeader ? /* @__PURE__ */ jsx7(ListHeader, { state, adapter, value: { query, rows, onNew } }) : /* @__PURE__ */ jsxs6("header", { className: "scui-head", children: [
2248
2249
  /* @__PURE__ */ jsxs6("span", { className: "scui-head-copy", children: [
2249
2250
  /* @__PURE__ */ jsx7("strong", { children: labels.chats }),
2250
2251
  /* @__PURE__ */ jsxs6("small", { children: [
@@ -2544,7 +2545,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
2544
2545
  onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2545
2546
  ] });
2546
2547
  }
2547
- function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates }) {
2548
+ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
2548
2549
  const Header = slots.header;
2549
2550
  const HeaderActions = slots.headerActions;
2550
2551
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
@@ -2554,6 +2555,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2554
2555
  const [settings, setSettings] = useState6(null);
2555
2556
  const restoreSequence = useRef7(0);
2556
2557
  const actionSequence = useRef7(0);
2558
+ const lastNavigation = useRef7(null);
2557
2559
  const acknowledged = useRef7(/* @__PURE__ */ new Set());
2558
2560
  const setPending = (update) => setPendingState((current) => {
2559
2561
  const next = typeof update === "function" ? update(current) : update;
@@ -2566,6 +2568,17 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2566
2568
  setPendingAction(null);
2567
2569
  setRestoreDraft(null);
2568
2570
  }, [memoryKey]);
2571
+ useEffect8(() => {
2572
+ if (!navigation || navigation.id === lastNavigation.current) return;
2573
+ lastNavigation.current = navigation.id;
2574
+ if (navigation.draft === void 0 && !navigation.context?.length && !navigation.images?.length) return;
2575
+ setRestoreDraft({
2576
+ id: `host:${String(navigation.id)}`,
2577
+ text: navigation.draft ?? "",
2578
+ context: navigation.context,
2579
+ images: navigation.images
2580
+ });
2581
+ }, [navigation]);
2569
2582
  useEffect8(() => {
2570
2583
  if (!pending) return;
2571
2584
  if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
@@ -2890,6 +2903,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2890
2903
  const [view, setViewState] = useState6(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2891
2904
  const [opening, setOpening] = useState6(null);
2892
2905
  const [newNavigation, setNewNavigation] = useState6(null);
2906
+ const [chatNavigation, setChatNavigation] = useState6(null);
2893
2907
  const [listFocus, setListFocus] = useState6(null);
2894
2908
  const lastNavigation = useRef7(null);
2895
2909
  const setView = (next) => {
@@ -2902,17 +2916,21 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2902
2916
  lastNavigation.current = navigation.id;
2903
2917
  if (navigation.view === "list") {
2904
2918
  setOpening(null);
2919
+ setChatNavigation(null);
2905
2920
  setView("list");
2906
2921
  return;
2907
2922
  }
2908
2923
  if (navigation.view === "new") {
2909
2924
  setListFocus("@new");
2925
+ setChatNavigation(null);
2910
2926
  setNewNavigation(navigation);
2911
2927
  setView("new");
2912
2928
  return;
2913
2929
  }
2914
2930
  if (navigation.view === "chat" && typeof navigation.sessionKey === "string" && navigation.sessionKey) {
2915
2931
  setListFocus(navigation.sessionKey);
2932
+ setNewNavigation(null);
2933
+ setChatNavigation(navigation);
2916
2934
  if (state.attached?.key === navigation.sessionKey) {
2917
2935
  setOpening(null);
2918
2936
  setView("chat");
@@ -2934,6 +2952,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2934
2952
  }, [opening, state.attachError?.key, state.attached?.key, state.error, state.operation]);
2935
2953
  const open = (row) => {
2936
2954
  setListFocus(row.key);
2955
+ setChatNavigation(null);
2937
2956
  setOpening(row);
2938
2957
  adapter.onIntent({ action: "attach", key: row.key });
2939
2958
  };
@@ -2943,6 +2962,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2943
2962
  return /* @__PURE__ */ jsxs8("main", { className: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
2944
2963
  view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
2945
2964
  setListFocus("@new");
2965
+ setChatNavigation(null);
2946
2966
  setNewNavigation(null);
2947
2967
  setView("new");
2948
2968
  }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
@@ -2952,9 +2972,10 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2952
2972
  setView("list");
2953
2973
  }, onNew: () => {
2954
2974
  setListFocus("@new");
2975
+ setChatNavigation(null);
2955
2976
  setNewNavigation(null);
2956
2977
  setView("new");
2957
- }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates }) : null,
2978
+ }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
2958
2979
  opening ? /* @__PURE__ */ jsxs8("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
2959
2980
  /* @__PURE__ */ jsx9(HarnessLogo, { id: opening.harness, size: 34 }),
2960
2981
  /* @__PURE__ */ jsxs8("span", { children: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@volter-ai-dev/supercode-ui",
3
- "version": "0.1.38",
3
+ "version": "0.1.40",
4
4
  "type": "module",
5
5
  "description": "Composable default UI kit for Supercode-powered coding-agent experiences",
6
6
  "exports": {
@@ -2301,6 +2301,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2301
2301
  const rowScroller = useRef5(null);
2302
2302
  const rows = filterSessions(state.sessions, query);
2303
2303
  const Row = components.SessionRow ?? SessionRow;
2304
+ const ListHeader = slots.listHeader;
2304
2305
  const BeforeSessions = slots.beforeSessions;
2305
2306
  const AfterSessions = slots.afterSessions;
2306
2307
  useLayoutEffect3(() => {
@@ -2327,7 +2328,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2327
2328
  }
2328
2329
  };
2329
2330
  return /* @__PURE__ */ jsxs7("section", { className: "scui-list", ref: root, children: [
2330
- /* @__PURE__ */ jsxs7("header", { className: "scui-head", children: [
2331
+ ListHeader ? /* @__PURE__ */ jsx8(ListHeader, { state, adapter, value: { query, rows, onNew } }) : /* @__PURE__ */ jsxs7("header", { className: "scui-head", children: [
2331
2332
  /* @__PURE__ */ jsxs7("span", { className: "scui-head-copy", children: [
2332
2333
  /* @__PURE__ */ jsx8("strong", { children: labels.chats }),
2333
2334
  /* @__PURE__ */ jsxs7("small", { children: [
@@ -2627,7 +2628,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
2627
2628
  onClose ? /* @__PURE__ */ jsx10("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx10(UiIcon, { name: "close", size: 18 }) }) : null
2628
2629
  ] });
2629
2630
  }
2630
- function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates }) {
2631
+ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
2631
2632
  const Header = slots.header;
2632
2633
  const HeaderActions = slots.headerActions;
2633
2634
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
@@ -2637,6 +2638,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2637
2638
  const [settings, setSettings] = useState6(null);
2638
2639
  const restoreSequence = useRef7(0);
2639
2640
  const actionSequence = useRef7(0);
2641
+ const lastNavigation = useRef7(null);
2640
2642
  const acknowledged = useRef7(/* @__PURE__ */ new Set());
2641
2643
  const setPending = (update) => setPendingState((current) => {
2642
2644
  const next = typeof update === "function" ? update(current) : update;
@@ -2649,6 +2651,17 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2649
2651
  setPendingAction(null);
2650
2652
  setRestoreDraft(null);
2651
2653
  }, [memoryKey]);
2654
+ useEffect8(() => {
2655
+ if (!navigation || navigation.id === lastNavigation.current) return;
2656
+ lastNavigation.current = navigation.id;
2657
+ if (navigation.draft === void 0 && !navigation.context?.length && !navigation.images?.length) return;
2658
+ setRestoreDraft({
2659
+ id: `host:${String(navigation.id)}`,
2660
+ text: navigation.draft ?? "",
2661
+ context: navigation.context,
2662
+ images: navigation.images
2663
+ });
2664
+ }, [navigation]);
2652
2665
  useEffect8(() => {
2653
2666
  if (!pending) return;
2654
2667
  if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
@@ -2973,6 +2986,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2973
2986
  const [view, setViewState] = useState6(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2974
2987
  const [opening, setOpening] = useState6(null);
2975
2988
  const [newNavigation, setNewNavigation] = useState6(null);
2989
+ const [chatNavigation, setChatNavigation] = useState6(null);
2976
2990
  const [listFocus, setListFocus] = useState6(null);
2977
2991
  const lastNavigation = useRef7(null);
2978
2992
  const setView = (next) => {
@@ -2985,17 +2999,21 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2985
2999
  lastNavigation.current = navigation.id;
2986
3000
  if (navigation.view === "list") {
2987
3001
  setOpening(null);
3002
+ setChatNavigation(null);
2988
3003
  setView("list");
2989
3004
  return;
2990
3005
  }
2991
3006
  if (navigation.view === "new") {
2992
3007
  setListFocus("@new");
3008
+ setChatNavigation(null);
2993
3009
  setNewNavigation(navigation);
2994
3010
  setView("new");
2995
3011
  return;
2996
3012
  }
2997
3013
  if (navigation.view === "chat" && typeof navigation.sessionKey === "string" && navigation.sessionKey) {
2998
3014
  setListFocus(navigation.sessionKey);
3015
+ setNewNavigation(null);
3016
+ setChatNavigation(navigation);
2999
3017
  if (state.attached?.key === navigation.sessionKey) {
3000
3018
  setOpening(null);
3001
3019
  setView("chat");
@@ -3017,6 +3035,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
3017
3035
  }, [opening, state.attachError?.key, state.attached?.key, state.error, state.operation]);
3018
3036
  const open = (row) => {
3019
3037
  setListFocus(row.key);
3038
+ setChatNavigation(null);
3020
3039
  setOpening(row);
3021
3040
  adapter.onIntent({ action: "attach", key: row.key });
3022
3041
  };
@@ -3026,6 +3045,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
3026
3045
  return /* @__PURE__ */ jsxs9("main", { className: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
3027
3046
  view === "list" ? /* @__PURE__ */ jsx10(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
3028
3047
  setListFocus("@new");
3048
+ setChatNavigation(null);
3029
3049
  setNewNavigation(null);
3030
3050
  setView("new");
3031
3051
  }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
@@ -3035,9 +3055,10 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
3035
3055
  setView("list");
3036
3056
  }, onNew: () => {
3037
3057
  setListFocus("@new");
3058
+ setChatNavigation(null);
3038
3059
  setNewNavigation(null);
3039
3060
  setView("new");
3040
- }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates }) : null,
3061
+ }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
3041
3062
  opening ? /* @__PURE__ */ jsxs9("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
3042
3063
  /* @__PURE__ */ jsx10(HarnessLogo, { id: opening.harness, size: 34 }),
3043
3064
  /* @__PURE__ */ jsxs9("span", { children: [
@@ -2218,6 +2218,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2218
2218
  const rowScroller = useRef5(null);
2219
2219
  const rows = filterSessions(state.sessions, query);
2220
2220
  const Row = components.SessionRow ?? SessionRow;
2221
+ const ListHeader = slots.listHeader;
2221
2222
  const BeforeSessions = slots.beforeSessions;
2222
2223
  const AfterSessions = slots.afterSessions;
2223
2224
  useLayoutEffect3(() => {
@@ -2244,7 +2245,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
2244
2245
  }
2245
2246
  };
2246
2247
  return /* @__PURE__ */ jsxs6("section", { className: "scui-list", ref: root, children: [
2247
- /* @__PURE__ */ jsxs6("header", { className: "scui-head", children: [
2248
+ ListHeader ? /* @__PURE__ */ jsx7(ListHeader, { state, adapter, value: { query, rows, onNew } }) : /* @__PURE__ */ jsxs6("header", { className: "scui-head", children: [
2248
2249
  /* @__PURE__ */ jsxs6("span", { className: "scui-head-copy", children: [
2249
2250
  /* @__PURE__ */ jsx7("strong", { children: labels.chats }),
2250
2251
  /* @__PURE__ */ jsxs6("small", { children: [
@@ -2544,7 +2545,7 @@ function ChatHeader({ state, adapter, pendingStatus, actionPending, onBack, onNe
2544
2545
  onClose ? /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Close", onClick: onClose, children: /* @__PURE__ */ jsx9(UiIcon, { name: "close", size: 18 }) }) : null
2545
2546
  ] });
2546
2547
  }
2547
- function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates }) {
2548
+ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, labels, contextCandidates, navigation }) {
2548
2549
  const Header = slots.header;
2549
2550
  const HeaderActions = slots.headerActions;
2550
2551
  const memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`;
@@ -2554,6 +2555,7 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2554
2555
  const [settings, setSettings] = useState6(null);
2555
2556
  const restoreSequence = useRef7(0);
2556
2557
  const actionSequence = useRef7(0);
2558
+ const lastNavigation = useRef7(null);
2557
2559
  const acknowledged = useRef7(/* @__PURE__ */ new Set());
2558
2560
  const setPending = (update) => setPendingState((current) => {
2559
2561
  const next = typeof update === "function" ? update(current) : update;
@@ -2566,6 +2568,17 @@ function Chat({ state, adapter, onBack, onNew, onClose, components, slots, label
2566
2568
  setPendingAction(null);
2567
2569
  setRestoreDraft(null);
2568
2570
  }, [memoryKey]);
2571
+ useEffect8(() => {
2572
+ if (!navigation || navigation.id === lastNavigation.current) return;
2573
+ lastNavigation.current = navigation.id;
2574
+ if (navigation.draft === void 0 && !navigation.context?.length && !navigation.images?.length) return;
2575
+ setRestoreDraft({
2576
+ id: `host:${String(navigation.id)}`,
2577
+ text: navigation.draft ?? "",
2578
+ context: navigation.context,
2579
+ images: navigation.images
2580
+ });
2581
+ }, [navigation]);
2569
2582
  useEffect8(() => {
2570
2583
  if (!pending) return;
2571
2584
  if (state.transcript.some((entry) => entry.role === "user" && entry.text.trim() === pending.text.trim() && !pending.baselineIds.includes(entry.id))) {
@@ -2890,6 +2903,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2890
2903
  const [view, setViewState] = useState6(initialView ?? (state.attention.length ? "list" : messengerViewMemory.get(memoryKey) ?? (state.attached || state.transcript.length ? "chat" : "list")));
2891
2904
  const [opening, setOpening] = useState6(null);
2892
2905
  const [newNavigation, setNewNavigation] = useState6(null);
2906
+ const [chatNavigation, setChatNavigation] = useState6(null);
2893
2907
  const [listFocus, setListFocus] = useState6(null);
2894
2908
  const lastNavigation = useRef7(null);
2895
2909
  const setView = (next) => {
@@ -2902,17 +2916,21 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2902
2916
  lastNavigation.current = navigation.id;
2903
2917
  if (navigation.view === "list") {
2904
2918
  setOpening(null);
2919
+ setChatNavigation(null);
2905
2920
  setView("list");
2906
2921
  return;
2907
2922
  }
2908
2923
  if (navigation.view === "new") {
2909
2924
  setListFocus("@new");
2925
+ setChatNavigation(null);
2910
2926
  setNewNavigation(navigation);
2911
2927
  setView("new");
2912
2928
  return;
2913
2929
  }
2914
2930
  if (navigation.view === "chat" && typeof navigation.sessionKey === "string" && navigation.sessionKey) {
2915
2931
  setListFocus(navigation.sessionKey);
2932
+ setNewNavigation(null);
2933
+ setChatNavigation(navigation);
2916
2934
  if (state.attached?.key === navigation.sessionKey) {
2917
2935
  setOpening(null);
2918
2936
  setView("chat");
@@ -2934,6 +2952,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2934
2952
  }, [opening, state.attachError?.key, state.attached?.key, state.error, state.operation]);
2935
2953
  const open = (row) => {
2936
2954
  setListFocus(row.key);
2955
+ setChatNavigation(null);
2937
2956
  setOpening(row);
2938
2957
  adapter.onIntent({ action: "attach", key: row.key });
2939
2958
  };
@@ -2943,6 +2962,7 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2943
2962
  return /* @__PURE__ */ jsxs8("main", { className: `scui-root ${className}`, "data-view": view, "data-mode": state.mode, "aria-label": "Supercode messenger", children: [
2944
2963
  view === "list" ? /* @__PURE__ */ jsx9(SessionList, { state, adapter, focusKey: listFocus, onOpen: open, onNew: () => {
2945
2964
  setListFocus("@new");
2965
+ setChatNavigation(null);
2946
2966
  setNewNavigation(null);
2947
2967
  setView("new");
2948
2968
  }, onClose: adapter.onClose ? close : void 0, components, labels: copy, memoryKey, slots, headerActions: HeaderActions }) : null,
@@ -2952,9 +2972,10 @@ function SupercodeMessenger({ state: stateInput, adapter, class: className = "",
2952
2972
  setView("list");
2953
2973
  }, onNew: () => {
2954
2974
  setListFocus("@new");
2975
+ setChatNavigation(null);
2955
2976
  setNewNavigation(null);
2956
2977
  setView("new");
2957
- }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates }) : null,
2978
+ }, onClose: adapter.onClose ? close : void 0, components, slots, labels: copy, contextCandidates, navigation: chatNavigation?.sessionKey === state.attached?.key ? chatNavigation : null }) : null,
2958
2979
  opening ? /* @__PURE__ */ jsxs8("div", { className: "scui-opening", role: "status", "aria-busy": "true", children: [
2959
2980
  /* @__PURE__ */ jsx9(HarnessLogo, { id: opening.harness, size: 34 }),
2960
2981
  /* @__PURE__ */ jsxs8("span", { children: [
@@ -365,6 +365,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
365
365
  const rowScroller = useRef4(null);
366
366
  const rows = filterSessions(state.sessions, query);
367
367
  const Row = components.SessionRow ?? SessionRow;
368
+ const ListHeader = slots.listHeader;
368
369
  const BeforeSessions = slots.beforeSessions;
369
370
  const AfterSessions = slots.afterSessions;
370
371
  useLayoutEffect2(() => {
@@ -391,7 +392,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
391
392
  }
392
393
  };
393
394
  return /* @__PURE__ */ jsxs5("section", { className: "scui-list", ref: root, children: [
394
- /* @__PURE__ */ jsxs5("header", { className: "scui-head", children: [
395
+ ListHeader ? /* @__PURE__ */ jsx6(ListHeader, { state, adapter, value: { query, rows, onNew } }) : /* @__PURE__ */ jsxs5("header", { className: "scui-head", children: [
395
396
  /* @__PURE__ */ jsxs5("span", { className: "scui-head-copy", children: [
396
397
  /* @__PURE__ */ jsx6("strong", { children: labels.chats }),
397
398
  /* @__PURE__ */ jsxs5("small", { children: [
package/sessions.mjs CHANGED
@@ -365,6 +365,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
365
365
  const rowScroller = useRef4(null);
366
366
  const rows = filterSessions(state.sessions, query);
367
367
  const Row = components.SessionRow ?? SessionRow;
368
+ const ListHeader = slots.listHeader;
368
369
  const BeforeSessions = slots.beforeSessions;
369
370
  const AfterSessions = slots.afterSessions;
370
371
  useLayoutEffect2(() => {
@@ -391,7 +392,7 @@ function SessionList({ state, adapter, onOpen, onNew, onClose, components = {},
391
392
  }
392
393
  };
393
394
  return /* @__PURE__ */ jsxs5("section", { className: "scui-list", ref: root, children: [
394
- /* @__PURE__ */ jsxs5("header", { className: "scui-head", children: [
395
+ ListHeader ? /* @__PURE__ */ jsx6(ListHeader, { state, adapter, value: { query, rows, onNew } }) : /* @__PURE__ */ jsxs5("header", { className: "scui-head", children: [
395
396
  /* @__PURE__ */ jsxs5("span", { className: "scui-head-copy", children: [
396
397
  /* @__PURE__ */ jsx6("strong", { children: labels.chats }),
397
398
  /* @__PURE__ */ jsxs5("small", { children: [