@bongos/core 1.19.345 → 1.19.347

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.
@@ -3,17 +3,17 @@
3
3
  "generator": "scripts/gds/copy-inventory.js",
4
4
  "goal": 1000074,
5
5
  "counts": {
6
- "strings": 1385,
7
- "unique_ids": 1228,
8
- "certain": 1385,
6
+ "strings": 1397,
7
+ "unique_ids": 1238,
8
+ "certain": 1397,
9
9
  "heuristic": 0,
10
- "duplicate_ids": 127
10
+ "duplicate_ids": 129
11
11
  },
12
12
  "surfaces": {
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
- "files": 67,
16
- "strings": 1096
15
+ "files": 70,
16
+ "strings": 1108
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -191,7 +191,7 @@
191
191
  "at": [
192
192
  "modules/hall-ui/public/hall-render.js:252",
193
193
  "modules/hall-ui/public/hall-render.js:355",
194
- "modules/hall-ui/public/shell.js:273"
194
+ "modules/hall-ui/public/shell.js:277"
195
195
  ]
196
196
  },
197
197
  {
@@ -338,6 +338,16 @@
338
338
  "modules/hall-ui/public/watch.html:31"
339
339
  ]
340
340
  },
341
+ {
342
+ "id": "b2656d0b2402",
343
+ "surface": "builders-hall",
344
+ "text": "Asked {…}",
345
+ "occurrences": 2,
346
+ "at": [
347
+ "modules/hall-ui/public/collab.js:36",
348
+ "modules/hall-ui/public/watch.js:205"
349
+ ]
350
+ },
341
351
  {
342
352
  "id": "08afb1257b71",
343
353
  "surface": "builders-hall",
@@ -398,6 +408,16 @@
398
408
  "modules/hall-ui/public/box-panel.js:370"
399
409
  ]
400
410
  },
411
+ {
412
+ "id": "c72c97dc33fe",
413
+ "surface": "builders-hall",
414
+ "text": "Collab — {{worldName}}",
415
+ "occurrences": 2,
416
+ "at": [
417
+ "modules/hall-ui/public/collab.html:6",
418
+ "modules/hall-ui/public/collab.html:6"
419
+ ]
420
+ },
401
421
  {
402
422
  "id": "9b16a22bf24c",
403
423
  "surface": "builders-hall",
@@ -1064,8 +1084,8 @@
1064
1084
  "text": "Sign out",
1065
1085
  "occurrences": 2,
1066
1086
  "at": [
1067
- "modules/hall-ui/public/shell.js:291",
1068
- "modules/hall-ui/public/shell.js:426"
1087
+ "modules/hall-ui/public/shell.js:295",
1088
+ "modules/hall-ui/public/shell.js:430"
1069
1089
  ]
1070
1090
  },
1071
1091
  {
@@ -2096,6 +2116,24 @@
2096
2116
  "origin": "js-markup",
2097
2117
  "confidence": "certain"
2098
2118
  },
2119
+ {
2120
+ "id": "688cc6dd38ee",
2121
+ "surface": "builders-hall",
2122
+ "text": "Asked of you",
2123
+ "file": "modules/hall-ui/public/collab.html",
2124
+ "line": 32,
2125
+ "origin": "html-text",
2126
+ "confidence": "certain"
2127
+ },
2128
+ {
2129
+ "id": "b2656d0b2402",
2130
+ "surface": "builders-hall",
2131
+ "text": "Asked {…}",
2132
+ "file": "modules/hall-ui/public/collab.js",
2133
+ "line": 36,
2134
+ "origin": "js-markup",
2135
+ "confidence": "certain"
2136
+ },
2099
2137
  {
2100
2138
  "id": "b2656d0b2402",
2101
2139
  "surface": "builders-hall",
@@ -2708,6 +2746,24 @@
2708
2746
  "origin": "html-attr:aria-label",
2709
2747
  "confidence": "certain"
2710
2748
  },
