diff --git a/CHANGELOG.md b/CHANGELOG.md index 9838916a..fa37d4a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to HotCRM are documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); HotCRM follows [Semantic Versioning](https://semver.org/). +## [2.2.1] — 2026-07-20 + +Follow-up patch to 2.2.0. The dashboard-filter fix in 2.2.0 only covered the built-in `dateRange` picker; the **`globalFilters[]`** have the same propagation behaviour (ObjectStack 15 / framework#2501 injects every dashboard filter into each widget's query) and were still crashing widgets on objects that lack the filtered field. Browser-verified by actually selecting the filter values, not just loading the dashboards. + +### Fixed + +- **Selecting the Executive dashboard's `Lead Source` filter crashed every account widget.** The `lead_source` global filter was injected into each widget's query; `crm_account` has no `lead_source` column, so `total_accounts`, `new_accounts_by_month`, and `accounts_by_industry` failed with `SqliteError: no such column: lead_source`. Added `lead_source: false` to those three widgets' [`filterBindings`](src/dashboards/executive.dashboard.ts) (they keep `dateRange: false` too). `crm_contact` and `crm_lead` *do* have `lead_source`, so `total_contacts` / `open_leads` correctly keep filtering. Verified in the browser: selecting `Lead Source = Web` now returns filtered/empty results with zero analytics errors. +- **CRM dashboard's `Owner` filter would crash `top_products`.** `crm_product` has no `owner` column, so the `owner` global filter would fail the product-category widget the same way. Added `owner: false` to its [`filterBindings`](src/dashboards/crm.dashboard.ts) (alongside the existing `dateRange: false`). +- Full field-vs-filter matrix audited across all four dashboards: Sales (all `opportunity_metrics`, which has `owner`/`type`/`close_date`) and Service (`created_date`/`owner`/`priority` all on `crm_case`) needed no change. + ## [2.2.0] — 2026-07-20 Platform upgrade to ObjectStack **16.0.0-rc.1** — the 16 release-candidate line (from 14.7, skipping the entire 15.x line). Manifest `specVersion` now declares `^16.0.0-rc.1` (was `^14.0.0`); app version `2.2.0`. ObjectStack 16 finishes the ADR-0049 "enforce-or-remove" sweep (dead metadata props now fail loudly instead of parsing inert), converges the hook/action org identifier on `organizationId`, flips `.strict()` on dashboard-widget / view-form / page schemas, and — most consequentially for this app — makes the `ai` capability a **fail-fast hard requirement** resolved to the closed `@objectstack/service-ai` package. Built, validated, type-checked, unit-tested (17/17), and browser-verified against `@objectstack/* 16.0.0-rc.1` (boot → 38 plugins loaded → 17 flows / 15 trigger-bound → `/_console/` login renders at HTTP 200 with no boot or console errors). diff --git a/objectstack.config.ts b/objectstack.config.ts index d107b5d6..558ac4ce 100644 --- a/objectstack.config.ts +++ b/objectstack.config.ts @@ -31,7 +31,7 @@ export default defineStack({ manifest: { id: 'app.objectstack.hotcrm', namespace: 'crm', - version: '2.2.0', + version: '2.2.1', type: 'app', name: 'HotCRM', description: 'AI-Native CRM for the ObjectStack marketplace — Accounts, Contacts, Leads, Opportunities, Cases, Knowledge, Forecasts, Campaigns, Contracts.', diff --git a/package.json b/package.json index d96bff17..50dbdc7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hotcrm", - "version": "2.2.0", + "version": "2.2.1", "description": "HotCRM - Production CRM built on ObjectStack", "private": true, "main": "./objectstack.config.ts", diff --git a/src/dashboards/crm.dashboard.ts b/src/dashboards/crm.dashboard.ts index b15f8cd2..0d0ea37f 100644 --- a/src/dashboards/crm.dashboard.ts +++ b/src/dashboards/crm.dashboard.ts @@ -181,10 +181,12 @@ export const CrmOverviewDashboard: Dashboard = { description: 'Total list-price revenue by product category', type: 'bar', colorVariant: 'blue', - // crm_product has no `close_date`; opt out of the dashboard date-range - // picker (ObjectStack 15 framework#2501 wired dashboard filters into every - // widget query, so an unbound widget here fails with `no such column`). - filterBindings: { dateRange: false }, + // crm_product has neither `close_date` nor `owner`; opt out of both + // dashboard filters bound to those fields. ObjectStack 15 (framework#2501) + // injects every dashboard filter (dateRange + globalFilters) into each + // widget's query, so a widget on an object lacking a filter field fails + // with `no such column`. + filterBindings: { dateRange: false, owner: false }, dataset: 'product_metrics', dimensions: ['category'], values: ['list_price_sum'], layout: { x: 6, y: 6, w: 6, h: 4 }, chartConfig: { diff --git a/src/dashboards/executive.dashboard.ts b/src/dashboards/executive.dashboard.ts index e82146ea..3ecfb9dd 100644 --- a/src/dashboards/executive.dashboard.ts +++ b/src/dashboards/executive.dashboard.ts @@ -88,11 +88,13 @@ export const ExecutiveDashboard: Dashboard = { actionUrl: '/objects/account', actionType: 'url', actionIcon: 'ArrowUpRight', - // crm_account has no `close_date`; opt out of the dashboard date-range - // picker (bound to close_date) — ObjectStack 15 (framework#2501) wired - // dashboard filters into every widget's query, so an unbound widget on an - // object lacking the date field would fail with `no such column`. - filterBindings: { dateRange: false }, + // crm_account has neither `close_date` nor `lead_source`; opt this widget + // out of both dashboard filters bound to those fields. ObjectStack 15 + // (framework#2501) injects every dashboard filter (dateRange + globalFilters) + // into each widget's query, so a widget on an object lacking a filter field + // fails with `no such column`. crm_account DOES have `owner`, so the owner + // filter is left to apply. + filterBindings: { dateRange: false, lead_source: false }, dataset: 'account_metrics', values: ['account_count'], layout: { x: 3, y: 0, w: 3, h: 2 }, options: { @@ -203,7 +205,7 @@ export const ExecutiveDashboard: Dashboard = { type: 'bar', filter: { created_at: { $gte: '{6_months_ago}' } }, colorVariant: 'purple', - filterBindings: { dateRange: false }, // crm_account has no close_date; this widget scopes itself by created_at + filterBindings: { dateRange: false, lead_source: false }, // crm_account has no close_date/lead_source; scopes itself by created_at dataset: 'account_metrics', dimensions: ['created_at'], values: ['account_count'], layout: { x: 6, y: 6, w: 6, h: 4 }, chartConfig: { @@ -230,7 +232,7 @@ export const ExecutiveDashboard: Dashboard = { description: 'Total annual revenue and account count per industry', type: 'table', colorVariant: 'default', - filterBindings: { dateRange: false }, // crm_account has no close_date — opt out of the date picker + filterBindings: { dateRange: false, lead_source: false }, // crm_account has no close_date/lead_source — opt out of both dataset: 'account_metrics', dimensions: ['industry'], values: ['annual_revenue_sum', 'account_count'], layout: { x: 0, y: 10, w: 12, h: 4 }, options: {