Skip to content

💥 feat(core): declare component props under "props" - #179

Merged
taras merged 2 commits into
mainfrom
feat/props-vocabulary
Jul 27, 2026
Merged

💥 feat(core): declare component props under "props"#179
taras merged 2 commits into
mainfrom
feat/props-vocabulary

Conversation

@taras

@taras taras commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Why

A component declared caller-supplied values under inputs, but callers passed them as props and component bodies read them through props. The same concept had two public names, so the declaration never matched invocation or consumption.

This also settles the vocabulary #176 builds on: meta is component-owned configuration, props are caller-supplied, content is rendered Markdown, and returns will declare a component's return value.

Closes #177.

What changes

Before:

inputs:
  name: { type: string }
Hello, {props.name}

After:

props:
  name: { type: string }
Hello, {props.name}

The same term now spans declaration, invocation, and consumption:

  • Markdown frontmatter inputs:props:
  • function component export const inputsexport const props
  • ComponentDefinition.inputs / FunctionComponentDefinition.inputs.props
  • inspectDocument().inputs.props
  • InputSchemaPropsSchema, InputSchemaErrorPropsSchemaError, compileInputSchemacompilePropsSchema

How it works

frontmatter props: → parsePropsSchema → compilePropsSchema → validateProps → {props.name}

packages/core/src/frontmatter.ts reserves props and required; everything else is meta. definition.ts and execute.ts compile the resulting schema for Markdown and function components respectively, and expand.ts validates caller props against it.

CLI internals take propsSchema rather than props, because resolveProps already binds props to the resolved values — reusing the name would shadow it.

Review guide

Start with: packages/core/src/frontmatter.ts

Then review:

  1. packages/core/src/validate.tscompilePropsSchema, PropsSchemaError, and the reworded diagnostics
  2. packages/core/src/{definition,execute,expand,inspect}.ts — the two compile sites and the two validation sites
  3. packages/cli/src/{props,cli}.tspropsSchema threading
  4. specs/executable-mdx-spec.md — §5.1 and the acceptance/invariant tables

Look carefully at:

  • The no-alias behavior is deliberately not uniform, and the tests encode both halves (see below).

What must stay true

  • No inputs compatibility alias. Enforced by PROPS_KEYS = ["props", "required"] in frontmatter.ts and by "props" in mod in execute.ts; checked by the alias tests in frontmatter.test.ts, validation-integration.test.ts, and function-components.test.ts.
  • The generated CLI surface is unchanged. --props-*, XMD_PROPS_*, --props, and XMD_PROPS keep their spelling; checked by packages/cli/tests/props-{cli,sources,schema}.test.ts.
  • Concise/full schema behavior from 💥 feat(core): concise input declarations #175 is unchanged — only the key it is declared under.

How to verify it

  • frontmatter.test.ts "treats a full inputs schema as ordinary metadata" proves a full old-form declaration degrades to meta.inputs plus the default closed empty-object schema, and fails if an alias were reintroduced.
  • frontmatter.test.ts "rejects a concise inputs map declared with a top-level required" proves required stays reserved, and fails if the concise old form were silently accepted.
  • validation-integration.test.ts "a component declaring inputs rejects the prop it used to accept" proves the end-to-end consequence: the document parses, then fails at prop validation.
  • function-components.test.ts FC-alias (two cases) proves export const inputs declares nothing while export const props validates normally — catching an undocumented alias on the function-component path.

Local results with the CI-pinned Deno 2.9.1:

deno task lint        # 0 errors, formatting clean
deno task check       # no errors
deno task test        # 142 passed (1119 steps), 0 failed
deno task check:jsr   # Success Dry run complete
deno task build && ./dist/xmd test smoke-test/README.md \
  --component-dir smoke-test --component-dir packages/core/components   # 55 assertions, 0 failures
./dist/xmd test smoke-test/test-agent/README.md --raw                   # exit 0
./dist/xmd test smoke-test/agent/README.md \
  --component-dir smoke-test/agent/components --raw                     # exit 0

