@spotpatch/runtime 1.15.2 → 1.15.4

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.
@@ -2,6 +2,10 @@ import {
2
2
  getContextualAskExtension,
3
3
  registerContextualAskExtension
4
4
  } from "./chunk-PXJKFEUJ.js";
5
+ import {
6
+ SELECT_PICKER_STYLES,
7
+ createSelectPicker
8
+ } from "./chunk-ELCIEQGT.js";
5
9
  import {
6
10
  createButton,
7
11
  createMarkedElement
@@ -763,158 +767,6 @@ function createContextualAskWorkflow(input) {
763
767
  });
764
768
  }
765
769
 
766
- // src/ui/ask-picker.ts
767
- function createAskPicker(document, onViewChange) {
768
- const root = createMarkedElement(document, "div");
769
- root.className = "spotpatch-ask-executor-picker";
770
- const trigger = createButton(document, "", "spotpatch-ask-executor");
771
- trigger.id = `spotpatch-ask-picker-${Math.random().toString(36).slice(2)}`;
772
- trigger.setAttribute("role", "combobox");
773
- trigger.setAttribute("aria-haspopup", "listbox");
774
- trigger.setAttribute("aria-expanded", "false");
775
- const text = createMarkedElement(document, "span");
776
- trigger.append(text);
777
- const menu = createMarkedElement(document, "div");
778
- menu.id = `${trigger.id}-menu`;
779
- menu.className = "spotpatch-ask-executor-menu";
780
- menu.setAttribute("role", "listbox");
781
- menu.hidden = true;
782
- trigger.setAttribute("aria-controls", menu.id);
783
- const select = createMarkedElement(document, "select");
784
- select.className = "spotpatch-ask-executor-native";
785
- select.tabIndex = -1;
786
- select.setAttribute("aria-hidden", "true");
787
- root.append(trigger, menu, select);
788
- function close(focus = false) {
789
- if (menu.hidden) return;
790
- menu.hidden = true;
791
- trigger.setAttribute("aria-expanded", "false");
792
- trigger.removeAttribute("aria-activedescendant");
793
- if (focus) trigger.focus({ preventScroll: true });
794
- onViewChange();
795
- }
796
- function activate(index) {
797
- const options = [...menu.children];
798
- const active = options[index];
799
- if (active === void 0) return;
800
- for (const option of options) option.dataset.active = String(option === active);
801
- trigger.setAttribute("aria-activedescendant", active.id);
802
- const itemRect = active.getBoundingClientRect();
803
- const menuRect = menu.getBoundingClientRect();
804
- if (itemRect.top < menuRect.top) menu.scrollTop -= menuRect.top - itemRect.top;
805
- else if (itemRect.bottom > menuRect.bottom)
806
- menu.scrollTop += itemRect.bottom - menuRect.bottom;
807
- }
808
- function open() {
809
- if (trigger.disabled || !menu.hidden) return;
810
- menu.hidden = false;
811
- trigger.setAttribute("aria-expanded", "true");
812
- activate(Math.max(0, select.selectedIndex));
813
- onViewChange();
814
- }
815
- function sync() {
816
- text.textContent = select.selectedOptions[0]?.textContent ?? "";
817
- trigger.title = text.textContent;
818
- trigger.dataset.empty = String(select.value.length === 0);
819
- for (const option of [...menu.children]) {
820
- option.setAttribute(
821
- "aria-selected",
822
- String(option.dataset.value === select.value)
823
- );
824
- }
825
- }
826
- function choose(value) {
827
- if (trigger.disabled) return;
828
- select.value = value;
829
- sync();
830
- close(true);
831
- select.dispatchEvent(new Event("change", { bubbles: true }));
832
- }
833
- function rebuild() {
834
- close();
835
- menu.replaceChildren();
836
- for (const [index, option] of [...select.options].entries()) {
837
- const item = createMarkedElement(document, "div");
838
- item.id = `${menu.id}-${String(index)}`;
839
- item.className = "spotpatch-ask-executor-option";
840
- item.setAttribute("role", "option");
841
- item.dataset.value = option.value;
842
- item.textContent = option.textContent;
843
- item.title = option.textContent;
844
- item.addEventListener("pointerdown", (event) => {
845
- event.preventDefault();
846
- });
847
- item.addEventListener("click", () => {
848
- choose(option.value);
849
- });
850
- menu.append(item);
851
- }
852
- sync();
853
- }
854
- trigger.addEventListener("click", () => {
855
- if (menu.hidden) open();
856
- else close();
857
- });
858
- trigger.addEventListener("keydown", (event) => {
859
- if (event.key === "Escape" && !menu.hidden) {
860
- event.preventDefault();
861
- event.stopPropagation();
862
- close();
863
- return;
864
- }
865
- if (event.key === "Tab") {
866
- close();
867
- return;
868
- }
869
- if (["Enter", " "].includes(event.key) && !menu.hidden) {
870
- event.preventDefault();
871
- const active = [...menu.children].find(
872
- (item) => item.id === trigger.getAttribute("aria-activedescendant")
873
- );
874
- if (active?.dataset.value !== void 0) choose(active.dataset.value);
875
- return;
876
- }
877
- if (!["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) return;
878
- event.preventDefault();
879
- if (menu.hidden) {
880
- open();
881
- return;
882
- }
883
- const options = [...menu.children];
884
- const index = options.findIndex(
885
- (item) => item.id === trigger.getAttribute("aria-activedescendant")
886
- );
887
- activate(
888
- event.key === "Home" ? 0 : event.key === "End" ? options.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + options.length) % options.length
889
- );
890
- });
891
- root.addEventListener("focusout", (event) => {
892
- if (!(event.relatedTarget instanceof Node) || !root.contains(event.relatedTarget))
893
- close();
894
- });
895
- const outside = (event) => {
896
- if (!event.composedPath().includes(root)) close();
897
- };
898
- document.addEventListener("pointerdown", outside);
899
- select.addEventListener("change", sync);
900
- return {
901
- root,
902
- trigger,
903
- select,
904
- rebuild,
905
- close,
906
- setDisabled(disabled) {
907
- select.disabled = disabled;
908
- trigger.disabled = disabled || select.options.length < 2;
909
- trigger.dataset.expandable = String(!trigger.disabled);
910
- if (trigger.disabled) close();
911
- },
912
- dispose() {
913
- document.removeEventListener("pointerdown", outside);
914
- }
915
- };
916
- }
917
-
918
770
  // src/ui/contextual-ask-localization.ts
919
771
  var EN = Object.freeze({
920
772
  mode: Object.freeze({ ask: "Ask", change: "Change", label: "Task mode" }),
@@ -930,12 +782,12 @@ var EN = Object.freeze({
930
782
  ]),
931
783
  executorLabel: "Read-only executor",
932
784
  modelLabel: "Model",
933
- loadingExecutors: "Checking available executors\u2026",
785
+ loadingExecutors: "Checking available executors…",
934
786
  noExecutor: "No verified read-only executor is available.",
935
787
  unavailable: "unavailable",
936
788
  consent: "Allow the selected source snapshot to be sent to this provider for this answer.",
937
- dataSummary: (targets, sources) => `${String(targets)} selected element${targets === 1 ? "" : "s"} \xB7 ${String(sources)} source-backed`,
938
- safety: "Single turn \xB7 read-only \xB7 no history",
789
+ dataSummary: (targets, sources) => `${String(targets)} selected element${targets === 1 ? "" : "s"} · ${String(sources)} source-backed`,
790
+ safety: "Single turn · read-only · no history",
939
791
  submit: "Ask",
940
792
  cancel: "Cancel",
941
793
  newQuestion: "Start over",
@@ -955,7 +807,7 @@ var EN = Object.freeze({
955
807
  cancelled: "Cancelled",
956
808
  failed: "Could not answer"
957
809
  }),
958
- sourceLabel: (path, start, end) => `${path}:${String(start)}${end === start ? "" : `\u2013${String(end)}`}`,
810
+ sourceLabel: (path, start, end) => `${path}:${String(start)}${end === start ? "" : `–${String(end)}`}`,
959
811
  readSource: (path) => `Reading ${path}`,
960
812
  readFiles: (bucket) => `Reading ${bucket} files`,
961
813
  copied: "Answer copied.",
@@ -987,74 +839,74 @@ var EN = Object.freeze({
987
839
  warning: (code) => code === "insufficient-evidence" ? "The selected source did not contain enough evidence for a complete answer." : "Some source references were omitted because they could not be verified."
988
840
  });
989
841
  var ZH = Object.freeze({
990
- mode: Object.freeze({ ask: "\u95EE\u7B54", change: "\u4FEE\u6539", label: "\u4EFB\u52A1\u6A21\u5F0F" }),
991
- title: "\u8BE2\u95EE\u6240\u9009\u5143\u7D20",
992
- subtitle: "\u4E00\u6B21\u63D0\u95EE\uFF0C\u4E00\u6B21\u5E26\u6E90\u7801\u5F15\u7528\u7684\u56DE\u7B54\uFF1BSpotPatch \u4E0D\u4F1A\u5199\u5165\u4EE3\u7801\u3002",
993
- questionLabel: "\u95EE\u9898",
994
- questionPlaceholder: "\u8FD9\u4E2A\u7EC4\u4EF6\u662F\u505A\u4EC0\u4E48\u7684\uFF1F",
995
- suggestionsLabel: "\u63A8\u8350\u95EE\u9898",
842
+ mode: Object.freeze({ ask: "问答", change: "修改", label: "任务模式" }),
843
+ title: "询问所选元素",
844
+ subtitle: "一次提问,一次带源码引用的回答;SpotPatch 不会写入代码。",
845
+ questionLabel: "问题",
846
+ questionPlaceholder: "这个组件是做什么的?",
847
+ suggestionsLabel: "推荐问题",
996
848
  suggestions: Object.freeze([
997
- "\u8FD9\u4E2A\u7EC4\u4EF6\u662F\u505A\u4EC0\u4E48\u7684\uFF1F",
998
- "\u5B83\u7684\u6570\u636E\u4ECE\u54EA\u91CC\u6765\uFF1F",
999
- "\u4E3A\u4EC0\u4E48\u8981\u8FD9\u6837\u5B9E\u73B0\uFF1F"
849
+ "这个组件是做什么的?",
850
+ "它的数据从哪里来?",
851
+ "为什么要这样实现?"
1000
852
  ]),
1001
- executorLabel: "\u53EA\u8BFB\u6267\u884C\u5668",
1002
- modelLabel: "\u6A21\u578B",
1003
- loadingExecutors: "\u6B63\u5728\u68C0\u67E5\u53EF\u7528\u6267\u884C\u5668\u2026",
1004
- noExecutor: "\u76EE\u524D\u6CA1\u6709\u901A\u8FC7\u53EA\u8BFB\u9A8C\u8BC1\u7684\u6267\u884C\u5668\u3002",
1005
- unavailable: "\u4E0D\u53EF\u7528",
1006
- consent: "\u5141\u8BB8\u672C\u6B21\u56DE\u7B54\u5C06\u6240\u9009\u6E90\u7801\u5FEB\u7167\u53D1\u9001\u7ED9\u8BE5\u63D0\u4F9B\u5546\u3002",
1007
- dataSummary: (targets, sources) => `${String(targets)} \u4E2A\u5DF2\u9009\u5143\u7D20 \xB7 ${String(sources)} \u4E2A\u5177\u5907\u6E90\u7801\u4E0A\u4E0B\u6587`,
1008
- safety: "\u5355\u8F6E \xB7 \u53EA\u8BFB \xB7 \u4E0D\u4FDD\u5B58\u5386\u53F2",
1009
- submit: "\u63D0\u95EE",
1010
- cancel: "\u53D6\u6D88",
1011
- newQuestion: "\u91CD\u65B0\u5F00\u59CB",
1012
- copy: "\u590D\u5236\u56DE\u7B54",
1013
- convert: "\u8F6C\u6210\u4FEE\u6539\u8BF7\u6C42",
1014
- answerTitle: "\u56DE\u7B54",
1015
- sourcesTitle: "\u6E90\u7801\u5F15\u7528",
1016
- stale: "\u56DE\u7B54\u540E\u9009\u62E9\u5DF2\u53D1\u751F\u53D8\u5316\u3002\u8BF7\u91CD\u65B0\u63D0\u95EE\u540E\u518D\u91C7\u4FE1\u6B64\u56DE\u7B54\u3002",
1017
- convertedTitle: "\u6765\u81EA\u4E0A\u4E0B\u6587\u95EE\u7B54",
1018
- convertedBody: "\u56DE\u7B54\u4E0E\u6E90\u7801\u5F15\u7528\u4EC5\u4F5C\u4E3A\u672C\u5730\u4E0A\u4E0B\u6587\u4FDD\u7559\uFF0C\u5C1A\u672A\u53D1\u8D77\u4EFB\u4F55\u4FEE\u6539\u3002",
853
+ executorLabel: "只读执行器",
854
+ modelLabel: "模型",
855
+ loadingExecutors: "正在检查可用执行器…",
856
+ noExecutor: "目前没有通过只读验证的执行器。",
857
+ unavailable: "不可用",
858
+ consent: "允许本次回答将所选源码快照发送给该提供商。",
859
+ dataSummary: (targets, sources) => `${String(targets)} 个已选元素 · ${String(sources)} 个具备源码上下文`,
860
+ safety: "单轮 · 只读 · 不保存历史",
861
+ submit: "提问",
862
+ cancel: "取消",
863
+ newQuestion: "重新开始",
864
+ copy: "复制回答",
865
+ convert: "转成修改请求",
866
+ answerTitle: "回答",
867
+ sourcesTitle: "源码引用",
868
+ stale: "回答后选择已发生变化。请重新提问后再采信此回答。",
869
+ convertedTitle: "来自上下文问答",
870
+ convertedBody: "回答与源码引用仅作为本地上下文保留,尚未发起任何修改。",
1019
871
  status: Object.freeze({
1020
- queued: "\u5DF2\u6392\u961F",
1021
- authorizing: "\u6B63\u5728\u9A8C\u8BC1\u53EA\u8BFB\u6743\u9650",
1022
- running: "\u6B63\u5728\u5206\u6790\u6240\u9009\u6E90\u7801",
1023
- cancelling: "\u6B63\u5728\u53D6\u6D88",
1024
- answered: "\u56DE\u7B54\u5DF2\u5C31\u7EEA",
1025
- cancelled: "\u5DF2\u53D6\u6D88",
1026
- failed: "\u65E0\u6CD5\u56DE\u7B54"
872
+ queued: "已排队",
873
+ authorizing: "正在验证只读权限",
874
+ running: "正在分析所选源码",
875
+ cancelling: "正在取消",
876
+ answered: "回答已就绪",
877
+ cancelled: "已取消",
878
+ failed: "无法回答"
1027
879
  }),
1028
- sourceLabel: (path, start, end) => `${path}:${String(start)}${end === start ? "" : `\u2013${String(end)}`}`,
1029
- readSource: (path) => `\u6B63\u5728\u8BFB\u53D6 ${path}`,
1030
- readFiles: (bucket) => `\u6B63\u5728\u8BFB\u53D6 ${bucket} \u4E2A\u6587\u4EF6`,
1031
- copied: "\u56DE\u7B54\u5DF2\u590D\u5236\u3002",
1032
- copyFailed: "\u65E0\u6CD5\u590D\u5236\u56DE\u7B54\u3002",
1033
- sourceOpenFailed: "\u65E0\u6CD5\u6253\u5F00\u6E90\u7801\u3002",
1034
- converted: "\u5DF2\u5207\u6362\u5230\u4FEE\u6539\u6A21\u5F0F\uFF0C\u5E76\u628A\u56DE\u7B54\u4F5C\u4E3A\u672C\u5730\u4E0A\u4E0B\u6587\u9644\u5E26\u3002",
880
+ sourceLabel: (path, start, end) => `${path}:${String(start)}${end === start ? "" : `–${String(end)}`}`,
881
+ readSource: (path) => `正在读取 ${path}`,
882
+ readFiles: (bucket) => `正在读取 ${bucket} 个文件`,
883
+ copied: "回答已复制。",
884
+ copyFailed: "无法复制回答。",
885
+ sourceOpenFailed: "无法打开源码。",
886
+ converted: "已切换到修改模式,并把回答作为本地上下文附带。",
1035
887
  error: (code) => {
1036
888
  const errors = {
1037
- INVALID_TOKEN: "\u672C\u5730 SpotPatch \u4F1A\u8BDD\u5DF2\u5931\u6548\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u3002",
1038
- ORIGIN_NOT_ALLOWED: "\u672C\u5730\u8BF7\u6C42\u672A\u901A\u8FC7\u540C\u6E90\u5B89\u5168\u6821\u9A8C\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u6216\u91CD\u542F\u5F00\u53D1\u670D\u52A1\u3002",
1039
- ASK_DISABLED: "\u4E0A\u4E0B\u6587\u95EE\u7B54\u672A\u542F\u7528\u3002",
1040
- ASK_SELECTION_REQUIRED: "\u8BF7\u5148\u9009\u62E9\u81F3\u5C11\u4E00\u4E2A\u5143\u7D20\u3002",
1041
- ASK_SELECTION_STALE: "\u6240\u9009\u6E90\u7801\u5DF2\u7ECF\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u9009\u62E9\u540E\u91CD\u65B0\u63D0\u95EE\u3002",
1042
- ASK_QUESTION_INVALID: "\u8BF7\u8F93\u5165\u4E00\u4E2A\u6709\u6548\u7684\u95EE\u9898\u3002",
1043
- ASK_EXECUTOR_UNAVAILABLE: "\u6240\u9009\u53EA\u8BFB\u6267\u884C\u5668\u5F53\u524D\u4E0D\u53EF\u7528\u3002",
1044
- ASK_TIMEOUT: "\u53EA\u8BFB\u6267\u884C\u5668\u672A\u80FD\u5728\u9650\u5B9A\u65F6\u95F4\u5185\u8FD4\u56DE\u56DE\u7B54\uFF0C\u8BF7\u91CD\u65B0\u63D0\u95EE\u3002",
1045
- ASK_CONSENT_REQUIRED: "\u8BF7\u786E\u8BA4\u672C\u6B21\u56DE\u7B54\u7684\u63D0\u4F9B\u5546\u6570\u636E\u8BBF\u95EE\u3002",
1046
- ASK_BUSY: "\u5DE5\u4F5C\u533A\u6B63\u5728\u6267\u884C\u5176\u4ED6\u4EFB\u52A1\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5\u3002",
1047
- ASK_CANCELLED: "\u672C\u6B21\u63D0\u95EE\u5DF2\u53D6\u6D88\u3002",
1048
- ASK_RESULT_EXPIRED: "\u56DE\u7B54\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u65B0\u63D0\u95EE\u3002",
1049
- ASK_WRITE_ATTEMPTED: "\u6267\u884C\u5668\u5C1D\u8BD5\u5199\u5165\uFF0C\u5DF2\u88AB\u963B\u6B62\u3002",
1050
- ASK_ANSWER_INVALID: "\u6267\u884C\u5668\u8FD4\u56DE\u7684\u5E26\u5F15\u7528\u56DE\u7B54\u65E0\u6548\u3002",
1051
- ASK_PROTOCOL_INCOMPATIBLE: "\u6267\u884C\u5668\u534F\u8BAE\u4E0D\u517C\u5BB9\u3002",
1052
- ASK_LIMIT_EXCEEDED: "\u6240\u9009\u4E0A\u4E0B\u6587\u8FC7\u5927\u3002",
1053
- ASK_SOURCE_SCOPE_DENIED: "\u8BF7\u6C42\u7684\u6E90\u7801\u8D85\u51FA\u4E86\u6388\u6743\u5FEB\u7167\u8303\u56F4\u3002"
889
+ INVALID_TOKEN: "本地 SpotPatch 会话已失效,请刷新页面。",
890
+ ORIGIN_NOT_ALLOWED: "本地请求未通过同源安全校验,请刷新页面或重启开发服务。",
891
+ ASK_DISABLED: "上下文问答未启用。",
892
+ ASK_SELECTION_REQUIRED: "请先选择至少一个元素。",
893
+ ASK_SELECTION_STALE: "所选源码已经变化,请刷新选择后重新提问。",
894
+ ASK_QUESTION_INVALID: "请输入一个有效的问题。",
895
+ ASK_EXECUTOR_UNAVAILABLE: "所选只读执行器当前不可用。",
896
+ ASK_TIMEOUT: "只读执行器未能在限定时间内返回回答,请重新提问。",
897
+ ASK_CONSENT_REQUIRED: "请确认本次回答的提供商数据访问。",
898
+ ASK_BUSY: "工作区正在执行其他任务,请稍后重试。",
899
+ ASK_CANCELLED: "本次提问已取消。",
900
+ ASK_RESULT_EXPIRED: "回答已过期,请重新提问。",
901
+ ASK_WRITE_ATTEMPTED: "执行器尝试写入,已被阻止。",
902
+ ASK_ANSWER_INVALID: "执行器返回的带引用回答无效。",
903
+ ASK_PROTOCOL_INCOMPATIBLE: "执行器协议不兼容。",
904
+ ASK_LIMIT_EXCEEDED: "所选上下文过大。",
905
+ ASK_SOURCE_SCOPE_DENIED: "请求的源码超出了授权快照范围。"
1054
906
  };
1055
- return code === void 0 ? "SpotPatch \u672A\u80FD\u5B8C\u6210\u672C\u6B21\u63D0\u95EE\u3002" : errors[code] ?? "SpotPatch \u672A\u80FD\u5B8C\u6210\u672C\u6B21\u63D0\u95EE\u3002";
907
+ return code === void 0 ? "SpotPatch 未能完成本次提问。" : errors[code] ?? "SpotPatch 未能完成本次提问。";
1056
908
  },
1057
- warning: (code) => code === "insufficient-evidence" ? "\u6240\u9009\u6E90\u7801\u4E0D\u8DB3\u4EE5\u652F\u6301\u5B8C\u6574\u56DE\u7B54\u3002" : "\u90E8\u5206\u6E90\u7801\u5F15\u7528\u56E0\u65E0\u6CD5\u9A8C\u8BC1\u800C\u88AB\u7701\u7565\u3002"
909
+ warning: (code) => code === "insufficient-evidence" ? "所选源码不足以支持完整回答。" : "部分源码引用因无法验证而被省略。"
1058
910
  });
1059
911
  function contextualAskMessages(locale) {
1060
912
  return locale === "zh-CN" ? ZH : EN;
@@ -1064,6 +916,7 @@ function contextualAskMessages(locale) {
1064
916
  function createStyles(document) {
1065
917
  const style = document.createElement("style");
1066
918
  style.textContent = `
919
+ ${SELECT_PICKER_STYLES}
1067
920
  .spotpatch-ask-mode { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; margin-bottom: 14px; padding: 4px; border: 1px solid var(--spotpatch-border-subtle); border-radius: 10px; background: #0a0a0e; }
1068
921
  .spotpatch-ask-mode button { min-height: 34px; border: 0; border-radius: 7px; color: var(--spotpatch-text-secondary); background: transparent; cursor: pointer; font-size: 12px; font-weight: 700; }
1069
922
  .spotpatch-ask-mode button[aria-selected="true"] { color: #f7f7ff; background: linear-gradient(135deg, rgb(139 123 255 / 25%), rgb(82 168 255 / 14%)); box-shadow: inset 0 0 0 1px rgb(139 123 255 / 24%); }
@@ -1076,20 +929,6 @@ function createStyles(document) {
1076
929
  .spotpatch-ask-suggestions { display: flex; flex-wrap: wrap; gap: 6px; }
1077
930
  .spotpatch-ask-suggestions button { border: 1px solid var(--spotpatch-border-subtle); border-radius: 999px; padding: 5px 9px; color: var(--spotpatch-text-secondary); background: rgb(255 255 255 / 2%); cursor: pointer; font-size: 10.5px; }
1078
931
  .spotpatch-ask-suggestions button:hover { border-color: rgb(139 123 255 / 42%); color: #fff; }
1079
- .spotpatch-ask-executor-picker { display: grid; gap: 5px; min-width: 0; }
1080
- .spotpatch-ask-executor { box-sizing: border-box; display: flex; width: 100%; min-height: 38px; align-items: center; justify-content: space-between; gap: 10px; border: 1px solid var(--spotpatch-border); border-radius: 9px; padding: 0 11px; overflow: hidden; color: var(--spotpatch-text); background: var(--spotpatch-bg-input); cursor: pointer; font: inherit; outline: none; text-align: left; }
1081
- .spotpatch-ask-executor > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1082
- .spotpatch-ask-executor[data-expandable="true"]::after { width: 7px; height: 7px; flex: 0 0 auto; border-right: 1.5px solid #8f96a3; border-bottom: 1.5px solid #8f96a3; content: ""; transform: translateY(-2px) rotate(45deg); }
1083
- .spotpatch-ask-executor[aria-expanded="true"]::after { transform: translateY(2px) rotate(225deg); }
1084
- .spotpatch-ask-executor:focus-visible { border-color: rgb(139 123 255 / 68%); box-shadow: 0 0 0 3px rgb(139 123 255 / 12%); }
1085
- .spotpatch-ask-executor:disabled { color: var(--spotpatch-text); background: rgb(255 255 255 / 3%); cursor: default; opacity: 1; }
1086
- .spotpatch-ask-executor[data-empty="true"]:disabled { color: var(--spotpatch-text-muted); }
1087
- .spotpatch-ask-executor-menu { display: grid; gap: 3px; border: 1px solid var(--spotpatch-border); border-radius: 9px; padding: 4px; background: var(--spotpatch-bg-input); max-height: 224px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: thin; box-shadow: 0 10px 28px rgb(0 0 0 / 28%); }
1088
- .spotpatch-ask-executor-menu[hidden] { display: none; }
1089
- .spotpatch-ask-executor-option { box-sizing: border-box; min-height: 34px; border: 0; border-radius: 6px; padding: 7px 9px; color: var(--spotpatch-text-secondary); background: transparent; cursor: pointer; font: inherit; text-align: left; overflow-wrap: anywhere; }
1090
- .spotpatch-ask-executor-option[data-active="true"], .spotpatch-ask-executor-option:hover { color: #fff; background: rgb(139 123 255 / 13%); outline: none; }
1091
- .spotpatch-ask-executor-option[aria-selected="true"] { color: #f4f1ff; background: rgb(139 123 255 / 20%); }
1092
- .spotpatch-ask-executor-native { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; border: 0; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
1093
932
  .spotpatch-ask-executor-status { margin: 0; border-left: 2px solid var(--spotpatch-warning); padding-left: 8px; color: #d8b66c; font-size: 10.5px; line-height: 1.45; }
1094
933
  .spotpatch-ask-safety { display: grid; gap: 8px; border: 1px solid rgb(82 168 255 / 16%); border-radius: 10px; padding: 10px 11px; background: rgb(82 168 255 / 4%); }
1095
934
  .spotpatch-ask-data { display: flex; justify-content: space-between; gap: 10px; color: #aab3c2; font-size: 10.5px; }
@@ -1188,13 +1027,13 @@ function createContextualAskPanel(input) {
1188
1027
  const executorField = createMarkedElement(document, "div");
1189
1028
  executorField.className = "spotpatch-ask-field";
1190
1029
  const executorLabel = createMarkedElement(document, "label");
1191
- const executorPicker = createAskPicker(document, input.onViewChange);
1030
+ const executorPicker = createSelectPicker(document, input.onViewChange);
1192
1031
  const executorSelect = executorPicker.select;
1193
1032
  executorLabel.htmlFor = executorPicker.trigger.id;
1194
1033
  const modelField = createMarkedElement(document, "div");
1195
1034
  modelField.className = "spotpatch-ask-field";
1196
1035
  const modelLabel = createMarkedElement(document, "label");
1197
- const modelPicker = createAskPicker(document, input.onViewChange);
1036
+ const modelPicker = createSelectPicker(document, input.onViewChange);
1198
1037
  modelLabel.htmlFor = modelPicker.trigger.id;
1199
1038
  modelField.append(modelLabel, modelPicker.root);
1200
1039
  let modelExecutorId;
@@ -1398,7 +1237,7 @@ function createContextualAskPanel(input) {
1398
1237
  for (const executor of readyExecutors) {
1399
1238
  const option = document.createElement("option");
1400
1239
  option.value = executor.executorId;
1401
- option.textContent = executor.models === void 0 ? `${executor.label} \xB7 ${executor.effectiveModelLabel}` : executor.label;
1240
+ option.textContent = executor.models === void 0 ? `${executor.label} · ${executor.effectiveModelLabel}` : executor.label;
1402
1241
  executorSelect.append(option);
1403
1242
  }
1404
1243
  if (readyExecutors.length === 0) {
@@ -1435,7 +1274,7 @@ function createContextualAskPanel(input) {
1435
1274
  expandedHeadline: messages.status[snapshot.status],
1436
1275
  action: messages.safety,
1437
1276
  expandedAction: messages.safety,
1438
- meta: `${snapshot.executor.label} \xB7 ${snapshot.executor.modelLabel}`,
1277
+ meta: `${snapshot.executor.label} · ${snapshot.executor.modelLabel}`,
1439
1278
  recentActivities: Object.freeze([]),
1440
1279
  startedAt: snapshot.createdAt
1441
1280
  });
@@ -1462,7 +1301,7 @@ function createContextualAskPanel(input) {
1462
1301
  expandedHeadline: messages.status.running,
1463
1302
  action: activityLabel,
1464
1303
  expandedAction: activityLabel,
1465
- meta: `${currentJob.executor.label} \xB7 ${currentJob.executor.modelLabel}`,
1304
+ meta: `${currentJob.executor.label} · ${currentJob.executor.modelLabel}`,
1466
1305
  activity: {
1467
1306
  key: `ask:${String(event.sequence)}`,
1468
1307
  kind: "read",