Skip to content

fix(opportunity-page): keep the deal's empty classification, campaign and notes sections on screen - #1991

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-1211-opportunity-empty-groups
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-1211-opportunity-empty-groups

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #1211
Clause-②: no

What changes

src/sales/pages/opportunity_detail.page.ts: three of the seven { group } sections on the Details tab now carry hideEmpty: false. They are classification, campaign and notes. Nothing else in the page changes, and no fieldGroups change.

The platform default is objectui#8603 ruling A: a section whose fields are all empty renders nothing, with no heading and no skeleton. On a deal that carries only its required fields, that default removed the deal's type and source, its campaign, and its description and next step from the page. These are the fields the confirmed plan (5823958911, maintainer: 「同意。」) asked to keep visible. With hideEmpty: false they show as labelled empty rows that a seller can fill in.

Mapping: old hand-written sections to today's groups (read from crm_opportunity.fieldGroups membership)

old section old field group today where it renders on a deal
info name basic page title (the renderer drops it from sections)
info crm_account basic highlights strip
info owner_id basic highlights strip
info type classification Details tab
info lead_source classification Details tab
info crm_campaign campaign Details tab
description description notes Details tab
description next_step notes Details tab

So the info and description sections of the plan land on classification, campaign and notes. The share of info that sits in basic is already on screen for every deal, in the title and the strip. basic itself is left alone; see the measurement below.

Before / after: sparse deal, measured in Chromium on 17.6.0

Setup: a fresh objectstack dev -p 4811 --no-watch --compile boot. The sparse deal was created by POST /api/v1/data/crm_opportunity as admin with required fields only: name, crm_account, amount, stage = prospecting and close_date. It has no type, lead source, campaign, description, next step or primary contact. Before each probe, the served GET /api/v1/meta/page/opportunity_detail_page sections were compared with the compiled artifact and with the source, and they were equal.

group section members left after the strip and title are removed before (1e47470e) after (4f3a58c4) probe only: + basic, + financials
basic primary_contact absent absent renders, 1 empty row
financials none (amount and expected_revenue are in the strip) absent absent absent
classification type, lead_source, win_reason, loss_reason, loss_details absent renders, 5 empty rows renders
campaign (collapsed) crm_campaign absent renders collapsed; opens to 1 empty row renders
sales_process stage, stage_entry_date, approval_status, approved_date renders renders renders
crm_forecast (collapsed) days_in_stage, is_private, forecast_category renders renders renders
notes description, next_step absent renders, 2 empty rows renders
sections on screen 2 5 6

Reasons for each choice:

  • classification, campaign, notes: on. Together they hold every field of the old info and description sections that is not already in the title or the strip. Each went from absent to present.
  • financials: off. This is the PM's ⚠️ item, and the measurement settles it. With hideEmpty: false the section still renders nothing: no heading and no empty shell. The renderer first removes the strip's fields from the group's members, which leaves an empty list. It then returns null for an empty list whatever hideEmpty says. On this page the key would be authored and do nothing, so it is not authored.
  • basic: off. With the key it renders a "Basic Information" card holding a single empty Primary Contact row. primary_contact was never part of the old info section, and the plan's list (source, campaign, type, next step, description) does not name it. The rest of basic is shown on every deal in the title and the strip.

The "probe only" column comes from a one-off mutation run through node scripts/ablation-replace.mjs from objectstack in WRAP mode. The anchor matched once, the blob changed from 6d276c2a5d73 to cc438f42b412, and the served sections showed the mutation. The restore was proven by blob equal to HEAD (6d276c2a5d73) and an empty git diff HEAD. Nothing from that run is committed.

