@feugene/granularity-forms-schema
Записи релизов — авторский текст библиотеки, и он английский. Портал его не переводит: журнал изменений — история чужого репозитория, а переписанная история перестаёт быть историей.
v0.4.02026-08-27
Changed
-
Peer floors on
@feugene/*raised to the current minor. Every peer this package declares on the ecosystem now starts at the version the monorepo actually ships:@feugene/fint-i18n→>=0.7.0 <1.0.0@feugene/granularity→>=0.36.0 <1.0.0@feugene/granularity-chrono→>=0.10.0 <1.0.0@feugene/unocss-preset-granular→>=0.13.0 <1.0.0@feugene/unplugin-granularity→>=0.7.0 <1.0.0
The floors had drifted far behind — some still admitted releases from a year of development ago — and a range that claims support it was never tested against is worse than a narrow one: the install succeeds and the breakage surfaces later, in the consumer’s app.
This is breaking for anyone below a floor. Installing against an older
@feugene/granularitynow produces a peer conflict instead of silence. The fix is to move the core up; nothing in this package’s own API changed.
v0.3.42026-08-26
Changed
-
Перевод читается композаблом ядра
useGranularityTranslations. Свой резолвер (src/internal/i18n.ts) удалён — он расходился с ядром по трём пунктам, и каждое расхождение проявлялось молча:- видел только инстанс
fint-i18nпоSymbol.for('FintI18n'), поэтому адаптер, отданный приложением поGRANULARITY_I18N_KEY, оставался невидимым — приложение наvue-i18nилиi18nextполучало английский fallback; - спрашивал «есть ли перевод» сравнением
t(key) === keyвместоte(), а это врёт на словаре, где значение совпадает с ключом; - не разэкранировал
{{/}}во fallback и не подставлял{name}, когда параметры не переданы.
Публичный API не менялся;
@feugene/granularityу пакета и так обязательный peer. - видел только инстанс
-
Peer range for
@feugene/fint-i18nwidened to>=0.6.0 <1.0.0. On0.xversions a caret does not admit the next minor, so^0.6.0excluded0.7.0— the release consumers had already moved to. The peer is optional, so nothing ever failed to install; the mismatch surfaced as a warning in every consumer’s install log.Nothing was removed in
0.7.0: it adds locale negotiation and changes how a regional tag falls back to its base language, and this package touches neither. Compatibility is verified rather than assumed — the dev dependency now points at^0.7.0, so the suite runs against the version the peer range claims to support.
v0.3.32026-08-22
Fixed
-
Parts of the form can now be imported granularly, and auto-import routes them here.
GrSchemaField,GrSchemaArrayField,GrSchemaUnionFieldandGrSchemaAdditionalFieldslive in theGrSchemaFormdirectory and had no subpath of their own, so a granular import failed. Worse, the resolver whitelist did not list them either — the core’s greedyGr*resolver picked them up and pointed at@feugene/granularity/components/GrSchemaField, a path no package publishes, so a consumer’s build broke as soon as one of them appeared in a template.Both lists are generated now:
exportsgets an alias per part (its own key, the form’s module), and the resolver whitelist gets the names. The build config keeps reading the components list alone — CSS assets are laid out per component directory, and a part has none.
v0.3.22026-08-22
Fixed
- A free-key value field had no accessible name. In a pair «key — value» the visible label is
the key input itself, so the value control was left with nothing:
GrFormFieldwithout alabelgives no name, and an input drawn next to it does not become one. axe reported it aslabel, critical. The value is now named by its own key («Value of {key}»), and the name follows a rename.
Added
GrSchemaFieldacceptsariaLabel— a name for the control where a visible label does not belong (a table cell, a free-key row). Weaker thanui.controlProps, so a consumer can still name the field their own way.
Fixed
uiSchemacould not disable a field inside a repeater row.GrSchemaFieldresolvesprops.disabled ?? uiSchema ?? form, and every caller passed a literalfalsedown — which short-circuits both lower tiers. A field markeddisabled(orreadonly) foritems.*.namestayed editable, with nothing to indicate why.falsenow means “no opinion” and is not forwarded; onlytruetravels down.
Changed
-
The node-kind switch lives in one place. Array-of-objects, union, nested object and leaf field were dispatched by four separate copies of the same
v-ifchain — the form root, both branches ofSchemaObjectNodeand the repeater row. That is how the union branch shipped missing from two of them in0.3.0. The chain now lives inSchemaNodeSwitch.vue, and thestructuralKindsgate fails both when a caller stops delegating to it and when a caller grows a copy of its own.Internal only — no public component, prop or slot changed.
v0.3.02026-08-20
Added
-
Branching schemas now build a form. A discriminated union — delivery method, payment type, document kind — used to be a promise the package did not keep: the model had
kind: 'union'and the zod adapter even built it, but without an initial value nothing rendered at all, and with one the discriminator came out as a free text field, so the only way to pick a branch was to guess and typepickup. The form now renders a branch switcher (up to five variants as radios, more as a select) and the fields of the selected variant beneath it.Switching rewrites the value: keys the new variant also has are kept, foreign ones are dropped, the discriminator is set. Keeping foreign keys is not an option — the schema rejects them — and resetting everything would lose shared fields such as a comment that every variant carries.
The discriminator itself is not drawn as a field: the switcher owns it, and a second field under the same name would fight it for the value.
-
JSON Schema learned to branch.
oneOf/anyOfover object variants becomes a union, with the discriminator found two ways:discriminator.propertyName(the OpenAPI extension, whose type was declared and never read) or inference — the key that carries aconstin every variant, which is how plain JSON Schema writes it. Properties sitting next tooneOfbelong to every branch and are merged into each variant, so a shared field need not be repeated. Neither path resolves — the node stays residual and now says so throughmodel.warnings, which the docs had been promising all along. -
oneOfof bareconsts is an enum, not a branch. It used to be marked residual and rendered as a free text input; it now becomes a choice with per-branchtitleas the label. -
Free-form keys are editable.
additionalPropertieswith a value schema (andcatchall/looseObjectin zod) keeps that schema in the newadditionalValuenode, and the object renders a list of key–value pairs with add, rename and remove. The value is an ordinary control built from the stored node, so its constraints apply as they would to a declared field. Theadditionalflag itself was written by four places and read by none.additionalProperties: truerenders nothing: keys are allowed, but the schema never said what the value looks like, and inventing a text field would silently lose the type.
Fixed
-
A resolved union no longer runs the whole schema for nothing. The zod adapter set
residual: trueon every union unconditionally, which made a parsed branch indistinguishable from an unparsed one and dragged the full schema check along with it. It is now set only when the branch could not be resolved — and that case also emits a warning instead of staying silent. -
A union in a repeater row rendered as a text field. The kind switch is copied into every template that iterates fields, and there are four such copies; the new branch was missing from the one inside array rows. A gate (
structuralKinds) now fails when any of them falls behind. -
Dead branch removed in
validation/compile.ts: the condition was a strict subset of the line above it and could never be reached.
Changed
GrSchemaObjectNodegainedadditionalValue;GrSchemaFormContextgaineddeleteValueAt, which removes a key outright —setValueAt(name, undefined)would leave it in the payload and keep the name occupied.
v0.2.02026-08-20
Added
-
Cross-field schema rules now reach the fields they name.
z.object({…}).refine(…)— password confirmation, “end date after start”, “fill at least one of these two” — used to do nothing at all: the flag it sets lands on the container, and containers carry no rules, so the compiled validation never saw it. There was no error and no warning; the form simply submitted. The form now runsmodel.validate(value)on submit and routes the issues by path through the same channel it already uses for a server response — a path that matches a field lands on that field, one that does not goes to the form summary.submitis withheld andinvalidis emitted instead, so the “either submit or invalid” contract survives the new outcome.A rule on a field (
z.string().refine(…)) was never affected: it marks that node, and the compiler has always turned it into an ordinary field rule.The check runs only when there is something to check — the schema can validate itself and carries a rule no node can express. A form without cross-field rules pays nothing, including asynchrony: its
submitfires exactly as before, and a synchronous validator (zod) does not push the emit onto a microtask either. Turn it off through the existingvalidation.tiersby dropping'residual'. JSON Schema has no built-in full check — the package ships no validator — so pass a compiled Ajv throughparseOptions.validateto get the same behaviour.
v0.1.32026-08-20
Fixed
z.email()and friends kept their format again. zod 4 moved string formats onto the schema itself (z.email()setsdef.formatand registers no check), while the deprecatedz.string().email()still expresses them as a check. The adapter read checks only, so the recommended modern idiom silently lost the format: an email field parsed as a plain string and rendered as a plain text input, with no warning anywhere. Both spellings now yield the same node, underoptional()too.
v0.1.22026-08-19
Fixed
GrConfigProvidernow actually configuresGrSchemaForm. All four declared keys —columns,labelPosition,labelWidth,headingLevel— were registered as configurable and never read: the package contained no call touseGrComponentPropat all.headingLevelwould not have worked even then, because it carried a default of3inwithDefaults, which Vue substitutes before the component can consult the provider. Resolution order is now the usual one: prop →uiSchema→ provider → built-in default.
Более ранние версии
Тела этих релизов не показываются: лента целиком весит больше, чем страница имеет право весить. Файл в репозитории всегда полон.
v0.1.12026-08-19v0.1.02026-08-18