Dashboard › Code › Session sc5c8f004731
sc5c8f004731286fbDismiss button doesn't seem to do anything?
Let me check how the dismiss feedback affects what's shown. The dismiss records a reject signal in dedup_feedback, but that only affects the adaptive threshold calibrator — it doesn't directly hide the suggestion on the next page load. The dedup dry-run will still return the same pair because the similarity score hasn't changed.
Let me verify this by reading the dedup flow:
<path>/home/byk/Code/opencode-lore/packages/core/src/entities.ts</path>
<type>file</type>
<content>
1447: projectPath ? ensureProject(projectPath) : null,
1448: ) ??
1449: ENTITY_EMBEDDING_DEDUP_THRESHOLD;
1450:
1451: // --- Load embeddings for the candidate entities (if available) ---
1452: const embeddingMap = new Map<string, Float32Array>();
1453: {
1454: const ids = entities.map((e) => e.id);
1455: const placeholders = ids.map(() => "?").join(",");
1456: const rows = db()
1457: .query(
1458: `SELECT id, embedding FROM entities WHERE embedding IS NOT NULL AND id IN (${placeholders})`,
1459: )
1460: .all(...ids) as Array<{ id: string; embedding: Buffer }>;
1461: for (const row of rows) {
(Showing lines 1447-1461 of 1936. Use offset=1462 to continue.)
</content>
<path>/home/byk/Code/opencode-lore/packages/core/src/entities.ts</path>
<type>file</type>
<content>
1700: /** Max auto-signal pairs to record per dedup run (closest to threshold). */
1701: const ENTITY_AUTO_SIGNAL_MAX_PAIRS = 50;
1702: /** Max feedback rows to keep per project (prevents unbounded growth). */
1703: const MAX_ENTITY_FEEDBACK_ROWS_PER_PROJECT = 500;
1704:
1705: /** Record a single entity dedup feedback row (kind='entity'). */
1706: export function recordEntityDedupFeedback(input: {
1707: projectId: string | null;
1708: entryATitle: string;
1709: entryBTitle: string;
1710: similarity: number;
1711: accepted: boolean;
1712: source: EntityDedupFeedbackSource;
1713: }): void {
1714: db()
1715: .query(
1716: `INSERT INTO dedup_feedback
1717: (project_id, entry_a_title, entry_b_title, similarity, accepted, source, created_at, kind)
1718: VALUES (?, ?, ?, ?, ?, ?, ?, 'entity')`,
1719: )
1720: .run(
1721: ...
Now let me check the dashboard's dedup display — does it filter out dismissed pairs?
Found 6 matches
/home/byk/Code/opencode-lore/packages/gateway/src/ui.ts:
Line 2821: body += `<form method="POST" action="/ui/api/dismiss/entity/${esc(m.id)}/${esc(c.surviving.id)}" style="display:inline;">`;
Line 3178: const dismissEntity = matchRoute(
Line 3180: "/ui/api/dismiss/entity/:entityAId/:entityBId",
Line 3182: if (dismissEntity) {
Line 3183: const entityA = entities.get(dismissEntity.entityAId);
Line 3184: const entityB = entities.get(dismissEntity.entityBId);
As I suspected — the dismiss handler records feedback but there's no filtering of dismissed pairs when rendering the dedup suggestions. The dedupDryRun results are shown as-is, without checking whether the user already dismissed that pair.
Let me find where the dedup suggestions are generated for the dashboard:
No files found
Found 8 matches
/home/byk/Code/opencode-lore/packages/gateway/src/ui.ts:
Line 2792: // Merge suggestions (#462): surface duplicate candidates as a dry-run banner.
Line 2797: const dupes = await entities.deduplicateEntities(undefined, {
Line 2804: body += `<strong>${pairCount} possible duplicate ${pairCount === 1 ? "entity" : "entities"} found.</strong> Review and merge below, or run <code>lore entity dedup</code>.`;
Line 2830: body += `<span class="muted" style="color:#888;">…and ${pairCount - shown} more. Use <code>lore entity dedup</code>.</span>`;
Line 2835: log.warn("entity dedup suggestions failed (non-fatal):", err);
Line 3136: // Merge entity (dedup suggestion): keep target, absorb source (#462)
Line 3160: // value from the dedup dry-run, passed through as a hidden form field.
Line 3177: // Dismiss entity merge suggestion: record reject feedback (#462)
<path>/home/byk/Code/opencode-lore/packages/gateway/src/ui.ts</path>
<type>file</type>
<content>
2792: // Merge suggestions (#462): surface duplicate candidates as a dry-run banner.
2793: // Only compute when embeddings are available (the primary signal); cheap for
2794: // typical registry sizes. Non-fatal — failures just omit the section.
2795: if (embedding.isAvailable() && all.length >= 2) {
2796: try {
2797: const dupes = await entities.deduplicateEntities(undefined, {
2798: dryRun: true,
2799: });
2800: const clusters = [...dupes.merged, ...dupes.suggested];
2801: if (clusters.length > 0) {
2802: const pairCount = clusters.reduce((n, c) => n + c.merged.length, 0);
2803: body += `<div class="banner" style="border:1px solid #d0a000;background:#fffbe6;padding:12px 16px;border-radius:6px;margin:12px 0;">`;
2804: body += `<strong>${pairCount} possible duplicate ${pairCount === 1 ? "entity" : "entities"} ...
I see the problem clearly. The dedup suggestions come from deduplicateEntities(undefined, { dryRun: true }), and there's no filtering of dismissed pairs. I need to:
dedup_feedbackLet me check how to identify a dismissed pair — it's stored by entryATitle/entryBTitle:
<path>/home/byk/Code/opencode-lore/packages/core/src/entities.ts</path>
<type>file</type>
<content>
1640: ?.find((h) => h.id === survivor.id);
1641: if (fromMember)
1642: return { score: fromMember.score, force: fromMember.forceMerge };
1643: // Fallback: use the combined/boosted score (not raw cosine) to preserve
1644: // the Jaccard/knowledge boosts when the survivor differs from the center.
1645: const pk = entityPairKey(survivor.id, memberId);
1646: const s = pairScores.get(pk) ?? 0;
1647: return { score: s, force: s >= ENTITY_AUTO_MERGE_THRESHOLD };
1648: };
1649:
1650: const mergeMembers: EntityDedupCluster["merged"] = [];
1651: const suggestMembers: EntityDedupCluster["merged"] = [];
1652: for (const m of sorted.slice(1)) {
1653: const { score, force } = scoreFor(m.id);
1654: const entry = { id: m.id, name: m.canonical_name, similarity: score };
(Showing lines 1640-1654 of 1936. ...
The feedback uses entity names (titles), not IDs. The simplest approach is to query dismissed pairs from dedup_feedback and filter them out in the dashboard rendering code. Let me implement this:
<path>/home/byk/Code/opencode-lore/packages/core/src/entities.ts</path>
<type>file</type>
<content>
1706: export function recordEntityDedupFeedback(input: {
1707: projectId: string | null;
1708: entryATitle: string;
1709: entryBTitle: string;
1710: similarity: number;
1711: accepted: boolean;
1712: source: EntityDedupFeedbackSource;
1713: }): void {
1714: db()
1715: .query(
1716: `INSERT INTO dedup_feedback
1717: (project_id, entry_a_title, entry_b_title, similarity, accepted, source, created_at, kind)
1718: VALUES (?, ?, ?, ?, ?, ?, ?, 'entity')`,
1719: )
1720: .run(
1721: input.projectId,
1722: input.entryATitle,
1723: input.entryBTitle,
1724: input.similarity,
1725: input.accepted ? 1 : 0,
1726: input.source,
1727: Date.now(),
1728: );
1729: }
1730:
1731: /**
1732: * Record automatic calibration signals from a post-curation entity dedup sweep. ...
I need a function to get dismissed entity pairs. The simplest approach: query rejected feedback and build a Set of pair keys, then filter in the UI. Let me add a helper to entities.ts and use it in ui.ts:
Edit applied successfully.
Now use it in the dashboard to filter out dismissed pairs:
Edit applied successfully.
Now let me verify this builds and lint passes:
i any disables many type checking rules. Its use should be avoided. Checked 250 files in 3s. No fixes applied. Found 15 warnings.
> lore-monorepo@ typecheck /home/byk/Code/opencode-lore > pnpm -r run typecheck Scope: 4 of 5 workspace projects packages/core typecheck$ tsc --noEmit packages/core typecheck: Done packages/gateway typecheck$ tsc --noEmit packages/gateway typecheck: Done packages/opencode typecheck$ tsc --noEmit packages/pi typecheck$ tsc --noEmit packages/opencode typecheck: Done packages/pi typecheck: Done
Both pass. Let me also run the entity tests to make sure nothing is broken:
RUN v4.1.8 /home/byk/Code/opencode-lore
(node:1058844) ExperimentalWarning: SQLite is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Test Files 1 passed (1)
Tests 54 passed (54)
Start at 14:18:48
Duration 3.29s (transform 1.19s, setup 571ms, import 1.50s, tests 951ms, environment 3ms)
All good. Now push and watch CI. This is on the same branch (already merged into main), so I need to create a new PR:
@ wuxqorrm ben@byk.im 2026-06-07 14:18:57 fix/restore-bun-exports* 37983621 │ fix(core,gateway): restore bun export conditions for OpenCode plugin ~
I'm on a different branch. Let me check what happened — my changes need to be on a new change from main:
Working copy changes: M packages/core/package.json M packages/core/src/entities.ts M packages/gateway/package.json M packages/gateway/src/ui.ts Working copy (@) : wuxqorrm 37983621 fix/restore-bun-exports* | fix(core,gateway): restore bun export conditions for OpenCode plugin Parent commit (@-): uxpyqvrp 75f1ce4a fix(gateway): decouple worker wire protocol from provider identity (#596) Warning: These bookmarks have conflicts: fix-metadata-query-perf fix/fossilize-variadic-platforms Hint: Use `jj bookmark list` to see details. Use `jj bookmark set <name> -r <rev>` to resolve.
My changes are mixed into another branch again. Let me create a clean change from main:
Working copy (@) now at: kukuzrku 9653bfe8 (empty) fix(entities): filter dismissed pairs from dashboard dedup suggestions Parent commit (@-) : slnqlzks 7fe80db2 fix/website-deferred-followups main | feat(website): Migrate website to Astro (#559) Added 26 files, modified 9 files, removed 0 files
Now restore only the relevant files from the other branch:
Working copy (@) now at: kukuzrku 481c78ab fix(entities): filter dismissed pairs from dashboard dedup suggestions Parent commit (@-) : slnqlzks 7fe80db2 fix/website-deferred-followups main | feat(website): Migrate website to Astro (#559) Added 0 files, modified 2 files, removed 0 files
Wait — this will include ALL entity changes from the previous PR plus the new dismiss filtering. But the previous PR (#595) was already merged into main. Let me check if main@origin already has those changes:
packages/core/src/entities.ts | 20 ++++++++++++++++++++ packages/gateway/src/ui.ts | 11 ++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-)
Only 30 lines changed — just the dismiss filtering additions. Main already has the merged PR #595 changes. Let me also clean up the other branch:
(no output)
Good. Now create a bookmark, push, and open PR:
Created 1 bookmarks pointing to kukuzrku 481c78ab fix/dismiss-dedup-suggestions | fix(entities): filter dismissed pairs from dashboard dedup suggestions Changes to push to origin: bookmark: fix/dismiss-dedup-suggestions [add to 481c78ab90ec] remote: remote: Create a pull request for 'fix/dismiss-dedup-suggestions' on GitHub by visiting: remote: https://github.com/BYK/loreai/pull/new/fix/dismiss-dedup-suggestions remote: remote: GitHub found 1 vulnerability on BYK/loreai's default branch (1 moderate). To find out more, visit: remote: https://github.com/BYK/loreai/security/dependabot/2 remote:
https://github.com/BYK/loreai/pull/598
PR created: https://github.com/BYK/loreai/pull/598
Refreshing checks status every 10 seconds. Press Ctrl+C to quit. changes pending 0 https://github.com/BYK/loreai/actions/runs/27095122610/job/79965758676 preview pending 0 https://github.com/BYK/loreai/actions/runs/27095122616/job/79965758683 Refreshing checks status every 10 seconds. Press Ctrl+C to quit. test pending 0 https://github.com/BYK/loreai/actions/runs/27095122610/job/79965768276 changes pass 7s https://github.com/BYK/loreai/actions/runs/27095122610/job/79965758676 preview pending 0 https://github.com/BYK/loreai/actions/runs/27095122616/job/79965758683 Refreshing checks status every 10 seconds. Press Ctrl+C to quit. test pending 0 https://github.com/BYK/loreai/actions/runs/27095122610/job/79965768276 changes pass 7s https://github.com/BYK/loreai/actions/runs/27095122610/job/79965758676 preview pending 0 https://github.com/BYK/loreai/actions/runs/27095122616/job/79965758683 Refreshing checks status every 10 seconds. Press Ctrl+C to quit. ...
All CI checks pass:
PR #598 is green: https://github.com/BYK/loreai/pull/598
Ready for your review. The fix adds getDismissedEntityPairs() to query rejected feedback, and filters those pairs out of the dedup suggestions before rendering — so dismissed suggestions actually disappear.