Skip to content

feat(ux): lay out forms and record pages for the work done on them (#2369) - #2380

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-0snsgp
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/project-thread-0snsgp

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Irene · project thread
Part of #2369
Clause-②: no
Metadata-protocol: none

维护者速读

  • 改了什么: Lay out forms and record pages for the work done on them: required contact email, lead form, email dialog, product price prefill, highlights, case type and SLA colour, article body and votes, call and meeting prefill, related tabs, list record titles #2369 第 1–9 行逐行实测后处理:新建联系人把邮箱和电话放到第一个标签页;新建线索的称呼、名、姓同一行,线索评分说明"留空会自动评分";发送邮件对话框写明发往联系人邮箱;客户、报价单、产品、营销活动、知识文章的页头信息条改为标题里没有的内容(产品选择器因此显示标价);工单表单加入工单类型,SLA 未违反不再显示红色"× 否",违反时显示红色"已违反 SLA"横幅;知识文章正文在前,页头显示有用/没用计数,投票后提示"已记录/已改为/本来就是"并带当前计数;记录通话/会议的参与者字段说明当前联系人或线索会自动加入;商机和工单的相关标签页默认展开 Quotes、Products、Open Tasks。第 10 行只测量,未改任何标题。
  • 为什么改: Irene 同意了 QA: complete browser pass of HotCRM on @objectstack/* 18.2.0 — all 161 feature-inventory rows, Setup and Studio, in six tracks #2320 UX 汇总的建议,这是第 5 张卡(表单与记录页)。
  • 风险与回滚: 低。改动是布局、页头字段、相关列表列、文案和一个动作返回值(文章投票多返回 outcome 和计数);不改权限、共享规则、标题字段或任何流程。回滚即 revert 本 PR。
  • 先看: src/service/actions/knowledge_article.actions.ts(投票返回 outcome 与计数、outcomeMessages)、src/service/pages/case_detail.page.ts(SLA 横幅)、下方"第 10 行:方案"一节。
  • 两分钟验证: pnpm exec vitest run test/forms-record-pages-2369.test.ts test/knowledge-feedback.test.ts test/case-create-form-narrowing.test.ts,预期全部通过;或以 admin 打开一篇已发布文章点 Helpful,应提示 "Thanks — recorded as helpful. Helpful: 1 · Not helpful: 0."。
  • 需要你做的: 第 10 行在"保持现状 / 只显示名称 / 名称在前"三者中选一(下方有实测和建议);平台问题 6 条由席位提交。

What changes

Every row was measured on origin/main (52083a5, then 66c63cb after #2368) in the console on 18.2.0 before it was edited. Screenshots, before and after, are in the project files (issue-2369/, 81 files) and named in the report on #2369.

Row Verdict Change Test
1 · New Contact: required Email on the third tab fixed src/sales/views/contact.view.ts: the Identity section is three columns and holds Email and Phone beside the names and Account. Create with a bad email now shows the error on the Identity tab. test/forms-record-pages-2369.test.ts row 1
2 · New Lead: name row; blank score fixed src/sales/views/lead.view.ts: Contact Information is three columns, Last Name no longer span: 'full'. crm_lead.rating description (and help in four packs) says a blank score is filled from the email domain, phone, title, industry and company size (computeRating, lead.hook.ts:175). row 2
3 · Send Email: generic title, "Confirm", no recipient partly fixed The title already names the action ("Send Email"). SendEmailAction.description now says the email goes to this contact's address and is logged on the timeline (four packs). The button text and naming the actual address are platform: the dialog's button is always "Confirm" with no metadata key, and the dialog is opened without the row, so a {{ record.full_name }} hole prints raw and defaultFromRow fills nothing (measured from the header and the list ⋮). row 3
4 · Product price not filled on pick platform for the prefill; picker improved 18.2.0 forms have no fill-on-pick for a lookup: the price still appears after save (_line-item-price-fill.ts, which reads crm_product.list_price, the field #2324 phase 1 keeps). crm_product.highlightFields now leads with category and List Price, so the product picker on a line item shows the price while you pick. rows 4–5
5 · Highlights repeat the title fixed highlightFields on account (number, tier, health, revenue, owner), quote (account, deal, status, total, expiry), product, campaign (type, status, start, budget) and knowledge article (category, status, audience, both vote counts). A related list without its own columns shows the child's highlightFields, so relatedListColumns pin the old columns on crm_account.parent_account, crm_quote.crm_account / crm_contact / crm_opportunity and crm_knowledge_article.related_to_case. rows 4–5
6 · Case form has no Case Type; on-track SLA is a red "× No" fixed src/service/views/case.view.ts: type on the internal form (the public form is #2368's, unchanged). case_detail.page.ts: is_sla_violated leaves the strip (the strip draws any false boolean as red "× No"); a record:alert case_sla_breached (severity error, four locales) shows only when the flag is true. On the Details tab the flag is a neutral circle. row 6; case-create-form-narrowing (type is creator-authorable; the flag keeps the list column, the alert and the details)
7 · Article body under metadata; no count, no "you voted" fixed, except a persistent voted state knowledge_article.object.ts: the content group is first; counts in the strip. The vote body returns outcome (recorded / changed / already) and the article's helpful / notHelpful tally; outcomeMessages say which, with the counts (four packs). A button that stays marked after you vote is platform: header buttons cannot read per-reader state. test/knowledge-feedback.test.ts "returns the outcome and the article's tally for the toast"
8 · Log a Call / Meeting do not prefill the contact platform for the prefill; hint added The same no-row dialog as row 3: defaultFromRow on attendee_contacts filled nothing, and on a case the field-bound param inherited dependsOn ("Select crm_account first"). The body already adds the contact or lead it runs on as an attendee (activity-actions.ts:325-332), so the picker's helpText now says so (three actions, four packs). The UTC date + time split (Marketing S9) is untouched, waiting on the platform. row 8
9 · Related accordions open collapsed fixed opportunity_detail.page.ts: allowMultiple: true, Quotes, Products and Open Tasks collapsed: false. case_detail.page.ts: Open Tasks collapsed: false. row 9
10 · Titles lead with the number measured, not changed See below. —

Docs: the in-app manual, en and zh: src/docs/crm_sales_activities (who is added as an attendee), src/docs/crm_sales_leads (blank score), src/service/docs/crm_service_cases (the SLA banner), src/service/docs/crm_service_knowledge (the vote message and counts, body first). Changeset (patch).

Row 10: options, measured

Measured three ways on 18.2.0: as shipped, and in a scratch copy of this branch booted fresh with the six titles (account, product, quote, campaign, case, article) set to A the name alone, then B name first (Globex Industries · ACC-000002). Nothing in this PR changes a title.

What depends on the stored title:

  • Import. @objectstack/core's lookup matcher tries, in order: id, the lookup's displayField, the target's nameField, then name, title, label, full_name, email, username, and refuses more than one hit as reference_ambiguous. A bare number (ACC-000012) matches nothing.
Lookup cell in an import today A B
ACC-000012 - Probe Solo (today's title, every old export) linked not found not found
Probe Solo (the name) linked linked linked
Probe Solo · ACC-000012 not found not found linked
ACC-000012 not found not found not found
a name two records share (opportunity, whose title is already its name) reference_ambiguous the same for every object whose name is not unique: product, quote, campaign, case subject, article title. Account names are unique per organization. unique, as today
  • Search. $search reads searchableFields (name and number), so it finds the record by name or number under all three. The global search hit shows the title.
  • Sort. A sort on the title column orders by number today, by name under A and B (measured on display_title).
  • ⌘K, lookup cells, related-list cells, pickers, record_link result screens all show the title (the spec's record_link: "by its display name").
  • Code that copies the title: line items store the product title (product_title, mirrored by product.hook.ts), price-book entries name themselves from it, quote success messages use it, and quote.hook.ts writes "QTE-… - name" into its freeze refusals itself. Stored rows keep the old value until scripts/backfill-record-titles.ts and scripts/backfill-line-product-title.ts run.
  • Tests that pin today's format: A turns 11 tests red in 4 files, B 15 in 4 (stored-record-titles, hooks-runtime-sales, line-item-product-title, quote-accepted-lookups).
Lists, pickers, ⌘K Import Cost
Keep ACC-000002 - Globex Ind…: the name is cut unchanged none
A · name alone, number as a column and the picker subtitle (highlightFields lead with it) Globex Industries, subtitle Account Number: ACC-000002 old exports stop matching; a shared name becomes ambiguous on every object but account 11 tests, backfills, quote.hook text
B · name first Globex Industries · ACC-00…: the number is cut, not the name old exports stop matching; new exports round-trip, unique 15 tests, backfills, quote.hook text; drop the number from highlightFields or the picker subtitle repeats it

Recommendation: B for all six, because it keeps every title unique, so an export still re-imports, and it still puts the name first. A fits crm_account alone, whose name is unique. Either way, the backfills must run on upgrade and old export files stop matching. A platform change would remove that cost: letting the import matcher also try an object's unique fields (the autonumber), which would make A safe everywhere.

Platform findings (for the seat to file)

  1. An action's param dialog is opened without its row, from the record header and from the list ⋮: {{ record.x }} in description and helpText prints raw, and defaultFromRow fills nothing (rows 3, 8).
  2. The param dialog's submit button is always "Confirm" (actionDialog.confirm) with no metadata key (row 3).
  3. A form has no fill-on-pick for a lookup: a line's price arrives only after save (row 4).
  4. The highlights strip draws any boolean false as a red "× No" and true as a green "✓ Yes", whatever the field means (row 6; worked around).
  5. A header action button cannot show per-reader state such as "you voted" (row 7).
  6. The import lookup matcher never tries an object's unique fields, so a bare autonumber cannot name a record and a title change breaks old exports (row 10).

Tests

New: test/forms-record-pages-2369.test.ts (18 tests, rows 1–9). With the source at origin/main all 18 are red. test/knowledge-feedback.test.ts adds one case, red with the action file at origin/main. test/case-create-form-narrowing.test.ts gains a detail.alert surface and type in the creator set. The lead-conversion and import tests that name form sections pass.

Verification

pnpm verify at 252b0e6 (after merging #2370's c362ccd)
pnpm verify = validate && typecheck && lint && hygiene && hygiene:tokens && build && test
 Test Files  253 passed (253)
      Tests  4873 passed | 1 skipped (4874)
EXIT 0

The first full run, before the merge, was red on one lint warning: campaign highlights that held both dates hid the "schedule" group from the detail page (field-group-shadowed). The campaign strip is type, status, start date and budget now. CI runs coverage and e2e.

Ablations
forms-record-pages-2369 with src at origin/main         18 failed | 0 passed
knowledge-feedback with the action file at origin/main   1 failed | 18 passed
Console readings on the dev boot (18.2.0, Playwright)
row 7 vote, published article:  "Thanks — recorded as helpful. Helpful: 1 · Not helpful: 0."
      again:                    "You already marked this article helpful. Helpful: 1 · Not helpful: 0."
      Not Helpful:              "Your vote is now Not Helpful. Helpful: 0 · Not helpful: 1."
      (with {{ result.x }} the holes printed raw; outcomeMessages read ${result.x})
row 3 Send Email from the header and the list row: {{ record.full_name }} printed raw; a to field with defaultFromRow stayed empty
row 8 Log a Call on a case, defaultFromRow on attendee_contacts: picker "Select crm_account first", no value
row 6 breached case: red "SLA breached" banner; on-track case: none; Details tab: neutral circle

🤖 Generated with Claude Code

https://claude.ai/code/session_01J8VVAnq1x2jshPfejjsKuT

Contact email and phone on the first tab, lead name row and score hint,
Send Email description, highlights that carry what the title does not
(product picker shows List Price), case type on the internal form and an
SLA-breached banner instead of a red "No", article body first with vote
counts and outcome messages, attendee hint on the activity actions, and
related panels open by default. Row 10 is measured only.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8VVAnq1x2jshPfejjsKuT
@vercel

vercel Bot commented Oct 29, 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 29, 2026 6:38am UTC

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation ci/cd CI plumbing and the verification pipeline metadata Declarative metadata — schema, security posture, UI surfaces backend Server-side behaviour — hooks, flows, actions labels Oct 29, 2026
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

Verdict: ACCEPT · repo:hotcrm seat 1 · huangyiirene · session session_01J8VVAnPkgK6CWrJa6t7Fk5 · 2026-10-29T06:40Z · head 252b0e6
Implemented-by: card thread session session_01J8VVAnq1x2jshPfejjsKuT (report 6489824787 on #2369). Reviewed-by: the seat, against the 46-file diff and hotcrm origin/main c362ccd.

  • Shape:
  • Rows:
    • 1–2 (create forms): the contact Identity tab now holds every field Create requires, email and phone included, and the contact and lead name parts share one row. The Lead Score help says a blank is scored for you.
    • 3: Send Email says where the mail goes. The "Confirm" button and a named address are platform-only.
    • 4, 8 (prefills): platform-only. The product picker now shows List Price, and the attendee picker's help says the record's own person is added.
    • 5 (header strips): the strips on account, product, quote, campaign and article no longer repeat the title. Each panel that fell back to the strip keeps its old columns through relatedListColumns (quote ×3, parent_account, originating case).
    • 6: the SLA flag left the case strip, where false drew a red pill. A breached case shows an SLA Breached alert instead, guarded with has(). Internal case forms gain Type. Make the public web forms read as guest-facing, and cap a guest's case priority at High: priority options and hotline hint, guest field labels, no pre-filled zero, case number on thank-you #2368's public form is untouched.
    • 7: an article's vote answers with recorded, changed or already, plus the tally. The body leads the page. A persistent voted state on the button is platform-only.
    • 9: every opportunity panel opens on arrival (allowMultiple), and so does the case's Open Tasks panel.
  • Tests:
    • pnpm verify exits 0 at 252b0e6: 253 files, 4873 passed, 1 skipped.
    • forms-record-pages-2369 is new, and its 18 cases are red with src at origin/main. The knowledge-feedback outcome case is red with the action at origin/main.
  • Gates: CI on 252b0e6: every finished run is success. Build and Test and CodeQL analysis are still running, and auto-merge waits for them.
  • Row 10 (record titles): measured only, with options keep / A (name alone) / B (name first). The seat takes it to the maintainer. A follow-up PR closes the card.
  • Platform gaps: the seat files the seven findings upstream. They are the dialog row context, the dialog verb, lookup-pick autofill, boolean highlight colours, per-reader button state, the import matcher's unique fields, and the ${} vs {{ }} lint.
  • Landing: non-governed. In this stroke the seat marks the PR ready and turns on auto-merge.

Generated by Claude Code

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

Labels

backend Server-side behaviour — hooks, flows, actions 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