Skip to content
Transtyle v0.1.0-alpha.5
ok: build 10:17 UTC31 pages main · 7ed21c5

Alpha — breaking changes ship without a deprecation cycle. What is built vs. planned

Weird things & diagnostics

Transtyle prefers surprising you loudly over failing silently. This page lists every diagnostic code and every behavior that looks wrong until you know why it’s right.

How a diagnostic is printed#

Two lines, never one:

✖ TST1301 Target "bootstrp" is not configured in transtyle.config.json
  ↳ Did you mean "bootstrap"? Configured targets: bootstrap, primeng

The first line says what is wrong; the ↳ hint says what to change. They’re separate fields (message and hint) in report.json too, so editors and CI annotations can place them independently. The “What to do” column below gives the same advice. The hint exists so you get it at the moment of failure, without coming here.

Two behaviors to know:

  • Identical diagnostics are printed once. Derivation runs once per mode combination, so a single authoring mistake used to be reported once per combination. A two-token alias cycle printed twelve lines. Anything per-mode still says which mode in its message.
  • Consequences are suppressed in favor of causes. A dangling alias makes semantic.color.primary.solid unresolvable, but you’re shown the dangling alias (TST1105), not the missing token (TST1201), because fixing the symptom wouldn’t have helped. Likewise an alias cycle reports TST1104 alone, not a “dangling alias” for each token in the loop. And a role whose .solid failed to resolve (TST1105, TST1104, TST1106) draws no dark-mode carry-over note (TST1204), because there is no light-mode colour to carry over. A role bound to a slot derived too late draws TST1205 only when its alias resolves in the end; a dangling or looping one reports TST1105 or TST1104 alone.

Diagnostic code reference#

