Motivation
Components can invoke caller-provided content through Markdown <Content /> or TypeScript useContent(). Projected content must retain the caller's lexical bindings, but its live effects may depend on middleware and resources supplied by the component invocation.
If those effects are owned by the caller's longer-lived resource scope, they can outlive their dependencies. For example:
<TempDir>
<Watch />
</TempDir>
<Watch /> must stop before <TempDir> removes the directory. Keeping the caller's binding environment does not require giving projected effects the caller's resource lifetime.
This story establishes that distinction before <TempDir> is implemented in #189.
Contract
Projected content has two separate kinds of context:
- Lexical context comes from the caller. Expressions and bindings resolve where the content was authored.
- Resource lifetime comes from the component invocation. Effects and resources created while the content executes are children of the component's resource scope.
A component applies its contextual middleware before invoking content. Projected content observes that middleware for the duration of its execution.
Values may escape a component scope. Ongoing effects may not escape it.
For both <Content /> and useContent():
- enter the component's resource scope;
- apply the component's middleware;
- expand projected content with the caller's lexical binding environment;
- halt and finish resources created by that content; and
- only then clean up resources owned by the component itself.
The same contract applies on success, error, and cancellation. Nested component scopes tear down leaf-first.
persist eval extends a resource from its block to the current component invocation, not beyond that invocation. A daemon started while projected content executes stops before the component invocation completes.
Architecture
Separate caller binding projection from resource ownership. Do not use the caller's resource scope merely because projected expressions use the caller's EvalEnv.
The implementation should provide one ordinary component-lifecycle mechanism shared by Markdown and TypeScript components. Resource-owning components such as <TempDir> and <Worktree> must compose that mechanism rather than constructing private eval-scope ownership machinery.
Preserve ancestor middleware visibility and the existing deterministic nested-component ordering.
Testing
Add lower-level lifecycle tests using a provider component and a projected watcher resource:
<Provider>
<Watch />
</Provider>
Verify that:
- projected expressions resolve caller bindings;
- projected content observes middleware installed by the provider;
- the watcher is running while projected content executes;
- the watcher stops before provider cleanup;
- success, propagated error, and cancellation all produce leaf-first teardown;
- nested providers and sibling invocations remain isolated;
- Markdown
<Content /> and TypeScript useContent() have the same ownership behavior;
- projected
persist eval resources stop with the component invocation;
- projected daemons stop with the component invocation; and
- no component middleware or resource state leaks to later siblings.
Use a harness-owned event timeline for exact teardown ordering. Add an executable Markdown integration test where the behavior is observable without JavaScript; keep internal scope-order assertions in lower-level tests.
Documentation
Update the component, content-projection, eval-scope, persist, and daemon sections of the executable Markdown specification. Replace language that conflates caller lexical context with caller resource ownership.
Document the invariant concisely:
Projected content retains the caller's bindings and executes within the callee component's resource scope.
Acceptance criteria
- Caller bindings remain lexical across content projection.
- Component middleware is visible while projected content executes.
- Resources created by projected content cannot outlive the component invocation.
- Child resource teardown finishes before component cleanup.
- Markdown and TypeScript components share the same behavior.
- Success, error, cancellation, nesting, and sibling isolation are covered.
- The specification and website documentation describe the same ownership model.
Not included
Motivation
Components can invoke caller-provided content through Markdown
<Content />or TypeScriptuseContent(). Projected content must retain the caller's lexical bindings, but its live effects may depend on middleware and resources supplied by the component invocation.If those effects are owned by the caller's longer-lived resource scope, they can outlive their dependencies. For example:
<Watch />must stop before<TempDir>removes the directory. Keeping the caller's binding environment does not require giving projected effects the caller's resource lifetime.This story establishes that distinction before
<TempDir>is implemented in #189.Contract
Projected content has two separate kinds of context:
A component applies its contextual middleware before invoking content. Projected content observes that middleware for the duration of its execution.
Values may escape a component scope. Ongoing effects may not escape it.
For both
<Content />anduseContent():The same contract applies on success, error, and cancellation. Nested component scopes tear down leaf-first.
persist evalextends a resource from its block to the current component invocation, not beyond that invocation. A daemon started while projected content executes stops before the component invocation completes.Architecture
Separate caller binding projection from resource ownership. Do not use the caller's resource scope merely because projected expressions use the caller's
EvalEnv.The implementation should provide one ordinary component-lifecycle mechanism shared by Markdown and TypeScript components. Resource-owning components such as
<TempDir>and<Worktree>must compose that mechanism rather than constructing private eval-scope ownership machinery.Preserve ancestor middleware visibility and the existing deterministic nested-component ordering.
Testing
Add lower-level lifecycle tests using a provider component and a projected watcher resource:
Verify that:
<Content />and TypeScriptuseContent()have the same ownership behavior;persist evalresources stop with the component invocation;Use a harness-owned event timeline for exact teardown ordering. Add an executable Markdown integration test where the behavior is observable without JavaScript; keep internal scope-order assertions in lower-level tests.
Documentation
Update the component, content-projection, eval-scope,
persist, and daemon sections of the executable Markdown specification. Replace language that conflates caller lexical context with caller resource ownership.Document the invariant concisely:
Acceptance criteria
Not included
<TempDir>,<File>,<Glob>,<Worktree>, or another resource component.