open-standards/joule-code/joule-code-rs/crates/joule-moq-web/web/room/messages.html
Transaction Science 02622a7fd1 joule-moq-web: a messaging view and a call flow beside the room page
web/room/messages.html: a member of a conversation in a page. It joins with a
proof of its own key, keys through the service's log with joule_moq_web_e2ee's
member (any member commits), seals each message before it is signed into its
receipt chain and posted, opens and verifies every message (wai_live_web's
Messages, live-messaging §11), shows threads, reactions, edits and
retractions, and acknowledges what it read.

web/room/call.html: a page's inbox, each invitation checked with its call
before it rings (callCheck); placing a call, ringing, accepting, declining and
cancelling; the call's media through the room page in a frame, which now takes
a grant handed to it (enter(role, { seed, answer })).

wai-live-web: the messaging records, receipts, acknowledgements, invitations
and answers a page signs, and Messages, verified against the corpus in its
tests. joule-moq-web: keyProofHttp, a proof naming no grant.

e2e/messaging.e2e.mjs (run by e2e/run.sh): the messaging scenario and the call
scenario.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-26 11:12:32 -04:00

73 lines
3.8 KiB
HTML

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Room messages</title>
<link rel="icon" href="data:,">
<style>
:root { --bg: #f6f7f9; --fg: #15171a; --muted: #5d6470; --panel: #ffffff; --line: #d9dde3; --mine: #e9f2ec; --accent: #1f6f43; }
@media (prefers-color-scheme: dark) { :root { --bg: #111317; --fg: #e8eaee; --muted: #9aa2ae; --panel: #1a1d23; --line: #2c313a; --mine: #1d2a22; --accent: #6fd39b; } }
* { box-sizing: border-box; }
[hidden] { display: none !important; }
body { margin: 0; padding: 16px; background: var(--bg); color: var(--fg); font: 14px/1.45 system-ui, sans-serif; }
header { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; margin-bottom: 12px; }
h1 { font-size: 16px; margin: 0; }
h2 { font-size: 13px; margin: 0 0 6px; color: var(--muted); font-weight: 600; }
#who, .key { color: var(--muted); font-family: ui-monospace, monospace; }
main { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 12px; }
@media (max-width: 760px) { main { grid-template-columns: minmax(0, 1fr); } }
section { background: var(--panel); border: 1px solid var(--line); border-radius: 6px; padding: 10px; min-width: 0; }
.controls { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 12px; }
#status { color: var(--muted); }
#messages { list-style: none; margin: 0; padding: 0; }
#messages > li { padding: 8px 0; border-bottom: 1px solid var(--line); overflow-wrap: anywhere; }
#messages li.mine > .body { background: var(--mine); border-radius: 4px; padding: 2px 6px; }
.thread { list-style: none; margin: 6px 0 0 18px; padding: 0 0 0 10px; border-left: 2px solid var(--line); }
.thread li { padding: 4px 0; }
.meta { color: var(--muted); font-size: 12px; }
.retracted { color: var(--muted); font-style: italic; }
.reactions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 2px; font-size: 12px; }
.reactions span { border: 1px solid var(--line); border-radius: 10px; padding: 0 6px; }
.acts { display: inline-flex; gap: 6px; margin-left: 6px; }
.acts button { font-size: 12px; padding: 0 6px; }
#compose { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
#compose input { flex: 1 1 220px; min-width: 0; }
#replying { color: var(--accent); font-size: 12px; }
#members { margin: 0; padding-left: 18px; font-family: ui-monospace, monospace; font-size: 12px; }
#log { white-space: pre-wrap; font-family: ui-monospace, monospace; font-size: 11px; color: var(--muted); max-height: 220px; overflow: auto; margin: 0; overflow-wrap: anywhere; }
</style>
</head>
<body>
<header><h1>Room messages</h1><span id="who">not joined</span></header>
<div class="controls">
<button id="join" type="button">Join the conversation</button>
<button id="leave" type="button" hidden>Leave</button>
<span id="status" role="status" aria-live="polite"></span>
</div>
<main>
<section>
<h2>Messages</h2>
<ul id="messages" aria-live="polite"></ul>
<form id="compose" hidden>
<span id="replying" hidden></span>
<label for="text" hidden>Message</label>
<input id="text" maxlength="1000" autocomplete="off" placeholder="Write a message">
<button type="submit">Send</button>
<button id="cancel" type="button" hidden>Cancel</button>
</form>
</section>
<section>
<h2>Members</h2>
<p id="epoch" class="meta"></p>
<ol id="members"></ol>
<p class="meta">End-to-end encrypted: every message is sealed on this page before it is sent, and the room service keeps only what it cannot open.</p>
</section>
<section style="grid-column: 1 / -1">
<h2>Log</h2>
<pre id="log"></pre>
</section>
</main>
<script type="module" src="./messages.js"></script>
</body>
</html>