Code Severity Meaning What to do
TST1001 warning A token glob matched no files Check the path in tokens
TST1002 error A token file failed to parse Fix the JSON; the message includes the parser error
TST1003 error A Tokens Studio export can’t be read: the path doesn’t exist, $metadata.json (or $metadata) has no tokenSetOrder, a listed set has no file, $themes isn’t a list, or a theme uses a set the order doesn’t list Point tokensStudio at the synced folder or the single-file export; re-export from Tokens Studio
TST1004 error A Tokens Studio theme group isn’t mapped, or its mapping is wrong: a theme left out of map, a theme or group name the export doesn’t have, two themes on one mode value, no theme on the dimension’s default, two groups on one dimension Map every theme of the group to a mode value (map), or compile one theme (fixed); the hint names the group’s themes
TST1005 warning A Tokens Studio set file is in the legacy format (value/type without $); it is read as DTCG Nothing to fix; switch Tokens Studio’s token format to W3C DTCG to silence it
TST1006 error A Tokens Studio expression can’t be evaluated: a function or operator outside the subset, two units in one expression, a non-number operand, division by zero, or a reference only derivation fills Use the subset (+ - * /, parentheses, roundTo, min, max, floor, ceil, round, rgba(<color>, <alpha>)), one unit per expression, or author the computed value
TST1007 error A Tokens Studio token has a color modifier ($extensions["studio.tokens"].modify), which Transtyle doesn’t apply Remove the modifier and let derivation produce the state color, or author the resulting color
TST1008 error A Tokens Studio export can’t be lowered to a base layer plus mode layers: a token only a non-default theme defines, a type that differs between themes, two theme groups whose combination differs from their separate overrides, a path two sets place under different tiers, or a token that is a group in another set Define the token in a set the default theme uses; keep one type; move tokens two groups both change into one group’s sets, or fixed one group; place both sets under one tier
TST1009 warning A Tokens Studio value is read with a loss: a font weight named with Italic (the style is dropped), or a token a non-default theme leaves out (the default theme’s value carries over) Put the italic in a typography token; add the token to a set the theme uses if it should differ
TST1010 error transtyle.config.json doesn’t match its schema: unknown key, wrong type, missing tokens, or a bad check.failOn/contrast.standard/derivation.contrast value Fix the flagged path; a typo’d key is an error, never silently ignored, and near-miss keys get a “did you mean” suggestion
TST1011 error A target’s options are invalid for its exporter: unknown option, wrong type, or a value outside the allowed set (for example an unknown shadcn era) Check the exporter’s page for its options; an exporter that takes none rejects any
TST1012 info A check.suppress entry silenced nothing: no diagnostic matched it, or it matched only an error, which cannot be suppressed Remove or fix the entry. When you build a single target, an entry for another target’s diagnostic reads the same way
TST1013 error check.contrast.standard (or derivation.contrast) is apca, but the apca-w3 package can’t be loaded from the project or the Transtyle install npm install --save-dev apca-w3 in the project (it is an optional peer dependency, see WCAG 2.1 or APCA), or go back to a WCAG standard
TST1014 error A declarative mapping (a target’s exporter pointing at a .json file, or a package whose manifest names one) can’t be read, isn’t JSON, or is malformed: a variable that doesn’t fit its template (--x, $x, @x), a variable written twice in one file, blocks outside a CSS file, a typography slot without a part, a mode dimension the design system doesn’t declare. One per problem, with the row’s path (files[0].rows[7].variable); nothing is written for that target
TST1015 error A declarative mapping row names a slot that is neither in the catalog nor in your design system (usually a typo); the hint gives the nearest slot
TST1103 warning Token defined more than once across base layers (last wins). Not raised for a layer marked "override" Mark the layer that redefines on purpose "override": true in tokens, or remove the duplicate
TST1104 error Alias cycle (the loop only, in traversal order; a token that aliases into the loop is not listed) Break the cycle
TST1105 error Dangling alias: {path} points at nothing (inside a composite, the message names the member’s path) Fix the path; check tier prefixes (option. vs semantic.)
TST1106 error Unparseable value (for example unsupported color syntax, a DTCG dimension object with no unit), or a malformed composite member, named by its own path (semantic.color.elevation.1.shadow.color) Colors: a CSS color or a DTCG color object (fourteen colorSpaces, three components, alpha 0–1, six-digit hex). DTCG objects need a number value and a px/rem (ms/s) unit; the hint lists every accepted form
TST1107 warning Mode-scoped file provides a value for a token with no default value Add the token to a base layer first
TST1108 warning A mode value was overridden by a later layer Confirm the layer order is intentional
TST1109 error Mode-scoped layer targets a mode not declared in modes Declare it, or fix the typo
TST1110 error Mode-scoped layer names no dimension ("mode": {}). A layer naming several dimensions is a combo layer, not an error Name the dimension and value the layer is for, or every dimension of the combination
TST1111 warning $extensions.transtyle.role.archetype isn’t brand/status/neutral The role still joins the grid regardless. Fix the value, or ignore it if intentional
TST1112 error color-scheme is declared (with more than one value) but isn’t the first mode dimension List it first. Only the first dimension carries light/dark polarity, so a later color-scheme would ship a dark block filled with light values, so the build stops rather than emit it
TST1113 error A semantic.* token aliases a component.* token: the alias points up the tiers, the wrong way (only the direct edge is reported: a chain names the token that points into the component tier) Alias the component token’s own source instead, or move the token under component.. Tiers layer option → semantic → component; the semantic tier is the stable surface the component tier reads from
TST1114 info option.* tokens that no alias resolves to (reported once per build, with the count and the first paths; the full list is in check --json) Alias them from a semantic or component token, or delete them. Severity is check.hygiene.unusedOption (info default, warning, off)
TST1115 info Several option.* tokens of the same type resolve to the same value (colors compared in OKLCH within a tiny tolerance); one diagnostic per group Keep one and alias the others to it, or give each its own value. Severity is check.hygiene.duplicateOption (info default, warning, off)
TST1116 warning A layer marked "override": true defines a token no earlier layer defines (usually a typo in the path), or is the first token layer and so overrides nothing Fix the path, or mark the layer "override": "extend" if it may add tokens
TST1117 error A bindings rule is malformed: an unknown placeholder (only {role}, {rung}, {level} exist), a from that is not one alias, a from placeholder the slot lacks, or roles without {role} or naming an unknown role Fix the rule; the hint names the placeholders and the known roles
TST1118 error A bindings rule marked required has slots whose target token does not exist (the message lists the first three) Author the missing tokens, narrow the rule with roles, or drop required
TST1119 info Two bindings rules bind the same slot; the earlier rule in the array wins Delete the later rule’s overlap, or reorder the rules if the later one should win
TST1120 info An authored colour that a semantic or component slot reaches is outside sRGB. Reported once per source token, naming the slots and the hex fallback Nothing breaks: targets that write oklch() keep your colour, targets that write hex or HSL (Bootstrap, Storybook, ECharts, shadcn tailwind-v3) ship the clamped hex the hint names. Author an in-gamut value to pick the fallback yourself
TST1121 warning A partially authored scale (space, type.size, size.control, border-width, breakpoint, duration) ships out of order: a catalog default is out of order with an authored neighbour, or every authored token is outside the catalog’s rung names Author the whole scale, or rescale the authored rungs to fit between the defaults. For a renamed scale, author the catalog’s names or alias them to yours. One tuned rung that stays in order is silent
TST1122 warning A semantic.* or component.* slot takes its value from a token marked $deprecated: the slot itself, a hop of its alias chain, or a composite member’s alias. Once per slot and deprecated token, in any mode. A deprecated option.* token nothing reaches stays silent Point the slot at the replacement (the hint is the $deprecated reason, when it gives one), or remove the binding. Every target’s usage.md lists the variables still fed by it; check.suppress with the slot’s path keeps a planned deprecation from failing failOn: "warning"
TST1123 warning A DTCG srgb color object’s hex differs from its components by more than 0.01 per channel: the components are compiled, and the message names both Fix whichever is wrong in the token file, or drop hex. Within 0.01 per channel (a two-decimal export) the hex wins silently, as the color the design tool showed
TST1124 info A false-friend binding: secondary.solid (or accent.solid) is bound to a token named secondary (accent) whose colour is within ΔEOK 0.05 of neutral.tint (for accent, primary.tint or neutral.tint): shadcn’s meaning of the word, a subtle gray surface or a hover wash, not a brand colour. Once per word Bind neutral.tint (accent.tint) to that token instead and leave the role derived, or bind the role to your real second brand (emphasis) colour. See false friends
TST1125 warning A token has values on two non-default dimensions (say color-scheme: dark and contrast: more) and no combo layer for that combination, so the later-declared dimension’s value applies there; the message names the combinations Add a mode-scoped layer naming both dimensions with the combination’s value ("mode": { "color-scheme": "dark", "contrast": "more" }). If the later dimension’s value is meant for every scheme, nothing is wrong
TST1126 error A token sits at a catalog path that was renamed in place before the first stable release: today semantic.color.border, now the border ladder’s semantic.color.border.base. The old path would otherwise compile as a custom token nothing reads Move the token to the new path (turn the leaf into a group: "border": { "base": { "$value": … } }) and update every {semantic.color.border} reference
TST1201 error semantic.color.primary.solid missing. It’s the one token derivation can’t invent Author your brand color at that exact path; a bare semantic.color.primary is a different slot. Silent when an upstream error already explains the absence
TST1202 error A token in derivation.require was derived, defaulted or absent, not authored (an alias counts as authored). A completeness:<level> entry reports each item of that level left, dark neutrals included Author it, or remove it from require. transtyle check --completeness <level> lists a level’s items in order
TST1203 warning A role archetype has no authored .solid in a given mode Author <name>.solid for that mode, or drop the archetype extension
TST1204 info An authored or bound role’s .solid resolves to the same colour in a non-default color-scheme value as in the default one, so the whole derived grid carries over unchanged Author <role>.solid for that scheme value if it should differ. Default behavior, not a mistake. With derivation.autoDark: true, the carry-over is at least classified derived in coverage, but it still doesn’t compute a distinct color
TST1205 warning A role’s .solid (or text.base) aliases a slot derived after it is read: the alias resolves, but the role’s grid (or the content ladder under text.base) is not derived Alias an authored token or another role’s .solid or grid cell instead, or author the slot as a colour. A role bound to ring, link.*, palette.categorical.* or the content ladder hits this
TST1206 info text.base has no value of its own for a color-scheme value of the other polarity (dark under a light default, or the reverse) and elevation.0.surface is not authored, so that mode’s page and text are the default mode’s pair swapped (rule swap-neutrals). Once per scheme value; the hint gives the ratio the carried-over text would have had Nothing is broken. Author elevation.0.surface and text.base for that scheme value to choose them yourself
TST1301 error Requested target instance isn’t in the config The hint distinguishes a typo (suggests the near name) from a real exporter you haven’t configured yet
TST1302 error A token declares $type but has neither $value nor child tokens Add the missing $value, or remove the leftover node
TST1304 info An $extensions namespace outside transtyle.* Nothing to fix. It’s carried through untouched, informational only
TST1305 warning A top-level group isn’t option/semantic/component Move the tokens under the right tier, or fix the typo
TST1306 warning A token’s $value has an unrecognized $type Use a DTCG type the IR understands, or accept it’s carried opaque
TST1307 error A token file looks like Style Dictionary v3 (value/type without $), so it has no DTCG tokens Convert it to DTCG ($value/$type/$description, references without .value); transtyle migrate --from style-dictionary does this for you
TST1308 error A target’s modes subset names a dimension or value the project doesn’t declare, or leaves out a dimension’s default Use declared names, and keep the default value (or change modes.<dimension>.default). See targets.<t>.modes
TST1309 error An exporter’s transtyle manifest declares an irSpec or a pluginApi this core doesn’t provide (one line per field, with the package, its version, what it declares and what core has). Every target is still checked, and nothing is written Install a release of the exporter built for this core (irSpec: "v0-draft", a pluginApi range accepting 0.0.0 such as "0"), or a core release that matches the exporter. See Write an exporter
TST1310 warning An exporter package has no transtyle manifest in its package.json (or it lacks irSpec or pluginApi), so its compatibility couldn’t be checked Add the manifest to the exporter’s package.json; the exporter still loads
TST1311 warning A token’s or group’s $description isn’t a string, or its $deprecated isn’t true, false or a string. The field is ignored; the token still compiles Write the description as a string; use true or a reason string for $deprecated (false opts a token out of its group’s deprecation). See Describing and deprecating tokens
TST1312 warning A generated file was changed or removed since the last build: its content no longer matches the sha256 in its output directory’s transtyle-manifest.json, or it is gone. Also fires once for a manifest that is not valid JSON or doesn’t match its schema. Only build and check look; a build warns, then overwrites Rebuild to get the generated file back, or move the change into the tokens or the config so it survives a build. If a formatter or linter rewrote it, exclude the output directory from that tool. See A generated file I didn’t touch is reported as changed
TST1313 error A target’s version is outside every range its exporter declares ("version": "4.6.2" on Bootstrap, >=5.3 <6), or the exporter declares no version ranges at all. The message lists the ranges; nothing is written for that target
TST1314 error A token has both a $value and child tokens. DTCG 2025.10 §6.1 makes that invalid: a token can’t also be a group, and the children were ignored Move the $value into a child (often base) so the node is a group, or move the children elsewhere
TST1315 warning The generated files still match their transtyle-manifest.json (nobody edited them), but a fresh compile writes something else: the tokens, the config or transtyle changed since the last build. One warning per target, naming the first files. Only check raises it; a build rewrites the files at once Run transtyle build to bring the output up to date; commit it if the output is committed, or have CI fail on check.failOn: "warning" to catch a forgotten rebuild
TST2101 warning Contrast below the configured standard: a WCAG ratio (4.4:1), or a signed APCA Lc (Lc -43) under apca. Checked on text.base/text.muted against the page surfaces, on every role’s on-solid and on-tint, authored or derived, and on the focus ring against the page surfaces (a non-text pair: 3:1 under WCAG, Lc 30 under APCA); measured in every combination of modes, and one level up in a contrast: more one (7:1, or APCA Lc 90 / Lc 75 for text, 4.5:1 or Lc 45 for the ring) Adjust the color, or accept the warning knowingly. When a color carried over from the default mode unchanged, the hint says so. That’s usually the real cause of a dark-mode warning. On a derived on-color, no candidate reached the level: author it or move its fill’s lightness. Under contrast: more, author the more value the hint names: authored values are never re-derived
TST2102 warning Two colors of palette.categorical.* are closer than ΔEOK 0.05 in a mode (clusters named once, closest distance printed) Author one of the entries as semantic.palette.categorical.N. Today’s derived palette never triggers it (its closest pair is 0.082); an authored palette can
TST2103 warning Two or more of success/warning/danger/info (and roles archetyped status) have .solid colors closer than ΔEOK 0.05 in a mode, or identical (one warning per cluster) Fix the source colors: when they are authored or bound that way Transtyle carries them through as written. Two roles bound to the very same token are intentional and skipped
TST2104 info shadcn: radius.md is above 0 but at most 4px, so shadcn’s own offsets bring --radius-sm (and --radius-md at 2px or less) to 0 or below; browsers clamp them to 0. Once per shadcn instance, named in the message Author radius.md above 0.25rem (4px) to keep every rung rounded, or 0 for a square design. radius.md: 0 never raises it
TST2105 warning shadcn: the target sets both options.era and a Tailwind version whose profile is the other era; the explicit era wins
TST3001 error An exporter threw while emitting. The message names the target instance and the exporter’s own error; the other targets still run, so every crash is reported in one go, but nothing is written to disk until all of them succeed This is an exporter bug, not a design-system mistake. Re-run with --verbose (or TRANSTYLE_DEBUG=1) to print the stack, then report it or fix your plugin. Exits 1, like any error
TST3002 error An exporter could not be loaded (package not installed, or the exporter field is wrong). The message names the target instance; the other targets still run, and nothing is written Install the package in this project (npm install <package>), or fix the target’s exporter field

