Skip to content

fix(record-pages): keep the lead's and case's empty to-fill sections on screen - #2005

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-2003-lead-case-empty-groups
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-2003-lead-case-empty-groups

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #2003
Clause-②: no

What changes

Four { group } sections now carry hideEmpty: false:

  • src/sales/pages/lead_detail.page.ts: contact_info, address, additional.
  • src/service/pages/case_detail.page.ts: origin.

Nothing else in either page changes, except the comment beside the sections that records why. 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 fresh lead that carries only its required fields, that default removed 5 of the 10 groups. On a fresh case it removed 3 of 6. This PR applies the #1211 / PR #1991 reasoning, as the maintainer ruled it (option A, 「决裁的三张同意」). Groups whose members a user is expected to fill stay on screen as labelled empty rows. Derived and system groups stay hidden while empty.

Before / after: fresh records, measured in Chromium on 17.6.0

Setup: every reading comes from a new objectstack dev -p 4821 --no-watch --fresh --ui boot with its own fresh OS_HOME, and the artifact was built under the shared lock just before. Each record was created by POST /api/v1/data/{object} as the dev admin, with required fields only:

  • lead: first_name, last_name, company, email, status = new;
  • case: subject, description, status = new, priority = low.

Before each probe, the served GET /api/v1/meta/page/{name} record:details sections were compared with dist/objectstack.json, and they were equal on both pages. A DOM probe then listed every section card on the Details tab. It opened any collapsed card and read the field labels and values.

The "probe only" column is a one-off artifact. It is the HEAD build with hideEmpty: false added to the four groups this PR leaves off, written to a scratch copy of the JSON and not to the source. Nothing from it is committed.

Lead (crm_lead)

group section members left after the strip and title are removed before (94668373) after (1ebe784c) probe only: key on every group
identity salutation, first_name, last_name, full_name renders renders renders
company_info company, title, industry renders renders renders
contact_info mobile, website (email and phone are in the strip) absent renders, 2 empty rows renders
preferences (collapsed) do_not_call, email_opt_out renders renders renders
qualification need_type, estimated_amount, conversion_approval_status, next_followup_date, last_contacted_date, disqualification_reason renders renders renders
assignment none (owner_id is in the strip) absent absent absent
address (collapsed) address absent renders collapsed; opens to 1 empty row renders
conversion (collapsed) is_converted, the three converted lookups, converted_date renders renders renders
duplicates (collapsed) duplicate_of_type, duplicate_of_lead, duplicate_of_contact, duplicate_status absent absent renders, 4 empty rows
additional (collapsed) annual_revenue, number_of_employees, description, notes absent renders collapsed; opens to 4 empty rows renders
sections on screen 5 8 9

Records: before juqdws4hHYcLtTa_, after eeWK2GZn_s60qT0P, probe vE80CUxeMsPQ5TCB.

Case (crm_case)

group section members left after the strip and title are removed before (94668373) after (1ebe784c) probe only: key on every group
basic case_number, subject, description, crm_contact, type renders renders renders
origin origin (owner_id is in the strip) absent renders, 1 empty row renders
sla created_date, closed_date, first_response_date, resolution_time_hours (priority, sla_due_date and is_sla_violated are in the strip) absent absent renders, 4 empty rows
escalation (collapsed) is_escalated, escalated_date, escalation_reason renders renders renders
resolution resolution, resolved_by_article absent absent renders, 2 empty rows
system (collapsed) internal_notes, is_closed renders renders renders
sections on screen 3 4 6

Records: before p4Gcwot6r3hRll7r, after ug6aIeQ3GWO-8Qun, probe ohUbMxwy2off1ji0. On all three, created_date is stored as null (the defect #1992 tracks), so it never counts as a filled member here.

Reasons for each choice (from the members)

Lead

  • contact_info: on. Once the strip takes email and phone, the members left are mobile and website. A rep types both, and the default create form asks for both.
  • address: on. Its one member, address, is typed by a rep, and the create form has an Address section for it.
  • additional: on. This differs from the card's initial reading, which left it hidden. Every member (annual_revenue, number_of_employees, description, notes) is typed by a rep. None is readonly, and no hook or flow writes any of them. lead.hook.ts only reads revenue and headcount to compute the starting rating on insert. The default create form asks for all four, in its Company Information and Additional Information sections. This group holds the lead's Description. It is the lead's equivalent of the opportunity's notes group, which The opportunity's 详情 tab renders two fields out of the fourteen it authors — highlight fields are dropped and the sections left empty disappear #1211 turned on, and the page comment records that a Description section always sat on this page. If the maintainer reads this group as optional rather than expected, the revert is one key.
  • assignment: off. The PM's ⚠️ case, settled by the probe. owner_id is in the strip, so the derived list is empty. With the key the section still renders nothing, the same result The opportunity's 详情 tab renders two fields out of the fourteen it authors — highlight fields are dropped and the sections left empty disappear #1211 measured for financials.
  • duplicates: off. The lead_duplicate_check hook writes these fields as a machine signal. A person changes duplicate_status only after the hook has raised one. On a clean lead every member is empty by nature. With the key it would draw a 4-row empty card.

Case

A record that already has values does not change. In the 17.6.0 console bundle (.objectui-sha 31971ff1, the same bundle #1211 read), the record-details renderer reads the key in exactly one branch: hideEmpty === true with every field of the section empty. A section with any value is therefore not affected by it. This is a code reading. I did not re-measure a seeded record in the browser in this run (NOT MEASURED: seeded-record browser check, reason: the code reading plus #1211's measurement on the same bundle cover it).

Legality of the key

RecordDetailsProps is ComponentPropsMap['record:details'] in @objectstack/spec@17.6.0. Its safeParse on each edited page's record:details properties succeeds, and keeps hideEmpty: false on contact_info, address and additional (lead) and on origin (case). Three controls per page are each refused:

  • hideEmpty: 'no' (expected boolean);
  • the misspelt hideEmptyX (unrecognized key);
  • collapsible: true beside group (cannot be combined).

As #1211 found, PageSchema.safeParse accepts all three controls too, because a component's properties is an open record at that level. So it is not used as the proof.

Verification

  • OS_VERIFY_LOCK_SLOT=hotcrm-issue-2003 bash scripts/pm/os-verify-lock.sh -c 'pnpm verify' at 6e68fa1e, the HEAD of this PR: os-verify-lock: VERDICT command-exit 0. Inside the chain:
    • Validation passed;
    • tsc --noEmit clean;
    • lint 1 warning(s), 18 suggestion(s);
    • 0 i18n/missing-* issues;
    • source hygiene clean;
    • source token ratchet clean;
    • Build complete (9 author-time warnings);
    • Test Files 177 passed (177), Tests 3794 passed | 1 skipped.
  • os lint on the two pages at 94668373 and at HEAD gave identical output (1 warning(s), 18 suggestion(s) both times). The author-time warning list is identical at 9 → 9.
  • Token ratchet:
    • src/sales: interaction layer ~27,968 → ~27,981 (+13; chars 111,870 → 111,924), authored total ~99,929 → ~99,943.
    • src/service: interaction layer ~5,592 → ~5,597 (chars 22,369 → 22,387), authored total ~20,544 → ~20,549.
    • Every ceiling is unchanged.
  • No test is added, as in The opportunity's 详情 tab renders two fields out of the fourteen it authors — highlight fields are dropped and the sections left empty disappear #1211. The tests that read these sections (detail-section-dedup, lead-duplicate-visibility, case-create-form-narrowing) do not read hideEmpty, and they stay green.

Acceptance notes

  • Two departures from the card's initial reading: lead additional is on, and case sla is off. The reasons are above. The card asked for its reading to be confirmed by measurement, not taken as decided.
  • Boot hygiene. The first probe-only boot passed --artifact from the worktree. It served the worktree's dist/objectstack.json instead: the served sections did not equal the probe artifact on either page, which is the objectstack dev -a PATH prints Artifact: PATH but serves dist/objectstack.json, and objectstack start --artifact PATH run beside an objectstack.config.ts serves the config — the explicit artifact flag loses to the cwd objectstack#21501 shape. That reading was discarded. The probe column above comes from a reboot in a directory with no objectstack.config.ts (No objectstack.config.ts found — booting from artifact), where served and probe were equal.
  • Release-note overlap. .changeset/806-detail-sections-group-ref.md (still unreleased) says that SLA & Priority and Resolution appear once the case has a value. That stays true after this PR.
  • Ratchet hint, not acted on. The ratchet suggests re-anchoring two ceilings downward (src/sales interaction layer to ~30,000, src/service interaction layer to ~6,000). That is outside this PR's file surface.

Generated by Claude Code

claude added 2 commits October 3, 2026 23:21
…on screen

An all-empty `record:details` section renders nothing by the platform
default (objectui#8603 ruling A), so on a fresh lead the Details tab
dropped the contact numbers, the address, and the revenue, headcount,
description and notes a rep fills in; on a fresh case it dropped the
origin channel an agent picks. `hideEmpty: false` on the lead's
`contact_info`, `address` and `additional` group references and on the
case's `origin` keeps them as labelled empty rows to fill in. Derived and
system-stamped groups (lead `duplicates`, case `sla` and `resolution`)
stay hidden while empty; lead `assignment` derives an empty list once the
strip takes `owner_id`, so the key would do nothing there.

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 11:33pm UTC

Request Review

@github-actions github-actions Bot added the metadata Declarative metadata — schema, security posture, UI surfaces label Oct 3, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 3, 2026 23:39
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit f24c196 Oct 3, 2026
10 checks passed
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.

Lead and case detail pages: show the sections a user is expected to fill even while empty (hideEmpty: false), the way #1211 did for the opportunity page

2 participants