MiOpIIk IUI
English | 简体中文
Version 0.1.0-rc.4 — prerelease. This is a release candidate, not a stable release. It has not been published to npm, it is not a DeepSeek Harness upstream feature, and rendering inside the chat depends on an unofficial, version-coupled patch of
@deepseek-ai/dsh-client-ui-chat. See Release status.
MiOpIIk IUI is a presentation-only plugin for DeepSeek Harness (DSH) Web. Models write ordinary Markdown that may contain a few trusted inline tokens and controlled iui fences; the browser turns those fragments into declarative interface components. The plugin does not require the MiOpIIk Mind/Ink/Operator agent, does not keep a ledger, and does not expose model tools.
The scope is deliberately narrow: just enough interface, in place. Everything else about the chat experience (messages, reasoning rows, images, history, streaming, copy, accessibility ownership) stays with DSH.
- Inline tokens:
{{badge:success:passed}},{{icon:check}},{{code:npm test}} - Fenced
iuiblocks containingmiopiik-mlor a validatediui/1JSON AST - A small whitelist of declarative components, local state bindings and local
segmentedinteraction - Read-only replay of legacy
ink-viewwidget JSON, so historical sessions do not lose their content - A native DSH plugin page with an Overview using the real renderer and a Configuration view with the one browser-local switch and diagnostics
It is not a UI builder: models cannot ship arbitrary HTML, JavaScript or CSS, and nothing in the message can issue a network request, call a tool, or start a model turn.
Release status
Version 0.1.0-rc.4 is a prerelease. The code is usable and tested, but the package cannot be called stable yet:
- Experimental host patch. In-chat rendering needs a local fork of
@deepseek-ai/dsh-client-ui-chat, generated by tools/fork-chat-embeds.mjs from a specific DSH version. DSH upstream exposes no Markdown embed API. Without the patch, aniuifence renders as an ordinary code block; the plugin page still previews correctly and the diagnostics say so. - Lossy cross-token Markdown. Trusted fragments split the Markdown text block, so formatting, selection/copy and screen-reader reading order that cross a token boundary are not identical to native Markdown.
- Non-incremental streaming. An unclosed
iuifence is shown as plain text until the closing fence arrives; then the block materializes at once.
The full gate list for calling v1 finished lives in docs/IUI-V1-RELEASE-CANDIDATE.md, and the per-version stable blockers are recorded in docs/RELEASE-NOTES.md. Treat those documents as baselines, not as permanent health certificates.
Compared with MiOpIIk Agent
The MiOpIIk project is split into two independent repositories. They can be installed together or separately; neither depends on the other.
| MiOpIIk IUI (this repository) | MiOpIIk Agent | |
|---|---|---|
| Package | miopiik-iui 0.1.0-rc.4, prerelease |
miopiik-dsh 0.1.1, stable |
| Layer | presentation only: trusted Markdown fragments in the browser | host side: memory, governance, background execution |
| Main surface | inline tokens, iui fences, declarative components, local state |
ink_* tool surface, inbox/facts/loops/branches ledger, operator dispatch |
| State | one browser-local render preference | append-only JSONL ledger under the workspace .dsh/miopiik/ |
| Model tools | none | yes |
| Chat ownership | native DSH AssistantMarkdown / ReasoningRow remain in charge |
session views and plugin pages; does not render IUI |
| Install requirement | DSH Web profile; experimental chat patch for in-chat rendering | DSH Web profile |
| Repository | this one | https://github.com/Chillizu/miopiik |
Repository split
This repository was extracted from the former MiOpIIk monorepo with git subtree split -P miopiik-iui, so the IUI commit history is real history. The agent part moved to its own repository, https://github.com/Chillizu/miopiik. Pre-split release tags in the old repository used the miopiik-iui-v... prefix; their assets and download links stay valid. Historical session data is not migrated or rewritten: legacy ink-view content is mapped read-only at render time (see Legacy replay). The full provenance record is in docs/MIGRATION.md.
Architecture
model output (plain Markdown from the model)
|
| shared/markdown-segments.js split fenced iui / ink-view and inline tokens;
| other code fences and inline code are left untouched
v
L-- shared/miopiik-ml.js iui fence in miopiik-ml -> raw AST document
| (or iui/1 JSON fence) JSON AST is kept for debugging/compatibility
v
shared/iui-ast.js validateAst(): component whitelist, props,
| binding paths, budgets, safe links, local degrade
v
renderer.js React component registry; theme tokens, inline SVG
|
v
browser local React state only; no network, no model round-trip
Files and responsibilities:
| File | Role |
|---|---|
| index.js | Host half. Injects one system-prompt section (miopiik-iui:render-contract, order 670, interpolation off) with the short output convention. Registers no tools. |
| shared/markdown-segments.js | Stateless splitter for fenced blocks and inline tokens. A bare top-level <ui>...</ui> at line start is also recognized when a model forgets the fence. |
| shared/miopiik-ml.js | Parser for the model-facing miopiik-ml tag grammar; produces an unvalidated AST document. |
| shared/iui-ast.js | The single validator for iui/1: REGISTRY whitelist, LIMITS, binding parser, table/chart normalization, local degradation. |
| shared/legacy-widgets.js | Read-only adapter mapping the retired seven-widget ink-view JSON into iui/1. |
| renderer.js | React renderer for the validated AST, plus inline primitives. Bundled verbatim into client.js. |
| settings.js | The DSH plugin page (plugins.bundle.config, key miopiik-iui): Overview and Configuration views. |
| client.js | Generated browser bundle. Do not edit by hand; regenerate with node tools/build.mjs. |
| cordis.patch.yml | Bundle patch used by the DSH profile to load the plugin. |
Host-side patch
DSH has no official Markdown embed API at this version. The client needs one hook inside the chat message renderer, so the repository ships a patch generator instead of a private fork that silently drifts:
- tools/fork-chat-embeds.mjs copies the installed
@deepseek-ai/dsh-client-ui-chatpackage intoupstream/dsh-client-ui-chat-embeds/(generated output, gitignored) and inserts exactly one controlled hook in thecase "text"branch ofAssistantMarkdown. - It writes the exact difference to patches/chat-markdown-embeds.patch, runs
node --checkon the result, and links the fork'snode_modulesto the DSH dependency tree so imports resolve. - Native
AssistantMarkdown,ReasoningRow(fold, summary, streaming, accessibility), images, attachments, copy and replay stay in charge; only text fragments matched by trusted markers are handed to the plugin. - The hook is fail-closed: if the plugin registry is absent, the browser preference is off, or rendering throws, the fragment goes back to native Markdown or a plain fallback. The hook also sets
globalThis.__miopiikMarkdownEmbedsHost = true, which the plugin page uses to report whether the patch is loaded.
This is not a DSH upstream API. The generator locates anchors in the installed package and fails loudly instead of producing a wrong patch, so the fork is coupled to a DSH version. Regenerate it after a DSH upgrade and point only the target profile at it; never edit the global DSH installation.
What you can write
Inline tokens
Anywhere in a normal paragraph:
| Token | Meaning |
|---|---|
{{badge:success:passed}} |
Badge with tone neutral, success, warning or danger, followed by text. Tone may be omitted ({{badge:draft}} becomes neutral). |
{{icon:check}} |
Icon by name. Allowed names: check, alert, dot, arrow-right, spark, chart, layers, shield, clock, inbox, info. Unknown names degrade to dot. |
{{code:npm test}} |
Inline monospace text; the payload is shown literally. |
Payloads are limited to 120 characters and must not contain braces or newlines. Other fenced code blocks are left untouched; a token written inside inline code is still recognized, which is a known limitation (see Limitations).
Fenced iui blocks
The fence info string is exactly iui. Two formats are accepted:
miopiik-ml(preferred): a small XML-like tag language. It is compiled to an AST, then validated.iui/1JSON AST: a JSON object with aroottree. Kept as a debugging and compatibility entry point; it goes through the same validator.
The miopiik-ml grammar uses tags for components, attributes for properties, JSON for structured values, and $state.* for bindings:
<ui title="Weekly activity" fallback="Mon 12, Tue 19">
<state name="range" value='{"total":"31"}'/>
<column gap="md">
<metric label="Total" value="$state.range.total" unit="visits"/>
<chart kind="line" series='[["Mon",12],["Tue",19]]' source="demo data"/>
</column>
</ui>
Notes on the dialect:
- Root element is
<ui>;titleandfallbackare optional.fallbackis used when the block cannot be rendered at all. <state>either uses<state name="range" value='{"...":...}'/>or carries a JSON object body:<state>{"range":{...}}</state>.- Attributes may be quoted strings, JSON literals (
options='[...]',rows='[...]'), numbers, booleans, or$state.*strings. gapacceptsnone,xs,sm,md,lg,xlor a number.chartacceptskind="bar"orkind="line"; legacytype/dataspellings are normalized tokind/series.- If a model forgets the fence, a line-start top-level
<ui>...</ui>is still parsed asmiopiik-ml; this is a tolerance path, not the documented format.
Component catalog
| Component | ML tag | Notes |
|---|---|---|
| Root | ui |
Wraps one block; title, fallback. |
| State declaration | state |
Declares local initial values; not a rendered node. |
| Text | text |
Plain text node. |
| Paragraph | p, paragraph |
Holds inline primitives only: text, badge, icon, emphasis, code, link. |
| Badge | badge |
text, tone. Tones: neutral, success, warning, danger, info, accent. Works inline and as a block. |
| Icon | icon |
Inline primitive inside p/paragraph, or empty.icon. name, label; names come from a fixed list. |
| Emphasis | b, emphasis |
Inline primitive inside p/paragraph. text, strong. |
| Code | code |
Inline primitive inside p/paragraph. text; inline monospace. |
| Link | a, link |
Inline primitive inside p/paragraph. text, href; only https://..., root-relative /... or #anchor. |
| Row / Column | row, column |
Layout containers; gap, align. |
| Grid | grid |
columns 1-6, gap, align. |
| Card | card |
title, subtitle, gap, span, align; container. |
| Divider | divider |
label optional. |
| Metric | metric |
label, value, unit, delta, caption/note, tone. Values may bind to $state. |
| Table | table |
columns (1-12 strings or {key,label} descriptors), rows (up to 200 arrays or objects), caption. |
| Chart | chart |
kind = bar or line; series = point array or $state binding; source. Rendered as inline SVG. |
| Progress | progress |
value >= 0, max positive, label, tone. |
| Callout | callout |
tone, title, text; can contain children. |
| Empty | empty |
text/title/description, icon. |
| Segmented | segmented |
Local control; bind must be $state.*, 1-6 options, label. |
The catalog is enforced by REGISTRY in shared/iui-ast.js. Unknown component types are not rendered.
Local state and bindings
Bindings are path reads only, never expressions: $state.a.b and $data.a.b. There is no evaluation, no function call and no eval. A segmented control may only bind to $state.*; pressing an option replaces that local value and re-evaluates dependent nodes in the same message. It does not send a message, call a tool or start a model turn. The current client provides no external data channel, so the data scope is empty and $data.* resolves to an absent value; it exists for a future read-only channel.
Legacy ink-view replay
Older sessions may contain fenced ink-view blocks with one of the retired widget specs (dataset, graph, timeline, text, choice, media, layout). The renderer maps those specs read-only into iui/1 before validation, so history keeps showing content instead of raw JSON. Mapping is best-effort: specs that cannot be mapped are left as their raw text (the current path does not display the old spec's own fallback field), choice actions are displayed as non-executable options, and nothing is written back to the session or to any ledger. The old protocol is documented as retired design history in docs/EXTRA-UI.md.
Plugin page
The native DSH plugin page lives at Plugins -> Installed -> MiOpIIk IUI and has two views:
- Overview: a short explanation, a live preview rendered by the real renderer with fictional demo data (when rendering is enabled), inline semantic badges, and compatibility facts (version, protocol
iui/1/miopiik-ml, access method, renderer state). - Configuration: the one real setting (show IUI in chat, browser-local), diagnostics (renderer connected, plugin version, protocol, preference key and current value, access method and patch detection), and a copyable Markdown example.
The page uses official DSH primitives when the running shell exposes them and plain theme-token elements otherwise.
Validation, budgets and error isolation
Validation is centralized in validateAst:
- Component whitelist; props are normalized and checked per type.
- Budgets: 120 nodes, depth 6, 60 chart points, 6 segmented options, 2000 characters per text node, 16384 bytes for state/table payloads, 200 table rows, 12 columns.
- Unsafe keys (
__proto__,constructor,prototype) are rejected in bindings, state and table keys. - Invalid root, depth overflow or node overflow rejects the whole block (fails closed) and shows the block's
fallbacktext. - A fence that cannot be parsed into any AST at all is handed back to native Markdown as raw text, so content is never silently dropped.
- An invalid subtree degrades only itself into a text node; its siblings and parent layout remain.
- A renderer exception at the hook boundary falls back to a plain text span marked
data-miopiik-embed-fallback.
Examples
Models produce these as ordinary answers. No tool call is needed; the model does not need to know how the renderer works. The following Markdown can be pasted into a DSH session directly.
Inline tokens mixed with normal text:
Build {{badge:success:passed}}, duration {{badge:neutral:42s}}; status {{icon:check}}.
Local check: {{code:npm test}}
A fenced block with local state, metrics, a chart, a table and a segmented control:
This release check finished {{badge:success:2/3}}:
```iui
<ui title="Release checks" fallback="3 checks this round: parse passed, validate passed, render under observation">
<state name="range" value='{"total":"3","passed":"2","series":[["Parse",12],["Validate",9],["Render",7]]}'/>
<column gap="md">
<row gap="md">
<metric label="Checks" value="$state.range.total" unit="items"/>
<metric label="Passed" value="$state.range.passed" unit="items" tone="success" delta="+1"/>
</row>
<segmented label="Round" bind="$state.range" options='[{"label":"This round","value":{"total":"3","passed":"2","series":[["Parse",12],["Validate",9],["Render",7]]}},{"label":"Previous","value":{"total":"3","passed":"1","series":[["Parse",11],["Validate",8],["Render",6]]}}]'/>
<grid columns="2">
<chart kind="bar" series="$state.range.series" source="demo data"/>
<table columns='["Check","Result"]' rows='[["Parse","passed"],["Validate","passed"],["Render","observing"]]'/>
</grid>
</column>
</ui>
```
The same content as an iui/1 JSON AST (useful for debugging and for tools that already emit JSON):
```iui
{"version":"iui/1","title":"Component usage","fallback":"Component usage: metric 2, chart 1","state":{},"root":{"id":"root","type":"column","props":{"gap":10},"children":[{"id":"m1","type":"metric","props":{"label":"Components","value":"3","unit":"items"},"children":[]},{"id":"t1","type":"table","props":{"columns":["Type","Count"],"rows":[["metric",2],["chart",1]]},"children":[]}]}}
```
The plugin's own output convention is injected by index.js. It tells the model to prefer ordinary Markdown and to use IUI only when a comparison, trend or local choice is clearer, and it warns against tool calls made just to draw a UI. The natural-output audit behind that decision is in docs/IUI-NATURAL-OUTPUT-AUDIT.md.
Compatibility
| Item | Requirement |
|---|---|
| DSH | DSH Web only (web profile). Test baseline: DSH 0.2.1-rc.5. Other DSH versions are not verified; the chat patch is version-coupled. |
| Node.js | >= 23.6 (declared in package.json engines). |
| DSH CLI | dsh plugin --profile <profile> add/remove, available in the tested baseline. |
| Browser | Chromium is used by the isolated DOM test. Other browsers are not in the verified matrix. |
The fuller package, install and removal conditions are collected in docs/SUPPORT-MATRIX.md.
Install, upgrade, rollback
0.1.0-rc.4 is a prerelease and requires a local host patch. Install it only into a DSH profile you control, in an environment where you can restart DSH Web. Do not install it into a profile used by other people without their agreement.
Prerequisites
- A DSH Web installation with a profile you can modify.
- Node.js >= 23.6 and the
dshCLI onPATH. - A local checkout of this repository for the generator and verification tools.
Install
Add the package to the profile:
dsh plugin --profile <profile> add /absolute/path/to/miopiik-iuiA tarball works too:
npm pack --pack-destination .dsh/tmp/packs dsh plugin --profile <profile> add .dsh/tmp/packs/miopiik-iui-0.1.0-rc.4.tgzThe repository also has a wrapper, tools/install-plugin.mjs, which is dry-run by default and refuses to write a running
webprofile unless--allow-running-profileis passed:node tools/install-plugin.mjs --profile <profile> --mode link # dry-run node tools/install-plugin.mjs --profile <profile> --mode link --execute # executeVerify that the committed browser bundle matches its sources:
node tools/build.mjs --checkclient.jsis generated fromshared/,renderer.js,settings.jsandpackage.json; a mismatch means the checkout is inconsistent and must not be installed.Create the chat hook once, in a controlled environment. The generator reads the installed
@deepseek-ai/dsh-client-ui-chatfrom the DSH installation (not from the profile). It auto-detects a DSH installation in common global npm prefixes; setMIOPIIK_DSH_DIRif yours is elsewhere:MIOPIIK_DSH_DIR=/path/to/dsh/installation node tools/fork-chat-embeds.mjs MIOPIIK_DSH_DIR=/path/to/dsh/installation node tools/fork-chat-embeds.mjs --checkThe output directory is
upstream/dsh-client-ui-chat-embeds/(generated, gitignored), overridable withMIOPIIK_FORK_DIR; the patch file is patches/chat-markdown-embeds.patch, overridable withMIOPIIK_FORK_PATCH.--checkis read-only: it compares the fork with what the generator would produce and validates the fork's dependency link, but creates nothing. On a machine without a DSH installation, or before a fork exists, it reports that and passes, because "no fork" means "no drift". Do not rebuild a fork that a running DSH service is loading. Background and the proposed upstream API are documented in docs/UPSTREAM-MARKDOWN-EMBED.md.Point only that profile's
@deepseek-ai/dsh-client-ui-chatresolution at the fork. Do not overwrite the global DSH installation or sharednode_modulesused by other profiles:cd <profile>/node_modules/@deepseek-ai [ -e dsh-client-ui-chat ] && mv dsh-client-ui-chat dsh-client-ui-chat.bak ln -s /absolute/path/to/miopiik-iui/upstream/dsh-client-ui-chat-embeds dsh-client-ui-chatThe fork is a separate package and resolves its own dependencies; the generator creates its
node_moduleslink to the DSH dependency tree. Keep that link intact.Back up the profile configuration and the previous
ui-chatresolution, confirm that no DSH task is running, restart DSH Web, then reload the browser. The renderer is enabled by default once both the plugin and the hook are loaded; open Plugins -> Installed -> MiOpIIk IUI to confirm.
Upgrade
- Replace the profile dependency with the new version (
dsh plugin --profile <profile> add <new-checkout-or-tarball>). - Run
node tools/build.mjs --checkin the new checkout. - If the DSH version changed, regenerate the fork and the patch (
node tools/fork-chat-embeds.mjs, then--check) and keep the profile'sui-chatresolution pointing at the regenerated fork. - Restart DSH Web. Historical messages are not migrated: they are Markdown in the session log and are rendered by the new version on next view.
Uninstall
Remove the plugin from the profile:
dsh plugin --profile <profile> remove miopiik-iuiRestore the profile's original
ui-chatresolution (for example movedsh-client-ui-chat.bakback) and keep the global DSH installation untouched.Restart DSH Web. The chat returns to native Markdown rendering. The generated
upstream/directory and the browser preference may be deleted; historical sessions are unaffected.
Rollback
Quick rollback, no restart: turn off rendering on the plugin page, or set the browser preference directly and reload:
localStorage.setItem("miopiik.markdownEmbeds", "0"); location.reload()This affects only the current browser and leaves session data untouched.
Full rollback: remove the profile dependency and restore the previous
ui-chatresolution as in Uninstall, then restart DSH Web.
Without the host patch
If the plugin is installed but the ui-chat fork is not loaded:
iuifences in chat render as ordinary fenced code blocks; inline tokens appear as literal text.- The plugin page still works and its Overview preview still renders, because the renderer runs locally in the page.
- Configuration diagnostics report that no patch was detected, instead of silently pretending rendering works.
- The host output convention is still injected, so a model may keep producing IUI; those blocks simply stay as code blocks until the hook is installed.
Configuration
There is no server-side configuration schema. The pieces are:
| Knob | Where | Effect |
|---|---|---|
Browser preference miopiik.markdownEmbeds |
Browser localStorage |
"0" disables rendering in this browser; any other value (default: absent) enables it. Toggling reloads the page to rehydrate already-rendered messages. |
| Plugin page switch | Plugins -> Installed -> MiOpIIk IUI -> Configuration | The supported UI for the same preference: "show IUI in chat". It is browser-local and not synced across tabs, browsers or profiles. |
Host patch marker globalThis.__miopiikMarkdownEmbedsHost |
globalThis in the browser |
Set to true by the ui-chat hook when it loads. The plugin page uses it to report "patch loaded" vs "no patch detected". |
Embed registry globalThis.__miopiikMarkdownEmbeds |
globalThis in the browser |
Installed by the plugin bundle; the hook calls its segments, renderInline and renderBlock. If absent, the hook returns null and the native Markdown path is used. |
Bundle patch cordis.patch.yml |
Package metadata (dsh.bundle.patch) |
Tells the profile how to load the plugin. |
The rendering switch controls display only. Turning it off does not stop the host output convention from being injected into the model prompt, so the model may still write IUI syntax; it will be shown as Markdown code.
Security and permissions
- No arbitrary HTML, JavaScript or CSS. Model output is parsed into a whitelisted component tree; there is no
innerHTML, noeval, no dynamic style sheet and no script injection. Styles come from DSH theme tokens and fixed code. - No network requests. The renderer issues no
fetch, XHR, WebSocket or external asset requests; charts are inline SVG and icons are built-in glyphs. - No model round-trips from controls. The only interactive control is the local
segmentedswitch; it replaces local React state. It cannot call a tool, start a turn or write durable state. - No arbitrary event handlers. Model output cannot attach
on*handlers; only the built-in control has behavior. - Bounded data and state. Node count, depth, chart points, options, text length, table size and state size are limited; prototype-polluting keys are rejected.
- Safe links. Only
https://..., root-relative paths and#anchorsare accepted; other schemes degrade the link node. - Local error isolation. An invalid subtree degrades itself; an invalid document falls back to its
fallbacktext; a renderer exception becomes a plain fallback span. The rest of the message remains native Markdown. - No logging. The plugin writes no logs and no files; the only value it persists is the single
localStoragepreference for the current browser.
Development and tests
npm test # node --test — offline unit tests
npm run check # npm pack + unpack + per-file syntax + import-graph check
npm run build:check # client.js must match its sources
npm run icon:check # icon asset constraints (static checks)
node tools/fork-chat-embeds.mjs --check
- The offline suite covers the splitter, the
miopiik-mlparser,iui/1validation, table/chart normalization, legacy mapping, the settings bundle, and release readiness. It needs no DSH installation, no network and no browser. - The fork-generator regression is skipped when no DSH installation can be resolved (point
MIOPIIK_DSH_DIRat one to run it); the read-only--checkno-host path is always tested. - The isolated Chromium DOM test for the plugin page is skipped when no Chromium binary is available (override the path with
CHROMIUM_PATHorMIOPIIK_CHROMIUM). - Checks that need a live DSH Web host or a real patched chat package are development scripts, not part of
npm test; they read their host paths fromMIOPIIK_*environment variables (see tools/dev-env.mjs) instead of hard-coded paths. node tools/fork-chat-embeds.mjs --checkis safe offline: without a DSH installation or a generated fork it reports that and exits successfully.- CI is defined in .github/workflows/ci.yml: unit tests on Node 23.6.0, 24.x and 26.x, the release check, the bundle sync check and the icon guard, plus a pack/install verification job.
The protocol sources of truth are shared/iui-ast.js and shared/miopiik-ml.js. renderer.js is the rendering source; client.js is generated and must never be edited by hand. Development-only end-to-end scripts live under tools/: e2e-gallery-replay.mjs, e2e-iui-user-sessions.mjs, e2e-playwright.mjs, e2e-dual-settings.mjs, e2e-legacy-replay.mjs, e2e-iui.mjs and e2e-iui-model.mjs. They require a running local DSH Web plus an isolated Playwright browser, must not send test messages to existing main sessions, and must not be treated as part of npm test. The script inventory and environment variables are documented in docs/DEV-TOOLS.md.
FAQ
Why is it still a release candidate?
Because in-chat rendering depends on an unofficial patch of a specific @deepseek-ai/dsh-client-ui-chat build, cross-token Markdown is not fully lossless, and streaming is not incremental. Calling it stable would hide those conditions from users. The gate list is in docs/IUI-V1-RELEASE-CANDIDATE.md.
What happens if I do not apply the chat patch?
The plugin still installs and its page preview works, but chat iui fences appear as ordinary code blocks and inline tokens remain literal text. The page reports "no patch detected" instead of failing silently. See Without the host patch.
Does it affect historical sessions?
No. Messages remain the original Markdown in the session log; rendering happens per view. Old ink-view blocks are mapped read-only and never rewritten; disabling rendering simply returns the message to its plain Markdown appearance.
Do I have to install MiOpIIk Agent too?
No. The two repositories are independent and can be installed together or separately. IUI has no dependency on the agent's tool surface, ledger or background execution.
Do messages change when I turn rendering off?
Only their appearance. The message text is unchanged; iui fences show as code blocks and inline tokens as text. Your browser preference is the only state that changes.
Limitations
- Inline code is not protected from tokens. Inline tokens are matched in ordinary paragraph text, including text inside backticks; keep token examples in fenced code blocks.
- Cross-token Markdown. A trusted token splits the Markdown text block. Bold/italic or other formatting that spans the token may break at the boundary; selection, copy and screen-reader order may differ from native Markdown. A real fix needs a DSH upstream Markdown AST extension point.
- Streaming is not incremental. An unclosed or malformed fence displays as plain text until it closes, then the block materializes once.
- Version-coupled experimental patch. The host hook is not an upstream API and is generated against anchors in a specific
ui-chatpackage. A DSH upgrade may make the generator fail (loudly) until the patch is refreshed. - No upstream guarantees. The patch and the fork are local artifacts for controlled use; do not publish the fork as a replacement official package.
- Legacy widgets are replay-only. The seven-widget protocol is retired; mapping is best-effort,
choiceactions do not execute, and unmappable specs keep their raw text on screen (the old spec's ownfallbackfield is not used by the current render path). - Incomplete accessibility and theme matrix. Basic dark/narrow-screen checks exist; a full light/dark/high-contrast, keyboard and screen-reader matrix has not been verified.
- Model compliance is not guaranteed. The output convention is a prompt section, so models may ignore it; the natural-output audit documents what was measured and what was not proven.
Contributing
Issues and pull requests are welcome, with the prerelease state in mind:
- Change the protocol in
shared/and the renderer inrenderer.js; then regenerateclient.jswithnode tools/build.mjsand commit both source and generated output. - Do not edit
client.js,patches/chat-markdown-embeds.patchorupstream/by hand. The patch is generated;upstream/is gitignored. - Keep the plugin dependency-free and offline. New components need whitelist, validation, budget and degradation behavior, plus tests.
- Run
npm test,npm run checkandnpm run build:checkbefore opening a pull request. Host-dependent end-to-end verification needs a running DSH Web instance and an isolated browser; do not test against other people's sessions. - Keep the user's
icon.svgasset byte-identical;npm run icon:checkguards its constraints.
License
MIT. See LICENSE.
No comments yet. Be the first to write one.