TST1303 isn’t a separate code. An alias to a non-existent path is TST1105 above, part of the same “authoring mistake” family the DTCG validation pass (T10) documents together.

Aliasing a derived slot is fine and won’t raise TST1105: component.button.radius: "{semantic.radius.full}" works even though nothing authors radius.full, because such aliases resolve right after derivation. TST1105 means the target never exists, at any stage.

Surprising-but-correct behaviors#

A three-token design system is valid, and a target variable just goes missing#

Nothing obliges you to author a radius scale, a font stack, or a border color. If you don’t, the slots that derive from them don’t exist, and an exporter that has a binding for them emits nothing rather than a broken declaration. You’ll see the variable in report.json classed dropped, with a note naming the slot to author if you want it driven:

$btn-border-radius  dropped  nothing to bind: this design system has no
                             component.button.radius. The binding exists, its
                             source does not — author that slot (or the scale it
                             derives from) and this variable starts being driven.

The target’s own default then applies, which is what “we have nothing to say about this” should mean. A Bootstrap theme map is the one place an entry can’t go missing, because the exporter replaces Bootstrap’s whole map. The entry keeps Bootstrap’s own variable ("dark": $dark-text-emphasis) and is reported as $theme-colors-text.dark, dropped. check:minimal-ds compiles three design systems against every exporter on each CI run: a one-token one (only primary.solid, whose page and text the engine defaults), a three-token one (primary.solid, the page background elevation.0.surface and text.base), and one whose text.base is bound to a role cell (so neutral.text-strong and the content ladder stay empty). It asserts that none of them crashes and none writes a JavaScript value into a stylesheet.