2749
+ {
2750
+ "id": "c72c97dc33fe",
2751
+ "surface": "builders-hall",
2752
+ "text": "Collab — {{worldName}}",
2753
+ "file": "modules/hall-ui/public/collab.html",
2754
+ "line": 6,
2755
+ "origin": "html-text",
2756
+ "confidence": "certain"
2757
+ },
2758
+ {
2759
+ "id": "c72c97dc33fe",
2760
+ "surface": "builders-hall",
2761
+ "text": "Collab — {{worldName}}",
2762
+ "file": "modules/hall-ui/public/collab.html",
2763
+ "line": 6,
2764
+ "origin": "html-title",
2765
+ "confidence": "certain"
2766
+ },
2711
2767
  {
2712
2768
  "id": "2867c85d196d",
2713
2769
  "surface": "builders-hall",
@@ -3113,6 +3169,15 @@
3113
3169
  "origin": "js-text-assign",
3114
3170
  "confidence": "certain"
3115
3171
  },
3172
+ {
3173
+ "id": "8a4b590fa1a0",
3174
+ "surface": "builders-hall",
3175
+ "text": "Could not load requests.",
3176
+ "file": "modules/hall-ui/public/collab.js",
3177
+ "line": 137,
3178
+ "origin": "js-text-assign",
3179
+ "confidence": "certain"
3180
+ },
3116
3181
  {
3117
3182
  "id": "7775fec0fa85",
3118
3183
  "surface": "builders-hall",
@@ -4882,7 +4947,7 @@
4882
4947
  "surface": "builders-hall",
4883
4948
  "text": "Jump to a page or record",
4884
4949
  "file": "modules/hall-ui/public/shell.js",
4885
- "line": 312,
4950
+ "line": 316,
4886
4951
  "origin": "js-markup",
4887
4952
  "confidence": "certain"
4888
4953
  },
@@ -4891,7 +4956,7 @@
4891
4956
  "surface": "builders-hall",
4892
4957
  "text": "Jump to…",
4893
4958
  "file": "modules/hall-ui/public/shell.js",
4894
- "line": 314,
4959
+ "line": 318,
4895
4960
  "origin": "js-markup",
4896
4961
  "confidence": "certain"
4897
4962
  },
@@ -5957,6 +6022,15 @@
5957
6022
  "origin": "js-markup",
5958
6023
  "confidence": "certain"
5959
6024
  },
6025
+ {
6026
+ "id": "2c6e0dfbd781",
6027
+ "surface": "builders-hall",
6028
+ "text": "Nothing is waiting on you. Requests reach you by your craft — set yours in",
6029
+ "file": "modules/hall-ui/public/collab.html",
6030
+ "line": 38,
6031
+ "origin": "html-text",
6032
+ "confidence": "certain"
6033
+ },
5960
6034
  {
5961
6035
  "id": "18ce0723be0d",
5962
6036
  "surface": "builders-hall",
@@ -6074,6 +6148,15 @@
6074
6148
  "origin": "js-notice",
6075
6149
  "confidence": "certain"
6076
6150
  },
6151
+ {
6152
+ "id": "0c471e06cfc1",
6153
+ "surface": "builders-hall",
6154
+ "text": "Only you can settle your own request — mark it answered when someone helped, or withdraw it when you no longer need it.",
6155
+ "file": "modules/hall-ui/public/collab.html",
6156
+ "line": 46,
6157
+ "origin": "html-text",
6158
+ "confidence": "certain"
6159
+ },
6077
6160
  {
6078
6161
  "id": "6912678b962b",
6079
6162
  "surface": "builders-hall",
@@ -6106,7 +6189,7 @@
6106
6189
  "surface": "builders-hall",
6107
6190
  "text": "Open navigation",
6108
6191
  "file": "modules/hall-ui/public/shell.js",
6109
- "line": 303,
6192
+ "line": 307,
6110
6193
  "origin": "js-markup",
6111
6194
  "confidence": "certain"
6112
6195
  },
@@ -6965,6 +7048,15 @@
6965
7048
  "origin": "js-notice",
6966
7049
  "confidence": "certain"
