Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ dist
.agents

**/_compiled-docs
**/_compiled-learn
public/search-index.json
public/og
tsconfig.tsbuildinfo
Expand Down
158 changes: 153 additions & 5 deletions .marko-run/routes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,12 @@ declare module "@marko/run" {
"/docs/tutorial/fundamentals": [M1, L1, L2, P53, D51];
"/docs/reference-full.md": [M1, H2];
"/docs/newsletter/feed.xml": [M1, H3];
"/playground": [L1, P54, D52];
"/learn": [H4];
"/learn/basics/components": [L1, L3, P54, D52];
"/learn/basics/dynamic-content": [L1, L3, P55, D53];
"/learn/basics/state": [L1, L3, P56, D54];
"/learn/basics/templates": [L1, L3, P57, D55];
"/playground": [L1, P58, D56];
}> {}
}

Expand Down Expand Up @@ -161,6 +166,29 @@ declare module "../src/routes/docs/newsletter/feed%2exml+handler" {
}
}

type H4 = $.Handler<"H4", typeof import("../src/routes/learn/+handler")>;
declare module "../src/routes/learn/+handler" {
const Run: $.Namespace<H4>;
namespace Run {
type Context = $.ContextForFile<H4>;
}

/** @deprecated use `Run` namespace instead */
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = $.Routes["/learn"];
export type Context = $.MultiRouteContext<Route>;
export type Handler = $.HandlerLike<Route>;
export type GET = $.HandlerLike<Route, "GET">;
export type HEAD = $.HandlerLike<Route, "HEAD">;
export type POST = $.HandlerLike<Route, "POST">;
export type PUT = $.HandlerLike<Route, "PUT">;
export type DELETE = $.HandlerLike<Route, "DELETE">;
export type PATCH = $.HandlerLike<Route, "PATCH">;
export type OPTIONS = $.HandlerLike<Route, "OPTIONS">;
}
}

