@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 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 so the outer element's identity and document position survive (helpful for animations or external DOM references). The recycled element's inner content is rebuilt from the new vnode on each re-render, so static interpolations (`${value}`) reflect current state. 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.
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.2",
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>streamo chat</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 chat <span id="my-name"></span>
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
- if (!server.streamo.get('members')) {
22
- server.streamo.set({ ...(server.streamo.get() ?? {}), members: [] })
23
- console.log('[chat] initialized chat room')
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" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 16 16%22><text y=%2214%22 font-size=%2214%22>🌊</text></svg>">
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: baseline; gap: 0.75rem; margin-bottom: 0.25rem; }
13
- .wordmark { font-size: 1.6rem; letter-spacing: -0.02em; }
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
- <div class="wordmark">streamo</div>
348
- <div class="crumbs">explorer</div>
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>