Skip to content

fix(nav): hide the opportunity-built entries from members who cannot read opportunities, and give Sales Home a sidebar entry - #2117

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-2060-item-5-persona-home
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-2060-item-5-persona-home

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Part of #2060 (item 5: C-07, B-19, C-14, A-22). The card stays open; the landing half of this item is not done here, see "What this PR does not change".

Clause-②: no

What changed

  • The sidebar stops offering pages a member cannot open. Five entries are built on crm_opportunity: Home (Executive Overview), Sales Home, Sales Performance, CRM Overview and Pipeline Coverage. Each now carries visible: current_user.can("crm_opportunity", "read") (src/sales/apps/crm.app.ts, one READS_OPPORTUNITIES constant). A member whose permission sets do not read opportunities (Service Agent, Service Manager) no longer gets them. Everyone who reads opportunities (dev admin, sales manager, reps) sees the sidebar they saw before, plus Sales Home.
  • Sales Home has a UI path. nav_sales_home (type: 'page', sales_home_page) is pinned second, under Home. Before, the page could only be reached by typing its URL. Its nav label is in the zh-CN / es-ES / ja-JP packs, with the wording each pack already uses for the page.
  • Sales Home's Quick Create card is removed (rework 1, src/sales/pages/home.page.ts). It was a page:card titled "Quick Create" around a bare nav:menu, so it drew a 28-link copy of the sidebar and could create nothing. That is a title that lies, the same defect for which the Recent Items card was removed from this region. The pinned entry puts the page one click from every rep, so the card goes now, together with its pages.sales_home_page.components.quick_create key in all four packs (en, es-ES, ja-JP, zh-CN). Measured first: ComponentPropsMap on 18.0.0-next.1 has no create-record block. The action:* blocks run script / url / modal / flow / api / form; objectui's runner sends form to a FormView at /forms/NAME, and a string modal target may only name a page. A "New Lead" button would therefore have to hard-code a console route into metadata. A comment in the region says why the card is gone and what would bring it back. The now-empty left_sidebar region stays: measured, an empty region renders nothing (no data-region node, no column). Nothing in test/ or content/docs named the card.
  • Sales Home's description no longer promises quick actions. With the card gone, "key metrics and quick actions" named something the page does not have, and the page shows that line under its title. The description, its aria text and the four packs now read "key metrics, my open work and upcoming events". This goes beyond the card and region the rework named: the rule is that a sentence this change makes false gets corrected.
  • Docs, in all three locales: sales/activities no longer says Sales Home is "the page you land on". getting-started/quick-tour names both pinned entries and says which five entries a member without opportunity read does not see. analytics/dashboards says CRM Overview is built for those who read opportunities.
  • Pin: test/app-navigation-shape.test.ts gets a new describe that checks three things: the gated entry set is exactly these five, Sales Home is second after Home, and the permission sets split the personas the way the gate assumes (rep and manager read opportunities; service agent and service manager do not). test/docs-quick-tour-navigation.test.ts now expects two pinned entries.

Measurements (port 4165, fresh DB, pnpm demo:staff, Playwright DOM probes and API reads)

H1: confirmed, with one correction. Every persona opened the app on dashboard/executive_dashboard. As service.agent, Home showed 8 no-access texts (4 × 403 on POST /api/v1/analytics/dataset/query). Lead Inflow was not refused: it is a crm_lead report, and every permission set reads leads, so it stays ungated. Sales Activity refuses 1 of its 13 tiles (one opportunity_metrics widget); the rest are events, tasks and accounts, so it also stays ungated.

H2: the landing is the first entry of the navigation the server serves. Nothing else decides it. The console (@objectstack/console 18.0.0-next.1, built from objectui b9814b334) handles the bare app URL with resolveLandingRoute → findFirstRoute(activeApp.navigation) (packages/app-shell/src/console/AppContent.tsx). That walk does not apply the sidebar's own gates, neither visible nor the object-read prune withoutUnreadableObjectEntries. Measured on this branch: service.agent still lands on dashboard/executive_dashboard, an entry its own sidebar no longer draws. The spec's PageTypeSchema calls home "the platform-level landing page … Use home for app-level landing". The console does not honour that: a home page renders like any other page, at /page/NAME only. The same goes for isDefault: true on a non-record page: sales_home_page carries it, pnpm validate accepts it with no word, and it changes nothing. Both are reported upstream by the seat. Nothing here routes around them.

