Skip to content

Commit 8dbd1c6

Browse files
committed
fix(sdui-parser): stamp inert-quick-add as a literal, and add the changeset
check:dispatcher-error-vocabulary cannot reduce a kebab-case SCREAMING_SNAKE constant at a `code:` position, so objectui's `code: INERT_QUICK_ADD` is an unresolvable-constant finding here. This is the same documented divergence `unconsumed-widget-option` already carries: stamp the inline literal, keep the exported constant, and pin the two equal next door. The lockstep resolves the identifier through the constant, so both copies still agree on all 26 codes. The changeset is OWED and measured: both codes reach the published dist (the package ships `files: [dist]`), against a negative control that reads zero. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
1 parent f84826f commit 8dbd1c6

3 files changed

Lines changed: 44 additions & 1 deletion

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@objectstack/sdui-parser": minor
3+
---
4+
5+
The save gate now stamps `inert-quick-add` and `member-type-mismatch`, the two diagnostics that existed only in objectui's copy of this parser — so a page no longer saves clean here and renders with a different verdict there (#17645).
6+
7+
The two copies of this parser owe each other one thing: byte agreement on the accepted grammar and on diagnostic codes. This copy runs the **save gate** and objectui's runs the **renderer**, so a code on one side only is a dialect — the author gets one reading when they save and another when the page draws, which is surface-dependent and therefore reaches them as intermittent. Measured at the ported revision: objectui stamped 26 codes, this copy stamped 24, and the missing two were exactly these.
8+
9+
- **`inert-quick-add`** (warning) — `quickAdd` on `<object-kanban>` reaches no control. The Quick Add button is gated on **both** `quickAdd` and an `onQuickAdd` handler, and `onQuickAdd` takes a function, which no page on this tier can write (this tier parses, it never executes) and which the board substitutes none of its own for. It **replaces** the `unknown-prop` this copy used to emit for the key, which was false against the contract: `ComponentPropsMap['object-kanban']` publishes `quickAdd`, so an author who checked the spec found the warning contradicted and kept a key that will never do anything. Asked ahead of the declaration lookup on purpose — the claim is about the render path, so declaring the key must not silently disarm it. A falsy value and an unevaluated braced expression are deliberately untouched.
10+
- **`member-type-mismatch`** (warning; `error` when an `enum` arm is present) — the coarse type check one level down, over the member kind an input declares. This brings the `ManifestInput.of` key and its three readers with it: the validator, the serializer's canonicalization, and the codegen's element type. `of: 'string'` on an array input now types the members `string[]` in the generated `.d.ts` instead of `unknown[]`, and a member no declared arm accepts draws **one** diagnostic naming every offending position rather than one per member.
11+
12+
**Nothing published changes shape for an input that declares no `of`.** The key is absent-means-undeclared: the validator checks no member, the codegen emits the unnarrowed element type, and `manifestFromConfigs` publishes no `of` at all, so an entry written before the key existed serializes byte-identically. Measured on the tracked `sdui.manifest.json`: 0 of 339 inputs declare `of`, and the artefact regenerates to the same sha256 across this change.
13+
14+
⚠️ **Both new codes are diagnostics, not a new red gate.** Each is a warning, so `compile().ok` — the save gate's pass/fail — is unchanged, and a page that saves today still saves. Escalating an inert authored key to `error` is a separate question and belongs at the save gate, not here.

‎packages/sdui-parser/src/__tests__/inert-quick-add.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,19 @@ describe('an authored `quickAdd` is diagnosed on the ObjectKanban tags', () => {
108108
expect(HOST_TAGS).toEqual(['object-kanban']);
109109
});
110110

111+
it('the stamped literal equals the exported constant', () => {
112+
// objectui stamps `code: INERT_QUICK_ADD`; this copy stamps the inline
113+
// literal, because `check:dispatcher-error-vocabulary` cannot reduce a
114+
// kebab-case constant at a `code:` position (the divergence is documented
115+
// at the stamp, and is the same one `unconsumed-widget-option` carries).
116+
// This row is what keeps the two spellings from drifting apart — and the
117+
// lockstep gate resolves the identifier through this constant, so both
118+
// copies still agree on the same code.
119+
expect(INERT_QUICK_ADD).toBe('inert-quick-add');
120+
const [diagnostic] = diagnose({ type: 'object-kanban', objectName: 'task', quickAdd: true });
121+
expect(diagnostic.code).toBe(INERT_QUICK_ADD);
122+
});
123+
111124
it.each(HOST_TAGS)('<%s> — an authored `quickAdd: true` draws exactly one warning', (tag) => {
112125
const diagnostics = diagnose({ type: tag, objectName: 'task', quickAdd: true });
113126
expect(diagnostics).toHaveLength(1);

‎packages/sdui-parser/src/kanban-quick-add.ts‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,23 @@ export function checkKanbanQuickAdd(tag: string, key: string, value: unknown): D
149149
if (!value) return null;
150150
return {
151151
severity: 'warning',
152-
code: INERT_QUICK_ADD,
152+
// DIVERGENCE FROM OBJECTUI, and the only one below this file's header:
153+
// objectui writes `code: INERT_QUICK_ADD` here. This repo runs
154+
// `check:dispatcher-error-vocabulary`, whose `objlitconst` shape reads the
155+
// SCREAMING_SNAKE constant NAME at a `code:` position and then must reduce
156+
// it to a literal — and its literal grammar is `[A-Za-z][A-Za-z0-9_]*`,
157+
// which a KEBAB-case value cannot satisfy. So the constant form is reported
158+
// as an unresolvable code constant, and that finding cannot be declared
159+
// away. `inert-quick-add` is a parser DIAGNOSTIC code, not an ADR-0112 wire
160+
// code, and an inline quoted literal is the form both vocabulary gates
161+
// already accept for the sibling diagnostic codes in `validate.ts` and for
162+
// `unconsumed-widget-option` next door. The emitted VALUE is unchanged, and
163+
// the test next door pins it equal to `INERT_QUICK_ADD` so the two
164+
// spellings cannot drift apart. ⛔ This does NOT weaken the lockstep: that
165+
// gate resolves an identifier at a code position through the package's own
166+
// module-level constants, so both spellings reduce to the same code and the
167+
// two copies still agree on all 26.
168+
code: 'inert-quick-add',
153169
message:
154170
`<${tag}> prop "${QUICK_ADD_KEY}" reaches no control — the Quick Add button is gated on ` +
155171
`BOTH "${QUICK_ADD_KEY}" and an "onQuickAdd" handler, and this block supplies neither half ` +

0 commit comments

Comments
 (0)