Workbench API — engine boundary for external editor tooling
This document defines the Weaver-side API shape needed by a live XSLT workbench or playground. It is intentionally about the engine boundary, not the UI or product implementation.
For the public host-facing shape of the first weaverxslt.org embed, see
WORKBENCH_EMBED.md.
It complements ARCHITECTURE.md, especially the public API boundary and source-map guidance, DIFFERENTIATORS.md for why this matters to the product thesis, ROADMAP.md for where the workbench lands, ERRORS.md for the diagnostic contract, and URI_RESOLUTION.md for host-owned resource loading and identity.
Why this doc exists
The live workbench idea is good, and the current plan is to embed the first
public example directly into weaverxslt.org.
Weaver should therefore contribute the engine contract that such a host
needs without collapsing the engine boundary into page-specific UI code.
That means this doc is deliberately narrower than the original note:
- it defines what Weaver should expose
- it defines what the host/workbench must own
- it does not design the final UI
- it does not require the engine package itself to absorb website UI logic
Core rule
Weaver may expose a workbench-friendly API. Weaver does not blur engine semantics and website UI into one surface.
Corollaries:
- The engine returns structured artifacts: diagnostics, generated TS, source-map data, IR handles, output.
- The embedding host owns panes, debouncing policy, editor widgets, persistence, sharing, starter presets, and browser sandbox UX.
- The same compile/run semantics must serve CLI, watch mode, tests, and the embedded workbench. No UI-only compiler path is allowed.
Goals
- Support an in-memory XML + XSLT editing loop without filesystem-only assumptions.
- Return first-class structured artifacts suitable for editor tooling.
- Preserve the existing diagnostic contract rather than inventing a second UI-specific error shape.
- Keep the generated TypeScript inspectable and read-only in the first slice.
- Leave enough room for later linked-highlighting, trace, and output mapping.
Non-goals
- Building the actual four-pane UI in this repository.
- Choosing Monaco versus another editor surface.
- Defining the final website information architecture, routing, persistence, examples gallery, or sharing model beyond what the engine boundary needs.
- Allowing edited generated TS to flow back into XSLT semantics.
- Requiring browser execution of generated TS in Weaver itself.
Host placement
For MVP+6.5, assume the first host is a public embed on weaverxslt.org.
That changes where the example appears, but it does not change the boundary:
- this repository owns the engine-facing compile/run and mapping API
- the site embed owns pane layout, browser worker wiring, and page UX
- the site embed may ship a small preset picker that seeds both editable panes with starter XML + XSLT examples
- the workbench example should be demonstrable from docs-site content without inventing a second compiler path
Placement in the roadmap
This contract exists to support ROADMAP.md MVP+6.5.
It depends on earlier increments providing:
- readable codegen output
- stable source maps
- watch-mode correctness
- structured compile-time and runtime diagnostics
Without those, a workbench API is just a thin wrapper over unstable internals.
Design principles
1. In-memory first
Workbench callers must be able to supply XML and XSLT as uri + text pairs.
No core loop should require local files.
2. Structured artifacts, not scraped strings
The host should never parse formatted diagnostic text or generated output logs to recover semantics. Weaver returns structure; renderers are separate.
3. Same engine path everywhere
Workbench compile/run calls must reuse the same compiler/runtime machinery as other entry points. If CLI/watch/tests and workbench disagree, the workbench API is wrong.
4. Read-only generated TS in v1
Generated TS is an inspectable artifact. It is not an editable round-trip surface in the first workbench slice.
5. Host-owned execution and sandbox policy
If a host executes generated TS in the browser, sandboxing and runtime policy
belong to the host boundary. Weaver must not assume ambient eval(), network,
filesystem, or DOM access.
Terminology
Use these terms consistently:
- source document: a
uri + textinput supplied by the host - compiled stylesheet: Weaver-owned compiled form suitable for repeated transform runs
- generated TS: emitted TypeScript artifact for inspection or execution
- source map: mapping between XSLT spans and generated TS spans
- workbench host: the external application or product embedding Weaver
Recommended contract shape
The exact exported function names can still evolve. What matters is the shape and separation of concerns.
Current implemented slice:
@arakendo/weaver-xslt/workbenchnow exportsSourceDocument,CompiledStylesheet,WeaverSourceMap,CompileRequest,CompileResult,TransformRequest,TransformResult,ResolveSourceXmlNodeHandleRequest,ResolveSourceXmlNodeHandleResult,ResolveSourceXmlNodeHandleAtOffsetRequest,ResolveSourceXmlNodeHandleAtOffsetResult,ResolveSourceXmlNodeHandleInRangeRequest,ResolveSourceXmlNodeHandleInRangeResult,CompileAndTransformRequest,CompileAndTransformResult,compile(...),transform(...),resolveSourceXmlNodeHandle(...),resolveSourceXmlNodeHandleAtOffset(...),resolveSourceXmlNodeHandleInRange(...), andcompileAndTransform(...).- The first slice now includes a reusable compiled-stylesheet handle for
repeated runs while keeping
compileAndTransform(...)as the one-shot convenience boundary. - The source-map surface is now structured rather than raw-only: callers keep
access to the raw JSON while also getting
mapSourceToGenerated(...)andmapGeneratedToSource(...)for linked-highlighting work.
Source documents
The uri is for diagnostics, source maps, caching, and host identity. It is
not permission to perform I/O.
Compile surface
export interface CompileRequest {
stylesheet: SourceDocument;
options?: {
emitGeneratedTs?: boolean;
emitSourceMap?: boolean;
emitIr?: boolean;
};
}
export interface CompileSuccessResult {
ok: true;
diagnostics: DiagnosticReport[];
stylesheet: CompiledStylesheet;
generatedTs?: string;
sourceMap?: WeaverSourceMap;
}
export interface CompileFailureResult {
ok: false;
diagnostics: DiagnosticReport[];
}
export type CompileResult = CompileSuccessResult | CompileFailureResult;
Rules:
diagnosticsis always present.- Successful compile returns a reusable
CompiledStylesheethandle. generatedTsmay be present even when a later transform would fail.
Transform surface
export class CompiledStylesheet {
readonly stylesheet: SourceDocument;
readonly diagnostics: DiagnosticReport[];
readonly generatedTs?: string;
readonly sourceMap?: WeaverSourceMap;
transform(sourceXml: SourceDocument, options?: TransformOptions): TransformResult;
}
export interface TransformRequest {
stylesheet: CompiledStylesheet;
sourceXml: SourceDocument;
options?: TransformOptions;
}
export interface TransformResult {
ok: true | false;
diagnostics: DiagnosticReport[];
output?: string;
execution?: TransformExecutionInfo;
notices?: WorkbenchNotice[];
}
Rules:
- Transform-only callers use the compiled handle returned from
compile(...). - Runtime failures still use
DiagnosticReport; no special workbench-only exception shape is introduced.
Source-XML node handle resolution
For tracked-node debugging hosts, the workbench surface can resolve the stable
XML trace path format, a caret offset, or a selection range against an
in-memory SourceDocument:
export interface ResolveSourceXmlNodeHandleRequest {
sourceXml: SourceDocument;
path: string;
}
export interface ResolveSourceXmlNodeHandleAtOffsetRequest {
sourceXml: SourceDocument;
offset: number;
}
export interface ResolveSourceXmlNodeHandleInRangeRequest {
sourceXml: SourceDocument;
offsetStart: number;
offsetEnd: number;
}
export interface ResolveSourceXmlNodeHandleSuccessResult {
ok: true;
diagnostics: DiagnosticReport[];
handle?: XmlNodeHandle;
}
export interface ResolveSourceXmlNodeHandleFailureResult {
ok: false;
diagnostics: DiagnosticReport[];
}
export type ResolveSourceXmlNodeHandleResult =
| ResolveSourceXmlNodeHandleSuccessResult
| ResolveSourceXmlNodeHandleFailureResult;
export type ResolveSourceXmlNodeHandleAtOffsetResult =
ResolveSourceXmlNodeHandleResult;
export type ResolveSourceXmlNodeHandleInRangeResult =
ResolveSourceXmlNodeHandleResult;
Rules:
- Parse failures use the normal
DiagnosticReport[]contract. - A missing path is not a parse/runtime failure; it returns
ok: truewith nohandleso hosts can treat it as an empty selection lookup. - A caret offset resolves the deepest matching element, attribute, or non-whitespace text node using the parsed XML node locations already tracked by the engine.
- A selection range resolves the deepest matching element, attribute, or
non-whitespace text node whose selectable region fully covers the range.
Ranges spanning multiple selectable nodes return
ok: truewith no handle. - The returned handle matches the same stable trace identity used by XML-node breakpoints and pause payloads.
Convenience surface
For the external workbench host, one-shot compile-and-run is useful:
export interface CompileAndTransformRequest {
stylesheet: SourceDocument;
sourceXml: SourceDocument;
options?: {
emitGeneratedTs?: boolean;
emitSourceMap?: boolean;
} & TransformOptions;
}
export interface WorkbenchNotice {
severity: 'warning';
code: 'native_fallback';
message: string;
details: { key: string; value: string }[];
suggestions?: {
kind: 'fix' | 'hint' | 'alternative';
label: string;
confidence?: number;
replacement?: string;
}[];
}
export interface CompileAndTransformResult {
ok: true | false;
diagnostics: DiagnosticReport[];
output?: string;
stylesheet?: CompiledStylesheet;
generatedTs?: string;
sourceMap?: WeaverSourceMap;
execution?: TransformExecutionInfo;
notices?: WorkbenchNotice[];
}
This is a boundary convenience, not a new semantic engine.
On success, compile(...) and compileAndTransform(...) return the reusable
compiled handle directly. On failure, callers still receive the same
DiagnosticReport[] contract without a partial stylesheet handle.
For the first implemented slice, notices is where caller-facing execution
warnings belong. In particular, execution: 'auto' fallback to the
interpreter should surface a structured warning notice instead of forcing the
host to reverse-engineer CLI text formatting.
Generated TS inspection
If generated TS is exposed directly, the contract should stay minimal:
export interface EmitResult {
code: string;
sourceMap?: WeaverSourceMap;
diagnostics: DiagnosticReport[];
}
The host may show this text in a pane. The host must not treat local symbol names or helper names as a stable imported API.
Mapping surfaces
The workbench's first useful linked-highlighting features depend on mappings, not on reparsing generated code.
Recommended shape:
export interface SourceSpanMap {
mapSourceToGenerated(span: SourceSpan): readonly GeneratedSpan[];
mapGeneratedToSource(span: GeneratedSpan): readonly SourceSpan[];
}
The current WeaverSourceMap implementation uses the existing emitted
line-based source map plus provenance comments, so returned spans are honest
line-level spans rather than pretending to have token precision.
Rules:
- Empty results are acceptable; fake precision is not.
- Mapping APIs should consume the same span model used by diagnostics.
- Output-to-source mapping is explicitly later work and not required for the first public contract.
Diagnostics
The workbench consumes the same DiagnosticReport contract defined in
ERRORS.md.
That means:
- code, phase, category, severity,
primary,related,frames,details,suggestions, andcausesremain first-class data - formatted text is optional convenience, not the semantic contract
- the workbench host should render structure directly where practical
No new workbench-specific diagnostic schema should be introduced.
Non-diagnostic execution warnings still belong in structured data. The current
example is native fallback under execution: 'auto', which is surfaced as a
workbench notice alongside the existing execution.fallbackReason metadata.
Resource loading and identity
Any future workbench-facing compile or transform entry points must honor the resolver split from URI_RESOLUTION.md:
- Weaver owns URI resolution semantics.
- The host owns resource access and policy.
That includes in-memory providers for playground:/..., untitled:..., or
other virtual URIs.
Deferred surfaces
The following are real future needs, but they should not be required for the first workbench-friendly API boundary.
Trace events
Useful later for step/trace UI:
export interface TraceOptions {
enabled: boolean;
includeValues?: boolean;
includeTemplateFrames?: boolean;
includeGeneratedLocations?: boolean;
}
This should land only after the compile/run surfaces are already stable.
Session / incremental editing API
An incremental session object may be valuable later, but it is an optimization surface and should not be the first contract.
Stateless compile + transform calls are enough to prove the loop first.
Output mapping
mapOutputToSource(...) is high-value but should be treated as a later layer.
Do not block the first workbench contract on output provenance.
Minimal Weaver-side surface for MVP+6.5
For the roadmap slice in this repository, the minimal useful contract is:
- source documents with
uri + text - compile result returning
DiagnosticReport[] - optional generated TS artifact
- optional source-map artifact
- transform result returning output +
DiagnosticReport[] - structured notices for caller-facing execution warnings such as native
fallback under
execution: 'auto' - convenience compile-and-transform entry point
That is enough for a weaverxslt.org embed or any other host to build:
- XML pane
- XSLT pane
- read-only generated TS pane
- output pane
- diagnostics panel
- a small preset selector that swaps in editable starter documents
without forcing the engine package to absorb the actual page implementation.
Starter preset set for MVP+6.5
For the first public weaverxslt.org embed, keep the preset set small and
intentional. The point is to remove blank-page friction, not to ship a full
examples catalog.
Recommended host-side preset shape:
interface WorkbenchPreset {
id: string;
label: string;
description: string;
sourceXml: SourceDocument;
stylesheet: SourceDocument;
}
Rules:
- Selecting a preset should replace both editable panes.
- After hydration, users must be able to edit both panes freely.
- Presets should be static content owned by the host, not fetched through a second compiler path.
- The first set should stay small enough that users can understand what each one is demonstrating at a glance.
Recommended first set:
1. Hello world
Purpose: prove the core compile -> generated TS -> output loop with the smallest possible stylesheet.
Source XML:
Stylesheet:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<hello>
<xsl:value-of select="/root/name"/>
</hello>
</xsl:template>
</xsl:stylesheet>
Expected output:
2. Parameters with defaults
Purpose: show that stylesheets can expose explicit configuration while still producing useful output without requiring extra UI beyond the XML + XSLT panes.
Source XML:
Stylesheet:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:param name="greeting" select="'hello'"/>
<xsl:template match="/root">
<out>
<xsl:value-of select="$greeting"/>
</out>
</xsl:template>
</xsl:stylesheet>
Expected output:
3. Apply-templates flow
Purpose: show the rule-based execution model instead of only single-template rendering.
Source XML:
Stylesheet:
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/catalog">
<items>
<xsl:apply-templates select="item"/>
</items>
</xsl:template>
<xsl:template match="item">
<entry>
<xsl:value-of select="."/>
</entry>
</xsl:template>
</xsl:stylesheet>
Expected output:
These three presets are enough to prove:
- direct value extraction
- parameterized stylesheet structure
- multi-template rule dispatch
without turning the first embed into an examples browser.
Product boundary reminder
This repository should stop at the engine contract.
The website embed or any later host may build:
- panes and editor chrome
- sharing
- examples gallery
- authentication and persistence
- browser worker orchestration
- execution policy and sandbox UX
Weaver's job is to make that possible without leaking semantics through prose, deep imports, or UI-only hacks.