From 3a5e2057e088448b56bd614239942c73961dd4af Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sat, 22 Aug 2026 21:14:08 +0200 Subject: [PATCH] Expose editable limits and accessible command errors --- packages/eve-browser-lowering/dist/index.js | 7 +++++-- packages/eve-browser-lowering/src/index.ts | 8 +++++--- .../eve-browser-lowering/test/host-isolation.test.mjs | 7 ++++--- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/eve-browser-lowering/dist/index.js b/packages/eve-browser-lowering/dist/index.js index 9799864..7d9b624 100644 --- a/packages/eve-browser-lowering/dist/index.js +++ b/packages/eve-browser-lowering/dist/index.js @@ -137,7 +137,7 @@ export class EveBrowserProviderHost { await this.consumeCommandResult(intent, result); } catch (error) { - this.presentCommandStatus(error instanceof Error ? error.message : "Command failed.", "error"); + this.presentCommandStatus(error instanceof Error ? error.message : "Command failed.", "alert"); return; } this.lastSurfaceVersion = ""; @@ -158,7 +158,7 @@ export class EveBrowserProviderHost { surface: this.selected, }); } - this.presentCommandStatus(result.receipt.message || result.receipt.state, accepted ? "status" : "error", result.transientProjection); + this.presentCommandStatus(result.receipt.message || result.receipt.state, accepted ? "status" : "alert", result.transientProjection); } presentCommandStatus(message, role, transient) { let region = this.host.querySelector(":scope > .eve-command-result-region"); @@ -1874,6 +1874,9 @@ function renderEditableControl(node, kind, props, layout, style, options) { else if (props.rows !== undefined) { control.rows = positiveInt(props.rows, 3); } + const maxLength = positiveInt(props.maxLength, 0); + if (maxLength > 0) + control.maxLength = maxLength; control.value = String(context.value ?? ""); control.disabled = context.disabled; control.name = context.bindingName; diff --git a/packages/eve-browser-lowering/src/index.ts b/packages/eve-browser-lowering/src/index.ts index d373c32..efa7e8a 100644 --- a/packages/eve-browser-lowering/src/index.ts +++ b/packages/eve-browser-lowering/src/index.ts @@ -294,7 +294,7 @@ export class EveBrowserProviderHost { const result = parseEveCommandResult(await this.transport.submitCommand(intent)); await this.consumeCommandResult(intent, result); } catch (error) { - this.presentCommandStatus(error instanceof Error ? error.message : "Command failed.", "error"); + this.presentCommandStatus(error instanceof Error ? error.message : "Command failed.", "alert"); return; } this.lastSurfaceVersion = ""; @@ -318,12 +318,12 @@ export class EveBrowserProviderHost { } this.presentCommandStatus( result.receipt.message || result.receipt.state, - accepted ? "status" : "error", + accepted ? "status" : "alert", result.transientProjection, ); } - private presentCommandStatus(message: string, role: "status" | "error", transient?: unknown): void { + private presentCommandStatus(message: string, role: "status" | "alert", transient?: unknown): void { let region = this.host.querySelector(":scope > .eve-command-result-region"); if (!region) { region = el("section", "eve-command-result-region"); @@ -2236,6 +2236,8 @@ function renderEditableControl( } else if (props.rows !== undefined) { control.rows = positiveInt(props.rows, 3); } + const maxLength = positiveInt(props.maxLength, 0); + if (maxLength > 0) control.maxLength = maxLength; control.value = String(context.value ?? ""); control.disabled = context.disabled; control.name = context.bindingName; diff --git a/packages/eve-browser-lowering/test/host-isolation.test.mjs b/packages/eve-browser-lowering/test/host-isolation.test.mjs index c72a00a..b759aba 100644 --- a/packages/eve-browser-lowering/test/host-isolation.test.mjs +++ b/packages/eve-browser-lowering/test/host-isolation.test.mjs @@ -225,7 +225,7 @@ test("editable bindings preserve local drafts across authoritative refresh and o { id: "composer", kind: "control.input.textarea", - props: { label: "Message", value }, + props: { label: "Message", value, maxLength: 2000 }, stateBindings: [{ targetProp: "value", pointerId: "ghostlight.local.composer.message", @@ -245,6 +245,7 @@ test("editable bindings preserve local drafts across authoritative refresh and o const options = { provider, draftStore: drafts, commandSink: intent => commands.push(intent) }; renderEveSurface(surface(""), host, options); const composer = host.querySelector("textarea"); + assert.equal(composer.maxLength, 2000); composer.value = "The world should remember this."; composer.dispatchEvent(new window.Event("input", { bubbles: true })); @@ -408,7 +409,7 @@ test("component captures extend rather than disappear behind operation descripto } }); -test("authoritative refresh preserves the renderer-owned command result", () => { +test("authoritative refresh preserves the renderer-owned accessible command result", () => { const restoreDom = installDom(); try { const host = document.querySelector("#a"); @@ -420,7 +421,7 @@ test("authoritative refresh preserves the renderer-owned command result", () => renderEveSurface(surface(7), host); const result = document.createElement("section"); result.className = "eve-command-result-region"; - result.setAttribute("role", "error"); + result.setAttribute("role", "alert"); result.textContent = "The command was denied."; host.append(result);