Read a Markless error in four lines
Each Markless error names a code, says why, suggests a fix, and links to a page. A markless-allow comment silences one warning.
Last page, a test clicked a button. Sometimes the build stops before any test runs, with a long code in capital letters.
Markless refuses some code when it builds. It does not hand you a stack trace. It answers four questions: what broke, why, how to fix it, and where to read more.
Your editor shows type errors and syntax errors. Markless’s own rules run when the dev server or the build reads a .tsrx file. An error stops the build. A warning lets the build go on.
Press Build. Then fix the file and build again.
What does Markless tell you when it stops a build?
Press Build. Read the four parts of the error. Then press Add the key and build again.
Nothing built yet.
import { state } from '@markless/core'; export default function List() @{ let items = state(['a', 'b']); <ul> @for (const item of items) { <li>{item}</li> } </ul>}- Nothing yet. Press Build.
Simplified. The code, the place, the fix, and the link come from a real build of this file. The “why” line is shortened. The full text is in the next section. The items are plain words, so each word is its own key.
Notice that the fix fits on one line. The error suggested it.
What the build prints
Here is a list with no key. The output below comes from a real compile of this file.
import { state } from '@markless/core';
export default function List() @{
let items = state(['a', 'b']);
<ul>
@for (const item of items) {
<li>{item}</li>
}
</ul>
}
MARKLESS_COMPILE_BLOCKED: List.tsrx has 1 compiler error(s).
MARKLESS_REPEAT_KEY_REQUIRED: @for (const item of items) repeats reactive state without a key. When items changes, the rows of this list have no identity to update, reorder, or resume by. (List.tsrx:7:3)
A keyed loop item keeps its state, events, and DOM attached to the same logical item across reorder, insert, and delete; without a key there is no stable identity root.
Add a stable domain key such as `@for (const item of items; key item.id)`, or key by position with `index i; key i` when state should follow the slot.
https://markless.dev/errors/MARKLESS_REPEAT_KEY_REQUIRED
Read each block top down: the code and the place, then why, then the first fix, then the link.
Silence a warning
If you accept a warning on purpose, write // markless-allow CODE: reason. Put it on the line above the warning, or on the same line.
<section>
// markless-allow MARKLESS_REPEAT_KEY_IS_INDEX: static list, order never changes
@for (const record of records; index slot; key slot) {
<p>{record.label}</p>
}
</section>
The comment has three rules. If you break one, you get a warning of its own.
| Rule | Code if you break it |
|---|---|
| Write a reason after the colon. | MARKLESS_ALLOW_REASON_REQUIRED |
| You can’t silence an error. Fix it. | MARKLESS_ALLOW_ERROR_UNSUPPRESSIBLE |
| If the warning no longer fires there, delete the comment. | MARKLESS_ALLOW_STALE |
Common codes
Every code starts with MARKLESS_. The table drops that start.
| Code | Kind | What to do |
|---|---|---|
FRAMEWORK_IMPORT_REQUIRED |
error | Add import { state } from '@markless/core'. |
STATE_MODULE_SCOPE |
error | Move state() into the component body. For values many components share, use shared(). |
STATE_CREATION_SITE_UNSTABLE |
error | Declare state() in the component body itself. Keep it out of branches, loops, handlers, and computed(). |
STATE_STALE_LOCAL_WRITE |
error | A handler writes a plain let. Wrap its value in state(). |
REPEAT_KEY_REQUIRED |
error | Add a key to the @for, like key item.id. |
REPEAT_KEY_IS_INDEX |
warning | Key by a stable field. Keep key i only if state belongs to the slot. |
ASYNC_BOUNDARY_REQUIRED |
error | Wrap the async read in @try with @pending and @catch. |
ASYNC_POST_AWAIT_READ |
error | Read the state before the first await. |
STATE_WRITE_IN_COMPUTED |
error | Move the write into an event handler. |
EVENT_HANDLER_EMIT_UNSUPPORTED |
error | The handler reads a plain local from the body. Use state, props, element handles, or imports instead. |
TRY_BLOCK_TOGGLE_RERENDER |
warning | An @if inside @try holds a component. Move the component out of the @if. |
SHARED_FAMILY_SCOPE_IMPLICIT |
warning | Pass { scope: 'widget' } or { scope: 'page' } to shared(). |
FRAMEWORK_API_RUNTIME_CALL |
error at run time | Call state() and its friends only in .tsrx files. |
The compiler has many more codes than this table shows.
You now have every tool an app author needs.
Next: Why does Markless insist on rules like a key for every list? Why Markless, technically →
