@x-otto/cli 0.0.1-alpha.50 → 0.0.1-alpha.52
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/dist/{auth-command-BYaFM76w.js → auth-command-DF_POx4L.js} +2 -2
- package/dist/{auth-command-BYaFM76w.js.map → auth-command-DF_POx4L.js.map} +1 -1
- package/dist/build-id-BmQPXMGN.js +3 -0
- package/dist/build-id-BmQPXMGN.js.map +1 -0
- package/dist/bundled-extensions/plugin-otto-wire-protocols/plugin-dist/meta.json +1 -1
- package/dist/{command-dispatcher-BLT8IO4K.js → command-dispatcher-FRiCoN16.js} +2 -2
- package/dist/{command-dispatcher-BLT8IO4K.js.map → command-dispatcher-FRiCoN16.js.map} +1 -1
- package/dist/{create-host-app-CbJ8ptv-.js → create-host-app-BgjEc81C.js} +2 -2
- package/dist/{create-host-app-CbJ8ptv-.js.map → create-host-app-BgjEc81C.js.map} +1 -1
- package/dist/{doctor-EndB-ycb.js → doctor-2AA2A9G7.js} +2 -2
- package/dist/{doctor-EndB-ycb.js.map → doctor-2AA2A9G7.js.map} +1 -1
- package/dist/{feedback-QcONRnO3.js → feedback-BsU8b1k9.js} +2 -2
- package/dist/{feedback-QcONRnO3.js.map → feedback-BsU8b1k9.js.map} +1 -1
- package/dist/{feedback-backend-methods-BNm38ddr.js → feedback-backend-methods-C2SzcIzm.js} +2 -2
- package/dist/{feedback-backend-methods-BNm38ddr.js.map → feedback-backend-methods-C2SzcIzm.js.map} +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/{interactive-schedule-Bv__2Kwc.js → interactive-schedule-aQEHfUKL.js} +2 -2
- package/dist/{interactive-schedule-Bv__2Kwc.js.map → interactive-schedule-aQEHfUKL.js.map} +1 -1
- package/dist/{mcp-server-Rr__aWO7.js → mcp-server-C2bw9J2i.js} +2 -2
- package/dist/{mcp-server-Rr__aWO7.js.map → mcp-server-C2bw9J2i.js.map} +1 -1
- package/dist/{parse-cli-Db8pF_Ya.js → parse-cli-C1r2cQ0K.js} +2 -2
- package/dist/{parse-cli-Db8pF_Ya.js.map → parse-cli-C1r2cQ0K.js.map} +1 -1
- package/dist/{serve-K6BOT5K_.js → serve-DHqx-Mab.js} +2 -2
- package/dist/{serve-K6BOT5K_.js.map → serve-DHqx-Mab.js.map} +1 -1
- package/dist/{version-DrCy32wR.js → version-CdzXY8m2.js} +2 -2
- package/dist/{version-DrCy32wR.js.map → version-CdzXY8m2.js.map} +1 -1
- package/package.json +14 -14
- package/dist/build-id-DjE6eVpQ.js +0 -3
- package/dist/build-id-DjE6eVpQ.js.map +0 -1
- package/dist/bundled-extensions/plugin-feedback/otto-plugin.json +0 -24
- package/dist/bundled-extensions/plugin-feedback/panels/feedback.tsx +0 -348
- package/dist/bundled-extensions/plugin-feedback/panels/panel-host-globals.d.ts +0 -67
- package/dist/bundled-extensions/plugin-feedback/panels-dist/feedback.mjs +0 -257
- package/dist/bundled-extensions/plugin-feedback/plugin-dist/meta.json +0 -1
- package/dist/bundled-extensions/plugin-feedback/plugin-dist/plugin.cjs +0 -11304
- package/dist/bundled-extensions/plugin-feedback/plugin.ts +0 -198
- package/dist/bundled-extensions/plugin-feedback/src/coding-adapter.ts +0 -183
- package/dist/bundled-extensions/plugin-feedback/src/coding-lister.ts +0 -26
- package/dist/bundled-extensions/plugin-feedback/src/github-lister.ts +0 -230
- package/dist/bundled-extensions/plugin-feedback/src/issue-aggregator.ts +0 -109
- package/dist/bundled-extensions/plugin-feedback/src/issue-submitter.ts +0 -241
- package/dist/bundled-extensions/plugin-feedback/src/platform-resolver.ts +0 -76
- package/dist/bundled-extensions/plugin-feedback/src/pulse-survey-sink.ts +0 -114
- package/dist/bundled-extensions/plugin-feedback/src/resolved-sink.ts +0 -163
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@x-otto/cli",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,26 +20,26 @@
|
|
|
20
20
|
"react": "^19.2.4",
|
|
21
21
|
"zod": "4.3.6",
|
|
22
22
|
"@x-otto/agent": "0.0.1-alpha.5",
|
|
23
|
-
"@x-otto/
|
|
23
|
+
"@x-otto/env": "0.1.0-alpha.7",
|
|
24
|
+
"@x-otto/coding": "0.0.1-alpha.21",
|
|
25
|
+
"@x-otto/hooks": "0.0.1-alpha.3",
|
|
24
26
|
"@x-otto/ai": "0.0.1-alpha.4",
|
|
25
|
-
"@x-otto/
|
|
26
|
-
"@x-otto/
|
|
27
|
-
"@x-otto/interchange": "0.1.0-alpha.5",
|
|
28
|
-
"@x-otto/memory": "0.0.1-alpha.3",
|
|
27
|
+
"@x-otto/interchange": "0.1.0-alpha.7",
|
|
28
|
+
"@x-otto/guard": "0.0.1-alpha.1",
|
|
29
29
|
"@x-otto/persistence": "0.0.1-alpha.4",
|
|
30
|
-
"@x-otto/
|
|
30
|
+
"@x-otto/memory": "0.0.1-alpha.3",
|
|
31
31
|
"@x-otto/remote-persistence-server": "0.0.1-alpha.4",
|
|
32
|
-
"@x-otto/
|
|
32
|
+
"@x-otto/plugin": "0.1.0-alpha.7",
|
|
33
33
|
"@x-otto/schedule": "0.0.1-alpha.3",
|
|
34
|
-
"@x-otto/
|
|
34
|
+
"@x-otto/runtime": "0.0.1-alpha.10",
|
|
35
35
|
"@x-otto/service": "0.0.1-alpha.3",
|
|
36
|
-
"@x-otto/setting": "0.0.1-alpha.4",
|
|
37
|
-
"@x-otto/shared": "0.1.0-alpha.6",
|
|
38
|
-
"@x-otto/hooks": "0.0.1-alpha.3",
|
|
39
36
|
"@x-otto/session": "0.0.1-alpha.5",
|
|
40
|
-
"@x-otto/
|
|
41
|
-
"@x-otto/
|
|
37
|
+
"@x-otto/mcp": "0.0.1-alpha.3",
|
|
38
|
+
"@x-otto/setting": "0.0.1-alpha.4",
|
|
39
|
+
"@x-otto/session-contract": "0.0.1-alpha.3",
|
|
40
|
+
"@x-otto/shared": "0.1.0-alpha.7",
|
|
42
41
|
"@x-otto/trace-view": "0.0.1-alpha.2",
|
|
42
|
+
"@x-otto/tui": "0.0.1-alpha.20",
|
|
43
43
|
"@x-otto/tools": "0.0.1-alpha.4"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"build-id-DjE6eVpQ.js","names":[],"sources":["../src/build-id.ts"],"sourcesContent":["// auto-generated by scripts/gen-build-id.mjs — DO NOT EDIT\nexport const BUILD_ID = 5427\nexport const BUILD_BRANCH = 'dev'\nexport const BUILD_HASH = '9ae7b15d9'\n"],"mappings":";AACA,MAAa,EAAW,KAEX,EAAa"}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "plugin-feedback",
|
|
3
|
-
"name": "Feedback",
|
|
4
|
-
"version": "0.1.0",
|
|
5
|
-
"description": "Feedback business contract (RFC-209): pulse survey recording (RFC-208) + GitHub issue submission (RFC-207) + dual-platform feedback panel (RFC-430). Renders no UI itself — TUI implements panels via `contributes.panels` and web-ui calls into this plugin's data/logic contract through FeedbackRegistry.",
|
|
6
|
-
"capabilities": ["feedback", "settings.read", "tui.renderer", "tui.command"],
|
|
7
|
-
"scripts": { "postinstall": "otto extension build" },
|
|
8
|
-
"contributes": {
|
|
9
|
-
"panels": [
|
|
10
|
-
{
|
|
11
|
-
"id": "feedback",
|
|
12
|
-
"title": "Feedback",
|
|
13
|
-
"entry": "panels-dist/feedback.mjs"
|
|
14
|
-
}
|
|
15
|
-
],
|
|
16
|
-
"slashCommands": [
|
|
17
|
-
{
|
|
18
|
-
"name": "feedback",
|
|
19
|
-
"description": "Report an issue or send feedback via GitHub (/feedback [--repo <owner/repo>])",
|
|
20
|
-
"group": "other"
|
|
21
|
-
}
|
|
22
|
-
]
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* feedback.tsx — RFC-430 M3:feedback 面板 Ink 组件。
|
|
3
|
-
*
|
|
4
|
-
* 面板走 plugin 的 `contributes.panels` 轴,由宿主 postinstall esbuild 编译为
|
|
5
|
-
* `panels-dist/feedback.mjs`。面板内数据/动作经 `usePanelBackend()` 桥接到宿主
|
|
6
|
-
* FeedbackRegistry(同 plugin-schedule 的 `usePanelBackend()` 模式)。
|
|
7
|
-
*
|
|
8
|
-
* 动作:n=新建 / m=标记已解决 / x=取消标记 / o=打开URL / r=刷新 / f=过滤 / q=关闭
|
|
9
|
-
*/
|
|
10
|
-
import React, { useState, useEffect, useCallback, useMemo } from 'react'
|
|
11
|
-
import { Box, Text, useInput } from 'ink'
|
|
12
|
-
import { GroupDivider } from '@x-otto/tui'
|
|
13
|
-
import type { PanelTheme, PanelFigures } from './panel-host-globals'
|
|
14
|
-
|
|
15
|
-
interface FeedbackIssueRow {
|
|
16
|
-
source: 'github' | 'coding'
|
|
17
|
-
platformIssueId: string
|
|
18
|
-
title: string
|
|
19
|
-
state: 'open' | 'closed'
|
|
20
|
-
url: string
|
|
21
|
-
author?: string
|
|
22
|
-
createdAt?: string
|
|
23
|
-
updatedAt?: string
|
|
24
|
-
kind: 'issue' | 'discussion'
|
|
25
|
-
excerpt?: string
|
|
26
|
-
labels?: string[]
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
interface FeedbackListResult {
|
|
30
|
-
items: FeedbackIssueRow[]
|
|
31
|
-
unavailable?: Partial<Record<'github' | 'coding', string>>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
interface FeedbackResolvedMark {
|
|
35
|
-
ts: number
|
|
36
|
-
source: 'github' | 'coding'
|
|
37
|
-
platformIssueId: string
|
|
38
|
-
url: string
|
|
39
|
-
title: string
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
type View = 'list' | 'new'
|
|
43
|
-
type FilterMode = 'all' | 'open' | 'resolved'
|
|
44
|
-
|
|
45
|
-
declare global {
|
|
46
|
-
function getTheme(): PanelTheme
|
|
47
|
-
function getFigures(): PanelFigures
|
|
48
|
-
function usePanelFocusClaimed(): boolean
|
|
49
|
-
function requestPanelClose(): void
|
|
50
|
-
function setPanelHint(hint: string | Array<[string, string]> | undefined): void
|
|
51
|
-
function t(key: string, opts?: Record<string, unknown>): string
|
|
52
|
-
interface PanelBackendHandle<T> {
|
|
53
|
-
state: T | undefined
|
|
54
|
-
status: 'connecting' | 'ready' | 'crashed' | 'disabled'
|
|
55
|
-
call: <P extends Record<string, unknown>, R>(method: string, params: P) => Promise<R>
|
|
56
|
-
}
|
|
57
|
-
function usePanelBackend<T>(): PanelBackendHandle<T>
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const POLL_MS = 15000
|
|
61
|
-
|
|
62
|
-
const sourceBadge = (source: 'github' | 'coding', theme: PanelTheme): React.ReactElement => {
|
|
63
|
-
const label = source === 'github' ? 'GH' : 'COD'
|
|
64
|
-
const color = source === 'github' ? theme.success : theme.warning
|
|
65
|
-
return (
|
|
66
|
-
<Text key="badge" color={color} bold>
|
|
67
|
-
[{label}]
|
|
68
|
-
</Text>
|
|
69
|
-
)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function formatDate(iso?: string): string {
|
|
73
|
-
if (!iso) return ''
|
|
74
|
-
try {
|
|
75
|
-
return new Date(iso).toLocaleDateString()
|
|
76
|
-
} catch {
|
|
77
|
-
return iso
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/** 行唯一 id(source + platformIssueId 组合,避免跨平台 id 碰撞)。 */
|
|
82
|
-
function rowId(row: FeedbackIssueRow): string {
|
|
83
|
-
return `${row.source}:${row.platformIssueId}`
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export default function FeedbackPanel(): React.ReactElement {
|
|
87
|
-
const backend = usePanelBackend<unknown>()
|
|
88
|
-
const [view, setView] = useState<View>('list')
|
|
89
|
-
const [selectedIndex, setSelectedIndex] = useState(0)
|
|
90
|
-
const [filterMode, setFilterMode] = useState<FilterMode>('all')
|
|
91
|
-
const [listResult, setListResult] = useState<FeedbackListResult | undefined>(undefined)
|
|
92
|
-
const [resolvedMarks, setResolvedMarks] = useState<FeedbackResolvedMark[]>([])
|
|
93
|
-
const [platform, setPlatform] = useState<'github' | 'coding'>('github')
|
|
94
|
-
const [loading, setLoading] = useState(true)
|
|
95
|
-
|
|
96
|
-
// 新建表单状态(对齐 schedule 面板 create 表单范式:formFocus + 单字符输入收集)。
|
|
97
|
-
const [formTitle, setFormTitle] = useState('')
|
|
98
|
-
const [formRepro, setFormRepro] = useState('')
|
|
99
|
-
const [formExpected, setFormExpected] = useState('')
|
|
100
|
-
const [formActual, setFormActual] = useState('')
|
|
101
|
-
const [formFocus, setFormFocus] = useState(0) // 0=title, 1=repro, 2=expected, 3=actual
|
|
102
|
-
const [formMsg, setFormMsg] = useState<string | null>(null)
|
|
103
|
-
const [formMsgTone, setFormMsgTone] = useState<'success' | 'error' | null>(null)
|
|
104
|
-
const [submitting, setSubmitting] = useState(false)
|
|
105
|
-
|
|
106
|
-
const refresh = useCallback(async () => {
|
|
107
|
-
setLoading(true)
|
|
108
|
-
try {
|
|
109
|
-
const [listResult, resolvedResult, platformResult] = await Promise.all([
|
|
110
|
-
backend.call<Record<string, unknown>, FeedbackListResult>('feedback.listIssues', { state: 'all', limitPerSource: 20 }),
|
|
111
|
-
backend.call<Record<string, unknown>, { marks: FeedbackResolvedMark[] }>('feedback.listResolvedMarks', {}),
|
|
112
|
-
backend.call<Record<string, unknown>, { platform: 'github' | 'coding' }>('feedback.resolvePlatform', {}),
|
|
113
|
-
])
|
|
114
|
-
setListResult(listResult)
|
|
115
|
-
setResolvedMarks(resolvedResult.marks ?? [])
|
|
116
|
-
setPlatform(platformResult.platform)
|
|
117
|
-
} catch {
|
|
118
|
-
// fail-soft:面板不因单次刷新失败而崩——保持上次数据 + loading=false。
|
|
119
|
-
} finally {
|
|
120
|
-
setLoading(false)
|
|
121
|
-
}
|
|
122
|
-
}, [backend])
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
const timer = setInterval(refresh, POLL_MS)
|
|
126
|
-
refresh()
|
|
127
|
-
return () => clearInterval(timer)
|
|
128
|
-
}, [refresh])
|
|
129
|
-
|
|
130
|
-
const allRows = useMemo<FeedbackIssueRow[]>(() => listResult?.items ?? [], [listResult])
|
|
131
|
-
|
|
132
|
-
const resolvedSet = useMemo(() => {
|
|
133
|
-
const s = new Set<string>()
|
|
134
|
-
for (const m of resolvedMarks) s.add(`${m.source}:${m.platformIssueId}`)
|
|
135
|
-
return s
|
|
136
|
-
}, [resolvedMarks])
|
|
137
|
-
|
|
138
|
-
const filteredRows = useMemo(() => {
|
|
139
|
-
return allRows.filter((row) => {
|
|
140
|
-
const isResolved = resolvedSet.has(rowId(row))
|
|
141
|
-
if (filterMode === 'resolved') return isResolved
|
|
142
|
-
if (filterMode === 'open') return !isResolved
|
|
143
|
-
return true
|
|
144
|
-
})
|
|
145
|
-
}, [allRows, resolvedSet, filterMode])
|
|
146
|
-
|
|
147
|
-
useEffect(() => {
|
|
148
|
-
setSelectedIndex((i) => (filteredRows.length === 0 ? 0 : Math.min(i, filteredRows.length - 1)))
|
|
149
|
-
}, [filteredRows])
|
|
150
|
-
|
|
151
|
-
const markResolved = useCallback(async (row: FeedbackIssueRow) => {
|
|
152
|
-
await backend.call('feedback.markResolved', {
|
|
153
|
-
source: row.source,
|
|
154
|
-
platformIssueId: row.platformIssueId,
|
|
155
|
-
url: row.url,
|
|
156
|
-
title: row.title,
|
|
157
|
-
})
|
|
158
|
-
refresh()
|
|
159
|
-
}, [backend, refresh])
|
|
160
|
-
|
|
161
|
-
const unmarkResolved = useCallback(async (row: FeedbackIssueRow) => {
|
|
162
|
-
await backend.call('feedback.unmarkResolved', {
|
|
163
|
-
source: row.source,
|
|
164
|
-
platformIssueId: row.platformIssueId,
|
|
165
|
-
})
|
|
166
|
-
refresh()
|
|
167
|
-
}, [backend, refresh])
|
|
168
|
-
|
|
169
|
-
const submitForm = useCallback(async () => {
|
|
170
|
-
if (submitting) return
|
|
171
|
-
if (!formTitle.trim()) {
|
|
172
|
-
setFormMsg(t('feedbackPanel.formRequired'))
|
|
173
|
-
return
|
|
174
|
-
}
|
|
175
|
-
setSubmitting(true)
|
|
176
|
-
setFormMsg(t('feedbackPanel.formSubmitting'))
|
|
177
|
-
setFormMsgTone(null)
|
|
178
|
-
try {
|
|
179
|
-
const resp = await backend.call<
|
|
180
|
-
Record<string, unknown>,
|
|
181
|
-
{ ok: boolean; url?: string; error?: string }
|
|
182
|
-
>('feedback.submitIssue', {
|
|
183
|
-
title: formTitle,
|
|
184
|
-
reproduce: formRepro || undefined,
|
|
185
|
-
expected: formExpected || undefined,
|
|
186
|
-
actual: formActual || undefined,
|
|
187
|
-
})
|
|
188
|
-
if (resp?.ok && resp.url) {
|
|
189
|
-
setFormMsg(t('feedbackPanel.formSuccess', { url: resp.url }))
|
|
190
|
-
setFormMsgTone('success')
|
|
191
|
-
setView('list')
|
|
192
|
-
} else if (resp?.error === 'repo-unresolved') {
|
|
193
|
-
setFormMsg(t('feedbackPanel.formRepoUnresolved'))
|
|
194
|
-
setFormMsgTone('error')
|
|
195
|
-
} else {
|
|
196
|
-
setFormMsg(t('feedbackPanel.formNoChannel'))
|
|
197
|
-
setFormMsgTone('error')
|
|
198
|
-
}
|
|
199
|
-
} catch {
|
|
200
|
-
setFormMsg(t('feedbackPanel.formNoChannel'))
|
|
201
|
-
setFormMsgTone('error')
|
|
202
|
-
} finally {
|
|
203
|
-
setSubmitting(false)
|
|
204
|
-
}
|
|
205
|
-
}, [backend, formTitle, formRepro, formExpected, formActual, submitting])
|
|
206
|
-
|
|
207
|
-
useInput((input, key) => {
|
|
208
|
-
if (view === 'new') {
|
|
209
|
-
if (key.escape || input === 'q') {
|
|
210
|
-
setView('list')
|
|
211
|
-
setFormMsg(null)
|
|
212
|
-
return
|
|
213
|
-
}
|
|
214
|
-
if (key.tab) {
|
|
215
|
-
setFormFocus((f) => (f + 1) % 4)
|
|
216
|
-
return
|
|
217
|
-
}
|
|
218
|
-
if (key.return) {
|
|
219
|
-
void submitForm()
|
|
220
|
-
return
|
|
221
|
-
}
|
|
222
|
-
if (key.backspace || key.delete) {
|
|
223
|
-
if (formFocus === 0) setFormTitle((v) => v.slice(0, -1))
|
|
224
|
-
else if (formFocus === 1) setFormRepro((v) => v.slice(0, -1))
|
|
225
|
-
else if (formFocus === 2) setFormExpected((v) => v.slice(0, -1))
|
|
226
|
-
else if (formFocus === 3) setFormActual((v) => v.slice(0, -1))
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
if (input && input.length === 1) {
|
|
230
|
-
if (formFocus === 0) setFormTitle((v) => v + input)
|
|
231
|
-
else if (formFocus === 1) setFormRepro((v) => v + input)
|
|
232
|
-
else if (formFocus === 2) setFormExpected((v) => v + input)
|
|
233
|
-
else if (formFocus === 3) setFormActual((v) => v + input)
|
|
234
|
-
}
|
|
235
|
-
return
|
|
236
|
-
}
|
|
237
|
-
if (key.downArrow || input === 'j') {
|
|
238
|
-
setSelectedIndex((i) => (filteredRows.length === 0 ? 0 : Math.min(i + 1, filteredRows.length - 1)))
|
|
239
|
-
}
|
|
240
|
-
if (key.upArrow || input === 'k') {
|
|
241
|
-
setSelectedIndex((i) => Math.max(i - 1, 0))
|
|
242
|
-
}
|
|
243
|
-
if (input === 'n') setView('new')
|
|
244
|
-
if (input === 'r') refresh()
|
|
245
|
-
if (input === 'f') setFilterMode((v) => (v === 'all' ? 'open' : v === 'open' ? 'resolved' : 'all'))
|
|
246
|
-
if (input === 'm' && filteredRows[selectedIndex]) {
|
|
247
|
-
const row = filteredRows[selectedIndex]
|
|
248
|
-
const isResolved = resolvedSet.has(rowId(row))
|
|
249
|
-
if (isResolved) unmarkResolved(row)
|
|
250
|
-
else markResolved(row)
|
|
251
|
-
}
|
|
252
|
-
if (input === 'x' && filteredRows[selectedIndex]) {
|
|
253
|
-
unmarkResolved(filteredRows[selectedIndex])
|
|
254
|
-
}
|
|
255
|
-
if (input === 'o' && filteredRows[selectedIndex]) {
|
|
256
|
-
// 打开 URL——面板进程内不可直接 spawn 浏览器,经宿主侧 backend 的
|
|
257
|
-
// feedback.openExternal(openBrowser)打开(RFC-430 D4)。
|
|
258
|
-
void backend.call('feedback.openExternal', { url: filteredRows[selectedIndex].url })
|
|
259
|
-
}
|
|
260
|
-
if (input === 'q' || key.escape) requestPanelClose()
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
const theme = getTheme()
|
|
264
|
-
const figures = getFigures()
|
|
265
|
-
|
|
266
|
-
useEffect(() => {
|
|
267
|
-
setPanelHint([
|
|
268
|
-
['n', t('feedbackPanel.hintNew')],
|
|
269
|
-
['m', t('feedbackPanel.hintMark')],
|
|
270
|
-
['x', t('feedbackPanel.hintUnmark')],
|
|
271
|
-
['o', t('feedbackPanel.hintOpen')],
|
|
272
|
-
['r', t('feedbackPanel.hintRefresh')],
|
|
273
|
-
['f', t('feedbackPanel.hintFilter')],
|
|
274
|
-
['q', t('feedbackPanel.hintClose')],
|
|
275
|
-
])
|
|
276
|
-
}, [])
|
|
277
|
-
|
|
278
|
-
const formRow = (label: string, value: string, focused: boolean, placeholder: string) => (
|
|
279
|
-
<Box flexDirection="row" gap={1}>
|
|
280
|
-
<Text dimColor={!focused}>{focused ? figures.pointer : ' '} {label}</Text>
|
|
281
|
-
<Text dimColor={!value && !focused}>{value || placeholder}</Text>
|
|
282
|
-
<Text color={theme.selection}>{value || (focused ? '_' : '')}</Text>
|
|
283
|
-
</Box>
|
|
284
|
-
)
|
|
285
|
-
|
|
286
|
-
if (view === 'new') {
|
|
287
|
-
return (
|
|
288
|
-
<Box flexDirection="column" gap={1}>
|
|
289
|
-
<Text bold>{t('feedbackPanel.newTitle')}</Text>
|
|
290
|
-
<Text dimColor>{t('feedbackPanel.newSubtitle', { platform })}</Text>
|
|
291
|
-
{formRow(t('feedbackPanel.formTitle'), formTitle, formFocus === 0, '')}
|
|
292
|
-
{formRow(t('feedbackPanel.formRepro'), formRepro, formFocus === 1, t('feedbackPanel.formRepro'))}
|
|
293
|
-
{formRow(t('feedbackPanel.formExpected'), formExpected, formFocus === 2, t('feedbackPanel.formExpected'))}
|
|
294
|
-
{formRow(t('feedbackPanel.formActual'), formActual, formFocus === 3, t('feedbackPanel.formActual'))}
|
|
295
|
-
{formMsg && (
|
|
296
|
-
<Text color={formMsgTone === 'success' ? theme.success : theme.warning}>
|
|
297
|
-
{formMsg}
|
|
298
|
-
</Text>
|
|
299
|
-
)}
|
|
300
|
-
<Text dimColor>{t('feedbackPanel.formHint')}</Text>
|
|
301
|
-
</Box>
|
|
302
|
-
)
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
return (
|
|
306
|
-
<Box flexDirection="column" gap={1}>
|
|
307
|
-
<Box justifyContent="space-between">
|
|
308
|
-
<Text bold>{t('feedbackPanel.title')}</Text>
|
|
309
|
-
<Text dimColor>{t('feedbackPanel.platform', { platform })}</Text>
|
|
310
|
-
</Box>
|
|
311
|
-
{loading && <Text dimColor>{t('feedbackPanel.loading')}</Text>}
|
|
312
|
-
{listResult?.unavailable &&
|
|
313
|
-
Object.entries(listResult.unavailable).map(([source, reason]) => (
|
|
314
|
-
<Box key={source}>
|
|
315
|
-
<Text color={theme.warning}>
|
|
316
|
-
{source}: {reason}
|
|
317
|
-
</Text>
|
|
318
|
-
</Box>
|
|
319
|
-
))}
|
|
320
|
-
<Box flexDirection="column">
|
|
321
|
-
{filteredRows.length === 0 && !loading && (
|
|
322
|
-
<Text dimColor>{t('feedbackPanel.empty')}</Text>
|
|
323
|
-
)}
|
|
324
|
-
{filteredRows.map((row, i) => {
|
|
325
|
-
const isSelected = i === selectedIndex
|
|
326
|
-
const isResolved = resolvedSet.has(rowId(row))
|
|
327
|
-
return (
|
|
328
|
-
<Box key={rowId(row)} gap={1}>
|
|
329
|
-
{isSelected && <Text color={theme.selection}>{figures.pointer}</Text>}
|
|
330
|
-
{sourceBadge(row.source, theme)}
|
|
331
|
-
<Text dimColor={isResolved}>
|
|
332
|
-
{isResolved ? `${figures.check} ` : ''}{row.title}
|
|
333
|
-
</Text>
|
|
334
|
-
<Text dimColor>{formatDate(row.updatedAt ?? row.createdAt)}</Text>
|
|
335
|
-
<Text dimColor>{row.state}</Text>
|
|
336
|
-
</Box>
|
|
337
|
-
)
|
|
338
|
-
})}
|
|
339
|
-
</Box>
|
|
340
|
-
<GroupDivider label={t('feedbackPanel.legend')} />
|
|
341
|
-
<Box gap={2}>
|
|
342
|
-
<Text dimColor>{t('feedbackPanel.filter', { filter: filterMode })}</Text>
|
|
343
|
-
<Text dimColor>{t('feedbackPanel.total', { count: String(filteredRows.length) })}</Text>
|
|
344
|
-
</Box>
|
|
345
|
-
<Text dimColor>{t('feedbackPanel.resolvedNote')}</Text>
|
|
346
|
-
</Box>
|
|
347
|
-
)
|
|
348
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* panel-host-globals.d.ts — 插件面板宿主桥接全局类型声明(RFC-353 S12)。
|
|
3
|
-
*
|
|
4
|
-
* `panel-compiler.ts` 用 esbuild banner 把宿主运行时(`getTheme`/`getFigures`/
|
|
5
|
-
* `usePanelBackend`/`usePanelFocusClaimed`/`requestPanelClose`/`setPanelHint`/`t`)
|
|
6
|
-
* 注入为面板产物的全局引用(非常规 import,见 `packages/coding/src/plugin/panel-compiler.ts`
|
|
7
|
-
* 头注释)。此前每个面板 `.tsx` 文件各自手写一份 `declare function ...`(逐文件重复),
|
|
8
|
-
* 是漂移风险源——2026-08-11 review 实测发现 schedule.tsx 三处 `backend.call<TResult>()`
|
|
9
|
-
* 调用漏传 `TParams` 泛型参数(`call<TParams, TResult>` 签名下单参数会被错误绑定到
|
|
10
|
-
* `TParams`),typecheck 覆盖打开后才暴露(本文件与该修复同批落地)。
|
|
11
|
-
*
|
|
12
|
-
* 本文件用 `declare global` 环境声明(ambient declaration)替代逐文件重复:TS 编译器
|
|
13
|
-
* 按 tsconfig `include` 把本文件纳入同一程序,`declare global` 块内的声明对整个程序
|
|
14
|
-
* 全局可见,面板 `.tsx` 文件无需 import 即可直接使用这些函数名(与
|
|
15
|
-
* `packages/coding/src/global.d.ts` 声明 `globalThis.React` 同一模式)。
|
|
16
|
-
*
|
|
17
|
-
* 本文件是纯类型声明(zero JS 产出),且从不被任何 `.tsx` 文件 import——不会被
|
|
18
|
-
* esbuild 打包,只参与 tsc 类型检查程序。
|
|
19
|
-
*/
|
|
20
|
-
export interface PanelTheme {
|
|
21
|
-
text: string | undefined
|
|
22
|
-
inactive: string
|
|
23
|
-
secondary: string
|
|
24
|
-
success: string
|
|
25
|
-
error: string
|
|
26
|
-
warning: string
|
|
27
|
-
special: string
|
|
28
|
-
selection: string
|
|
29
|
-
focusBorder: string
|
|
30
|
-
panelBorder: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface PanelFigures {
|
|
34
|
-
check: string
|
|
35
|
-
cross: string
|
|
36
|
-
pointer: string
|
|
37
|
-
bullet: string
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export type PanelBackendStatus = 'connecting' | 'ready' | 'crashed' | 'disabled'
|
|
41
|
-
|
|
42
|
-
export interface PanelBackendHandle<TState = unknown> {
|
|
43
|
-
/**
|
|
44
|
-
* `TParams` 必须显式指定(或从传入的 `params` 字面量推断)——本函数签名是
|
|
45
|
-
* `<TParams, TResult>`(不是 `<TResult>`)。调用方若只想指定返回类型,须写
|
|
46
|
-
* `call<Record<string, unknown>, TResult>(...)` 或让 TS 从 params 参数推断 TParams,
|
|
47
|
-
* 不能省略只传一个类型参数(会被错误绑定到 TParams 而非 TResult)。
|
|
48
|
-
*/
|
|
49
|
-
call: <TParams extends Record<string, unknown>, TResult = unknown>(
|
|
50
|
-
method: string,
|
|
51
|
-
params: TParams,
|
|
52
|
-
opts?: { operationId?: string },
|
|
53
|
-
) => Promise<TResult>
|
|
54
|
-
cancel: (operationId: string) => void
|
|
55
|
-
state: TState | undefined
|
|
56
|
-
status: PanelBackendStatus
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
declare global {
|
|
60
|
-
function getTheme(): PanelTheme
|
|
61
|
-
function getFigures(): PanelFigures
|
|
62
|
-
function usePanelFocusClaimed(): boolean
|
|
63
|
-
function requestPanelClose(): void
|
|
64
|
-
function setPanelHint(hint: string | Array<[string, string]> | undefined): void
|
|
65
|
-
function t(key: string, opts?: Record<string, unknown>): string
|
|
66
|
-
function usePanelBackend<TState = unknown>(): PanelBackendHandle<TState>
|
|
67
|
-
}
|