6967
7050
  },
7051
+ {
7052
+ "id": "61f862ffb9eb",
7053
+ "surface": "builders-hall",
7054
+ "text": "Requests addressed to your craft, or to you by name. Answering is optional and declining costs nothing — nobody is assigned by being asked.",
7055
+ "file": "modules/hall-ui/public/collab.html",
7056
+ "line": 33,
7057
+ "origin": "html-text",
7058
+ "confidence": "certain"
7059
+ },
6968
7060
  {
6969
7061
  "id": "a030881168b5",
6970
7062
  "surface": "builders-hall",
@@ -7510,7 +7602,7 @@
7510
7602
  "surface": "builders-hall",
7511
7603
  "text": "Sign in with GitHub",
7512
7604
  "file": "modules/hall-ui/public/shell.js",
7513
- "line": 273,
7605
+ "line": 277,
7514
7606
  "origin": "js-markup",
7515
7607
  "confidence": "certain"
7516
7608
  },
@@ -7519,7 +7611,7 @@
7519
7611
  "surface": "builders-hall",
7520
7612
  "text": "Sign out",
7521
7613
  "file": "modules/hall-ui/public/shell.js",
7522
- "line": 291,
7614
+ "line": 295,
7523
7615
  "origin": "js-markup",
7524
7616
  "confidence": "certain"
7525
7617
  },
@@ -7528,7 +7620,7 @@
7528
7620
  "surface": "builders-hall",
7529
7621
  "text": "Sign out",
7530
7622
  "file": "modules/hall-ui/public/shell.js",
7531
- "line": 426,
7623
+ "line": 430,
7532
7624
  "origin": "js-markup",
7533
7625
  "confidence": "certain"
7534
7626
  },
@@ -7559,6 +7651,15 @@
7559
7651
  "origin": "js-markup",
7560
7652
  "confidence": "certain"
7561
7653
  },
7654
+ {
7655
+ "id": "7da6a848cd26",
7656
+ "surface": "builders-hall",
7657
+ "text": "Someone helped",
7658
+ "file": "modules/hall-ui/public/collab.js",
7659
+ "line": 65,
7660
+ "origin": "js-markup",
7661
+ "confidence": "certain"
7662
+ },
7562
7663
  {
7563
7664
  "id": "867df58c9aaa",
7564
7665
  "surface": "builders-hall",
@@ -7690,7 +7791,7 @@
7690
7791
  "surface": "builders-hall",
7691
7792
  "text": "Switch theme",
7692
7793
  "file": "modules/hall-ui/public/shell.js",
7693
- "line": 318,
7794
+ "line": 322,
7694
7795
  "origin": "js-markup",
7695
7796
  "confidence": "certain"
7696
7797
  },
@@ -7825,7 +7926,7 @@
7825
7926
  "surface": "builders-hall",
7826
7927
  "text": "Text size",
7827
7928
  "file": "modules/hall-ui/public/shell.js",
7828
- "line": 317,
7929
+ "line": 321,
7829
7930
  "origin": "js-markup",
7830
7931
  "confidence": "certain"
7831
7932
  },
@@ -8945,6 +9046,15 @@
8945
9046
  "origin": "js-notice",
8946
9047
  "confidence": "certain"
8947
9048
  },
9049
+ {
9050
+ "id": "da274462bb30",
9051
+ "surface": "builders-hall",
9052
+ "text": "You have no open asks.",
9053
+ "file": "modules/hall-ui/public/collab.html",
9054
+ "line": 51,
9055
+ "origin": "html-text",
9056
+ "confidence": "certain"
9057
+ },
8948
9058
  {
8949
9059
  "id": "3e5b3271bdb4",
8950
9060
  "surface": "builders-hall",
@@ -8999,6 +9109,15 @@
8999
9109
  "origin": "html-text",
9000
9110
  "confidence": "certain"
9001
9111
  },
