11<script setup lang="ts">
22import type { SkillFileTreeNode as TreeNode } from ' ../utils/skill-file-tree'
33import { formatByteSize } from ' ../utils/skill-context-cost'
4- import { fileIcon , isInlineRenderable } from ' ../utils/skill-file-tree'
4+ import { fileIcon , isInlineRenderable , splitFileLabel } from ' ../utils/skill-file-tree'
55
66const props = defineProps <{
77 node: TreeNode
@@ -55,6 +55,8 @@ function onFileClick(event: MouseEvent) {
5555 emit (' select' , props .node .path )
5656}
5757
58+ const label = computed (() => splitFileLabel (props .node .name ))
59+
5860const inlineRenderable = computed (() =>
5961 props .node .kind === ' file' && !! props .node .asset && isInlineRenderable (props .node .asset .type ),
6062)
@@ -68,22 +70,29 @@ const inlineRenderable = computed(() =>
6870 class =" tree-row"
6971 :aria-expanded =" open"
7072 :aria-controls =" `${idPrefix}-${node.path}`"
73+ :title =" node.path"
7174 @click =" open = !open"
7275 >
73- <UIcon
74- name="i-lucide-chevron-right"
75- class="size-3 text-muted transition-transform shrink-0"
76- :class =" { ' rotate-90' : open } "
77- aria-hidden="true"
78- />
7976 <SkillFileIcon
8077 :name =" open ? ' default-folder-opened' : ' default-folder' "
8178 class="size-4 shrink-0"
8279 />
83- <span class =" tree-label" >{{ node.name }}</span >
80+ <span
81+ class =" tree-label"
82+ :style =" { '--label-tail': `${label.tail.length}ch` }"
83+ >
84+ <span class =" tree-label-head" >{{ label.head }}</span >
85+ <span v-if =" label.tail" >{{ label.tail }}</span >
86+ </span >
8487 <span
8588 class =" tree-size"
8689 >{{ folderFileCount }} {{ folderFileCount === 1 ? 'file' : 'files' }}</span >
90+ <UIcon
91+ name="i-lucide-chevron-right"
92+ class="size-3 text-muted transition-transform shrink-0"
93+ :class =" { ' rotate-90' : open } "
94+ aria-hidden="true"
95+ />
8796 </button >
8897 <ul
8998 v-show =" open"
@@ -122,12 +131,17 @@ const inlineRenderable = computed(() =>
122131 :title =" node.path"
123132 @click =" onFileClick"
124133 >
125- <span class =" tree-spacer" aria-hidden =" true" />
126134 <SkillFileIcon
127135 :name =" fileIcon (node .name )"
128136 class="size-4 shrink-0"
129137 />
130- <span class =" tree-label" >{{ node.name }}</span >
138+ <span
139+ class =" tree-label"
140+ :style =" { '--label-tail': `${label.tail.length}ch` }"
141+ >
142+ <span class =" tree-label-head" >{{ label.head }}</span >
143+ <span v-if =" label.tail" >{{ label.tail }}</span >
144+ </span >
131145 <span
132146 v-if =" node.asset?.size"
133147 class =" tree-size"
@@ -142,12 +156,17 @@ const inlineRenderable = computed(() =>
142156 :title =" node.path"
143157 @click =" onFileClick"
144158 >
145- <span class =" tree-spacer" aria-hidden =" true" />
146159 <SkillFileIcon
147160 :name =" fileIcon (node .name )"
148161 class="size-4 shrink-0"
149162 />
150- <span class =" tree-label" >{{ node.name }}</span >
163+ <span
164+ class =" tree-label"
165+ :style =" { '--label-tail': `${label.tail.length}ch` }"
166+ >
167+ <span class =" tree-label-head" >{{ label.head }}</span >
168+ <span v-if =" label.tail" >{{ label.tail }}</span >
169+ </span >
151170 <span
152171 v-if =" node.asset?.size"
153172 class =" tree-size"
@@ -161,12 +180,17 @@ const inlineRenderable = computed(() =>
161180 class =" tree-row file"
162181 :title =" node.path"
163182 >
164- <span class =" tree-spacer" aria-hidden =" true" />
165183 <SkillFileIcon
166184 :name =" fileIcon (node .name )"
167185 class="size-4 shrink-0"
168186 />
169- <span class =" tree-label" >{{ node.name }}</span >
187+ <span
188+ class =" tree-label"
189+ :style =" { '--label-tail': `${label.tail.length}ch` }"
190+ >
191+ <span class =" tree-label-head" >{{ label.head }}</span >
192+ <span v-if =" label.tail" >{{ label.tail }}</span >
193+ </span >
170194 <span
171195 v-if =" node.asset?.size"
172196 class =" tree-size"
@@ -185,13 +209,14 @@ const inlineRenderable = computed(() =>
185209.tree-children {
186210 list-style : none ;
187211 margin : 0 ;
188- padding-left : 0.875 rem ;
212+ padding-left : 0.375 rem ;
189213 border-left : 1px dashed var (--ui-border );
190- margin-left : 0.4375rem ;
214+ /* Puts the guide line under the parent folder icon. */
215+ margin-left : 0.9375rem ;
191216}
192217.tree-row {
193218 display : flex ;
194- align-items : flex-start ;
219+ align-items : center ;
195220 gap : 0.375rem ;
196221 width : 100% ;
197222 padding : 0.25rem 0.5rem ;
@@ -217,21 +242,25 @@ const inlineRenderable = computed(() =>
217242 background : var (--ui-bg-muted );
218243 font-weight : 500 ;
219244}
220- .tree-spacer {
221- width : 0.75rem ;
222- flex-shrink : 0 ;
223- }
224245.tree-size {
225246 flex-shrink : 0 ;
226247 font-size : 0.6875rem ;
227248 font-variant-numeric : tabular-nums ;
228249 color : var (--ui-text-muted );
229250}
230- /* The explorer column is narrow, so long names wrap instead of hiding the
231- part that tells two files apart. */
232251.tree-label {
233- min-width : 0 ;
252+ display : flex ;
234253 flex : 1 ;
235- overflow-wrap : anywhere;
254+ min-width : 0 ;
255+ white-space : nowrap ;
256+ container-type : inline-size;
257+ }
258+ /* The tree is monospace. Rounding the head down to whole characters puts the
259+ ellipsis flush against the tail, with no part-character gap between them. */
260+ .tree-label-head {
261+ min-width : 0 ;
262+ max-width : round (down, calc (100 cqw - var (--label-tail )), 1ch );
263+ overflow : hidden ;
264+ text-overflow : ellipsis ;
236265}
237266 </style >
0 commit comments