cursor-feedback 2.0.5 → 2.1.1

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.
@@ -10,7 +10,9 @@
10
10
  remove: '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
11
11
  file: '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z"/><path d="M14 2v4a2 2 0 0 0 2 2h4"/>',
12
12
  folder: '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
13
- code: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>'
13
+ code: '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
14
+ pencil: '<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/>',
15
+ check: '<path d="M20 6 9 17l-5-5"/>'
14
16
  };
15
17
  function svg(paths, cls) {
16
18
  const el = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
@@ -72,6 +74,8 @@
72
74
  const timeoutWrap = document.getElementById('timeoutWrap');
73
75
  const timeoutBar = document.getElementById('timeoutBar');
74
76
  const timeoutInfo = document.getElementById('timeoutInfo');
77
+ const pauseBtn = document.getElementById('pauseBtn');
78
+ const quickReplies = document.getElementById('quickReplies');
75
79
  const toggleKeyModeBtn = document.getElementById('toggleKeyModeBtn');
76
80
  const dropOverlay = document.getElementById('dropOverlay');
77
81
  const summaryCard = document.getElementById('summaryCard');
@@ -96,6 +100,11 @@
96
100
  const feishuAckToggle = document.getElementById('feishuAckToggle');
97
101
  const osNotifySub = document.getElementById('osNotifySub');
98
102
  const feishuAckSub = document.getElementById('feishuAckSub');
103
+ const notifyTestBtn = document.getElementById('notifyTestBtn');
104
+ const notifyTestHint = document.getElementById('notifyTestHint');
105
+ const historyBtn = document.getElementById('historyBtn');
106
+ const historyPopup = document.getElementById('historyPopup');
107
+ const toastEl = document.getElementById('toast');
99
108
 
100
109
  let uploadedImages = [];
101
110
  let attachedFiles = [];
@@ -106,6 +115,11 @@
106
115
  let requestTimeout = 300;
107
116
  let countdownInterval = null;
108
117
  let submitFallbackTimer = null;
118
+ // 倒计时暂停态:真相源在 MCP server(HTTP 指令暂停/恢复真实计时器),
119
+ // 这里只维护显示用的锚点,poll 每秒回传的 pauseState 会持续校准,不怕 webview 重建。
120
+ let cdPaused = false;
121
+ let cdRemainingMs = 0;
122
+ let cdAnchor = 0;
109
123
 
110
124
  // ---------- Language switch ----------
111
125
  langSwitchBtn.addEventListener('click', () => {
@@ -226,6 +240,15 @@
226
240
  vscode.postMessage({ type: 'toggleFeishuAck', payload: { enabled: feishuAckToggle.checked } });
227
241
  });
228
242
 
243
+ // 发送测试通知:一键验证系统通知链路通不通(权限被拒时收不到,配合上方排查提示定位)
244
+ let notifyTestHintTimer = null;
245
+ notifyTestBtn.addEventListener('click', () => {
246
+ vscode.postMessage({ type: 'testNotification' });
247
+ notifyTestHint.textContent = i18n.notifyTestSentHint || 'Sent — check your system notifications.';
248
+ if (notifyTestHintTimer) clearTimeout(notifyTestHintTimer);
249
+ notifyTestHintTimer = setTimeout(() => { notifyTestHint.textContent = ''; }, 8000);
250
+ });
251
+
229
252
  // 子开关受主开关约束:主关时子置灰禁用
230
253
  function syncSubSwitchDisabled() {
231
254
  const osOn = systemNotifyToggle.checked;
@@ -710,29 +733,312 @@
710
733
  if (!document.hidden) restoreSummaryHeight();
711
734
  });
712
735
 