9112
+ {
9113
+ "id": "7d26333b5fb0",
9114
+ "surface": "builders-hall",
9115
+ "text": "Your asks",
9116
+ "file": "modules/hall-ui/public/collab.html",
9117
+ "line": 45,
9118
+ "origin": "html-text",
9119
+ "confidence": "certain"
9120
+ },
9002
9121
  {
9003
9122
  "id": "59a66b5ac5dd",
9004
9123
  "surface": "builders-hall",
@@ -9701,6 +9820,15 @@
9701
9820
  "origin": "js-markup",
9702
9821
  "confidence": "certain"
9703
9822
  },
9823
+ {
9824
+ "id": "2123a62d69e3",
9825
+ "surface": "builders-hall",
9826
+ "text": "if this stays empty.",
9827
+ "file": "modules/hall-ui/public/collab.html",
9828
+ "line": 40,
9829
+ "origin": "html-text",
9830
+ "confidence": "certain"
9831
+ },
9704
9832
  {
9705
9833
  "id": "b2b399fdd6a4",
9706
9834
  "surface": "builders-hall",
@@ -0,0 +1,77 @@
1
+ // modules/hall-ui/public/collab-lib.js — the Collab page's PURE helpers, exposed
2
+ // as window.OTBCollabLib (task 1003172 / C02, goal 1000075).
3
+ //
4
+ // Same split as goals-lib.js: everything here is a pure function of its
5
+ // arguments, so the rules can be executed by a test instead of grepped out of a
6
+ // DOM file. Loaded by collab.html BEFORE collab.js.
7
+ //
8
+ // THE RULE THAT MATTERS IS THE AGEING ONE. The criterion says a request nobody
9
+ // takes must be "visibly aging rather than silently dead" — so age is a BAND with
10
+ // its own label and class, not a sort order. Sorting alone fails the criterion:
11
+ // the oldest request looks identical to the newest, just lower down, and "nobody
12
+ // answered this for nine days" is exactly the fact the page exists to make
13
+ // impossible to miss.
14
+ (function () {
15
+ 'use strict';
16
+
17
+ // Bands, oldest-first so the first match wins. Deliberately coarse: an exact
18
+ // hour count invites reading precision into a signal that only needs to say
19
+ // "this has been sitting a while".
20
+ var BANDS = [
21
+ { id: 'stale', minDays: 7, label: 'waiting over a week', severity: 3 },
22
+ { id: 'aging', minDays: 3, label: 'waiting a few days', severity: 2 },
23
+ { id: 'waiting', minDays: 1, label: 'waiting a day', severity: 1 },
24
+ { id: 'fresh', minDays: 0, label: 'just asked', severity: 0 },
25
+ ];
26
+
27
+ var DAY_MS = 24 * 60 * 60 * 1000;
28
+
29
+ // ageBand(createdAt, nowMs) -> band. `nowMs` is injected so this is testable
30
+ // without a clock, and so a test can assert the boundaries rather than trust
31
+ // them. An unparseable date reads as 'fresh': an unknown age must never be
32
+ // rendered as an accusation.
33
+ function ageBand(createdAt, nowMs) {
34
+ var then = createdAt ? Date.parse(createdAt) : NaN;
35
+ var now = typeof nowMs === 'number' ? nowMs : Date.now();
36
+ if (isNaN(then)) return BANDS[BANDS.length - 1];
37
+ var days = (now - then) / DAY_MS;
38
+ for (var i = 0; i < BANDS.length; i += 1) {
39
+ if (days >= BANDS[i].minDays) return BANDS[i];
40
+ }
41
+ return BANDS[BANDS.length - 1];
42
+ }
43
+
44
+ // Oldest first WITHIN the page, because the thing most at risk of dying
45
+ // silently should be read first. This is in ADDITION to the band, never
46
+ // instead of it.
47
+ function byAgeOldestFirst(a, b) {
48
+ var ta = a && a.created_at ? Date.parse(a.created_at) : 0;
49
+ var tb = b && b.created_at ? Date.parse(b.created_at) : 0;
50
+ return (ta || 0) - (tb || 0);
51
+ }
52
+
53
+ // Who a request is addressed to, in words. A craft request and a by-name
54
+ // request read differently on purpose: "asked of builders" is a call anyone of
55
+ // that craft may take, "asked of you" is personal and should feel it.
56
+ function addressedTo(req, viewerId) {
57
+ if (req && req.needs_builder_id != null) {
58
+ return String(req.needs_builder_id) === String(viewerId) ? 'asked of you by name' : 'asked of one person';
59
+ }
60
+ if (req && req.needs_craft) return 'asked of ' + req.needs_craft + 's';
61
+ return 'asked';
62
+ }
63
+
64
+ // What the request is about, if anything. Returns null rather than an empty
65
+ // string so a caller can skip the element entirely.
66
+ function contextOf(req) {
67
+ if (!req) return null;
68
+ if (req.task_id != null) return { kind: 'task', id: req.task_id, href: '/builders/task/' + req.task_id };
69
+ if (req.goal_id != null) return { kind: 'goal', id: req.goal_id, href: '/builders/goals#/goal/' + req.goal_id };
70
+ return null;
71
+ }
72
+
73
+ window.OTBCollabLib = {
74
+ BANDS: BANDS, ageBand: ageBand, byAgeOldestFirst: byAgeOldestFirst,
75
+ addressedTo: addressedTo, contextOf: contextOf,
76
+ };
77
+ })();
@@ -0,0 +1,36 @@
1
+ /* modules/hall-ui/public/collab.css — the Collab page (task 1003172 / C02).
2
+ Tokens only from style.css's real set: inventing one renders as nothing, which
3
+ is how copy-desk.css ended up with invisible fallbacks (idea 1000821). */
4
+
5
+ .cb-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
6
+
7
+ .cb-item {
8
+ border: 1px solid var(--rule);
9
+ border-left: 3px solid var(--rule);
10
+ border-radius: var(--radius-sm);
11
+ background: var(--surface);
12
+ padding: 10px 12px;
13
+ }
14
+
15
+ .cb-item__stuck { margin: 0; color: var(--ink); font-size: 14px; line-height: 1.5; white-space: pre-wrap; }
16
+ .cb-item__meta { margin: 6px 0 0; display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-size: 12px; }
17
+ .cb-item__actions { margin: 8px 0 0; display: flex; gap: 8px; }
18
+ .cb-who { color: var(--ink-soft); }
19
+ .cb-ctx { color: var(--accent); text-decoration: none; }
20
+ .cb-ctx:hover { text-decoration: underline; }
21
+ .cb-empty { color: var(--ink-faint); font-size: 13px; margin: 10px 0 0; }
22
+
23
+ /* THE AGEING SIGNAL. The criterion asks that a request nobody takes be visibly
24
+ aging rather than silently dead — so age changes how a row LOOKS, not only
25
+ where it sits. The left border carries it (a band of colour that reads at a
26
+ glance down a list) and the chip says it in words, because colour alone is not
27
+ a message and not everyone sees it. */
28
+ .cb-age { color: var(--ink-faint); font-size: 11.5px; letter-spacing: 0.01em; }
29
+ .cb-item--fresh { border-left-color: var(--rule); }
30
+ .cb-item--waiting { border-left-color: var(--ink-faint); }
31
+ .cb-item--aging { border-left-color: var(--warn); }
32
+ .cb-item--stale { border-left-color: var(--danger); }
33
+ .cb-item--aging .cb-age { color: var(--warn); }
34
+ .cb-item--stale .cb-age { color: var(--danger); font-weight: 600; }
35
+
36
+ .btn--quiet { background: transparent; color: var(--ink-soft); border-color: var(--rule); }
@@ -0,0 +1,66 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Collab — {{worldName}}</title>
7
+ <meta name="description" content="Requests for help addressed to your craft or to you by name, and the asks you have open — with how long each has been waiting.">
8
+ <link rel="icon" href="data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text x='16' y='24' font-size='24' text-anchor='middle' font-family='sans-serif'>⌂</text></svg>">
9
+ <link rel="preconnect" href="https://fonts.googleapis.com">
10
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
12
+ <link rel="stylesheet" href="/builders/style.css?v=2026-07-02-shell1">
13
+ <link rel="stylesheet" href="/builders/collab.css">
14
+ </head>
15
+ <body data-page="collab" data-page-title="Collab">
16
+
17
+ <div class="app">
18
+ <aside class="app-sidebar" id="app-sidebar" aria-label="Primary"></aside>
19
+ <div class="app-scrim" id="app-scrim" hidden></div>
20
+ <div class="app-body">
21
+ <header class="app-topbar" id="app-topbar"></header>
22
+ <div class="app-content">
23
+ <div class="page-head">
24
+ <p class="page-head__sub" id="cb-sub">Loading…</p>
25
+ </div>
26
+
27
+ <main>
28
+ <!-- ASKED OF YOU comes first: it is the half where someone is waiting on a
29
+ human. Your own asks are below, because they are a status check, not a
30
+ call to act. -->
31
+ <section class="scroll" aria-labelledby="cb-for-me-h">
32
+ <h2 class="scroll__h" id="cb-for-me-h">Asked of you</h2>
33
+ <p class="scroll__note" id="cb-for-me-note">
34
+ Requests addressed to your craft, or to you by name. Answering is optional
35
+ and declining costs nothing — nobody is assigned by being asked.
36
+ </p>
37
+ <ul class="cb-list" id="cb-for-me"></ul>
38
+ <p class="cb-empty" id="cb-for-me-empty" hidden>
39
+ Nothing is waiting on you. Requests reach you by your craft — set yours in
40
+ <a href="/builders/settings">Settings</a> if this stays empty.
41
+ </p>
42
+ </section>
43
+
44
+ <section class="scroll" aria-labelledby="cb-mine-h">
45
+ <h2 class="scroll__h" id="cb-mine-h">Your asks</h2>
46
+ <p class="scroll__note">
47
+ Only you can settle your own request — mark it answered when someone
48
+ helped, or withdraw it when you no longer need it.
49
+ </p>
50
+ <ul class="cb-list" id="cb-mine"></ul>
51
+ <p class="cb-empty" id="cb-mine-empty" hidden>You have no open asks.</p>
52
+ </section>
53
+ </main>
54
+ </div>
55
+ </div>
56
+ </div>
57
+
58
+ <div id="toast" class="toast" role="status" aria-live="polite" data-visible="0"></div>
59
+
60
+ <script src="/builders/shell.js?v=2026-07-02-shell1"></script>
61
+ <script src="/builders/palette.js" defer></script>
62
+ <script src="/builders/dom-utils.js?v=2026-06-23-contrib"></script>
63
+ <script src="/builders/collab-lib.js?v=2026-08-24-c02"></script>
64
+ <script src="/builders/collab.js?v=2026-08-24-c02"></script>
65
+ </body>
66
+ </html>
@@ -0,0 +1,139 @@
1
+ // modules/hall-ui/public/collab.js — the Collab page (/builders/collab).
2
+ // Task 1003172 / C02 of goal 1000075, criterion collab-page-matches-my-craft.
3
+ //
4
+ // The hall surface for C01's help requests. Two sections, in this order on
5
+ // purpose: what is ASKED OF YOU first, because someone is waiting on a human
6
+ // answer, then YOUR OWN ASKS, which are a status check rather than a call to act.
7
+ //
8
+ // THE PART THAT IS EASY TO SKIP, and the criterion names it explicitly: a request
9
+ // nobody takes must be VISIBLY AGING, not silently dead. Sorting the oldest to the
10
+ // top does not satisfy that — the oldest then looks exactly like the newest, only
11
+ // lower. So every row carries an age BAND with its own label and styling, and the
12
+ // rules for it live in collab-lib.js where a test can execute them.
13
+ //
14
+ // MATCHING IS THE SERVER'S JOB, not this file's. GET /help-requests/for-me derives
15
+ // the viewer's crafts from their own profile; sending a craft from here would be
16
+ // the privacy hole C01's grader found (a ?craft= param that let anyone read
17
+ // another craft's requests). This page never names a craft in a request.
18
+ (() => {
19
+ 'use strict';
20
+
21
+ // NO baseUrl OVERRIDE. The generated client's methods (api.helpRequests.*, api.me.*)
22
+ // emit API-RELATIVE paths like '/help-requests/for-me'. Passing baseUrl:'' means
23
+ // "these paths are already absolute" and sends them to the site root, where nothing
24
+ // is mounted — every call 404s. Letting it default resolves them against
25
+ // '/api/bongos/v1' (api-prefix.js VERSIONED_API_PREFIX), which is where the router
26
+ // actually mounts. Pages that DO pass baseUrl:'' must build full paths themselves
27
+ // via api.request('GET', `${API}/...`) — the two styles must never be mixed.
28
+ const api = window.BongosClient.createClient({ credentials: 'same-origin', throwOnError: false });
29
+ const { escapeHtml, $, toast } = window.OTB;
30
+ const L = window.OTBCollabLib;
31
+
32
+ let settling = false;
33
+
34
+ function ageChip(req, nowMs) {
35
+ const band = L.ageBand(req.created_at, nowMs);
36
+ return `<span class="cb-age cb-age--${band.id}" title="Asked ${escapeHtml(String(req.created_at || ''))}">${escapeHtml(band.label)}</span>`;
37
+ }
38
+
39
+ function contextChip(req) {
40
+ const ctx = L.contextOf(req);
41
+ if (!ctx) return '';
42
+ return `<a class="cb-ctx" href="${escapeHtml(ctx.href)}">${escapeHtml(ctx.kind)} #${escapeHtml(String(ctx.id))}</a>`;
43
+ }
44
+
45
+ // A row for the "asked of you" list. There is deliberately NO "claim this"
46
+ // button: answering is a conversation, and inventing a reply thread here would
47
+ // be reimplementing the Agora, which this goal explicitly forbids (C06 owns the
48
+ // chat mirror). What the page owes a helper is the request, its age, its
49
+ // context, and who to answer.
50
+ function forMeRow(req, viewerId, nowMs) {
51
+ const who = L.addressedTo(req, viewerId);
52
+ return `<li class="cb-item cb-item--${L.ageBand(req.created_at, nowMs).id}">
53
+ <p class="cb-item__stuck">${escapeHtml(req.what_is_stuck || '')}</p>
54
+ <p class="cb-item__meta">
55
+ <span class="cb-who">${escapeHtml(who)}</span>
56
+ ${contextChip(req)}
57
+ ${ageChip(req, nowMs)}
58
+ </p>
59
+ </li>`;
60
+ }
61
+
62
+ // Your own ask, with the two settle actions. Only the asker can settle — the
63
+ // server enforces it in the UPDATE's WHERE, this is just the affordance.
64
+ function mineRow(req, nowMs) {
65
+ return `<li class="cb-item cb-item--${L.ageBand(req.created_at, nowMs).id}" data-id="${escapeHtml(String(req.id))}">
66
+ <p class="cb-item__stuck">${escapeHtml(req.what_is_stuck || '')}</p>
67
+ <p class="cb-item__meta">
68
+ <span class="cb-who">${escapeHtml(L.addressedTo(req, null))}</span>
69
+ ${contextChip(req)}
70
+ ${ageChip(req, nowMs)}
71
+ </p>
72
+ <p class="cb-item__actions">
73
+ <button class="btn btn--sm" data-settle="answered" data-id="${escapeHtml(String(req.id))}">Someone helped</button>
74
+ <button class="btn btn--sm btn--quiet" data-settle="withdrawn" data-id="${escapeHtml(String(req.id))}">Withdraw</button>
75
+ </p>
76
+ </li>`;
77
+ }
78
+
79
+ function renderList(hostSel, emptySel, rows, html) {
80
+ const host = $(hostSel);
81
+ const empty = $(emptySel);
82
+ if (!host) return;
83
+ host.innerHTML = rows.map(html).join('');
84
+ if (empty) empty.hidden = rows.length > 0;
85
+ }
86
+
87
+ async function load() {
88
+ const nowMs = Date.now();
89
+ const [forMe, mine, me] = await Promise.all([
90
+ api.helpRequests.getHelpRequestsForMe({}),
91
+ api.helpRequests.getHelpRequestsMine({ query: { status: 'open' } }),
92
+ api.me.getMe ? api.me.getMe({}) : Promise.resolve({ ok: false }),
93
+ ]);
94
+
95
+ const viewerId = me && me.ok && me.data && me.data.builder ? me.data.builder.id : null;
96
+
97
+ const forMeRows = ((forMe.ok && forMe.data && forMe.data.help_requests) || []).slice().sort(L.byAgeOldestFirst);
98
+ const mineRows = ((mine.ok && mine.data && mine.data.help_requests) || []).slice().sort(L.byAgeOldestFirst);
99
+
100
+ renderList('#cb-for-me', '#cb-for-me-empty', forMeRows, (r) => forMeRow(r, viewerId, nowMs));
101
+ renderList('#cb-mine', '#cb-mine-empty', mineRows, (r) => mineRow(r, nowMs));
102
+
103
+ const sub = $('#cb-sub');
104
+ if (sub) {
105
+ const crafts = (forMe.ok && forMe.data && forMe.data.crafts) || [];
106
+ sub.textContent = crafts.length
107
+ ? `Matching your craft: ${crafts.join(', ')}.`
108
+ : 'You have not set a craft, so only requests addressed to you by name reach you.';
109
+ }
110
+ }
111
+
112
+ async function settle(id, status) {
113
+ if (settling) return;
114
+ settling = true;
115
+ try {
116
+ const r = await api.helpRequests.patchHelpRequestsId({ id, body: { status } });
117
+ if (!r.ok) {
118
+ toast(window.OTB.errMessage ? window.OTB.errMessage(r.data) : 'Could not update that request.');
119
+ return;
120
+ }
121
+ toast(status === 'answered' ? 'Marked answered.' : 'Withdrawn.');
122
+ await load();
123
+ } finally {
124
+ settling = false;
125
+ }
126
+ }
127
+
128
+ document.addEventListener('click', (ev) => {
129
+ const btn = ev.target && ev.target.closest ? ev.target.closest('[data-settle]') : null;
130
+ if (!btn) return;
131
+ ev.preventDefault();
132
+ settle(Number(btn.getAttribute('data-id')), btn.getAttribute('data-settle'));
133
+ });
134
+
135
+ load().catch(() => {
136
+ const sub = $('#cb-sub');
137
+ if (sub) sub.textContent = 'Could not load requests.';
138
+ });
139
+ })();
@@ -153,6 +153,10 @@
153
153
  { id: 'profile', label: 'Profile', icon: 'profile', href: '/profile', gate: 'signed' },
154
154
  ] },
155
155
  { group: 'Community', gate: 'member', items: [
156
+ // task 1003172 (C02, goal 1000075): help requests addressed to you. In
157
+ // Community because the group is picked by WHOSE data it is, and this is
158
+ // other builders reaching you — not your own numbers (that is You).
159
+ { id: 'collab', label: 'Collab', icon: 'leaderboard', href: '/collab', gate: 'member' },
156
160
  { id: 'leaderboard', label: 'Leaderboard', icon: 'leaderboard', href: '/#leaderboard', view: 'leaderboard', gate: 'member' },
157
161
  // task 1002577: the cross-project builder directory (/scouting graduated
158
162
  // into the hall). Gated to MATCH its data — /api/gds/scouting checks
package/package-lock.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@bongos/core",
3
- "version": "1.19.345",
3
+ "version": "1.19.347",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bongos/core",
9
- "version": "1.19.345",
9
+ "version": "1.19.347",
10
10
  "license": "AGPL-3.0-or-later",
11
11
  "dependencies": {
12
12
  "express": "^4.21.2",