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]>
73 lines
3.8 KiB
HTML
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>
|