Lower semantic rosters in browser clients

This commit is contained in:
Meta Vens 2026-07-11 20:02:43 +01:00
commit 698f3672e0
5 changed files with 139 additions and 3 deletions

View file

@ -47,6 +47,12 @@ export interface EveCommandIntent {
issuedAt: string;
clientId: string;
}
export interface EveSemanticListItem {
label: string;
status: string;
detail: string;
badges: string[];
}
export interface EveSurfaceWorldInteraction {
commandBoundary?: string;
receiptSchema?: string;
@ -145,6 +151,7 @@ export interface EveProjectedWorldEntity {
}
export declare function renderEveSurface(surface: EveSurfaceDocument, host: HTMLElement, options?: EveBrowserLoweringOptions): HTMLElement;
export declare function renderEveComponent(node: EveSurfaceComponent, options?: EveBrowserLoweringOptions): HTMLElement;
export declare function projectSemanticListItem(node: EveSurfaceComponent): EveSemanticListItem;
export declare function projectWorldScene(node: EveSurfaceComponent): EveProjectedWorldEntity[];
export declare function createWorldActionIntent(command: string, action: Record<string, unknown>, options?: EveBrowserLoweringOptions): EveCommandIntent;
export declare function applyEveSurfaceStyles(styles: EveSurfaceStyles | undefined, body?: HTMLElement): void;

View file

@ -294,9 +294,10 @@ export function renderEveComponent(node, options = currentOptions) {
pane.append(renderEveComponent(child, options));
return pane;
}
if (kind === "list") {
if (kind === "list" || kind.endsWith(".roster")) {
const list = el("section", "cultui-list");
assignId(list, node);
list.dataset.componentKind = kind;
const title = stringProp(props.title, "");
if (title)
list.append(el("h3", "cultui-list-title", title));
@ -304,6 +305,28 @@ export function renderEveComponent(node, options = currentOptions) {
list.append(renderEveComponent(child, options));
return list;
}
if (kind === "list.item" || kind.endsWith(".item")) {
const semantic = projectSemanticListItem(node);
const item = el("article", "cultui-list-item");
assignId(item, node);
item.dataset.componentKind = kind;
const heading = el("div", "cultui-list-item-heading");
heading.append(el("strong", "cultui-list-item-label", semantic.label));
if (semantic.status)
heading.append(el("span", "cultui-list-item-status", semantic.status));
item.append(heading);
if (semantic.detail)
item.append(el("div", "cultui-list-item-detail", semantic.detail));
if (semantic.badges.length) {
const badgeRow = el("div", "cultui-list-item-badges");
for (const badge of semantic.badges)
badgeRow.append(el("span", "cultui-list-item-badge", badge));
item.append(badgeRow);
}
for (const child of children)
item.append(renderEveComponent(child, options));
return item;
}
if (kind === "bar") {
const bar = el("div", "cultui-meter");
assignId(bar, node);
@ -521,6 +544,15 @@ export function renderEveComponent(node, options = currentOptions) {
fallback.append(renderEveComponent(child, options));
return fallback;
}
export function projectSemanticListItem(node) {
const props = objectProps(node.props);
return {
label: firstString(props.label, props.title, node.text, node.kind, "item"),
status: firstString(props.status, ""),
detail: firstString(props.detail, props.phase, ""),
badges: firstString(props.badges, "").split(",").map(value => value.trim()).filter(Boolean),
};
}
function applyGeneratedLayout(element, layout, style = {}) {
const display = firstString(layout.display, "");
if (display)

View file

@ -48,6 +48,13 @@ export interface EveCommandIntent {
clientId: string;
}
export interface EveSemanticListItem {
label: string;
status: string;
detail: string;
badges: string[];
}
export interface EveSurfaceWorldInteraction {
commandBoundary?: string;
receiptSchema?: string;
@ -450,15 +457,35 @@ export function renderEveComponent(
return pane;
}
if (kind === "list") {
if (kind === "list" || kind.endsWith(".roster")) {
const list = el("section", "cultui-list");
assignId(list, node);
list.dataset.componentKind = kind;
const title = stringProp(props.title, "");
if (title) list.append(el("h3", "cultui-list-title", title));
for (const child of children) list.append(renderEveComponent(child, options));
return list;
}
if (kind === "list.item" || kind.endsWith(".item")) {
const semantic = projectSemanticListItem(node);
const item = el("article", "cultui-list-item");
assignId(item, node);
item.dataset.componentKind = kind;
const heading = el("div", "cultui-list-item-heading");
heading.append(el("strong", "cultui-list-item-label", semantic.label));
if (semantic.status) heading.append(el("span", "cultui-list-item-status", semantic.status));
item.append(heading);
if (semantic.detail) item.append(el("div", "cultui-list-item-detail", semantic.detail));
if (semantic.badges.length) {
const badgeRow = el("div", "cultui-list-item-badges");
for (const badge of semantic.badges) badgeRow.append(el("span", "cultui-list-item-badge", badge));
item.append(badgeRow);
}
for (const child of children) item.append(renderEveComponent(child, options));
return item;
}
if (kind === "bar") {
const bar = el("div", "cultui-meter");
assignId(bar, node);
@ -679,6 +706,16 @@ export function renderEveComponent(
return fallback;
}
export function projectSemanticListItem(node: EveSurfaceComponent): EveSemanticListItem {
const props = objectProps(node.props);
return {
label: firstString(props.label, props.title, node.text, node.kind, "item"),
status: firstString(props.status, ""),
detail: firstString(props.detail, props.phase, ""),
badges: firstString(props.badges, "").split(",").map(value => value.trim()).filter(Boolean),
};
}
function applyGeneratedLayout(
element: HTMLElement,
layout: Record<string, unknown>,

View file

@ -518,6 +518,45 @@ body[data-pixel-art="true"] .cultui-background {
text-transform: uppercase;
}
.cultui-list-item {
display: grid;
gap: 5px;
min-width: 0;
padding: 8px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--panel);
}
.cultui-list-item-heading,
.cultui-list-item-badges {
display: flex;
gap: 6px;
align-items: center;
flex-wrap: wrap;
min-width: 0;
}
.cultui-list-item-label {
min-width: 0;
overflow-wrap: anywhere;
}
.cultui-list-item-status,
.cultui-list-item-badge {
padding: 2px 5px;
border: 1px solid var(--line-hot);
border-radius: 4px;
color: var(--quiet);
font: 600 10px var(--font-mono);
}
.cultui-list-item-detail {
color: var(--quiet);
font-size: 12px;
overflow-wrap: anywhere;
}
.cultui-avatar-card {
flex: 0 0 104px;
display: grid;

View file

@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { createEveCommandIntent, createWorldActionIntent, normalizeFieldsDocument, projectWorldScene, resolveRequiredPluginAdapters, selectAdvertisedSurface } from "../dist/index.js";
import { createEveCommandIntent, createWorldActionIntent, normalizeFieldsDocument, projectSemanticListItem, projectWorldScene, resolveRequiredPluginAdapters, selectAdvertisedSurface } from "../dist/index.js";
test("selects the requested surface from provider authority", () => {
const provider = {
@ -60,6 +60,27 @@ test("projects provider world entities into a normalized tactical plane", () =>
assert.ok(entities.every(entity => entity.yPercent >= 6 && entity.yPercent <= 94));
});
test("projects unknown semantic items through the generic list contract", () => {
const item = projectSemanticListItem({
id: "worker.one",
kind: "agent.item",
props: {
label: "Foundry Tug",
status: "working",
detail: "Hauling ore",
badges: "haul, tow, explore",
providerSpecificState: "remains provider-owned",
},
});
assert.deepEqual(item, {
label: "Foundry Tug",
status: "working",
detail: "Hauling ore",
badges: ["haul", "tow", "explore"],
});
});
test("command intents use the active surface advertisement boundary", () => {
const intent = createEveCommandIntent("aetheria.daemon.editor.inspect", {
action: {