736
+ // ---------- Quick replies (一键发送的快捷短语,可编辑,存 localStorage) ----------
737
+ const QUICK_REPLY_KEY = 'cursorFeedback_quickReplies';
738
+ let quickReplyEditing = false;
739
+
740
+ function defaultQuickReplies() {
741
+ return [
742
+ i18n.quickReplyDefault1 || 'Keep waiting for my feedback',
743
+ i18n.quickReplyDefault2 || 'End the task'
744
+ ];
745
+ }
746
+
747
+ function loadQuickReplies() {
748
+ try {
749
+ const raw = localStorage.getItem(QUICK_REPLY_KEY);
750
+ if (raw) {
751
+ const arr = JSON.parse(raw);
752
+ if (Array.isArray(arr)) {
753
+ return arr.filter((s) => typeof s === 'string' && s.trim()).slice(0, 20);
754
+ }
755
+ }
756
+ } catch (e) { /* ignore */ }
757
+ return defaultQuickReplies();
758
+ }
759
+
760
+ function saveQuickReplies(list) {
761
+ try { localStorage.setItem(QUICK_REPLY_KEY, JSON.stringify(list)); } catch (e) { /* ignore */ }
762
+ }
763
+
764
+ // 点击快捷短语=立即提交。已有草稿时把短语追加到末尾一起发,不悄悄丢弃用户已输入的内容
765
+ function sendQuickReply(phrase) {
766
+ if (!currentRequestId || submitBtn.disabled) return;
767
+ const cur = feedbackInput.value.trim();
768
+ feedbackInput.value = cur ? cur + '\n' + phrase : phrase;
769
+ saveDraft();
770
+ updateCharCount();
771
+ submitFeedback();
772
+ }
773
+
774
+ // 编辑模式下把「添加输入框」里未回车的内容也收进列表(防止用户直接点完成而丢词)
775
+ function commitQuickReplyDraft() {
776
+ const inp = quickReplies.querySelector('.quick-add-input');
777
+ const v = inp && inp.value.trim();
778
+ if (!v) return;
779
+ const next = loadQuickReplies();
780
+ if (!next.includes(v)) {
781
+ next.push(v);
782
+ saveQuickReplies(next);
783
+ }
784
+ }
785
+
786
+ function renderQuickReplies() {
787
+ if (!quickReplies) return;
788
+ const list = loadQuickReplies();
789
+ quickReplies.innerHTML = '';
790
+
791
+ list.forEach((phrase, idx) => {
792
+ // 编辑态用 span:chip 内含删除按钮,button 嵌 button 不合法
793
+ const chip = document.createElement(quickReplyEditing ? 'span' : 'button');
794
+ if (!quickReplyEditing) chip.type = 'button';
795
+ chip.className = 'quick-chip' + (quickReplyEditing ? ' is-editing' : '');
796
+ chip.title = quickReplyEditing ? phrase : ((i18n.quickReplySendTip || 'Click to send') + ' · ' + phrase);
797
+
798
+ const label = document.createElement('span');
799
+ label.className = 'quick-chip__label';
800
+ label.textContent = phrase;
801
+ chip.appendChild(label);
802
+
803
+ if (quickReplyEditing) {
804
+ chip.appendChild(makeRemoveBtn(() => {
805
+ const next = loadQuickReplies();
806
+ next.splice(idx, 1);
807
+ saveQuickReplies(next);
808
+ renderQuickReplies();
809
+ }));
810
+ } else {
811
+ chip.addEventListener('click', () => sendQuickReply(phrase));
812
+ }
813
+ quickReplies.appendChild(chip);
814
+ });
815
+
816
+ if (quickReplyEditing) {
817
+ const input = document.createElement('input');
818
+ input.type = 'text';
819
+ input.className = 'quick-add-input';
820
+ input.placeholder = i18n.quickReplyAddPlaceholder || 'Type a phrase and press Enter';
821
+ input.addEventListener('keydown', (e) => {
822
+ if (e.isComposing) return;
823
+ if (e.key === 'Enter') {
824
+ e.preventDefault();
825
+ const v = input.value.trim();
826
+ if (!v) return;
827
+ const next = loadQuickReplies();
828
+ if (!next.includes(v)) {
829
+ next.push(v);
830
+ saveQuickReplies(next);
831
+ }
832
+ renderQuickReplies();
833
+ const ni = quickReplies.querySelector('.quick-add-input');
834
+ if (ni) ni.focus();
835
+ } else if (e.key === 'Escape') {
836
+ quickReplyEditing = false;
837
+ renderQuickReplies();
838
+ }
839
+ });
840
+ quickReplies.appendChild(input);
841
+ }
842
+
843
+ const editBtn = document.createElement('button');
844
+ editBtn.type = 'button';
845
+ editBtn.className = 'iconbtn quick-edit-btn' + (quickReplyEditing ? ' is-on' : '');
846
+ const tip = quickReplyEditing
847
+ ? (i18n.quickReplyDone || 'Done editing')
848
+ : (i18n.quickReplyEdit || 'Edit quick replies');
849
+ editBtn.setAttribute('aria-label', tip);
850
+ editBtn.setAttribute('data-tip', tip);
851
+ editBtn.appendChild(svg(quickReplyEditing ? ICONS.check : ICONS.pencil));
852
+ editBtn.addEventListener('click', () => {
853
+ if (quickReplyEditing) commitQuickReplyDraft();
854
+ quickReplyEditing = !quickReplyEditing;
855
+ renderQuickReplies();
856
+ if (quickReplyEditing) {
857
+ const ni = quickReplies.querySelector('.quick-add-input');
858
+ if (ni) ni.focus();
859
+ }
860
+ });
861
+ quickReplies.appendChild(editBtn);
862
+ }
863
+ renderQuickReplies();
864
+
865
+ // ---------- Toast (提交结果轻提示) ----------
866
+ let toastTimer = null;
867
+ function showToast(text) {
868
+ toastEl.textContent = text;
869
+ toastEl.classList.remove('hidden');
870
+ // 重触发进入动画
871
+ toastEl.classList.remove('is-in');
872
+ void toastEl.offsetWidth;
873
+ toastEl.classList.add('is-in');
874
+ if (toastTimer) clearTimeout(toastTimer);
875
+ toastTimer = setTimeout(() => {
876
+ toastEl.classList.remove('is-in');
877
+ toastTimer = setTimeout(() => toastEl.classList.add('hidden'), 300);
878
+ }, 3500);
879
+ }
880
+
881
+ // ---------- Feedback history (最近提交的反馈,点击回填) ----------
882
+ // 只存文本不存图片(图片 base64 太大会撑爆 localStorage);上限 20 条
883
+ const HISTORY_KEY = 'cursorFeedback_history';
884
+ const HISTORY_MAX = 20;
885
+ let historyOpen = false;
886
+ // 提交发出时暂存文本,等 extension 确认成功(feedbackSubmitted)后才写入历史,
887
+ // 避免提交失败的内容混进历史
888
+ let pendingHistoryText = '';
889
+
890
+ function loadHistory() {
891
+ try {
892
+ const arr = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]');
893
+ if (Array.isArray(arr)) {
894
+ return arr.filter((e) => e && typeof e.text === 'string' && e.text.trim());
895
+ }
896
+ } catch (e) { /* ignore */ }
897
+ return [];
898
+ }
899
+
900
+ function pushHistory(text) {
901
+ const t = (text || '').trim();
902
+ if (!t) return; // 纯图片/附件提交没有文本,不记
903
+ let list = loadHistory();
904
+ // 与最近一条相同(如快捷短语连点)就只刷新时间,不堆重复条目
905
+ list = list.filter((e) => e.text !== t);
906
+ list.unshift({ text: t, at: Date.now() });
907
+ if (list.length > HISTORY_MAX) list = list.slice(0, HISTORY_MAX);
908
+ try { localStorage.setItem(HISTORY_KEY, JSON.stringify(list)); } catch (e) { /* ignore */ }
909
+ }
910
+
911
+ function formatHistoryTime(at) {
912
+ const d = new Date(at);
913
+ const pad = (n) => String(n).padStart(2, '0');
914
+ const hm = pad(d.getHours()) + ':' + pad(d.getMinutes());
915
+ const now = new Date();
916
+ const sameDay = d.getFullYear() === now.getFullYear() &&
917
+ d.getMonth() === now.getMonth() && d.getDate() === now.getDate();
918
+ return sameDay ? hm : (pad(d.getMonth() + 1) + '-' + pad(d.getDate()) + ' ' + hm);
919
+ }
920
+
921
+ function positionHistoryPopup() {
922
+ const r = historyBtn.getBoundingClientRect();
923
+ historyPopup.style.left = '8px';
924
+ historyPopup.style.right = '8px';
925
+ historyPopup.style.bottom = (window.innerHeight - r.top + 6) + 'px';
926
+ historyPopup.style.maxHeight = Math.min(280, r.top - 12) + 'px';
927
+ }
928
+
929
+ function renderHistoryPopup() {
930
+ const list = loadHistory();
931
+ historyPopup.innerHTML = '';
932
+ if (list.length === 0) {
933
+ const empty = document.createElement('div');
934
+ empty.className = 'mention-empty';
935
+ empty.textContent = i18n.historyEmpty || 'No history yet';
936
+ historyPopup.appendChild(empty);
937
+ return;
938
+ }
939
+ list.forEach((entry) => {
940
+ const item = document.createElement('div');
941
+ item.className = 'history-item';
942
+ item.title = entry.text;
943
+
944
+ const text = document.createElement('span');
945
+ text.className = 'history-item__text';
946
+ text.textContent = entry.text.replace(/\s+/g, ' ');
947
+ item.appendChild(text);
948
+
949
+ const time = document.createElement('span');
950
+ time.className = 'history-item__time';
951
+ time.textContent = formatHistoryTime(entry.at);
952
+ item.appendChild(time);
953
+
954
+ item.appendChild(makeRemoveBtn((ev) => {
955
+ ev.stopPropagation();
956
+ const next = loadHistory().filter((e) => !(e.text === entry.text && e.at === entry.at));
957
+ try { localStorage.setItem(HISTORY_KEY, JSON.stringify(next)); } catch (e) { /* ignore */ }
958
+ renderHistoryPopup();
959
+ }, 'history-item__remove'));
960
+
961
+ // 点击回填:已有草稿则换行追加,不覆盖用户正在写的内容
962
+ item.addEventListener('click', () => {
963
+ const cur = feedbackInput.value;
964
+ feedbackInput.value = cur.trim() ? cur.replace(/\n?$/, '\n') + entry.text : entry.text;
965
+ saveDraft();
966
+ updateCharCount();
967
+ closeHistoryPopup();
968
+ feedbackInput.focus();
969
+ });
970
+ historyPopup.appendChild(item);
971
+ });
972
+ }
973
+
974
+ function openHistoryPopup() {
975
+ renderHistoryPopup();
976
+ positionHistoryPopup();
977
+ historyPopup.classList.remove('hidden');
978
+ historyOpen = true;
979
+ }
980
+ function closeHistoryPopup() {
981
+ historyPopup.classList.add('hidden');
982
+ historyOpen = false;
983
+ }
984
+ historyBtn.addEventListener('click', (e) => {
985
+ e.stopPropagation();
986
+ if (historyOpen) closeHistoryPopup();
987
+ else openHistoryPopup();
988
+ });
989
+ document.addEventListener('click', (e) => {
990
+ if (historyOpen && !historyPopup.contains(e.target)) closeHistoryPopup();
991
+ });
992
+ document.addEventListener('keydown', (e) => {
993
+ if (e.key === 'Escape' && historyOpen) closeHistoryPopup();
994
+ });
995
+
713
996
  // ---------- Countdown + progress bar ----------
