Repository navigation
feat(picklists): the shared option lists become picklist metadata — *.picklist.ts and Field.select({ picklist }) replace _picklists.ts (acceptance of objectstack#18164) #2000
Description
Unblocked: #2031 landed as 2f3d898d (PR #2091, 2026-10-13); see the unlock-scan comment.
Re-derived 2026-10-09T14:13Z by the repo:hotcrm seat (session_018Mk4tab2eCyY41UTWK7y7V): the previous blocker objectstack-ai/objectstack#22178 closed completed at 2026-10-08T08:00:12Z, but no installable release carries it yet (npm view @objectstack/cli version answers 17.7.0, which is this repo's pin). So this card waits on #2031, the upgrade card, whose Restart-when: is that release; re-derive again when #2031 closes.
Unlock: a PUBLISHED @objectstack/* release that carries the objectstack#22178 fix, pinned in this repo's package.json (merged upstream is not installable). The migration is done and green on claude/issue-2000-picklist-metadata (c8f331cf); on unlock: merge main, re-run pnpm verify and the live-boot probe (including the zh-CN analytics dimension reading), then open the PR. Ruled 6051357805 (batch hotcrm-R74 item 2, letter A); blocked 2026-10-08T04:13Z.
Sub-issue of objectstack-ai/objectstack#18164 (shared picklist / global value set), the acceptance step. Filed by the #18164 epic PM: the director seat, summon #32, session_016tKoy8NJa35Yih1FdzrVmn. It was taken over on the maintainer's order in the live director chat, verbatim 「你负责接手,并派发处理所有所有相关的」.
Filing gate: ② a ruling's execution. objectstack-ai/objectstack#18164 ruling batch #209 item 1 (5755653853, maintainer 「18164 同意」) states: "hotcrm's migration of its shared lists is filed as a hotcrm card by the epic PM when this lands." The runtime half (objectstack-ai/objectstack#19519, PR #21047, 88b484e00) landed on 2026-10-01 and ships in @objectstack/spec / @objectstack/runtime 17.6.0, which hotcrm already pins. The card was never filed; this is it.
What the platform now offers (17.6.0)
definePicklist({ name, label, description?, options })(packages/spec/src/data/picklist.zod.ts). The kind ispicklist, authored as*.picklist.tsordefineStack({ picklists }).optionsis the field option shape, reused verbatim.- A select field references a list:
Field.select({ picklist: '<name>' }), mutually exclusive withoptions. - The server resolves the reference and serves the field with the resolved
options. Writes are validated against the resolved set. picklistExtensions: [{ extend, options }]: additive, package-level.- Translation face:
TranslationData.picklists.<name>.{ label?, options: { value: label } }. A field that references a picklist inherits it. - Not in 17.6.0: a per-organization overlay (deferred phase 2).
The reading (hotcrm main f44ab6429c)
src/sales/objects/_picklists.ts (286 lines) exports 13 option lists and a plainOptions helper. It is imported by 21 files across src/sales, src/revenue and test/.
Lists used in two or more files:
| List | Files |
|---|---|
LEAD_SOURCE_OPTIONS |
5 |
INDUSTRY_OPTIONS |
3 |
PAYMENT_TERMS_OPTIONS |
3 |
SALUTATION_OPTIONS |
2 |
TASK_TYPE_OPTIONS |
2 |
OPPORTUNITY_STAGE_OPTIONS |
2 |
RELATED_TO_TYPE_OPTIONS |
2 |
The rest are each used by one file.
Scope
- Every option list that more than one select field uses becomes a
picklistin the owning package's*.picklist.ts. Each referencing field switches toField.select({ picklist: '<name>' }).- Names follow the platform's snake_case rule and the app's prefix convention; the claim measures which applies to a picklist name.
- A list used by exactly one field returns to that field as inline
options. A shared kind with one user is the special case the kind exists to remove. - Whether a list is "shared" is measured by its field references, not by import count.
- Non-field consumers (views, flows, actions, the dashboard, the import mapping, the lead hook, tests) read the values from the picklist definition, or keep a local constant if the value list is not a field's option set. Each consumer gets a disposition in the PR body; none is left silently pointing at a deleted constant.
- Translations: option labels for a picklist-bound field move to
picklists.<name>in each locale bundle that carries them (theja-JPshared file included). Field-level option translations remain only for inline options. - Special lists:
DUPLICATE_OF_TYPE_*(authorable vs. erased) andplainOptionsare kept or retired on measurement, with the reason in the PR body. _picklists.tsis deleted if nothing is left in it. Otherwise it keeps only what is not a field option list, and is renamed for what it is.
Acceptance
pnpm verifygreen: validate, typecheck, lint, hygiene, build and test.- Live boot, the checklist item
records-forms.picklist-shared-across-objects: one shared picklist on two objects is served the same resolved options, a value outside the set is refused, and a locale switch relabels. Run it against the app booted on 17.6.0, and record the run in the PR body. - Community-edition rule: picklists are core spec and runtime, and no enterprise capability is needed.
- A changeset: hotcrm's
changeset-checkgate needs one; this changes shipped app metadata.
Not in scope
The per-organization overlay (records-forms.picklist-org-append), and the Studio picklist page (objectstack-ai/objectui#10202).
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 3, 2026
Claim: epic PM round 1 (objectstack-ai/objectstack#18164)
Session:
session_016tKoy8NJa35Yih1FdzrVmnAccount:
hotlong(the session's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-2000-picklist-metadataWorktree:
hotcrm-issue-2000Domain:
repo:hotcrm(a single-lane repository), underepic:#18164Seat: the epic PM of objectstack-ai/objectstack#18164, which is the director seat, summon #32. It was taken over on the maintainer's order 「你负责接手,并派发处理所有所有相关的」; the checklist is objectstack-ai/objectstack#18164
5967887779.File surface:
src/sales/objects/_picklists.ts(deleted, or reduced to what is not a field option list and renamed).*.picklist.tsfiles in the owning package, plus the barrel ordefineStackregistration they need.src/sales/objects/{account,contact,lead,opportunity,task,event,event_attendee}.object.tsandsrc/revenue/objects/{contract,quote}.object.ts.src/sales/views/{lead,opportunity}.view.tssrc/sales/flows/schedule-followup.flow.tssrc/sales/actions/opportunity.actions.tssrc/sales/dashboards/executive.dashboard.tssrc/sales/mappings/_shared.tssrc/sales/objects/lead.hook.tssrc/**/translations/**that carry those options' labels.objectstack.config.tsonly if registration requires it._picklists:test/actions-flows-integrity,test/lead-duplicate-management,test/quote-accepted-payment-terms,test/view-referencesandtest/placeholder-picklist-options..changeset/2000-*.md.Stop on breach; explain in the report.
Container & model:
M,mode:subagent,model: opusClause-②: no
Thread-read: none
Serial constraints cleared: zero open hotcrm PRs at this write. The upstream halves (objectstack-ai/objectstack#19518 and #19519) landed and ship in 17.6.0, which hotcrm
mainpins.objectstack-fleet commentedon Oct 3, 2026
Release: the epic PM of objectstack-ai/objectstack#18164,
session_016tKoy8NJa35Yih1FdzrVmn· 2026-10-03T09:58Z · cause: the maintainer's order in the live director chat, verbatim 「hotcrm延后,先处理平台」 · destination:pm:on-holdclaude/issue-2000-picklist-metadatais still on the remote atf44ab64, which ismain's head. It is an empty claim marker and carries no work, so it is ⛔ not a live claim. Deleting it was refused by this environment's transport, so it is left for whoever restarts the card.Restart-when:the platform half of spec: a shared picklist (global value set) metadata kind — option lists reused across objects and packages are TypeScript constants today objectstack#18164 lands, which is Studio: picklist page and 「use picklist」 in the object designer — the objectui half of objectstack#18164 (phase 2) objectui#10202 (the Studio picklist page), or the maintainer schedules this card sooner.5967912141is released with this comment, and the assignee is removed.Generated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Ruling: batch hotcrm-R74 item 2 · letter A · maintainer 「同意」 2026-10-08T03:08Z
repo:hotcrmseat,session_012zh91QzFgePbkmuHnugLN3. The maintainer answered 「同意」 in this session's chat to the batch presented there with the recommendations 1A 2A 3A 4B.The decision: A. Migrate now, without waiting for objectstack-ai/objectui#10202 (the Studio picklist page). The run is serial after hotcrm#1992, because both edit
src/sales/translations. The fallback was B, wait for #10202; C was not to do the migration.Transition:
pm:on-hold→pm:queue· reason: this ruling schedules the card sooner, which is the second arm of itsRestart-when:line ·pm:epicis removed in the same write, because the card leaves objectstack-ai/objectstack#18164's subtree and goes to therepo:hotcrmlane · source: the maintainer's 「同意」 above, in this session's chat.Premises re-read on
origin/mainc967803. Each one is still a claim for the dev to falsify:src/sales/objects/_picklists.tshas 286 lines and 13 exported*_OPTIONSlists. 21 files undersrcandtestimport it.@objectstack/spec@17.7.0, the pin, carriesdefinePicklistandPicklistSchema(packages/spec/src/data/picklist.zod.ts).5755653853(Ruling: batch #209 item 1 · letter A · maintainer 「18164 同意」): "hotcrm's migration of its shared lists is filed as a hotcrm card by the epic PM when this lands."1e88edc1(PR fix(service): retire crm_case.created_date — cases use the platform's created_at #2009).The scope and acceptance in the body are unchanged. The empty branch
claude/issue-2000-picklist-metadata(atf44ab64, no commits) is reused by the next claim.Generated by Claude Code
7 remaining items
objectstack-fleet commentedon Oct 8, 2026
Verdict: os-dev report
6052029866— ACCEPT asblockedon the platform,pr: null.repo:hotcrmseat,session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T04:15Z.Release: session
session_012zh91QzFgePbkmuHnugLN3(claim6051525236) · reason: a platform defect blocks landing (AGENTS.md §2: wait for the platform fix; no route-around, no half landing) · destination:pm:blocked, unassigned in the same act. The branchclaude/issue-2000-picklist-metadataatc8f331cfkeeps the finished work, ⛔ as a resume point, not a live claim.Blocked-by: objectstack-ai/objectstack#22178
The blocker, verified by the seat in source, not taken from the report. On objectstack
origin/main959c209d:packages/services/service-analytics/src/plugin.tstranslateSelectOptionsbuilds its synthetic field as{ name: fieldName, options }.packages/spec/src/system/i18n-resolver.ts(~3063) reacheslookupPicklistOptiononly throughdef.picklist.So a chart or report dimension over a picklist-bound field keeps its English labels in every locale. The dev measured it on
POST /api/v1/analytics/dataset/query(zh-CN):1e88edc1,industryreads 教育 andlead_sourcereads 广告.typereads 正式客户 on both.Landing now would put English category labels on 7 widgets across 4 dashboards, and on 3 reports, in zh-CN, ja-JP and es-ES. Filed upstream as objectstack-ai/objectstack#22178, with its duplicate check.
What is verified done on the branch (read from the report; the seat re-ran nothing on it):
pnpm verifypassed atc8f331cf: validate shows 5 picklists,lint --strictreads 0 / 0 / 18, and 3803 tests passed.records-forms.picklist-shared-across-objectspassed all three clauses: the same resolved options in 20 of 20 cells,invalid_optionrefused, and the four locales relabelled.picklists.NAMEwith 0 label mismatches, and_picklists.tsdeleted.main+ 5 commits, 75 files (REST compare: behind 0).The dev's open questions, answered by the seat:
not_required / pending / approved / rejected(approval status) are ⛔ not folded in (A). This card's reading, and the ruling, cover the_picklists.tslists. The approval-status vocabulary is a separate question with approval-flow semantics and gets its own card only if someone pulls for it.Unlock and resume: this card is unblocked when #22178's fix is INSTALLABLE, meaning a published
@objectstack/*release carries it and this repo's pin moves to it. Merged upstream is not enough. Then:maininto the branch.pnpm verifyand the live-boot probe, including the zh-CN analytics dimension reading as the regression check.pr_create.hotcrm#1907 and #1595, queued behind this card for file overlap, are not blocked by the platform. Their order is re-decided at the next dispatch.
Implemented-by: os-dev subagent of this session · Reviewed-by:
repo:hotcrmseatGenerated by Claude Code
objectstack-fleet commentedon Oct 8, 2026
Resume note for this card (
repo:hotcrmseat,session_012zh91QzFgePbkmuHnugLN3, 2026-10-08T13:40Z). ⛔ Not a state change: this card stayspm:blockedon objectstack-ai/objectstack#22178.PR #2013 (#1595), awaiting the maintainer, deletes the five hand-built test stand-ins (
test/helpers/{hook-harness,flow-harness,action-sandbox,metadata-fixtures,tenancy-probe}.ts). The tests now run on@objectstack/verify's handle, throughtest/helpers/verify-stack.ts.This card's branch
claude/issue-2000-picklist-metadata(c8f331cf) edits seven of the same files, and its versions still import a deleted stand-in:global-actions,hook-org-inheritance,i18n-references,opportunity-line-item-add-picker,placeholder-picklist-options,quote-accepted-payment-terms,view-references.On resume, after merging
main, move this branch's additions onto the handle (metadata/hooks.run/actions.run/rows) rather than restoring a helper. A re-grown stand-in is refused under #1595's rule ("⛔ Never re-grow a local stand-in").Generated by Claude Code
objectstack-fleet commentedon Oct 13, 2026
Unlock scan by the
repo:hotcrmseat (session_01BCDqr1DqnQjEh5jA9gGkUG), 2026-10-13T12:50Z. ⛔ Not a claim.@objectstack/*past 17.7.0 once the next release publishes — it carries the platform fixes #2016, #2000, #1583 and #1585 wait on #2031 is closed. PR chore(deps): upgrade the ObjectStack platform to 18.0.0-next.0 #2091 merged as2f3d898d, andmainnow pins@objectstack/*18.0.0-next.0.translateSelectOptionsbuilds its synthetic field withoutpicklist, sopicklists.<name>translations never apply objectstack#22178 (PR #22213, fix0e9371f, service-analytics): picklist-bound analytics dimensions. It is present in the installed 18.0.0-next.0, per the fix commit in the package CHANGELOG (the per-row table in chore(deps): upgrade@objectstack/*past 17.7.0 once the next release publishes — it carries the platform fixes #2016, #2000, #1583 and #1585 wait on #2031's report6142372109). For this lane the criterion is "released and pinned", and it now holds.claude/issue-2000-picklist-metadata@c8f331cf(finished work on a 17.x base): mergemain(now v18) first, then re-run the dev's zh-CN dataset probe (industry→ 教育,lead_source→ 广告).pm:blocked→pm:queue. The body'sBlocked-by:line is retired in this act.Generated by Claude Code
objectstack-fleet commentedon Oct 14, 2026
Claim: PM loop round R87 (resume of the finished migration branch)
Session:
session_01P8mZuzuyZAHSrgQKmv6ASvAccount:
sunhaolin(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-2000-picklist-metadata(resumesc8f331cf;mainis merged in, never rebased)Worktree:
hotcrm-issue-2000Domain:
repo:hotcrm(single-lane repo, nodomain:*taxonomy)Seat:
repo:hotcrm#1File surface: as claim
6051525236:src/sales/objects/_picklists.ts(deleted, or reduced and renamed), new*.picklist.tsfiles plus their registration (objectstack.composition.ts/objectstack.config.tsonly if registration needs it), the select fields insrc/sales/objects/{account,contact,lead,opportunity,task,event,event_attendee}.object.tsandsrc/revenue/objects/{contract,quote}.object.ts, the non-field consumers (src/sales/views/{lead,opportunity}.view.ts,src/sales/flows/schedule-followup.flow.ts,src/sales/actions/opportunity.actions.ts,src/sales/dashboards/executive.dashboard.ts,src/sales/mappings/_shared.ts,src/sales/objects/lead.hook.ts), the locale bundles undersrc/**/translations/**carrying those options' labels, the tests importing_picklists(moved onto the@objectstack/verifyhandle per6061167800, ⛔ no re-grown stand-in), one.changeset/2000-*.md(stop on breach; explain in the report)Container & model:
L(a ~50-commit merge onto v18, then a live-boot locale probe),mode:subagent,model: default(dispatch-gates --tier --repo objectstack-ai/hotcrm: "no path-derived mandate"; the tier stays the PM's per-card call)Clause-②: no
Responsibility:
n/a — not a defect cardThread-read: 6145948198
Serial constraints cleared:
src/sales/translationshas no other writer in flight; #2083 (this round) writessrc/revenue/objects/quote.hook.tsonly, and this card writesquote.object.ts/contract.object.ts(disjoint files; the later lander re-reads thesrc/revenuetoken ratchet on the merged tree). #2057 (this round) writes no file.Generated by Claude Code
objectstack-fleet commentedon Oct 15, 2026
os-dev-report
{
"issue": 2000,
"status": "done",
"branch": "claude/issue-2000-picklist-metadata",
"pr": "#2111",
"session": "session_01P8mZuzuyZAHSrgQKmv6ASv (subagent; the parent's harness id)",
"premise_still_valid": true,
"summary": "Resumed the finished 17.x branch (c8f331c), merged origin/main 4de5343 (46 commits, @objectstack 18.0.0-next.1) as merge commit 346951a with no rebase and no force-push, re-verified, and opened draft PR #2111 (head 05f36d1). H1 holds: the migration survives the merge. Picklists now register with the app package stack (objectstack.composition.ts appPicklists into createHotCrmAppStack); objectstack.config.ts is main's. Every conflict was resolved toward main's behaviour plus the migration: 22 files (21 content conflicts plus the _picklists.ts modify/delete), listed in deviations (e) and in the PR body. The branch's test additions moved onto the @objectstack/verify handle (verify.metadata.object, runtime.metadata.items('picklist')), and no stand-in was re-grown. test/helpers/stack-picklists.ts now resolves offered options through the platform's SchemaRegistry.resolvePicklistOptions instead of a local lookup. Equivalence with main, measured mechanically: (1) all 18 objects' offered options and field properties equal main's, 0 diffs; (2) 384 locale-pack option labels equal main's served labels, and the rest of each pack is identical, 0 mismatches; (3) every other composed collection is identical, and picklists is the only new key. H2 holds: definePicklist, Field.select({ picklist }), picklistExtensions and TranslationData.picklists.NAME are all present in the installed 18.0.0-next.1 dist. H3 holds: the analytics blocker objectstack#22178 is fixed on the pin. H4 holds on all three clauses, plus the form enumeration. H5 holds: the token ratchet is clean. Two comments that the migration made false were repointed. lead-conversion.flow.ts arrived from main via #2068 and still cited _picklists.ts. do-not-call-enforcement.test.ts still cited the retired plainOptions.",
"tests": "FINAL HEAD 05f36d1:
pnpm verifythrough os-verify-lock (slot hotcrm-2000) printedos-verify-lock: VERDICT command-exit 0. Step verdict lines: validate✓ Validation passed (2250ms)/Data: 18 Objects 357 Fields 5 Picklists; typechecktsc --noEmitpassed in the && chain (--listFiles shows 190 test/ files inside it); lint18 suggestion(s), 0 errors / 0 warnings; lint:i18n-gate✓ i18n lint gate: 0 i18n/missing-* issues (18 total lint issue(s) reported, unaffected by this gate); hygiene✓ source hygiene clean; hygiene:tokens✓ source token ratchet clean; build✓ Build complete (2427ms)/5 Picklists; testTest Files 179 passed (179)/Tests 3660 passed | 1 skipped (3661). The same split chain also passed at 2b4eb38: 179 files and 3660 tests; validate, lint, i18n-gate, build, hygiene and tokens each exit 0. TARGETED at 2b4eb38: the 17 touched suites gaveTest Files 17 passed (17)/Tests 471 passed (471), VERDICT command-exit 0. LINT VS MAIN:objectstack lint --jsonon an export of origin/main and on the branch gave 0/0/18 both, and the issue-set diff is empty both ways. H3 LIVE (objectstack dev -p 4120, fresh DB, tree 2b4eb38), POST /api/v1/analytics/dataset/query: zh-CN account_metrics x industry reads 教育 / 能源公用事业, lead_metrics x lead_source reads 广告 / 陌生拜访, control account_metrics x type reads 正式客户. ja-JP reads 教育 / エネルギー・公益, 広告 / コールドコール, control 顧客. en reads Education / Energy & Utilities, Advertisement / Cold Call, Customer. drillRawRows keeps the stored values. H4 LIVE (records-forms.picklist-shared-across-objects). Clause 1: GET /api/v1/meta/picklist lists industry, lead_source, payment_terms, related_to_type, salutation; served options are identical with picklist kept in 20/20 cells (5 lists x 4 locales); PicklistServedFieldSchema.safeParse succeeds 44/44 (11 fields x 4 locales); Chromium form enumeration on the create forms of crm_account, crm_lead, crm_contact and crm_opportunity in zh-CN and en has native options and the opened listbox equal to the served set in 14/14. Clause 2: crm_lead with industry=logistics, lead_source=webinar, salutation=prof returned 201; crm_account with industry=logistics returned 201; re-reads in 4 locales return the same values. industry=not_an_industry on crm_lead and crm_account, and lead_source=carrier_pigeon on crm_contact, each return 400 with code VALIDATION_FAILED, fields[].code invalid_option, and a message naming the picklist (zh-CN: 行业必须是选项列表“industry”中的值之一). Clause 3: industry=logistics reads Logistics / 物流 / 物流 / Logística, and payment_terms=due_on_receipt reads Due on Receipt / 货到付款 / 請求書受領時払い / Pago a la Recepción. Extension sub-clause: NOT MEASURED, reason: hotcrm authors no picklistExtensions (outside the card). ABLATION (the picklist translation-key guard; the HEAD with the implementation was committed first; each leg was mutated with objectstack scripts/ablation-replace.mjs and restored to the HEAD blob d07e1406bae1 withgit diff HEADempty). Leg A: bogus keymisterunder zh-CN picklists.salutation.options. Strict lint gave lint-exit=0 with 0 picklist findings; vitest i18n-references gave 1 failed / 21 passed. Leg B: an undeclared picklists.not_a_list entry. Strict lint gave lint-exit=0 with 0 findings; i18n-references gave 1 failed / 21 passed. Leg B's first two attempts were voided: one was refused by the tool (the anchor reappeared in the replacement); the other stripped salutation.options, so the stack schema refused the pack before either check ran. The direction was the expected one: red at the guard, while lint stayed silent.","gates": "LOCAL (hand-derived from hotcrm's package.json verify script + ci.yml, as the dispatch states; dispatch-gates.mjs answers objectstack only, so no --ran reconciliation exists for this repo). Each exit was captured before any pipe, via redirect then $?. On 2b4eb38: validate exit 0, typecheck exit 0, lint exit 0, lint:i18n-gate exit 0, hygiene exit 0, hygiene:tokens exit 0, build exit 0, test exit 0. Final, on 05f36d1:
pnpm verifyexit 0 (VERDICT command-exit 0). CI on PR #2111 head 05f36d1, one read after opening: Vercel Preview Comments success; Label Pull Request success; in_progress: Check Changeset, Typecheck and Build, Quality Checks, link-check, Build and Test (22.x), Playwright, Analyze Code (javascript). The Playwright e2e is CI's, not run locally.","mcp_calls": "0",
"api_writes": "2 — (1) pr_create via node scripts/pm/fleet-write/dispatch.mjs: POST /repos/objectstack-ai/objectstack/dispatches (HTTP 204); the relay run 38661232113 executed POST /repos/objectstack-ai/hotcrm/pulls, and the read-back found 12168 bytes sent and stored identical, assignee sunhaolin, draft true. (2) this os-dev-report: POST /repos//issues/2000/comments via post-stamped.mjs. git push is not REST. label-write: zero, as dispatched. Reads were single-card REST GETs: issue 2000 and its comments, pulls/2111, and check-runs.",
"line_budget": "PR #2111 vs main: +945 / -744 = 1689 changed lines across 75 files (9 added, 1 deleted, 65 modified). This is under the ~3,000 stop line. It counts the branch's own diff, the merge resolutions, the test moves and 3 small follow-up commits (2 comment repoints, 1 changeset sentence).",
"files_changed": {
"count": 75,
"added": [
".changeset/2000-shared-picklists.md",
"src/revenue/picklists/index.ts",
"src/revenue/picklists/payment_terms.picklist.ts",
"src/sales/picklists/index.ts",
"src/sales/picklists/industry.picklist.ts",
"src/sales/picklists/lead_source.picklist.ts",
"src/sales/picklists/related_to_type.picklist.ts",
"src/sales/picklists/salutation.picklist.ts",
"test/helpers/stack-picklists.ts"
],
"deleted": [
"src/sales/objects/_picklists.ts"
],
"modified": [
"content/docs/administration/setup.mdx",
"content/docs/administration/setup.zh-Hans.mdx",
"content/docs/administration/setup.zh-Hant.mdx",
"content/docs/sales/opportunities.mdx",
"content/docs/sales/opportunities.zh-Hans.mdx",
"content/docs/sales/opportunities.zh-Hant.mdx",
"content/docs/sales/pipeline-management.mdx",
"content/docs/sales/pipeline-management.zh-Hans.mdx",
"content/docs/sales/pipeline-management.zh-Hant.mdx",
"objectstack.composition.ts",
"src/revenue/objects/contract.object.ts",
"src/revenue/objects/quote.object.ts",
"src/sales/actions/activity-actions.ts",
"src/sales/actions/opportunity.actions.ts",
"src/sales/dashboards/executive.dashboard.ts",
"src/sales/flows/lead-conversion.flow.ts",
"src/sales/flows/schedule-followup.flow.ts",
"src/sales/mappings/_shared.ts",
"src/sales/objects/account.object.ts",
"src/sales/objects/contact.object.ts",
"src/sales/objects/event.object.ts",
"src/sales/objects/event_attendee.object.ts",
"src/sales/objects/lead.hook.ts",
"src/sales/objects/lead.object.ts",
"src/sales/objects/opportunity.object.ts",
"src/sales/objects/task.object.ts",
"src/sales/translations/en/app.ts",
"src/sales/translations/en/objects.activity.ts",
"src/sales/translations/en/objects.commerce.ts",
"src/sales/translations/en/objects.customer.ts",
"src/sales/translations/en/objects.pipeline.ts",
"src/sales/translations/es-ES/app.ts",
"src/sales/translations/es-ES/objects.activity.ts",
"src/sales/translations/es-ES/objects.commerce.ts",
"src/sales/translations/es-ES/objects.customer.ts",
"src/sales/translations/es-ES/objects.pipeline.ts",
"src/sales/translations/ja-JP/_shared.ts",
"src/sales/translations/ja-JP/app.ts",
"src/sales/translations/ja-JP/objects.activity.ts",
"src/sales/translations/ja-JP/objects.commerce.ts",
"src/sales/translations/ja-JP/objects.customer.ts",
"src/sales/translations/ja-JP/objects.pipeline.ts",
"src/sales/translations/zh-CN/app.ts",
"src/sales/translations/zh-CN/objects.activity.ts",
"src/sales/translations/zh-CN/objects.commerce.ts",
"src/sales/translations/zh-CN/objects.customer.ts",
"src/sales/translations/zh-CN/objects.pipeline.ts",
"src/sales/views/lead.view.ts",
"test/actions-flows-integrity.test.ts",
"test/attendee-type-resolution.test.ts",
"test/campaign-member-cascade.test.ts",
"test/do-not-call-enforcement.test.ts",
"test/event-attendee-cascade.test.ts",
"test/flow-variable-conditions.test.ts",
"test/global-actions.test.ts",
"test/hook-org-inheritance.test.ts",
"test/i18n-references.test.ts",
"test/import-mappings.test.ts",
"test/lead-duplicate-management.test.ts",
"test/line-item-cascade.test.ts",
"test/opportunity-line-item-add-picker.test.ts",
"test/placeholder-picklist-options.test.ts",
"test/quote-accepted-payment-terms.test.ts",
"test/view-references.test.ts",
"test/win-loss-capture.test.ts"
]
},
"deviations": [
"(a) File surface beyond claim 6180895114. Every item is comment-only, follows from deleting _picklists.ts, or follows from fields becoming picklist-bound. No platform file was touched. NEW this round: src/sales/flows/lead-conversion.flow.ts (comment; the text arrived from main via #2068 and still cited _picklists.ts) and test/do-not-call-enforcement.test.ts (comment naming the retired plainOptions). CARRIED from the 17.x round and reported in 6052029866: src/sales/actions/activity-actions.ts (comment), 9 content/docs pages, test/helpers/stack-picklists.ts, and the test suites that do not import _picklists. Those suites are the 8 ObjectQL.create engine suites, global-actions, i18n-references and import-mappings.",
"(b) The changeset keeps its existing name, .changeset/2000-shared-picklists.md, not 2000-picklist-metadata.md. The dispatch says 'the branch may already carry one; keep one', so it stays the one changeset. One sentence on chart and report labels was added after H3 measured them.",
"(c) PR body footer: the os-dev session-URL form
_Generated by [Claude Code](https://claude.ai/code/session_01P8mZuzuyZAHSrgQKmv6ASv)_, following the agent definition rather than the harness reminder's two-line attribution. Commits carry the harness's Co-Authored-By and Claude-Session trailers. No card trailers were used.","(d) For module resolution, four short-lived probe scripts were placed in the worktree root. They were the served-field parse and three Playwright DOM probes, named .probe-served-2000.mts and .pw-*-2000.mjs. Each was deleted in the same command that ran it, and none was committed; git status stayed clean. Everything else lived in the scratchpad issue-2000/ directory.",
"(e) H1 conflicted files (21 content conflicts plus 1 modify/delete), each resolved toward main's behaviour plus the migration: objectstack.config.ts (main's composeStacks; registration moved to the composition), objectstack.composition.ts (main's two stacks + appPicklists), src/sales/objects/_picklists.ts (modify/delete: stays deleted, main's #2035/#2040 comment edits carried into payment_terms.picklist.ts), src/revenue/objects/contract.object.ts (main's hook name + picklist), src/sales/views/opportunity.view.ts (main's: rowColor gone, #2108), src/sales/dashboards/executive.dashboard.ts and src/sales/mappings/_shared.ts (branch's comments), test/{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/syncSchemas + registerStackPicklists), test/{global-actions,i18n-references,quote-accepted-payment-terms,view-references} (main's verify-handle versions + the branch's additions moved onto the handle; view-references drops the retired rowColor pins), test/actions-flows-integrity (both non-vacuity guards kept), test/{import-mappings,placeholder-picklist-options} (main's imports + the branch's picklist reads)."
],
"open_questions": [],
"out_of_scope_findings": [
"class: c · reach: public door —
objectstack lint --strict(os lint) on hotcrm 2b4eb38, @objectstack 18.0.0-next.1. A zh-CN locale pack carrying picklists.salutation.options.mister (not a value of the list) exits 0 with 0 findings. So does a picklists.not_a_list entry that no package declares. The runtime drops both translations silently. The same mistake under a field's own options is reported as translation-option-key-unknown, and an orphan object or field key as translation-target-unknown. Named producer: hotcrm's locale packs, src/sales/translations/LOCALE/app.tspicklists. Evidence: ablation legs A and B in tests. In @objectstack/lint 18.0.0-next.1 dist/index.js, checkOptionKeys walks field options only, and no translation rule readspicklists. hotcrm keeps its local guard (test/i18n-references) until the platform covers it. · Seam: spec:TranslationData.picklists → runtime:@objectstack/lint translation-option-key-unknown / translation-target-unknown (do not walk picklists) | consumer: packages/spec/src/system/i18n-resolver.ts lookupPicklistOption (misses silently) · dedupe words: translation-option-key-unknown picklists, lint picklist translation key, picklists.NAME unknown option key, orphan picklist translation, translation-target-unknown picklist","carrier: none (承接者:无) · noted, not filed ·
ObjectQL.create({ datasources, objects, hooks })still takes nopicklistson 18.0.0-next.1. An engine built from an app's objects therefore refuses every value on a picklist-bound field until registry.registerItem('picklist', …) runs, and hotcrm's 8 engine suites do that via test/helpers/stack-picklists.ts. It is a harness factory, not an authoring surface, so there is no production reach for class a/b/c.","carrier: none (承接者:无) · noted, not filed · internal maintainer docs still name _picklists.ts in dated or planning prose. docs/architecture/module-split-plan.md (also says 'no global picklist metadata kind', now false), docs/architecture/psa-module-plan.md (plans a _psa-picklists.ts beside it), docs/architecture/module-split-inventory.json, and docs/requirements/0004, 0005 and 0006. They are outside the claim and were not edited (dated records stay, AGENTS.md upgrade rule 5)."
]
}
objectstack-fleet commentedon Oct 15, 2026
ACCEPT → PR #2111 comment 6181823445 · 2026-10-15T00:52Z
objectstack-fleet commentedon Oct 15, 2026
落地 · 2026-10-15T00:53Z
git ls-remote origin 'refs/heads/gh-readonly-queue/*'origin/maingit merge-base --is-ancestor 8dfed905 origin/mainorigin/main=8dfed905feat(picklists): the shared option lists become picklist metadata (#2000) (#2111),父提交9e064b3d(#2109)状态变更:
Fixes #2000已代关(completed);摘pm:dispatched、unassign,收口。合并前席位已在本地把 head 与9e064b3d合并:无冲突,token ratchet 干净(src/revenue~19,162 / ~28,500)。随此解锁:#2110(报价总价只读,p3)不再被quote.object.ts卡住。