type L1 = $.Template<"L1", typeof import("../src/routes/+layout.marko")>;
declare module "../src/routes/+layout.marko" {
interface Input extends $.LayoutInput<L1> {}
Expand All @@ -172,7 +200,7 @@ declare module "../src/routes/+layout.marko" {
/** @deprecated use `Run` namespace instead */
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = $.Routes["/" | "/brand" | "/docs/newsletter" | "/docs/newsletter/april-2026" | "/docs/newsletter/february-2026" | "/docs/newsletter/january-2026" | "/docs/newsletter/june-2026" | "/docs/newsletter/march-2026" | "/docs/newsletter/may-2026" | "/docs/explanation/class-vs-tags-api" | "/docs/explanation/controllable-components" | "/docs/explanation/fine-grained-bundling" | "/docs/explanation/immutable-state" | "/docs/explanation/let-vs-const" | "/docs/explanation/nested-reactivity" | "/docs/explanation/optimizing-performance" | "/docs/explanation/separation-of-concerns" | "/docs/explanation/serializable-state" | "/docs/explanation/streaming" | "/docs/explanation/targeted-compilation" | "/docs/explanation/why-is-marko-fast" | "/docs/guide/duplicate-form-submissions" | "/docs/guide/library-integration" | "/docs/guide/low-level-apis" | "/docs/guide/marko-5-interop" | "/docs/guide/publishing-components" | "/docs/guide/styling" | "/docs/introduction/getting-started" | "/docs/introduction/installation" | "/docs/introduction/integrations" | "/docs/introduction/welcome-to-marko" | "/docs/introduction/why-marko" | "/docs/marko-run/adapters" | "/docs/marko-run/cli" | "/docs/marko-run/data-loading" | "/docs/marko-run/file-based-routing" | "/docs/marko-run/getting-started" | "/docs/marko-run/runtime" | "/docs/marko-run/typescript" | "/docs/marko-run/validation" | "/docs/marko-run/vite-plugin" | "/docs/reference/concise-syntax" | "/docs/reference/core-tag" | "/docs/reference/custom-tag" | "/docs/reference/language" | "/docs/reference/lazy-loading" | "/docs/reference/native-tag" | "/docs/reference/reactivity" | "/docs/reference/supported-environments" | "/docs/reference/template" | "/docs/reference/typescript" | "/docs/tutorial/components-and-reactivity" | "/docs/tutorial/fundamentals" | "/playground"];
export type Route = $.Routes["/" | "/brand" | "/docs/newsletter" | "/docs/newsletter/april-2026" | "/docs/newsletter/february-2026" | "/docs/newsletter/january-2026" | "/docs/newsletter/june-2026" | "/docs/newsletter/march-2026" | "/docs/newsletter/may-2026" | "/docs/explanation/class-vs-tags-api" | "/docs/explanation/controllable-components" | "/docs/explanation/fine-grained-bundling" | "/docs/explanation/immutable-state" | "/docs/explanation/let-vs-const" | "/docs/explanation/nested-reactivity" | "/docs/explanation/optimizing-performance" | "/docs/explanation/separation-of-concerns" | "/docs/explanation/serializable-state" | "/docs/explanation/streaming" | "/docs/explanation/targeted-compilation" | "/docs/explanation/why-is-marko-fast" | "/docs/guide/duplicate-form-submissions" | "/docs/guide/library-integration" | "/docs/guide/low-level-apis" | "/docs/guide/marko-5-interop" | "/docs/guide/publishing-components" | "/docs/guide/styling" | "/docs/introduction/getting-started" | "/docs/introduction/installation" | "/docs/introduction/integrations" | "/docs/introduction/welcome-to-marko" | "/docs/introduction/why-marko" | "/docs/marko-run/adapters" | "/docs/marko-run/cli" | "/docs/marko-run/data-loading" | "/docs/marko-run/file-based-routing" | "/docs/marko-run/getting-started" | "/docs/marko-run/runtime" | "/docs/marko-run/typescript" | "/docs/marko-run/validation" | "/docs/marko-run/vite-plugin" | "/docs/reference/concise-syntax" | "/docs/reference/core-tag" | "/docs/reference/custom-tag" | "/docs/reference/language" | "/docs/reference/lazy-loading" | "/docs/reference/native-tag" | "/docs/reference/reactivity" | "/docs/reference/supported-environments" | "/docs/reference/template" | "/docs/reference/typescript" | "/docs/tutorial/components-and-reactivity" | "/docs/tutorial/fundamentals" | "/learn/basics/components" | "/learn/basics/dynamic-content" | "/learn/basics/state" | "/learn/basics/templates" | "/playground"];
export type Context = Run.Context;
export type Handler = $.HandlerLike<Route>;
export type GET = $.HandlerLike<Route, "GET">;
Expand Down Expand Up @@ -209,6 +237,30 @@ declare module "../src/routes/docs/+layout.marko" {
}
}

type L3 = $.Template<"L3", typeof import("../src/routes/learn/+layout.marko")>;
declare module "../src/routes/learn/+layout.marko" {
interface Input extends $.LayoutInput<L3> {}
const Run: $.Namespace<L3>;
namespace Run {
type Context = $.ContextForFile<L3> & Marko.Global;
}

/** @deprecated use `Run` namespace instead */
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = $.Routes["/learn/basics/components" | "/learn/basics/dynamic-content" | "/learn/basics/state" | "/learn/basics/templates"];
export type Context = Run.Context;
export type Handler = $.HandlerLike<Route>;
export type GET = $.HandlerLike<Route, "GET">;
export type HEAD = $.HandlerLike<Route, "HEAD">;
export type POST = $.HandlerLike<Route, "POST">;
export type PUT = $.HandlerLike<Route, "PUT">;
export type DELETE = $.HandlerLike<Route, "DELETE">;
export type PATCH = $.HandlerLike<Route, "PATCH">;
export type OPTIONS = $.HandlerLike<Route, "OPTIONS">;
}
}

type P1 = $.Template<"P1", typeof import("../src/routes/_home/+page.marko")>;
declare module "../src/routes/_home/+page.marko" {
const Run: $.Namespace<P1>;
Expand Down Expand Up @@ -1428,13 +1480,105 @@ declare module "../src/routes/docs/_compiled-docs/tutorial/fundamentals+page.mar
}
}

type P54 = $.Template<"P54", typeof import("../src/routes/playground/+page.marko")>;
declare module "../src/routes/playground/+page.marko" {
type P54 = $.Template<"P54", typeof import("../src/routes/learn/_compiled-learn/basics/components+page.marko")>;
declare module "../src/routes/learn/_compiled-learn/basics/components+page.marko" {
const Run: $.Namespace<P54>;
namespace Run {
type Context = $.ContextForFile<P54> & Marko.Global;
}

/** @deprecated use `Run` namespace instead */
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = $.Routes["/learn/basics/components"];
export type Context = Run.Context;
export type Handler = $.HandlerLike<Route>;
export type GET = $.HandlerLike<Route, "GET">;
export type HEAD = $.HandlerLike<Route, "HEAD">;
export type POST = $.HandlerLike<Route, "POST">;
export type PUT = $.HandlerLike<Route, "PUT">;
export type DELETE = $.HandlerLike<Route, "DELETE">;
export type PATCH = $.HandlerLike<Route, "PATCH">;
export type OPTIONS = $.HandlerLike<Route, "OPTIONS">;
}
}

type P55 = $.Template<"P55", typeof import("../src/routes/learn/_compiled-learn/basics/dynamic-content+page.marko")>;
declare module "../src/routes/learn/_compiled-learn/basics/dynamic-content+page.marko" {
const Run: $.Namespace<P55>;
namespace Run {
type Context = $.ContextForFile<P55> & Marko.Global;
}

/** @deprecated use `Run` namespace instead */
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = $.Routes["/learn/basics/dynamic-content"];
export type Context = Run.Context;
export type Handler = $.HandlerLike<Route>;
export type GET = $.HandlerLike<Route, "GET">;
export type HEAD = $.HandlerLike<Route, "HEAD">;
export type POST = $.HandlerLike<Route, "POST">;
export type PUT = $.HandlerLike<Route, "PUT">;
export type DELETE = $.HandlerLike<Route, "DELETE">;
export type PATCH = $.HandlerLike<Route, "PATCH">;
export type OPTIONS = $.HandlerLike<Route, "OPTIONS">;
}
}

type P56 = $.Template<"P56", typeof import("../src/routes/learn/_compiled-learn/basics/state+page.marko")>;
declare module "../src/routes/learn/_compiled-learn/basics/state+page.marko" {
const Run: $.Namespace<P56>;
namespace Run {
type Context = $.ContextForFile<P56> & Marko.Global;
}

/** @deprecated use `Run` namespace instead */
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = $.Routes["/learn/basics/state"];
export type Context = Run.Context;
export type Handler = $.HandlerLike<Route>;
export type GET = $.HandlerLike<Route, "GET">;
export type HEAD = $.HandlerLike<Route, "HEAD">;
export type POST = $.HandlerLike<Route, "POST">;
export type PUT = $.HandlerLike<Route, "PUT">;
export type DELETE = $.HandlerLike<Route, "DELETE">;
export type PATCH = $.HandlerLike<Route, "PATCH">;
export type OPTIONS = $.HandlerLike<Route, "OPTIONS">;
}
}

type P57 = $.Template<"P57", typeof import("../src/routes/learn/_compiled-learn/basics/templates+page.marko")>;
declare module "../src/routes/learn/_compiled-learn/basics/templates+page.marko" {
const Run: $.Namespace<P57>;
namespace Run {
type Context = $.ContextForFile<P57> & Marko.Global;
}

/** @deprecated use `Run` namespace instead */
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
export type Route = $.Routes["/learn/basics/templates"];
export type Context = Run.Context;
export type Handler = $.HandlerLike<Route>;
export type GET = $.HandlerLike<Route, "GET">;
export type HEAD = $.HandlerLike<Route, "HEAD">;
export type POST = $.HandlerLike<Route, "POST">;
export type PUT = $.HandlerLike<Route, "PUT">;
export type DELETE = $.HandlerLike<Route, "DELETE">;
export type PATCH = $.HandlerLike<Route, "PATCH">;
export type OPTIONS = $.HandlerLike<Route, "OPTIONS">;
}
}

type P58 = $.Template<"P58", typeof import("../src/routes/playground/+page.marko")>;
declare module "../src/routes/playground/+page.marko" {
const Run: $.Namespace<P58>;
namespace Run {
type Context = $.ContextForFile<P58> & Marko.Global;
}

/** @deprecated use `Run` namespace instead */
namespace MarkoRun {
export { NotHandled, NotMatched, GetPaths, PostPaths, GetablePath, GetableHref, PostablePath, PostableHref, Platform };
Expand Down Expand Up @@ -1524,4 +1668,8 @@ type D48 = $.Meta<"D48", typeof import("../src/routes/docs/_compiled-docs/refere
type D49 = $.Meta<"D49", typeof import("../src/routes/docs/_compiled-docs/reference/typescript+meta.json")>;
type D50 = $.Meta<"D50", typeof import("../src/routes/docs/_compiled-docs/tutorial/components-and-reactivity+meta.json")>;
type D51 = $.Meta<"D51", typeof import("../src/routes/docs/_compiled-docs/tutorial/fundamentals+meta.json")>;
type D52 = $.Meta<"D52", typeof import("../src/routes/playground/+meta.json")>;
type D52 = $.Meta<"D52", typeof import("../src/routes/learn/_compiled-learn/basics/components+meta.json")>;
type D53 = $.Meta<"D53", typeof import("../src/routes/learn/_compiled-learn/basics/dynamic-content+meta.json")>;
type D54 = $.Meta<"D54", typeof import("../src/routes/learn/_compiled-learn/basics/state+meta.json")>;
type D55 = $.Meta<"D55", typeof import("../src/routes/learn/_compiled-learn/basics/templates+meta.json")>;
type D56 = $.Meta<"D56", typeof import("../src/routes/playground/+meta.json")>;
6 changes: 6 additions & 0 deletions agent-feedback/bugs.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Suspected Bugs

Out-of-scope defects noticed while working on something else. Format and rules: [README.md](README.md).

## `<let-debounce>` return value never flows when client-rendered without server markup

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Shorten the heading.

Use a 1-3 word heading, such as ## Return Binding; retain the detailed failure description in the body. As per coding guidelines, headings must be 1-3 words maximum.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-feedback/bugs.md` at line 5, Shorten the bugs.md heading for the
let-debounce return-value issue to a concise 1–3 word title, such as “Return
Binding,” while preserving the detailed failure description in the body.

Source: Coding guidelines


`src/tags/let/let-debounce.marko` › `Input` | 2026-07-24 | impact:med | effort:med

When `<let-debounce>` is used inside a component tree that is rendered entirely in the browser (mounted via a dynamic tag after a lazy `import()`, with no server-rendered markup to hydrate), its `<return>` value is never assigned: the compiled parent output wires `$value`/`$delay` setters but the tag's returned binding never fires, so downstream `const`/`if`/`script` sections depending on it never run. The same usage works on `/playground` where the tree is server-rendered and hydrated (`src/routes/playground/tags/playground/tags/result/result.marko`). This looks like a `@marko/compiler`/runtime issue with `<return>` in client-only render paths and may be worth reducing and reporting upstream. Re-verify by rendering any lazily-imported component containing `let-debounce/files=input.files` followed by `script -- console.log(files)`; the log never fires (workaround in `src/tags/app-playground/tags/embed-result.marko` uses a manual `setTimeout` debounce instead).
11 changes: 9 additions & 2 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"mlog",
"nbsp",
"noindex",
"noopener",
"onbeforeinput",
"openjsf",
"optgroup",
Expand Down Expand Up @@ -80,6 +81,12 @@
"WHATWG"
],
"ignoreRegExpList": [],
"files": ["*", "docs/**/*", "src/**/*"],
"ignorePaths": ["**/_compiled-docs", "dist", "skills", ".claude"]
"files": ["*", "docs/**/*", "learn/**/*", "src/**/*"],
"ignorePaths": [
"**/_compiled-docs",
"**/_compiled-learn",
"dist",
"skills",
".claude"
]
}
18 changes: 9 additions & 9 deletions docs/tutorial/components-and-reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ As with many user interfaces, our first step is to gather input from the user. W

Of course, right now we aren't keeping track of the value that this input contains. To do this, we need to introduce state. In Marko, the most common way to do this is with [tag variables](../reference/language.md#tag-variables). Here, we will use [Marko's `<let>` tag](../reference/core-tag.md#let):

```marko
```marko playground
<let/degF=80>

<input type="number" value=degF>
Expand All @@ -29,7 +29,7 @@ Of course, right now we aren't keeping track of the value that this input contai

Now the `<input>` has an initial value, but we still aren't keeping track of it when it changes. One way you may think to do this is by listening for [the `input` event](https://developer.mozilla.org/en-US/docs/Web/API/Element/input_event) with an [event handler](../reference/native-tag.md#event-handlers):

```marko
```marko playground
// Warning: There's a better way to do this!
<let/degF=80>

Expand All @@ -41,7 +41,7 @@ Now the `<input>` has an initial value, but we still aren't keeping track of it

This _seems_ to work at first glance, but you'll find out quickly that the value of the input isn't fully synchronized. This is because in HTML, `value=` actually refers to the [_default_ value](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input#value) of the input and not its current value. This is why instead, we should leverage the [controllable](../reference/native-tag.md#change-handlers) pattern with `Change` handlers.

```marko
```marko playground
<let/degF=80>

<input type="number" value=degF valueChange(value) { degF = parseFloat(value) }>
Expand All @@ -50,7 +50,7 @@ This _seems_ to work at first glance, but you'll find out quickly that the value

Because this is such a common pattern, Marko provides a [shorthand](../reference/language.md#shorthand-change-handlers-two-way-binding) for it!

```marko
```marko playground
<let/degF=80>

<input type="number" value:parseFloat:=degF>
Expand All @@ -61,7 +61,7 @@ Because this is such a common pattern, Marko provides a [shorthand](../reference

Now we can use [the `<const>` tag](../reference/core-tag.md#const) to convert to celsius!

```marko
```marko playground
<let/degF=80>
<const/degC=(degF - 32) * 5 / 9>

Expand All @@ -77,7 +77,7 @@ Since `degC` is a [tag variable](../reference/language.md#tag-variables), its ch

Now that we have a reactive variable, let's see what else we can do! Maybe some notes about the temperature, using [conditional tags](../reference/core-tag.md#if--else)?

```marko
```marko playground
<let/degF=80>
<const/degC=(degF - 32) * 5 / 9>

Expand All @@ -101,7 +101,7 @@ Now that we have a reactive variable, let's see what else we can do! Maybe some

Or what about a temperature gauge, with some fancy CSS?

```marko
```marko playground
<let/degF=80>
<const/degC=(degF - 32) * 5 / 9>

Expand Down Expand Up @@ -141,7 +141,7 @@ Or what about a temperature gauge, with some fancy CSS?

Actually, this is getting a little bit too complex to all put in one place. Maybe we should pull that temperature gauge out into a component:

```marko
```marko playground
/* index.marko */
<let/degF=80>
<const/degC=(degF - 32) * 5 / 9>
Expand All @@ -154,7 +154,7 @@ Actually, this is getting a little bit too complex to all put in one place. Mayb
<gauge temperature=degF/>
```

```marko
```marko playground
/* tags/gauge.marko */
<div class="gauge">
<div class="needle" style={"--rotation": `${input.temperature * 180 / 100}deg`}/>
Expand Down
36 changes: 36 additions & 0 deletions learn/1-basics/1-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Templates Are HTML

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add TLDR callouts to all new lessons.

Each lesson begins without the required > [!TLDR] block.

  • learn/1-basics/1-templates.md#L1-L1: add a 2-4 bullet TLDR before the introduction.
  • learn/1-basics/2-dynamic-content.md#L1-L1: add a 2-4 bullet TLDR before the introduction.
  • learn/1-basics/3-state.md#L1-L1: add a 2-4 bullet TLDR before the introduction.
  • learn/1-basics/4-components.md#L1-L1: add a 2-4 bullet TLDR before the introduction.

As per coding guidelines, tutorial documents include a TLDR section. Based on learnings, Markdown files use the > [!TLDR] format.

📍 Affects 4 files
  • learn/1-basics/1-templates.md#L1-L1 (this comment)
  • learn/1-basics/2-dynamic-content.md#L1-L1
  • learn/1-basics/3-state.md#L1-L1
  • learn/1-basics/4-components.md#L1-L1
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@learn/1-basics/1-templates.md` at line 1, Add a `> [!TLDR]` callout
containing 2–4 concise lesson-summary bullets before the introduction in
learn/1-basics/1-templates.md (line 1), learn/1-basics/2-dynamic-content.md
(line 1), learn/1-basics/3-state.md (line 1), and learn/1-basics/4-components.md
(line 1); keep each summary specific to its lesson.

Sources: Coding guidelines, Learnings


Welcome! This is a hands-on tour of Marko. Each lesson loads files into the
editor on the right, and everything we type compiles and runs immediately in
the preview below it.

Marko is a superset of HTML, so nearly any valid HTML is already a valid
template. The editor starts with an event flyer written in plain HTML:

```marko playground
<h1>Open Mic Night</h1>
<p>Every Thursday at the corner cafe.</p>
<ul>
<li>Doors at 7pm</li>
<li>Sign-ups at the bar</li>
<li>Five minutes per act</li>
</ul>
```

Try changing some of the markup and watch the preview update.

## Attributes

Attribute values are JavaScript expressions, not strings. That means template
literals, math, objects, and function calls all work directly in markup:

```marko playground
<const/host="events@example.com">

<h1 style={ color: "rebeccapurple" }>Open Mic Night</h1>
<p>Every Thursday at the corner cafe.</p>
<a href=`mailto:${host}`>Email the host</a>
```

Load this version and try giving the heading a different color, or computing
one from an expression.
Loading
Loading