Repository navigation
fix(forms): stop requiring values the server fills on a line item, a quote and a forecast (#2060 item 7) - #2185
Merged
objectstack-fleet[bot] merged 5 commits intoOct 20, 2026
Conversation
…quote and a forecast The console's create form reads `required` as "the user must type this" and cannot see a hook's default. So four fields that a hook always filled blocked the save on the client, while the same insert over REST stored the value (measured on 18.0.0-next.3). - crm_quote.expiration_date and crm_forecast.snapshot_date: the default moves from the hook into a declared `defaultValue` (quote_date + 30 days, and today). `required` stays. The form now shows "Set automatically when saved." and does not mark the field required on create. The engine applies the default on every insert door, the import dry-run and an import with automations off included. The hook branches that stamped the defaults are removed, so each value has one producer. - crm_opportunity_line_item.unit_price and crm_forecast.period_end: only a hook can compute them (from the product, or from a period start the hook may itself derive). So the hook stays the producer, `required` is dropped and `storage.notNull` keeps the column whole. Claude-Session: https://claude.ai/code/session_01KqkTQNLcxVc588eZpJmp1r Co-authored-by: Claude <noreply@anthropic.com>
quote.object.ts merged without a conflict: #2183 narrowed crm_contact's requiredWhen, this branch declares expiration_date's default; both kept. Claude-Session: https://claude.ai/code/session_01KqkTQNLcxVc588eZpJmp1r Co-authored-by: Claude <noreply@anthropic.com>
…are declared field defaults automation (en, zh-Hans, zh-Hant) said a quote's expiration default lives in an object hook; since a95e2e1 it is the field's own defaultValue, so the page now says which date logic is declared on the field and which stays in hooks. quotes gains the 30-day default in its "What happens automatically" list, and forecasting says a snapshot date left blank is today, in all three locales. Claude-Session: https://claude.ai/code/session_01KqkTQNLcxVc588eZpJmp1r Co-authored-by: Claude <noreply@anthropic.com>
On next.5 a signed-in user's import with automations off is refused (403), so the release note no longer promises that door. It names the one that moved for a user instead: the import dry-run preview stops reporting the four server-filled values as missing (measured: 200, ok 1, for a line item, a quote and a forecast). It also names the three docs pages. Claude-Session: https://claude.ai/code/session_01KqkTQNLcxVc588eZpJmp1r Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
This was referenced Oct 20, 2026
Contributor
Author
席位核对ACCEPT (#2060 item 7) · PR #2185 at
Landing: Generated by Claude Code |
This was referenced Oct 20, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #2060 (item 7)
Clause-②: no
Item 7 of the browser-pass list card (findings A-04, A-21, B-11, C-08, A-20, plus the web-to-case Priority folded in by
6183901714). #2060 remains open for its other items.Three create forms refused a save the server would have completed: the field was
required, so the console demanded a value that a hook or a declared default fills in on insert. This PR makes each declaration say who supplies the value. A default the field can state for itself is now declared on the field, andrequiredstays. A value only a hook can compute keeps the hook as its producer and dropsrequired, whilestorage.notNullkeeps the column whole (ADR-0113's engine-populated column).This is a takeover of the claim in
6268036396. The previous dev's commita95e2e18is kept after a hunk-by-hunk review. Their measurements are repeated on this head, which isorigin/mainb6cadbd5(18.0.0-next.5, #2182, #2183) merged in. Two hunks fall outside the claim's bar and are declared below.Item 7, sub-item by sub-item
All measurements are on 18.0.0-next.5. The console and REST readings come from a dev server booted from this branch's artifact, built at
ffba6024(eed2961fadds only the changeset), on a fresh database staffed bypnpm demo:staff.main, no change here. A sales rep cannot save a new lead without picking themselves as owner: the Create Lead form marksowner_idrequired, which blocks the insert-time stamp that already makes the creator the owner #2097 landed it (PR fix(lead): stop requiring an owner pick on the New Lead form #2099,4f389428, thelead.view.tscomment above'owner_id'). Confirmed: the New Lead form shows Lead Owner with no required mark. A rep saved only name, company and email:POST /api/v1/data/crm_leadanswered 201, and the storedowner_idequalscreated_by, which is the rep's own user id.crm_opportunity_line_item.unit_price(A-21). Verdict: fixed here.requiredis dropped andstorage.notNullis kept.opportunity_line_item_price_fillstays the producer, because a cross-record default has no declared form.cc0608c, next.3): "Please check the highlighted fields: Sales Price", and no POST.{quantity:1, discount:0, crm_opportunity, crm_product}and got 201. Read back:unit_price22000 =list_price22000.PATCH … {unit_price:null}gets 400VALIDATION_FAILED), but the text now reads "unit_price is required". That is finding P2 below.crm_quote.expiration_date(B-11). Verdict: fixed here. The field gainsdefaultValue: cel\addDays(record.quote_date, 30)`, andrequired` stays.quote_date2026-10-20 andexpiration_date2026-11-19.crm_forecast(C-08). Verdict: fixed here.period_enddropsrequired, and the hook stays its producer, becauseperiod_startmay itself be derived after defaults run.snapshot_dategainsdefaultValue: cel\today()`, andrequired` stays.period_end2026-10-31, label Oct 2026,snapshot_date2026-10-20.snapshot_datestill gets 400 "…cannot be cleared". Clearingperiod_endre-derives it: 200, 2026-12-31.stagedeclaresprospectingwithdefault: trueon the option. The engine applies that flag since objectstack#7246 (resolveOptionDefault), and the console's create form seeds it since objectui#11914.stagegets 201 withprospecting.defaultValue: 'prospecting'would declare the same default a second time, so it is not added.6183901714). Verdict: platform, finding P1 below, no app change. The declaration is already correct:crm_case.priorityisrequiredwithlowmarkeddefault: true, andweb_to_caseputs no form-levelrequiredon it.POST /api/v1/forms/support/submitwithout a priority gets 201, and the stored row haspriority: low./_console/f/supportshows Priority* set to "Select an option", and Submit shows "Required: Priority" with no request sent.What changed
opportunity_line_item.object.ts:unit_pricedropsrequired, with a comment.quote.object.ts:expiration_dategets its declared default.quote_dateis declared above it, and the engine applies defaults in declaration order. A test pins that order. fix(quotes): an accepted quote no longer makes its contact, or the account above it, undeletable #2183'scrm_contactrequiredWhencame in untouched.forecast.object.ts:period_enddropsrequired.snapshot_dategets its declared default.quote.hook.tsandforecast.hook.ts: the two stamps the declarations replaced are removed. This is declared deviation D1.opportunity_detail.page.ts: comment only. This is declared deviation D2.administration/automation: it said the quote's expiration default lived in a hook, and now says which date logic is declared on the field.sales/quotes: gains the 30-day default (declared deviation D3).sales/forecasting: says a blank snapshot date is today (declared deviation D3).test/form-required-server-defaults.test.tscovers the declarations, every create door including the hookless dry-run, explicit values kept, and update protection.opportunity-line-item-add-picker.test.ts: the ablation now expects the NOT NULL column's refusal.forecast-period-boundary.test.ts: comment only.Declared deviations from the claim
nulland''for each default, plus a forecast with no start). The results were byte-identical with the base stamps restored and with them removed. The engine applies field defaults beforebeforeInsertand treatsnulland''as unsupplied, so the stamps can never fire.new Date()in UTC against the platform'stoday()), under hook headers that would still call the hook the producer.opportunity_detail.page.ts. The claim allowed page edits "only where therequiredmark lives". The mark does not live in this page. The hunk changes two comment blocks and nothing else; the page's metadata bytes are unchanged. Those comments saidunit_priceisrequiredand that the ablation refuses with "Sales Price is required", and both statements become false with this change.sales/quotes(one bullet) andsales/forecasting(one clause) did not state these defaults before. They now do, in all three locales.administration/automationis the page in the claim's surface that did state one.Verification on
eed2961fEvery heavy step ran through
os-verify-lock.sh, with theVERDICT command-exitread rather than a bare$?.pnpm validate,typecheck,lint,lint:i18n-gate,hygiene,hygiene:tokensandbuild: each exit 0.hygiene:tokensforsrc/revenue: business semantics ~17,997 (ceiling ~24,000), interaction ~2,165 (~4,500), authored total ~21,282 (~28,500).pnpm testasvitest run --shard=k/3 --maxWorkers=2:test/**/*.test.tsin the tree.mainhas since moved tob94cf856(fix(seed): every seeded escalated case carries an authored escalated_date, so an edit no longer re-escalates CASE-00007 #2184, service seed and two tests).git merge-treeagainst this head is clean and no file overlaps, so it is not merged in.Ablations, one per changed behaviour, on
eed2961f. Each went throughablation-replace.mjs, which checked that the anchor hit once and the blob moved, then restored with blob == HEAD and an emptygit diff HEAD.expiration_date'sdefaultValuerequired: trueback onunit_pricesnapshot_date'sdefaultValuerequired: trueback onperiod_endAcceptance notes
snapshot_dateis now the platform'stoday(), the same function behindquote_date's existing default. Before, the hook usednew Date()in UTC. Both read the same date on a UTC box (measured 2026-10-20). They can differ only when a write carries a non-UTC timezone.runAutomations: falseis refused with 403, which is new in the pin and not caused by this PR. So the changeset names the dry-run preview as the import door that changed for a user. The source comments that mention a hookless import describe the engine's own path, which the system principal still uses.docs/feature-inventory.mdrows QUO-003 and FCT-002 still name the hooks as producers of the two defaults. The behaviour they describe is still true. That file belongs to item 16, the family the seat folds such rows into, and it is outside this claim's surface.Findings for the seat
None of these was filed by this run; the seat files them.
default: true.class: a·reach:anonymous/_console/f/support: leaving Priority unpicked shows "Required: Priority" and sends nothing, whilePOST /api/v1/forms/support/submitwithout it gets 201 withlow.Seam: spec:SelectOption.default → runtime:ObjectQL.resolveOptionDefault (honoured) | renderer:apps/console/src/components/FormPage.tsx readPrefill. At objectui61ed5ada, that function seeds only a literal fielddefaultValue, whileplugin-form/src/schemaDefaults.ts(objectui#11914) reads both.readPrefill,FormPage,default: true,Required: Priority,public form.class: a·reach:on this branch,PATCH /api/v1/data/crm_opportunity_line_item/ID {unit_price:null}gets 400VALIDATION_FAILED"unit_price is required", and the console's inline error says the same. The field's label is "Sales Price".Seam: spec:storage.notNull (ADR-0113 engine-populated column) → runtime:@objectstack/types data-error translator, not-null branch. It builds${field} is required, and its hint tells the author to declarerequired, which is the declaration that blocks the create form. That hint is noted as a boundary, not a separate class.NOT NULL,is required,storage.notNull,engine-populated,label.crm_quote_line_item.unit_priceisrequiredwhile the sharedcreateLineItemPriceFillfills it on insert.class: a·reach:console New Quote Line Item, with a quote and a product picked: "Please check the highlighted fields: Sales Price", and no POST.POST /api/v1/data/crm_quote_line_itemwithout a price gets 201 withunit_price75000 = list._line-item-price-fill.ts's own header warns against fixing one line object and skipping the other. It is not changed here because it is outside the claim's file surface, which this takeover cannot amend. The fix is A-21's change, word for word.crm_quote_line_item,unit_price,Sales Price,price fill.class: a·reach:console New Forecast, Period Month, Period Start 2026-10-15, Period End blank. The toast reads "Period Start must be the first day of the period … ; Period End must be the last day of the period …", and the POST carried noperiod_endand got 400.period_end_matches_calendar_periodjudges the derived end. Before this PR the form stopped on the required Period End first, so this PR makes the message reachable from the form.period_end_matches_calendar_period,Period End must be the last day,mid-quarter,crm_forecast.Generated by Claude Code