We are building a web UI and web-renderer system for AppWeaver. Current goal

Reusable local date/time display: timestamp elements accept timestampMs (epoch milliseconds) and an optional label prefix. The client formats them in the browser's locale/timezone, including the timezone abbreviation, with an ISO datetime/tooltip for the exact instant. Standard text size/tone/style props apply.

Backend/web

  1. Inspect the todo plugin structure, especially:
    • plugins/todo/commands/list/*
    • existing handler/representation/text rendering
  2. Add a custom renderers/web.ts for todo list
  3. Keep the current web shell architecture, but teach it to recognize/render the todo list result specially
  4. Reuse existing command execution for actions when possible
    • for example, a click in the web renderer can call the existing /api/commands/... endpoint with the right command/subcommand/payload
  5. After an action succeeds, refresh the todo list web view by re-running list with the same filters/options
  6. Do not try to solve every renderer/dispatcher abstraction globally unless needed for this slice
  7. Prefer one focused working example over a generalized framework that is not yet exercised Suggested approach

Scoped styles (Shadow DOM)

Command web UIs (WebNodeRoot, kind: 'ui') render inside a Shadow DOM island on the web client. Shared primitives (.web-node, .web-button, tree, overflow menu, tone utilities, etc.) come from a base stylesheet injected automatically; optional stylesheets on the root payload add presentation-only CSS scoped to that render.

Plugin / handler guardrails:

Schema: WebStyleSheet in src/web/ui-schema.ts ({ id, cssText }); optional stylesheets array on WebRenderResultSchema / WebNodeRoot.

Opt-in live refresh

WebNodeRoot.autoRefreshMs (2,000–60,000 ms) opts a render into polling its own meta command. Use it only on read-only view commands, never a command that starts execution. The Shadow DOM host skips polling while hidden/unmounted, busy, or while an input/textarea/select/contenteditable is focused. Requests do not overlap, create timeline entries, or show a pending overlay. Responses use normal root reconciliation and stale-response handling; polling is disposed when the render changes or unmounts. Include a manual Refresh action for recovery after connection errors.

Shadow mount overflow: optional shadowMountOverflow on WebNodeRoot ('hidden' | 'scroll-y'). Omitted or 'scroll-y' lets the inner Solid mount scroll when content is taller than the host (typical timeline cards). Use 'hidden' when your tree defines its own scroll regions (for example a fixed chrome row plus an overflow: auto panel inside stylesheets). The file tree web renderer sets 'hidden' so only .web-file-tree-block scrolls in the modal.

Stable identity and pending UI

Refreshed WebNodeRoot payloads are authoritative complete trees. The client reconciles compatible element nodes so stateful components can keep local UI state while server-rendered props, children, metadata, and stylesheets update.

Command actions may choose pending presentation:

An explicit surface: 'timeline' action renders its result in the main timeline, even when the target command has widget/dock metadata. It does not replace the origin widget or modal root. Actions without a surface can update their origin in place via onReplaceRoot.

pendingUi: {
  presentation: 'entity',
  label: 'Updating...',
}