Namespace-aware bootstrap parser
apps/backend/internal/plugins/webapp/runtime_bootstrap.go ↗Tracks open elements, namespaces and template depth so the injector knows when a script is inert or foreign.
Parser state
type runtimeBootstrapNamespace uint8
const (
runtimeBootstrapHTMLNamespace runtimeBootstrapNamespace = iota
runtimeBootstrapSVGNamespace
runtimeBootstrapMathMLNamespace
runtimeBootstrapTemplateTag = "template"
)
var runtimeBootstrapForeignBreakoutTags = map[string]struct{}{
"b": {}, "big": {}, "blockquote": {}, "body": {}, "br": {}, "center": {}, "code": {}, "dd": {}, "div": {}, "dl": {}, "dt": {}, "em": {}, "embed": {},
"h1": {}, "h2": {}, "h3": {}, "h4": {}, "h5": {}, "h6": {}, "head": {}, "hr": {}, "i": {}, "img": {}, "li": {}, "listing": {}, "menu": {}, "meta": {},
"nobr": {}, "ol": {}, "p": {}, "pre": {}, "ruby": {}, "s": {}, "small": {}, "span": {}, "strong": {}, "strike": {}, "sub": {}, "sup": {}, "table": {}, "tt": {},
"u": {}, "ul": {}, "var": {},
}
type runtimeBootstrapOpenElement struct {
name string
namespace runtimeBootstrapNamespace
childNamespace runtimeBootstrapNamespace
}
type runtimeBootstrapParserState struct {
elements []runtimeBootstrapOpenElement
templateDepth int
}
Namespace resolution
func (state *runtimeBootstrapParserState) namespaceForTag(tagName string) runtimeBootstrapNamespace {
if len(state.elements) == 0 {
return runtimeBootstrapHTMLNamespace
}
top := state.elements[len(state.elements)-1]
if top.namespace == runtimeBootstrapMathMLNamespace && isRuntimeBootstrapMathMLTextIntegrationPoint(top.name) && tagName != "mglyph" && tagName != "malignmark" {
return runtimeBootstrapHTMLNamespace
}
return top.childNamespace
}
func (state *runtimeBootstrapParserState) elementNamespaces(tagName string, attrs []html.Attribute) (runtimeBootstrapNamespace, runtimeBootstrapNamespace) {
parentNamespace := state.namespaceForTag(tagName)
switch parentNamespace {
case runtimeBootstrapHTMLNamespace:
switch tagName {
case "svg":
return runtimeBootstrapSVGNamespace, runtimeBootstrapSVGNamespace
case "math":
return runtimeBootstrapMathMLNamespace, runtimeBootstrapMathMLNamespace
default:
return runtimeBootstrapHTMLNamespace, runtimeBootstrapHTMLNamespace
}
case runtimeBootstrapSVGNamespace:
if isRuntimeBootstrapSVGHTMLIntegrationPoint(tagName) {
return runtimeBootstrapSVGNamespace, runtimeBootstrapHTMLNamespace
}
return runtimeBootstrapSVGNamespace, runtimeBootstrapSVGNamespace
case runtimeBootstrapMathMLNamespace:
if tagName == "annotation-xml" && hasRuntimeBootstrapHTMLAnnotationEncoding(attrs) {
return runtimeBootstrapMathMLNamespace, runtimeBootstrapHTMLNamespace
}
if isRuntimeBootstrapMathMLTextIntegrationPoint(tagName) {
return runtimeBootstrapMathMLNamespace, runtimeBootstrapHTMLNamespace
}
return runtimeBootstrapMathMLNamespace, runtimeBootstrapMathMLNamespace
default:
return runtimeBootstrapHTMLNamespace, runtimeBootstrapHTMLNamespace
}
}
Insertion with implied body fallback
apps/backend/internal/plugins/webapp/runtime_bootstrap.go ↗Finds the first executable script or falls back to the implied body end, and rejects unclosed HTML templates.
Insertion logic
func runtimeBootstrapInsertion(entry []byte) (int, error) {
tokenizer := html.NewTokenizer(bytes.NewReader(entry))
tokenizer.SetMaxBuf(len(entry) + 1)
position := 0
insertion := -1
fallback := -1
state := runtimeBootstrapParserState{}
for {
tokenType := tokenizer.Next()
raw := tokenizer.Raw()
if tokenType == html.ErrorToken {
if errors.Is(tokenizer.Err(), io.EOF) {
break
}
return -1, ErrRuntimeBootstrapUnavailable
}
if len(raw) == 0 {
return -1, ErrRuntimeBootstrapUnavailable
}
insertion, fallback = updateRuntimeBootstrapPosition(tokenizer, tokenType, raw, position, insertion, fallback, &state)
position += len(raw)
}
if position != len(entry) {
return -1, ErrRuntimeBootstrapUnavailable
}
if insertion < 0 {
insertion = fallback
}
if insertion < 0 {
if state.templateDepth > 0 {
return -1, ErrRuntimeBootstrapUnavailable
}
// HTML supplies implied head and body elements when an entry omits the
// corresponding wrapper tags. Appending here keeps the original doctype
// and encoding declarations intact while placing the script in the
// implied body, outside any inert template content.
insertion = len(entry)
}
return insertion, nil
}
Start tag handling
func updateRuntimeBootstrapStartTagPosition(tokenizer *html.Tokenizer, token html.Token, tokenType html.TokenType, raw []byte, tagName string, namespace runtimeBootstrapNamespace, position, insertion, fallback int, state *runtimeBootstrapParserState) (int, int) {
elementNamespace, childNamespace := state.elementNamespaces(tagName, token.Attr)
if elementNamespace != runtimeBootstrapHTMLNamespace {
tokenizer.NextIsNotRawText()
}
if tagName == "script" && tokenType == html.StartTagToken && state.templateDepth == 0 && insertion < 0 {
insertion = position
}
if elementNamespace == runtimeBootstrapHTMLNamespace && tagName == runtimeBootstrapTemplateTag {
state.templateDepth++
}
if tokenType == html.StartTagToken && namespace == runtimeBootstrapHTMLNamespace && state.templateDepth == 0 {
insertion, fallback = updateRuntimeBootstrapStartTag(tagName, raw, position, insertion, fallback)
}
state.openElement(tagName, elementNamespace, childNamespace, tokenType)
return insertion, fallback
}
Bootstrap edge-case tests
apps/backend/internal/plugins/webapp/runtime_test.go ↗Proves the injector skips inert template scripts, handles foreign templates, and supports documents without wrappers.
Template skipping
func TestInjectRuntimeBootstrapSkipsTemplateContent(t *testing.T) {
entry := `<!doctype html><template><script>window.__template = true;</script></template><script src="./app.js"></script>`
result, err := injectRuntimeBootstrap([]byte(entry))
if err != nil {
t.Fatalf("injectRuntimeBootstrap: %v", err)
}
body := string(result)
bootstrap := `<script src="./_kandev/host-runtime.js"></script>`
bootstrapIndex := strings.Index(body, bootstrap)
templateEnd := strings.Index(body, "</template>")
authoredScript := strings.Index(body, `<script src="./app.js">`)
if bootstrapIndex <= templateEnd || bootstrapIndex >= authoredScript {
t.Fatalf("host bootstrap was inserted outside executable document order: %q", body)
}
}
Omitted wrappers
func TestRuntimeStartupBootstrapSupportsOmittedHTMLWrappers(t *testing.T) {
entry := `<!doctype html><meta charset="utf-8"><title>Report</title><p>Hello</p>`
archive := canvasArchive(t, map[string]string{
"manifest.yaml": staticManifestYAML,
"ui/index.html": entry,
})
pkg, err := ValidatePackage(bytes.NewReader(archive))
if err != nil {
t.Fatalf("ValidatePackage: %v", err)
}
artifacts, err := NewArtifactStore(filepath.Join(t.TempDir(), "artifacts"))
if err != nil {
t.Fatalf("NewArtifactStore: %v", err)
}
artifact, err := artifacts.Put(pkg)
if err != nil {
t.Fatalf("Put: %v", err)
}
manager := NewTokenManager(nil)
token, err := manager.Issue(CapabilityBinding{
UserID: "user-1", InstanceID: "instance-1", ReleaseID: "release-1", WebAppKey: "main",
Placement: "task-canvas", Artifact: artifact, Entry: "ui/index.html",
}, 0)
if err != nil {
t.Fatalf("Issue: %v", err)
}
runtime := NewRuntime(manager, artifacts, nil, nil)
response := httptest.NewRecorder()
runtime.Serve(response, httptest.NewRequest(http.MethodGet, "/", nil), token, "")
if response.Code != http.StatusOK {
t.Fatalf("entry status = %d, body = %s", response.Code, response.Body.String())
}
body := response.Body.String()
bootstrap := `<script src="./_kandev/host-runtime.js"></script>`
if !strings.HasPrefix(body, `<!doctype html><meta charset="utf-8">`) {
t.Fatalf("doctype or encoding prefix changed: %q", body)
}
if strings.Index(body, bootstrap) <= strings.Index(body, "<p>Hello</p>") {
t.Fatalf("host bootstrap was not appended to the implied body: %q", body)
}
}
Foreign template
func TestInjectRuntimeBootstrapDoesNotTreatForeignTemplateAsInert(t *testing.T) {
entry := `<svg><template><script>window.__svg = true;</script></template></svg><script src="./app.js"></script>`
result, err := injectRuntimeBootstrap([]byte(entry))
if err != nil {
t.Fatalf("injectRuntimeBootstrap: %v", err)
}
body := string(result)
bootstrap := `<script src="./_kandev/host-runtime.js"></script>`
bootstrapIndex := strings.Index(body, bootstrap)
foreignScript := strings.Index(body, `<script>window.__svg = true;</script>`)
if bootstrapIndex < 0 || foreignScript < 0 || bootstrapIndex >= foreignScript {
t.Fatalf("host bootstrap was inserted after foreign executable script: %q", body)
}
}
Responsive desktop review dialog
apps/web/components/settings/canvas-release-review.tsx ↗Makes the desktop dialog use a responsive max width so it stays wide on large screens without breaking mobile.
Width fix
const surfaceClassName = isMobile
? "!left-0 !top-0 !h-dvh !max-h-dvh !w-screen !max-w-none !translate-x-0 !translate-y-0 flex flex-col gap-0 overflow-hidden rounded-none p-0 [padding-top:max(1rem,env(safe-area-inset-top))]"
: "flex h-[min(90dvh,48rem)] max-h-[calc(100dvh-2rem)] w-full max-w-[48rem] flex-col gap-0 overflow-hidden p-0";
: "flex h-[min(90dvh,48rem)] max-h-[calc(100dvh-2rem)] w-full sm:max-w-[48rem] flex-col gap-0 overflow-hidden p-0";
E2E fixture and desktop layout test
apps/web/e2e/tests/canvas/canvas-fixture.ts ↗Adds permission variants and release helpers so the E2E test can publish a minimal-permission canvas and verify the dialog layout.
Fixture helpers
export type CanvasReleaseRecord = {
id: string;
validation_status?: string;
permissions?: {
reads?: string[];
writes?: string[];
events?: string[];
shared_state?: boolean;
};
};
export async function listCanvasReleases(
apiClient: ApiClient,
canvasId: string,
): Promise<CanvasReleaseRecord[]> {
const response = await apiClient.rawRequest(
"GET",
`/api/v1/canvases/${encodeURIComponent(canvasId)}/releases`,
);
if (!response.ok) {
throw new Error(`Canvas release lookup failed (${response.status}).`);
}
const body = (await response.json()) as { releases?: CanvasReleaseRecord[] };
return body.releases ?? [];
}
export type CanvasSourceOptions = {
noPermissions?: boolean;
minimalPermissions?: boolean;
};
function canvasCapabilities(options?: CanvasSourceOptions): string[] {
if (options?.noPermissions) return [];
if (options?.minimalPermissions) return [" api_read:", " - tasks", " - workflows"];
return [
" api_read:",
" - tasks",
" - workflows",
" api_write:",
" - tasks",
" - messages",
" events:",
" - task.updated",
" state: true",
];
}
Desktop layout test
test("keeps the desktop release review wide without scrolling two permissions", async ({
testPage, apiClient, backend, seedData,
}) => {
await testPage.setViewportSize({ width: 1280, height: 720 });
const releaseFeature = await enableCanvasFeature(backend, apiClient, seedData.workspaceId);
const seeded = await seedTaskCanvas(testPage, apiClient, seedData, true, { noPermissions: true });
writeCanvasSource(workspacePath, seeded.canvas, { minimalPermissions: true });
// publish a permission-increasing release and wait for pending_permission
await expect.poll(async () => {
const releases = await listCanvasReleases(apiClient, canvasId!);
return releases.find((r) => r.validation_status === "pending_permission")?.id ?? null;
}).not.toBeNull();
await testPage.goto(canvasHref(canvasId));
await testPage.getByRole("button", { name: "Releases and permissions" }).click();
const dialog = testPage.getByTestId("canvas-releases-dialog");
const dialogBox = await dialog.boundingBox();
expect(dialogBox?.width).toBeGreaterThanOrEqual(720);
expect(dialogBox?.width).toBeLessThanOrEqual(780);
const permissions = dialog.getByTestId("canvas-permission-summary");
await expect(permissions.locator("li")).toHaveCount(2);
const scrollMetrics = await dialog.getByTestId("canvas-release-review-scroll").evaluate((el) => ({
clientHeight: el.clientHeight,
scrollHeight: el.scrollHeight,
}));
expect(scrollMetrics.scrollHeight).toBeLessThanOrEqual(scrollMetrics.clientHeight);
});