fix(wiki,agent): tidy up post-merge review nits

- WikiPageTypeProfile: normalise pageType keys to lowercase on set, so a
  user-authored profile with an uppercase key still matches the
  case-insensitive hasPageType/get lookups.
- WikiDirectoryScanService: normalise the symlink-resolved glob base to
  forward slashes so directory-scan globs work on Windows paths.
- Agents roster tag filter: keep selected tags that no longer exist on any
  agent visible and deselectable (and show the filter bar when only such
  orphan selections remain) instead of silently filtering with no way to clear.
This commit is contained in:
matevip 2026-06-08 22:48:31 +08:00
parent 21798d6be5
commit b95ee90c64
3 changed files with 31 additions and 3 deletions

View File

@ -36,6 +36,28 @@ public class WikiPageTypeProfile {
*/
private boolean allowAdditionalFields = false;
/**
* Normalise keys to lowercase on set so a user-authored profile with an
* uppercase pageType key (e.g. {@code "Concept"}) still matches the
* case-insensitive {@link #hasPageType}/{@link #get} lookups. Replaces the
* Lombok-generated setter (so Jackson deserialization goes through here too).
*/
public void setPageTypes(Map<String, WikiPageTypeDef> pageTypes) {
Map<String, WikiPageTypeDef> normalized = new LinkedHashMap<>();
if (pageTypes != null) {
for (Map.Entry<String, WikiPageTypeDef> e : pageTypes.entrySet()) {
if (e.getKey() == null) {
continue;
}
String key = e.getKey().trim().toLowerCase();
if (!key.isEmpty()) {
normalized.put(key, e.getValue());
}
}
}
this.pageTypes = normalized;
}
/** Whether this profile declares the given pageType (case-insensitive). */
public boolean hasPageType(String pageType) {
if (pageType == null) {

View File

@ -234,7 +234,10 @@ public class WikiDirectoryScanService {
// wildcard tail; escape glob metacharacters in the base so a real
// directory name containing */?/{}/[] is treated literally.
String wildcardTail = pattern.substring(basePath.length());
String effectivePattern = globEscape(scanRoot.toString()) + wildcardTail;
// Normalise the resolved base to forward slashes: glob uses '/' as its
// separator, and on Windows scanRoot.toString() yields backslashes that
// globEscape would escape, producing a pattern that never matches.
String effectivePattern = globEscape(scanRoot.toString().replace('\\', '/')) + wildcardTail;
try {
matcher = FileSystems.getDefault().getPathMatcher("glob:" + effectivePattern);
} catch (IllegalArgumentException e) {

View File

@ -59,7 +59,7 @@
</div>
<!-- Tag filter (#146): orthogonal to the type/status tabs; click to
toggle, multi-select narrows by intersection (AND). -->
<div v-if="availableTags.length" class="tag-filter-bar">
<div v-if="availableTags.length || activeTags.length" class="tag-filter-bar">
<span class="tag-filter-bar__label">{{ t('agents.tagFilter.label') }}</span>
<button v-for="tag in visibleTags" :key="tag" class="tag-filter-chip"
:class="{ active: activeTags.includes(tag) }" @click="toggleTag(tag)">
@ -1056,7 +1056,10 @@ const visibleTags = computed(() => {
const q = tagSearch.value.trim().toLowerCase()
if (q) return availableTags.value.filter(t => t.toLowerCase().includes(q))
const top = availableTags.value.slice(0, TAG_FILTER_LIMIT)
const extraActive = activeTags.value.filter(t => !top.includes(t) && availableTags.value.includes(t))
// Always surface selected tags outside the top-N including ones that no
// longer exist on any agent (edited/deleted) so they stay deselectable
// instead of silently filtering the roster with no chip to clear them.
const extraActive = activeTags.value.filter(t => !top.includes(t))
return [...top, ...extraActive]
})