diff --git a/.changeset/2118-retire-launcher-utility-bar.md b/.changeset/2118-retire-launcher-utility-bar.md new file mode 100644 index 00000000..67c9f57a --- /dev/null +++ b/.changeset/2118-retire-launcher-utility-bar.md @@ -0,0 +1,22 @@ +--- +'hotcrm': minor +--- + +Remove the **App Launcher** and **Utility Bar** pages (`app_launcher_page`, `utility_bar_page`). + +Neither page could be reached from the app: no sidebar entry, button or other page pointed at +either, so each opened only at a typed address. Once opened, neither did what its name said. +The App Launcher was a search box and a "Your apps" strip holding one app, a copy of the app +switcher the console already has. The Utility Bar did not float: it laid out inline in the main +column as a heading, an empty **Quick Notes** card, a search box and a notifications block that +drew nothing. The console has no slot for a utility bar to dock into. + +HotCRM is also the reference app people read to learn how to build one, and these two pages +taught that a page of type `app` makes a launcher and a page of type `utility` makes a floating +bar. Neither is true on the current platform, so both pages are retired together with their +labels in the four language packs. + +**What this means for you.** The two pages are no longer in the app, and reading either one +(`GET /api/v1/meta/page/app_launcher_page` or `.../utility_bar_page`) now answers 404. A +bookmark to either address stops working. Every other page, **Sales Home** and the record +pages included, is unchanged. To switch apps, use the console's own app switcher. diff --git a/docs/STATUS.md b/docs/STATUS.md index 02740fc9..6b4b378f 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -20,7 +20,7 @@ loader registers: ```text HotCRM v4.0.1 Data: 18 Objects 358 Fields -UI: 1 Apps 14 Views 8 Pages 5 Dashboards 10 Reports 31 Actions +UI: 1 Apps 14 Views 6 Pages 5 Dashboards 10 Reports 31 Actions Logic: 33 Flows Security: 12 Positions 7 Permissions ``` diff --git a/docs/architecture/module-split-inventory.json b/docs/architecture/module-split-inventory.json index 2431973e..cc5157ef 100644 --- a/docs/architecture/module-split-inventory.json +++ b/docs/architecture/module-split-inventory.json @@ -29,6 +29,13 @@ "change": "Removed the files[] row for src/sales/flows/demo-bootstrap.flow.ts (kind 'flow', module app.objectstack.hotcrm, 860 authored tokens). The file existed at measured_at.commit and was deleted by #1892, which retired the demo_bootstrap ownership sweep once the platform's seed-settle ownership claim (objectstack#17872, in the 17.6.0 pin) was measured leaving no seeded owner-scoped row ownerless.", "figures_left_as_measured": "Untouched, per do_not_hand_patch: totals, ratchet, edges (the demo_bootstrap entries included), modules[] and every other files[].authored_tokens are still the one measurement taken at measured_at.commit. Only the row that pointed at a deleted file was removed, as #1698 did for case-csat-followup.flow.ts.", "roster_drift_measured_at_this_date": "files[] held 170 rows before this edit and holds 169 after. The 35 files the 2026-09-14 entry lists as postdating measured_at.commit are still left out, for the reason it gives." + }, + { + "date": "2026-10-15", + "issue": 2118, + "change": "Removed the files[] rows for src/sales/pages/app_launcher.page.ts (kind 'page', module app.objectstack.hotcrm, 310 authored tokens) and src/sales/pages/utility_bar.page.ts (kind 'page', module app.objectstack.hotcrm, 279 authored tokens). Both files existed at measured_at.commit and were deleted by #2118, which retired the two pages: neither had a navigation entry or any other consumer, and their page types promised shell roles (an app launcher, a floating utility bar) the console does not give.", + "figures_left_as_measured": "Untouched, per do_not_hand_patch: totals, ratchet, edges, modules[] and every other files[].authored_tokens are still the one measurement taken at measured_at.commit. Only the two rows that pointed at deleted files were removed, as #1698 and #1892 did before.", + "roster_drift_measured_at_this_date": "files[] held 169 rows before this edit and holds 167 after. The 35 files the 2026-09-14 entry lists as postdating measured_at.commit are still left out, for the reason it gives." } ], "measured_at": { @@ -2885,15 +2892,6 @@ "ratcheted": true, "authored_tokens": 233 }, - { - "path": "src/sales/pages/app_launcher.page.ts", - "kind": "page", - "module": "app.objectstack.hotcrm", - "anchor_object": null, - "reason": "global launcher, no object binding", - "ratcheted": true, - "authored_tokens": 310 - }, { "path": "src/service/pages/case_detail.page.ts", "kind": "page", @@ -2939,15 +2937,6 @@ "ratcheted": true, "authored_tokens": 1915 }, - { - "path": "src/sales/pages/utility_bar.page.ts", - "kind": "page", - "module": "app.objectstack.hotcrm", - "anchor_object": null, - "reason": "global utility bar, no object binding", - "ratcheted": true, - "authored_tokens": 279 - }, { "path": "src/sales/profiles/guest-portal.profile.ts", "kind": "profile", diff --git a/docs/architecture/module-split-plan.md b/docs/architecture/module-split-plan.md index e4b45471..85ae302d 100644 --- a/docs/architecture/module-split-plan.md +++ b/docs/architecture/module-split-plan.md @@ -122,7 +122,7 @@ enumerated below and in the JSON, each with a stated reason. ### What stays in the app package, and why it holds no objects The app package holds the consumer surface — the app and its navigation scaffolding, the -three pages that are not record pages (`home`, `app_launcher`, `utility_bar`), the two +one page authored against no object (`home`), the two cross-domain dashboards, the three cross-domain AI skills, the six permission sets, the four locale bundles, the package docs, and the shared TypeScript source described below. diff --git a/docs/feature-inventory.md b/docs/feature-inventory.md index a52e1bfd..ee0405d8 100644 --- a/docs/feature-inventory.md +++ b/docs/feature-inventory.md @@ -249,7 +249,7 @@ | --- | --- | --- | --- | | ADM-001 | 应用定义与导航 | `crm_enterprise` 应用:7 个导航组(Sales/My Work/Activity/Marketing/Service/Insights/Approvals),橙色品牌,落地页为高管仪表板 | `src/sales/apps/crm.app.ts` | | ADM-002 | Sales Home 页 | 三栏首页(sales_rep/manager 默认):4 个实时 KPI、我的线索/交易/任务三个嵌入列表 tab、快速新建、AI 助手引导卡、今日日程 | `src/sales/pages/home.page.ts` | -| ADM-003 | 启动器与工具条 | App Launcher(应用网格+全局搜索)与 Utility Bar(通知面板、快速笔记、快速搜索),四个 profile 全覆盖 | `src/sales/pages/app_launcher.page.ts`、`src/sales/pages/utility_bar.page.ts` | +| ADM-003 | 启动器与工具条 | **已移除 (#2118)**:曾有 App Launcher 与 Utility Bar 两个页面,但只能经手输 URL 打开,没有任何导航入口;前者重复控制台自带的应用切换,后者并不悬浮,只在主栏内平铺(控制台没有工具条槽位,上游 objectstack#23610)。两页连同其四语言文案一并下线,应用内不再有对应功能 | 已删除 | | ADM-004 | 数据导入 | 3 个可复用导入映射(客户/联系人/线索,CSV/XLSX upsert)+ 行业与线索来源同义词表 + 现成模板 | `src/sales/mappings/` + `assets/import-templates/` | | ADM-005 | 数据导出 | 五对象(客户/联系人/线索/商机/工单)列表 CSV/XLSX 导出,经单一服务端路由与 `allowExport` 权限把关 | `src/*/views/*.view.ts`(exportOptions)+ `src/sales/profiles/index.ts` | | ADM-006 | 文件与附件 | 6 个对象启用附件面(客户/联系人/商机/报价/合同/工单);记录评论 feed 全对象默认开启;产品图/logo/头像/签名等媒体字段带服务端类型与大小限制 | `src/*/objects/*.object.ts`(enable.files)+ `src/sales/objects/index.ts`(机制说明) | diff --git a/objectstack.composition.ts b/objectstack.composition.ts index 0952594b..f507f106 100644 --- a/objectstack.composition.ts +++ b/objectstack.composition.ts @@ -288,7 +288,7 @@ export const appViews = byExportName({ ...salesViews, ...revenueViews, ...marketingViews, }); -/** Its page layouts — `home`, `app_launcher` and `utility_bar` among them. */ +/** Its page layouts — `home` among them. */ export const appPages = byExportName({ ...salesPages }); /** Every import mapping — sales only; nothing else authors one. */ diff --git a/src/sales/pages/app_launcher.page.ts b/src/sales/pages/app_launcher.page.ts deleted file mode 100644 index 85f6925e..00000000 --- a/src/sales/pages/app_launcher.page.ts +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { Page } from '@objectstack/spec/ui'; - -/** - * App Launcher Page - * - * Demonstrates an application launcher page similar to Salesforce App Launcher. - * - * Features: - * - Grid-based app icon layout - * - Global search - * - Quick access to all apps - */ -export const AppLauncherPage: Page = { - name: 'app_launcher_page', - label: 'App Launcher', - description: 'Central hub for accessing all applications', - - type: 'app', - - template: 'centered', - kind: 'full', - regions: [ - { - name: 'header', - width: 'full', - components: [ - { - type: 'global:search', - id: 'app_search', - label: 'Search Apps', - properties: {}, - }, - ], - }, - - { - name: 'main', - width: 'large', - components: [ - { - type: 'page:header', - id: 'launcher_header', - label: 'App Launcher Header', - properties: { - title: 'App Launcher', - subtitle: 'Select an app to get started', - // `icon` removed from `page:header` in @objectstack/spec 17.0.0 - // (#6946, ADR-0087 D2) — deleted, not renamed. See the full note on - // `account_detail.page.ts`; nothing ever drew it. - // `breadcrumb` retired from `page:header` in @objectstack/spec 17.6.0 - // (#20785, `page-header-breadcrumb-removed`) — deleted: no renderer - // ever drew a trail for it; the app shell's own trail is unchanged. - }, - }, - { - type: 'app:launcher', - id: 'app_grid', - label: 'Application Grid', - properties: {}, - }, - ], - }, - ], - - isDefault: false, - - aria: { - ariaLabel: 'App Launcher Page', - ariaDescribedBy: 'Central application launcher for accessing all apps', - }, -}; diff --git a/src/sales/pages/index.ts b/src/sales/pages/index.ts index 44fc7c8d..e48f3d37 100644 --- a/src/sales/pages/index.ts +++ b/src/sales/pages/index.ts @@ -13,5 +13,3 @@ export { OpportunityDetailPage } from './opportunity_detail.page'; export { AccountDetailPage } from './account_detail.page'; export { AccountWorkbenchPage } from './account_workbench.page'; export { SalesHomePage } from './home.page'; -export { AppLauncherPage } from './app_launcher.page'; -export { UtilityBarPage } from './utility_bar.page'; diff --git a/src/sales/pages/utility_bar.page.ts b/src/sales/pages/utility_bar.page.ts deleted file mode 100644 index fd8815f8..00000000 --- a/src/sales/pages/utility_bar.page.ts +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. - -import { Page } from '@objectstack/spec/ui'; - -/** - * Utility Bar Page - * - * Demonstrates a utility bar page for floating panels and quick access tools. - * Similar to Salesforce Lightning Utility Bar. - * - * Features: - * - Floating utility panels - * - Quick notes - * - Chat/messaging - * - Notifications - */ -export const UtilityBarPage: Page = { - name: 'utility_bar_page', - label: 'Utility Bar', - description: 'Quick access utility bar with floating tools', - - type: 'utility', - - template: 'utility-bar', - kind: 'full', - regions: [ - { - name: 'utilities', - width: 'full', - components: [ - { - type: 'global:notifications', - id: 'notifications_panel', - label: 'Notifications', - properties: {}, - }, - { - type: 'page:card', - id: 'quick_notes', - label: 'Quick Notes', - properties: { - title: 'Quick Notes', - bordered: true, - }, - }, - { - type: 'global:search', - id: 'quick_search', - label: 'Quick Search', - properties: {}, - }, - ], - }, - ], - - isDefault: false, - - aria: { - ariaLabel: 'Utility Bar', - ariaDescribedBy: 'Quick access utility bar with floating tools and notifications', - }, -}; diff --git a/src/sales/translations/en/app.ts b/src/sales/translations/en/app.ts index 7f720b89..dadf7279 100644 --- a/src/sales/translations/en/app.ts +++ b/src/sales/translations/en/app.ts @@ -162,15 +162,6 @@ export const appSurface: Omit = { label: 'Account Workbench', description: 'Curated account list for the sales team: quick filters only, no view management.', }, - app_launcher_page: { - label: 'App Launcher', - description: 'Central hub for accessing all applications', - subtitle: 'Select an app to get started', - components: { - app_search: { label: 'Search Apps' }, - app_grid: { label: 'Application Grid' }, - }, - }, case_detail_page: { label: 'Case Detail', description: 'Service-agent case record: highlights, SLA path, details and activity timeline.', @@ -220,15 +211,6 @@ export const appSurface: Omit = { upcoming_events: { title: 'Upcoming Events', label: 'Upcoming Events' }, }, }, - utility_bar_page: { - label: 'Utility Bar', - description: 'Quick access utility bar with floating tools', - components: { - notifications_panel: { label: 'Notifications' }, - quick_notes: { title: 'Quick Notes', label: 'Quick Notes' }, - quick_search: { label: 'Quick Search' }, - }, - }, }, // Shared option lists (`src//picklists/*.picklist.ts`), translated ONCE: // every field that references a list (`Field.select({ picklist })`) inherits diff --git a/src/sales/translations/es-ES/app.ts b/src/sales/translations/es-ES/app.ts index ce431261..1cbf4f3b 100644 --- a/src/sales/translations/es-ES/app.ts +++ b/src/sales/translations/es-ES/app.ts @@ -498,15 +498,6 @@ export const appSurface: Omit = { label: 'Mesa de Trabajo de Cuentas', description: 'Lista de cuentas curada para el equipo comercial: solo filtros rápidos, sin gestión de vistas.', }, - app_launcher_page: { - label: 'Lanzador de Aplicaciones', - description: 'Punto de acceso central a todas las aplicaciones', - subtitle: 'Seleccione una aplicación para empezar', - components: { - app_search: { label: 'Buscar Aplicaciones' }, - app_grid: { label: 'Cuadrícula de Aplicaciones' }, - }, - }, case_detail_page: { label: 'Detalle de Caso', description: 'Página de caso para el agente de servicio: datos destacados, progreso del SLA, detalles y cronología de actividad.', @@ -580,15 +571,6 @@ export const appSurface: Omit = { upcoming_events: { title: 'Próximos Eventos', label: 'Próximos Eventos' }, }, }, - utility_bar_page: { - label: 'Barra de Utilidades', - description: 'Barra de acceso rápido con herramientas flotantes', - components: { - notifications_panel: { label: 'Notificaciones' }, - quick_notes: { title: 'Notas Rápidas', label: 'Notas Rápidas' }, - quick_search: { label: 'Búsqueda Rápida' }, - }, - }, }, // Screen-flow copy (#1210, #1975). Keyed `flows..label`, // `screens..title` and `screens..fields..{label,placeholder}` diff --git a/src/sales/translations/ja-JP/app.ts b/src/sales/translations/ja-JP/app.ts index 31308c1b..34142838 100644 --- a/src/sales/translations/ja-JP/app.ts +++ b/src/sales/translations/ja-JP/app.ts @@ -500,15 +500,6 @@ export const appSurface: Omit = { label: '取引先ワークベンチ', description: '営業チーム向けに厳選した取引先リスト。クイックフィルターのみで、ビュー管理は行いません。', }, - app_launcher_page: { - label: 'アプリランチャー', - description: 'すべてのアプリケーションにアクセスするための統合ハブ', - subtitle: '開始するアプリを選択してください', - components: { - app_search: { label: 'アプリを検索' }, - app_grid: { label: 'アプリグリッド' }, - }, - }, case_detail_page: { label: 'ケース詳細', description: 'サービス担当者向けのケースレコードページ:ハイライト、SLA パス、詳細、活動タイムライン。', @@ -582,15 +573,6 @@ export const appSurface: Omit = { upcoming_events: { title: '今後の予定', label: '今後の予定' }, }, }, - utility_bar_page: { - label: 'ユーティリティバー', - description: 'フローティングツールへのクイックアクセスバー', - components: { - notifications_panel: { label: '通知' }, - quick_notes: { title: 'クイックメモ', label: 'クイックメモ' }, - quick_search: { label: 'クイック検索' }, - }, - }, }, // Screen-flow copy (#1210, #1975). Keyed `flows..label`, // `screens..title` and `screens..fields..{label,placeholder}` diff --git a/src/sales/translations/zh-CN/app.ts b/src/sales/translations/zh-CN/app.ts index ec0d38a3..6fc4cf9f 100644 --- a/src/sales/translations/zh-CN/app.ts +++ b/src/sales/translations/zh-CN/app.ts @@ -499,15 +499,6 @@ export const appSurface: Omit = { label: '客户工作台', description: '面向销售团队的精选客户列表:仅提供快捷筛选,不含视图管理。', }, - app_launcher_page: { - label: '应用中心', - description: '访问全部应用的统一入口', - subtitle: '选择一个应用开始使用', - components: { - app_search: { label: '搜索应用' }, - app_grid: { label: '应用网格' }, - }, - }, case_detail_page: { label: '工单详情', description: '客服工单记录页:关键信息、SLA 进度、明细与活动时间线。', @@ -581,15 +572,6 @@ export const appSurface: Omit = { upcoming_events: { title: '即将开始的活动', label: '即将开始的活动' }, }, }, - utility_bar_page: { - label: '工具栏', - description: '悬浮工具的快捷访问栏', - components: { - notifications_panel: { label: '通知' }, - quick_notes: { title: '快速笔记', label: '快速笔记' }, - quick_search: { label: '快速搜索' }, - }, - }, }, // Screen-flow copy (#1210, #1975). Keyed `flows..label`, // `screens..title` and `screens..fields..{label,placeholder}` diff --git a/test/metadata-references.test.ts b/test/metadata-references.test.ts index 51bdc603..5f83e749 100644 --- a/test/metadata-references.test.ts +++ b/test/metadata-references.test.ts @@ -707,57 +707,30 @@ describe('page component references resolve', () => { * an open bag, so an undeclared key is a success receipt for configuration * that renders nothing. A container is full when it holds components. * - * # The exemption list is the honest half + * # No exemptions * * #771 declined to widen its rule to sidebars because a rule that goes red - * on things the PR is not fixing gets suppressed rather than fixed. The way - * out is not a narrower rule, it is a rule that covers everything with the - * known-open cases named, dated and owned — so the debt is visible instead - * of invisible. Each entry needs an issue, and the assertion below fails - * when an entry is no longer reachable, so a fixed defect cannot leave its - * exemption behind to cover the next one. + * on things the PR is not fixing gets suppressed rather than fixed. #734 + * widened it anyway, naming its known-open cases in an exemption list that + * went red when an entry outlived its defect. #1581 fixed one entry and + * #2118 retired the page holding the last, so the list went with them: + * every container on every page is held to the rule. */ it('no page region renders an empty container', () => { - /** - * `page.name/component.id` of containers that are known-empty and NOT this - * card's to fix. Shrink-only: adding a line needs an issue and a reason. - * - * utility_bar_page/quick_notes — an empty `page:card` on a page #734 - * deliberately does not touch (that page is the reproduction of an - * upstream renderer gap for `global:*` component types, and editing it - * would destroy the evidence). Filed as #1215. - * - * sales_home_page/ai_briefing — RESOLVED, exemption removed (#1581). Its - * copy sat under `properties.description`, which `page:card` does not - * declare. It is now an `element:text` child, so the card holds a - * component, and the stale arm below went red naming this line. - */ - const KNOWN_EMPTY = new Set(['utility_bar_page/quick_notes']); - const bad: string[] = []; - const emptyFound = new Set(); let containersSeen = 0; for (const page of pages) { for (const c of [...walk(page.regions), ...walk(page.slots)] as AnyRec[]) { if (!CONTAINERS.has(c.type)) continue; containersSeen++; if (hasContent(c)) continue; - const where = `${page.name}/${c.id}`; - emptyFound.add(where); - if (KNOWN_EMPTY.has(where)) continue; - bad.push(`${where}: ${c.type} "${c.properties?.title ?? c.label ?? ''}" has no body — it renders as a title over nothing`); + bad.push(`${page.name}/${c.id}: ${c.type} "${c.properties?.title ?? c.label ?? ''}" has no body — it renders as a title over nothing`); } } // Same reason as `tabsSeen` above: the rule must not pass by finding nothing. expect(containersSeen, 'no page containers were inspected — the walk found nothing').toBeGreaterThan(0); expect(bad, `containers that render blank:\n ${bad.join('\n ')}`).toEqual([]); - - // An exemption outlives its defect only if nobody checks. Keyed on still - // being EMPTY, not on still existing: a `quick_notes` that gets a body - // must drop its line here, or the next empty card inherits the cover. - const stale = [...KNOWN_EMPTY].filter((k) => !emptyFound.has(k)); - expect(stale, `exemptions whose container is no longer empty — delete these lines:\n ${stale.join('\n ')}`).toEqual([]); }); /**