feat(runtime): lower generated runtime ids

req: wire/001

req: ts/001
This commit is contained in:
slhx agent
2026-05-10 23:04:45 +02:00
parent 9e0342951c
commit 79414fbb39
6 changed files with 208 additions and 22 deletions
+66
View File
@@ -0,0 +1,66 @@
// req: ts/001
export type ResourceKind = "slot" | "atom" | "handle" | "form";
export interface ResourceId {
kind: ResourceKind;
id: number;
}
export type ScopeKey =
| { kind: "key"; value: string }
| { kind: "field"; value: string };
export interface ResourceRef {
resource: ResourceId;
scope: ScopeKey | null;
}
export type Payload =
| { kind: "text"; value: string }
| { kind: "html"; value: string };
export type ScrollBehavior =
| "preserve"
| "top"
| { kind: "element"; target: ResourceRef };
export type Effect =
| { kind: "put"; target: ResourceRef; payload: Payload }
| { kind: "insert"; target: ResourceRef; key: string; payload: Payload }
| { kind: "prepend"; target: ResourceRef; key: string; payload: Payload }
| { kind: "remove"; target: ResourceRef; key: string | null }
| { kind: "move"; target: ResourceRef; key: string; before: string | null }
| { kind: "focus"; target: ResourceRef }
| { kind: "navigate"; url: string; mode: "push" | "replace" | "redirect"; scroll: ScrollBehavior; title: string | null }
| { kind: "emit"; name: string; payload: string };
export interface EffectBatch {
abiVersion: number;
fingerprint: bigint;
ops: Effect[];
}
export interface AtomSnapshot {
id: number;
bytes: Uint8Array;
}
export interface SlhxRuntime {
readonly runtimeAbiVersion: number;
roots(): Element[];
rootOf(node: Element | null): Element | null;
applyHtml(html: string, root?: ParentNode | null, title?: string | null): boolean;
applyBatch(buffer: ArrayBuffer, root?: ParentNode | null): void;
decodeBatch(buffer: ArrayBuffer): EffectBatch;
atomValue(root: Element | ParentNode | null | undefined, id: number): Uint8Array | undefined;
decodeAtomState(encoded: string): AtomSnapshot[];
}
declare global {
interface Window {
slhx: SlhxRuntime;
}
}
declare const slhx: SlhxRuntime;
export default slhx;
+16 -1
View File
@@ -341,7 +341,9 @@
const scope = root || document;
const doc = new DOMParser().parseFromString(html, "text/html");
const template = doc.querySelector("template[data-slhx]");
if (!replaceSlot(scope, doc, "content", template ? template.innerHTML : html)) {
const lowered = replaceLoweredSlots(scope, doc);
const named = replaceSlot(scope, doc, "content", lowered ? undefined : (template ? template.innerHTML : html));
if (!lowered && !named) {
emit(scope, "slhx:missing-content-slot", null);
return false;
}
@@ -351,6 +353,19 @@
return true;
}
function replaceLoweredSlots(scope, doc) {
let changed = false;
doc.querySelectorAll("[data-sid], [data-slot-id]").forEach((source) => {
const id = source.getAttribute("data-sid") || source.getAttribute("data-slot-id");
const target = scope.querySelector(`[data-sid="${cssEscape(id)}"], [data-slot-id="${cssEscape(id)}"]`);
if (target) {
target.innerHTML = source.innerHTML;
changed = true;
}
});
return changed;
}
function replaceSlot(scope, doc, name, fallback) {
const target = scope.querySelector(`[data-slhx-slot="${name}"], [data-slot="${name}"]`);
if (!target) return false;