…i#9738)
Maintainer ruling, letter A: the key is declared on the published
TypeScript twin, so the validator, the type checker, the renderer and the
docs say the same thing, and the key's meaning is written once in the
declaration.
- `packages/types/src/views.ts` — `DetailViewField` gains
`dueLike?: boolean`, with a docblock stating the rendering effect it
actually has (both halves of the overdue affordance, their different
day thresholds, and the field-name fallback that `false` does not
suppress). Every sentence is checked against the tree it describes.
- `packages/types/src/zod/views.zod.ts` — untouched. The mirror already
declared the key; that was the whole defect.
- `mirror-only-published-keys-9729.test.ts` — the pin MOVES with the
declaration rather than being repaired. The `@ts-expect-error` that
recorded the refusal turns unused the moment the twin declares the key
(`TS2578`), so it is replaced by the assignment it guarded plus an
exact `Equal` that separates a DECLARED member from one absorbed by an
index signature. Losing the member again is `TS2353` on the same leg.
- `zod-mirror-parity.test.ts` — the `MirroredUndeclared` row for
`views.zod.ts#DetailViewFieldSchema` is deleted, which is the only
direction that ratchet may move in, and the ledger docstring's two
figures are corrected to the values the sizing pin derives.
Clause-②: yes — the published type's accept set widens by exactly one
key. Measured, not asserted: the interface parses to 13 members at
edbcf1e and 14 at head, the difference being `dueLike`, with
`currency` read by the same instrument on both legs as the lit control;
and a `DetailViewField` literal carrying the key is `TS2353` at
edbcf1e and compiles at head.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L
Fixes #9738
Clause-②: yes
Maintainer ruling, director seat summon 24, 2026-09-18 — letter A, quoted:
Base for every reading below:
edbcf1e7aa17ea85bcc61eb8c3fa657c0a3bdc59, which wasorigin/mainwhen this branch was cut. Head:0457692462.What landed
packages/types/src/views.ts—DetailViewFieldgainsdueLike?: boolean. The docblock states the rendering effect: both halves of the overdue affordance (theOverdue Ndwording inside the relative face, andtext-red-600on the cell's span), their two different day thresholds, the field-NAME fallback, and that writingfalsedoes not suppress that fallback. Every sentence is checked against the tree — see the section below.packages/types/src/zod/views.zod.ts— untouched, and that is a verified reading, not an assumption.DetailViewFieldSchemaalready declaresdueLike: z.boolean().optional().describe(...); the whole defect was that the mirror declared it and the twin did not. Nothing in this branch edits that file.packages/types/src/__tests__/mirror-only-published-keys-9729.test.ts— the pin moves with the measurement; it is ⛔ not "a test that was fixed". See below.packages/types/src/__tests__/zod-mirror-parity.test.ts— theMirroredUndeclaredrow forviews.zod.ts#DetailViewFieldSchemais deleted, and the ledger docstring's two figures are corrected to the values its own sizing pin derives..changeset/duelike-declared-on-detail-view-field-9738.md—@object-ui/typesminor.The widening, measured
1. Member-declaration delta on
DetailViewField, comments stripped, base vs headInstrument: the TypeScript parser, so comments are stripped by construction rather than by a regex —
ts.createSourceFile, find theDetailViewFieldinterface declaration, listmembers.Lit control, same instrument, same corpus, both legs:
currencyis read on base and on head. So the base zero fordueLikeis a measured absence, not a dead instrument. Delta: exactly one key.2. Ablation — a document the twin REFUSED at base and ACCEPTS at head
A throwaway probe file was added to
packages/types/src/__tests__/and the package's own type-check leg run over it. It exists in neither commit; it was deleted before the firstgit add, and the tree is clean of it.Exact code at base: TS2353. That is the same code the card's four-axis analysis named, re-derived here rather than copied.
3. The pin moved WITH the declaration — it was not repaired
mirror-only-published-keys-9729.test.tscarried a@ts-expect-errorwhose own docblock said it "fails in both directions — if the key stops being refused the directive becomes unused and TypeScript reports it". Declaring the key is exactly that direction, and it fired:Three reds, all of them the instruments reporting the ruling correctly. What replaced the directive keeps the pin discriminating in its new direction:
Expect< Equal< DetailViewField['dueLike'], boolean OR undefined > >(spelled with a real union in the source; the word is used here because this body eats short angle-bracket-shaped fragments). ⛔ Not weakened toextends: the bare assignment alone would still compile if someone gaveDetailViewFieldan index signature while removing the member — which is precisely the shapeObjectGridSchemahas one section up in the same file, where an authored key is absorbed asany. The exact equality separates DECLARED from ABSORBED, and also refuses a widening toany.⛔ No pin was deleted to get green, and no exact assertion was relaxed.
4. The ledger row, and why the neighbouring row stayed
MirroredUndeclaredmeasures "the zod mirror STATES a key the TypeScript twin does not". Once the twin states it, that pair measuresnever, so the row is deleted — the ledger's own docblock calls that "the ratchet doing its job, and the only direction this seed may move in". Its sizing pin then reported the docstring figures, derived at test time:Corrected to the derived values, per that assertion's own "WHICH SIDE TO CHANGE" instruction (an entry removed ⇒ correct the docstring). ⛔ No key set was edited to satisfy a sentence.
Note the asymmetry with the row directly above it:
objectql.zod.ts#ObjectGridSchemastayed after objectui#9739's letter C, because a retirement tombstone is still a stated member. Two rulings, the same day, moving this ledger in opposite directions. A comment left where the deleted row stood says so.The docblock's factual claims, checked against the tree
PR objectui#9360 was failed for a docblock asserting what the code does not do. Each claim here was verified:
DetailSectioncallsenrichDetailField(field, objectDefField), whose body opensconst enriched = { ...viewField };, then rendersCellRenderer value=… field=enrichedFieldresolveDueLikeinpackages/fields/src/index.tsx:const declared = (field as DateFieldMetadata OR DateTimeFieldMetadata OR undefined)?.dueLike; if (declared === true) return true;then theDUE_LIKE_FIELD_NAMEregexfalsedoes not suppress the fallback=== trueshort-circuits; any other value falls through to the name test.content/docs/fields/datetime.mdxsays the same ("authoring a neutral name" is the opt-out)formatRelativeDatein@object-ui/core:if (diffDays < -1 && options?.dueLike)returns theOverdue NdphraseisOverdueInstant(date, dueLike)gatestext-red-600inDateCellRenderer's span and inDateTimeCellRenderer'scellClass, outside the per-face branchesisOverdueInstantisdate < startOfToday;formatRelativeDategates ondiffDays < -1— so the red starts a day before the wording doesformatRelativeDatereturns the absolute face fordiffDays < -7, before the overdue branch;cellClassis computed before any branchBehaviour is not merely read, it is run:
packages/plugin-detail/src/__tests__/DetailSection.dueLikeReachesTheCell-9729.test.tsxdrawsend_date(a name that deliberately does not match the convention) with and without the key and asserts the drawn wording changes, with a by-value control leg.What was run
All at head
0457692462, in a dedicated worktree, heavy runs serialised through the shared verify lock.pnpm --filter @object-ui/types type-checktsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json. The third is the leg that reads@ts-expect-error.pnpm exec vitest run packages/types/(repo root)Test Files 209 passed (209),Tests 4834 passed (4834)pnpm exec vitest runover the three renderer suites that read the keyTest Files 3 passed (3),Tests 21 passed (21)pnpm --filter @object-ui/types lint298 problems (0 errors, 298 warnings), every warning a pre-existingno-explicit-any; none falls inside the added rangenode scripts/check-changeset-presence.mjsnode scripts/check-changeset-no-major.mjsmajorbump"pnpm check:control-bytespnpm check:new-line-citationsnode scripts/check-governed-queue-guard.mjs --test(the four changed paths)Lint narrowing, declared. The repo's
lintisturbo run lint, i.e. each package's owneslint .; the narrowing run here is that same script for the one package this diff touches. (1) The population is read from eslint's own config resolution, not guessed. (2)--format jsonover that leg counts 280 files, 0 errors, 298 warnings. (3) Invariance for untouched files:eslint.config.jsconfigures noproject/projectService, so type-aware linting is not enabled and this diff cannot move a verdict on a file it does not contain. The full farm is CI's run.Acceptance notes
Out-of-scope observations, recorded here and in the report, ⛔ not filed and ⛔ not fixed:
content/docs/fields/date.mdxandcontent/docs/fields/datetime.mdxteachdueLikeon object field metadata (DateFieldMetadata/DateTimeFieldMetadata), which was already declared. Neither page documents the detail-view field key this PR declares. That is a gap, not a contradiction, and no page needed editing for this ruling. Noted, not filed — no PR or person is queued to touch those pages. Dedupe words if it is ever wanted:dueLike·detail-view field·docs fields date mdx.resolveDueLikegivesdueLike: falseno suppressing power — the name convention still wins. That is deliberate and documented on both sides; recorded because the new docblock has to state it, and a reader could otherwise takefalsefor an opt-out.⛔ This PR does not open the wider question of whether a UI hint like this belongs in
@objectstack/spec; the key stays objectui-owned until the protocol claims it. Nothing upstream is touched or proposed.⛔ This PR does not add, remove or otherwise touch the
needs:contract-reviewlabel the seat hung on the card, does not leave draft, and does not enqueue. The seat lands it.This branch was produced in the Claude Code session
https://claude.ai/code/session_01UanLVj6xvbS6puBCewLr8L.Generated by Claude Code