From b9b0380927bafa7baaab27bc37234f36bd0e7472 Mon Sep 17 00:00:00 2001 From: dharaneesh-r Date: Tue, 22 Sep 2026 17:32:07 +0530 Subject: [PATCH] updates on the design and chatbot fix --- jest.config.cjs | 4 + .../assistant/DoormileAI/AIPanel.jsx | 4 + .../assistant/DoormileAI/pageContext.jsx | 144 +++++++++++------- src/components/assistant/intents.js | 24 +++ src/components/ui/tooltip.jsx | 2 +- .../agentStudio/AgentConfigAndInsights.jsx | 2 +- .../agentStudio/AgentScopeNavigator.jsx | 137 +++++++++-------- .../settings/agentStudio/AgentStudio.jsx | 13 +- .../settings/agentStudio/SkillCard.jsx | 59 ++++--- .../settings/agentStudio/SkillCardsGrid.jsx | 72 ++++----- .../settings/agentStudio/agentRegistryData.js | 2 +- tests/assistant/pageSuggestions.test.js | 119 +++++++++++++++ 12 files changed, 383 insertions(+), 199 deletions(-) create mode 100644 tests/assistant/pageSuggestions.test.js diff --git a/jest.config.cjs b/jest.config.cjs index fc93c78..a9c1522 100644 --- a/jest.config.cjs +++ b/jest.config.cjs @@ -30,6 +30,10 @@ module.exports = { '\.(css|less|scss|sass)$': '/tests/setup/styleMock.cjs', '\.(png|jpe?g|gif|svg|webp|woff2?|ttf|eot)$': '/tests/setup/fileMock.cjs', '^@/(.*)$': '/src/$1', + // Vite resolves these bare specifiers from src/ (jsconfig baseUrl). + // Jest needs telling, or any test importing a file that uses them fails + // to resolve rather than failing an assertion. + '^(components|pages|utils|themes|assets|lib|api)/(.*)$': '/src/$1/$2', }, collectCoverageFrom: [ 'src/api/doormile/**/*.js', diff --git a/src/components/assistant/DoormileAI/AIPanel.jsx b/src/components/assistant/DoormileAI/AIPanel.jsx index ee3c372..1982770 100644 --- a/src/components/assistant/DoormileAI/AIPanel.jsx +++ b/src/components/assistant/DoormileAI/AIPanel.jsx @@ -1428,6 +1428,7 @@ const AIPanel = ({ isOpen, onClose }) => { { } @@ -1445,6 +1447,7 @@ const AIPanel = ({ isOpen, onClose }) => { { } diff --git a/src/components/assistant/DoormileAI/pageContext.jsx b/src/components/assistant/DoormileAI/pageContext.jsx index f0c5f8e..5b47e2f 100644 --- a/src/components/assistant/DoormileAI/pageContext.jsx +++ b/src/components/assistant/DoormileAI/pageContext.jsx @@ -69,7 +69,12 @@ export const CHIP_LABELS = { 'How many tripsheets are dispatched?': 'Tripsheets', 'How many open exceptions?': 'Open exceptions', 'How many pricing rules are configured?': 'Pricing rules', - "Repeat yesterday's orders": 'Repeat yesterday' + "Repeat yesterday's orders": 'Repeat yesterday', + 'How many consignments do we have?': 'Consignments', + 'How many app users do we have?': 'App users', + 'How many partners do we have?': 'Partners', + 'How many competitor branches are tracked?': 'Competitor branches', + 'How many carriers do we have?': 'Carriers' }; // The one follow-up phrasing that is not already a page suggestion. Verified @@ -121,39 +126,27 @@ const ORDERS = { }; const RIDERS = { - label: 'Riders', + label: 'Milers', suggestions: [ { icon: LuBike, text: 'How many riders are active?' }, - { icon: LuClock3, text: "Give me today's operations summary" }, - { icon: LuPackage, text: 'How many orders today?' }, - { icon: LuTruck, text: 'How many vehicles are available?' } + { icon: LuUserPlus, text: 'Assign a rider' } ], - more: [ - { icon: LuBuilding2, text: 'Current hub status' }, - { icon: LuBanknote, text: 'Total revenue today' } - ] + more: [] }; const VEHICLES = { label: 'Vehicles', - suggestions: [ - { icon: LuTruck, text: 'How many vehicles are available?' }, - { icon: LuBike, text: 'How many riders are active?' }, - { icon: LuBuilding2, text: 'Current hub status' }, - { icon: LuClock3, text: "Give me today's operations summary" } - ], - more: [{ icon: LuPackage, text: 'How many orders today?' }] + suggestions: [{ icon: LuTruck, text: 'How many vehicles are available?' }], + more: [] }; const HUBS = { label: 'Hubs', suggestions: [ { icon: LuBuilding2, text: 'Current hub status' }, - { icon: LuTimerOff, text: 'Which hubs are experiencing delays?' }, - { icon: LuTruck, text: 'How many vehicles are available?' }, - { icon: LuBike, text: 'How many riders are active?' } + { icon: LuTimerOff, text: 'Which hubs are experiencing delays?' } ], - more: [{ icon: LuPackage, text: 'How many orders today?' }] + more: [] }; const DISPATCH = { @@ -162,24 +155,16 @@ const DISPATCH = { { icon: LuLayers, text: 'Morning Batch orders today' }, { icon: LuTimerOff, text: 'Which orders are delayed?' }, { icon: LuBike, text: 'How many riders are active?' }, - { icon: LuClock3, text: "Give me today's operations summary" } + { icon: LuUserPlus, text: 'Assign a rider' } ], - more: [ - { icon: LuPackage, text: 'How many orders today?' }, - { icon: LuCircleDot, text: 'How many pending orders today?' } - ] + more: [] }; // Linehaul and middle-mile runs. const TRIPSHEETS = { label: 'Tripsheets', - suggestions: [ - { icon: LuLayers, text: 'How many tripsheets are dispatched?' }, - { icon: LuTruck, text: 'How many vehicles are available?' }, - { icon: LuBike, text: 'How many riders are active?' }, - { icon: LuClock3, text: "Give me today's operations summary" } - ], - more: [{ icon: LuBuilding2, text: 'Current hub status' }] + suggestions: [{ icon: LuLayers, text: 'How many tripsheets are dispatched?' }], + more: [] }; // The register of things that went wrong. @@ -187,9 +172,7 @@ const EXCEPTIONS = { label: 'Exceptions', suggestions: [ { icon: LuCircleDot, text: 'How many open exceptions?' }, - { icon: LuTimerOff, text: 'Which orders are delayed?' }, - { icon: LuTimerOff, text: 'Which hubs are experiencing delays?' }, - { icon: LuClock3, text: "Give me today's operations summary" } + { icon: LuTimerOff, text: 'Which orders are delayed?' } ], more: [] }; @@ -198,13 +181,8 @@ const EXCEPTIONS = { // real page and deserves its own questions like any other. const PRICING = { label: 'Pricing', - suggestions: [ - { icon: LuBanknote, text: 'How many pricing rules are configured?' }, - { icon: LuBanknote, text: 'Total revenue today' }, - { icon: LuUsers, text: 'How many tenants do we have?' }, - { icon: LuClock3, text: "Give me today's operations summary" } - ], - more: [{ icon: LuBanknote, text: 'Total revenue this week' }] + suggestions: [{ icon: LuBanknote, text: 'How many pricing rules are configured?' }], + more: [] }; // Bookings — what customers raised in the app, before it becomes an order. @@ -227,23 +205,62 @@ const BOOKINGS = { suggestions: [ { icon: LuPackage, text: 'How many orders today?' }, { icon: LuCircleDot, text: 'How many pending orders today?' }, - { icon: LuTimerOff, text: 'Which orders are delayed?' }, - { icon: LuClock3, text: "Give me today's operations summary" } - ], - more: [ - { icon: LuPackage, text: 'How many delivered orders today?' }, { icon: LuPackage, text: 'How many cancelled orders today?' }, - { icon: LuClock3, text: 'Orders today vs yesterday' } - ] + { icon: LuTimerOff, text: 'Which orders are delayed?' } + ], + more: [] +}; + +// Deliveries — an order after it has been picked up and become a consignment. +// +// This page used to be `{ ...ORDERS, label: 'Deliveries' }`: the Orders +// question set under a different name, so a table of consignments offered +// "Create an order" and "Repeat yesterday's orders". The label made it look +// deliberate, which is the same trap the Tripsheets/Exceptions/Pricing note +// below describes. +// +// Every phrasing here is checked against its intent's own `match`: +// consignmentStatus is /\bconsignments?\b/, parcelTrack wants a tracking +// number, and the delivered/delayed counts are the order intents narrowed to +// the statuses this page actually shows. +const DELIVERIES = { + label: 'Deliveries', + suggestions: [ + { icon: LuTruck, text: 'How many consignments do we have?' }, + { icon: LuPackage, text: 'How many delivered orders today?' }, + { icon: LuTimerOff, text: 'Which orders are delayed?' }, + { icon: LuBike, text: 'How many riders are active?' } + ], + more: [] +}; + +// Reference data behind the console — not an operational page, so it gets the +// one question that is actually about it rather than the operations summary. +const APP_USERS = { + label: 'App users', + suggestions: [{ icon: LuUsers, text: 'How many app users do we have?' }], + more: [] +}; + +const COMPETITIVE_INTEL = { + label: 'Competitive intel', + suggestions: [ + { icon: LuBuilding2, text: 'How many competitor branches are tracked?' }, + // NOT "how many carrier pricing rules", which is the phrasing + // carrierPricingCount documents for itself and which never reaches it: + // pricingCount matches /\bpricing\b/ and runs 57 lines earlier, so that + // wording is answered with the general pricing-rule count. Its actual + // predicate is /\bcarriers?\b/. + { icon: LuBanknote, text: 'How many carriers do we have?' } + ], + more: [] }; const TENANTS = { label: 'Tenants', suggestions: [ { icon: LuUsers, text: 'How many tenants do we have?' }, - { icon: LuPackage, text: 'How many orders today?' }, - { icon: LuBanknote, text: 'Total revenue today' }, - { icon: LuClock3, text: "Give me today's operations summary" } + { icon: LuUsers, text: 'How many partners do we have?' } ], more: [] }; @@ -352,7 +369,7 @@ const FOLLOW_UPS = { // with the wrong label. const ROUTES = [ ['/doormile/control-x', DISPATCH], - ['/doormile/deliveries', { ...ORDERS, label: 'Deliveries' }], + ['/doormile/deliveries', DELIVERIES], ['/doormile/bookings', BOOKINGS], ['/doormile/orders', ORDERS], ['/doormile/milers', RIDERS], @@ -386,7 +403,11 @@ const ROUTES = [ // so none is shadowed by the order intents that run earlier in the catalog. ['/doormile/tripsheets', TRIPSHEETS], ['/doormile/exceptions', EXCEPTIONS], - ['/doormile/pricing', PRICING] + ['/doormile/pricing', PRICING], + // Missing from this list entirely until now, so both fell through to + // DEFAULT_CONTEXT and offered rider and hub questions over their own data. + ['/doormile/app-users', APP_USERS], + ['/doormile/competitive-intel', COMPETITIVE_INTEL] ]; // ---- every question, from every page --------------------------------------- @@ -399,7 +420,20 @@ const ROUTES = [ // // `more` is retired: AIWelcome renders one flat list now, so a second array // would only be a second place for a question to hide. -const ALL_CONTEXTS = [ORDERS, RIDERS, VEHICLES, HUBS, DISPATCH, TENANTS, REPORTS, DEFAULT_CONTEXT, ...ROUTES.map(([, c]) => c)]; +const ALL_CONTEXTS = [ + ORDERS, + RIDERS, + VEHICLES, + HUBS, + DISPATCH, + DELIVERIES, + APP_USERS, + COMPETITIVE_INTEL, + TENANTS, + REPORTS, + DEFAULT_CONTEXT, + ...ROUTES.map(([, c]) => c) +]; // Every question declared anywhere. Two jobs: it is the icon index behind // QUESTION_BY_TEXT (so a follow-up naming a question that is no longer global diff --git a/src/components/assistant/intents.js b/src/components/assistant/intents.js index 67867f5..85a0672 100644 --- a/src/components/assistant/intents.js +++ b/src/components/assistant/intents.js @@ -2090,6 +2090,30 @@ const INTENTS = [ const INTENTS_BY_ID = Object.fromEntries(INTENTS.map((i) => [i.id, i])); +// Which intent the ordered catalog reaches for a question, WITHOUT running it. +// +// Exported for the page-suggestion test. The catalog is ordered and the first +// match wins, so "something matches this chip" and "the right thing matches +// this chip" are different questions — ROADMAP.md B1/B2 are two shipped cases +// where they had different answers. +// +// Covers the deterministic pass only. answerQuestion tries semantic routing +// and the multi-part splitter ahead of this, both of which are additive: they +// can route a question the catalog would miss, never away from one it hits. +export const resolveIntentId = (text) => { + const normalized = correctTypos(text); + for (const intent of INTENTS) { + let params = null; + try { + params = intent.match(normalized); + } catch { + params = null; + } + if (params) return intent.id; + } + return null; +}; + export const SUPPORTED_QUESTIONS = INTENTS.map((i) => i.label); // Clean, directly-askable example phrasings — for "recommended question" diff --git a/src/components/ui/tooltip.jsx b/src/components/ui/tooltip.jsx index 1fa45b9..612e022 100644 --- a/src/components/ui/tooltip.jsx +++ b/src/components/ui/tooltip.jsx @@ -18,7 +18,7 @@ const TooltipContent = React.forwardRef(({ className, sideOffset = 4, ...props } ref={ref} sideOffset={sideOffset} className={cn( - "z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", + "z-[9999] overflow-hidden rounded-lg bg-slate-900 px-2.5 py-1 text-xs font-medium text-white shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", className )} {...props} /> diff --git a/src/pages/doormile/settings/agentStudio/AgentConfigAndInsights.jsx b/src/pages/doormile/settings/agentStudio/AgentConfigAndInsights.jsx index 7398789..c5f61e5 100644 --- a/src/pages/doormile/settings/agentStudio/AgentConfigAndInsights.jsx +++ b/src/pages/doormile/settings/agentStudio/AgentConfigAndInsights.jsx @@ -198,7 +198,7 @@ export function AgentInsightsView() { 420 calls (28%)
-
+
diff --git a/src/pages/doormile/settings/agentStudio/AgentScopeNavigator.jsx b/src/pages/doormile/settings/agentStudio/AgentScopeNavigator.jsx index 5eeeac1..f1f6c06 100644 --- a/src/pages/doormile/settings/agentStudio/AgentScopeNavigator.jsx +++ b/src/pages/doormile/settings/agentStudio/AgentScopeNavigator.jsx @@ -1,6 +1,5 @@ import React, { useState } from 'react'; import { - GraduationCap, Sparkles, ChevronDown, ChevronRight, @@ -9,6 +8,8 @@ import { Wrench, Pencil, Users, + Package, + Route, Layers, Check, } from 'lucide-react'; @@ -36,32 +37,28 @@ export default function AgentScopeNavigator({ const boardSkills = skills.filter((s) => s.scope === 'board'); return ( -
+
{/* 1. Agent Profile Header + Dropdown Switcher */}
{/* Agent Switcher Menu */} @@ -71,7 +68,7 @@ export default function AgentScopeNavigator({ className="fixed inset-0 z-40 bg-transparent" onClick={() => setAgentMenuOpen(false)} /> -
+
Switch Agent Scope
@@ -84,7 +81,7 @@ export default function AgentScopeNavigator({ setAgentMenuOpen(false); }} className={cn( - 'w-full flex items-center justify-between px-3 py-2 rounded-xl text-xs font-semibold text-left transition-colors cursor-pointer', + 'w-full flex items-center justify-between px-3 py-2 rounded-lg text-xs font-semibold text-left transition-colors cursor-pointer', agent.id === activeAgentId ? 'bg-brand-tint text-brand' : 'text-slate-700 hover:bg-slate-50' @@ -101,14 +98,14 @@ export default function AgentScopeNavigator({
- {/* 2. Hierarchical Collapsible Tree */} -
+ {/* 2. Hierarchical Tree (Clean, un-cluttered style) */} +
{/* SURFACES TREE NODE */}
{surfacesOpen && ( -
+
{surfaces.map((surf) => (
{surf.name} @@ -144,7 +141,7 @@ export default function AgentScopeNavigator({ {skillsOpen && ( -
+
{/* Agent Skills Sub-node */}
{agentSkillsOpen && ( -
- {agentSkills.map((skill) => ( - - ))} +
+ {agentSkills.map((skill) => { + const getSkillIcon = (cat) => { + switch ((cat || '').toUpperCase()) { + case 'AUTHORING': + return Pencil; + case 'WORKFORCE': + return Users; + case 'LOGISTICS': + return Package; + case 'DISPATCH': + return Route; + default: + return Zap; + } + }; + const SkillIcon = getSkillIcon(skill.category); + + return ( + + ); + })}
)}
@@ -209,7 +220,7 @@ export default function AgentScopeNavigator({ {boardOpen && ( -
+

None drawing on this agent's surfaces.

@@ -242,7 +253,7 @@ export default function AgentScopeNavigator({ {toolsOpen && ( -
- {tools.slice(0, 4).map((tool) => ( +
+ {tools.map((tool) => ( ); })} diff --git a/src/pages/doormile/settings/agentStudio/SkillCard.jsx b/src/pages/doormile/settings/agentStudio/SkillCard.jsx index a7a8bca..b22bca7 100644 --- a/src/pages/doormile/settings/agentStudio/SkillCard.jsx +++ b/src/pages/doormile/settings/agentStudio/SkillCard.jsx @@ -9,7 +9,6 @@ import { Route, Check, Layers, - CheckCircle2, } from 'lucide-react'; import { cn } from '@/lib/utils'; @@ -24,34 +23,34 @@ export default function SkillCard({ skill, onTest, onDetails }) { icon: Pencil, textColor: 'text-brand', bgColor: 'bg-brand-tint', - borderColor: 'border-brand/30', + borderColor: 'border-brand/20', }; case 'WORKFORCE': return { icon: Users, - textColor: 'text-indigo-700', - bgColor: 'bg-indigo-50/80', - borderColor: 'border-indigo-200/80', + textColor: 'text-slate-700', + bgColor: 'bg-slate-100', + borderColor: 'border-slate-200', }; case 'LOGISTICS': return { icon: Package, textColor: 'text-emerald-700', - bgColor: 'bg-emerald-50/80', - borderColor: 'border-emerald-200/80', + bgColor: 'bg-emerald-50', + borderColor: 'border-emerald-200', }; case 'DISPATCH': return { icon: Route, - textColor: 'text-rose-700', - bgColor: 'bg-rose-50/80', - borderColor: 'border-rose-200/80', + textColor: 'text-brand', + bgColor: 'bg-brand-tint', + borderColor: 'border-brand/20', }; default: return { icon: Layers, - textColor: 'text-slate-700', - bgColor: 'bg-slate-100', + textColor: 'text-slate-600', + bgColor: 'bg-slate-50', borderColor: 'border-slate-200', }; } @@ -61,27 +60,27 @@ export default function SkillCard({ skill, onTest, onDetails }) { const CategoryIcon = meta.icon; return ( -
- {/* Top Header: Category Tag (Left) & Status Badge (Right) */} +
-
+ {/* Top Header: Category Tag (Left) & Status Badge (Right) */} +
- + {(skill.category || 'SKILL').toUpperCase()}
@@ -97,27 +96,27 @@ export default function SkillCard({ skill, onTest, onDetails }) {
{/* Skill Title */} -

+

{skill.title}

{/* Skill Description */} -

+

{skill.description}

{/* Prompt Trigger Pill (if present) */} {skill.prompt && ( -
+
- "{skill.prompt}" + "{skill.prompt}"
)} {/* Scope / Integration Tag */} {skill.integration && ( -
-
+
+
{skill.integration}
@@ -126,23 +125,23 @@ export default function SkillCard({ skill, onTest, onDetails }) {
{/* Footer Action Bar */} -
+
diff --git a/src/pages/doormile/settings/agentStudio/SkillCardsGrid.jsx b/src/pages/doormile/settings/agentStudio/SkillCardsGrid.jsx index 5dcf701..573ac63 100644 --- a/src/pages/doormile/settings/agentStudio/SkillCardsGrid.jsx +++ b/src/pages/doormile/settings/agentStudio/SkillCardsGrid.jsx @@ -59,9 +59,9 @@ export default function SkillCardsGrid({ const boardCount = skills.filter((s) => s.scope === 'board').length; return ( -
+
{/* 1. Scope Sub-tabs (Agent Skills / Board Skills) */} -
+
- {/* 2. Global Search Bar */} + {/* 2. Global Search Input (Sleek pill style matching reference) */}
setSearchQuery(e.target.value)} placeholder="Search skills, capabilities or pages..." - className="w-full bg-white border border-slate-200/90 rounded-2xl pl-11 pr-10 py-3 text-xs sm:text-sm text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-2 focus:ring-brand/20 shadow-2xs transition-all" + className="w-full h-10 bg-white border border-slate-200 rounded-full pl-10 pr-9 text-xs text-slate-800 placeholder:text-slate-400 focus:outline-none focus:border-brand focus:ring-1 focus:ring-brand/20 shadow-2xs transition-all" /> {searchQuery && (
{/* 5. Responsive Skill Cards Grid */} {scopeTab === 'board' && filteredSkills.length === 0 ? ( -
- -
No Board Skills Connected
-

+

+ +
No Board Skills Connected
+

None drawing on this agent's surfaces. Connect board skills to link live operational boards with this agent.

) : filteredSkills.length === 0 ? ( -
+
-
No matching capabilities found
+
No matching capabilities found

Try adjusting your search query or selecting a different category filter.

@@ -216,13 +210,13 @@ export default function SkillCardsGrid({ setSearchQuery(''); setCategoryFilter('ALL'); }} - className="px-4 py-2 rounded-xl bg-slate-100 hover:bg-slate-200 text-xs font-semibold text-slate-700 transition-colors cursor-pointer" + className="px-3.5 py-1.5 rounded-lg bg-slate-100 hover:bg-slate-200 text-xs font-medium text-slate-700 transition-colors cursor-pointer" > Clear Filters
) : ( -
+
{filteredSkills.map((skill) => ( resolveIntentId(text); + +const chipsFor = (route) => getPageContext(route).suggestions; + +describe('every page suggestion resolves to an intent', () => { + PAGES.forEach((route) => { + it(`${route}`, () => { + const chips = chipsFor(route); + expect(chips.length).toBeGreaterThan(0); + chips.forEach((chip) => { + expect(winningIntent(chip.text)).not.toBeNull(); + }); + }); + }); +}); + +describe('a page offers its own subject, not another page\'s', () => { + // The intent id a page's questions are allowed to reach. This is the actual + // separation being asked for: on Orders, order intents; on Deliveries, + // delivery intents. Not "an intent exists" — the RIGHT one. + const ALLOWED = { + '/doormile/orders': /order|repeat|create|assign|summary|revenue|delay/i, + '/doormile/bookings': /order|delay/i, + '/doormile/deliveries': /consignment|order|delay|rider/i, + '/doormile/control-x': /order|batch|rider|assign|delay/i, + '/doormile/milers': /rider|assign/i, + '/doormile/vehicles': /vehicle/i, + '/doormile/hubs': /hub/i, + '/doormile/tenants': /tenant|partner/i, + '/doormile/customers': /customer/i, + '/doormile/tripsheets': /tripsheet/i, + '/doormile/exceptions': /exception|delay/i, + '/doormile/pricing': /pricing/i, + '/doormile/app-users': /appuser/i, + '/doormile/competitive-intel': /competitor|carrier/i + }; + + Object.entries(ALLOWED).forEach(([route, allowed]) => { + it(`${route} stays on topic`, () => { + chipsFor(route).forEach((chip) => { + const id = winningIntent(chip.text); + expect(`${route} · "${chip.text}" → ${id}`).toMatch(allowed); + }); + }); + }); +}); + +describe('the two bugs this change fixes', () => { + it('Deliveries is no longer the Orders list wearing a different name', () => { + const deliveries = chipsFor('/doormile/deliveries').map((c) => c.text); + const orders = chipsFor('/doormile/orders').map((c) => c.text); + expect(deliveries).not.toEqual(orders); + // It used to be `{ ...ORDERS, label: 'Deliveries' }`, so a page of + // consignments offered "Create an order" and "Repeat yesterday's orders". + expect(deliveries).not.toContain('Create an order'); + expect(deliveries).not.toContain("Repeat yesterday's orders"); + expect(deliveries).toContain('How many consignments do we have?'); + }); + + it('"carrier pricing rules" would be answered by the WRONG intent', () => { + // Kept as a live assertion rather than a comment. carrierPricingCount + // documents this exact phrasing for itself, and pricingCount matches + // /\bpricing\b/ 57 lines earlier, so the catalog never reaches it. + expect(winningIntent('How many carrier pricing rules?')).toBe('pricingCount'); + // Which is why Competitive Intel asks it the other way. + expect(winningIntent('How many carriers do we have?')).toBe('carrierPricingCount'); + }); +}); + +describe('chips are readable in a 393px panel', () => { + it('every suggestion has a short label', () => { + const missing = []; + PAGES.forEach((route) => { + chipsFor(route).forEach((chip) => { + if (!CHIP_LABELS[chip.text]) missing.push(`${route} · ${chip.text}`); + }); + }); + expect(missing).toEqual([]); + }); +});