@volter-ai-dev/supercode-ui 0.1.18 → 0.1.20

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/composer.mjs CHANGED
@@ -82,6 +82,7 @@ function boundedSet(map, key, value) {
82
82
  // src/icon.jsx
83
83
  import { Fragment, jsx, jsxs } from "preact/jsx-runtime";
84
84
  var ICONS = {
85
+ attach: () => /* @__PURE__ */ jsx("path", { d: "M6.25 9.75 10.6 5.4a2.1 2.1 0 0 1 2.97 2.97l-5.4 5.4a3.3 3.3 0 0 1-4.67-4.66l5.52-5.52" }),
85
86
  back: () => /* @__PURE__ */ jsx("path", { d: "m11.5 4.5-4.5 4.5 4.5 4.5" }),
86
87
  check: () => /* @__PURE__ */ jsx("path", { d: "m4 9 3.25 3.25L14 5.5" }),
87
88
  chevron: () => /* @__PURE__ */ jsx("path", { d: "m7 4.5 4.5 4.5L7 13.5" }),
@@ -122,6 +123,44 @@ function UiIcon({ name, size = 16, class: className = "" }) {
122
123
  return /* @__PURE__ */ jsx("svg", { class: `scui-icon ${className}`, style: { "--scui-icon-size": `${size}px` }, viewBox: "0 0 18 18", fill: "none", stroke: "currentColor", "stroke-width": "1.5", "stroke-linecap": "round", "stroke-linejoin": "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Glyph, {}) });
123
124
  }
124
125
 
126
+ // src/context.jsx
127
+ import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
128
+ var MAX_CONTEXT_ITEMS = 32;
129
+ function normalizeContext(value) {
130
+ return (Array.isArray(value) ? value : value ? [value] : []).flatMap((item) => {
131
+ if (!item || typeof item.label !== "string" || typeof item.detail !== "string") return [];
132
+ const label = item.label.trim().slice(0, 200);
133
+ const detail = item.detail.slice(0, 2e4);
134
+ if (!label || !detail) return [];
135
+ return [{
136
+ ...typeof item.id === "string" && item.id ? { id: item.id.slice(0, 2e3) } : {},
137
+ ...typeof item.kind === "string" && item.kind ? { kind: item.kind.slice(0, 100) } : {},
138
+ label,
139
+ detail
140
+ }];
141
+ }).slice(0, MAX_CONTEXT_ITEMS);
142
+ }
143
+ function mergeContext(current, picked) {
144
+ const next = [...current];
145
+ const seen = new Set(current.map((item) => item.id ? `id:${item.id}` : `value:${item.kind ?? ""}\0${item.label}\0${item.detail}`));
146
+ for (const item of normalizeContext(picked)) {
147
+ const key = item.id ? `id:${item.id}` : `value:${item.kind ?? ""}\0${item.label}\0${item.detail}`;
148
+ if (seen.has(key)) continue;
149
+ seen.add(key);
150
+ next.push(item);
151
+ if (next.length === MAX_CONTEXT_ITEMS) break;
152
+ }
153
+ return next;
154
+ }
155
+ function ContextTray({ items, onRemove }) {
156
+ if (!items.length) return null;
157
+ return /* @__PURE__ */ jsx2("div", { class: "scui-compose-context", "aria-label": "Attached context", children: items.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
158
+ /* @__PURE__ */ jsx2(UiIcon, { name: "attach", size: 12 }),
159
+ /* @__PURE__ */ jsx2("strong", { children: item.label }),
160
+ /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove context ${item.label}`, onClick: () => onRemove(index), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 12 }) })
161
+ ] }, item.id ?? `${item.label}:${index}`)) });
162
+ }
163
+
125
164
  // src/textarea.js
126
165
  import { useLayoutEffect } from "preact/hooks";
127
166
  function useAutosizeTextarea(ref, value) {
@@ -137,7 +176,7 @@ function useAutosizeTextarea(ref, value) {
137
176
  }
138
177
 
139
178
  // src/composer.jsx
140
- import { jsx as jsx2, jsxs as jsxs2 } from "preact/jsx-runtime";
179
+ import { jsx as jsx3, jsxs as jsxs3 } from "preact/jsx-runtime";
141
180
  var composerMemory = /* @__PURE__ */ new Map();
142
181
  function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
143
182
  if (state.mode !== "mirror" || state.canSend) return null;
@@ -147,29 +186,32 @@ function ContinuationBar({ state, adapter, labels = DEFAULT_LABELS }) {
147
186
  const resume = canContinueHere(state);
148
187
  const join = state.canAttach;
149
188
  const branch = state.canBranch;
150
- if (!resume && !join && !branch) return /* @__PURE__ */ jsx2("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs2("span", { children: [
151
- /* @__PURE__ */ jsx2("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
152
- /* @__PURE__ */ jsx2("small", { children: "This session cannot be continued by an available harness." })
189
+ if (!resume && !join && !branch) return /* @__PURE__ */ jsx3("div", { class: "scui-continuation", children: /* @__PURE__ */ jsxs3("span", { children: [
190
+ /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
191
+ /* @__PURE__ */ jsx3("small", { children: "This session cannot be continued by an available harness." })
153
192
  ] }) });
154
- return /* @__PURE__ */ jsxs2("div", { class: "scui-continuation", children: [
155
- /* @__PURE__ */ jsxs2("span", { children: [
156
- /* @__PURE__ */ jsx2("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
157
- /* @__PURE__ */ jsx2("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
193
+ return /* @__PURE__ */ jsxs3("div", { class: "scui-continuation", children: [
194
+ /* @__PURE__ */ jsxs3("span", { children: [
195
+ /* @__PURE__ */ jsx3("strong", { children: activeElsewhere ? "Active elsewhere" : "Read-only" }),
196
+ /* @__PURE__ */ jsx3("small", { children: join ? "Join the proven live runtime without taking it over." : resume ? `Resume this ${harnessDisplayName(state.harness)} session here.` : "Start an independent continuation." })
158
197
  ] }),
159
- /* @__PURE__ */ jsx2("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
198
+ /* @__PURE__ */ jsx3("button", { type: "button", disabled: Boolean(state.operation), onClick: () => adapter.onIntent(join ? { action: "join" } : resume ? { action: "resume" } : { action: "branch" }), children: join ? labels.joinLive : resume ? labels.continueHere : labels.forkHere })
160
199
  ] });
161
200
  }
162
201
  function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.attached?.key || `${state.harness}:${state.workspace}`, pendingStatus = null, restoreDraft = null, onPending, onDraftRestored }) {
163
- const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, queue: [] };
202
+ const remembered = composerMemory.get(memoryKey) ?? { draft: state.savedDraft, context: [], queue: [] };
164
203
  const [draft, setDraft] = useState(remembered.draft);
204
+ const [context, setContext] = useState(remembered.context);
165
205
  const [queue, setQueue] = useState(remembered.queue);
166
206
  const [dispatching, setDispatching] = useState(false);
207
+ const [picking, setPicking] = useState(false);
208
+ const [pickerError, setPickerError] = useState(null);
167
209
  const textarea = useRef(null);
168
210
  useAutosizeTextarea(textarea, draft);
169
- const remember = (nextDraft, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, queue: nextQueue });
211
+ const remember = (nextDraft, nextContext, nextQueue) => boundedSet(composerMemory, memoryKey, { draft: nextDraft, context: nextContext, queue: nextQueue });
170
212
  const updateQueue = (update) => setQueue((items) => {
171
213
  const next = update(items);
172
- remember(draft, next);
214
+ remember(draft, context, next);
173
215
  return next;
174
216
  });
175
217
  const queueBlocked = state.busy || pendingStatus !== null || dispatching;
@@ -179,9 +221,9 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
179
221
  const [next, ...rest] = queue;
180
222
  setDispatching(true);
181
223
  setQueue(rest);
182
- remember(draft, rest);
183
- onPending?.(next);
184
- adapter.onIntent({ action: "send", text: next });
224
+ remember(draft, context, rest);
225
+ onPending?.(next.text, next.context);
226
+ adapter.onIntent({ action: "send", text: next.text, ...next.context.length ? { context: next.context } : {} });
185
227
  }
186
228
  }, [adapter, draft, memoryKey, onPending, queue, queueBlocked, state.canSend]);
187
229
  useEffect(() => {
@@ -193,7 +235,9 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
193
235
  useEffect(() => {
194
236
  if (!restoreDraft) return;
195
237
  setDraft(restoreDraft.text);
196
- remember(restoreDraft.text, queue);
238
+ const restoredContext = normalizeContext(restoreDraft.context);
239
+ setContext(restoredContext);
240
+ remember(restoreDraft.text, restoredContext, queue);
197
241
  textarea.current?.focus({ preventScroll: true });
198
242
  onDraftRestored?.(restoreDraft.id);
199
243
  }, [onDraftRestored, restoreDraft?.id]);
@@ -201,43 +245,70 @@ function Composer({ state, adapter, labels = DEFAULT_LABELS, memoryKey = state.a
201
245
  const timer = setTimeout(() => adapter.onIntent({ action: "draft", text: draft }), 250);
202
246
  return () => clearTimeout(timer);
203
247
  }, [adapter, draft]);
248
+ const pickContext = () => {
249
+ if (!adapter.pickContext || state.mode !== "control" || picking || context.length >= MAX_CONTEXT_ITEMS) return;
250
+ setPicking(true);
251
+ setPickerError(null);
252
+ Promise.resolve().then(() => adapter.pickContext()).then((picked) => {
253
+ setContext((current) => {
254
+ const next = mergeContext(current, picked);
255
+ remember(draft, next, queue);
256
+ return next;
257
+ });
258
+ }, (error) => setPickerError(error instanceof Error ? error.message : "Could not attach context.")).finally(() => setPicking(false));
259
+ };
204
260
  const send = () => {
205
261
  const text = draft.trim();
206
262
  if (!text) return;
207
- if (queuesNewMessage) updateQueue((items) => [...items, text]);
263
+ const message = { text, context };
264
+ if (queuesNewMessage) updateQueue((items) => [...items, message]);
208
265
  else if (state.canSend) {
209
266
  if (onPending) setDispatching(true);
210
- onPending?.(text);
211
- adapter.onIntent({ action: "send", text });
267
+ onPending?.(text, context);
268
+ adapter.onIntent({ action: "send", text, ...context.length ? { context } : {} });
212
269
  } else return;
213
270
  setDraft("");
214
- remember("", queuesNewMessage ? [...queue, text] : queue);
271
+ setContext([]);
272
+ remember("", [], queuesNewMessage ? [...queue, message] : queue);
215
273
  };
216
- return /* @__PURE__ */ jsxs2("div", { class: "scui-compose", children: [
217
- queue.length ? /* @__PURE__ */ jsxs2("div", { class: "scui-queue", children: [
218
- /* @__PURE__ */ jsxs2("strong", { children: [
274
+ return /* @__PURE__ */ jsxs3("div", { class: "scui-compose", children: [
275
+ queue.length ? /* @__PURE__ */ jsxs3("div", { class: "scui-queue", children: [
276
+ /* @__PURE__ */ jsxs3("strong", { children: [
219
277
  queue.length,
220
278
  " queued"
221
279
  ] }),
222
- queue.map((item, index) => /* @__PURE__ */ jsxs2("span", { children: [
223
- item,
224
- /* @__PURE__ */ jsx2("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: /* @__PURE__ */ jsx2(UiIcon, { name: "close", size: 13 }) })
225
- ] }, `${index}:${item}`))
280
+ queue.map((item, index) => /* @__PURE__ */ jsxs3("span", { children: [
281
+ /* @__PURE__ */ jsxs3("span", { children: [
282
+ item.text,
283
+ item.context.length ? /* @__PURE__ */ jsxs3("small", { children: [
284
+ item.context.length,
285
+ " attached"
286
+ ] }) : null
287
+ ] }),
288
+ /* @__PURE__ */ jsx3("button", { type: "button", "aria-label": `Remove queued message ${index + 1}`, onClick: () => updateQueue((items) => items.filter((_, itemIndex) => itemIndex !== index)), children: /* @__PURE__ */ jsx3(UiIcon, { name: "close", size: 13 }) })
289
+ ] }, `${index}:${item.text}`))
226
290
  ] }) : null,
227
- /* @__PURE__ */ jsxs2("div", { class: "scui-envelope", children: [
228
- /* @__PURE__ */ jsx2("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onInput: (event) => {
291
+ /* @__PURE__ */ jsx3(ContextTray, { items: context, onRemove: (index) => setContext((items) => {
292
+ const next = items.filter((_, itemIndex) => itemIndex !== index);
293
+ remember(draft, next, queue);
294
+ return next;
295
+ }) }),
296
+ pickerError ? /* @__PURE__ */ jsx3("small", { class: "scui-context-error", role: "alert", children: pickerError }) : null,
297
+ /* @__PURE__ */ jsxs3("div", { class: "scui-envelope", children: [
298
+ adapter.pickContext && state.mode === "control" ? /* @__PURE__ */ jsx3("button", { class: "scui-attach", type: "button", "aria-label": "Attach context", disabled: picking || context.length >= MAX_CONTEXT_ITEMS, onClick: pickContext, children: picking ? /* @__PURE__ */ jsx3("i", { class: "scui-control-spinner" }) : /* @__PURE__ */ jsx3(UiIcon, { name: "attach", size: 17 }) }) : null,
299
+ /* @__PURE__ */ jsx3("textarea", { ref: textarea, rows: 1, "aria-label": `Message ${harnessDisplayName(state.harness) || "agent"}`, placeholder: state.startup !== "ready" ? "Connecting\u2026" : pendingStatus === "failed" ? "Retry or edit the unsent message\u2026" : pendingStatus === "editing" ? "Edit and resend\u2026" : queueBlocked ? "Queue a follow-up\u2026" : labels.askAgent, value: draft, disabled: state.mode !== "control" && !state.canSend, onInput: (event) => {
229
300
  const value = event.currentTarget.value;
230
301
  setDraft(value);
231
- remember(value, queue);
302
+ remember(value, context, queue);
232
303
  }, onKeyDown: (event) => {
233
304
  if (isSendKey(event)) {
234
305
  event.preventDefault();
235
306
  send();
236
307
  }
237
308
  } }),
238
- /* @__PURE__ */ jsxs2("span", { children: [
239
- state.busy ? /* @__PURE__ */ jsx2("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx2(UiIcon, { name: "stop", size: 15 }) }) : null,
240
- /* @__PURE__ */ jsx2("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx2(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
309
+ /* @__PURE__ */ jsxs3("span", { children: [
310
+ state.busy ? /* @__PURE__ */ jsx3("button", { class: "scui-stop", type: "button", "aria-label": "Stop agent", disabled: !state.canInterrupt, onClick: () => adapter.onIntent({ action: "interrupt" }), children: /* @__PURE__ */ jsx3(UiIcon, { name: "stop", size: 15 }) }) : null,
311
+ /* @__PURE__ */ jsx3("button", { class: "scui-send", type: "button", "aria-label": queuesNewMessage ? "Queue message" : "Send message", disabled: !draft.trim() || !queuesNewMessage && !state.canSend, onClick: send, children: /* @__PURE__ */ jsx3(UiIcon, { name: queuesNewMessage ? "plus" : "send", size: 17 }) })
241
312
  ] })
242
313
  ] })
243
314
  ] });
package/controller.mjs CHANGED
@@ -376,10 +376,10 @@ async function dispatchStandard(controller, intent, options) {
376
376
  const active = snapshot.activeSessionKey;
377
377
  if (intent.action === 'mounted') return;
378
378
  if (intent.action === 'attach') return controller.dispatch({ type: 'observe', sessionKey: intent.key });
379
- if (intent.action === 'send') return controller.dispatch({ type: 'send', text: intent.text });
379
+ if (intent.action === 'send') return controller.dispatch({ type: 'send', text: intent.text, ...(intent.context?.length ? { context: intent.context } : {}) });
380
380
  if (intent.action === 'new') {
381
381
  await controller.dispatch({ type: 'start', harness: intent.harness });
382
- return controller.dispatch({ type: 'send', text: intent.text });
382
+ return controller.dispatch({ type: 'send', text: intent.text, ...(intent.context?.length ? { context: intent.context } : {}) });
383
383
  }
384
384
  if (intent.action === 'resume' && active) return controller.dispatch({ type: 'resume', sessionKey: active });
385
385
  if (intent.action === 'join' && active) return controller.dispatch({ type: 'attach', sessionKey: active });
package/conversation.mjs CHANGED
@@ -575,6 +575,7 @@ function boundedSet(map, key, value) {
575
575
  // src/icon.jsx
576
576
  import { Fragment, jsx as jsx2, jsxs } from "preact/jsx-runtime";
577
577
  var ICONS = {
578
+ attach: () => /* @__PURE__ */ jsx2("path", { d: "M6.25 9.75 10.6 5.4a2.1 2.1 0 0 1 2.97 2.97l-5.4 5.4a3.3 3.3 0 0 1-4.67-4.66l5.52-5.52" }),
578
579
  back: () => /* @__PURE__ */ jsx2("path", { d: "m11.5 4.5-4.5 4.5 4.5 4.5" }),
579
580
  check: () => /* @__PURE__ */ jsx2("path", { d: "m4 9 3.25 3.25L14 5.5" }),
580
581
  chevron: () => /* @__PURE__ */ jsx2("path", { d: "m7 4.5 4.5 4.5L7 13.5" }),
@@ -1066,18 +1067,23 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1066
1067
  const element = scroller.current;
1067
1068
  if (!element) return;
1068
1069
  const anchor = earlierAnchor.current;
1069
- if (anchor && state.transcript.length > anchor.entries) {
1070
- element.scrollTop = anchor.top + (element.scrollHeight - anchor.height);
1071
- earlierAnchor.current = null;
1072
- remember({ top: element.scrollTop, atBottom: false });
1073
- return;
1070
+ if (anchor) {
1071
+ if (state.operation === "loadEarlier") anchor.seenOperation = true;
1072
+ const prepended = state.transcript.length > anchor.entries && state.transcript[0]?.id !== anchor.firstId;
1073
+ if (prepended) {
1074
+ element.scrollTop = anchor.top + (element.scrollHeight - anchor.height);
1075
+ earlierAnchor.current = null;
1076
+ remember({ top: element.scrollTop, atBottom: false });
1077
+ return;
1078
+ }
1079
+ if (state.error || anchor.seenOperation && state.operation !== "loadEarlier") earlierAnchor.current = null;
1074
1080
  }
1075
1081
  if (!restored.current) {
1076
1082
  restored.current = true;
1077
1083
  if (remembered.top !== null && !remembered.atBottom) element.scrollTop = remembered.top;
1078
1084
  else pin();
1079
1085
  } else if (atBottom) pin();
1080
- }, [memoryKey, state.transcript, state.busy, state.operation, pendingMessage?.text, pendingMessage?.status]);
1086
+ }, [memoryKey, state.transcript, state.busy, state.operation, state.error, pendingMessage?.text, pendingMessage?.status]);
1081
1087
  return /* @__PURE__ */ jsxs2("div", { class: "scui-conversation-wrap", children: [
1082
1088
  /* @__PURE__ */ jsx3(ConversationAnnouncements, { state }),
1083
1089
  /* @__PURE__ */ jsx3("div", { class: "scui-conversation", ref: scroller, tabIndex: 0, "aria-label": "Conversation", onScroll: (event) => {
@@ -1091,7 +1097,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1091
1097
  /* @__PURE__ */ jsx3(SessionDetails, { semantics: state.semantics }),
1092
1098
  state.history.hasEarlier ? /* @__PURE__ */ jsx3("button", { class: "scui-load", type: "button", disabled: Boolean(state.operation), onClick: () => {
1093
1099
  const element = scroller.current;
1094
- if (element) earlierAnchor.current = { height: element.scrollHeight, top: element.scrollTop, entries: state.transcript.length };
1100
+ if (element) earlierAnchor.current = { height: element.scrollHeight, top: element.scrollTop, entries: state.transcript.length, firstId: state.transcript[0]?.id, seenOperation: false };
1095
1101
  setAtBottom(false);
1096
1102
  adapter.onIntent({ action: "loadEarlier" });
1097
1103
  }, children: "Load earlier messages" }) : null,
@@ -1103,6 +1109,7 @@ function Conversation({ state, adapter, components = {}, slots = {}, memoryKey =
1103
1109
  ] }, block.id)),
1104
1110
  pendingMessage ? /* @__PURE__ */ jsxs2("article", { class: "scui-message scui-pending", "data-role": "user", "data-status": pendingMessage.status, "aria-label": "Your pending message", children: [
1105
1111
  /* @__PURE__ */ jsx3(Markdown, { value: pendingMessage.text, copyText: adapter?.copyText }),
1112
+ /* @__PURE__ */ jsx3(ContextDisclosure, { context: pendingMessage.context }),
1106
1113
  /* @__PURE__ */ jsxs2("footer", { children: [
1107
1114
  /* @__PURE__ */ jsx3("small", { children: pendingMessage.status === "failed" ? "Not sent" : "Sending\u2026" }),
1108
1115
  pendingMessage.status === "failed" ? /* @__PURE__ */ jsxs2("span", { children: [
package/core.mjs CHANGED
@@ -721,7 +721,7 @@ export function sessionActivity(state, row) {
721
721
  export function filterSessions(rows, query) {
722
722
  const needle = query.trim().toLocaleLowerCase();
723
723
  if (!needle) return [...rows];
724
- return rows.filter((row) => [row.name, row.title, row.cwd, row.harness, harnessDisplayName(row.harness)].some((value) => value.toLocaleLowerCase().includes(needle)));
724
+ return rows.filter((row) => [row.name, row.title, row.preview, row.cwd, row.harness, harnessDisplayName(row.harness)].some((value) => typeof value === 'string' && value.toLocaleLowerCase().includes(needle)));
725
725
  }
726
726
 
727
727
  export function groupConversation(entries) {