A facet has three states: filter in, filter out, or do not care - #18
Conversation
FacetChoice was a value or nothing, so the panel could ask "only Evennia" and could not ask "anything but Evennia" -- a question the catalogue can answer and the interface had no way to put. It now carries a polarity, and every choice facet gets it at once: charset, codebase, family, genre and language, plus the codebase-family filter the reference pages link in on. Null still means the facet is not being asked about, which is the state that must not be confused with either of the others. The polarity is applied ONCE, TO THE ANSWER, and never per token. A facet can hand one row several tokens -- a game reached an hour ago is in the last day, the last week and the last month -- so inverting the comparison per token would make an exclusion mean "some token differs", which every multi-token row satisfies. Covers() compares the value and Admits() applies the polarity, and they are separate methods so the call site cannot collapse them by accident. An excluded value does not drop the games that have no value for the facet. The tempting reading of "not Evennia" is "has a codebase, and it is not Evennia", which quietly discards every game whose codebase we never identified -- turning a gap in our own measurement into a property of those games. Not identifying a codebase is a measurement, and it is not a measurement of being Evennia. The unknown can itself be excluded, which is the separate question of "games whose codebase we did identify", and there is a test for each. In a URL an exclusion is a leading bang, and a value that genuinely starts with one is doubled so it stays reachable rather than reading as its own negation. The panel offers both directions as two <optgroup>s in the same select -- the only way to carry a third state in a plain GET form with no script -- so the question is still entirely in the URL and a filtered listing is still linkable. One bug worth recording. Invert() is a method rather than a property because a record's generated ToString prints every public property, so a property returning another FacetChoice recursed until the stack ran out -- and it surfaced as an unrelated test dying inside an assertion message, with nothing pointing at facets. Verified against the real catalogue: every polarity pair partitions it exactly. 2 PennMUSH and 14 not, 1 Evennia and 15 not, 5 with no identified codebase and 11 with one, against 16 games. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 27 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (9)
Comment |
FacetChoicewas a value or nothing, so the panel could ask only Evennia and could not ask anything but Evennia — a question the catalogue can answer and the interface had no way to put.It now carries a polarity, and every choice facet gets it at once: charset, codebase, family, genre and language, plus the
codebase-familyfilter the reference pages link in on. Null still means the facet is not being asked about, which is the state that must not be confused with either of the others.Three decisions worth reviewing
Polarity is applied once, to the answer — never per token. A facet can hand one row several tokens (a game reached an hour ago is in the last day, the last week and the last month). Inverting per token would make an exclusion mean "some token differs", which every multi-token row satisfies — so
!daywould return everything.Covers()compares the value,Admits()applies the polarity, and they are separate methods so a call site cannot collapse them by accident.An excluded value does not drop games that have no value for that facet. The tempting reading of "not Evennia" is has a codebase, and it is not Evennia, which quietly discards every game whose codebase we never identified — turning a gap in our own measurement into a property of those games. Not identifying a codebase is a measurement, and it is not a measurement of being Evennia. The unknown can itself be excluded, which is the separate question games whose codebase we did identify; there is a test for each.
The panel offers both directions as two
<optgroup>s in one select. That is the only way to carry a third state in a plain GET form with no script, so the question stays entirely in the URL and a filtered listing stays linkable. In a URL an exclusion is a leading!, and a value that genuinely starts with one is doubled (!!important) so it stays reachable rather than reading as its own negation.One bug worth recording
Invert()is a method rather than a property because a record's generatedToStringprints every public property — so a property returning anotherFacetChoicerecursed until the stack ran out. It surfaced as an unrelated test dying inside an assertion message, with nothing pointing at facets.Verified against the real catalogue
Every polarity pair partitions it exactly:
codebase-family=PennMUSH/!PennMUSHcodebase=Evennia/!Evenniacodebase=~unknown/!~unknownFive suites green, zero warnings. Ten new tests in
FacetPolarityTests.Not in this change
Presence facets — the measured protocols and TLS — are a different shape (a set of checkboxes, not a choice) and keep their two states. "Games that do not offer GMCP" is the obvious next question and a good follow-up, but it is a different control and would have made this diff two features.
🤖 Generated with Claude Code
https://claude.ai/code/session_01NrGKmKcRCGktyhRTFbQDMk