You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As an XMD author, I want Agent conversation entries rendered through ordinary Markdown components, so session viewers and interactive REPLs can share a configurable presentation without sharing their transports or lifecycles.
Example
Repository-local components can change the presentation for one message kind:
<Session.Message.Tool entry={entry} />
The initial default kinds are:
Session.Message.User
Session.Message.Assistant
Session.Message.Thought
Session.Message.Tool
Session.Message.Permission
Session.Message.Status
Session.Message.Unknown
A Tail viewer and an ACP-backed REPL both normalize their own source events before invoking these components. XMD supplies one immutable entry prop whose exact kind selects the component.
Current gap
#774 established that provider files can be observed read-only, and XMD already receives conversation updates while it owns an ACP prompt. These sources have different schemas, timing, authority, and lifecycles. Without one presentation boundary, Tail and REPL would duplicate normalization-facing rendering behavior or expose provider records to authored components.
Accepted evidence
#799, implemented on open PR #805, establishes that @bomb.sh/tty is viable as a zero-I/O rendering and input layer under both Deno source and deno compile. It also establishes that XMD's generator model can keep ingesting immutable snapshots while Markdown rendering is blocked, discard stale projection work, preserve entry-anchored viewport state, and render only the newest pending state.
The production design preserves those conclusions, not #799's private entry type, test-only props, narrow Markdown lexer, mutation controls, evidence record, byte counts, barrier implementation, development-dependency placement, or proof-harness permissions.
Contract
The presentation layer accepts immutable snapshots of provider-neutral logical entries. Each entry has a stable identity, one presentation kind, normalized state and normalized content. Provider adapters retain raw records and private fields; authored renderers receive only the XMD entry.
An unsupported but safely framed provider record becomes an unknown entry instead of disappearing. A source whose schema cannot safely establish record boundaries or session identity refuses before presentation.
Each conversation view has one XMD-owned update loop and one stable internal region per logical entry. XMD owns normalized entry state, stable entry identity, replacement, coalescing, viewport anchoring, follow mode, and unread state. A streaming snapshot replaces only its entry's region; settlement replaces the same region with its final presentation without duplicating neighboring entries.
Source ingestion is independent of rendering. If a component blocks, snapshots continue to enter the in-memory projection. Stale projection work is discarded, pending work coalesces, and release renders the newest immutable state instead of replaying every intermediate revision.
Viewport position is anchored to entry identity rather than terminal rows. Resize and relayout preserve that anchor, history order, entry identities, follow mode, and unread state.
Message presentation uses ordinary Markdown components and the established component-resolution tiers. Each standard kind has a package default, and a repository-local component of the same name overrides that default alone. This Story does not add a renderer-specific capability restriction or a per-instance renderer map.
@bomb.sh/tty owns zero-I/O layout, input decoding, resize processing, and ANSI output for cells changed since the preceding frame. XMD applies decoded input and size changes to its own viewport state. Bombshell does not own normalized entries, entry identity, replacement, coalescing, viewport anchoring, follow mode, unread state, provider observation, ACP, Agent authority, PTYs, terminal emulation, child processes, or Grid lifecycle.
Rendered frames, streaming revisions, cursor, selection, scroll position, follow mode, and unread state are ephemeral. This presentation layer journals none of them. A consumer may retain one final normalized turn for its separate replay contract; doing so does not retain or restore the presentation state.
Tail and REPL share this presentation contract without sharing transports, stores, file watchers, ACP connections, or lifecycle authority. Each view owns and tears down its own ingestion and projection work.
Default Markdown components and every Bombshell WASM or other runtime asset use the canonical compiled-asset path in scripts/lib/compile.ts. Local builds, releases, and clean verification consume that one list; no Tail or REPL integration creates another compile inventory.
Normalized entry contract
Every entry is a recursively immutable JSON value. It has:
a non-empty opaque id, stable for the logical entry within one conversation source;
a kind selecting user, assistant, thought, tool, permission, status, or unknown;
a state selecting pending, active, completed, failed, or cancelled; and
user, assistant, and thought add no fields. tool adds a required human title and a category: read, edit, delete, move, search, execute, think, fetch, switch-mode, or other. permission adds a required title, display-only options with allow-once, allow-always, reject-once, or reject-always dispositions, and an optional selected display value. status may add a title. unknown adds a required safe label.
Each Session.Message.* component receives exactly one required prop named entry, containing the complete entry variant for its kind. No sibling props or implicit body are supplied.
The entry identifier is source-local presentation identity, not durable or cross-source identity, and grants no authority. Entry order, view revision, source and provider identity, session identity, file paths, credentials, terminal identifiers, permission option identifiers, and interaction authority stay outside the entry. data is JSON deliberately constructed by the normalizer, never a copied provider object, raw tool input or output, metadata, or extension payload. A diff carries no path and terminal content carries no terminal identity.
Production ownership
The first production implementation remains private to @executablemd/cli; #800 creates no @executablemd/session, @executablemd/session-ui, or @executablemd/ui package.
The seven Markdown defaults live under:
packages/cli/src/components/Session/Message/
Their filenames are User.md, Assistant.md, Thought.md, Tool.md, Permission.md, Status.md, and Unknown.md. Normalized entry types and validation, component registration, the update loop, view state, and Bombshell projection live under:
packages/cli/src/session-ui/
These are private modules, not public @executablemd/cli library exports. They receive source streams and terminal input/output capabilities as arguments and do not read command flags, process globals, provider files, or ACP connections themselves.
The CLI imports core and ACP; neither lower package imports CLI. ACP exposes its own transport information and the CLI converts it into normalized entries. Tail separately converts provider-file records at the CLI boundary. Tail, REPL, Grid, and command modules may depend on session-ui; session-ui depends on none of them.
The Tail or REPL caller owns its watcher or ACP connection. A presentation operation owns only its update loop, subscription, and projection children in its Effection scope. Cancellation dismantles those children, while the source owner performs its own teardown.
All current production consumers are CLI-hosted. A non-CLI production consumer is the condition for extracting this boundary into a workspace package. Until then, the CLI remains the only release unit and publishes no speculative package.
Acceptance
Every standard entry kind has a default Markdown component and a repository-local override replaces only that kind.
Tail fixtures and controlled ACP events that describe the same semantic conversation produce the same kind-specific component input except for source-local opaque identity.
Each component receives exactly one entry prop containing its recursively immutable, kind-narrowed entry; malformed variants and unknown fields refuse.
Raw provider records, file paths, credentials, terminal identifiers, permission option identifiers, and unrecognized private payloads never reach authored components.
Production presentation modules exist only under packages/cli/src/session-ui, the Markdown defaults exist only under packages/cli/src/components/Session/Message, and core and ACP import neither private path.
@executablemd/cli exposes no public library export for the private UI boundary and remains the only release unit added by this Story.
An unknown safely framed record remains visible as Session.Message.Unknown; an unsafe or identity-ambiguous source refuses.
Streaming snapshots replace only their entry's stable region, and settlement replaces that region with its final presentation.
A signal-controlled blocked renderer does not stop source ingestion; stale work is discarded and release renders the latest coalesced snapshot.
Two independent views do not share a watcher, ACP connection, live store, cursor, or lifecycle.
Source and deno compile entrypoints produce the same normalized component selection and viewport behavior with every required component and Bombshell asset embedded through the canonical compile inputs.
Negative controls fail if provider records leak through, entry identity changes during streaming, neighboring regions are replaced, ingestion waits for rendering, stale projection is committed, or a compiled asset is omitted.
Story
As an XMD author, I want Agent conversation entries rendered through ordinary Markdown components, so session viewers and interactive REPLs can share a configurable presentation without sharing their transports or lifecycles.
Example
Repository-local components can change the presentation for one message kind:
<Session.Message.Tool entry={entry} />The initial default kinds are:
Session.Message.UserSession.Message.AssistantSession.Message.ThoughtSession.Message.ToolSession.Message.PermissionSession.Message.StatusSession.Message.UnknownA Tail viewer and an ACP-backed REPL both normalize their own source events before invoking these components. XMD supplies one immutable
entryprop whose exact kind selects the component.Current gap
#774 established that provider files can be observed read-only, and XMD already receives conversation updates while it owns an ACP prompt. These sources have different schemas, timing, authority, and lifecycles. Without one presentation boundary, Tail and REPL would duplicate normalization-facing rendering behavior or expose provider records to authored components.
Accepted evidence
#799, implemented on open PR #805, establishes that
@bomb.sh/ttyis viable as a zero-I/O rendering and input layer under both Deno source anddeno compile. It also establishes that XMD's generator model can keep ingesting immutable snapshots while Markdown rendering is blocked, discard stale projection work, preserve entry-anchored viewport state, and render only the newest pending state.The production design preserves those conclusions, not #799's private entry type, test-only props, narrow Markdown lexer, mutation controls, evidence record, byte counts, barrier implementation, development-dependency placement, or proof-harness permissions.
Contract
The presentation layer accepts immutable snapshots of provider-neutral logical entries. Each entry has a stable identity, one presentation kind, normalized state and normalized content. Provider adapters retain raw records and private fields; authored renderers receive only the XMD entry.
An unsupported but safely framed provider record becomes an
unknownentry instead of disappearing. A source whose schema cannot safely establish record boundaries or session identity refuses before presentation.Each conversation view has one XMD-owned update loop and one stable internal region per logical entry. XMD owns normalized entry state, stable entry identity, replacement, coalescing, viewport anchoring, follow mode, and unread state. A streaming snapshot replaces only its entry's region; settlement replaces the same region with its final presentation without duplicating neighboring entries.
Source ingestion is independent of rendering. If a component blocks, snapshots continue to enter the in-memory projection. Stale projection work is discarded, pending work coalesces, and release renders the newest immutable state instead of replaying every intermediate revision.
Viewport position is anchored to entry identity rather than terminal rows. Resize and relayout preserve that anchor, history order, entry identities, follow mode, and unread state.
Message presentation uses ordinary Markdown components and the established component-resolution tiers. Each standard kind has a package default, and a repository-local component of the same name overrides that default alone. This Story does not add a renderer-specific capability restriction or a per-instance renderer map.
@bomb.sh/ttyowns zero-I/O layout, input decoding, resize processing, and ANSI output for cells changed since the preceding frame. XMD applies decoded input and size changes to its own viewport state. Bombshell does not own normalized entries, entry identity, replacement, coalescing, viewport anchoring, follow mode, unread state, provider observation, ACP, Agent authority, PTYs, terminal emulation, child processes, or Grid lifecycle.Rendered frames, streaming revisions, cursor, selection, scroll position, follow mode, and unread state are ephemeral. This presentation layer journals none of them. A consumer may retain one final normalized turn for its separate replay contract; doing so does not retain or restore the presentation state.
Tail and REPL share this presentation contract without sharing transports, stores, file watchers, ACP connections, or lifecycle authority. Each view owns and tears down its own ingestion and projection work.
Default Markdown components and every Bombshell WASM or other runtime asset use the canonical compiled-asset path in
scripts/lib/compile.ts. Local builds, releases, and clean verification consume that one list; no Tail or REPL integration creates another compile inventory.Normalized entry contract
Every entry is a recursively immutable JSON value. It has:
id, stable for the logical entry within one conversation source;kindselectinguser,assistant,thought,tool,permission,status, orunknown;stateselectingpending,active,completed,failed, orcancelled; andcontentarray.Content values are exactly:
user,assistant, andthoughtadd no fields.tooladds a required humantitleand acategory:read,edit,delete,move,search,execute,think,fetch,switch-mode, orother.permissionadds a requiredtitle, display-only options withallow-once,allow-always,reject-once, orreject-alwaysdispositions, and an optional selected display value.statusmay add a title.unknownadds a required safe label.Each
Session.Message.*component receives exactly one required prop namedentry, containing the complete entry variant for its kind. No sibling props or implicit body are supplied.The entry identifier is source-local presentation identity, not durable or cross-source identity, and grants no authority. Entry order, view revision, source and provider identity, session identity, file paths, credentials, terminal identifiers, permission option identifiers, and interaction authority stay outside the entry.
datais JSON deliberately constructed by the normalizer, never a copied provider object, raw tool input or output, metadata, or extension payload. A diff carries no path and terminal content carries no terminal identity.Production ownership
The first production implementation remains private to
@executablemd/cli; #800 creates no@executablemd/session,@executablemd/session-ui, or@executablemd/uipackage.The seven Markdown defaults live under:
Their filenames are
User.md,Assistant.md,Thought.md,Tool.md,Permission.md,Status.md, andUnknown.md. Normalized entry types and validation, component registration, the update loop, view state, and Bombshell projection live under:These are private modules, not public
@executablemd/clilibrary exports. They receive source streams and terminal input/output capabilities as arguments and do not read command flags, process globals, provider files, or ACP connections themselves.The CLI imports core and ACP; neither lower package imports CLI. ACP exposes its own transport information and the CLI converts it into normalized entries. Tail separately converts provider-file records at the CLI boundary. Tail, REPL, Grid, and command modules may depend on
session-ui;session-uidepends on none of them.The Tail or REPL caller owns its watcher or ACP connection. A presentation operation owns only its update loop, subscription, and projection children in its Effection scope. Cancellation dismantles those children, while the source owner performs its own teardown.
All current production consumers are CLI-hosted. A non-CLI production consumer is the condition for extracting this boundary into a workspace package. Until then, the CLI remains the only release unit and publishes no speculative package.
Acceptance
entryprop containing its recursively immutable, kind-narrowed entry; malformed variants and unknown fields refuse.packages/cli/src/session-ui, the Markdown defaults exist only underpackages/cli/src/components/Session/Message, and core and ACP import neither private path.@executablemd/cliexposes no public library export for the private UI boundary and remains the only release unit added by this Story.Session.Message.Unknown; an unsafe or identity-ambiguous source refuses.deno compileentrypoints produce the same normalized component selection and viewport behavior with every required component and Bombshell asset embedded through the canonical compile inputs.Relationships
9a3cf2473ca706c0ac89b66700a539a6ca368908.<Session.Tail>andxmd tail#782 and Run and reopen a linear multi-Agent XMD REPL #783, which may proceed independently afterward.<Session.Tail>andxmd tail#782 owns provider-file observation and a disposable Tail projection.<Grid>and<Pane>#781 and Present Agent session grids without an external multiplexer #784 own Grid/Pane and provider presentation, not this layer.Out of scope