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
48 changes: 48 additions & 0 deletions .changeset/strict-validate-cleanup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
'hotcrm': patch
---

Clear the `os validate --strict` findings that were dead metadata

The 17.6.0 upgrade left `os validate --strict` reporting 111 warnings. This
change clears 97 of them. The 14 that remain are deliberate, and each is listed
below with its reason.

- **List rows are now tinted as the docs said they were.** Nine `rowColor` maps
wrote hex colours (`#dc2626`, …). The console resolves only colour names (or a
full `bg-*` class), so no row was ever tinted. The maps now name the same hue
(`red`, `orange`, `yellow`, `slate`, `green`, `blue`, `sky`, `teal`, `amber`,
`purple`, `emerald`, `gray`). **What readers will see:** soft row tints by
priority on *All Cases*, *Unassigned Triage* and the task list; by stage on
*Open Deals*; by status on events and knowledge articles; by response on
event attendees; and by rating on *High Priority* leads. The *Cases* and
*Opportunities* pages already described these tints. Accounts declare an
active/inactive tint too, but it belongs to a list the *All Accounts* landing
tab does not use, so that tab is unchanged.
- **Dashboard widgets drop 86 `options` keys that no renderer reads**: tile
icons and formats, table `columns` / `striped` / `density`, a `suffix`, the
pivot's field keys and its `drillDown`, and the SLA gauge's
green/amber/red `thresholds`. A dataset-bound widget takes its labels and
formats from the dataset, and every removed `format` matched its measure's
own, so no dashboard changes. The pivot never opened a drill-down drawer and
the gauge never drew those bands. Two tests pinned these keys, and both
changes go with the keys. `test/ownership-model.test.ts` loses its check of
the pivot's drill-down columns, which would have run over nothing. In
`test/sla-compliance-gauge.test.ts`, the band assertions go, and the gauge
still pins its compliance measure, value and target line.
- **Four permission-set row-security policies drop `label` / `description`.**
17.6.0 marks those keys as having no runtime effect. Their wording moves into
a comment beside each policy.

Still reported, on purpose:

- `hierarchy-security` in `requires` (the enterprise capability). It stays
declared under the #1378 ruling.
- Six approval nodes routed to positions. The warning is that an unstaffed
position leaves a request waiting. That is a staffing fact about each
deployment, not a metadata defect.
- Six `{…}` template expressions in *Quote Generation* and *Forecast
Snapshot*. The lint says the template form keeps working. Moving the arithmetic
to CEL changes how it divides, so that is its own change with its own tests.
- The *Ask the AI Assistant* card's `description` on Sales Home. A
ruling-backed guard pins it (#1216).
9 changes: 0 additions & 9 deletions src/sales/dashboards/activity.dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'success',
dataset: 'event_metrics', values: ['event_count'],
layout: { x: 0, y: 0, w: 3, h: 2 },
options: { icon: 'PhoneCall', format: '0,0' },
},
{
id: 'meetings_booked',
Expand All @@ -100,7 +99,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'blue',
dataset: 'event_metrics', values: ['event_count'],
layout: { x: 3, y: 0, w: 3, h: 2 },
options: { icon: 'CalendarPlus', format: '0,0' },
},
{
id: 'customer_minutes',
Expand All @@ -111,7 +109,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'purple',
dataset: 'event_metrics', values: ['total_minutes'],
layout: { x: 6, y: 0, w: 3, h: 2 },
options: { icon: 'Clock', format: '0,0' },
},
{
// First widget in the app to use `task_metrics`. The dataset shipped with
Expand All @@ -125,7 +122,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'orange',
dataset: 'task_metrics', values: ['task_count'],
layout: { x: 9, y: 0, w: 3, h: 2 },
options: { icon: 'CheckCheck', format: '0,0' },
},

// ─── Row 2: who, and when ─────────────────────────────────────────
Expand Down Expand Up @@ -211,7 +207,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'success',
dataset: 'event_metrics', values: ['event_count'],
layout: { x: 8, y: 6, w: 2, h: 4 },
options: { icon: 'Target', format: '0,0' },
},
{
id: 'open_deals_for_activity',
Expand All @@ -222,7 +217,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'blue',
dataset: 'opportunity_metrics', values: ['opp_count'],
layout: { x: 10, y: 6, w: 2, h: 4 },
options: { icon: 'Briefcase', format: '0,0' },
},