My dark-native system comes out light-first#

modes.default declares your native mode; it doesn’t reorder exporter output. Exporters bind mode names. shadcn’s structure is always :root = light, .dark = dark, because that’s what shadcn consumers expect. Your native look is intact, just addressed by name. (Found by the Cathode example, now an IR rule.)

My brand color is identical in dark mode#

You didn’t author a dark value, so it falls back to the light one. That’s by design, and TST1204 (info) surfaces it in the report, not only here. The test is the resolved colour, not the token text: if you bound the role to your own vocabulary and that token carries a dark value, nothing carried over and no note is raised. Transtyle doesn’t invent brand decisions. Author primary.solid a dark-mode value (one line), or set derivation.autoDark: true so the carry-over is at least classified derived in coverage instead of authored. Either way the color itself stays the light one. autoDark doesn’t yet compute a distinct dark color, and that transform is still an open question (roadmap).

My dark page is my light text color#

You authored text.base with no dark value and left the page (elevation.0.surface) to the default, so dark mode got your light pair swapped: the page is your text color, the text is the light page. Before, your dark text carried over onto the default dark page at about 1:1. A page and its text, unlike a brand color, are not a brand decision, and the swap keeps your light mode’s exact contrast ratio. TST1206 (info) says it happened. transtyle explain semantic.color.text.base --mode dark shows the rule and the two light-mode values it read. Author both for color-scheme: dark to choose them yourself. An authored page is never swapped, even with no dark value: then the light pair carries over as written.

