pub struct StructuredFilter {Show 14 fields
pub assignee: Option<String>,
pub label: Option<String>,
pub status: Option<String>,
pub priority: Option<String>,
pub cycle: Option<String>,
pub milestone: Option<String>,
pub reviewer: Option<String>,
pub start_date: Option<String>,
pub due_date: Option<String>,
pub overdue: Option<bool>,
pub parent: Option<String>,
pub category: Option<String>,
pub scope: Option<Vec<String>>,
pub archived: Option<bool>,
}Expand description
Typed, extensible structured filter (FR-007..FR-010, FR-011).
Every criterion is optional; an None/empty criterion narrows nothing.
Criteria match case-insensitively against the serialized corpus text
(the same haystack fuzzy_score runs over), following the existing
fuzzy matcher’s to_lowercase() behavior — this keeps the predicate a
pure, config-independent function of the corpus (WP02 runs in parallel
with WP01 for exactly that reason).
Fields§
§assignee: Option<String>§label: Option<String>§status: Option<String>§priority: Option<String>§cycle: Option<String>§milestone: Option<String>§reviewer: Option<String>§start_date: Option<String>§due_date: Option<String>§overdue: Option<bool>§parent: Option<String>The referenced parent, as a resolved issue number (WP06 T025).
A number, not the selector the user typed, because matching happens against
an issue’s rendered frontmatter and that carries parent: 7. Resolution
happens once, at the command layer, where the store is available; matching
then stays a pure string comparison and query_matches keeps its
store-free signature.
Direct children only, and by construction: an issue’s parent: field is
its direct parent, so a grandchild’s file says parent: 2 and never matches
a criterion of parent: 1. A transitive implementation would have to walk
the chain explicitly, and this way there is nothing to get wrong.
category: Option<String>A workflow category key (WP08 T035), never a column name.
A category key rather than a column, because several columns can share one
category and the whole point of the criterion is to select across that
boundary: category:completed on a board with shipped, done and
released all complete returns every one of them, where status:done
returns one.
Validated through the service’s from_key when the query runs
([resolve_category_criterion]), so this never holds a value the
vocabulary does not contain. The TUI modal types into it unchecked — see
FilterField::value_mut for why that is the right layer to do it at.
scope: Option<Vec<String>>The columns a scope: token selected (WP02 T010).
A set of column names, not a name, because the columns are not
knowable from the query: scope:triage means “the columns [triage]
names”, and only the config can answer that — the same reason parent:
and category: are resolved rather than matched from bytes. Filled by
resolve_filter_criteria through
crate::triage::inbox::scope_columns, and judged by
Self::scope_matches, which needs the issue the way category does.
Deliberately not a FilterField: the filter modal has no row for
it and no user can type a scope into it, and appending a variant would
change FilterField::ALL’s length — which the modal’s row layout, its
click mapping and its own tests all count. The extensibility promise on
FilterField is about criteria a user can enter; this is a resolved
selection a token produces, and overdue already shows a
StructuredFilter field that is not a modal row.
archived: Option<bool>Whether the issue is archived (issue #1492), from an
archived:true|false token.
A derived criterion, like overdue and scope, and for the same
reason: archived is not a frontmatter key. The marker is the leading dot
of the issue’s filename, mirrored onto Issue::archived, so nothing in
the serialized bytes can answer the question and the token is lifted out
by resolve_filter_criteria into this field, then judged by
Self::archived_matches, which has the issue.
None (no token) narrows nothing.
Implementations§
Source§impl StructuredFilter
impl StructuredFilter
Sourcepub fn criteria(&self) -> impl Iterator<Item = (FilterField, &str)>
pub fn criteria(&self) -> impl Iterator<Item = (FilterField, &str)>
The active (set, non-empty) criteria, in FilterField::ALL order.
Sourcepub fn has_active_criteria(&self) -> bool
pub fn has_active_criteria(&self) -> bool
True when at least one criterion is active. When false the board must render unchanged (no criteria → pass-through).
The scope criterion is counted here even though it is not a
FilterField and so never appears in Self::criteria: a query that
selected rows is active however it selected them, and omitting it would
make has_active_criteria disagree with Self::evaluate_issue about
whether the same filter narrowed anything.
Sourcepub fn evaluate(&self, haystack: &str) -> bool
pub fn evaluate(&self, haystack: &str) -> bool
AND predicate over the serialized corpus text (FR-012, C-002).
Applies every active criterion; the first miss short-circuits the
entry to false. The caller (WP03’s build_filtered) invokes this
once per corpus entry, giving one linear O(entries) pass (NFR-001)
— haystack is lowercased once and each active criterion is a single
substring contains on that pre-lowered text, with no nested
field×corpus loops.
Matching is case-insensitive and substring-based, consistently across
assignee/label/status/priority, exactly as WP02’s prompt specifies
(T009). The stricter per-criterion “equals”/“contains-in-labels” rules
in contracts/filtering.md would require re-parsing the issue; on the
serialized corpus the substring rule is the single consistent gate.
Sourcepub fn evaluate_issue(
&self,
issue: &Issue,
text: &str,
resolution: &CategoryResolution,
) -> bool
pub fn evaluate_issue( &self, issue: &Issue, text: &str, resolution: &CategoryResolution, ) -> bool
The full structured predicate for an issue, including the criteria
Self::evaluate structurally cannot judge.
WP08 T035. [evaluate] takes a haystack and is therefore store-free; the
category criterion needs the issue’s status and the board’s
category_map, so it is judged here instead, where both are available.
The two halves AND together, exactly as evaluate ANDs its own criteria.
text is passed in rather than re-serialized from issue, because
Corpus already holds the exact bytes every issue was matched against and
a second to_file_contents() per issue per keystroke is measurable work
for an identical string.
Sourcepub fn archived_matches(&self, issue: &Issue) -> bool
pub fn archived_matches(&self, issue: &Issue) -> bool
Whether issue’s archived state matches the archived: criterion
(issue #1492).
Judged here rather than in Self::evaluate because the archived flag is
derived from the filename and never appears in the haystack; None
narrows nothing, and a set value is an exact bool comparison —
archived:false selects the active issues, archived:true the archived
ones.
Sourcepub fn scope_matches(&self, issue: &Issue) -> bool
pub fn scope_matches(&self, issue: &Issue) -> bool
Whether issue is in one of the columns the scope: token selected.
WP02 T010. The same three-way reasoning as Self::category_matches,
in reverse: a scope is a membership set, so an issue either has a
status in it or does not, and there is no category to default. None (no
token) passes everything — an unset criterion narrows nothing — and a set
that fails to contain issue.status fails closed, because a scope the
issue is not in is precisely what was asked for.
Judged here rather than in Self::evaluate for the reason that
function gives: evaluate has a haystack and no issue, and the issue’s
status is not reliably in the haystack in a form this can compare —
status: triage is a line of frontmatter, but a scope is a set of
columns and substring-matching the set against the file would call an
issue whose title contains todo a member of the todo column.
Sourcepub fn category_matches(
&self,
issue: &Issue,
resolution: &CategoryResolution,
) -> bool
pub fn category_matches( &self, issue: &Issue, resolution: &CategoryResolution, ) -> bool
Whether issue is in the requested category (WP08 T035).
Three cases, and the third is the one the surfaces already distinguish:
- A mapped column → its category, from the service’s resolution.
- A configured but unmapped column → the safe default, because
that is what it resolves to. T035 step 3 asks for this explicitly, and it
is what keeps the criterion consistent with
tix workflow show, which reports the same default for the same column. - A column the board does not configure → matches nothing. There
is no category to report:
resolveisNone,tix list --show categoryprints(unmapped), and MCP returnscategory: null(WP07). A criterion has to agree with the surfaces that show the same fact, and silently folding this into the safe default would makecategory:unstartedreturn issues every other surface call unmapped.
The unknown-category case is not handled here and cannot be: a value the
vocabulary does not contain is an error at the command layer
([resolve_category_criterion]), not a match decision here. A criterion
that reached this function unchecked compares against a key nothing
resolves to and therefore matches nothing — the same empty result an
unknown status: gives, which is the established behaviour for a bad value
in a haystack criterion.
Trait Implementations§
Source§impl Clone for StructuredFilter
impl Clone for StructuredFilter
Source§impl Debug for StructuredFilter
impl Debug for StructuredFilter
Source§impl Default for StructuredFilter
impl Default for StructuredFilter
Source§impl<'de> Deserialize<'de> for StructuredFilter
impl<'de> Deserialize<'de> for StructuredFilter
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Display for StructuredFilter
impl Display for StructuredFilter
Source§fn fmt(&self, f: &mut Formatter<'_>) -> Result
fn fmt(&self, f: &mut Formatter<'_>) -> Result
Diagnostics for the hint bar: assignee:alice, status:todo, or
(no active criteria).
Built as a list rather than written field by field, because there are
now three sources of a part — the FilterField criteria, an active
scope, and overdue — and the first version returned early when the
criteria were empty. A scope-only filter would then have printed
(no active criteria) while actively filtering, which is the hint bar
telling the user the opposite of what the board is doing.
impl Eq for StructuredFilter
Source§impl PartialEq for StructuredFilter
impl PartialEq for StructuredFilter
Source§impl Serialize for StructuredFilter
impl Serialize for StructuredFilter
impl StructuralPartialEq for StructuredFilter
Auto Trait Implementations§
impl Freeze for StructuredFilter
impl RefUnwindSafe for StructuredFilter
impl Send for StructuredFilter
impl Sync for StructuredFilter
impl Unpin for StructuredFilter
impl UnsafeUnpin for StructuredFilter
impl UnwindSafe for StructuredFilter
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§fn equivalent(&self, key: &K) -> bool
fn equivalent(&self, key: &K) -> bool
key and return true if they are equal.Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> ToCompactString for Twhere
T: Display,
impl<T> ToCompactString for Twhere
T: Display,
Source§fn try_to_compact_string(&self) -> Result<CompactString, ToCompactStringError>
fn try_to_compact_string(&self) -> Result<CompactString, ToCompactStringError>
ToCompactString::to_compact_string() Read moreSource§fn to_compact_string(&self) -> CompactString
fn to_compact_string(&self) -> CompactString
CompactString. Read more