agent-canvas 0.2.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -17
- package/bin/agent-canvas.js +7 -7
- package/bin/agent-canvas.ts +48 -37
- package/bin/lib/commands/daemon.ts +46 -43
- package/bin/lib/commands/exec.ts +100 -100
- package/bin/lib/commands/fetch.ts +15 -15
- package/bin/lib/commands/install.ts +36 -36
- package/bin/lib/commands/instructions.ts +51 -51
- package/bin/lib/commands/push.ts +115 -97
- package/bin/lib/commands/review.ts +190 -0
- package/bin/lib/commands/watch.ts +24 -24
- package/bin/lib/config.ts +12 -12
- package/bin/lib/daemon-lifecycle.ts +47 -51
- package/bin/lib/helpers.ts +82 -76
- package/bin/lib/instructions/app-review.md +88 -0
- package/bin/lib/instructions/component-checklist.md +7 -5
- package/bin/lib/instructions/component-custom.md +20 -14
- package/bin/lib/instructions/component-diff.md +4 -3
- package/bin/lib/instructions/component-item.md +18 -8
- package/bin/lib/instructions/component-markdown.md +4 -2
- package/bin/lib/instructions/component-mermaid.md +12 -6
- package/bin/lib/instructions/component-multichoice.md +6 -2
- package/bin/lib/instructions/component-note.md +1 -1
- package/bin/lib/instructions/component-priority.md +1 -1
- package/bin/lib/instructions/component-rangeinput.md +9 -2
- package/bin/lib/instructions/component-secretinput.md +12 -11
- package/bin/lib/instructions/component-svg.md +62 -16
- package/bin/lib/instructions/component-table.md +6 -6
- package/bin/lib/instructions/component-usefeedback.md +8 -8
- package/bin/lib/instructions/component-userinput.md +6 -2
- package/bin/lib/instructions/flow-feature.md +22 -5
- package/bin/lib/instructions/flow-plan.md +5 -5
- package/bin/lib/instructions/flow-runbook.md +28 -25
- package/bin/lib/instructions/overview.md +27 -18
- package/bin/lib/instructions/theming.md +5 -0
- package/daemon/build.ts +171 -152
- package/daemon/client/App.tsx +1205 -1077
- package/daemon/client/CompareView.tsx +478 -458
- package/daemon/client/FileBrowser.tsx +241 -203
- package/daemon/client/FileViewer.tsx +220 -211
- package/daemon/client/OverviewView.tsx +283 -267
- package/daemon/client/ReviewerIdentityDialog.tsx +72 -71
- package/daemon/client/RevisionSelect.tsx +111 -95
- package/daemon/client/SessionSwitcher.tsx +81 -81
- package/daemon/client/ShareDialog.tsx +211 -212
- package/daemon/client/apiSchemas.ts +78 -0
- package/daemon/client/canvas-runtime.ts +1 -1
- package/daemon/client/clientApi.ts +194 -206
- package/daemon/client/components/SecretInput.tsx +138 -138
- package/daemon/client/components/index.ts +2 -2
- package/daemon/client/domDiff.ts +341 -333
- package/daemon/client/errorMessage.ts +3 -0
- package/daemon/client/exportMarkdown.ts +222 -201
- package/daemon/client/lcs.ts +19 -14
- package/daemon/client/shareCrypto.ts +57 -57
- package/daemon/client/unifiedDiff.ts +660 -649
- package/daemon/dist/annotations-runtime.js +1 -1
- package/daemon/dist/client.css +2 -2
- package/daemon/dist/client.js +77 -80
- package/daemon/dist/components.js +59 -59
- package/daemon/dist/preact-compat.js +1 -1
- package/daemon/dist/review.js +361 -0
- package/daemon/dist/runtime.js +1 -1
- package/daemon/langMap.ts +24 -6
- package/daemon/review-widget/anchor.ts +187 -0
- package/daemon/review-widget/client.ts +95 -0
- package/daemon/review-widget/index.tsx +36 -0
- package/daemon/review-widget/markup.ts +71 -0
- package/daemon/review-widget/screenshot.ts +91 -0
- package/daemon/review-widget/source.ts +103 -0
- package/daemon/review-widget/styles.css +359 -0
- package/daemon/review-widget/text-modules.d.ts +4 -0
- package/daemon/review-widget/tsconfig.json +16 -0
- package/daemon/review-widget/widget.tsx +436 -0
- package/daemon/src/components.ts +3 -3
- package/daemon/src/handlers/api.ts +330 -328
- package/daemon/src/handlers/files.ts +112 -105
- package/daemon/src/handlers/static.ts +47 -46
- package/daemon/src/handlers/uploads.ts +61 -56
- package/daemon/src/paths.ts +13 -10
- package/daemon/src/remote-feedback.ts +192 -192
- package/daemon/src/review/markdown.ts +52 -0
- package/daemon/src/review/model.ts +132 -0
- package/daemon/src/review/proxy.ts +177 -0
- package/daemon/src/review/server.ts +199 -0
- package/daemon/src/review/store.ts +151 -0
- package/daemon/src/server.ts +127 -86
- package/daemon/src/share.ts +163 -169
- package/daemon/src/stubFetch.ts +7 -0
- package/daemon/tsconfig.json +19 -19
- package/package.json +47 -47
- package/skills/canvas/SKILL.md +9 -1
- package/bin/lib/commands/exec.test.ts +0 -27
- package/bin/lib/helpers.test.ts +0 -79
- package/daemon/src/remote-feedback.test.ts +0 -165
- package/daemon/src/secrets-api.test.ts +0 -123
- package/daemon/src/share-e2e.test.ts +0 -277
- package/daemon/src/share.test.ts +0 -289
|
@@ -1,206 +1,244 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import { useAnnotations } from
|
|
4
|
-
import { ActiveViewContext } from
|
|
5
|
-
import { FileIcon } from
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
interface
|
|
1
|
+
import { SessionContext } from '#canvas/runtime'
|
|
2
|
+
import { RESPONSE_ANNOTATION_PATH } from '@fabrika/annotations'
|
|
3
|
+
import { filePreviewResponseSchema, useAnnotations } from '@fabrika/canvas-kernel/client'
|
|
4
|
+
import { ActiveViewContext } from '@fabrika/canvas-kernel/client'
|
|
5
|
+
import { FileIcon } from '@fabrika/canvas-kernel/client'
|
|
6
|
+
import React, { useContext, useEffect, useMemo, useState } from 'react'
|
|
7
|
+
import { treeResponseSchema } from './apiSchemas'
|
|
8
|
+
import { FS_AVAILABLE } from './clientApi'
|
|
9
|
+
|
|
10
|
+
interface TreeEntry {
|
|
11
|
+
name: string
|
|
12
|
+
type: 'file' | 'dir'
|
|
13
|
+
}
|
|
14
|
+
interface DirState {
|
|
15
|
+
entries: TreeEntry[]
|
|
16
|
+
expanded: boolean
|
|
17
|
+
loaded: boolean
|
|
18
|
+
}
|
|
11
19
|
|
|
12
20
|
export function FileBrowser({ embedded }: { embedded?: boolean } = {}) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
21
|
+
// Shared-mode canvases have no filesystem backing — the daemon's /api/tree
|
|
22
|
+
// endpoint isn't reachable. Render a simple placeholder so reviewers see
|
|
23
|
+
// something informative instead of a broken tree.
|
|
24
|
+
if (!FS_AVAILABLE) {
|
|
25
|
+
return (
|
|
26
|
+
<div className={embedded ? 'px-4 py-3' : 'p-4'}>
|
|
27
|
+
<div className="text-[11px] uppercase tracking-widest text-text-tertiary font-body mb-2">Files</div>
|
|
28
|
+
<p className="text-[11px] text-text-tertiary font-body leading-relaxed">
|
|
29
|
+
File browsing is not available in shared view. File previews embedded in the canvas still work.
|
|
30
|
+
</p>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
return <LocalFileBrowser embedded={embedded} />
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function LocalFileBrowser({ embedded }: { embedded?: boolean }) {
|
|
38
|
+
const sessionId = useContext(SessionContext)
|
|
39
|
+
const { annotations, addAnnotation } = useAnnotations()
|
|
40
|
+
const { setActiveView } = useContext(ActiveViewContext)
|
|
41
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
42
|
+
const [dirs, setDirs] = useState<Record<string, DirState>>({})
|
|
43
|
+
|
|
44
|
+
const { annCountMap, annotatedFiles } = useMemo(() => {
|
|
45
|
+
const map = new Map<string, number>()
|
|
46
|
+
for (const a of annotations) {
|
|
47
|
+
if (a.filePath && a.filePath !== RESPONSE_ANNOTATION_PATH) map.set(a.filePath, (map.get(a.filePath) || 0) + 1)
|
|
48
|
+
}
|
|
49
|
+
return { annCountMap: map, annotatedFiles: [...map.keys()] }
|
|
50
|
+
}, [annotations])
|
|
51
|
+
|
|
52
|
+
const loadDir = async (path: string) => {
|
|
53
|
+
const res = await fetch(`/api/tree?session=${sessionId}&path=${encodeURIComponent(path)}`)
|
|
54
|
+
const data = treeResponseSchema.safeParse(await res.json())
|
|
55
|
+
if (data.success) setDirs((prev) => ({ ...prev, [path]: { entries: data.data.entries, expanded: true, loaded: true } }))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
if (!collapsed && !dirs['']) loadDir('')
|
|
60
|
+
}, [collapsed])
|
|
61
|
+
|
|
62
|
+
const toggleDir = (path: string) => {
|
|
63
|
+
if (dirs[path]?.loaded) setDirs((prev) => ({ ...prev, [path]: { ...prev[path], expanded: !prev[path].expanded } }))
|
|
64
|
+
else loadDir(path)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const openFile = (path: string) => {
|
|
68
|
+
setActiveView({ type: 'file', path })
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const addWholeFile = async (path: string) => {
|
|
72
|
+
const res = await fetch(`/api/file?session=${sessionId}&path=${encodeURIComponent(path)}`)
|
|
73
|
+
const data = filePreviewResponseSchema.safeParse(await res.json())
|
|
74
|
+
if (data.success && 'content' in data.data) {
|
|
75
|
+
const { content } = data.data
|
|
76
|
+
const preview = content.split('\n').slice(0, 3).join('\n') + (content.split('\n').length > 3 ? '\n...' : '')
|
|
77
|
+
addAnnotation(preview, 'Include this file for context', path)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const addWholeDir = async (path: string) => {
|
|
82
|
+
// Load dir if needed, then add all files
|
|
83
|
+
let entries: TreeEntry[] = []
|
|
84
|
+
if (dirs[path]?.loaded) {
|
|
85
|
+
entries = dirs[path].entries
|
|
86
|
+
} else {
|
|
87
|
+
const res = await fetch(`/api/tree?session=${sessionId}&path=${encodeURIComponent(path)}`)
|
|
88
|
+
const data = treeResponseSchema.safeParse(await res.json())
|
|
89
|
+
if (data.success) entries = data.data.entries
|
|
90
|
+
}
|
|
91
|
+
await Promise.all(
|
|
92
|
+
entries
|
|
93
|
+
.filter((entry) => entry.type === 'file')
|
|
94
|
+
.map((entry) => addWholeFile(path ? `${path}/${entry.name}` : entry.name)),
|
|
95
|
+
)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const getAnnCount = (path: string) => annCountMap.get(path) || 0
|
|
99
|
+
|
|
100
|
+
const renderTree = (basePath: string, depth: number = 0): React.ReactNode => {
|
|
101
|
+
const dir = dirs[basePath]
|
|
102
|
+
if (!dir?.expanded) return null
|
|
103
|
+
return dir.entries.map((entry) => {
|
|
104
|
+
const fullPath = basePath ? `${basePath}/${entry.name}` : entry.name
|
|
105
|
+
const annCount = getAnnCount(fullPath)
|
|
106
|
+
|
|
107
|
+
return (
|
|
108
|
+
<div key={fullPath}>
|
|
109
|
+
<div
|
|
110
|
+
className="group/item flex items-center py-[3px] hover:bg-bg-elevated text-[13px] font-body cursor-default transition-colors duration-100"
|
|
111
|
+
style={{ paddingLeft: `${16 + depth * 16}px`, paddingRight: '8px' }}
|
|
112
|
+
>
|
|
113
|
+
{entry.type === 'dir'
|
|
114
|
+
? (
|
|
115
|
+
<>
|
|
116
|
+
<span
|
|
117
|
+
onClick={() => toggleDir(fullPath)}
|
|
118
|
+
className="flex items-center gap-1.5 flex-1 text-text-secondary hover:text-text-primary cursor-pointer truncate"
|
|
119
|
+
>
|
|
120
|
+
<FileIcon name={entry.name} type="dir" expanded={dirs[fullPath]?.expanded} />
|
|
121
|
+
<span className="truncate">{entry.name}</span>
|
|
122
|
+
</span>
|
|
123
|
+
<button
|
|
124
|
+
onClick={(e) => {
|
|
125
|
+
e.stopPropagation()
|
|
126
|
+
addWholeDir(fullPath)
|
|
127
|
+
}}
|
|
128
|
+
className="text-[11px] text-text-tertiary hover:text-text-secondary opacity-0 group-hover/item:opacity-100 transition-opacity px-1 flex-shrink-0"
|
|
129
|
+
title="Add all files in folder"
|
|
130
|
+
>
|
|
131
|
+
+
|
|
132
|
+
</button>
|
|
133
|
+
</>
|
|
134
|
+
)
|
|
135
|
+
: (
|
|
136
|
+
<>
|
|
137
|
+
<span
|
|
138
|
+
onClick={() => openFile(fullPath)}
|
|
139
|
+
className={`flex-1 truncate cursor-pointer flex items-center gap-1.5 ${
|
|
140
|
+
annCount > 0 ? 'text-text-primary' : 'text-text-secondary hover:text-text-primary'
|
|
141
|
+
}`}
|
|
142
|
+
>
|
|
143
|
+
<FileIcon name={entry.name} type="file" />
|
|
144
|
+
<span className="truncate">{entry.name}</span>
|
|
145
|
+
</span>
|
|
146
|
+
{/* Right side: annotation badge + add button */}
|
|
147
|
+
<div className="flex items-center gap-1 flex-shrink-0 ml-1">
|
|
148
|
+
{annCount > 0 && (
|
|
149
|
+
<span className="text-[10px] font-medium bg-badge-bg text-accent-amber px-1.5 py-px rounded-full min-w-[18px] text-center">
|
|
150
|
+
{annCount}
|
|
151
|
+
</span>
|
|
152
|
+
)}
|
|
153
|
+
<button
|
|
154
|
+
onClick={(e) => {
|
|
155
|
+
e.stopPropagation()
|
|
156
|
+
addWholeFile(fullPath)
|
|
157
|
+
}}
|
|
158
|
+
className="text-[11px] text-text-tertiary hover:text-text-secondary opacity-0 group-hover/item:opacity-100 transition-opacity px-1"
|
|
159
|
+
title="Add file to context"
|
|
160
|
+
>
|
|
161
|
+
+
|
|
162
|
+
</button>
|
|
163
|
+
</div>
|
|
164
|
+
</>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
{entry.type === 'dir' && renderTree(fullPath, depth + 1)}
|
|
168
|
+
</div>
|
|
169
|
+
)
|
|
170
|
+
})
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (embedded) {
|
|
174
|
+
return (
|
|
175
|
+
<>
|
|
176
|
+
<div className="flex items-center justify-between px-4 pt-3 pb-2 flex-shrink-0">
|
|
177
|
+
<span className="text-[11px] font-medium uppercase tracking-widest text-text-tertiary font-body">Files</span>
|
|
178
|
+
</div>
|
|
179
|
+
<div className="flex-1 overflow-y-auto">{renderTree('')}</div>
|
|
180
|
+
{annotatedFiles.length > 0 && (
|
|
181
|
+
<div className="border-t border-border-subtle px-4 py-3 flex-shrink-0">
|
|
182
|
+
<div className="text-[10px] uppercase tracking-widest text-text-tertiary font-body mb-1.5">Referenced</div>
|
|
183
|
+
{annotatedFiles.map((f) => (
|
|
184
|
+
<div
|
|
185
|
+
key={f}
|
|
186
|
+
onClick={() => openFile(f)}
|
|
187
|
+
className="flex items-center justify-between text-[12px] font-body text-text-secondary hover:text-text-primary cursor-pointer py-0.5"
|
|
188
|
+
>
|
|
189
|
+
<span className="truncate">{f.split('/').pop()}</span>
|
|
190
|
+
<span className="text-[10px] font-medium bg-badge-bg text-accent-amber px-1.5 py-px rounded-full min-w-[18px] text-center flex-shrink-0 ml-1">
|
|
191
|
+
{getAnnCount(f)}
|
|
192
|
+
</span>
|
|
193
|
+
</div>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
</>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (collapsed) {
|
|
202
|
+
return (
|
|
203
|
+
<div className="hidden lg:flex w-8 border-r border-border-subtle bg-bg-surface flex-col items-center flex-shrink-0">
|
|
204
|
+
<button onClick={() => setCollapsed(false)} className="mt-3 p-1 text-text-tertiary hover:text-text-secondary transition-colors">
|
|
205
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
206
|
+
<path d="M5 3l4 4-4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
207
|
+
</svg>
|
|
208
|
+
</button>
|
|
209
|
+
</div>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div className="hidden lg:flex w-60 border-r border-border-subtle bg-bg-surface flex-col flex-shrink-0 overflow-hidden">
|
|
215
|
+
<div className="flex items-center justify-between px-4 pt-4 pb-3 flex-shrink-0">
|
|
216
|
+
<span className="text-[11px] font-medium uppercase tracking-widest text-text-tertiary font-body">Files</span>
|
|
217
|
+
<button onClick={() => setCollapsed(true)} className="text-text-tertiary hover:text-text-secondary transition-colors p-0.5">
|
|
218
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
219
|
+
<path d="M9 3l-4 4 4 4" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
220
|
+
</svg>
|
|
221
|
+
</button>
|
|
222
|
+
</div>
|
|
223
|
+
<div className="flex-1 overflow-y-auto">{renderTree('')}</div>
|
|
224
|
+
|
|
225
|
+
{annotatedFiles.length > 0 && (
|
|
226
|
+
<div className="border-t border-border-subtle px-4 py-3 flex-shrink-0">
|
|
227
|
+
<div className="text-[10px] uppercase tracking-widest text-text-tertiary font-body mb-1.5">Referenced</div>
|
|
228
|
+
{annotatedFiles.map((f) => (
|
|
229
|
+
<div
|
|
230
|
+
key={f}
|
|
231
|
+
onClick={() => openFile(f)}
|
|
232
|
+
className="flex items-center justify-between text-[12px] font-body text-text-secondary hover:text-text-primary cursor-pointer py-0.5"
|
|
233
|
+
>
|
|
234
|
+
<span className="truncate">{f.split('/').pop()}</span>
|
|
235
|
+
<span className="text-[10px] font-medium bg-badge-bg text-accent-amber px-1.5 py-px rounded-full min-w-[18px] text-center flex-shrink-0 ml-1">
|
|
236
|
+
{getAnnCount(f)}
|
|
237
|
+
</span>
|
|
238
|
+
</div>
|
|
239
|
+
))}
|
|
240
|
+
</div>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
)
|
|
206
244
|
}
|