--secondary doesn’t look like my secondary color#

shadcn’s --secondary is a subtle surface (think of a secondary button background), not a brand-secondary. The exporter maps it from neutral.tint on purpose. Your brand secondary role exists in the IR and will map to targets that actually mean “second brand color”. The mistake in the other direction, porting a shadcn theme and binding its near-white --secondary gray to secondary.solid, is what TST1124 notes.

A variable is classified approximated#

Three current causes: --input (shadcn distinguishes input borders; the catalog doesn’t yet); gamut clamping where a target writes hex or HSL (shadcn’s tailwind-v3 era, Radix’s step scales, ECharts; the note says so); unit conversions. Approximation is information, not an error. The report exists so you can decide if you care.

Bootstrap and Storybook also write hex, but their reports do not class a clamped colour approximated yet. When the colour you authored is outside sRGB, TST1120 says so whatever the target, with the hex every hex-writing target ships.

Negative radius in the output#

shadcn derives its smaller rungs with fixed offsets: --radius-sm: calc(var(--radius) - 4px) and --radius-md: calc(var(--radius) - 2px). With radius.md: 0rem both are negative, and browsers clamp a negative calc() radius to 0, which is exactly what a square design asked for: nothing is reported. With a small non-zero radius (0.125rem, 2px), the same clamp turns rounded-sm and rounded-md square while you asked for rounded corners. That case is TST2104 (info), once per shadcn instance; author radius.md above 4px to keep every rung rounded.

