Skip to content

Commit 3db7a5f

Browse files
authored
Merge slice/013-liveview-chat-streaming
Slice 013: LiveView chat UI with streaming
2 parents 835485c + c2663ad commit 3db7a5f

56 files changed

Lines changed: 2372 additions & 378 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/package.yml‎

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,19 @@ jobs:
102102

103103
# --overwrite is not optional. Without it, and with a release directory already
104104
# present, mix release prompts, gets no stdin, and exits 0 having built nothing.
105+
# Slice 013: on Linux, mdex's NIF is built from source for Burrito's musl ERTS, linked
106+
# through Zig (scripts/zig-cc-musl; config/config.exs names the three settings and
107+
# NOTES.md finding 14 the measurement). rustup installed the musl standard library from
108+
# rust-toolchain.toml above. macOS and Windows load the precompiled artifact for their
109+
# native ERTS; the smoke step below is what says whether it loaded.
105110
- name: Build the Burrito binary
106111
shell: bash
107112
env:
108113
BURRITO_TARGET: ${{ matrix.target }}
109114
MIX_ENV: prod
115+
MDEX_NATIVE_BUILD: ${{ runner.os == 'Linux' && '1' || '' }}
116+
TRINITY_NIF_TARGET: ${{ runner.os == 'Linux' && 'x86_64-unknown-linux-musl' || '' }}
117+
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: ${{ runner.os == 'Linux' && format('{0}/scripts/zig-cc-musl', github.workspace) || '' }}
110118
run: |
111119
set -euo pipefail
112120
# `mix deps.get` above runs in the default env. On the Windows runner the prod build
@@ -188,6 +196,8 @@ jobs:
188196
sleep 3
189197
ours > ps-after.txt
190198
grep -q '^TRINITY_SMOKE_PORT=[0-9][0-9]*$' smoke.log
199+
# Slice 013: the renderer's NIF loaded and rendered in this binary (exit 3 otherwise).
200+
grep -q '^TRINITY_SMOKE_MARKDOWN=ok$' smoke.log
191201
echo "--- ours, before ---"; cat ps-before.txt
192202
echo "--- ours, after ----"; cat ps-after.txt
193203
diff ps-before.txt ps-after.txt
@@ -202,6 +212,9 @@ jobs:
202212
if (-not (Select-String -Path smoke.log -Pattern '^TRINITY_SMOKE_PORT=\d+$')) {
203213
throw "no port line"
204214
}
215+
if (-not (Select-String -Path smoke.log -Pattern '^TRINITY_SMOKE_MARKDOWN=ok$')) {
216+
throw "the markdown renderer did not render in this binary (slice 013)"
217+
}
205218
206219
# AC1's property on a runner, and AC6's per-OS cold start, which used to be skipped.
207220
# `bash` on every OS, Windows included: the job that never curled was the one gap in
@@ -233,7 +246,14 @@ jobs:
233246
[ -n "${PORT:-}" ] && break
234247
sleep 0.5
235248
done
236-
test -n "${PORT:-}"
249+
# No endpoint line in two minutes says nothing on its own either: runs 35512430836
250+
# (slice/011), 35515556177 (slice/012) and 35521749862 all failed here on macOS and
251+
# Windows with an empty step log. The log the binary wrote goes to stderr first.
252+
if [ -z "${PORT:-}" ]; then
253+
echo "no 'Running TrinityWeb.Endpoint' line in serve.log after 120 s" >&2
254+
echo "--- serve.log ---" >&2; tail -60 serve.log >&2
255+
exit 1
256+
fi
237257
# Bounded. The first version of this loop had no limit and the Windows runner sat in
238258
# it until the run was cancelled by hand: a step that hangs reports nothing and
239259
# fails nothing, which is the same shape as a workflow that never fires.
@@ -252,6 +272,13 @@ jobs:
252272
echo "HTTP $code on port $PORT"
253273
echo "COLD_START_MS=$elapsed" | tee -a "$GITHUB_ENV"
254274
echo "### ${{ matrix.name }}: cold start to first HTTP 200: **${elapsed} ms**" >> "$GITHUB_STEP_SUMMARY"
275+
# A non-200 says nothing on its own: run 35518054546 (slice 013) printed "HTTP 500"
276+
# and nothing else, and the log that would have named the exception was never
277+
# read. The body and the tail of serve.log go to stderr before the step fails.
278+
if [ "$code" != "200" ]; then
279+
echo "--- body of GET / ---" >&2; curl -sS "http://127.0.0.1:$PORT/" | head -c 2000 >&2; echo >&2
280+
echo "--- serve.log ---" >&2; tail -60 serve.log >&2
281+
fi
255282
test "$code" = "200"
256283
# Stopping it, and Windows needs its own verb. `kill` from Git-bash does not stop a
257284
# native Windows process, so the backgrounded .exe outlived the step and the job hung

‎.sobelow-skips‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Config.CSP: Missing Content-Security-Policy,lib/trinity_web/router.ex:12,2DBC9F9
2-
Traversal.FileModule: Directory Traversal in `File.mkdir_p`,lib/trinity/data_dir/lock.ex:57,35AB9EC
3-
Traversal.FileModule: Directory Traversal in `File.read`,lib/trinity/data_dir/lock.ex:143,55D5ADB
4-
Traversal.FileModule: Directory Traversal in `File.rm`,lib/trinity/data_dir/lock.ex:82,21D88E2
5-
Traversal.FileModule: Directory Traversal in `File.write`,lib/trinity/data_dir/lock.ex:132,1C42814
1+
Config.CSP: Missing Content-Security-Policy,lib/trinity_web/router.ex:14,1DC4768
2+
Traversal.FileModule: Directory Traversal in `File.mkdir_p`,lib/trinity/data_dir/lock.ex:60,4ECF02C
3+
Traversal.FileModule: Directory Traversal in `File.read`,lib/trinity/data_dir/lock.ex:146,5EFB666
4+
Traversal.FileModule: Directory Traversal in `File.rm`,lib/trinity/data_dir/lock.ex:85,7D2B826
5+
Traversal.FileModule: Directory Traversal in `File.write`,lib/trinity/data_dir/lock.ex:135,42D2817

‎.sobelow-skips.reasons‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
# exactly that. Re-run mix sobelow --mark-skip-all and rekey this file when it happens.
1010
#
1111
# format: <fingerprint><TAB><reason>
12-
2DBC9F9 Missing CSP on the browser pipeline. The scaffold ships no Content-Security-Policy, and setting one needs LiveView nonce handling, which belongs with the UI rather than the toolchain. Slice 013 owns it. Skipped rather than fixed blind: a CSP written without the UI it protects is a guess.
13-
35AB9EC Slice 010, Trinity.DataDir.Lock.acquire/2: the directory is the application's own data directory from config or Trinity.Paths, never a request or a model output; the lock has to create it before the Repo does. Low confidence traversal by the tool's own label.
14-
55D5ADB Slice 010, Trinity.DataDir.Lock read/1: reads <data_dir>/LOCK, a path this module composes from the data directory and a constant file name; no caller supplies it.
15-
21D88E2 Slice 010, Trinity.DataDir.Lock.release/2: removes <data_dir>/LOCK only when the file carries this process's own token; same composed path as the read.
16-
1C42814 Slice 010, Trinity.DataDir.Lock take_over/2: rewrites <data_dir>/LOCK after the recorded holder's pid is found dead; same composed path, same constant name.
12+
1DC4768 Slice 013, the browser pipeline: the policy IS set, by TrinityWeb.Plugs.ContentSecurityPolicy with a nonce per request, placed before put_secure_browser_headers. sobelow recognises only a header map given to put_secure_browser_headers, so it keeps reporting the pipeline. test/trinity_web/plugs/content_security_policy_test.exs asserts the header and the nonce on every browser response. Rekeyed from 2DBC9F9 (router.ex:12, slice 000, "slice 013 owns it") when the plug moved the line to 14.
13+
4ECF02C Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock.acquire/2: the directory is the application's own data directory from config or Trinity.Paths, never a request or a model output; the lock has to create it before the Repo does. Low confidence traversal by the tool's own label.
14+
5EFB666 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock read/1: reads <data_dir>/LOCK, a path this module composes from the data directory and a constant file name; no caller supplies it.
15+
7D2B826 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock.release/2: removes <data_dir>/LOCK only when the file carries this process's own token; same composed path as the read.
16+
42D2817 Rekeyed at slice 013 (the moduledoc grew three lines for the macOS and Windows liveness check). Slice 010, Trinity.DataDir.Lock take_over/2: rewrites <data_dir>/LOCK after the recorded holder's pid is found dead; same composed path, same constant name.

‎REUSE.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SPDX-PackageDownloadLocation = "https://github.com/ScriptKittyOS/Trinity"
1111
# the gap is recorded rather than glossed.
1212

1313
[[annotations]]
14-
path = ["mix.lock", "src-tauri/Cargo.lock", "**/*.md", "**/*.json", "**/*.tsv", "**/*.cff", "**/*.toml", ".tool-versions", ".gitignore", ".sobelow-skips", ".sobelow-skips.reasons", "priv/name_digests.txt", ".formatter.exs"]
14+
path = ["mix.lock", "src-tauri/Cargo.lock", "**/*.md", "**/*.json", "**/*.tsv", "**/*.cff", "**/*.toml", ".tool-versions", ".gitignore", ".sobelow-skips", ".sobelow-skips.reasons", "priv/name_digests.txt", ".formatter.exs", "slices/**/proof/**"]
1515
precedence = "aggregate"
1616
SPDX-FileCopyrightText = "Sudo Apt Holdings LLC"
1717
SPDX-License-Identifier = "Apache-2.0"

‎ROADMAP.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ standards register names the rows that ask for them.
3636
| 010 | Core domain + persistence (Ecto/SQLite, schemas, Repo owner) | 1 Core loop | M | 000 | approved |
3737
| 011 | LLM provider layer (req_llm behind `Trinity.LLM` behaviour) | 1 Core loop | M | 010 | approved |
3838
| 012 | Session process + agent loop (gen_statem, DynamicSupervisor, rehydration) | 1 Core loop | L | 010, 011 | approved |
39-
| 013 | LiveView chat UI with streaming | 1 Core loop | M | 012 | planned |
39+
| 013 | LiveView chat UI with streaming | 1 Core loop | M | 012 | done |
4040
| 020 | Tool protocol + registry | 2 Tools | M | 012 | planned |
4141
| 021 | Permission gate + approval UI (M2 fingerprint-bound, M7) | 2 Tools | M | 020, 013 | planned |
4242
| 022 | Core tools: filesystem, web fetch/search, shell (MuonTrap) | 2 Tools | L | 021 | planned |

‎VERSIONS.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,9 @@ never pin a version hex marks as retired or vulnerable.
133133
| `ex_tauri` | ~> 0.2 | ✅ in `mix.lock` | ⚠️ Declares `otp_release: "~> 27.0"`, and Slice 000's probe refutes the reason it gives: OTP 28 macOS universal returns 200 and OTP 27 returns 404. Whether it runs on the pinned OTP is Slice 001's first measurement. ⚠️ 439 downloads all-time, so the ADR-0004 fallback matrix carries real weight. |
134134
| `nostrum` | ~> 0.10 | 🔍 not yet a dependency | Discord. ⚠️ No release in roughly 18 months. R11's trigger already fires. Check intents and components against the current gateway before Slice 072. |
135135
| `telegex` | **not pinned** | 🔍 not a single package | Telegram. ⚠️ The latest release on hex is a release candidate, roughly two years old, and this file's own rule forbids pinning an `-rc`. Alternative: ex_gram. Slice 071 decides with the measurement. |
136-
| `phoenix_streamdown` | **not pinned** | 🔍 not a single package | Streaming markdown renderer for LiveView. ⚠️ Pre-release, and this file's own rule forbids pinning an `-rc`; a beta is the same category. Verify at Slice 013; fallback: earmark or mdex with chunk buffering. |
136+
| `phoenix_streamdown` | not used (measured at Slice 013, 2026-09-20) | 🔍 not a single package | Streaming markdown renderer for LiveView. Measured at Slice 013: 1.0.0-beta.4 (2026-05-03) was still the latest release with no stable behind it, and this file's own rule forbids a pre-release. It is 801 lines over `mdex`, whose own `streaming: true` option completes fragments; the rest is a rendering rule Slice 013 keeps anyway. The row stays so the decision is visible where a reader would look for the package. |
137+
| `mdex` | ~> 0.13 | ✅ in `mix.lock` | Markdown renderer for the chat (`TrinityWeb.Markdown`), streaming fragments completed by its `streaming: true` option, raw HTML omitted (`unsafe: false`) and the default sanitizer on top. Added at Slice 013 after the measurement in its NOTES.md: `earmark` 1.4.49 is retired on hex with an open XSS advisory (EEF-CVE-2026-48591), which the gate refuses. ⚠️ A Rust NIF (`mdex_native`): the first in the bundle. Measured at Slice 013 (NOTES finding 13): neither precompiled artifact loads in Burrito's musl ERTS on Linux, so the linux package builds it from source for musl with Zig as the linker (`rustler` below, `scripts/zig-cc-musl`, the three settings in config/config.exs); macOS and Windows load the precompiled artifact. The `--smoke` path prints whether it rendered, and the `package` workflow reads that line on every target. |
138+
| `rustler` | ~> 0.38 | ✅ in `mix.lock` | Build time only (`runtime: false`): what `rustler_precompiled` needs to compile `mdex_native` from source when `MDEX_NATIVE_BUILD=1`, which the linux package sets (owner decision 2026-09-20, Slice 013 NOTES finding 14). Nothing in the tree calls it. |
137139

138140
### Dev and quality
139141

0 commit comments

Comments
 (0)