H3: the server-enforced gate cannot express "reads opportunities" without a grant change. The server keeps a nav entry only when the caller's permission sets grant every requiredPermissions entry as a capability (systemPermissions; holdsRequiredPermissions in @objectstack/rest). The object:verb form that the console's UnifiedSidebar also accepts is not one. Measured: requiredPermissions: ['crm_opportunity:read'] on Sales Performance removed it from the served /api/v1/meta/app/crm_enterprise for every persona, the admin and the manager included, and pnpm build warned capability-reference-unknown. The demo personas hold no capabilities (/api/v1/auth/me/permissions: systemPermissions: [] for rep, manager and agent). A capability of our own means editing permission sets, which this claim excludes. visible is evaluated in the browser, and current_user.can(object, verb) answers from the same effective object permissions the server enforces. The entry is not secret; the data behind it is already server-protected (403). requiresObject is a capability gate (is the object registered?), not an authorization gate, and is not used.

H4: Sales Home, App Launcher and Utility Bar at their typed URLs (as na.rep).

Page What renders A console shell element?
Sales Home Header, 4 KPI tiles with values, 3 tabs of embedded list views, the AI card, the Upcoming Events list. Before this PR, the Quick Create card rendered a copy of the app navigation (28 links), not create actions. Removed here, see What changed n/a. Now reached from the sidebar (clicked in the probe)
App Launcher Header, a search input, and the app:launcher "Your apps" strip with one app (HotCRM) No. It renders inline in the main column; the console's own app switcher and /home "Your apps" already do this
Utility Bar Heading, an empty Quick Notes card, a search input; the notifications block shows nothing No. It renders in the main column (position: static), not floating; the console has no utility-bar slot

None of the three shows "Unknown component". App Launcher and Utility Bar get no nav entry and are not deleted. The give-a-path / retire / leave decision goes to the maintainer through the seat.

Per persona, before and after

Before (base bd5fda21):

Persona /_console/ lands App lands on Sidebar Dashboard / report entries opened
dev admin launcher /home Executive Overview, 0 refusals 27 entries all 9 render
na.rep, eu.rep, sales.manager into the app Executive Overview, 0 refusals 27 entries all 9 render
service.agent, service.manager into the app Executive Overview: 8 no-access texts, 4 × 403 21 (the console already prunes Opportunities, Quotes, Contracts, My Deals, Campaigns, Forecasts) Home 4 × 403 · Sales Performance 17 × 403 · CRM Overview 8 × 403 · Pipeline Coverage 1 × 403 · Sales Activity 1 × 403 · Account Workbench, Service Overview, Lead Inflow, SLA Performance render

After (this branch):

Persona /_console/ lands App lands on Sidebar Dashboard / report / page entries opened
dev admin launcher /home Executive Overview, 0 refusals 28 (+ Sales Home) all 10 render
na.rep, eu.rep, sales.manager into the app Executive Overview, 0 refusals 28 (+ Sales Home) all 10 render
service.agent, service.manager into the app unchanged: Executive Overview, 8 no-access texts, 4 × 403 17 (no Home, Sales Home, Sales Performance, CRM Overview, Pipeline Coverage) Sales Activity 1 × 403 (1 of 13 tiles) · Account Workbench, Service Overview, Lead Inflow, SLA Performance render

What this PR does not change

Where each persona lands. No mechanism on 18.0.0-next.1 can express a per-member landing within this claim. The console lands on the first served entry and ignores visible. The server-side gate needs new capabilities granted on permission sets. So every member still opens on Executive Overview. For a service agent, that page is now one its sidebar does not list. That is the platform defect above made visible, not a new one. Once the console's landing follows the sidebar's gates, this PR's gates move the agent off it automatically, to Leads, its first visible entry. The per-persona design (rep → Sales Home, manager → Executive Overview, agent → the service dashboard) goes to the maintainer as a decision, through the seat.