Contrast warning on a color I like#

TST2101 prints the measured contrast: a WCAG ratio, or under check.contrast.standard: "apca" an APCA Lc whose sign gives the polarity (negative for light text on a dark background). Both are rounded toward zero, so a pair just under the level never prints as the level itself. It’s a warning, not a veto: set check.failOn to decide whether your CI cares. The number is real, though, and near misses (4.4:1) are the ones eyes don’t catch.

Two colors that can’t be told apart#

TST2102 and TST2103 measure the distance in OKLab (ΔEOK, the Euclidean distance CSS Color 4 uses for gamut mapping, where 0.02 is the just-noticeable difference). They warn under 0.05, and they report a cluster once per mode: four status roles that all resolve to the same color give one warning naming the four, not six pairs. Two slots bound to one token (warning and a custom crt-amber role both bound to {semantic.color.crt.amber}) are a stated intent, so that pair is skipped. Equal colors in different tokens are the accident the check exists for. Both are warnings, so check.failOn decides whether CI cares. The derived palette and derived status roles never trip them (closest pairs: 0.082 and 0.12); they guard authored values.

Part of my scale went backwards#

The catalog fills every rung you don’t author with its default (space steps by 0.25rem, type.size is a 1.25 modular scale), and authored tokens always win. Tuning one rung is fine. Authoring an 8px space.1 to space.4 (0.5, 1, 1.5, 2rem) is not: space.4 is 2rem while the default space.5 is 1.25rem, so the shipped scale goes backwards. Authoring a scale under your own names (space.sm/md/lg) replaces nothing, because targets read the catalog’s rung names. Both are TST1121 (warning), once per scale. Units are compared in px (rem at units.remBase, 16px by default) or ms; em and calc() values are skipped.

Suppressing a diagnostic#

A warning or info you have looked at and accept can be silenced in transtyle.config.json, with the reason written next to it:

"check": {
  "suppress": [
    { "code": "TST1305", "path": "scratch", "reason": "scratch is a throwaway group for a spike" }
  ]
}

reason is required (an empty or blank one fails config load as TST1010). path is optional: an exact token path, or a prefix ending in .* (component.button.* matches the group’s children, not the group). Without path, the entry matches every diagnostic with that code, the only way to silence one that isn’t about a token, such as an exporter’s own TST2104. A suppressed diagnostic is silenced, not downgraded. It’s no longer printed and no longer counts for check.failOn. It stays auditable, listed with its reason under suppressed in report.json and check --json. Errors can’t be suppressed. An entry that matches nothing raises TST1012 (an info).

Where a diagnostic points#

A diagnostic about something you authored names the file, line and column of its key (✖ TST1105 tokens/brand.tokens.json:17:9 …), and report.json carries path, file, line and column. A diagnostic about a value Transtyle derived (a contrast ratio of a computed on-solid, a missing primary.solid) has no source line, because the value is in no file: it prints without one. Config-file errors name the file but not the line yet.

A generated file I didn’t touch is reported as changed#

TST1312 compares each generated file with the hash its last build recorded in transtyle-manifest.json. Something other than you can change a file. A formatter or a linter run over the whole repository (Prettier, Stylelint --fix) can, and so can an editor that trims trailing whitespace on save, or a merge that resolved a conflict inside dist/. Exclude the output directories from those tools (.prettierignore, the linter’s ignore file), then rebuild. Line endings are not a cause: hashes are taken with CRLF turned into LF, so a Windows checkout with core.autocrlf is not reported.

Two builds, byte-identical output#

Not a bug, a promise. No timestamps, no randomness, sorted file loading, fixed number formatting. If two builds of the same inputs ever differ, that is a bug, so please report it.