Skip to content

v1.19.0 — a real yaml mode, and two diagnostics that were compiled away - #27

Merged
aicodebar merged 1 commit into
mainfrom
release/v1.19.0
Aug 8, 2026
Merged

v1.19.0 — a real yaml mode, and two diagnostics that were compiled away#27
aicodebar merged 1 commit into
mainfrom
release/v1.19.0

Conversation

@aicodebar

Copy link
Copy Markdown
Collaborator

Four findings from flows.codebar adopting v1.18.0 (which took its own type backlog 169 → 82). Two fixed, one fixed differently than asked, one declined with evidence.

1. CodeEditor had no yaml mode — and silently painted YAML with the JSON grammar

A real rendering bug, not a typing nicety. The consumer's compiled-flow-definition page has been highlighting YAML as JSON since it was written.

@codemirror/lang-yaml added the same way as the other grammars; language="yaml" is now a real mode on CodeEditor and CodePreview — editing YAML but only previewing it as plain text isn't defensible. The unknown-language arm is now explicit: no grammar, plus a warnOnce dev warning through the existing helpers/dev.ts mechanism, rather than silently falling back to JSON.

Why the obvious test would not have caught it. Running both grammars over a typical flow definition at the lezer level: the JSON grammar still paints 9 tokens on YAML — every : as punctuation, 3 and 50 as numbers. The existing expectHighlighted helper ("some token is coloured") therefore passes on the broken version. What JSON cannot paint is #, a comment in YAML and nothing in JSON. The new story asserts on that line specifically, and was mutation-tested by forcing it onto language="json" to confirm it fails.

2. CodeEditorProps['modelValue'] widened to string | null

Not string | number | null. The runtime ?? '' guards prove null is accepted, which removes three consumer-side ?? ''. number was declined: Input is a native <input> whose value the DOM stringifies anyway, while CodeEditor holds a document — a number would be silently stringified in and returned as a string. Textarea, the closer sibling, is string | null, and that is the shape matched.

3. DataTableColumn.key narrowed to keyof T & string — declined