Acceptance notes

  • In UnifiedSidebar, the console's client-side requiredPermissions check accepts an object:verb form. The server strips such an entry before it reaches the browser, so for nav entries that branch never runs. Noted only.
  • Sales Activity keeps one refused tile for a member without opportunity read (the platform's per-widget no-access state). The other 12 tiles are activity data that member reads.
  • Sales Home's three-column template recognises only the regions named header / sidebar / main / aside / footer (objectui ThreeColumnTemplate). The page's left_sidebar / right_sidebar are therefore drawn as extra blocks under main, at full width, and their width is ignored. Measured: 0 aside nodes, and right_sidebar sits at x=280, w=1136, below main. The page reads fine as a single column. Reported to the seat, not changed here.
  • While the permissions payload loads, current_user.can cannot answer yet, and the console's visible evaluation fails open. A gated entry can show for a moment before it disappears. This is the console's documented policy.

Tests and gates (head cab1440d, after rework 1)

  • pnpm validate: ✓ Validation passed, exit 0
  • pnpm typecheck: exit 0
  • pnpm lint: 18 suggestion(s), exit 0
  • pnpm lint:i18n-gate: ✓ i18n lint gate: 0 i18n/missing-* issues, exit 0
  • pnpm hygiene: ✓ source hygiene clean, exit 0
  • pnpm hygiene:tokens: ✓ source token ratchet clean, exit 0
  • pnpm build: ✓ Build complete, ⚠ 8 author-time warning(s) (8 on base too), exit 0
  • pnpm test: Test Files 181 passed (181) · Tests 3690 passed | 1 skipped (3691), exit 0
  • apps/docs: pnpm run types:check gives ✓ Types generated successfully, exit 0. next build is left to the Docs App workflow.
  • Browser re-measure of Sales Home as na.rep on cab1440d (sidebar click into the page): description "Sales team home page with key metrics, my open work and upcoming events", 0 "Quick Create" texts, 0 nav:menu blocks, cards Key Performance Indicators / Ask the AI Assistant / Upcoming Events, 0 refused API calls.
  • Ablation (first round, on 97d9ef68), run once with no file kept: removing visible: READS_OPPORTUNITIES from nav_crm_dashboard turned "exactly these entries carry the opportunity read gate" red (expected [ 'nav_home', 'nav_sales_home', …(2) ] to deeply equal [ …(3) ]). The restore was proven: blob 35d70620df70 equals HEAD, and git diff HEAD is empty.

Body refreshed for rework 1 at 2026-10-15T05:03Z; the Vercel preview comment above it carried no request.


Generated by Claude Code

…read opportunities, and give Sales Home a sidebar entry

A service agent was offered Home (Executive Overview), Sales Performance,
CRM Overview and Pipeline Coverage, and each opened on "You don't have
access" tiles. Those entries, and a new pinned Sales Home entry, now carry
`visible: current_user.can("crm_opportunity", "read")`. Sales Home had no
entry at all, only a typed URL. Docs that named the landing page or the
audience are corrected in all three locales.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
…d Sales Home's entry

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
@vercel

vercel Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Oct 15, 2026 4:53am UTC

Request Review

… again and created nothing

The card was a `page:card` titled "Quick Create" around a bare `nav:menu`:
28 navigation links under a title promising create actions. The pinned
Sales Home entry puts that page one click from every rep, so the card goes,
with its four locale-pack keys. Nothing on 18.0.0-next.1 creates a record
from a page block without hard-coding a console route; the comment in the
region says what brings it back.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
With the Quick Create card gone, "key metrics and quick actions" named
something the page does not have. The description, its aria text and the
four locale packs now say what the page shows.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

复核人:repo:hotcrm 执行席(#1353)· 判决 ACCEPT(返工 1 轮后)· 钉 head cab1440d535d81ab1ad7422fecc5dd17f519d02e · 2026-10-15T05:06Z

形态:Part of #2060(第 5 项),无关闭关键词,Clause-②: no;18 文件,+124/−51 = 175 行(派发预算约 150,返工追加约 30,超出部分是页面描述随卡片删除而改写,见偏差 ③);路径面无 governed 面 → 非 Tier H,席位落地。origin/main bd5fda21 是 head 的祖先。4 个提交的 trailer 均为无模型名的 Co-authored-by: Claude + Claude-Session。

交付了什么:第 5 项的「导航」与「Sales Home 入口」两半;「按角色落地」一半未交付(平台限制,见下)。

  • crm.app.ts:建在商机上的 5 个入口(Home = Executive Overview、新增的 Sales Home、Sales Performance、CRM Overview、Pipeline Coverage)带 visible: current_user.can("crm_opportunity", "read")。客服 / 客服经理侧栏 21 → 17 项;管理员、经理、销售保留原有 27 项并多出 Sales Home。Sales Activity(13 块里 1 块读商机)与 Lead Inflow(线索报表)保持开放——dev 实测 H1 列表里 Lead Inflow 并不拒绝,派发令列错一项。
  • 新入口 nav_sales_home(sales_home_page)固定在 Home 之下,是这一页第一次有界面路径;四个语言包补了它的标签。
  • 文档三语:activities 不再说 Sales Home 是「落地页」;quick-tour 写两个固定入口及哪 5 项对不读商机者隐藏;dashboards 把 CRM Overview 的受众改为「读得到商机的人」。
  • 测试:app-navigation-shape.test.ts 钉住被门控的 5 项集合、门控表达式、Sales Home 排第二,以及权限集对商机读取的分界;切除(去掉 CRM Overview 的 visible)红 1 例,恢复后 blob 与 HEAD 一致。
  • 返工 1(席位提出):本 PR 让 Sales Home 一键可达,而它的「Quick Create」卡片画的是一份 28 个链接的侧栏副本、什么也创建不了。dev 先测 18.0.0-next.1 的 ComponentPropsMap:没有新建记录的组件(action:* 只能跑 script / url / modal / flow / api / form,做「New Lead」按钮得写死控制台路由),故按同页「Recent Items」卡的先例删除该卡及四个语言包的 key,留注释说明何时补回;空的 left_sidebar 实测不画任何东西,保留。复测:0 处「Quick Create」、0 个 nav:menu、0 个 ≥400 的 API 响应。

H3 实测(为何用 visible 而非 requiredPermissions):服务端只把导航的 requiredPermissions 当能力名(systemPermissions)核对;写 ['crm_opportunity:read'] 会把入口对所有人(含管理员)隐藏,而为此新建能力属于授权变更,超出认领面。visible 走前端 CEL,直接问对象权限;数据本身仍由服务端 403 保护,入口只是不再引人点进去。

H2 实测(为何落地页没改):控制台的落地 = resolveLandingRoute → findFirstRoute,遍历服务端下发的导航树,不评估 visible、也不做对象读取裁剪,所以客服仍然落在 Executive Overview(现在是一个它侧栏里看不到的入口)。席位在 objectui origin/main 7c13d30 复读 AppContent.tsx L1280-1343 与 UnifiedSidebar.tsx L179 / L246 属实,已立 objectstack-ai/objectui#12426。spec 的 PageTypeSchema 称 home 为落地页、utility 为浮动面板,运行时都不兑现(app.zod.ts 自己说落地页就是第一项导航)——已立 objectstack-ai/objectstack#23610。

门禁 check-runs(钉 head):Build and Test (22.x) ✓ · Typecheck and Build ✓ · Quality Checks ✓ · Check Changeset ✓ · Playwright ✓ · link-check ✓ · CodeQL ✓ · Analyze Code (javascript) ✓ · Label Pull Request ✓ · Vercel Preview Comments ✓ — 10/10 success,mergeable_state clean,base bd5fda21 = 当前 origin/main(未移动,无需在合并树上复读 token ratchet)。

接受的偏差:① 门控走 visible(理由见 H3)。② 认领面加宽:四个语言包各加 nav_sales_home 标签(AGENTS.md i18n 清单要求),返工时 home.page.ts 与语言包的 Quick Create 部分由席位授权。③ 返工超出席位授权:Sales Home 的页面描述原写「key metrics and quick actions」,删卡后变成假话,dev 按「改假即改」改为「key metrics, my open work and upcoming events」(含 aria 与四个语言包)——席位接受。④ 行数 175 / 预算约 160,超出即 ③。⑤ 未改 src/service/index.ts:服务模块的入口无需门控,也无法贡献落地页。

范围外去向:

落地条款:席位经中继 pr_ready + automerge_enable,以 objectstack-fleet[bot] 入合并队列;合并后 #2060 退回 pm:queue(Part of)。这是 p2 队列的最后一项:落地后席位向维护者汇报并等待下一步指示,第 5 项剩余的两个决定一并呈报。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline documentation Improvements or additions to documentation metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants