From b521c43436125a6bc6d4ea26da7e0603e26ab5ab Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 12:40:15 +0000 Subject: [PATCH 1/2] docs(changeset): the four ComponentInput constraint keys were TOMBSTONED, not removed The FROM -> TO list in the objectui#5905 changeset said "removed" for all four keys, contradicting the paragraph directly below it ("Deleting the members outright was the option NOT taken") and describing the exact option the maintainer ruled against: under a non-strict z.object a deleted key is silently STRIPPED, which trades one silent no-op for another. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3 --- ...5-componentinput-retire-constraint-keys.md | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.changeset/5905-componentinput-retire-constraint-keys.md b/.changeset/5905-componentinput-retire-constraint-keys.md index b5fe614c7b..cca4c4d014 100644 --- a/.changeset/5905-componentinput-retire-constraint-keys.md +++ b/.changeset/5905-componentinput-retire-constraint-keys.md @@ -17,11 +17,16 @@ and `placeholder` **0**, against `name` 926, `type` 926, `description` 161, `enu FROM → TO, per key: -- `min: number` → **removed**. Spell the numeric domain out in `description`, which IS - published (`'A positive integer — the contract rejects 0 and fractional values'`). -- `max: number` → **removed**. Same remedy. -- `step: number` → **removed**. Same remedy. -- `placeholder: string` → **removed**. Put the hint in `description`. ⚠️ +⚠️ The four are **TOMBSTONED, not removed** — the declaration stays, the key becomes +unwritable. An earlier draft of this list said "removed", which contradicted the paragraph +below it and described the option this change deliberately did NOT take. + +- `min: number` → **tombstoned** (`?: never`, named Zod refusal). Spell the numeric domain + out in `description`, which IS published (`'A positive integer — the contract rejects 0 + and fractional values'`). +- `max: number` → **tombstoned**. Same remedy. +- `step: number` → **tombstoned**. Same remedy. +- `placeholder: string` → **tombstoned**. Put the hint in `description`. ⚠️ `BaseSchema.placeholder` — the node-level prop a renderer does read — is a DIFFERENT key and is unaffected. @@ -42,7 +47,9 @@ Two limits worth stating rather than papering over: is exactly what the tombstone buys. - The fifth key objectui#5905 named, `inputType`, is **NOT retired here**. `packages/plugin-markdown` authors it (`inputType: 'textarea'`), so it is - declared-and-DROPPED — a different defect that needs a ruling, not a removal. + declared-and-DROPPED — a different defect that needs a ruling, not a removal. That + ruling landed on 2026-08-31 and `inputType` is tombstoned in the follow-up change; this + note records the state as of THIS change, which is what a changeset is for. This is not a verdict that constraint slots on `ComponentInput` were a mistake. The neighbouring `type` field carries a maintainer ruling of 2026-08-17 recording that giving From d3f4cca9edd4e8cfdd15ecc57a45fad1af6b44c4 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 4 Sep 2026 12:44:40 +0000 Subject: [PATCH 2/2] feat(types)!: retire ComponentInput.inputType as an ADR-0049 tombstone The fifth and last key objectui#5905 named, ruled by the maintainer on 2026-08-31 (option B). It was held back from the four-sibling retirement because its defect was a different one: min/max/step/placeholder were declared-and-UNREAD, while inputType was declared-and-DROPPED -- the repository really did author it (plugin-markdown's `content` input) while the manifest serializer dropped it, so retiring it had to rule on that registration first. - delete the no-op `inputType: 'textarea'` write in plugin-markdown - tombstone ComponentInput.inputType: `?: never` on the interface plus a retirementTombstone() named refusal on the Zod mirror, matching the four siblings exactly - flip three pins rather than delete them: plugin-markdown's index.test.ts and the two fork-half controls in component-input-retired-constraint-keys.test.ts - correct the stale "the fork is recorded ... until then this stays a live, writable key" wording in base.ts, base.zod.ts and widget.ts, which this change falsifies Deleting the member outright stays the option NOT taken: ComponentInputSchema is a non-strict z.object, so an undeclared key is silently stripped. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3 --- ...5905-componentinput-inputtype-tombstone.md | 64 +++++++++++++++ packages/plugin-markdown/src/index.test.ts | 36 ++++++++- packages/plugin-markdown/src/index.tsx | 3 +- ...nent-input-retired-constraint-keys.test.ts | 81 ++++++++++++++----- packages/types/src/base.ts | 38 +++++---- packages/types/src/widget.ts | 23 +++--- packages/types/src/zod/base.zod.ts | 23 ++++-- 7 files changed, 216 insertions(+), 52 deletions(-) create mode 100644 .changeset/5905-componentinput-inputtype-tombstone.md diff --git a/.changeset/5905-componentinput-inputtype-tombstone.md b/.changeset/5905-componentinput-inputtype-tombstone.md new file mode 100644 index 0000000000..f2ef359a70 --- /dev/null +++ b/.changeset/5905-componentinput-inputtype-tombstone.md @@ -0,0 +1,64 @@ +--- +'@object-ui/types': minor +'@object-ui/plugin-markdown': minor +--- + +Retire `ComponentInput.inputType` — the fifth and last key objectui#5905 named (ADR-0049 +enforce-or-remove, maintainer ruling 2026-08-31, option B). + +`inputType` was held back when `min` / `max` / `step` / `placeholder` were retired, because +its defect was a different one. Those four were declared-and-UNREAD. `inputType` was +declared-and-DROPPED: the repository really did author it — `packages/plugin-markdown` +wrote `inputType: 'textarea'` on its `content` input, pinned by that package's own test — +while the manifest serializer dropped it. Retiring it therefore had to decide what that +registration should say instead, which is the fork the card reported and the ruling closed. + +FROM → TO: + +- `inputType?: string` → **tombstoned** (`?: never` on the interface, `retirementTombstone()` + named refusal on the Zod mirror). Put the control hint in `description`, which IS + published. +- `plugin-markdown`'s `inputType: 'textarea'` write → **deleted**, at zero capability cost. + +The write was measured as a no-op before it was deleted, and re-measured on this branch's +base rather than inherited from the card. A structural census over every `inputs:` array in +the repository (211 regions, all tracked TS/TSX/JS sources) scores `inputType` at exactly +ONE authoring site — the `plugin-markdown` registration — against `name` 953, `type` 969, +`label` 966, `description` 194, `enum` 119, `required` 86 and `binding` 4 in the same pass +over the same regions, so the instrument was not blind. The other 192 in-repo `inputType` +hits are a DIFFERENT face: `FormField.inputType` (`zod/form.zod.ts`), the text-input +renderer's prop, and `SchemaBuilder.inputType`, none of which sit on a `ComponentInput`. +The publication path is unchanged and was re-confirmed: `packages/sdui-parser/src/index.ts` +forwards exactly six keys per input — `name`, `type`, `required`, `enum`, `binding`, +`description` — so an authored `inputType` could not reach the published +`sdui.manifest.json` even in principle. + +Option A — teach `sdui-parser` to forward the key — is REFUSED on record. The only thing +that looked like demand for it was a write that had never taken effect, and a write nothing +reads is not demand for a feature. The neighbouring 2026-08-17 expression-ceiling ruling +(quoted on `ComponentInput.type`) is untouched and stays deferred, with its reopen +condition — a measured case of an author shipping a spec-rejected value objectui's silence +let through — unchanged. + +Deleting the member outright was again the option NOT taken, for the reason the four +siblings established: `ComponentInputSchema` is a non-strict `z.object`, so an undeclared +key is silently STRIPPED. The tombstone is what converts a write from OUTSIDE this +repository — the half objectui#5905 could not measure — into a named refusal carrying its +own remedy, with `code: 'invalid_type'` and the key named in the issue `path`. + +Accept-set change, stated plainly for reviewers: a document that sets `ComponentInput.inputType` +used to parse GREEN (the value was then dropped by the serializer) and now parses RED. That +is the intended effect and the reason this carries a contract-review label. + +Three pins were FLIPPED rather than deleted, so the closure stays asserted instead of +becoming a silent absence: `plugin-markdown`'s `index.test.ts` (which asserted the write) +now asserts the key's absence plus a `tsc` refusal at that package's own authoring site, +and the two fork-half controls in +`packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts` — one +type-level, one parse-level — now assert refusal where they asserted liveness. + +Stale wording corrected in the same pass, because this change falsifies it: `base.ts` and +`zod/base.zod.ts` both said the fork was "recorded for a ruling; until then this stays a +live, writable key", and `widget.ts` called it "the open fork". All three now record the +ruling. A reader who greps the source instead of the card thread was meeting an open fork +that no longer existed. diff --git a/packages/plugin-markdown/src/index.test.ts b/packages/plugin-markdown/src/index.test.ts index 381b188ae8..50978070de 100644 --- a/packages/plugin-markdown/src/index.test.ts +++ b/packages/plugin-markdown/src/index.test.ts @@ -8,6 +8,7 @@ import { describe, it, expect } from 'vitest'; import { ComponentRegistry } from '@object-ui/core'; +import type { ComponentInput } from '@object-ui/types'; // Imports all renderers to register them. Module scope, NOT awaited inside a // `beforeAll` — there the cold transform of the renderer graph is billed to the // hook, against `hookTimeout`. That is what made the sibling plugin-kanban test @@ -48,7 +49,40 @@ describe('Plugin Markdown', () => { expect(contentInput).toBeDefined(); expect(contentInput?.required).toBe(true); expect(contentInput?.type).toBe('string'); - expect(contentInput?.inputType).toBe('textarea'); + }); + + it('no longer authors `inputType` — the write was a measured no-op (objectui#5905)', () => { + // FLIPPED, not deleted. This assertion used to read + // `expect(contentInput?.inputType).toBe('textarea')`, and it pinned the + // ONLY `ComponentInput.inputType` write in the repository. The manifest + // serializer forwards six keys — `name`, `type`, `required`, `enum`, + // `binding`, `description` — and this was never one of them, so the + // write could not reach the published `sdui.manifest.json` even in + // principle, and a structural census over every `inputs:` array found no + // reader either. Maintainer ruling 2026-08-31 (objectui#5905) deleted + // the write and tombstoned the key. Restated here rather than removed, + // so the deletion stays asserted instead of becoming a silent absence. + const config = ComponentRegistry.getConfig('markdown'); + const contentInput = config?.inputs?.find((input: any) => input.name === 'content'); + + expect(contentInput).toBeDefined(); + expect(contentInput?.inputType).toBeUndefined(); + }); + + it('and re-authoring `inputType` is a `tsc` error at this package\'s own site', () => { + // REAL enforcement, not decoration: this package's `type-check` script + // runs `tsc -p tsconfig.test.json`, so the directive below is evaluated + // and an UNUSED one fails the build. Re-widening + // `ComponentInput.inputType` therefore turns this line red instead of + // quietly letting the no-op write back in. + const reAuthored: ComponentInput = { + name: 'content', + type: 'string', + // @ts-expect-error `inputType` is an ADR-0049 retirement tombstone (objectui#5905) + inputType: 'textarea', + }; + + expect(reAuthored.name).toBe('content'); }); it('should have sensible default props', () => { diff --git a/packages/plugin-markdown/src/index.tsx b/packages/plugin-markdown/src/index.tsx index d28f65192c..c1ea470c59 100644 --- a/packages/plugin-markdown/src/index.tsx +++ b/packages/plugin-markdown/src/index.tsx @@ -56,8 +56,7 @@ ComponentRegistry.register( name: 'content', type: 'string', label: 'Markdown Content', - required: true, - inputType: 'textarea' + required: true }, { name: 'className', type: 'string', label: 'CSS Class' } ], diff --git a/packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts b/packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts index 11ecf9ebc4..6de688af12 100644 --- a/packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts +++ b/packages/types/src/__tests__/component-input-retired-constraint-keys.test.ts @@ -7,9 +7,15 @@ */ /** - * `ComponentInput`'s four inert constraint keys are ADR-0049 RETIREMENT + * `ComponentInput`'s five inert authoring keys are ADR-0049 RETIREMENT * TOMBSTONES, and the refusal is LOUD (objectui#5905). * + * (The file name says "constraint keys" because the first four were retired + * together and all four read like constraint slots. The fifth, `inputType`, is + * a control-kind hint rather than a constraint; it joined them later and by its + * own ruling — see the section below. The name is kept so the file's history + * stays greppable.) + * * ## What was measured * * `min` / `max` / `step` / `placeholder` were declared on `ComponentInput` and @@ -40,15 +46,26 @@ * plus the CONTRAST against a genuinely undeclared key, so nobody can "simplify" * the tombstones into deletions without this file going red. * - * ## `inputType` is NOT here, deliberately + * ## `inputType` IS here now — the fork closed, and how + * + * The fifth key objectui#5905 named used to be live and writable, because the + * repository AUTHORED it: `packages/plugin-markdown/src/index.tsx` declared + * `inputType: 'textarea'` on its `content` input. That was + * declared-and-DROPPED — a different defect from the declared-and-unread four + * — so it needed a ruling rather than a removal, and its liveness was pinned + * here to keep the fork visible. + * + * The ruling landed: maintainer, 2026-08-31 (objectui#5905, director seat + * summon 6, decision batch #14), option B. The write was measured as a NO-OP + * (the serializer dropped it; a structural census over every `inputs:` array + * found no reader), so it was DELETED at zero capability cost and the key + * joined the tombstones. Option A — teach `sdui-parser` to forward it — is + * REFUSED on record: a write nothing reads is not demand for a feature. * - * The fifth key objectui#5905 named is still live and still writable, because - * the repository AUTHORS it: `packages/plugin-markdown/src/index.tsx` declares - * `inputType: 'textarea'` on its `content` input (pinned by that package's own - * test). That is declared-and-DROPPED — a different defect from the - * declared-and-unread four — and it needs a ruling, not a removal. Its liveness - * is pinned below so the fork stays visible and closing it stays a deliberate - * edit to this file. + * The two controls that pinned the fork's LIVE half are FLIPPED below rather + * than deleted (`keeps inputType WRITABLE` and `inputType still parses green`). + * A control is restated on success, never deleted into a vacuum — deleting them + * would leave the closure asserted by nothing at all. * * The `@ts-expect-error` directives are REAL enforcement: this package * type-checks its tests through `tsconfig.test.json`, so re-widening the @@ -59,12 +76,16 @@ import { describe, it, expect } from 'vitest'; import type { ComponentInput } from '../base'; import { ComponentInputSchema } from '../zod/base.zod'; -/** The four retired keys, with a value an author would plausibly have written. */ +/** The five retired keys, with a value an author would plausibly have written. */ const RETIRED = { min: 0, max: 100, step: 1, placeholder: 'Type here…', + // Retired later than the four above, by its own ruling (2026-08-31). The + // value is the exact one `plugin-markdown` used to author, so the loop below + // exercises the real historical write rather than an invented one. + inputType: 'textarea', } as const; type RetiredKey = keyof typeof RETIRED; @@ -103,12 +124,24 @@ describe('the interface tombstones make authoring a `tsc` error', () => { expect(input.name).toBe('content'); }); - it('keeps `inputType` WRITABLE — the fork objectui#5905 reported, not an oversight', () => { - // No `@ts-expect-error`: `plugin-markdown` authors this key today, so - // retiring it is a ruling about that registration, not a cleanup. If this - // line ever needs a directive, the fork was closed — say so on the card. - const input: ComponentInput = { name: 'content', type: 'string', inputType: 'textarea' }; - expect(input.inputType).toBe('textarea'); + it('refuses `inputType` too — the fork objectui#5905 reported is now CLOSED', () => { + // FLIPPED, not deleted. This control used to carry NO directive and assert + // `input.inputType === 'textarea'`, pinning the key's liveness while the + // fork was open. Its own comment named the trigger: "If this line ever + // needs a directive, the fork was closed — say so on the card." The + // maintainer closed it on 2026-08-31 (objectui#5905, option B), so the + // directive is here and this is that saying-so. + // + // The directive IS the assertion: this package type-checks its tests + // through `tsconfig.test.json`, so re-widening the declaration fails the + // build on the now-unused directive. + const input: ComponentInput = { + name: 'content', + type: 'string', + // @ts-expect-error `inputType` is a retirement tombstone (objectui#5905, ruled 2026-08-31) + inputType: 'textarea', + }; + expect(input.name).toBe('content'); }); }); @@ -126,10 +159,18 @@ describe('the zod tombstones REFUSE, loudly (objectui#5905)', () => { } }); - it('`inputType` still parses green — the fork half of the same control', () => { + it('`inputType` now parses RED — the same fork half, restated on the ruling', () => { + // FLIPPED, not deleted: this asserted `success === true` while the fork was + // open. The parameterised loop below covers `inputType` as well, now that + // it is in `RETIRED`; this case is kept because it is the one a reader + // diffs against the old file to see the fork close. const result = ComponentInputSchema.safeParse({ ...LIVE_INPUT, inputType: 'textarea' }); - expect(result.success).toBe(true); - if (result.success) expect(result.data.inputType).toBe('textarea'); + expect(result.success).toBe(false); + if (!result.success) { + const issue = result.error.issues.find((i) => String(i.path[0]) === 'inputType'); + expect(issue?.code).toBe('invalid_type'); + expect(issue?.message).toContain('RETIRED (objectui#5905)'); + } }); for (const key of Object.keys(RETIRED) as RetiredKey[]) { @@ -188,7 +229,7 @@ describe('a tombstone is not a deletion — the contrast, measured in one run', if (result.success) expect(result.data).not.toHaveProperty('notAKeyAtAll'); }); - it('the four stay in the mirror\'s shape — a tombstone is DECLARED, just unwritable', () => { + it('the five stay in the mirror\'s shape — a tombstone is DECLARED, just unwritable', () => { for (const key of Object.keys(RETIRED)) { expect(shapeOf(ComponentInputSchema)).toHaveProperty(key); expect(describeOf(ComponentInputSchema, key)).toContain('RETIRED (objectui#5905)'); diff --git a/packages/types/src/base.ts b/packages/types/src/base.ts index 2e57dcc18e..a93b73789f 100644 --- a/packages/types/src/base.ts +++ b/packages/types/src/base.ts @@ -601,25 +601,35 @@ export interface ComponentInput { advanced?: boolean; /** - * Specific input type (e.g., 'email', 'password' for string) + * RETIRED (objectui#5905, ADR-0049) — never read, and never published: the + * manifest serializer forwards six keys and this is not one of them. Put the + * control hint in `description`, which IS published. + * + * The LAST of the five to be retired, and by its own ruling, because its + * defect was a different one. The four below were declared-and-UNREAD; this + * key was declared-and-DROPPED — the repository really did author it, on + * `packages/plugin-markdown`'s `content` input — so retiring it had to decide + * what that registration should say instead, which is why it was held back + * as a fork rather than retired alongside them. + * + * Maintainer ruling 2026-08-31 (objectui#5905, director seat summon 6, + * decision batch #14) took option B: DELETE the write. It was measured as a + * no-op — the serializer dropped it, and a structural census over every + * `inputs:` array in the repository found no reader — so deleting it costs + * zero capability. Option A, teaching `sdui-parser` to forward the key, is + * REFUSED on record: a write nothing reads is not demand for a feature. The + * 2026-08-17 expression-ceiling ruling quoted on `type` above is untouched + * by this and stays deferred, reopen condition unchanged. * - * ⚠️ NOT retired alongside the four tombstones below (objectui#5905), and the - * difference is measured rather than stylistic. `plugin-markdown`'s - * registration AUTHORS this key — `inputs: [{ name: 'content', …, inputType: - * 'textarea' }]` in `packages/plugin-markdown/src/index.tsx`, pinned by that - * package's own test — while the manifest serializer still drops it. That is - * declared-and-DROPPED, a different defect from the declared-and-unread four - * below: retiring it would convert one registration's silent no-op into a - * build failure without first deciding what that registration should say - * instead (delete the line, or teach the publication path to carry it). The - * fork is recorded on objectui#5905 for a ruling; until then this stays a - * live, writable key that nothing publishes. + * @deprecated Not part of `ComponentInput`'s contract — the value was inert. */ - inputType?: string; + inputType?: never; /** * ADR-0049 RETIREMENT TOMBSTONES — `min` / `max` / `step` / `placeholder` - * (objectui#5905). + * (objectui#5905). `inputType`, declared directly above, is a FIFTH tombstone + * of exactly this shape; it carries its own block because it was retired + * later, by its own ruling, out of a different defect. * * `?: never` is this package's tombstone convention (see `crud.ts` `confirm` * and {@link StaticTableColumn} in `data-display.ts`): the key stays diff --git a/packages/types/src/widget.ts b/packages/types/src/widget.ts index 85dd8a9ad6..1895bee386 100644 --- a/packages/types/src/widget.ts +++ b/packages/types/src/widget.ts @@ -244,15 +244,20 @@ export interface WidgetSourceRegistry { * these. Copying them here would mirror surface that nothing reads on the * face it already lives on. * - * ⚠️ FOUR of those five are now ADR-0049 RETIREMENT TOMBSTONES on - * `ComponentInput` (`min` / `max` / `step` / `placeholder` — `?: never` plus - * a named Zod refusal, objectui#5905), so what this clause records is no - * longer "five keys this face declines to copy" but ONE live key - * (`inputType`) plus four unwritable ones. Copying any of them here is now - * doubly wrong: the four are REFUSED on the face they already live on, and - * `inputType` is the open fork objectui#5905 reported — `plugin-markdown` - * authors it and the serializer still drops it, which is a ruling to make, - * not a surface to mirror. + * ⚠️ ALL FIVE are now ADR-0049 RETIREMENT TOMBSTONES on `ComponentInput` + * (`inputType` / `min` / `max` / `step` / `placeholder` — `?: never` plus a + * named Zod refusal, objectui#5905), so what this clause records is no + * longer "five keys this face declines to copy" but five keys that are + * UNWRITABLE on the face they already live on. Copying any of them here + * would mirror a refusal. + * + * `inputType` was the last of the five and took its own ruling, because it + * was the one the repository actually authored: `plugin-markdown` wrote it + * while the serializer dropped it. Maintainer, 2026-08-31 (objectui#5905): + * delete the write — measured a no-op, so zero capability lost — and + * tombstone the key; teaching `sdui-parser` to forward it was REFUSED on + * record. ⇒ The fork this block used to report as OPEN is CLOSED; do not + * re-read it as a pending question. * * Pin: `__tests__/widget-input-control-vocabulary.test.ts`. */ diff --git a/packages/types/src/zod/base.zod.ts b/packages/types/src/zod/base.zod.ts index 3e65ece460..df31ace4ab 100644 --- a/packages/types/src/zod/base.zod.ts +++ b/packages/types/src/zod/base.zod.ts @@ -314,16 +314,27 @@ export const ComponentInputSchema = z.object({ description: z.string().optional().describe('Help text'), advanced: z.boolean().optional().describe('Advanced option flag'), /** - * ⚠️ NOT retired with the four tombstones below (objectui#5905): - * `plugin-markdown`'s registration authors it, so it is declared-and-DROPPED - * rather than declared-and-unread. See `ComponentInput.inputType` in - * `../base.ts` for the fork and what a ruling on it has to decide. + * ADR-0049 RETIREMENT TOMBSTONE (objectui#5905) — the FIFTH key, retired + * later than the four below and by its own ruling (maintainer, 2026-08-31). + * It was declared-and-DROPPED rather than declared-and-unread: + * `plugin-markdown`'s registration really did author it while the serializer + * dropped it, so retiring it meant ruling on that registration first. The + * ruling deleted the write as a measured no-op and REFUSED teaching + * `sdui-parser` to forward the key. See `ComponentInput.inputType` in + * `../base.ts` for the full record. */ - inputType: z.string().optional().describe('Specific input type'), + inputType: retirementTombstone( + 'RETIRED (objectui#5905) — `ComponentInput.inputType` was never read, and never published: the manifest ' + + 'serializer forwards `name`/`type`/`required`/`enum`/`binding`/`description` and this is not one of them, ' + + 'so an authored value was silently dropped. Delete the key; put the control hint in `description`, ' + + 'which IS published.', + ), /** * ADR-0049 RETIREMENT TOMBSTONES (objectui#5905) — `min` / `max` / `step` / * `placeholder`, the four `ComponentInput` keys measured with no reader on - * either the consumption or the publication path. + * either the consumption or the publication path. `inputType` directly above + * is a fifth tombstone of the same shape, added by a later ruling; everything + * this block says about the mechanism applies to it too. * * `retirementTombstone()` (`./tombstone.zod.ts`) writes each guidance string * ONCE into both author-facing channels — the parse-time issue message and