Skip to content

V2 Roadmap#27

Draft
multiarc wants to merge 10 commits into
mainfrom
feature/lang_asmt
Draft

V2 Roadmap#27
multiarc wants to merge 10 commits into
mainfrom
feature/lang_asmt

Conversation

@multiarc

@multiarc multiarc commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Heddle v2.0

The 2.0 evolution: a sandbox-safe expression tier, safe-by-default output, declarative branching, a component model, editor tooling, a build-time compiler, and a streaming render surface. Almost everything is additive and differential-gated — the dynamic engine's rendered bytes are unchanged. The breaking changes are two engine defaults flipped together and the removal of the legacy @import() include (see Migration).

Native expression tier

A sandbox-safe, Roslyn-free expression language in call parens: @if(Count > 0), @(Price * Quantity), @(Name ?? "anon"), @(IsFeatured ? "★" : ""). Operators and literals follow the C# precedence table exactly and compile to System.Linq.Expressions delegates — no Roslyn, no arbitrary method calls. Capability is limited to properties/indexers/operators/literals plus host-registered whitelisted functions (FunctionRegistry, [ExportFunctions]). ExpressionMode selects MemberPathsOnly / Native (default) / FullCSharp; the inner-@ form stays full C#.

Safe output by default

An OutputProfile.Html profile HTML-encodes the unnamed @(...) carrier, closing the XSS-by-default gap, with @raw(...) as the explicit opt-out and a @profile(html|text) directive that flips mid-document. The HTML encoder is a pluggable TemplateOptions.Encoder (System.Text.Encodings.Web.TextEncoder) — opt-in, with the default preserving the existing WebUtility.HtmlEncode bytes — and context-encoding extensions @attr / @js / @url cover the HTML-attribute, JavaScript-string-literal, and URL-component contexts that element-text encoding does not.

Declarative branching

Optional @elif/@else as ordinary extensions that coordinate with a preceding @if/@ifnot through a published local context — no new keywords, no chaining, no imperative statement structure. Text between branch blocks is stripped with a warning (industry-standard adjacency). The underlying publish/read channel (Scope.Publish/TryRead, BranchState) is public API for custom extensions to coordinate; branch-set classification is driven by [BranchRole], so a custom extension can join or form a set with the same semantics as the built-ins.

Ergonomics

@for(5) / @for(Count) / @for(range(...)) loop sugar (no more hand-written C# loop models), an opt-in TrimDirectiveLines that swallows whole-line directives' trailing newlines, and a double-render warning when a default-output definition is also called by name.

Named props & slots

Definitions gain typed named parameters with defaults (@card(Article, style: "wide", compact: true)), prop reads in bodies, and parameterized @out() slots — with abstract-definition composition and inheritance narrowing. Missing/unknown/mistyped props are positioned compile errors at the call site.

Editor tooling & LSP

Editor tooling extracted as a zero-breaking-change, flag-gated projection (Heddle.LanguageServices, null-cost when off) plus a hand-rolled LSP 3.17 server: typed member completion inside @(...), hover types, diagnostics at template positions, and go-to-definition across @<< imports (imported-file diagnostics re-anchored to the import site). Ships with a VS Code extension.

Build-time precompilation

A Roslyn incremental source generator (Heddle.Generator) compiles .heddle templates to IProcessStrategy classes at build time — Razor-style, byte-for-byte identical to the dynamic engine, with no runtime Heddle dependency in the generated output. Covers the full construct set (member paths, native expressions, verbatim C#, :: dynamic, branches, @list/@for, definitions/props/slots, @partial, @profile, [ExportFunctions], @<< composition imports); template errors become positioned build diagnostics (HED70xx) and anything not yet emittable degrades safely to the dynamic path (e.g. the step-back value-call encoders @attr/@js/@url render dynamically, byte-identically). A whole-corpus differential gate proves parity. The runtime stays fully dynamic; a Heddle.CSharpTierEnabled trim switch keeps Roslyn out of a trimmed publish. Ships with the heddle CLI (heddle render <template> --model-json <file>), a T4-successor render tool.

Streaming & UTF-8 render surface

New Generate(data, TextWriter) and Generate(data, IBufferWriter<byte>) sink overloads with a two-tier, surrogate-safe UTF-8 transcode path — render straight into Response.BodyWriter with no full-string materialization. The string path stays bit-identical; sink paths allocate O(1) regardless of output size (benchmarked). Precompiled templates emit the sink entry points too, with an opt-in "…"u8 static-piece fast path.

Render budgets

Optional TemplateOptions.RenderBudget sets per-render resource caps for running untrusted templates — MaxOutputChars, MaxRenderOps, and MaxRenderTime — and a breach throws TemplateRenderBudgetException carrying Kind/Limit/Observed. Default is unlimited with zero render-path cost; enforcement lives at the renderer seam, so counts are uniform across the string/TextWriter/IBufferWriter sinks and identical on the dynamic and precompiled backends.

Demo, samples & docs

A progressively-enhanced in-browser demo (Ace editor + typed WASM language worker + sandboxed render pane) on the published docs site, backed by a trimmed Roslyn-pure WebAssembly host and a Playwright smoke suite. A gallery of ten runnable, golden-asserted samples (SSR, streaming SSR, sandboxed templates, HTML-safe output, component props/slots, custom extensions, definition libraries, dynamic models, build-time codegen, precompiled apps) doubling as end-to-end CI coverage, plus a traceability artifact mapping success criteria to the tests that prove them.

Migration (2.0 breaking changes)

Coming from 1.x, three changes need attention, absorbed as one migration:

  • Output profile default TextHtml — the unnamed @(...) now HTML-encodes. Opt out per output with @raw, or restore per template with OutputProfile.Text.
  • TrimDirectiveLines default-on — whole-line directives swallow their trailing newline. Set TrimDirectiveLines = false to restore 1.x behavior.
  • Legacy @import() removed — any @import call site now fails to compile with a positioned HED4003 error; use @<<{{ path }} to share definitions/layouts, or @partial(){{ name }} to embed another template's rendered output inline.

Everything else is additive — including the pluggable Encoder (default preserves existing bytes), the @attr/@js/@url context-encoders, and render budgets. TemplateOptions.AllowCSharp is deprecated in favour of ExpressionMode but keeps working as a bridge. Precompiled projects must be rebuilt with the 2.0 Heddle.Generator; 1.x manifests are rejected by the engine-version gate and fall back (or throw under PrecompiledMismatchPolicy.Strict).

CI & packaging

Cross-platform test, workflow, and packaging failures resolved: RID-specific tool packing, LF-pinned golden fixtures, serialized multi-TFM runs (fixing a process-global singleton race), culture-pinned formatter tests, explicit .NET runtime installs, project-level symbol-package config, the VS Code extension TypeScript build, a trimmed-runtime string-op fallback surfaced by the WASM demo, and a warning-clean multi-TFM build. Contributor spec/roadmap material is excluded from the published docs site.

@multiarc multiarc changed the title Feature/lang asmt V2 Roadmap Jul 7, 2026
Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

multiarc added 6 commits July 7, 2026 21:52
Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
Flip the 2.0 window defaults and complete the version shift:

- Engine: OutputProfile=Html, TrimDirectiveLines=true (TemplateOptions ctors)

- Generator: Heddle.Generator.props + ConfigReader code fallback -> Html/true

- TemplateResolver convenience-ctor defaults -> Html/true (serve Html manifests)

- Version 1.0.0 -> 2.0.0 across csproj, docs, vscode, lsp workflow, generator snapshots, CHANGELOG

Fixes exposed by the flip:

- Internal C# code-gen meta-templates pinned to Text (were HTML-encoding generated source)

- WidenToWholeLine defensive bounds clamp (trim crash on stale block position)

- Emitter records the 'html' binding name for the Html unnamed carrier (gauntlet resolution)

Re-ratified goldens/tests for the new defaults; pinned the codegen-t4-successor sample to Text.

Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
…y landed in the C# engine and ANTLR grammar.

Migrate Ace web editor to v2 language grammar
- WS1: wire JS grammar generation + propagation into the build (propagate_js.js, npm scripts); refresh stale v1.x mode-copy lexer/parser/listener to v2; CI drift guard in docs.yml.
- WS2: extend heddle_highlight_rules.js to classify all v2 constructs (literals, operators, brackets, commas, function/method calls, this, prop lists, named args, slots, @elif/@else/@raw/@Profile).
- WS4: rewrite beautify.js as a Heddle-aware, idempotent formatter with formatOptions schema.
- WS5: parse-only worker diagnostics baseline; align messages to HED0003; toAnnotations.js.
- WS6: add elif/elseif/else/profile/raw + slot out extensions, built-in functions, and v2 snippets.
- WS7: register [ ] bracket pair (auto-close/skip) and outdent; folding verified.
- WS8: demo.html showcases v2 constructs; demo-smoke updated.
- WS9: stand up JS test harness (node:test, ace-code shim); 129 tests.
- WS10: build_ace.sh picks up all js/src edits; Ace pin kept at v1.32.6.

Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
@multiarc
multiarc force-pushed the feature/lang_asmt branch from 9eaf197 to 38d0038 Compare July 9, 2026 07:29
Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
@multiarc
multiarc force-pushed the feature/lang_asmt branch from c19eba5 to 4845d51 Compare July 12, 2026 16:14
Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
@multiarc
multiarc force-pushed the feature/lang_asmt branch from 4845d51 to a9c7e36 Compare July 12, 2026 16:16
Signed-off-by: Aliaksandr Kukrash <multiarc@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants