Uh oh!
There was an error while loading. Please reload this page.
feat(web): markdown-it + pierre diffs in transcript and diff panel - #2
Merged
Conversation
Adds the three-screen MVP on top of @echohello/client: - connect: manual ws:// endpoint + label, saved to AsyncStorage, connects via SupaplaneClient - sessions: workspace list grouped by freshness, tap to drill in - session: transcript + composer (send/abort/start) bound to ClientCommand wire types Shared infrastructure: - zustand connection-store: single SupaplaneClient instance, status, workspaces, sessions, per-session event buffer - AsyncStorage wrapper (v3 API: setMany/removeMany) for the saved endpoint and TOFU-pinned server fingerprint - dark theme tokens, Button, TextField, ConnectionBanner Bundler plumbing: metro.config.cjs with explicit watchFolders and nodeModulesPaths so Bun's hoisting doesn't trip the resolver on a monorepo root. Co-authored-by: opencode <opencode@echohello.dev>
Desktop build wiring:
- packages/desktop/tsconfig.build.json emits ESM to dist/
- packages/desktop/package.json: dev now runs tsc -> electron
- main.js (dist) replaces main.ts as the package entry, and
preload.js resolves to the dist sibling
- @echohello/server and @echohello/protocol exports point at
dist/ so Electron's Node can resolve them. Run
'bun run build:server' (or 'bun run build') before
'desktop dev'.
Dev port scheme (avoiding Paseo + Vite + Metro defaults):
- Daemon listenPort: 6767 -> 17687 (IANA-sparse range,
visually echoes 6767)
- Vite port: 5173 -> 5179 (adjacent to Vite default so it
reads as 'the Supaplane Vite port')
- vite.config.ts reads SUPAPLANE_DAEMON_PORT and VITE_PORT
envs; defaults updated
- AGENTS.md + docs/development.md updated to reflect the
new ports
Server hygiene:
- daemon-keypair.ts: replace require('node:crypto') with
a top-level import so it builds cleanly to ESM
Co-authored-by: opencode <opencode@echohello.dev>Wire the long-form text and diff layers into the web renderer: - `markdown-it` (token stream, html:false, linkify on, sanitised attrs) backs the agent transcript's `message.delta` / `message.final` rows so chat output renders headings, lists, code fences, tables, and links without running inline HTML. Streaming `message.delta` events coalesce into a single open markdown line per turn. - `@pierre/diffs` (Shadow DOM + CSS grid web component) renders a two-version diff panel. Hunk text is generated client-side via `diff.createTwoFilesPatch`, processed with `pierre.processFile`, and passed to `<FileDiff>`. Long-context collapse is on by default; layout toggle (split/unified) is supported. New UI surface: - A workspace row in the sidebar can "Open diff" against a sample before/after pair, opening a side-panel diff view. This exercises the diff renderer end-to-end while the daemon's `ClientCommand.diff.open` handler is still unwired. - A `diff.open` button in the composer issues the protocol command for the current session id, mirroring the existing `session.send` path. Co-authored-by: opencode <opencode@echohello.dev>
johnnyhuy added a commit
that referenced
this pull request
Aug 9, 2026
* feat(app): scaffold mobile UI for connect/sessions/transcript
Adds the three-screen MVP on top of @echohello/client:
- connect: manual ws:// endpoint + label, saved to AsyncStorage,
connects via SupaplaneClient
- sessions: workspace list grouped by freshness, tap to drill in
- session: transcript + composer (send/abort/start) bound to
ClientCommand wire types
Shared infrastructure:
- zustand connection-store: single SupaplaneClient instance,
status, workspaces, sessions, per-session event buffer
- AsyncStorage wrapper (v3 API: setMany/removeMany) for the
saved endpoint and TOFU-pinned server fingerprint
- dark theme tokens, Button, TextField, ConnectionBanner
Bundler plumbing: metro.config.cjs with explicit watchFolders
and nodeModulesPaths so Bun's hoisting doesn't trip the
resolver on a monorepo root.
Co-authored-by: opencode <opencode@echohello.dev>
* chore(dev): wire desktop build + shift dev ports off 6767/5173
Desktop build wiring:
- packages/desktop/tsconfig.build.json emits ESM to dist/
- packages/desktop/package.json: dev now runs tsc -> electron
- main.js (dist) replaces main.ts as the package entry, and
preload.js resolves to the dist sibling
- @echohello/server and @echohello/protocol exports point at
dist/ so Electron's Node can resolve them. Run
'bun run build:server' (or 'bun run build') before
'desktop dev'.
Dev port scheme (avoiding Paseo + Vite + Metro defaults):
- Daemon listenPort: 6767 -> 17687 (IANA-sparse range,
visually echoes 6767)
- Vite port: 5173 -> 5179 (adjacent to Vite default so it
reads as 'the Supaplane Vite port')
- vite.config.ts reads SUPAPLANE_DAEMON_PORT and VITE_PORT
envs; defaults updated
- AGENTS.md + docs/development.md updated to reflect the
new ports
Server hygiene:
- daemon-keypair.ts: replace require('node:crypto') with
a top-level import so it builds cleanly to ESM
Co-authored-by: opencode <opencode@echohello.dev>
* feat(web): markdown-it + pierre diffs in transcript and diff panel
Wire the long-form text and diff layers into the web renderer:
- `markdown-it` (token stream, html:false, linkify on, sanitised
attrs) backs the agent transcript's `message.delta` /
`message.final` rows so chat output renders headings, lists,
code fences, tables, and links without running inline HTML.
Streaming `message.delta` events coalesce into a single open
markdown line per turn.
- `@pierre/diffs` (Shadow DOM + CSS grid web component) renders a
two-version diff panel. Hunk text is generated client-side via
`diff.createTwoFilesPatch`, processed with `pierre.processFile`,
and passed to `<FileDiff>`. Long-context collapse is on by
default; layout toggle (split/unified) is supported.
New UI surface:
- A workspace row in the sidebar can "Open diff" against a sample
before/after pair, opening a side-panel diff view. This exercises
the diff renderer end-to-end while the daemon's
`ClientCommand.diff.open` handler is still unwired.
- A `diff.open` button in the composer issues the protocol command
for the current session id, mirroring the existing
`session.send` path.
Co-authored-by: opencode <opencode@echohello.dev>
---------
Co-authored-by: opencode <opencode@echohello.dev>johnnyhuy added a commit
that referenced
this pull request
Aug 9, 2026
* feat(app): scaffold mobile UI for connect/sessions/transcript
Adds the three-screen MVP on top of @echohello/client:
- connect: manual ws:// endpoint + label, saved to AsyncStorage,
connects via SupaplaneClient
- sessions: workspace list grouped by freshness, tap to drill in
- session: transcript + composer (send/abort/start) bound to
ClientCommand wire types
Shared infrastructure:
- zustand connection-store: single SupaplaneClient instance,
status, workspaces, sessions, per-session event buffer
- AsyncStorage wrapper (v3 API: setMany/removeMany) for the
saved endpoint and TOFU-pinned server fingerprint
- dark theme tokens, Button, TextField, ConnectionBanner
Bundler plumbing: metro.config.cjs with explicit watchFolders
and nodeModulesPaths so Bun's hoisting doesn't trip the
resolver on a monorepo root.
Co-authored-by: opencode <opencode@echohello.dev>
* chore(dev): wire desktop build + shift dev ports off 6767/5173
Desktop build wiring:
- packages/desktop/tsconfig.build.json emits ESM to dist/
- packages/desktop/package.json: dev now runs tsc -> electron
- main.js (dist) replaces main.ts as the package entry, and
preload.js resolves to the dist sibling
- @echohello/server and @echohello/protocol exports point at
dist/ so Electron's Node can resolve them. Run
'bun run build:server' (or 'bun run build') before
'desktop dev'.
Dev port scheme (avoiding Paseo + Vite + Metro defaults):
- Daemon listenPort: 6767 -> 17687 (IANA-sparse range,
visually echoes 6767)
- Vite port: 5173 -> 5179 (adjacent to Vite default so it
reads as 'the Supaplane Vite port')
- vite.config.ts reads SUPAPLANE_DAEMON_PORT and VITE_PORT
envs; defaults updated
- AGENTS.md + docs/development.md updated to reflect the
new ports
Server hygiene:
- daemon-keypair.ts: replace require('node:crypto') with
a top-level import so it builds cleanly to ESM
Co-authored-by: opencode <opencode@echohello.dev>
* feat(web): markdown-it + pierre diffs in transcript and diff panel
Wire the long-form text and diff layers into the web renderer:
- `markdown-it` (token stream, html:false, linkify on, sanitised
attrs) backs the agent transcript's `message.delta` /
`message.final` rows so chat output renders headings, lists,
code fences, tables, and links without running inline HTML.
Streaming `message.delta` events coalesce into a single open
markdown line per turn.
- `@pierre/diffs` (Shadow DOM + CSS grid web component) renders a
two-version diff panel. Hunk text is generated client-side via
`diff.createTwoFilesPatch`, processed with `pierre.processFile`,
and passed to `<FileDiff>`. Long-context collapse is on by
default; layout toggle (split/unified) is supported.
New UI surface:
- A workspace row in the sidebar can "Open diff" against a sample
before/after pair, opening a side-panel diff view. This exercises
the diff renderer end-to-end while the daemon's
`ClientCommand.diff.open` handler is still unwired.
- A `diff.open` button in the composer issues the protocol command
for the current session id, mirroring the existing
`session.send` path.
Co-authored-by: opencode <opencode@echohello.dev>
---------
Co-authored-by: opencode <opencode@echohello.dev>johnnyhuy added a commit
that referenced
this pull request
Aug 9, 2026
* feat(app): scaffold mobile UI for connect/sessions/transcript
Adds the three-screen MVP on top of @echohello/client:
- connect: manual ws:// endpoint + label, saved to AsyncStorage,
connects via SupaplaneClient
- sessions: workspace list grouped by freshness, tap to drill in
- session: transcript + composer (send/abort/start) bound to
ClientCommand wire types
Shared infrastructure:
- zustand connection-store: single SupaplaneClient instance,
status, workspaces, sessions, per-session event buffer
- AsyncStorage wrapper (v3 API: setMany/removeMany) for the
saved endpoint and TOFU-pinned server fingerprint
- dark theme tokens, Button, TextField, ConnectionBanner
Bundler plumbing: metro.config.cjs with explicit watchFolders
and nodeModulesPaths so Bun's hoisting doesn't trip the
resolver on a monorepo root.
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
* chore(dev): wire desktop build + shift dev ports off 6767/5173
Desktop build wiring:
- packages/desktop/tsconfig.build.json emits ESM to dist/
- packages/desktop/package.json: dev now runs tsc -> electron
- main.js (dist) replaces main.ts as the package entry, and
preload.js resolves to the dist sibling
- @echohello/server and @echohello/protocol exports point at
dist/ so Electron's Node can resolve them. Run
'bun run build:server' (or 'bun run build') before
'desktop dev'.
Dev port scheme (avoiding Paseo + Vite + Metro defaults):
- Daemon listenPort: 6767 -> 17687 (IANA-sparse range,
visually echoes 6767)
- Vite port: 5173 -> 5179 (adjacent to Vite default so it
reads as 'the Supaplane Vite port')
- vite.config.ts reads SUPAPLANE_DAEMON_PORT and VITE_PORT
envs; defaults updated
- AGENTS.md + docs/development.md updated to reflect the
new ports
Server hygiene:
- daemon-keypair.ts: replace require('node:crypto') with
a top-level import so it builds cleanly to ESM
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
* feat(web): markdown-it + pierre diffs in transcript and diff panel
Wire the long-form text and diff layers into the web renderer:
- `markdown-it` (token stream, html:false, linkify on, sanitised
attrs) backs the agent transcript's `message.delta` /
`message.final` rows so chat output renders headings, lists,
code fences, tables, and links without running inline HTML.
Streaming `message.delta` events coalesce into a single open
markdown line per turn.
- `@pierre/diffs` (Shadow DOM + CSS grid web component) renders a
two-version diff panel. Hunk text is generated client-side via
`diff.createTwoFilesPatch`, processed with `pierre.processFile`,
and passed to `<FileDiff>`. Long-context collapse is on by
default; layout toggle (split/unified) is supported.
New UI surface:
- A workspace row in the sidebar can "Open diff" against a sample
before/after pair, opening a side-panel diff view. This exercises
the diff renderer end-to-end while the daemon's
`ClientCommand.diff.open` handler is still unwired.
- A `diff.open` button in the composer issues the protocol command
for the current session id, mirroring the existing
`session.send` path.
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
---------
Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
This was referenced Aug 9, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Wire the long-form text + diff layers into the web renderer so agent chat
content and code diffs render with structure (headings, lists, code,
links, hunks) instead of preformatted text.
Markdown:
markdown-itpackages/web/src/components/MarkdownView.tsxhtml: false(no inline HTML),linkify: true,typographer: true.renderSafe()walks the token list and stripson*/src/srcdoc/styleattrs and anyhrefwith an unsafe protocol(
javascript:,data:, etc.).<div className="markdown-body ...">with Tailwindutility selectors for h1/h2/h3/p/pre/code/ul/li/a/strong.
Wired into
AgentTranscriptformessage.deltaandmessage.finalevents. Streaming deltas coalesce into one open markdown line per turn
(so token pushes don't fragment the row list).
Diff:
@pierre/diffspackages/web/src/components/DiffView.tsxdiff.createTwoFilesPatch, processed withpierre.processFile, fedto
<FileDiff>.pierre-dark. Diff style: split (toggle to unified via prop).diff grid).
type, so the renderer is wrapped in a small
<RenderFileDiff>adapter that re-types the props and caststhrough
unknown as React.ComponentType<…>. Documented inline.New UI surface:
sample
before/afterpair into a side-panel<DiffView>. Thisexercises the renderer end-to-end while the daemon-side
ClientCommand.diff.openhandler is still unwired.diff.openbutton next to Send issues the protocolcommand for the current session id. Output is currently a
transcript confirmation; the future daemon reply will round-trip
into the same
<DiffView>.Verification
The 2 failing tests in
connection-offer.test.tsare pre-existing(they assert
app.supaplane.runwhile the encoder producessupaplane.com) and not related to this PR. No new test failures.Consequences
(shiki is pierre's highlighter backend). Vite code-splits by
language already; only the languages actually referenced by a
rendered file end up loaded at runtime, but the build emit is
larger as a result.
diff.opencomposer button currently issues a command thedaemon ignores — the Composer footer shows the "opened diff for
…" hint locally on a click, but no server-side confirmation is
wired yet. Daemon-side handling is out of scope for this PR.
pierre/diffsis shipped as a React-19-compatible library, but theReact 19 JSX type checker rejects its component type — hence the
<RenderFileDiff>adapter. Upstream may fix this in a futurerelease; we can drop the adapter when they do.
Testing
bun run typecheckcleanbun run build:webcleanpinned-language demo renders both before/after with hunks and
shiki highlighting (markdown pane + diff pane are independent)
the markdown-it sanitiser