@bongos/core 1.21.82 → 1.21.83
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/.bongos-core.json +43 -28
- package/docs/api/openapi.json +1 -1
- package/docs/api-reference.md +1 -1
- package/docs/module-api-changelog.md +2 -0
- package/docs/page-inventory.json +7 -0
- package/docs/page-readings.json +640 -639
- package/modules/platform-identity/invite-notices.js +78 -10
- package/modules/platform-identity/migrations/platform_identity_031_parked_invite_notices.sql +45 -0
- package/modules/platform-identity/routes/my-projects.js +7 -3
- package/modules/public-landing/public/account.html +1 -0
- package/modules/public-landing/public/assets/cosmos.css +8 -0
- package/modules/public-landing/public/assets/invite-badge.js +91 -0
- package/modules/public-landing/public/contact.html +1 -0
- package/modules/public-landing/public/guild.html +1 -0
- package/modules/public-landing/public/invitations.html +7 -2
- package/modules/public-landing/public/privacy.html +1 -0
- package/modules/public-landing/public/projects.html +3 -0
- package/modules/public-landing/public/terms.html +1 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/release-notes.json +6 -0
- package/src/module-api.js +1 -1
- package/tests/hub_invite_badge.mjs +204 -0
- package/tests/hub_invite_notices.mjs +61 -6
- package/tests/landing_legal_pages.mjs +1 -1
|
@@ -18,31 +18,96 @@
|
|
|
18
18
|
//
|
|
19
19
|
// Both writers touch ONLY a `pending` row, so a notice can never create, downgrade or
|
|
20
20
|
// remove a real membership (a member is left as-is; the row is not theirs to change).
|
|
21
|
-
//
|
|
22
|
-
//
|
|
21
|
+
//
|
|
22
|
+
// A login with no hub account is PARKED by login (task 1004646, migration 031): a
|
|
23
|
+
// platform account is only born when someone signs in to a project THROUGH the hub,
|
|
24
|
+
// so the usual invitee — new to the platform — has none, and dropping the notice
|
|
25
|
+
// meant they never saw it, even after signing in to the hub. The invitations read
|
|
26
|
+
// lists a parked notice for whoever signs in with that login (listParkedInvites).
|
|
23
27
|
//
|
|
24
28
|
// Its own file because platform-identity.js is at the size ratchet; it reuses that
|
|
25
29
|
// file's account lookup and pending-row writers rather than restating them.
|
|
26
30
|
'use strict';
|
|
27
31
|
|
|
32
|
+
const api = require('../../src/module-api');
|
|
28
33
|
const pi = require('./platform-identity');
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
const loginKey = (githubLogin) => String(githubLogin || '').trim().replace(/^@/, '').toLowerCase();
|
|
36
|
+
|
|
37
|
+
// The project noticed an invite for `githubLogin` → a `pending` hub row if the login
|
|
38
|
+
// has a hub account, else a parked notice under the login. Returns { noted } for the
|
|
32
39
|
// route's own logging; the route never tells the caller which case happened (see it).
|
|
33
|
-
async function noteProjectInvite({ clientId, githubLogin }, { pool } = {}) {
|
|
40
|
+
async function noteProjectInvite({ clientId, githubLogin }, { pool = api.pool } = {}) {
|
|
34
41
|
const account = await pi.getAccountByLogin(githubLogin, { pool });
|
|
35
|
-
if (!account)
|
|
42
|
+
if (!account) {
|
|
43
|
+
const login = loginKey(githubLogin);
|
|
44
|
+
if (!login) return { noted: false };
|
|
45
|
+
await pool.query(
|
|
46
|
+
`INSERT INTO platform_identity_parked_invite_notices (client_id, login_lower)
|
|
47
|
+
VALUES ($1, $2)
|
|
48
|
+
ON CONFLICT (client_id, login_lower) DO UPDATE SET noted_at = now()`,
|
|
49
|
+
[clientId, login],
|
|
50
|
+
);
|
|
51
|
+
return { noted: true, parked: true };
|
|
52
|
+
}
|
|
36
53
|
const out = await pi.inviteToProject({ clientId, githubId: account.github_id }, { pool });
|
|
37
54
|
return { noted: !!out.invited };
|
|
38
55
|
}
|
|
39
56
|
|
|
40
|
-
// The project rescinded the invite for `githubLogin` → drop the `pending` hub row
|
|
57
|
+
// The project rescinded the invite for `githubLogin` → drop the `pending` hub row and
|
|
58
|
+
// any parked notice under the login (an account made since the notice keeps both
|
|
59
|
+
// reachable, so both go).
|
|
41
60
|
async function clearProjectInvite({ clientId, githubLogin }, { pool } = {}) {
|
|
61
|
+
const parked = await removeParkedInvite({ clientId, githubLogin }, { pool });
|
|
42
62
|
const account = await pi.getAccountByLogin(githubLogin, { pool });
|
|
43
|
-
if (!account) return { cleared:
|
|
63
|
+
if (!account) return { cleared: parked.removed };
|
|
44
64
|
const out = await pi.removePendingInvite({ clientId, githubId: account.github_id }, { pool });
|
|
45
|
-
return { cleared: out.removed };
|
|
65
|
+
return { cleared: out.removed || parked.removed };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// The parked notices for one signed-in login, in the shape listPendingInvitesForAccount
|
|
69
|
+
// returns, minus any past the invite's 30 days (ADR 0356) and any project the reader
|
|
70
|
+
// already holds a real membership in — signing in there accepted it.
|
|
71
|
+
async function listParkedInvites({ githubLogin, githubId }, { pool = api.pool } = {}) {
|
|
72
|
+
const login = loginKey(githubLogin);
|
|
73
|
+
if (!login) return [];
|
|
74
|
+
const { rows } = await pool.query(
|
|
75
|
+
`SELECT n.client_id, n.noted_at AS invited_at,
|
|
76
|
+
c.name AS project_name, c.origin AS project_origin
|
|
77
|
+
FROM platform_identity_parked_invite_notices n
|
|
78
|
+
LEFT JOIN platform_identity_sso_clients c ON c.client_id = n.client_id
|
|
79
|
+
WHERE n.login_lower = $1
|
|
80
|
+
AND n.noted_at > now() - make_interval(days => $2)
|
|
81
|
+
AND NOT EXISTS (
|
|
82
|
+
SELECT 1 FROM platform_identity_project_memberships m
|
|
83
|
+
WHERE m.client_id = n.client_id AND m.github_id = $3 AND m.membership_kind <> 'pending')
|
|
84
|
+
ORDER BY n.noted_at DESC`,
|
|
85
|
+
[login, api.inviteExpiry.INVITE_EXPIRY_DAYS, String(githubId || '')],
|
|
86
|
+
);
|
|
87
|
+
return rows;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// The invitee declined, or the project rescinded: drop the parked notice for the login.
|
|
91
|
+
async function removeParkedInvite({ clientId, githubLogin }, { pool = api.pool } = {}) {
|
|
92
|
+
const login = loginKey(githubLogin);
|
|
93
|
+
if (!login) return { removed: false };
|
|
94
|
+
const { rowCount } = await pool.query(
|
|
95
|
+
`DELETE FROM platform_identity_parked_invite_notices WHERE client_id = $1 AND login_lower = $2`,
|
|
96
|
+
[clientId, login],
|
|
97
|
+
);
|
|
98
|
+
return { removed: rowCount > 0 };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Everything waiting on one signed-in builder: their pending rows plus the notices
|
|
102
|
+
// parked under their login, one per project (a pending row wins — it is the same
|
|
103
|
+
// invite, written once the account existed).
|
|
104
|
+
async function listInvitesForBuilder({ githubId, githubLogin }, { pool = api.pool } = {}) {
|
|
105
|
+
const [pending, parked] = await Promise.all([
|
|
106
|
+
pi.listPendingInvitesForAccount(githubId, { pool }),
|
|
107
|
+
listParkedInvites({ githubLogin, githubId }, { pool }),
|
|
108
|
+
]);
|
|
109
|
+
const seen = new Set(pending.map((r) => r.client_id));
|
|
110
|
+
return pending.concat(parked.filter((r) => !seen.has(r.client_id)));
|
|
46
111
|
}
|
|
47
112
|
|
|
48
113
|
// The project's own invite box for `githubLogin`: its hall's watch page, with the login
|
|
@@ -58,4 +123,7 @@ function hallInviteUrl(origin, githubLogin) {
|
|
|
58
123
|
return `${u.origin}/builders/watch${login ? `?invite=${encodeURIComponent(login)}` : ''}`;
|
|
59
124
|
}
|
|
60
125
|
|
|
61
|
-
module.exports = {
|
|
126
|
+
module.exports = {
|
|
127
|
+
noteProjectInvite, clearProjectInvite, hallInviteUrl,
|
|
128
|
+
removeParkedInvite, listInvitesForBuilder,
|
|
129
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
-- platform_identity_031_parked_invite_notices.sql — a project's invite notice for a
|
|
2
|
+
-- GitHub login the hub has no account for yet, kept until that person signs in
|
|
3
|
+
-- (task 1004646; ADR 0353 amended).
|
|
4
|
+
--
|
|
5
|
+
-- WHY. POST /sso/invites/notify resolves the invited login to a platform account and,
|
|
6
|
+
-- before this table, dropped the notice when there was none. A platform account is
|
|
7
|
+
-- only created when someone signs in to a PROJECT through the hub — signing in to the
|
|
8
|
+
-- hub itself creates none — so the usual invitee (new to the platform) never saw the
|
|
9
|
+
-- invite on /invitations, even after signing in. Now the notice is PARKED here by
|
|
10
|
+
-- login, and the invitations read lists it for whoever signs in with that login.
|
|
11
|
+
--
|
|
12
|
+
-- THE SHAPE CARRIES THE RULES:
|
|
13
|
+
-- * THE KEY IS (client_id, login_lower), so a re-invite cannot pile up rows: the
|
|
14
|
+
-- writer's upsert restarts noted_at in place.
|
|
15
|
+
-- * NO STATUS COLUMN (ADR 0201 D1.3, ADR 0353). A notice admits nobody: the
|
|
16
|
+
-- project's own access_requests row does, and signing in there accepts it. A
|
|
17
|
+
-- parked notice is hidden at read time once the reader holds a non-pending
|
|
18
|
+
-- membership in that project.
|
|
19
|
+
-- * THE 30-DAY WINDOW IS READ-TIME (ADR 0356, the doorway's INVITE_EXPIRY_DAYS),
|
|
20
|
+
-- exactly as for a pending membership row. Nothing sweeps the rows.
|
|
21
|
+
-- * KEYED BY LOGIN, like the project's own invited access_requests row, which is
|
|
22
|
+
-- what admits the same person — so the notice can claim no more than the invite.
|
|
23
|
+
--
|
|
24
|
+
-- NOTHING HERE IS AUTHORITY (ADR 0016 / ADR 0141).
|
|
25
|
+
--
|
|
26
|
+
-- Additive + platform_identity_-namespaced (ADR 0083 §5). CREATE only, so the
|
|
27
|
+
-- previous release still runs against this schema. Idempotent.
|
|
28
|
+
|
|
29
|
+
BEGIN;
|
|
30
|
+
|
|
31
|
+
CREATE TABLE IF NOT EXISTS platform_identity_parked_invite_notices (
|
|
32
|
+
client_id text NOT NULL,
|
|
33
|
+
login_lower text NOT NULL,
|
|
34
|
+
noted_at timestamptz NOT NULL DEFAULT now(),
|
|
35
|
+
PRIMARY KEY (client_id, login_lower)
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
-- The reader's side: every notice for one login.
|
|
39
|
+
CREATE INDEX IF NOT EXISTS platform_identity_parked_invite_notices_login
|
|
40
|
+
ON platform_identity_parked_invite_notices (login_lower);
|
|
41
|
+
|
|
42
|
+
COMMENT ON TABLE platform_identity_parked_invite_notices IS
|
|
43
|
+
'A project''s invite notice for a GitHub login with no hub account yet (task 1004646). Listed on /invitations for whoever signs in with that login; a notice only, never a way in (ADR 0353). The 30-day window is applied when read (ADR 0356).';
|
|
44
|
+
|
|
45
|
+
COMMIT;
|
|
@@ -29,6 +29,7 @@ const projectStats = require('../project-stats');
|
|
|
29
29
|
const applicationEcho = require('../application-echo');
|
|
30
30
|
const guilds = require('../guilds');
|
|
31
31
|
const guildEngagements = require('../guild-engagements');
|
|
32
|
+
const inviteNotices = require('../invite-notices');
|
|
32
33
|
const hubKeys = require('../hub-keys');
|
|
33
34
|
const { isAccountActive } = require('../account-visibility');
|
|
34
35
|
const { projectPublishability } = require('../publish-gate');
|
|
@@ -302,10 +303,12 @@ module.exports = function myProjectsRoutes() {
|
|
|
302
303
|
}
|
|
303
304
|
});
|
|
304
305
|
|
|
305
|
-
// rank: any-builder — the caller's OWN pending invitations (ADR 0141 §5)
|
|
306
|
+
// rank: any-builder — the caller's OWN pending invitations (ADR 0141 §5), plus the
|
|
307
|
+
// notices parked under their login before they had a hub account (task 1004646).
|
|
306
308
|
router.get('/my-projects/invites', requireBuilder, async (req, res) => {
|
|
307
309
|
try {
|
|
308
|
-
const invites = await
|
|
310
|
+
const invites = await inviteNotices.listInvitesForBuilder(
|
|
311
|
+
{ githubId: req.builder.github_id, githubLogin: req.builder.github_login }, { pool });
|
|
309
312
|
res.json({ invites: invites.map((i) => ({ client_id: i.client_id, name: i.project_name || i.project_origin || i.client_id, origin: i.project_origin || null, invited_at: i.invited_at })) });
|
|
310
313
|
} catch (err) {
|
|
311
314
|
console.error('[platform-identity] GET /my-projects/invites', err);
|
|
@@ -324,7 +327,8 @@ module.exports = function myProjectsRoutes() {
|
|
|
324
327
|
if (!clientId) return res.fail('bad_client_id', 400);
|
|
325
328
|
try {
|
|
326
329
|
const out = await pi.removePendingInvite({ clientId, githubId: req.builder.github_id }, { pool });
|
|
327
|
-
|
|
330
|
+
const parked = await inviteNotices.removeParkedInvite({ clientId, githubLogin: req.builder.github_login }, { pool });
|
|
331
|
+
if (!out.removed && !parked.removed) return res.fail('no_pending_invite', 404);
|
|
328
332
|
res.json({ ok: true, accepted: false });
|
|
329
333
|
} catch (err) {
|
|
330
334
|
console.error('[platform-identity] decline invite', err);
|
|
@@ -624,6 +624,7 @@
|
|
|
624
624
|
});
|
|
625
625
|
})();
|
|
626
626
|
</script>
|
|
627
|
+
<script src="/assets/invite-badge.js"></script>
|
|
627
628
|
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
628
629
|
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
629
630
|
</body>
|
|
@@ -628,6 +628,14 @@ p.sec-p{font-size:var(--fs-lede);font-weight:500;color:var(--ink-soft);line-heig
|
|
|
628
628
|
.signinDoor:hover{background:var(--ink);color:var(--bg);}
|
|
629
629
|
/* a long GitHub login truncates instead of pushing Sign out off the screen */
|
|
630
630
|
.who>span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:14ch;}
|
|
631
|
+
/* what is waiting on you (task 1004646, assets/invite-badge.js): a count on the chip and on
|
|
632
|
+
the band's Invitations item — the accent fill with --accent-on ink, the .btn pairing */
|
|
633
|
+
.invBadge{display:inline-flex;align-items:center;justify-content:center;min-width:20px;height:20px;padding:0 6px;
|
|
634
|
+
border-radius:var(--radius-pill);background:var(--accent);color:var(--accent-on);
|
|
635
|
+
font-family:var(--font-body);font-size:12px;font-weight:700;line-height:1;letter-spacing:0;vertical-align:middle;}
|
|
636
|
+
.who>.invBadge{max-width:none;overflow:visible;}
|
|
637
|
+
.bandT .invBadge{margin-left:10px;}
|
|
638
|
+
.invWaiting{margin:6px 0 4px;font-size:15.5px;font-weight:600;}
|
|
631
639
|
/* keyboard focus on the two search pills is VISIBLE — the input's own outline
|
|
632
640
|
is off by design, so the pill carries the ring */
|
|
633
641
|
.search:focus-within{box-shadow:0 0 0 3px color-mix(in srgb,var(--accent) 40%,transparent);}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* assets/invite-badge.js — WHAT IS WAITING ON YOU, said where you already look
|
|
2
|
+
(task 1004646; the owner's report of 2026-10-08 that invitations "aren't obvious").
|
|
3
|
+
|
|
4
|
+
/invitations lists what asks you in, but the only way there was the profile band,
|
|
5
|
+
with nothing saying anything was waiting. This counts what is waiting — a project's
|
|
6
|
+
invite (GET /my-projects/invites) and a guild's invite or a join request to a guild
|
|
7
|
+
you own (GET /me/guild-requests) — and, when it is more than none, paints:
|
|
8
|
+
- a count on the identity chip (#who) and on the profile band's Invitations item;
|
|
9
|
+
- on /projects?view=mine, a line "You have N invitations waiting →" under the
|
|
10
|
+
view's heading.
|
|
11
|
+
A guild's engagement with a project is left out: it asks nothing of you.
|
|
12
|
+
|
|
13
|
+
Hub only, with no flag of its own: the Invitations item sits inside the apex-only
|
|
14
|
+
region, which a child instance strips, so there this file finds no item and reads
|
|
15
|
+
nothing. Signed out, both reads 401 and nothing is painted. A failed read counts
|
|
16
|
+
as none — a badge is a convenience, never the record.
|
|
17
|
+
|
|
18
|
+
Built from nodes only. window.InviteBadge.refresh() re-reads, for a page
|
|
19
|
+
that has just answered one (the invitations page's Decline and Accept). */
|
|
20
|
+
(function () {
|
|
21
|
+
'use strict';
|
|
22
|
+
var API = '/api/bongos';
|
|
23
|
+
function $(id) { return document.getElementById(id); }
|
|
24
|
+
function item() { var m = $('menuProfile'); return m && m.querySelector('a[href="/invitations"]'); }
|
|
25
|
+
|
|
26
|
+
function readCount(path, pick) {
|
|
27
|
+
return fetch(API + path, { credentials: 'same-origin', headers: { Accept: 'application/json' } })
|
|
28
|
+
.then(function (res) { return res.ok ? res.json() : null; })
|
|
29
|
+
.then(function (d) { return d ? pick(d) : 0; })
|
|
30
|
+
.catch(function () { return 0; });
|
|
31
|
+
}
|
|
32
|
+
function len(x) { return Array.isArray(x) ? x.length : 0; }
|
|
33
|
+
|
|
34
|
+
function badge(host, n, label) {
|
|
35
|
+
if (!host) return;
|
|
36
|
+
var b = host.querySelector('.invBadge');
|
|
37
|
+
if (!n) { if (b) b.remove(); return; }
|
|
38
|
+
if (!b) {
|
|
39
|
+
b = document.createElement('b');
|
|
40
|
+
b.className = 'invBadge';
|
|
41
|
+
var shown = document.createElement('span');
|
|
42
|
+
shown.setAttribute('aria-hidden', 'true');
|
|
43
|
+
var said = document.createElement('span');
|
|
44
|
+
said.className = 'vh';
|
|
45
|
+
b.appendChild(shown);
|
|
46
|
+
b.appendChild(said);
|
|
47
|
+
// before the chip's chevron; at the end of a band item
|
|
48
|
+
host.insertBefore(b, host.querySelector('svg.cv'));
|
|
49
|
+
}
|
|
50
|
+
b.firstChild.textContent = n > 99 ? '99+' : String(n);
|
|
51
|
+
b.lastChild.textContent = ', ' + label;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// at the top of the mine view, under its heading and lede — the list of projects
|
|
55
|
+
// below it can be long, and the Invitations pointer sits at its foot
|
|
56
|
+
function mineLine(n, label) {
|
|
57
|
+
var view = $('view-mine');
|
|
58
|
+
var hero = view && view.querySelector('.hero');
|
|
59
|
+
if (!hero) return;
|
|
60
|
+
var p = $('mineInvitesWaiting');
|
|
61
|
+
if (!n) { if (p) p.remove(); return; }
|
|
62
|
+
if (!p) {
|
|
63
|
+
p = document.createElement('p');
|
|
64
|
+
p.id = 'mineInvitesWaiting';
|
|
65
|
+
p.className = 'invWaiting';
|
|
66
|
+
var a = document.createElement('a');
|
|
67
|
+
a.href = '/invitations';
|
|
68
|
+
p.appendChild(a);
|
|
69
|
+
hero.appendChild(p);
|
|
70
|
+
}
|
|
71
|
+
p.firstChild.textContent = 'You have ' + label + ' waiting →';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function paint(n) {
|
|
75
|
+
var label = n + (n === 1 ? ' invitation' : ' invitations');
|
|
76
|
+
badge($('who'), n, label + ' waiting');
|
|
77
|
+
badge(item(), n, label + ' waiting');
|
|
78
|
+
mineLine(n, label);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function refresh() {
|
|
82
|
+
if (!item()) return Promise.resolve(0);
|
|
83
|
+
return Promise.all([
|
|
84
|
+
readCount('/my-projects/invites', function (d) { return len(d.invites); }),
|
|
85
|
+
readCount('/me/guild-requests', function (d) { return len(d.incoming) + len(d.owner_incoming); }),
|
|
86
|
+
]).then(function (c) { var n = c[0] + c[1]; paint(n); return n; });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
window.InviteBadge = { refresh: refresh, paint: paint };
|
|
90
|
+
refresh();
|
|
91
|
+
})();
|
|
@@ -120,6 +120,7 @@
|
|
|
120
120
|
|
|
121
121
|
<footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy">Privacy</a><a href="/terms">Terms</a><a href="/contact" aria-current="page">Contact</a><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a></nav></footer>
|
|
122
122
|
<script src="/assets/hub-bar.js"></script>
|
|
123
|
+
<script src="/assets/invite-badge.js"></script>
|
|
123
124
|
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
124
125
|
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
125
126
|
</body>
|
|
@@ -629,6 +629,7 @@
|
|
|
629
629
|
load();
|
|
630
630
|
})();
|
|
631
631
|
</script>
|
|
632
|
+
<script src="/assets/invite-badge.js"></script>
|
|
632
633
|
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
633
634
|
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
634
635
|
</body>
|
|
@@ -232,11 +232,13 @@
|
|
|
232
232
|
button on failure. `onRemoved` carries whatever else the caller needs after
|
|
233
233
|
the row is gone (the empty-line flip). The busy/idle label swap the /projects
|
|
234
234
|
copy carries is for its Leave button, which never moved here. */
|
|
235
|
+
/* the bar's waiting count (assets/invite-badge.js, task 1004646) follows an answer here */
|
|
236
|
+
function recount() { if (typeof window !== 'undefined' && window.InviteBadge) window.InviteBadge.refresh(); }
|
|
235
237
|
function postThenRemoveRow(url, row, btn, onRemoved) {
|
|
236
238
|
btn.disabled = true;
|
|
237
239
|
fetch(url, { method: 'POST', credentials: 'same-origin' })
|
|
238
240
|
.then(function (r) { return r.ok ? r.json() : Promise.reject(); })
|
|
239
|
-
.then(function () { row.remove(); if (onRemoved) onRemoved(); })
|
|
241
|
+
.then(function () { row.remove(); if (onRemoved) onRemoved(); recount(); })
|
|
240
242
|
.catch(function () { btn.disabled = false; });
|
|
241
243
|
}
|
|
242
244
|
|
|
@@ -481,7 +483,7 @@
|
|
|
481
483
|
msg.className = 'projMsg';
|
|
482
484
|
msg.textContent = '';
|
|
483
485
|
guildPost('/guild-requests/' + encodeURIComponent(id) + '/decline').then(function (res) {
|
|
484
|
-
if (res.ok) { row.remove(); paintInvitesSection(); return; }
|
|
486
|
+
if (res.ok) { row.remove(); paintInvitesSection(); recount(); return; }
|
|
485
487
|
btns.forEach(function (b) { b.disabled = false; });
|
|
486
488
|
if (res.status === 401) { sessionExpired(msg); return; }
|
|
487
489
|
msg.className = 'projMsg err';
|
|
@@ -582,6 +584,7 @@
|
|
|
582
584
|
radios.forEach(function (r) { r.disabled = true; });
|
|
583
585
|
page.hidden = false;
|
|
584
586
|
msg.className = 'projMsg ok';
|
|
587
|
+
recount();
|
|
585
588
|
msg.textContent = 'You’ve joined ' + name + (on ? ', and your numbers count toward its totals.' : ', and your numbers are left out of its totals.');
|
|
586
589
|
return;
|
|
587
590
|
}
|
|
@@ -660,6 +663,7 @@
|
|
|
660
663
|
hint.hidden = true;
|
|
661
664
|
msg.className = 'projMsg ok';
|
|
662
665
|
msg.textContent = who + ' is in ' + (r.guild.name || r.guild.slug) + ' now.';
|
|
666
|
+
recount();
|
|
663
667
|
return;
|
|
664
668
|
}
|
|
665
669
|
accept.textContent = 'Accept';
|
|
@@ -961,6 +965,7 @@
|
|
|
961
965
|
});
|
|
962
966
|
})();
|
|
963
967
|
</script>
|
|
968
|
+
<script src="/assets/invite-badge.js"></script>
|
|
964
969
|
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
965
970
|
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
966
971
|
</body>
|
|
@@ -129,6 +129,7 @@
|
|
|
129
129
|
|
|
130
130
|
<footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy" aria-current="page">Privacy</a><a href="/terms">Terms</a><a href="/contact">Contact</a><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a></nav></footer>
|
|
131
131
|
<script src="/assets/hub-bar.js"></script>
|
|
132
|
+
<script src="/assets/invite-badge.js"></script>
|
|
132
133
|
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
133
134
|
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
134
135
|
</body>
|
|
@@ -11142,6 +11142,8 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
11142
11142
|
.then(function (data) {
|
|
11143
11143
|
me = data.builder || data.me || data;
|
|
11144
11144
|
renderWho();
|
|
11145
|
+
/* renderWho rebuilds the chip, so the waiting count is painted again on it (task 1004646) */
|
|
11146
|
+
if (window.InviteBadge) window.InviteBadge.refresh();
|
|
11145
11147
|
paintShellIdentity();
|
|
11146
11148
|
wizard.onAuth();
|
|
11147
11149
|
$('gate').hidden = true;
|
|
@@ -11174,6 +11176,7 @@ summary{min-height:24px;padding:3px 0;}
|
|
|
11174
11176
|
.then(loadSelfShipped, loadSelfShipped);
|
|
11175
11177
|
})();
|
|
11176
11178
|
</script>
|
|
11179
|
+
<script src="/assets/invite-badge.js"></script>
|
|
11177
11180
|
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
11178
11181
|
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
11179
11182
|
</body>
|
|
@@ -134,6 +134,7 @@
|
|
|
134
134
|
|
|
135
135
|
<footer class="foot"><!--apex-only:start--><span>A non-profit, capture-resistant commons. </span><!--apex-only:end-->Built by agents, on Cloud Bongos. <a href="https://www.npmjs.com/package/@bongos/core" target="_blank" rel="noopener">Open source</a> (<a href="https://www.gnu.org/licenses/agpl-3.0.html" target="_blank" rel="noopener">AGPL-3.0</a>).<nav class="footNav" aria-label="Site"><a href="/privacy">Privacy</a><a href="/terms" aria-current="page">Terms</a><a href="/contact">Contact</a><a data-link-hall href="{{buildersOrigin}}">Builders' hall</a></nav></footer>
|
|
136
136
|
<script src="/assets/hub-bar.js"></script>
|
|
137
|
+
<script src="/assets/invite-badge.js"></script>
|
|
137
138
|
<!-- the Artist review button: signed-in builders only (task 1004499) -->
|
|
138
139
|
<script src="/assets/artist-review.js" data-hall="{{buildersOrigin}}"></script>
|
|
139
140
|
</body>
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.83",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.21.
|
|
9
|
+
"version": "1.21.83",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.21.
|
|
3
|
+
"version": "1.21.83",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
package/release-notes.json
CHANGED
|
@@ -9318,5 +9318,11 @@
|
|
|
9318
9318
|
"id": "1004481",
|
|
9319
9319
|
"text": "Custom ranks can now be deleted from the Government page, so trying a recommended rank set is no longer a permanent choice. A rank someone still holds can't be deleted, and the page says who holds it. The four built-in ranks c"
|
|
9320
9320
|
}
|
|
9321
|
+
],
|
|
9322
|
+
"1.21.83": [
|
|
9323
|
+
{
|
|
9324
|
+
"id": "1004646",
|
|
9325
|
+
"text": "Project invites now reach people on cloudbongos.com. Before, an invite sent by GitHub username was silently lost if the person had never signed in to a project through cloudbongos.com, which is almost every new invitee, so the"
|
|
9326
|
+
}
|
|
9321
9327
|
]
|
|
9322
9328
|
}
|
package/src/module-api.js
CHANGED
|
@@ -75,7 +75,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
75
75
|
// MAJOR (see allowBoxScope below): passes the request through untouched.
|
|
76
76
|
function deprecatedNoopMiddleware(_req, _res, next) { next(); }
|
|
77
77
|
|
|
78
|
-
const CORE_VERSION = '1.21.
|
|
78
|
+
const CORE_VERSION = '1.21.83'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
79
79
|
|
|
80
80
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
81
81
|
// Usage: const log = api.logger('discord'); log.info('mounted');
|