// ─── Row 4: the churn story, now backed by a real signal ──────────
Expand All @@ -241,7 +235,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'warning',
dataset: 'account_metrics', values: ['account_count'],
layout: { x: 0, y: 10, w: 4, h: 2 },
options: { icon: 'BellOff', format: '0,0' },
},
{
id: 'quiet_accounts_60',
Expand All @@ -252,7 +245,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'orange',
dataset: 'account_metrics', values: ['account_count'],
layout: { x: 4, y: 10, w: 4, h: 2 },
options: { icon: 'AlertTriangle', format: '0,0' },
},
{
id: 'quiet_accounts_90',
Expand All @@ -263,7 +255,6 @@ export const ActivityDashboard: Dashboard = {
colorVariant: 'danger',
dataset: 'account_metrics', values: ['account_count'],
layout: { x: 8, y: 10, w: 4, h: 2 },
options: { icon: 'AlertOctagon', format: '0,0' },
},
],
};
22 changes: 2 additions & 20 deletions src/sales/dashboards/crm.dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { avgDealSizeMetricWidget, pipelineByStageFunnelWidget } from './shared-w
* Single-page snapshot of revenue, pipeline, and customer activity. Designed
* to mirror the polished CRM dashboard reference at
* https://github.com/objectstack-ai/objectui/tree/main/examples/crm/src/dashboards
* — KPI tiles with icons, an area-chart revenue trend, a lead-source donut, a
* — KPI tiles, an area-chart revenue trend, a lead-source donut, a
* pipeline funnel, and a recent-deals table.
*
* This dashboard intentionally uses the framework's first-class metadata fields
Expand Down Expand Up @@ -67,7 +67,6 @@ export const CrmOverviewDashboard: Dashboard = {
colorVariant: 'success',
dataset: 'opportunity_metrics', values: ['total_amount'],
layout: { x: 0, y: 0, w: 3, h: 2 },
options: { icon: 'DollarSign' },
},
{
id: 'active_deals',
Expand All @@ -78,10 +77,6 @@ export const CrmOverviewDashboard: Dashboard = {
colorVariant: 'blue',
dataset: 'opportunity_metrics', values: ['opp_count'],
layout: { x: 3, y: 0, w: 3, h: 2 },
options: {
icon: 'Briefcase',
format: '0,0',
},
},
{
id: 'won_deals',
Expand All @@ -92,13 +87,8 @@ export const CrmOverviewDashboard: Dashboard = {
colorVariant: 'purple',
dataset: 'opportunity_metrics', values: ['opp_count'],
layout: { x: 6, y: 0, w: 3, h: 2 },
options: {
icon: 'Trophy',
format: '0,0',
},
},
// No overrides left once the fabricated trend is gone: the factory already
// declares `options: { icon: 'bar-chart' }`, so re-passing it said nothing.
// No overrides left once the fabricated trend is gone.
avgDealSizeMetricWidget({ x: 9, y: 0, w: 3, h: 2 }),

// ─── Charts Row 1 ─────────────────────────────────────────────────
Expand Down Expand Up @@ -178,17 +168,9 @@ export const CrmOverviewDashboard: Dashboard = {
dataset: 'opportunity_metrics', dimensions: ['owner'], values: ['total_amount', 'opp_count', 'avg_amount'],
layout: { x: 0, y: 10, w: 12, h: 4 },
options: {
columns: [
{ header: 'Owner', accessorKey: 'owner' },
{ header: 'Pipeline', accessorKey: 'total_amount', format: '0,0' },
{ header: 'Opportunities', accessorKey: 'opp_count' },
{ header: 'Avg Deal Size', accessorKey: 'avg_amount', format: '0,0' },
],
sortBy: 'total_amount',
sortOrder: 'desc',
limit: 10,
striped: true,
density: 'comfortable',
},
},
],
Expand Down
73 changes: 0 additions & 73 deletions src/sales/dashboards/sales.dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,6 @@ export const SalesDashboard: Dashboard = {
colorVariant: 'blue',
dataset: 'opportunity_metrics', values: ['total_amount'],
layout: { x: 0, y: 0, w: 3, h: 2 },
options: {
icon: 'DollarSign',
format: '0,0',
},
},
{
id: 'closed_won_qtd',
Expand All @@ -90,10 +86,6 @@ export const SalesDashboard: Dashboard = {
colorVariant: 'success',
dataset: 'opportunity_metrics', values: ['total_amount'],
layout: { x: 3, y: 0, w: 3, h: 2 },
options: {
icon: 'Trophy',
format: '0,0',
},
},
{
id: 'open_opportunities',
Expand All @@ -104,20 +96,12 @@ export const SalesDashboard: Dashboard = {
colorVariant: 'orange',
dataset: 'opportunity_metrics', values: ['opp_count'],
layout: { x: 6, y: 0, w: 3, h: 2 },
options: {
icon: 'Briefcase',
format: '0,0',
},
},
avgDealSizeMetricWidget({ x: 9, y: 0, w: 3, h: 2 }, {
description: 'Average value of closed-won deals this quarter',
filter: { stage: 'closed_won', close_date: { $gte: '{current_quarter_start}' } },
filterBindings: { dateRange: false }, // self-scoped to QTD — the date picker must not re-window it
colorVariant: 'purple',
options: {
icon: 'bar-chart',
format: '0,0',
},
}),

// ─── Row 2: Win / Loss KPIs ───────────────────────────────────────
Expand Down Expand Up @@ -148,7 +132,6 @@ export const SalesDashboard: Dashboard = {
colorVariant: 'success',
dataset: 'opportunity_metrics', values: ['win_rate'],
layout: { x: 0, y: 2, w: 4, h: 2 },
options: { icon: 'Percent', format: '0%' },
},
{
id: 'won_deals_12m',
Expand All @@ -160,7 +143,6 @@ export const SalesDashboard: Dashboard = {
colorVariant: 'blue',
dataset: 'opportunity_metrics', values: ['won_count'],
layout: { x: 4, y: 2, w: 4, h: 2 },
options: { icon: 'Trophy', format: '0,0' },
},
{
id: 'lost_deals_12m',
Expand All @@ -172,7 +154,6 @@ export const SalesDashboard: Dashboard = {
colorVariant: 'orange',
dataset: 'opportunity_metrics', values: ['lost_count'],
layout: { x: 8, y: 2, w: 4, h: 2 },
options: { icon: 'TrendingDown', format: '0,0' },
},

// ─── Row 3: Pipeline & Trends ─────────────────────────────────────
Expand Down Expand Up @@ -250,17 +231,9 @@ export const SalesDashboard: Dashboard = {
dataset: 'opportunity_metrics', dimensions: ['owner'], values: ['total_amount', 'opp_count', 'avg_probability'],
layout: { x: 0, y: 12, w: 12, h: 4 },
options: {
columns: [
{ header: 'Owner', accessorKey: 'owner' },
{ header: 'Open Pipeline', accessorKey: 'total_amount', format: '0,0' },
{ header: 'Open Deals', accessorKey: 'opp_count' },
{ header: 'Avg Win Prob.', accessorKey: 'avg_probability', format: '0%' },
],
sortBy: 'total_amount',
sortOrder: 'desc',
limit: 10,
striped: true,
density: 'comfortable',
},
},

Expand Down Expand Up @@ -330,17 +303,9 @@ export const SalesDashboard: Dashboard = {
dataset: 'forecast_metrics', dimensions: ['owner'], values: ['quota_sum', 'closed_sum', 'attainment'],
layout: { x: 0, y: 16, w: 12, h: 4 },
options: {
columns: [
{ header: 'Owner', accessorKey: 'owner' },
{ header: 'Quota', accessorKey: 'quota_sum', format: '0,0' },
{ header: 'Closed', accessorKey: 'closed_sum', format: '0,0' },
{ header: 'Attainment', accessorKey: 'attainment', format: '0%' },
],
sortBy: 'attainment',
sortOrder: 'desc',
limit: 10,
striped: true,
density: 'comfortable',
},
},

Expand Down Expand Up @@ -373,19 +338,9 @@ export const SalesDashboard: Dashboard = {
values: ['won_count', 'lost_count', 'decided_count', 'win_rate', 'won_amount'],
layout: { x: 0, y: 20, w: 6, h: 4 },
options: {
columns: [
{ header: 'Owner', accessorKey: 'owner' },
{ header: 'Won', accessorKey: 'won_count' },
{ header: 'Lost', accessorKey: 'lost_count' },
{ header: 'Settled', accessorKey: 'decided_count' },
{ header: 'Win Rate', accessorKey: 'win_rate', format: '0%' },
{ header: 'Won Revenue', accessorKey: 'won_amount', format: '0,0' },
],
sortBy: 'decided_count',
sortOrder: 'desc',
limit: 10,
striped: true,
density: 'comfortable',
},
},
{
Expand All @@ -401,19 +356,9 @@ export const SalesDashboard: Dashboard = {
values: ['won_count', 'lost_count', 'decided_count', 'win_rate', 'won_amount'],
layout: { x: 6, y: 20, w: 6, h: 4 },
options: {
columns: [
{ header: 'Lead Source', accessorKey: 'lead_source' },
{ header: 'Won', accessorKey: 'won_count' },
{ header: 'Lost', accessorKey: 'lost_count' },
{ header: 'Settled', accessorKey: 'decided_count' },
{ header: 'Win Rate', accessorKey: 'win_rate', format: '0%' },
{ header: 'Won Revenue', accessorKey: 'won_amount', format: '0,0' },
],
sortBy: 'decided_count',
sortOrder: 'desc',
limit: 12,
striped: true,
density: 'comfortable',
},
},
{
Expand Down Expand Up @@ -454,24 +399,6 @@ export const SalesDashboard: Dashboard = {
colorVariant: 'default',
dataset: 'opportunity_metrics', dimensions: ['stage', 'lead_source'], values: ['total_amount'],
layout: { x: 0, y: 28, w: 12, h: 4 },
options: {
rowField: 'stage',
columnField: 'lead_source',
valueField: 'amount',
aggregation: 'sum',
showRowTotals: true,
showColumnTotals: true,
format: '0,0',
drillDown: {
enabled: true,
// Clicking a pivot cell opens a drawer listing the underlying
// opportunity records for that stage + lead-source slice — the
// same drill-through pattern used by the other dashboard widgets.
target: 'drawer',
columns: ['name', 'crm_account', 'amount', 'forecast_category', 'close_date', 'owner_id'],
maxRows: 100,
},
},
},
],
};
1 change: 0 additions & 1 deletion src/sales/dashboards/shared-widgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,5 @@ export const avgDealSizeMetricWidget = (
colorVariant: 'orange',
dataset: 'opportunity_metrics', values: ['avg_amount'],
layout,
options: { icon: 'bar-chart' },
...overrides,
});
17 changes: 8 additions & 9 deletions src/sales/profiles/marketing-user.profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ export const MarketingUserProfile = {
rowLevelSecurity: [
{
name: 'opportunity_private_owner_only_marketing',
label: 'Private opportunities stay with their owner',
description:
'A deal flagged Private is visible only to its owner, even to holders of org-wide opportunity read.',
// A deal flagged Private is visible only to its owner, even to holders of
// org-wide opportunity read. (`rowLevelSecurity[].label` / `description`
// have no runtime effect — liveness: dead in 17.6.0 — so the intent lives
// in this comment.)
object: 'crm_opportunity',
operation: 'select' as const,
using: 'is_private == false || owner_id == current_user.id',
Expand All @@ -77,9 +78,8 @@ export const MarketingUserProfile = {
// `{id: {$null: false}}`).
{
name: 'marketing_campaign_updates',
label: 'Marketing works any campaign',
description:
'Marketing users edit any campaign (and thereby enrol members into it), not only campaigns they created.',
// Marketing users edit any campaign (and thereby enrol members into it),
// not only campaigns they created.
object: 'crm_campaign',
operation: 'update' as const,
using: 'id != null',
Expand All @@ -89,9 +89,8 @@ export const MarketingUserProfile = {
// default owner-only-writes policy would otherwise deny.
{
name: 'marketing_campaign_member_updates',
label: 'Marketing updates any campaign member',
description:
'Marketing users update member response state on rows they did not personally create.',
// Marketing users update member response state on rows they did not
// personally create.
object: 'crm_campaign_member',
operation: 'update' as const,
using: 'id != null',
Expand Down
Loading
Loading