Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

`GambitManager.from()` breaks negated filters: `-is:` gambits lose their `-` and negated key-value gambits throw

Closed Beginner friendly
#5,179 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@HawkForge is already working on this.

Since Oct 15, 2026.

  • #5202 by @HawkForge — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
88/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript, php

Research direction

Start in framework/core/js/src/common/GambitManager.ts and trace GambitManager.from() alongside apply() to see how filter keys and values are converted. Add unit coverage for negated yes/no gambits and key-value gambits, then run the relevant GambitManager tests. Done means the filters round-trip correctly and the reported search link restores test -author:admin without an error.

Written by the indexing model from the issue text.

Description

Summary

GambitManager.from() turns API filters back into search text. It mishandles negated filter keys (-author, -unread, …). This affects every page that rebuilds its search box from the URL (GlobalSearchState.searchToQuery()), so it breaks the search modal's "Search all discussions" link whenever the query has a negated gambit.

Steps to reproduce

As a logged-in member, open the search modal and search for:

  1. test -author:admin, then choose Search all discussions. The link is /?q=test&filter[-author]=admin. The index page throws TypeError: Cannot read properties of undefined (reading 'toString') (from filterValueToGambitValue), shows no discussions, and the header search falls back to "Search Forum".
  2. test -is:unread, then choose Search all discussions. The list is filtered correctly, but the header search shows test is:unread. Searching again from there returns the opposite results.

Results of from('discussions', 'foo', filter):

filter result
{ author: 'admin' } foo author:admin
{ '-author': 'admin' } throws
{ '-created': '2026-01-01' } throws
{ '-hidden': true } foo is:hidden
{ '-unread': true } foo is:unread

Extensions' gambits behave the same way. For example, fof/best-answer's -is:solved comes back as is:solved.

Cause

framework/core/js/src/common/GambitManager.ts, from():

Object.keys(filter).forEach((key) => {
  for (const gambit of gambits) {
    const negate = key[0] === '-';

    if (negate) key = key.substring(1);

    if (gambit.filterKey() !== key) continue;

    q += ` ${gambit.fromFilter(filter[key], negate)}`;
  }
});

There are two bugs:

  1. key is reassigned inside the per-gambit loop. The first gambit, AuthorGambit for discussions, strips the -. Every later gambit then sees negate === false, so negated yes/no gambits come back un-negated.
  2. The value is read with the stripped key. filter['author'] is undefined when the filter holds '-author'. Key-value gambits then call undefined.toString() in filterValueToGambitValue(). That's why -author throws even though AuthorGambit is first and still sees negate === true.
Fix

Work out the negation and the bare key once per filter key, outside the gambit loop, and read the value with the original key:

Object.keys(filter).forEach((filterKey) => {
  const negate = filterKey[0] === '-';
  const key = negate ? filterKey.substring(1) : filterKey;

  for (const gambit of gambits) {
    if (gambit.filterKey() !== key) continue;

    q += ` ${gambit.fromFilter(filter[filterKey], negate)}`;
  }
});
Done when
  • from() round-trips apply() for negated yes/no gambits that aren't first in the list (-is:unread, -is:hidden) and for negated key-value gambits (-author:x, -created:x).
  • The search modal's Search all discussions link for test -author:admin opens the index without an error, with test -author:admin in the search box.
  • A unit test covers GambitManager.from() with negated filters.
Dominant language
PHP
Stars
6.8k
Forks
887
Avg merge
12h 56m
Merged PRs (30d)
117

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from flarum/framework

All issues in flarum/framework

Similar issues

More PHP issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.