Skip to main content

StructuredFilter

Struct StructuredFilter 

Source
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

Source

pub fn criteria(&self) -> impl Iterator<Item = (FilterField, &str)>

The active (set, non-empty) criteria, in FilterField::ALL order.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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.

Source

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:

  1. A mapped column → its category, from the service’s resolution.
  2. 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.
  3. A column the board does not configure → matches nothing. There is no category to report: resolve is None, tix list --show category prints (unmapped), and MCP returns category: null (WP07). A criterion has to agree with the surfaces that show the same fact, and silently folding this into the safe default would make category:unstarted return 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.

Source

pub fn normalize(&mut self)

Trim every criterion; values that trim to empty become None (the canonical “unset” state). Used by the filter modal’s commit path.

Trait Implementations§

Source§

impl Clone for StructuredFilter

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for StructuredFilter

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for StructuredFilter

Source§

fn default() -> Self

Returns the “default value” for a type. Read more
Source§

impl<'de> Deserialize<'de> for StructuredFilter

Source§

fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>
where __D: Deserializer<'de>,

Deserialize this value from the given Serde deserializer. Read more
Source§

impl Display for StructuredFilter

Source§

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.

Source§

impl Eq for StructuredFilter

Source§

impl PartialEq for StructuredFilter

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl Serialize for StructuredFilter

Source§

fn serialize<__S>(&self, __serializer: __S) -> Result<__S::Ok, __S::Error>
where __S: Serializer,

Serialize this value into the given Serde serializer. Read more
Source§

impl StructuralPartialEq for StructuredFilter

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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 more
Source§

impl<T> ToCompactString for T
where T: Display,

Source§

impl<T> ToLine for T
where T: Display,

Source§

fn to_line(&self) -> Line<'_>

Converts the value to a Line.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToSpan for T
where T: Display,

Source§

fn to_span(&self) -> Span<'_>

Converts the value to a Span.
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T> ToText for T
where T: Display,

Source§

fn to_text(&self) -> Text<'_>

Converts the value to a Text.
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.