clone

a personal forge on plain git: static pages, stacked-diff reviews, signed local ci, repo sites and releases

Count a run of whitespace in a quoted phrase as one space

cat committed bd87ef9
The search indexes collapse whitespace (commit bodies and wiki text are
flattened to single spaces), so a phrase typed with two spaces, like
"fix  the", could never match. Now a run of whitespace inside a quoted
phrase is one space, in every section and in code search alike, which
keeps the two reading a query the same way. Spaces at a phrase's ends
stay, one each. Code lines keep their own whitespace, so a phrase no
longer finds code aligned with several spaces.

The bench's default queries fetch and find exactly what they did. The
search-phrases scenario adds spaced-out phrases to its table and checks
that one matches the same commits as its single-spaced form.
commit
bd87ef9d2ca817e26a9e16ea2120e25dac4590af
parent
f72b3f833fcb3a5010a02d5331e6557ed33d156c
author
cat <admin@dev.site>
ci
✓ passed in 93s, signed by cat
kind
feat risk score 1.7 of 3, Jev's estimate
review
#3 Match quoted phrases in every search section
ci attestation
forge-attestation 1
type ci
commit bd87ef9d2ca817e26a9e16ea2120e25dac4590af
tree 6f6691b90884b25cc1259db50cef86a663cc786a
script .forge/ci e6f038ae810b87f90a4be115a762a9c91dd76a01
result pass
exit 0
started 2026-10-18T22:51:12Z
duration 93s
runner (not shown here; refs/notes/ci has it)
system Darwin 25.5.0 arm64
env host
log sha256:e7e334403239916a58d755a579fe49728bdf5bb510b5c91d26919846380ef02f 240 lines
|remote: review #4: /dami/greet/reviews/4/
|pushed wip/i18n
|950ef96 running .forge/ci: greet.sh: say what it is
|  | sh: /bin/sh
|  | ok   greet
|950ef96 pass in 1s, signed
|pushed main
|remote: review #4: /dami/greet/reviews/4/
|pushed wip/i18n
|f61c51b running .forge/ci: wave at the world
|  | sh: /bin/sh
|  | FAIL greet : got 'hello, world o/', want 'hello, world'
|f61c51b fail in 1s, signed
|forge: 1 commit(s) did not pass
|remote: review #6: /dami/greet/reviews/6/
|pushed ana/wave
|e8c0d33 running .forge/ci: update the test for the wave
|  | sh: /bin/sh
|  | ok   greet
|e8c0d33 pass in 0s, signed
|remote: review #6: /dami/greet/reviews/6/
|pushed ana/wave
|warning: You appear to have cloned an empty repository.
|site home: deployed 2271269 -> /sites/ana/dotfiles/home/
|created ana/notes
|remote: review #1: /ana/notes/reviews/1/
|pushed ana/todo
|landing #1 A todo list: ana/todo -> main
|d15fcff running .forge/ci: web, server and Python versions of greet
|  | sh: /bin/sh
|  | ok   greet
|d15fcff pass in 0s, signed
|pushed main
|2a70689 running .forge/ci: messages: notes for the later regions
|  | sh: /bin/sh
|  | ok   greet
|2a70689 pass in 0s, signed
|remote: review #7: /dami/greet/reviews/7/
|pushed dami/regions
|created dami/forge
|warning: You appear to have cloned an empty repository.
|site guide: deployed 55c6dda -> /sites/dami/forge/guide/
|(node:50780) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built ana/dotfiles in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:50912) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built ana/notes in 1s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:51094) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built dami/forge in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:51253) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built dami/greet in 2s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|(node:51927) ExperimentalWarning: stripTypeScriptTypes is an experimental feature and might change at any time
|(Use `node --trace-warnings ...` to show where the warning was created)
|built - in 0s -> /Users/dami/Library/Caches/forge/ci/35f7a65d0235/demo/root/.forge/www
|226 of 226 pages render the same without 11ty
|guide: 4 files, links ok
|ok

Signature: good, cat

4 files changed, +25 -9
MREADME.md8++++++--
Msite/assets/codesearch.ts3++-
Msite/assets/query.ts14++++++++------
Mtest/scenarios/search-phrases.sh9+++++++++
M README.md +6 -2
@@ -557,7 +557,9 @@ are cut to 240 characters and wiki pages to 2000, to keep the index small.
557557 558558 Every section reads a query the same way (`site/assets/query.ts`): terms559559 split on whitespace, in any order, and a quoted phrase (`"fix the search"`)560-is one term, spaces and all, that must appear as written. Every term must560+is one term, spaces and all, that must appear as written, except that a run561+of whitespace in it counts as one space, as in the indexes, which collapse562+whitespace (`"fix the"` is `"fix the"`). Every term must561563 match, ignoring case, and results mark each match, a phrase as a whole. A562564 quote opens a phrase only at the start of a term (in `it's"` it is just a563565 character), an unclosed quote runs to the end of the query as a phrase, so a
@@ -584,7 +586,9 @@ goes to a worker, and a Code section lists each line that has every word,
584586 ignoring case, as `path:line`, linked to that line of the file's page (the585587 source page for markdown). Its words are the terms the other sections read,586588 so a quoted phrase (`"import fs from"`) is one literal string, spaces and587-all; a word inside another goes, since a line with the longer one has it.589+all, a run of whitespace as one space as everywhere (so it won't find code590+aligned with several spaces); a word inside another goes, since a line with591+the longer one has it.588592 Words of 3 characters or more are looked up589593 in the index; shorter ones only filter the lines it finds. It loads nothing590594 until then: the first code query starts the worker, which loads
M site/assets/codesearch.ts +2 -1
M site/assets/query.ts +8 -6
M test/scenarios/search-phrases.sh +9 -0