List is strictly newest-first; search only filters
Drop the recency/pin/usage score bonuses that reordered the picker: an entry pasted before outranked a fresh copy for hours. Results are now sorted by timestamp only; query tokens and fuzzy terms filter rows.
This commit is contained in:
+1
-1
@@ -10,7 +10,7 @@ import "Classify.js" as Classify
|
|||||||
|
|
||||||
// Clipboard history picker — Raycast-style: fuzzy search bar, result list,
|
// Clipboard history picker — Raycast-style: fuzzy search bar, result list,
|
||||||
// and a per-type preview pane. Clone of omarchy.clipboard with richer
|
// and a per-type preview pane. Clone of omarchy.clipboard with richer
|
||||||
// capture metadata (app, size, dims, pins, usage) and full-text fuzzy search.
|
// capture metadata (app, size, dims, pins, usage) and full-text fuzzy filtering.
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ function fuzzyMatch(needle, haystack) {
|
|||||||
score += 8
|
score += 8
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gap penalty (light — we rank mostly by bonuses and recency).
|
// Gap penalty (light).
|
||||||
if (k > 0) {
|
if (k > 0) {
|
||||||
var gap = pos - positions[k - 1] - 1
|
var gap = pos - positions[k - 1] - 1
|
||||||
if (gap > 0) score -= Math.min(6, gap)
|
if (gap > 0) score -= Math.min(6, gap)
|
||||||
@@ -227,7 +227,7 @@ function recencyBonus(ts, now) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// rows: [{ entry, content, app, type, ts, pinned, uses, bytes }]
|
// rows: [{ entry, content, app, type, ts, pinned, uses, bytes }]
|
||||||
// Returns up to `limit` rows sorted by relevance: [{ row, score, positions, type }]
|
// Returns up to `limit` matching rows, newest first: [{ row, score, positions, type }]
|
||||||
function searchRows(rows, queryStr, now, limit) {
|
function searchRows(rows, queryStr, now, limit) {
|
||||||
var parsed = parseQuery(queryStr)
|
var parsed = parseQuery(queryStr)
|
||||||
var results = []
|
var results = []
|
||||||
@@ -255,11 +255,9 @@ function searchRows(rows, queryStr, now, limit) {
|
|||||||
if (!matched) continue
|
if (!matched) continue
|
||||||
}
|
}
|
||||||
|
|
||||||
var score = matched ? matched.score : 0
|
// Search only filters. The list is always ordered newest-first; no
|
||||||
score += recencyBonus(row.ts, now)
|
// relevance, pin, or usage ranking.
|
||||||
if (row.entry.pinned) score += 500
|
var score = 0
|
||||||
if (row.uses > 0) score += 4 * Math.min(10, row.uses)
|
|
||||||
|
|
||||||
results.push({
|
results.push({
|
||||||
row: row,
|
row: row,
|
||||||
score: score,
|
score: score,
|
||||||
@@ -268,7 +266,6 @@ function searchRows(rows, queryStr, now, limit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
results.sort(function(a, b) {
|
results.sort(function(a, b) {
|
||||||
if (b.score !== a.score) return b.score - a.score
|
|
||||||
return (b.row.ts || 0) - (a.row.ts || 0)
|
return (b.row.ts || 0) - (a.row.ts || 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ full theme integration.
|
|||||||
|
|
||||||
## Highlights
|
## Highlights
|
||||||
|
|
||||||
- **Fuzzy search over everything** — fzf-style scoring across content, source
|
- **Fuzzy search over everything** — fzf-style matching across content, source
|
||||||
app, and type, boosted by recency, pins, and usage. Query tokens:
|
app, and type. The list is always newest-first; search only filters it. Query tokens:
|
||||||
- `type:image|link|text|files|code|json|color|email|html|number` (prefix match)
|
- `type:image|link|text|files|code|json|color|email|html|number` (prefix match)
|
||||||
- `app:firefox` — fuzzy match on the source app
|
- `app:firefox` — fuzzy match on the source app
|
||||||
- `is:pinned`, `today`, `yesterday`, `week`, `<2h`, `>30s`, `<3d`
|
- `is:pinned`, `today`, `yesterday`, `week`, `<2h`, `>30s`, `<3d`
|
||||||
|
|||||||
+21
-4
@@ -79,6 +79,21 @@ test("searchRows empty query sorts by recency", () => {
|
|||||||
assert.equal(res[0].row.content, "new")
|
assert.equal(res[0].row.content, "new")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("searchRows: always newest first, pins and pastes do not reorder", () => {
|
||||||
|
const now = 1000000
|
||||||
|
const rows = [
|
||||||
|
{ entry: {}, content: "pasted twice, 5 min ago", app: "", type: "text", ts: now - 300, pinned: false, uses: 2, bytes: 3 },
|
||||||
|
{ entry: {}, content: "just copied", app: "", type: "text", ts: now - 5, pinned: false, uses: 0, bytes: 3 },
|
||||||
|
{ entry: { pinned: true }, content: "pinned, old", app: "", type: "text", ts: now - 86400 * 3, pinned: true, uses: 0, bytes: 3 }
|
||||||
|
]
|
||||||
|
const res = Fuzzy.searchRows(rows, "", now, 10)
|
||||||
|
assert.deepEqual(Array.from(res, r => r.row.content), ["just copied", "pasted twice, 5 min ago", "pinned, old"])
|
||||||
|
const typed = Fuzzy.searchRows(rows, "type:text <1h", now, 10)
|
||||||
|
assert.deepEqual(Array.from(typed, r => r.row.content), ["just copied", "pasted twice, 5 min ago"])
|
||||||
|
const searched = Fuzzy.searchRows(rows, "past", now, 10)
|
||||||
|
assert.deepEqual(Array.from(searched, r => r.row.content), ["pasted twice, 5 min ago"])
|
||||||
|
})
|
||||||
|
|
||||||
test("searchRows fuzzy ranks match above recency when strong", () => {
|
test("searchRows fuzzy ranks match above recency when strong", () => {
|
||||||
const now = 1000000
|
const now = 1000000
|
||||||
const rows = [
|
const rows = [
|
||||||
@@ -122,14 +137,16 @@ test("searchRows matches app field", () => {
|
|||||||
assert.equal(res[0].row.app, "firefox")
|
assert.equal(res[0].row.app, "firefox")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("searchRows pinned boost wins", () => {
|
test("searchRows is:pinned filters without reordering", () => {
|
||||||
const now = 1000000
|
const now = 1000000
|
||||||
const rows = [
|
const rows = [
|
||||||
{ entry: { pinned: true }, content: "zzz pinned weak", app: "", type: "text", ts: now - 86400 * 10, pinned: true, uses: 0, bytes: 3 },
|
{ entry: { pinned: true }, content: "zzz pinned old", app: "", type: "text", ts: now - 86400 * 10, pinned: true, uses: 0, bytes: 3 },
|
||||||
{ entry: {}, content: "fresh", app: "", type: "text", ts: now, pinned: false, uses: 0, bytes: 3 }
|
{ entry: {}, content: "fresh", app: "", type: "text", ts: now, pinned: false, uses: 0, bytes: 3 }
|
||||||
]
|
]
|
||||||
const res = Fuzzy.searchRows(rows, "", now, 10)
|
assert.equal(Fuzzy.searchRows(rows, "", now, 10)[0].row.content, "fresh")
|
||||||
assert.equal(res[0].row.content, "zzz pinned weak")
|
const pinned = Fuzzy.searchRows(rows, "is:pinned", now, 10)
|
||||||
|
assert.equal(pinned.length, 1)
|
||||||
|
assert.equal(pinned[0].row.content, "zzz pinned old")
|
||||||
})
|
})
|
||||||
|
|
||||||
test("searchRows respects limit", () => {
|
test("searchRows respects limit", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user