Three grounds, the third decisive and verified in a consumer fixture:

  • Narrowing forbids action and computed columns ({ key: 'actions' } + #cell-actions), and the kit offers no other way to place one outside the trailing #row-actions cell.
  • SortState.key is a free string on purpose — it goes to an API in server mode, where joined and computed sort keys are ordinary.
  • For the row shapes it was proposed for, it is the identity. DataTable is <T extends Record<string, unknown>>; an interface satisfies that constraint only by declaring an index signature (this package's own DataTable story does exactly that), and once [key: string]: unknown is present, keyof T & string is string and T[K] is unknown. value would still be unknown. It would only bite for rows declared as type aliases.

The real obstacle is the Record<string, unknown> constraint, not key. Relaxing it to T extends object is strictly more permissive and would break nobody, but it ripples into useSort's signature and the internal indexing — a separate release. Written up in the changelog.

4. rowKey dev warning — added, and the premise was stronger than reported

The consumer shipped nine <DataTable> call sites with no row-key, so every row was keyed undefined. The suspicion was that a Vue dev-mode warning should have caught this years earlier.

It cannot. Vue's "Missing required prop" warning cannot fire for any prop in this package: the library compiles with isProd: true, @vue/compiler-sfc emits required/type only for a dev build, and dist/flows.js declares the prop as the bare rowKey: {}. Confirmed by SSR-rendering the built bundle against a development Vue with no rowKey — Vue says nothing.

Same shape as the import.meta.env.DEV regression already documented in helpers/dev.ts, so the check is written out via warnOnce. The changelog notes that every other required prop is unvalidated for the same reason; a general fix means compiling the library in dev mode, which is its own release.

Breaking changes

  1. New peer dependency @codemirror/lang-yaml — the only thing this release asks of a consumer. Declared optional, but "optional" describes the manifest, not the bundler: dist/flows.js is one file that dynamic-imports every grammar by bare specifier, so a consuming build resolves all of them whether or not it renders an editor. An app that upgrades without installing it fails at build time, not runtime. Mitigating: this already applies to lang-json, lang-markdown, commands, language, state and view, so every existing consumer already installs that set — this adds one to it.
  2. CodeEditorProps['modelValue'] includes null. Reading it into a string needs a fallback; passing in is strictly freer. Same class as BreadcrumbItem.href in 1.18.0.
  3. An unknown language no longer highlights as JSON. Only reachable from untyped call sites; anything passing "yaml" now gets YAML.
  4. Not breaking but visible: an app with rowKey-less tables starts seeing a dev warning immediately.

Verification

A consumer fixture resolving through the package's exports map (--traceResolution confirms it reaches dist/index.d.ts), in three halves:

  • vue-tsc on a real SFClanguage="yaml", v-model on a Ref<string | null> with no ?? '', #cell-* slots; type-level assertions that 'toml' is not a mode and that the keyof T & string collapse holds. Negative-controlled: flipping one assertion errors.
  • Node runtime — components imported from the built bundle, yaml() resolves from the consumer, the bundle dynamic-imports @codemirror/lang-yaml with no inlined copy.
  • Dev warning — built bundle plus dev Vue warns for a missing rowKey, silent when supplied.

npm run build (all four verify steps), vue-tsc, ESLint (0 errors, the same 7 pre-existing warnings as main), Playwright 246 passed — 243 plus three new stories.

Version: 1.19.0

Minor rather than major: no existing API changes shape incompatibly, the widening and the new union member are additive, and the peer addition is an install step against a set consumers already carry rather than an API break. If you read a build-breaking peer addition as major, 2.0.0 is the alternative and the changelog gives the facts to decide.

Release mechanics: the manifest is already bumped in the commit (1.18.0's precedent, and verify:version on PRs requires it), so the tag step is npm version 1.19.0 --allow-same-version — still derived from the manifest, never hand-written. npm version minor at that point would land on 1.20.0.

🤖 Generated with Claude Code

… away

Four findings from the app that adopted 1.18.0. Three acted on, one declined.

CodeEditor loaded the JSON grammar for anything that was not `markdown`, so a
consuming app's compiled-flow-definition page highlighted YAML through a JSON
parser for the life of the page. It survived because the wrong grammar does not
fail loudly: JSON still paints nine tokens in a typical flow definition — every
`:` as punctuation, every bare integer as a number — which reads as syntax
highlighting from across the room. What it cannot paint is `#`, a comment in
YAML and nothing at all in JSON, so the Yaml story asserts on that line and
fails when the document is served the JSON grammar. A "some token is coloured"
assertion passes on the broken version, which is why there wasn't one.

`yaml` is now a mode on both code surfaces (@codemirror/lang-yaml, a new
optional peer — see the changelog's upgrade note, it is the one thing this
release asks of a consumer), and an unknown language renders unhighlighted and
warns instead of silently reaching for JSON. A component that quietly picks the
wrong grammar is worse than one that refuses the mode: the first is
indistinguishable from working.

DataTable.rowKey has always been required with no default, and nothing said so
at runtime — nine call sites shipped without it and keyed every row `undefined`.
Vue's own "Missing required prop" warning cannot fire for any prop in this
package: the library is compiled with isProd, @vue/compiler-sfc emits
`required`/`type` only for a development build, and dist declares the prop as
the bare `rowKey: {}`. Rendering DataTable with no rowKey against a development
Vue produces no Vue warning at all. Same shape as the import.meta.env.DEV
problem in helpers/dev.ts, so the check is written out by hand like the tone
deprecation, and verified against the built bundle rather than src.

CodeEditorProps.modelValue becomes `string | null`, which is what the runtime
has always accepted (`?? ''` guards every read). Not `string | number | null`
like Input: that is a native input whose value the DOM stringifies anyway,
while this holds a document. Textarea, the closer sibling, is `string | null`.

Declined: narrowing DataTableColumn.key to `keyof T & string`. It would forbid
action and computed columns, it disagrees with SortState.key which is a free
string on the wire, and for the row shapes it was proposed for it is the
identity — DataTable is `T extends Record<string, unknown>`, an interface only
satisfies that by declaring the index signature, and once `[key: string]:
unknown` is present `keyof T & string` IS `string` and `T[K]` IS `unknown`. The
obstacle is the constraint, not the key. Reasoning in full in the changelog.

Verified against a consumer fixture resolving the package through its exports
map: vue-tsc accepts `language="yaml"` and a nullable v-model with no `?? ''`,
rejects an unknown mode, and the built bundle warns for a missing rowKey under
a development Vue while a supplied one stays silent.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@aicodebar
aicodebar merged commit 87c4c75 into main Aug 8, 2026
2 checks passed
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.

1 participant