diff --git a/openspec/changes/dashboard-overview/.openspec.yaml b/openspec/changes/archive/2026-07-14-dashboard-overview/.openspec.yaml similarity index 100% rename from openspec/changes/dashboard-overview/.openspec.yaml rename to openspec/changes/archive/2026-07-14-dashboard-overview/.openspec.yaml diff --git a/openspec/changes/dashboard-overview/design.md b/openspec/changes/archive/2026-07-14-dashboard-overview/design.md similarity index 100% rename from openspec/changes/dashboard-overview/design.md rename to openspec/changes/archive/2026-07-14-dashboard-overview/design.md diff --git a/openspec/changes/dashboard-overview/proposal.md b/openspec/changes/archive/2026-07-14-dashboard-overview/proposal.md similarity index 100% rename from openspec/changes/dashboard-overview/proposal.md rename to openspec/changes/archive/2026-07-14-dashboard-overview/proposal.md diff --git a/openspec/changes/dashboard-overview/specs/reporting/spec.md b/openspec/changes/archive/2026-07-14-dashboard-overview/specs/reporting/spec.md similarity index 100% rename from openspec/changes/dashboard-overview/specs/reporting/spec.md rename to openspec/changes/archive/2026-07-14-dashboard-overview/specs/reporting/spec.md diff --git a/openspec/changes/dashboard-overview/tasks.md b/openspec/changes/archive/2026-07-14-dashboard-overview/tasks.md similarity index 100% rename from openspec/changes/dashboard-overview/tasks.md rename to openspec/changes/archive/2026-07-14-dashboard-overview/tasks.md diff --git a/openspec/changes/rules-workbench/.openspec.yaml b/openspec/changes/archive/2026-07-14-rules-workbench/.openspec.yaml similarity index 100% rename from openspec/changes/rules-workbench/.openspec.yaml rename to openspec/changes/archive/2026-07-14-rules-workbench/.openspec.yaml diff --git a/openspec/changes/rules-workbench/design.md b/openspec/changes/archive/2026-07-14-rules-workbench/design.md similarity index 100% rename from openspec/changes/rules-workbench/design.md rename to openspec/changes/archive/2026-07-14-rules-workbench/design.md diff --git a/openspec/changes/rules-workbench/proposal.md b/openspec/changes/archive/2026-07-14-rules-workbench/proposal.md similarity index 100% rename from openspec/changes/rules-workbench/proposal.md rename to openspec/changes/archive/2026-07-14-rules-workbench/proposal.md diff --git a/openspec/changes/rules-workbench/specs/categorization/spec.md b/openspec/changes/archive/2026-07-14-rules-workbench/specs/categorization/spec.md similarity index 100% rename from openspec/changes/rules-workbench/specs/categorization/spec.md rename to openspec/changes/archive/2026-07-14-rules-workbench/specs/categorization/spec.md diff --git a/openspec/changes/rules-workbench/specs/reporting/spec.md b/openspec/changes/archive/2026-07-14-rules-workbench/specs/reporting/spec.md similarity index 100% rename from openspec/changes/rules-workbench/specs/reporting/spec.md rename to openspec/changes/archive/2026-07-14-rules-workbench/specs/reporting/spec.md diff --git a/openspec/changes/rules-workbench/tasks.md b/openspec/changes/archive/2026-07-14-rules-workbench/tasks.md similarity index 100% rename from openspec/changes/rules-workbench/tasks.md rename to openspec/changes/archive/2026-07-14-rules-workbench/tasks.md diff --git a/openspec/specs/categorization/spec.md b/openspec/specs/categorization/spec.md index a799ed5..57788c3 100644 --- a/openspec/specs/categorization/spec.md +++ b/openspec/specs/categorization/spec.md @@ -1,57 +1,120 @@ -# categorization Specification - -## Purpose -TBD - created by archiving change bootstrap-finance-app. Update Purpose after archive. -## Requirements -### Requirement: Category management -The system SHALL provide flat categories with a name and a kind of `income`, `expense`, or `transfer`. Users SHALL be able to create, rename, and deactivate categories. A built-in `Transfer` category (kind `transfer`) SHALL exist from initial migration and SHALL NOT be deletable. Transactions SHALL reference categories directly (never any grouping construct), keeping future category grouping purely additive. - -#### Scenario: Create a category -- **WHEN** a user creates a category with a name and kind -- **THEN** the category is available for rules and manual assignment - -#### Scenario: Built-in Transfer category is protected -- **WHEN** a user attempts to delete the built-in Transfer category -- **THEN** the system refuses - -### Requirement: Append-only categorization event log -The system SHALL record every category assignment as an immutable event: transaction id, category id, source (`rule`, `manual`, or `reconciliation`), the rule id for rule events, the acting user's DID for manual events, and a timestamp. A transaction's current category SHALL be the latest event, denormalized onto the transaction row in the same database transaction as the event insert. Events SHALL never be updated or deleted. - -#### Scenario: Manual categorization records actor -- **WHEN** an authenticated user assigns a category to a transaction -- **THEN** a `manual` event is appended with that user's DID and the transaction's cached category is updated atomically with it - -#### Scenario: Provenance is visible -- **WHEN** a user views a categorized transaction's history -- **THEN** the UI shows every event in order: what assigned it (rule pattern or person), to which category, and when - -#### Scenario: Recategorization preserves history -- **WHEN** a user changes an already-categorized transaction to a different category -- **THEN** a new event is appended and prior events remain queryable - -### Requirement: Rule-based auto-categorization -The system SHALL support categorization rules with match types `exact` and `contains`, matched case-insensitively against the raw transaction description and, when the provider supplies them, the payee and memo fields (a rule fires if any of these matches). Rules record their creator's DID and creation time. When multiple rules match one transaction, precedence SHALL be deterministic: `exact` beats `contains`, then longer pattern beats shorter, then newer rule beats older. Rule application SHALL append a `rule` event recording the winning rule's id. - -#### Scenario: Rule fires on new transaction at sync -- **WHEN** a sync ingests an uncategorized transaction whose description matches an active rule -- **THEN** the winning rule's category is applied via a `rule` event referencing that rule - -#### Scenario: Precedence between overlapping rules -- **WHEN** a description matches both `contains "AMAZON"` and `contains "AMAZON PRIME"` -- **THEN** the longer pattern's rule wins and the fired rule id is recorded on the event - -#### Scenario: Rule matches the payee or memo field -- **WHEN** a transaction's description is a terse bank label but its provider-supplied payee or memo matches an active rule -- **THEN** the rule fires exactly as if the description had matched - -### Requirement: Manual decisions outrank rules -The system SHALL never allow a rule to overwrite a categorization whose latest event is `manual`. Rules fire only on transactions with no current category. When a user creates a rule, the system SHALL offer to retroactively apply it to existing matching transactions that are currently uncategorized, and SHALL NOT touch categorized ones. - -#### Scenario: Rule does not overwrite manual choice -- **WHEN** a rule matching a transaction is created or runs, and that transaction's latest event is `manual` -- **THEN** the transaction's category is unchanged - -#### Scenario: Retroactive application on rule creation -- **WHEN** a user creates a rule and accepts the retroactive-apply offer -- **THEN** the rule is applied to all matching currently-uncategorized transactions, each receiving a `rule` event - +# categorization Specification + +## Purpose +TBD - created by archiving change bootstrap-finance-app. Update Purpose after archive. +## Requirements +### Requirement: Category management +The system SHALL provide flat categories with a name and a kind of `income`, `expense`, or `transfer`. Users SHALL be able to create, rename, and deactivate categories. A built-in `Transfer` category (kind `transfer`) SHALL exist from initial migration and SHALL NOT be deletable. Transactions SHALL reference categories directly (never any grouping construct), keeping future category grouping purely additive. + +#### Scenario: Create a category +- **WHEN** a user creates a category with a name and kind +- **THEN** the category is available for rules and manual assignment + +#### Scenario: Built-in Transfer category is protected +- **WHEN** a user attempts to delete the built-in Transfer category +- **THEN** the system refuses + +### Requirement: Append-only categorization event log +The system SHALL record every category assignment as an immutable event: transaction id, category id, source (`rule`, `manual`, or `reconciliation`), the rule id for rule events, the acting user's DID for manual events, and a timestamp. A transaction's current category SHALL be the latest event, denormalized onto the transaction row in the same database transaction as the event insert. Events SHALL never be updated or deleted. + +#### Scenario: Manual categorization records actor +- **WHEN** an authenticated user assigns a category to a transaction +- **THEN** a `manual` event is appended with that user's DID and the transaction's cached category is updated atomically with it + +#### Scenario: Provenance is visible +- **WHEN** a user views a categorized transaction's history +- **THEN** the UI shows every event in order: what assigned it (rule pattern or person), to which category, and when + +#### Scenario: Recategorization preserves history +- **WHEN** a user changes an already-categorized transaction to a different category +- **THEN** a new event is appended and prior events remain queryable + +### Requirement: Rule-based auto-categorization +The system SHALL support categorization rules with match types `exact` and `contains`, matched case-insensitively against the raw transaction description and, when the provider supplies them, the payee and memo fields (a rule fires if any of these matches). A rule MAY additionally specify an exact amount in signed integer cents; such a rule fires only when the text pattern matches AND the transaction's amount equals the rule's amount exactly. A text pattern is always required — amount-only rules SHALL NOT be permitted. Rules record their creator's DID and creation time. When multiple rules match one transaction, precedence SHALL be deterministic: amount-constrained beats unconstrained, then `exact` beats `contains`, then longer pattern beats shorter, then newer rule beats older. Rule application SHALL append a `rule` event recording the winning rule's id. + +#### Scenario: Rule fires on new transaction at sync +- **WHEN** a sync ingests an uncategorized transaction whose description matches an active rule +- **THEN** the winning rule's category is applied via a `rule` event referencing that rule + +#### Scenario: Precedence between overlapping rules +- **WHEN** a description matches both `contains "AMAZON"` and `contains "AMAZON PRIME"` +- **THEN** the longer pattern's rule wins and the fired rule id is recorded on the event + +#### Scenario: Rule matches the payee or memo field +- **WHEN** a transaction's description is a terse bank label but its provider-supplied payee or memo matches an active rule +- **THEN** the rule fires exactly as if the description had matched + +#### Scenario: Amount conjunct narrows a match +- **WHEN** a transaction matches a rule's text pattern but its amount differs from the rule's specified amount +- **THEN** that rule does not fire for the transaction + +#### Scenario: Amount-constrained rule outranks unconstrained +- **WHEN** a transaction matches both a `contains` rule with a matching amount constraint and an `exact` rule with no amount constraint +- **THEN** the amount-constrained rule wins and its id is recorded on the event + +### Requirement: Manual decisions outrank rules +The system SHALL never allow a rule to overwrite a categorization whose latest event is `manual`. Rules fire only on transactions with no current category. When a user creates a rule, the system SHALL offer to retroactively apply it to existing matching transactions that are currently uncategorized, and SHALL NOT touch categorized ones. + +#### Scenario: Rule does not overwrite manual choice +- **WHEN** a rule matching a transaction is created or runs, and that transaction's latest event is `manual` +- **THEN** the transaction's category is unchanged + +#### Scenario: Retroactive application on rule creation +- **WHEN** a user creates a rule and accepts the retroactive-apply offer +- **THEN** the rule is applied to all matching currently-uncategorized transactions, each receiving a `rule` event + +### Requirement: Rule display-name overlay +A rule MAY specify a display name. Wherever the system displays a transaction whose current category was assigned by that rule, it SHALL show the rule's display name in place of the raw payee/description. The overlay SHALL be applied at read time: the stored transaction fields remain canonical and unmodified, no per-transaction rename events are recorded, and changing or deactivating the rule's display name SHALL be reflected everywhere immediately. The raw description SHALL remain accessible in the transaction's detail view. + +#### Scenario: Renamed transaction display +- **WHEN** a rule with display name "Netflix" categorized a transaction described "NETFLIX.COM 866-579-7172" +- **THEN** ledger and dashboard listings show "Netflix", and the transaction's detail view still shows the raw description + +#### Scenario: Rule edit re-renames instantly +- **WHEN** a user changes a rule's display name +- **THEN** every transaction currently categorized by that rule reflects the new name on next render, with no data migration or new events + +#### Scenario: Manual recategorization sheds the overlay +- **WHEN** a user manually recategorizes a transaction that a renaming rule had categorized +- **THEN** the transaction's displayed name reverts to its raw payee/description, consistent with the rule no longer being its provenance + +### Requirement: Rule management page +The system SHALL provide a dedicated rules page, linked from the primary navigation, replacing rule management in Settings. The page SHALL list all rules with their pattern, match type, amount constraint, display name, target category, and active state, and SHALL provide rule search over these fields. Rule creation, enable, and disable SHALL be available from this page with unchanged semantics (including the retroactive-apply offer on creation). + +#### Scenario: Rules relocated +- **WHEN** a user opens Settings +- **THEN** rule management is no longer present, and the rules page is reachable from the primary navigation + +#### Scenario: Rule search +- **WHEN** a user searches the rules page for "netflix" +- **THEN** rules whose pattern, display name, or category name match are listed + +### Requirement: Rule inspection +The system SHALL provide a per-rule detail view showing: (1) what the rule did — transactions whose categorization events reference the rule, from the append-only log; (2) what the rule would hit — a live, read-only probe across all non-removed transactions of non-hidden accounts, each hit labeled with its current categorization status (uncategorized, this rule, another rule, or manual); and (3) match health — when the rule last fired, and for amount-constrained rules, recent transactions that matched the text pattern but not the amount. The probe SHALL NOT modify any transaction or event. + +#### Scenario: Historical fires +- **WHEN** a user opens a rule's detail view +- **THEN** transactions the rule categorized are listed from the event log, including ones later recategorized + +#### Scenario: Prospective matches labeled +- **WHEN** a user views a rule's would-hit preview +- **THEN** matching transactions are shown with their current status, and none are modified + +#### Scenario: Amount drift surfaced +- **WHEN** an amount-constrained rule's text pattern matches recent transactions at a different amount +- **THEN** the detail view surfaces those transactions as pattern-hit/amount-miss, indicating a probable price change + +### Requirement: Rule creation from the ledger +The system SHALL allow creating a rule from any ledger transaction via a slide-over tray, without leaving the ledger. The tray SHALL be pre-filled from the transaction: its payee (preferred) or description as the pattern, with the transaction's amount available as an opt-in constraint and an optional display name. When opened from an active ledger search, the search term SHALL be offered as the pattern. Saving SHALL create the rule with unchanged creation semantics, including the offer to retroactively apply it to matching uncategorized transactions. + +#### Scenario: Create rule from a transaction +- **WHEN** a user opens the rule tray from a ledger row and saves +- **THEN** a rule is created pre-filled from that transaction without navigating away, and the retroactive-apply offer is presented + +#### Scenario: Amount opt-in +- **WHEN** a user opens the rule tray from a transaction +- **THEN** the amount constraint is offered but not enabled by default + +#### Scenario: Search term becomes pattern +- **WHEN** a user opens the rule tray while a ledger text search is active +- **THEN** the search term is pre-filled as the rule pattern diff --git a/openspec/specs/reporting/spec.md b/openspec/specs/reporting/spec.md index d96f0ff..a84cc0a 100644 --- a/openspec/specs/reporting/spec.md +++ b/openspec/specs/reporting/spec.md @@ -30,7 +30,7 @@ The system SHALL display net worth over time computed from balance snapshots: fo - **THEN** its balances are excluded from the net worth series ### Requirement: Transaction ledger -The system SHALL provide a ledger view of transactions filterable by account, category (including uncategorized), month, and pending status, showing date, account, description, amount, category, and a provenance indicator (rule vs. person). The ledger is the surface for manual categorization. +The system SHALL provide a ledger view of transactions filterable by account, category (including uncategorized), month, and pending status, and searchable by free text. Text search SHALL match case-insensitively against the raw description, payee, and memo fields and against the effective displayed name produced by rule display-name overlays, so search finds what the user sees. Search SHALL compose with all structured filters. The ledger shows date, account, displayed name (rule overlay applied when present), amount, category, and a provenance indicator (rule vs. person). The ledger is the surface for manual categorization and for creating rules from transactions. #### Scenario: Filter to uncategorized - **WHEN** a user filters the ledger to uncategorized transactions @@ -40,3 +40,30 @@ The system SHALL provide a ledger view of transactions filterable by account, ca - **WHEN** a categorized transaction is displayed - **THEN** the row indicates whether the category came from a rule, a person (with their identity), or reconciliation carry-forward +#### Scenario: Search matches a renamed transaction +- **WHEN** a rule renames "ACH TRANSFER 4417" to display as "Rent" and a user searches the ledger for "rent" +- **THEN** the transaction is found, even though no raw field contains "rent" + +#### Scenario: Search composes with filters +- **WHEN** a user searches for "netflix" with a month filter active +- **THEN** only that month's transactions matching the text (raw fields or displayed name) are listed + +### Requirement: Dashboard month-to-date overview +The system SHALL display on the dashboard, scoped to the current calendar month and to non-hidden accounts: (1) a spending pie chart of expense totals by category computed from posted transactions with the same semantics as the monthly report (transfer-kind categories excluded, uncategorized shown as its own slice); (2) total income and total expenses from posted transactions; (3) the count and total amount of pending transactions whose effective date falls in the current month; and (4) a small fixed number of the most recent transactions, each linking to the transaction ledger. Each section SHALL render a clear empty state when the month has no qualifying data. + +#### Scenario: Current month at a glance +- **WHEN** a user views the dashboard during a month with posted transactions +- **THEN** the spend pie reflects that month's expense totals by category, and stat tiles show the month's total income and total expenses in integer-cent arithmetic + +#### Scenario: Pending stats +- **WHEN** the current month contains pending transactions +- **THEN** the dashboard shows their count and summed amount, distinct from posted income/expense totals + +#### Scenario: Recent transactions +- **WHEN** a user views the dashboard +- **THEN** the most recent transactions (pending first, then by effective date descending) are listed with date, account, displayed name, amount, and a link to the full ledger + +#### Scenario: Empty month +- **WHEN** the current month has no transactions +- **THEN** the overview sections show empty states rather than being hidden or erroring, and account balances remain visible +