997
+ function updatePauseUI() {
998
+ timeoutWrap.classList.toggle('is-paused', cdPaused);
999
+ const tip = cdPaused
1000
+ ? (i18n.resumeCountdown || 'Resume countdown')
1001
+ : (i18n.pauseCountdown || 'Pause countdown');
1002
+ pauseBtn.setAttribute('data-tip', tip);
1003
+ pauseBtn.setAttribute('aria-label', tip);
1004
+ }
1005
+
1006
+ function currentRemainingMs() {
1007
+ return cdPaused ? cdRemainingMs : Math.max(0, cdRemainingMs - (Date.now() - cdAnchor));
1008
+ }
1009
+
714
1010
  function updateCountdown() {
715
- if (!requestTimestamp || !requestTimeout) return;
716
- const elapsed = Math.floor((Date.now() - requestTimestamp) / 1000);
717
- const remaining = Math.max(0, requestTimeout - elapsed);
1011
+ if (!requestTimeout) return;
1012
+ const totalMs = requestTimeout * 1000;
1013
+ const remainingMs = currentRemainingMs();
1014
+ const remaining = Math.max(0, Math.round(remainingMs / 1000));
718
1015
  const minutes = Math.floor(remaining / 60);
719
1016
  const seconds = remaining % 60;
720
- const ratio = Math.max(0, Math.min(1, remaining / requestTimeout));
1017
+ const ratio = Math.max(0, Math.min(1, remainingMs / totalMs));
721
1018
 
722
1019
  timeoutBar.style.width = (ratio * 100) + '%';
723
- timeoutWrap.classList.toggle('is-warning', ratio <= 0.25 && ratio > 0.1);
724
- timeoutWrap.classList.toggle('is-danger', ratio <= 0.1);
1020
+ timeoutWrap.classList.toggle('is-warning', !cdPaused && ratio <= 0.25 && ratio > 0.1);
1021
+ timeoutWrap.classList.toggle('is-danger', !cdPaused && ratio <= 0.1);
725
1022
 
726
- const label = i18n.remainingTime || 'Remaining time';
1023
+ const label = cdPaused ? (i18n.pausedLabel || 'Paused') : (i18n.remainingTime || 'Remaining time');
727
1024
  timeoutInfo.textContent = label + ' ' + minutes + ':' + seconds.toString().padStart(2, '0');
728
1025
 
729
- if (remaining <= 0) {
1026
+ if (!cdPaused && remainingMs <= 0) {
730
1027
  clearInterval(countdownInterval);
731
1028
  countdownInterval = null;
732
1029
  timeoutInfo.textContent = i18n.timeout || 'Timeout';
733
1030
  }
734
1031
  }
735
1032
 
1033
+ // 暂停/恢复:发给插件 → HTTP 通知 MCP server 冻结/重排真实计时器;UI 待 server 确认后刷新
1034
+ pauseBtn.addEventListener('click', () => {
1035
+ if (!currentRequestId) return;
1036
+ vscode.postMessage({
1037
+ type: 'togglePause',
1038
+ payload: { requestId: currentRequestId, paused: !cdPaused }
1039
+ });
1040
+ });
1041
+
736
1042
  // ---------- Submit ----------
737
1043
  function setSubmitting(on) {
738
1044
  if (on) {
@@ -778,11 +1084,13 @@
778
1084
  if (!currentRequestId || submitBtn.disabled) return;
779
1085
 
780
1086
  setSubmitting(true);
1087
+ // 先暂存本次文本,等 extension 确认成功后写入历史(提交失败不入历史)
1088
+ pendingHistoryText = buildFeedbackText();
781
1089
  vscode.postMessage({
782
1090
  type: 'submitFeedback',
783
1091
  payload: {
784
1092
  requestId: currentRequestId,
785
- interactive_feedback: buildFeedbackText(),
1093
+ interactive_feedback: pendingHistoryText,
786
1094
  images: uploadedImages.map(im => ({ name: im.name, data: im.dataUrl.split(',')[1], size: im.size })),
787
1095
  attachedFiles: attachedFiles,
788
1096
  project_directory: currentProjectDir
@@ -826,6 +1134,10 @@
826
1134
  currentProjectDir = message.payload.projectDir;
827
1135
  requestTimestamp = message.payload.timestamp;
828
1136
  requestTimeout = message.payload.timeout;
1137
+ cdPaused = false;
1138
+ cdRemainingMs = Math.max(0, requestTimeout * 1000 - (Date.now() - requestTimestamp));
1139
+ cdAnchor = Date.now();
1140
+ updatePauseUI();
829
1141
  summaryContent.innerHTML = renderMarkdown(message.payload.summary);
830
1142
  highlightCodeBlocks(summaryContent);
831
1143
  summaryContent.scrollTop = 0;
@@ -848,6 +1160,8 @@
848
1160
  submitBar.classList.add('hidden');
849
1161
  waitingStatus.classList.remove('hidden');
850
1162
  timeoutWrap.hidden = true;
1163
+ cdPaused = false;
1164
+ updatePauseUI();
851
1165
  break;
852
1166
 
853
1167
  case 'serverStatus':
@@ -919,9 +1233,36 @@
919
1233
  updateAutoRetryUI(!!message.payload.enabled);
920
1234
  break;
921
1235
 
1236
+ case 'pauseState': {
1237
+ // server 是暂停态与剩余时间的真相源:按钮确认与每秒 poll 都走这里持续校准,
1238
+ // webview 重建后也能恢复正确的暂停显示
1239
+ const p = message.payload || {};
1240
+ if (!currentRequestId || p.requestId !== currentRequestId) break;
1241
+ cdPaused = !!p.paused;
1242
+ if (typeof p.remainingMs === 'number') {
1243
+ cdRemainingMs = Math.max(0, p.remainingMs);
1244
+ cdAnchor = Date.now();
1245
+ }
1246
+ updatePauseUI();
1247
+ if (!countdownInterval && (cdPaused || cdRemainingMs > 0)) {
1248
+ countdownInterval = setInterval(updateCountdown, 1000);
1249
+ }
1250
+ updateCountdown();
1251
+ break;
1252
+ }
1253
+
922
1254
  case 'feishuState':
923
1255
  updateFeishuUI(message.payload);
924
1256
  break;
1257
+
1258
+ case 'feedbackSubmitted':
1259
+ // 提交已被 server 确认:写入历史 + 轻提示(queued = 撞上超时空窗、暂存待下一轮送达)
1260
+ pushHistory(pendingHistoryText);
1261
+ pendingHistoryText = '';
1262
+ showToast(message.payload && message.payload.queued
1263
+ ? (i18n.toastQueued || 'Queued — will be delivered to AI next round')
1264
+ : (i18n.toastSubmitted || 'Feedback sent'));
1265
+ break;
925
1266
  }
926
1267
  });
927
1268
 
@@ -664,6 +664,144 @@ body.vscode-light .hljs-regexp { color: #811f3f; }
664
664
  }
665
665
  .ref-chip .chip-remove:hover { opacity: 1; background: transparent; color: var(--vscode-errorForeground, #e51400); }
666
666
 
667
+ /* ---------- Quick replies (一键发送短语) ---------- */
668
+ .quick-replies {
669
+ flex: none;
670
+ display: flex;
671
+ flex-wrap: wrap;
672
+ align-items: center;
673
+ gap: 4px;
674
+ margin-bottom: 6px;
675
+ }
676
+ .quick-chip {
677
+ display: inline-flex;
678
+ align-items: center;
679
+ gap: 4px;
680
+ max-width: 100%;
681
+ padding: 3px 10px;
682
+ font-family: inherit;
683
+ font-size: 11px;
684
+ line-height: 1.5;
685
+ border-radius: 999px;
686
+ background: color-mix(in srgb, var(--accent) 10%, var(--surface-sunken));
687
+ border: 1px solid color-mix(in srgb, var(--accent) 30%, var(--line-soft));
688
+ color: var(--vscode-foreground);
689
+ cursor: pointer;
690
+ transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
691
+ transform var(--dur) var(--ease);
692
+ }
693
+ .quick-chip:hover {
694
+ background: color-mix(in srgb, var(--accent) 20%, var(--surface-sunken));
695
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--line-soft));
696
+ transform: translateY(-1px);
697
+ }
698
+ .quick-chip:active { transform: translateY(0); }
699
+ .quick-chip.is-editing { cursor: default; }
700
+ .quick-chip.is-editing:hover { transform: none; }
701
+ .quick-chip__label {
702
+ overflow: hidden;
703
+ text-overflow: ellipsis;
704
+ white-space: nowrap;
705
+ max-width: 180px;
706
+ }
707
+ .quick-chip .chip-remove {
708
+ position: static;
709
+ width: 14px;
710
+ height: 14px;
711
+ opacity: 0.6;
712
+ background: transparent;
713
+ color: var(--muted);
714
+ }
715
+ .quick-chip .chip-remove:hover { opacity: 1; background: transparent; color: var(--vscode-errorForeground, #e51400); }
716
+ .quick-edit-btn { width: 22px; height: 22px; }
717
+ .quick-edit-btn svg { width: 12px; height: 12px; }
718
+ .quick-add-input {
719
+ width: 150px;
720
+ padding: 3px 10px;
721
+ font-family: inherit;
722
+ font-size: 11px;
723
+ line-height: 1.5;
724
+ color: var(--vscode-input-foreground, var(--vscode-foreground));
725
+ background: var(--vscode-input-background, var(--surface-sunken));
726
+ border: 1px dashed var(--line);
727
+ border-radius: 999px;
728
+ }
729
+ .quick-add-input:focus { outline: none; border-color: var(--accent); border-style: solid; }
730
+ .quick-add-input::placeholder { color: var(--vscode-input-placeholderForeground, var(--muted)); opacity: 0.8; }
731
+
732
+ /* ---------- Toast (提交结果轻提示) ---------- */
733
+ .toast {
734
+ position: fixed;
735
+ top: 40px;
736
+ left: 50%;
737
+ z-index: 60;
738
+ max-width: calc(100% - 32px);
739
+ padding: 6px 14px;
740
+ font-size: 12px;
741
+ line-height: 1.5;
742
+ color: var(--vscode-foreground);
743
+ background: var(--surface-raised, var(--vscode-editorWidget-background, #252526));
744
+ border: 1px solid color-mix(in srgb, var(--vscode-charts-green, #3fb950) 45%, var(--line-soft));
745
+ border-left: 3px solid var(--vscode-charts-green, #3fb950);
746
+ border-radius: 6px;
747
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
748
+ opacity: 0;
749
+ transform: translate(-50%, -6px);
750
+ transition: opacity 0.25s ease, transform 0.25s ease;
751
+ pointer-events: none;
752
+ }
753
+ .toast.is-in { opacity: 1; transform: translate(-50%, 0); }
754
+ .toast.hidden { display: none; }
755
+
756
+ /* ---------- Feedback history (反馈历史浮层) ---------- */
757
+ .history-popup {
758
+ position: fixed;
759
+ z-index: 50;
760
+ overflow-y: auto;
761
+ background: var(--surface-raised, var(--vscode-editorWidget-background, #252526));
762
+ border: 1px solid var(--line);
763
+ border-radius: 8px;
764
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.3);
765
+ padding: 4px;
766
+ }
767
+ .history-popup.hidden { display: none; }
768
+ .history-item {
769
+ display: flex;
770
+ align-items: center;
771
+ gap: 8px;
772
+ padding: 6px 8px;
773
+ border-radius: 5px;
774
+ cursor: pointer;
775
+ font-size: 12px;
776
+ }
777
+ .history-item:hover { background: color-mix(in srgb, var(--accent) 12%, transparent); }
778
+ .history-item__text {
779
+ flex: 1;
780
+ overflow: hidden;
781
+ text-overflow: ellipsis;
782
+ white-space: nowrap;
783
+ color: var(--vscode-foreground);
784
+ }
785
+ .history-item__time {
786
+ flex: none;
787
+ font-size: 10px;
788
+ color: var(--muted);
789
+ }
790
+ .history-item .chip-remove.history-item__remove {
791
+ position: static;
792
+ flex: none;
793
+ width: 14px;
794
+ height: 14px;
795
+ opacity: 0.5;
796
+ background: transparent;
797
+ color: var(--muted);
798
+ }
799
+ .history-item .chip-remove.history-item__remove:hover {
800
+ opacity: 1;
801
+ background: transparent;
802
+ color: var(--vscode-errorForeground, #e51400);
803
+ }
804
+
667
805
  /* ---------- Countdown ---------- */
668
806
  .countdown {
669
807
  display: flex;
@@ -671,6 +809,18 @@ body.vscode-light .hljs-regexp { color: #811f3f; }
671
809
  gap: var(--sp-2);
672
810
  }
673
811
  .countdown[hidden] { display: none; }
812
+ .countdown__pause {
813
+ width: 22px;
814
+ height: 22px;
815
+ flex: none;
816
+ }
817
+ .countdown__pause svg { width: 12px; height: 12px; }
818
+ .countdown__play-icon { display: none; }
819
+ .countdown.is-paused .countdown__pause-icon { display: none; }
820
+ .countdown.is-paused .countdown__play-icon { display: block; }
821
+ .countdown.is-paused .countdown__pause { color: var(--accent); opacity: 1; }
822
+ .countdown.is-paused .countdown__bar { background: var(--muted); }
823
+ .countdown.is-paused .countdown__text { color: var(--muted); }
674
824
  .countdown__track {
675
825
  flex: 1;
676
826
  height: 4px;
@@ -990,6 +1140,19 @@ body.vscode-light .hljs-regexp { color: #811f3f; }
990
1140
  }
991
1141
  .feishu-subitem.is-disabled { opacity: 0.45; pointer-events: none; }
992
1142
  .feishu-modal__desc--sub { font-size: 11px; opacity: 0.9; }
1143
+ /* 通知排查提示:linux 等平台文案为空时整行隐藏 */
1144
+ .feishu-troubleshoot { opacity: 0.7; }
1145
+ .feishu-troubleshoot:empty { display: none; }
1146
+ .feishu-test-row {
1147
+ display: flex;
1148
+ align-items: baseline;
1149
+ gap: 8px;
1150
+ flex-wrap: wrap;
1151
+ }
1152
+ .feishu-test-hint {
1153
+ font-size: 11px;
1154
+ color: var(--vscode-charts-green, #3fb950);
1155
+ }
993
1156
  .feishu-input-wrap { position: relative; display: flex; align-items: center; }
994
1157
  .feishu-input-wrap .feishu-input { width: 100%; padding-right: 34px; box-sizing: border-box; }
995
1158
  .feishu-eye {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "cursor-feedback",
3
3
  "displayName": "Cursor Feedback",
4
4
  "description": "One Cursor conversation, unlimited AI interactions - Save your monthly request quota! Interactive feedback loop for AI chat via MCP",
5
- "version": "2.0.5",
5
+ "version": "2.1.1",
6
6
  "icon": "icon.png",
7
7
  "author": "jianger666",
8
8
  "license": "MIT",