Note: packages/cli/tests/agent-cli.test.ts (Tier CA) fails under Deno 2.9.4 on this branch and on main — it is unmodified here and references neither props nor inputs. It passes on the CI-pinned 2.9.1.

Scope

Included

  • The core component contract, CLI threading, every repository component and fixture, specifications, and the site copy.
  • specs/root-document-inputs-spec.mdspecs/root-document-props-spec.md and smoke-test/Guide/TypedInputs.mdTypedProps.md.

Intentionally unchanged

  • returns (Support schema-validated component returns #176) is not implemented — this PR only reserves the vocabulary for it.
  • Positional prop arguments (Let root documents declare ordered positional arguments #173) wait on this rename.
  • Generic uses of "input" are untouched: packages/acp/, packages/durable-streams/, interpolation inputs, the Standard Schema input parameter, JsonSchemaInput (the parameter type of z.fromJSONSchema), and GitHub Actions workflow_call inputs.
  • .github/workflows/review.yml and repo-analysis.yml still install the released binary — see Risks.

Generated or mechanical changes

  • The inputs:props: frontmatter change across .reviews/** (32 files), packages/core/components/** (5), and smoke-test/** (13) is mechanical with no intended behavior change.
  • specs/code-review-agent-spec.md and specs/oxlint-sensor-spec.md are frontmatter examples only.

Risks and limitations

  • The review check on this PR is expected to be red, and repo-analysis will be broken on main after merge. Both workflows curl | sh the latest published xmd and run it over .reviews/** and packages/core/components/**. The released 0.5.2 binary reads props: as ordinary meta, so every component gets a closed empty-object schema and rejects every prop. This is the accepted trade-off rather than adding a compatibility alias.
  • A release containing Use props consistently for component inputs #177 must follow the merge before either workflow is operational again. Both self-heal at that point with no further code change.
  • Recovery: if the gap is a problem, point those two workflows at a source build (deno task build) until the release ships.

Scope confirmation

  • Every changed file supports the purpose described above.
  • Unrelated cleanup and formatting changes are excluded.
  • Generated or mechanical changes are clearly identified.
  • The description matches the final diff and test results.

taras added 2 commits July 27, 2026 17:26
A component declared caller-supplied values under `inputs`, but callers
passed them as props and bodies read them through `props`. The same
concept had two public names.

Rename the declaration to match invocation and consumption:

- Markdown frontmatter `inputs:` → `props:`
- function component `export const inputs` → `export const props`
- `ComponentDefinition.inputs` / `FunctionComponentDefinition.inputs` → `.props`
- `inspectDocument().inputs` → `.props`
- `InputSchema` → `PropsSchema`, `InputSchemaError` → `PropsSchemaError`,
  `compileInputSchema` → `compilePropsSchema`
- CLI schema variables → `propsSchema`, keeping `props` for resolved values

This also settles the vocabulary #176 builds on: `meta` is component-owned
configuration, `props` are caller-supplied, `content` is rendered Markdown,
and `returns` will declare a component's return value. `returns` is not
implemented here.

`inputs` is not retained as a compatibility alias. A full `inputs:` schema
now falls through to the ordinary meta rules, leaving the component with the
default closed empty-object schema; a concise `inputs:` map with a top-level
`required:` fails parsing, because `required` stays reserved. Both paths are
covered by tests, as is the equivalent `export const inputs` removal.

The generated `--props-*` options, `XMD_PROPS_*`, aggregate props, and
programmatic `props` are unchanged, as is the concise/full schema behavior
from #175.

Closes #177
Test comments described the rename that produced the contract rather than
the contract itself. Replace the #177 narration with present-tense
statements of the surprising behavior, and rename the validation test to
name its outcome instead of what a component "used to accept".

Also correct `DocumentInfo.meta`, which excludes both reserved keys
(`props` and `required`), fix the "an props map" test title, and describe
the root-document sources as command-line options and environment
variables rather than "environment props".
@taras
taras merged commit 8c83cb6 into main Jul 27, 2026
8 of 9 checks passed
@taras
taras deleted the feat/props-vocabulary branch July 27, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Use props consistently for component inputs

1 participant