A deal that already has values does not change. Measured after the change on seeded JQBwoPdN8lNzs6q5: Classification shows Type and Lead Source, with Show 3 empty fields folding the rest as before. Campaigns now appears empty, as it does on 17 of the 23 seeded deals, which have no campaign. On seeded gc1G4KqzWSOD-Ofo, Notes & Next Steps now appears with 2 empty rows. In the 17.6.0 console bundle (.objectui-sha 31971ff1), DetailSection reads hideEmpty in exactly one place: the branch where every field is empty. A section with any value is therefore not affected by the key.

Legality of the key (hypothesis 1)

Confirmed, with one correction. PageSchema.safeParse on the edited page succeeds, but it cannot tell a good edit from a bad one: it also accepts all three controls below, because component properties is an open record at the PageSchema level. The parse that can tell them apart is RecordDetailsProps (=== ComponentPropsMap['record:details'], @objectstack/spec@17.6.0):

  • edited page: success: true, with hideEmpty: false kept on the three sections.
  • control collapsible: true beside group: refused (collapsible cannot be combined with group).
  • control hideEmpty: 'no': refused (expected boolean).
  • control misspelt hideEmptyX: refused (unrecognized key).

pnpm validate passes. The author-time warnings stay at 7 → 7, and the two rule lists are identical.

Verification

  • OS_VERIFY_LOCK_SLOT=hotcrm-issue-1211 bash scripts/pm/os-verify-lock.sh -c 'pnpm verify' at bfe9602a (the HEAD of this PR): os-verify-lock: VERDICT command-exit 0. Inside the chain: Validation passed · tsc --noEmit clean · lint 1 warning(s), 16 suggestion(s) · 0 i18n/missing-* issues · source hygiene clean · source token ratchet clean · Build complete (7 author-time warnings) · Test Files 174 passed (174), Tests 3705 passed | 1 skipped.
  • Token ratchet, src/sales: interaction layer ~27,633 → ~27,646 (+13; chars 110,531 → 110,585). Authored total ~96,545 → ~96,558. The ceilings (31,000 / 100,000) are unchanged.
  • No test is added. test/detail-section-dedup.test.ts reads these sections, and it does not read hideEmpty. It stays green.

Acceptance notes

  • Lead and case pages (measured, not edited; out of scope here). On a fresh lead created with required fields only, 5 of the 10 referenced groups render. contact_info, assignment, address, duplicates and additional vanish. assignment (only owner_id, which is in the strip) is an empty list, so hideEmpty cannot bring it back. On a fresh open case, 3 of 6 groups render. origin, sla and resolution vanish. The earlier note that the case page is not at risk predates the group form and no longer holds.
  • Release-note overlap. .changeset/806-detail-sections-group-ref.md (still unreleased) says the Campaigns group appears once a campaign is set. After this PR it appears always. This PR's changeset states the new behaviour. The older sentence is outside this PR's file surface.
  • Boot hygiene. The first "after" boot used --no-watch without --compile. It served the stale dist/objectstack.json; the CLI prints this boot serves the STALE build for this case. The served-metadata check caught it, and every "after" reading above comes from a --compile boot.

Generated by Claude Code

claude added 2 commits October 3, 2026 00:52
… and notes sections on screen

An all-empty `record:details` section renders nothing by the platform
default (objectui#8603 ruling A), so on a deal carrying only its required
fields the Details tab dropped the deal's type and source, its campaign,
and its description and next step. `hideEmpty: false` on those three
group references keeps them as labelled empty rows to fill in.

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

vercel Bot commented Oct 3, 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 3, 2026 1:05am UTC

Request Review

@github-actions github-actions Bot added the metadata Declarative metadata — schema, security posture, UI surfaces label Oct 3, 2026
…pty-group fix makes false

The opportunity Campaigns section now renders while empty, so the unreleased
806 note that it appears only once a campaign is set no longer holds.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ER8ntXZhYebyQ66aXWdjfT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

metadata Declarative metadata — schema, security posture, UI surfaces

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The opportunity's 详情 tab renders two fields out of the fourteen it authors — highlight fields are dropped and the sections left empty disappear

2 participants