Repository navigation
feat(picklists): the shared option lists become picklist metadata (#2000) - #2111
Conversation
The five option lists more than one select field offers are now platform
picklists, referenced by name with `Field.select({ picklist })`:
salutation, industry, lead_source and related_to_type (sales), and
payment_terms (revenue). Each list used by one field returns inline to it.
Option labels for picklist-bound fields move to `picklists.<name>` in all
four locale packs. `src/sales/objects/_picklists.ts` is deleted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3
…red lists
Engines built with `ObjectQL.create({ objects })` register the stack's
picklists (the factory takes none, so every picklist-bound field refused
every value), and the static option readers read a field's offered
options — its inline list, or the picklist it names — so the #490/#531
superset guard and the import-mapping legality check keep reading the
industry and lead_source copies instead of skipping them. The placeholder
and translation-key guards follow the option labels to `picklists.<name>`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3
…ngeset The product pages named `OPPORTUNITY_STAGE_OPTIONS` in the deleted `src/sales/objects/_picklists.ts`; the stage list is now the `stage` field's own options in `opportunity.object.ts`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3
Brings in #2010 (hotcrm#1581): `pnpm lint` now runs `objectstack lint --strict`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012zh91QzFgePbkmuHnugLN3
Brings the finished picklist migration onto main 4de5343 (@objectstack 18.0.0-next.1, the two-package artifact, the suites on @objectstack/verify and SQL). Conflicts, each resolved toward main's behaviour plus the migration: - objectstack.config.ts: main's composeStacks artifact. The picklists register with the app package instead: objectstack.composition.ts gains appPicklists (sales + revenue barrels) and hands it to createHotCrmAppStack. Every picklist-bound field is an app-package field; the service package references none. - src/sales/objects/_picklists.ts: stays deleted. Main's two comment edits (#2035, #2040) are carried to where the comments moved: payment_terms.picklist.ts now names quote_accepted_contract_draft. - contract.object.ts: main's hook name, the branch's picklist reference. - opportunity.view.ts: main's (the stage rowColor map is gone, #2108). - executive.dashboard.ts, mappings/_shared.ts: the branch's comments. - 14 suites: main's imports, drivers and syncSchemas(), plus the branch's picklist lines. Engine suites still built with ObjectQL.create register the app's lists (the factory takes no picklists on 18.0.0-next.1). global-actions reads related_to_type off the booted registry's served field; i18n-references checks picklist translation keys against the lists the booted registry holds. actions-flows-integrity keeps both non-vacuity guards. view-references drops the retired rowColor pins and keeps the mass_update_stage pin. - test/helpers/stack-picklists.ts reads the composed view (the artifact carries packages[] only) and resolves a field's options through the platform's SchemaRegistry.resolvePicklistOptions instead of a local lookup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
… projected task type lead-conversion.flow.ts (arrived with #2068) still said the two selects a converted contact copies live in _picklists.ts; they reference the salutation and lead_source picklists. do-not-call-enforcement.test.ts named plainOptions(TASK_TYPE_OPTIONS), retired by this migration; the screen projects crm_task.type's own options. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
…bels Measured on the live boot (18.0.0-next.1): POST /api/v1/analytics/dataset/query relabels a picklist-bound dimension from picklists.NAME in zh-CN and ja-JP, like the inline-options control dimension beside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
席位核对复核人:repo:hotcrm 执行席(#1353)· 判决 ACCEPT · 钉 head 形态:一卡一 PR, 前提实测:裁决 门禁 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 接受的偏差:① 落点超出认领面的几处:两处注释( 范围外去向:平台 lint 不检查语言包 落地条款:席位经中继 |
Fixes #2000
Clause-②: no
The five option lists that more than one select field offers become platform
picklistmetadata (objectstack#18164), each field references its list by name withField.select({ picklist }), andsrc/sales/objects/_picklists.tsis deleted. This is the finished 17.x branch (c8f331cf) withmain(4de5343e,@objectstack/*18.0.0-next.1) merged in, never rebased, and re-verified on the new pin. The platform change it waited for, objectstack-ai/objectstack#22178 (analytics dimension labels over a picklist-bound field), is in the pin: measured below.What changes
industrycrm_lead.industry,crm_account.industrylead_sourcecrm_lead,crm_contact,crm_opportunity.lead_sourcesalutationcrm_lead,crm_contact.salutationrelated_to_typecrm_task,crm_event.related_to_typepayment_termscrm_quote,crm_contract.payment_termssrc/sales/picklists/index.ts,src/revenue/picklists/index.ts).objectstack.composition.tscollects them asappPicklistsand hands them to the app package stack (createHotCrmAppStack). Every referencing field is an app-package field; the service package references none, so it registers none.objectstack.config.tsismain's, unchanged.crm_opportunity.stage,crm_task.type,crm_event.type,crm_event.status,crm_event_attendee.response,crm_lead.need_type,crm_lead.duplicate_of_type. Their rationale comments moved with them.picklists.NAME(inLOCALE/app.ts). Field labels stay on the field. The ja-JP shared constants for these lists are gone.crm_prefix:defineStack's prefix rule coversobjectsonly, and the lint issue set is identical tomainwith these names.Equivalence with
main, measured mechanicallyRun on the merged tree against an export of
origin/main(4de5343e):main's. For the 11 picklist-bound fields, the options are resolved through the platform'sSchemaRegistry.resolvePicklistOptions. Every other field property and every object-level property is identical. Result: 0 diffs.mainequals the onepicklists.NAMEserves now. No picklist-bound field keeps field-level options. With those moved keys set aside, each pack is identical tomain's. Result: 0 mismatches.main's: actions, flows, views, pages, dashboards, reports, datasets, mappings, hooks, apps, permissions, sharing rules, seed data and the rest.picklistsis the only new key.Merge onto
main(v18): conflicts and resolutionsobjectstack.config.tsmain'scomposeStacksartifact; registration moved toobjectstack.composition.ts(appPicklistsinto the app stack)objectstack.composition.tsmain's two-stack structure +appPicklistssrc/sales/objects/_picklists.ts(modify/delete)main's two comment edits (#2035, #2040) carried to where the comments live now (payment_terms.picklist.tsnamesquote_accepted_contract_draft)src/revenue/objects/contract.object.tsmain's hook name + the picklist referencesrc/sales/views/opportunity.view.tsmain's (the stagerowColormap is gone, #2108)src/sales/dashboards/executive.dashboard.ts,src/sales/mappings/_shared.tsObjectQL.create(attendee-type-resolution,campaign-member-cascade,event-attendee-cascade,flow-variable-conditions,hook-org-inheritance,line-item-cascade,opportunity-line-item-add-picker,win-loss-capture)main's drivers, imports andsyncSchemas(), plusregisterStackPicklists(ql); the factory still takes nopicklistson 18.0.0-next.1global-actions,i18n-references,quote-accepted-payment-terms,view-referencesmain's@objectstack/verifyversions. The branch's additions moved onto the handle. No stand-in was re-grown.global-actionsreadsrelated_to_typeoff the booted registry's served field (verify.metadata.object).i18n-referenceschecks picklist translation keys againstruntime.metadata.items('picklist').actions-flows-integritymain's copy counter and the branch'scheckedlist)import-mappings,placeholder-picklist-optionsmain's imports + the branch's picklist readstest/helpers/stack-picklists.tsnow reads the composed view (the artifact carriespackages[]only). It resolves a field's offered options through the platform'sSchemaRegistryrather than a local lookup.Scope item 2: every non-field consumer
src/sales/views/lead.view.tsDUPLICATE_OF_TYPE_AUTHORABLE_OPTIONSfromlead.object.tssrc/sales/flows/schedule-followup.flow.tsTask.fields.type.options, projected to label/value (a screen option entry is strict)src/sales/actions/opportunity.actions.tsmass_update_stageparam =Opportunity.fields.stage.options, projected; pinned inview-referencessrc/sales/dashboards/executive.dashboard.tslead_sourcefilter roster =LeadSourcePicklist.optionssrc/sales/mappings/_shared.ts,src/sales/objects/lead.hook.ts,src/sales/actions/activity-actions.tssrc/sales/flows/lead-conversion.flow.ts(arrived with #2068)do-not-call-enforcementcomment repointed (named the retiredplainOptions)Scope item 4.
DUPLICATE_OF_TYPE_AUTHORABLE_OPTIONSandDUPLICATE_OF_TYPE_ERASEDare kept and moved intolead.object.tsbeside their field. The authorable/erased split is the enforcement, and the form subset is not a field's option set, so it has no second field to share with.DUPLICATE_OF_TYPE_OPTIONSis retired: it is now the field's own options.plainOptionsis retired: its two callers project the field they mirror. Scope item 5._picklists.tsis deleted.Live boot on 18.0.0-next.1:
records-forms.picklist-shared-across-objectsobjectstack dev -p 4120on a fresh DB at2b4eb38b(the source tree of the head below, which only adds a changeset sentence). Probes: REST withAccept-Languageper locale, and Chromium DOM probes in the Console.GET /api/v1/meta/picklistlistsindustry, lead_source, payment_terms, related_to_type, salutation.picklistkept, in 20 of 20 cells (5 lists × 4 locales).PicklistServedFieldSchema: 44 of 44 (11 fields × 4 locales).crm_account,crm_lead,crm_contactandcrm_opportunity, each picklist-bound select was checked in zh-CN and en. Its native options and the opened listbox both equal the served set in 14 of 14. Two fields sit on a form tab, which was opened first.crm_leadwithindustry=logistics,lead_source=webinar,salutation=profreturns 201;crm_accountwithindustry=logisticsreturns 201. Re-reads return the same stored values in all 4 locales.400 VALIDATION_FAILEDand field codeinvalid_option, the message naming the picklist:industry=not_an_industryoncrm_leadand oncrm_account, andlead_source=carrier_pigeononcrm_contact.行业必须是选项列表“industry”中的值之一.crm_account.industry=logisticsreads Logistics / 物流 / 物流 / Logística in en / zh-CN / ja-JP / es-ES.crm_lead.lead_source=webinarreads Webinar / 线上研讨会 / ウェビナー / Seminario Web.crm_contract.payment_terms=due_on_receiptreads Due on Receipt / 货到付款 / 請求書受領時払い / Pago a la Recepción. Stored values do not change.picklistExtensions, which is outside this card.Analytics labels (the former blocker),
POST /api/v1/analytics/dataset/query:account_metrics×industrylead_metrics×lead_sourceaccount_metrics×typedrillRawRowskeeps the stored values (education,advertisement, …). A picklist-bound dimension is translated exactly like the inline-options control.Verification at
05f36d10(final head)pnpm verifythrough the shared verify lock:VERDICT command-exit 0.validate:✓ Validation passed·Data: 18 Objects 357 Fields 5 Pickliststypecheck:tsc --noEmitexit 0, and--listFilesconfirms the 190 test files are inside itlint(--strict):18 suggestion(s), 0 errors, 0 warnings. The--jsonissue set is identical tomain's (diff empty).lint:i18n-gate:✓ i18n lint gate: 0 i18n/missing-* issueshygiene:✓ source hygiene cleanhygiene:tokens:✓ source token ratchet clean. src/sales authored ~103,047 of ~160,500; src/revenue ~19,038 of ~28,500; src/marketing ~9,142 of ~15,000; src/service ~21,989 of ~33,000.build:✓ Build complete·5 Pickliststest:Test Files 179 passed (179)·Tests 3660 passed | 1 skipped (3661)Ablations (each leg mutated with the ablation tool and restored to the HEAD blob, with
git diff HEADempty):misterunder zh-CNpicklists.salutation.options.objectstack lint --strictexits 0 with 0 picklist findings.i18n-referencesgoes red, 1 failed / 21 passed.picklists.not_a_listentry no package declares.i18n-referencesgoes red, 1 failed / 21 passed.salutation.options, so the stack schema refused the pack first. Both are voided.So the picklist-key guard stays, by the same rule that retired its field-level twin when strict lint took that over (#2012).
Acceptance notes
picklists.NAMEin a locale pack on 18.0.0-next.1. An option key that is not a value, or a list nobody declares, is silently dropped (legs A and B above). This is reported to the seat for upstream filing, not routed around here. The local guard stays only until the platform covers it.ObjectQL.create({ datasources, objects, hooks })still takes nopicklistson 18.0.0-next.1, so the 8 engine suites register the app's lists themselves. It is a harness factory, not an authoring surface; noted, not filed._picklists.tsin dated or planning prose:docs/architecture/module-split-plan.md,docs/architecture/psa-module-plan.md,docs/architecture/module-split-inventory.json, anddocs/requirements/0004–0006. They were not edited: outside the claim, and dated records stay. Noted, not filed.content/docs/**: the three stage pages × three locales that named the deleted file now name thestagefield.Generated by Claude Code