Skip to content

Commit c1443e0

Browse files
raffelinoclaude
andauthored
fix(flow-editor): don't show repo resources twice in the palette (#53)
The rf-knowledge keyword-search path attributes a repo keyword to a "library" equal to its source file stem (rf_knowledge.py: `library = f.stem`), so `login.resource` keywords come back under a library group named `login`. Since the D1 rewrite renders those same keywords in the pinned "Your resources" section, they appeared a second time under the library suggestions. Fix: drop dynamic library groups whose name matches a repo resource-file stem (new pure helper `resourceFileStems`). No-op on the env-libdoc path, which never introspects resource files. Unit-tested (resourceFileStems) + e2e regression guard asserting the stem never renders as its own library category. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8aaf9f5 commit c1443e0

4 files changed

Lines changed: 53 additions & 0 deletions

File tree

e2e/tests/flow-editor-resource-ux.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ test.describe('Flow Editor — custom-resource UX (D1–D6)', () => {
8484
// D1 — the pinned "Your resources" section header is present.
8585
await expect(palette.locator('[data-testid="palette-resources-label"]')).toBeVisible({ timeout: 8_000 });
8686

87+
// Dedupe regression — the rf-knowledge search path returns repo keywords
88+
// under a "library" named after the file stem ("login.resource" -> "login").
89+
// Those must NOT also appear as a separate library category below; they live
90+
// only in "Your resources". Assert no category is named after the stem.
91+
await expect(palette.locator('.category-name', { hasText: /^login$/i })).toHaveCount(0);
92+
8793
// D5 / D6 — sort + filter controls live in the palette header.
8894
await expect(palette.locator('[data-testid="palette-sort-btn"]')).toBeVisible();
8995
await expect(palette.locator('[data-testid="palette-filter-btn"]')).toBeVisible();

frontend/src/components/editor/flow/KeywordPalette.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
applyFilter,
1717
hiddenCount,
1818
sortLibraries,
19+
resourceFileStems,
1920
type CatLike,
2021
} from './paletteView'
2122
import type { StepType, RobotStep } from './flowConverter'
@@ -375,7 +376,14 @@ const resourceCategories = computed<KeywordCategory[]>(() => {
375376
const libraryCategories = computed<KeywordCategory[]>(() => {
376377
const libCats: KeywordCategory[] = []
377378
const dynamicLibNames = new Set<string>()
379+
// Dedupe: the rf-knowledge search path returns repo keywords under a
380+
// "library" named after the source file stem (login.resource → "login").
381+
// Those keywords already render in the pinned "Your resources" section, so
382+
// skip the duplicate library group. (No-op on the env-libdoc path, which
383+
// never introspects resource files.)
384+
const resourceStems = resourceFileStems(projectKeywords.value.map(kw => kw.file_path))
378385
for (const [lib, keywords] of dynamicLibraries.value) {
386+
if (resourceStems.has(lib.toLowerCase())) continue
379387
libCats.push({
380388
name: lib,
381389
keywords: keywords.map(kw => kw.name),

frontend/src/components/editor/flow/paletteView.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,26 @@ export function parseStoredSort(raw: string | null): PaletteSort | null {
9595
return raw === 'mostUsed' || raw === 'alpha' || raw === 'importedFirst' ? raw : null
9696
}
9797

98+
/**
99+
* Lower-cased stems (basename minus final extension) of the repo's project
100+
* keyword files. The rf-knowledge search path attributes a repo keyword to a
101+
* "library" equal to its source file stem (`backend/.../rf_knowledge.py`:
102+
* `library = f.stem`), so `login.resource` keywords come back under a library
103+
* group named `login`. The palette already renders those same keywords in the
104+
* pinned "Your resources" section (grouped by `login.resource`), so the library
105+
* group is a duplicate — this set lets the palette drop it. No-op on the
106+
* env-libdoc path (which never introspects resource files).
107+
*/
108+
export function resourceFileStems(filePaths: string[]): Set<string> {
109+
const stems = new Set<string>()
110+
for (const p of filePaths) {
111+
const base = (p.split('/').pop() || p).trim()
112+
const stem = base.replace(/\.[^.]+$/, '')
113+
if (stem) stems.add(stem.toLowerCase())
114+
}
115+
return stems
116+
}
117+
98118
/** The minimal category shape these helpers reason about. */
99119
export interface CatLike {
100120
name: string

frontend/src/tests/components/PaletteView.spec.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
bucketOf,
1414
parseStoredFilter,
1515
parseStoredSort,
16+
resourceFileStems,
1617
SOPHISTICATED_MIN_STEPS,
1718
type CatLike,
1819
type PaletteFilter,
@@ -105,6 +106,24 @@ describe('sortLibraries (D5)', () => {
105106
})
106107
})
107108

109+
describe('resourceFileStems (dedupe vs rf-knowledge library attribution)', () => {
110+
it('maps file paths to lower-cased stems (strips dir + extension)', () => {
111+
const s = resourceFileStems(['resources/login.resource', 'tests/Sub/Common.robot'])
112+
expect(s).toEqual(new Set(['login', 'common']))
113+
})
114+
it('lets the rf-knowledge "library" group (file stem) be matched & dropped', () => {
115+
// rf_knowledge.py attributes a repo keyword to library = f.stem, so
116+
// login.resource keywords arrive under a library named "login".
117+
const stems = resourceFileStems(['resources/login.resource'])
118+
expect(stems.has('login')).toBe(true)
119+
expect(stems.has('browser')).toBe(false) // real libs are untouched
120+
})
121+
it('handles bare filenames and empty input', () => {
122+
expect(resourceFileStems(['common.resource'])).toEqual(new Set(['common']))
123+
expect(resourceFileStems([])).toEqual(new Set())
124+
})
125+
})
126+
108127
describe('parseStoredFilter / parseStoredSort (persistence)', () => {
109128
it('round-trips a valid filter', () => {
110129
const f: PaletteFilter = { resources: true, importedLibs: false, exampleLibs: true, builtin: false }

0 commit comments

Comments
 (0)