@dtudury/streamo 4.0.2 → 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +1 -1
- package/public/apps/chat/index.html +19 -3
- package/public/apps/chat/server.js +23 -3
- package/public/apps/explorer/index.html +121 -5
- package/public/apps/explorer/main.js +469 -96
- package/public/index.html +329 -15
- package/public/streamo/mount.js +135 -21
- package/public/streamo.svg +24 -0
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/dtudury/streamo/main/public/streamo.svg" alt="streamo" width="140">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# streamo
|
|
2
6
|
|
|
3
7
|
> every device is an equal author
|
|
@@ -186,7 +190,7 @@ mount(h`
|
|
|
186
190
|
`, document.body, recaller)
|
|
187
191
|
```
|
|
188
192
|
|
|
189
|
-
Functions interpolated as `${() => ...}` are reactive cells — they re-run automatically whenever the data they read changes. Only the exact DOM nodes bound to changed data update. Elements with stable `data-key` are recycled across re-renders
|
|
193
|
+
Functions interpolated as `${() => ...}` are reactive cells — they re-run automatically whenever the data they read changes. Only the exact DOM nodes bound to changed data update. Elements with stable `data-key` are recycled across re-renders, and their descendants are reconciled in place by recursive data-key/tag matching — so DOM identity, document position, scroll state, focus, and any external attachments survive on every level. Static interpolations (`${value}`) refresh to the current value on each re-render. SVG namespaces propagate automatically — `` h`<svg><path d="..."/></svg>` `` works without any extra wiring. `class` accepts an array (`['btn', isActive && 'active']`) or an object (`{btn: true, active: false}`); falsy entries are filtered out.
|
|
190
194
|
|
|
191
195
|
> **For lists that can reorder**, always set `data-key` on each item — the unkeyed positional fallback will recycle elements by tag in document order, which can attach the wrong DOM node (and any user focus/input on it) to the wrong vnode after a reorder.
|
|
192
196
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dtudury/streamo",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.5",
|
|
4
4
|
"description": "peer-to-peer sync where your data and identity belong to you, not the server",
|
|
5
5
|
"keywords": ["p2p", "peer-to-peer", "sync", "reactive", "content-addressed", "websocket", "signed", "append-only", "offline-first", "cryptographic", "identity"],
|
|
6
6
|
"repository": "git@github.com:dtudury/streamo.git",
|
|
@@ -4,14 +4,25 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>streamo chat</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/streamo.svg">
|
|
7
8
|
<style>
|
|
8
9
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0 }
|
|
9
10
|
:root { font-family: system-ui, sans-serif; font-size: 15px; --bg: #f5f5f5; --surface: #fff; --accent: #0070f3; --border: #ddd }
|
|
10
11
|
body { background: var(--bg); height: 100dvh; display: flex; align-items: center; justify-content: center }
|
|
11
12
|
|
|
13
|
+
/* Brand lockup: clickable mark + wordmark linking home, with a
|
|
14
|
+
lighter page-title beside it. Same pattern in login and chat
|
|
15
|
+
headers so the relationship reads consistently. */
|
|
16
|
+
.brand-lockup { display: inline-flex; align-items: center; gap: .4rem; color: inherit; text-decoration: none; font-weight: 600 }
|
|
17
|
+
.brand-lockup:hover { opacity: .8 }
|
|
18
|
+
.page-title { font-weight: 400; color: #888; letter-spacing: .04em }
|
|
19
|
+
.page-title::before { content: '· '; opacity: .5 }
|
|
20
|
+
|
|
12
21
|
/* Login */
|
|
13
22
|
#login { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 2rem; width: min(360px, 90vw); display: flex; flex-direction: column; gap: .75rem }
|
|
14
|
-
#login h1 { font-size: 1.2rem; font-weight: 600 }
|
|
23
|
+
#login h1 { font-size: 1.2rem; font-weight: 600; display: flex; align-items: center; gap: .4rem }
|
|
24
|
+
#login h1 img { width: 1.4rem; height: 1.4rem }
|
|
25
|
+
#chat-header img { width: 1.1rem; height: 1.1rem }
|
|
15
26
|
#login input { border: 1px solid var(--border); border-radius: 6px; padding: .5rem .75rem; font-size: 1rem; width: 100% }
|
|
16
27
|
#login button { background: var(--accent); color: #fff; border: none; border-radius: 6px; padding: .6rem; font-size: 1rem; cursor: pointer }
|
|
17
28
|
#login button:hover { opacity: .85 }
|
|
@@ -38,7 +49,10 @@
|
|
|
38
49
|
<body>
|
|
39
50
|
|
|
40
51
|
<div id="login">
|
|
41
|
-
<h1>
|
|
52
|
+
<h1>
|
|
53
|
+
<a class="brand-lockup" href="/" title="streamo home"><img src="/streamo.svg" alt="">streamo</a>
|
|
54
|
+
<span class="page-title">chat</span>
|
|
55
|
+
</h1>
|
|
42
56
|
<input id="username" placeholder="username" autocomplete="username">
|
|
43
57
|
<input id="password" type="password" placeholder="password" autocomplete="current-password">
|
|
44
58
|
<button id="join-btn">join</button>
|
|
@@ -47,7 +61,9 @@
|
|
|
47
61
|
|
|
48
62
|
<div id="chat">
|
|
49
63
|
<div id="chat-header">
|
|
50
|
-
streamo
|
|
64
|
+
<a class="brand-lockup" href="/" title="streamo home"><img src="/streamo.svg" alt="">streamo</a>
|
|
65
|
+
<span class="page-title">chat</span>
|
|
66
|
+
<span id="my-name"></span>
|
|
51
67
|
</div>
|
|
52
68
|
<div id="messages"></div>
|
|
53
69
|
<div id="input-row">
|
|
@@ -18,9 +18,29 @@ const server = await StreamoServer.create({ name, username, password, dataDir, k
|
|
|
18
18
|
console.log(`[chat] room key: ${server.publicKeyHex}`)
|
|
19
19
|
console.log(`[chat] serving on http://localhost:${port}/apps/chat/`)
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
// Seed the primary repo with chat-room bookkeeping AND the journal —
|
|
22
|
+
// the home repo doubles as the homepage's content source. Each future
|
|
23
|
+
// journal entry is a new commit on this repo, and the homepage walks
|
|
24
|
+
// `entries` to render. The relay link in the explorer now points
|
|
25
|
+
// somewhere meaningful: the journal you just read on the homepage.
|
|
26
|
+
{
|
|
27
|
+
const current = server.streamo.get() ?? {}
|
|
28
|
+
const seed = { ...current }
|
|
29
|
+
let needsCommit = false
|
|
30
|
+
if (!seed.members) { seed.members = []; needsCommit = true }
|
|
31
|
+
if (!Array.isArray(seed.entries) || seed.entries.length === 0) {
|
|
32
|
+
seed.entries = [{
|
|
33
|
+
headline: 'running streamo',
|
|
34
|
+
body: 'this is the streamo journal. each entry is a signed commit on this repo; the homepage walks them and the relay link in the explorer leads here. append-only history made visible.',
|
|
35
|
+
at: new Date()
|
|
36
|
+
}]
|
|
37
|
+
needsCommit = true
|
|
38
|
+
}
|
|
39
|
+
if (needsCommit) {
|
|
40
|
+
server.streamo.defaultMessage = seed.entries[seed.entries.length - 1].headline
|
|
41
|
+
server.streamo.set(seed)
|
|
42
|
+
console.log('[chat] initialized chat room + journal seed')
|
|
43
|
+
}
|
|
24
44
|
}
|
|
25
45
|
|
|
26
46
|
await server.web(port, {
|
|
@@ -4,13 +4,36 @@
|
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
6
|
<title>streamo explorer</title>
|
|
7
|
-
<link rel="icon"
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/streamo.svg">
|
|
8
8
|
<link rel="stylesheet" href="/apps/styles/proto.css">
|
|
9
9
|
<style>
|
|
10
|
+
/* scrollbar-gutter reserves the scrollbar's width whether or not it's
|
|
11
|
+
drawn, so a sudden scroll-needed (e.g. when the value pane grows
|
|
12
|
+
under hover preview) doesn't shift everything left by ~15px. */
|
|
13
|
+
html { scrollbar-gutter: stable; }
|
|
10
14
|
body { max-width: 60rem; margin: 0 auto; padding: 2rem 1.25rem; }
|
|
11
15
|
|
|
12
|
-
.header { display: flex; align-items:
|
|
13
|
-
|
|
16
|
+
.header { display: flex; align-items: center; gap: 0.6rem; margin-bottom: 0.25rem; }
|
|
17
|
+
/* Brand lockup: mark + wordmark, single clickable unit linking home.
|
|
18
|
+
Page title ("explorer") sits beside it as a separate, lighter
|
|
19
|
+
element so the relationship reads as [home] · [you-are-here]. */
|
|
20
|
+
.brand-lockup {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
gap: 0.5rem;
|
|
24
|
+
font-size: 1.6rem;
|
|
25
|
+
letter-spacing: -0.02em;
|
|
26
|
+
color: var(--ink);
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
}
|
|
29
|
+
.brand-lockup img { width: 1.8rem; height: 1.8rem; }
|
|
30
|
+
.brand-lockup:hover { opacity: 0.8; }
|
|
31
|
+
.page-title {
|
|
32
|
+
font-size: 0.95rem;
|
|
33
|
+
color: var(--ink-dim);
|
|
34
|
+
letter-spacing: 0.04em;
|
|
35
|
+
}
|
|
36
|
+
.page-title::before { content: '· '; opacity: 0.5; }
|
|
14
37
|
.crumbs { font-size: 0.85rem; color: var(--ink-dim); }
|
|
15
38
|
.back { cursor: pointer; color: var(--ink-dim); font-size: 0.85rem; display: inline-block; margin-bottom: 1rem; }
|
|
16
39
|
.back:hover { color: var(--ink); }
|
|
@@ -221,6 +244,19 @@
|
|
|
221
244
|
}
|
|
222
245
|
.repo-link:hover { background: var(--flash); text-decoration-style: solid; }
|
|
223
246
|
|
|
247
|
+
/* Sticky at-view header: selector + strip + tabs travel with you as
|
|
248
|
+
you scroll long value trees or storage detail. Background-cover so
|
|
249
|
+
content scrolling underneath doesn't bleed through. */
|
|
250
|
+
.atview-header {
|
|
251
|
+
position: sticky;
|
|
252
|
+
top: 0;
|
|
253
|
+
z-index: 10;
|
|
254
|
+
background: var(--bg, #fefdf8);
|
|
255
|
+
padding-top: 0.25rem;
|
|
256
|
+
border-bottom: 1px solid var(--rule);
|
|
257
|
+
margin-bottom: 0.75rem;
|
|
258
|
+
}
|
|
259
|
+
|
|
224
260
|
/* Byte stream — zoomed strip in a horizontally-scrollable container,
|
|
225
261
|
click-drag-to-pan inside for "look around" navigation. */
|
|
226
262
|
.byte-strip-container {
|
|
@@ -236,6 +272,38 @@
|
|
|
236
272
|
.byte-strip-container.dragging .chunk { cursor: grabbing; }
|
|
237
273
|
.byte-strip { display: block; }
|
|
238
274
|
|
|
275
|
+
/* Sig-coverage overlay: when hovering a sig anywhere on the page,
|
|
276
|
+
this rect is positioned over its [signedFrom, signedTo] byte range
|
|
277
|
+
on the strip. Subtle dashed band — doesn't fight the chunk colors. */
|
|
278
|
+
.byte-strip .sig-coverage {
|
|
279
|
+
fill: rgba(239, 68, 68, 0.12);
|
|
280
|
+
stroke: rgba(239, 68, 68, 0.6);
|
|
281
|
+
stroke-width: 1.5;
|
|
282
|
+
stroke-dasharray: 4 3;
|
|
283
|
+
opacity: 0;
|
|
284
|
+
transition: opacity 0.08s;
|
|
285
|
+
}
|
|
286
|
+
.byte-strip .sig-coverage.active { opacity: 1; }
|
|
287
|
+
|
|
288
|
+
/* Persistent context line for the at-view's current chunk: codec,
|
|
289
|
+
address, length, percentage. Quiet by default (it's permanent,
|
|
290
|
+
not the focus), lights up when something else on the page is
|
|
291
|
+
hovered to show that chunk instead. Reverts via data-default
|
|
292
|
+
on mouseout. */
|
|
293
|
+
.chunk-inspector {
|
|
294
|
+
font-family: monospace;
|
|
295
|
+
font-size: 0.8rem;
|
|
296
|
+
color: var(--ink-dim);
|
|
297
|
+
padding: 0.25rem 0.5rem;
|
|
298
|
+
margin: 0.25rem 0 0.75rem;
|
|
299
|
+
border-radius: var(--radius);
|
|
300
|
+
transition: color 0.08s, background 0.08s;
|
|
301
|
+
}
|
|
302
|
+
.chunk-inspector.active {
|
|
303
|
+
color: var(--ink);
|
|
304
|
+
background: var(--flash);
|
|
305
|
+
}
|
|
306
|
+
|
|
239
307
|
.byte-map {
|
|
240
308
|
display: block;
|
|
241
309
|
}
|
|
@@ -276,6 +344,52 @@
|
|
|
276
344
|
.tv-array, .tv-object { color: #1e40af; background: rgba(59, 130, 246, 0.10); }
|
|
277
345
|
.tv-duple { color: #6b21a8; background: rgba(168, 85, 247, 0.10); }
|
|
278
346
|
|
|
347
|
+
/* Recursive typed-value tree — used for rehydrated views. Outer levels
|
|
348
|
+
expand inline; un-expanded composites become drillable chips. */
|
|
349
|
+
.tv-tree {
|
|
350
|
+
font-size: 0.85rem;
|
|
351
|
+
line-height: 1.7;
|
|
352
|
+
font-family: monospace;
|
|
353
|
+
margin: 0.4rem 0;
|
|
354
|
+
}
|
|
355
|
+
.tv-tree-row {
|
|
356
|
+
padding-left: 1.5rem;
|
|
357
|
+
}
|
|
358
|
+
.tv-tree .tv-bracket {
|
|
359
|
+
color: var(--ink-dim);
|
|
360
|
+
font-weight: 600;
|
|
361
|
+
}
|
|
362
|
+
.tv-tree .tv-bracket.clickable {
|
|
363
|
+
cursor: pointer;
|
|
364
|
+
}
|
|
365
|
+
.tv-tree .tv-bracket.clickable:hover {
|
|
366
|
+
background: var(--flash);
|
|
367
|
+
color: var(--ink);
|
|
368
|
+
}
|
|
369
|
+
.tv-tree .tv-key {
|
|
370
|
+
color: var(--ink-dim);
|
|
371
|
+
margin-right: 0.4rem;
|
|
372
|
+
}
|
|
373
|
+
.tv-drill {
|
|
374
|
+
cursor: pointer;
|
|
375
|
+
text-decoration: underline dotted var(--ink-dim);
|
|
376
|
+
}
|
|
377
|
+
.tv-drill:hover { background: var(--flash); text-decoration-style: solid; }
|
|
378
|
+
|
|
379
|
+
/* codec-tag — colored codec name in the refs/referrers tables. Same
|
|
380
|
+
palette as the byte-strip and the typed-value pills, so a chunk's
|
|
381
|
+
codec reads visually consistent everywhere it appears. */
|
|
382
|
+
.codec-tag { font-weight: 500; }
|
|
383
|
+
.codec-tag.codec-commit { color: #c2410c; }
|
|
384
|
+
.codec-tag.codec-sig { color: #b91c1c; }
|
|
385
|
+
.codec-tag.codec-composite { color: #1e40af; }
|
|
386
|
+
.codec-tag.codec-duple { color: #6b21a8; }
|
|
387
|
+
.codec-tag.codec-string { color: #047857; }
|
|
388
|
+
.codec-tag.codec-bytes { color: #4d7c0f; }
|
|
389
|
+
.codec-tag.codec-num { color: #475569; }
|
|
390
|
+
.codec-tag.codec-var { color: #b45309; }
|
|
391
|
+
.codec-tag.codec-other { color: var(--ink-dim); }
|
|
392
|
+
|
|
279
393
|
/* codec category palette — used in both the legend and the SVG fills */
|
|
280
394
|
.cat-commit { fill: #f59e0b; background: #f59e0b; }
|
|
281
395
|
.cat-sig { fill: #ef4444; background: #ef4444; }
|
|
@@ -344,8 +458,10 @@
|
|
|
344
458
|
</head>
|
|
345
459
|
<body>
|
|
346
460
|
<div class="header">
|
|
347
|
-
<
|
|
348
|
-
|
|
461
|
+
<a class="brand-lockup" href="/" title="streamo home">
|
|
462
|
+
<img src="/streamo.svg" alt="">streamo
|
|
463
|
+
</a>
|
|
464
|
+
<span class="page-title">explorer</span>
|
|
349
465
|
</div>
|
|
350
466
|
<div id="conn" class="conn">connecting…</div>
|
|
351
467
|
<div id="app"></div>
|