Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion objectstack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
10 changes: 6 additions & 4 deletions src/dashboards/crm.dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
16 changes: 9 additions & 7 deletions src/dashboards/executive.dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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: {
Expand All @@ -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: {
Expand Down
Loading