← All news

Escaping Fixes and Clearer Errors

v0.8.0-rc.3 closes an escaping gap left over from rc.2's rewrite of attribute and text-content code generation, and spends the rest of the cycle on diagnostics: a new error type with multi-line suggestions, and i18n setup failures that now name the exact file or value at fault instead of just panicking.

Fix: formatted text and literal attributes weren't escapedFix

A formatted text expression, {"...{}", args}, was building its output with format! but never routing the result through sanitize!, so interpolated values were written into the page unescaped. Constant literal attribute values (class="...") had the same gap. Both now go through the same escaping path as every other text and attribute form:

1// Before: the format! result was never passed through sanitize!
2let name = "<script>evil()</script>";
3view! { <p>{"Hi {}", name}</p> }
4// rendered: <p>Hi <script>evil()</script></p>
1// After: the whole format! result is escaped, same as a plain {expr}
2view! { <p>{"Hi {}", name}</p> }
3// rendered: <p>Hi &lt;script&gt;evil()&lt;/script&gt;</p>

If you were relying on the old, unescaped output anywhere, switch to @html{...} at that call site to keep it explicit. Everywhere else this is a silent correctness fix, no code changes needed.

New: @class shorthand

@ prefixed on class (and only class) is sugar for scoped_css!. Because the generated class name is a compile-time literal instead of an opaque expression, it merges into the surrounding static markup instead of forcing a dynamic push:

1view! {
2 // -> class="tidos-<uuid>"
3 <div @class="./card.css"></div>
4
5 // -> class="tidos-<uuid> extra"
6 <div @class={"./card.css", "extra"}></div>
7}

Anything after the path is parsed with the same literal / format-string / expression grammar as a plain class={...} value, and appended space-separated.

Clearer compiler errorsQuality of life

Parsing errors used to bottom out in a handful of internal .unwrap() calls, or a single format!'d string when a diagnostic needed to suggest more than one fix. Both are gone: parsing no longer panics on malformed input, and a new TidosError type carries a list of suggestions that rustc renders as separate help lines:

1thread 'main' panicked at 'Parsing failed.'
1error: Unable to have a toggle attribute from a literal, change it into the following
2 --> src/main.rs:12:9
3 |
412 | :disabled="true"
5 | ^^^^^^^^^^^^^^^^
6 |
7 = help: :disabled
8 = help: :disabled={ boolean expression }
9 = help: disabled="true"
10 = help: disabled={ "true" }

i18n errors now name the fileFix

A broken Tidos.toml, an invalid default_locale, or a locale directory that isn't a valid language tag used to surface as a bare 'Parsing failed' or 'Unable to open resources' panic at startup. TidosI18nError now carries the resolved path or value that was actually the problem:

1Tidos i18n: could not read resource directory '/srv/app/locales': No such file or directory (os error 2)
2hint: `resource_location` in Tidos.toml is resolved relative to the process's current working directory.

Under the hood

Attribute code generation now flows through the same flat-args accumulator as text content, concatenating adjacent static pieces before the combine! call instead of after. Deeply nested view! trees no longer risk hitting rustc's macro recursion limit. The IsStatic trait, made redundant by that change, was removed.

Upgrading

Update your Cargo.toml to the third release candidate:

1[dependencies]
2tidos = "0.8.0-rc.3"

No API changes this cycle. Double-check any formatted text content or literal attribute values that contain &, <, >, " or ' characters, since those now render escaped where they previously didn't.