When searching inside comments is ON, the autocomplete dropdown now returns
nothing from ES (only usernames show). This avoids showing title-only matches
that are inconsistent with the full comment-body search results.
Also passes filter[autocomplete]=1 to the API so the server uses the fast
title.autocomplete path instead of the full has_child query.
Add a stem-exclusion textarea to the admin settings page. Words entered here
(one per line) are passed as stem_exclusion to the language analyzer, preventing
them from being stemmed. Requires a full re-index to take effect.
The two language-dependent fields (stem exclusion, min search length) are
disabled when CJK is selected, since neither applies to bigram analysis.
Settings are reordered: search-scope switches now appear above the language
block so the language-dependent fields sit directly below the language selector.
Replace the lowercase ES identifier labels in the language dropdown with proper
display names (e.g. "Brazilian Portuguese", "CJK (Chinese, Japanese, Korean)",
"Sorani (Kurdish)"). Drift detection now tracks stem exclusion instead of the
removed min_gram index setting.
When filter[autocomplete]=1 is present, bypass the full has_child query
and run a single MatchQuery on title.autocomplete (edge-ngram prefix match).
Results are sorted by updated_at desc to stay consistent with full search.
Replace the asymmetric ngram (index) / language (search) setup on content
with a symmetric language analyzer at both index and search time, eliminating
zero-hit cases caused by stemmed tokens falling below min_gram (e.g. Turkish).
Add a title field to discussion documents with a title.autocomplete edge-ngram
subfield (min=2, max=15) for prefix instant search, and a flarum_title_search
analyzer (lowercase only) as its search-time counterpart.
Remove flarum_analyzer_partial / partial_search_filter / search_analyzer from
the content mapping — no longer needed with symmetric analysis.
Bump INDEX_COMPAT_VERSION to v3. Also fix MatchQuery::toArray() to serialize
the analyzer property which was stored but never emitted.
When params.q is set, Flarum core suppresses TerminalPost and only shows an
excerpt from mostRelevantPost. This leaves the info section empty when the post
is null (e.g. hidden/deleted first post in production) or when results are
ordered by a field sort where an excerpt is meaningless.
Extend DiscussionListItem.infoItems to:
- Replace excerpt with TerminalPost when a field sort (latest, oldest, top) is
active — relevance excerpts are not meaningful when sorted by date/count.
- Fall back to TerminalPost when no excerpt was added (mostRelevantPost null).
- Search with no explicit sort now defaults to latest (updated_at desc)
on both backend and frontend, matching the forum UX expectation.
- "All discussions" link from the search box routes to /?q=...&sort=latest
so the sort dropdown shows "Latest" immediately after submission.
- extendDiscussionState injects sort=-lastPostedAt into API params when
no sort is present, keeping direct-URL navigation (?q=anan) consistent
with the backend default.
- Backend guard changed from phpSortField===null to empty($sorts), which
is the correct sentinel since $sorts is the authoritative ES sort list.
- Default sort changed from relevance to updated_at desc. This lets
has_child use score_mode:none, which skips child scoring entirely
and lets ES short-circuit early on large corpora.
- When a field sort is requested, score_mode is also none for the same
reason. score_mode:sum is only used when sorting by relevance.
- track_total_hits:false avoids a full-index count on every query,
allowing ES to stop once it has collected enough results.
- Strip all gambit operators (tag:foo, author:bar, is:unread, etc.)
from the ES query string, not just is:private. Leaving them in caused
operator:and to require the gambit tokens to appear literally in post
content, producing zero results when gambits were combined with text.
Without minimum_should_match=1 on the inner post bool query, Elasticsearch
defaults MSM to 0 whenever a filter clause is present. This caused has_child
to score every non-hidden post on every search request, saturating CPU on
shared ES nodes with large corpora (3.7M+ posts).
- Add BoolQuery subclass with create() override and minimumShouldMatch()
(spatie/elasticsearch-query-builder 1.x uses `new self()` in create(),
so subclassing requires overriding it)
- Set minimumShouldMatch(1) on the inner post query after adding the
is_hidden filter, so only genuinely matching posts are scored
- Remove the operator('or') clause from buildShouldClauses() — with
Turkish min_ngram=2, 'or' generates 2-gram tokens that match nearly
every post, causing near-total index scans
- Add ES client timeouts (connect: 2s, query: 10s) to prevent Apache
mod_php worker saturation when ES is slow or unreachable
- Add relationships() to Seeder base class; DiscussionSeeder implements it to
return tags/recipientUsers/recipientGroups for eager loading
- UpdateSearchJob calls loadMissing(relationships()) before the map loop,
eliminating N×R lazy-load queries per batch
- Remove refresh:true from bulk call — synchronous ES refresh was the primary
throughput bottleneck during indexing
- Cache viewForum permissions in DiscussionSeeder per job instance, replacing
N×2 Permission queries per document with a single cached query
- Increase seeder batch size from 1000 to 2500
- Write INDEX_COMPAT_VERSION into mapping _meta.index_compat_version on every
build/promote so the version travels with the index rather than being a
separate settings value
- saveIndexedConfig now reads compat version back from _meta alongside the
existing analyzer/min_gram sync, so rollback automatically restores the
correct value (or null for pre-feature indexes, triggering the reindex warning)
- Replace InvalidArgumentException in buildIndexSettings with $this->error()
fallback to avoid stack traces in the console
- Fix stale comment in ViewsSearchJob referencing removed CLI flags
Adds a 'Minimum search query length' setting (1–4, default 3). The admin
shows a warning when the analyzer or min-search-length have changed since
the last index build.
After a mapping change (e.g. the parent-child restructure), existing installs
need to rebuild their index. A DashboardWidget appears on the admin page
warning the admin and showing the command to run. Compatibility is tracked
via the blomstra-search.index-compatible setting.
The flag-based interface (--rebuild etc) became rather convoluted with the changes. Replace this with commands with more straight forward naming.
Also added an explicit 'rollback' action and updated the README.md
- Switch to ES parent/child join_field: discussion docs hold all metadata,
post docs hold content only. Routing ensures parent and child land on the
same shard. Removes the old flat-document approach where post data was
duplicated onto every comment document.
- Add HasChildQuery with inner_hits so the best-matching post ID is
surfaced as mostRelevantPost without a second DB query.
- Blue-green index rebuilds: --recreate writes into a timestamped pending
index, --swap atomically promotes it via alias. Interrupted builds are
resumable with --recreate --continue. --swap requires confirmation before
proceeding.
- Index hidden posts; non-moderators are filtered at query time via
is_hidden on the has_child clause. Core\Hidden and Core\Restored trigger
re-indexing so moderators with post.hide can search hidden posts.