@bongos/core 1.19.409 → 1.19.410

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": 1535,
7
- "unique_ids": 1334,
8
- "certain": 1535,
6
+ "strings": 1542,
7
+ "unique_ids": 1339,
8
+ "certain": 1542,
9
9
  "heuristic": 0,
10
- "duplicate_ids": 154
10
+ "duplicate_ids": 156
11
11
  },
12
12
  "surfaces": {
13
13
  "builders-hall": {
14
14
  "at": "modules/hall-ui/public",
15
15
  "files": 71,
16
- "strings": 1130
16
+ "strings": 1137
17
17
  },
18
18
  "status-dashboard": {
19
19
  "at": "modules/status-ui/public",
@@ -955,6 +955,26 @@
955
955
  "modules/hall-ui/public/work.js:1014"
956
956
  ]
957
957
  },
958
+ {
959
+ "id": "0bc1d1470e22",
960
+ "surface": "builders-hall",
961
+ "text": "Only an open goal can be managed.",
962
+ "occurrences": 2,
963
+ "at": [
964
+ "modules/hall-ui/public/goals-render.js:976",
965
+ "modules/hall-ui/public/goals-render.js:992"
966
+ ]
967
+ },
968
+ {
969
+ "id": "fa818ee444b4",
970
+ "surface": "builders-hall",
971
+ "text": "Only this goal's owner or a manager (or an Archon) may do this.",
972
+ "occurrences": 2,
973
+ "at": [
974
+ "modules/hall-ui/public/goals-render.js:975",
975
+ "modules/hall-ui/public/goals-render.js:991"
976
+ ]
977
+ },
958
978
  {
959
979
  "id": "6912678b962b",
960
980
  "surface": "builders-hall",
@@ -1291,8 +1311,8 @@
1291
1311
  "text": "This goal is not open.",
1292
1312
  "occurrences": 2,
1293
1313
  "at": [
1294
- "modules/hall-ui/public/goals-render.js:934",
1295
- "modules/hall-ui/public/goals-render.js:950"
1314
+ "modules/hall-ui/public/goals-render.js:946",
1315
+ "modules/hall-ui/public/goals-render.js:962"
1296
1316
  ]
1297
1317
  },
1298
1318
  {
@@ -1812,7 +1832,7 @@
1812
1832
  "surface": "builders-hall",
1813
1833
  "text": "+ Invite",
1814
1834
  "file": "modules/hall-ui/public/goals-render.js",
1815
- "line": 882,
1835
+ "line": 894,
1816
1836
  "origin": "js-markup",
1817
1837
  "confidence": "certain"
1818
1838
  },
@@ -1992,7 +2012,7 @@
1992
2012
  "surface": "builders-hall",
1993
2013
  "text": "A protected-scope goal requires a Metic+ member.",
1994
2014
  "file": "modules/hall-ui/public/goals-render.js",
1995
- "line": 932,
2015
+ "line": 944,
1996
2016
  "origin": "js-notice",
1997
2017
  "confidence": "certain"
1998
2018
  },
@@ -2451,7 +2471,7 @@
2451
2471
  "surface": "builders-hall",
2452
2472
  "text": "Awaiting the owner/manager's approval",
2453
2473
  "file": "modules/hall-ui/public/goals-render.js",
2454
- "line": 781,
2474
+ "line": 788,
2455
2475
  "origin": "js-markup",
2456
2476
  "confidence": "certain"
2457
2477
  },
@@ -3319,6 +3339,15 @@
3319
3339
  "origin": "js-notice",
3320
3340
  "confidence": "certain"
3321
3341
  },
3342
+ {
3343
+ "id": "1ef1efc15b6c",
3344
+ "surface": "builders-hall",
3345
+ "text": "Could not change that just now.",
3346
+ "file": "modules/hall-ui/public/goals-render.js",
3347
+ "line": 993,
3348
+ "origin": "js-notice",
3349
+ "confidence": "certain"
3350
+ },
3322
3351
  {
3323
3352
  "id": "78e18ea59b9f",
3324
3353
  "surface": "builders-hall",
@@ -3387,7 +3416,7 @@
3387
3416
  "surface": "builders-hall",
3388
3417
  "text": "Could not invite that builder just now.",
3389
3418
  "file": "modules/hall-ui/public/goals-render.js",
3390
- "line": 935,
3419
+ "line": 947,
3391
3420
  "origin": "js-notice",
3392
3421
  "confidence": "certain"
3393
3422
  },
@@ -3864,7 +3893,7 @@
3864
3893
  "surface": "builders-hall",
3865
3894
  "text": "Could not send the request just now.",
3866
3895
  "file": "modules/hall-ui/public/goals-render.js",
3867
- "line": 979,
3896
+ "line": 1008,
3868
3897
  "origin": "js-notice",
3869
3898
  "confidence": "certain"
3870
3899
  },
@@ -3900,7 +3929,7 @@
3900
3929
  "surface": "builders-hall",
3901
3930
  "text": "Could not update visibility just now.",
3902
3931
  "file": "modules/hall-ui/public/goals-render.js",
3903
- "line": 965,
3932
+ "line": 977,
3904
3933
  "origin": "js-notice",
3905
3934
  "confidence": "certain"
3906
3935
  },
@@ -3936,7 +3965,7 @@
3936
3965
  "surface": "builders-hall",
3937
3966
  "text": "Could not {…} just now.",
3938
3967
  "file": "modules/hall-ui/public/goals-render.js",
3939
- "line": 951,
3968
+ "line": 963,
3940
3969
  "origin": "js-notice",
3941
3970
  "confidence": "certain"
3942
3971
  },
@@ -4449,7 +4478,7 @@
4449
4478
  "surface": "builders-hall",
4450
4479
  "text": "Everyone is already a member",
4451
4480
  "file": "modules/hall-ui/public/goals-render.js",
4452
- "line": 898,
4481
+ "line": 910,
4453
4482
  "origin": "js-markup",
4454
4483
  "confidence": "certain"
4455
4484
  },
@@ -4764,7 +4793,7 @@
4764
4793
  "surface": "builders-hall",
4765
4794
  "text": "Goals could not be loaded. Refresh to try again.",
4766
4795
  "file": "modules/hall-ui/public/goals-page.js",
4767
- "line": 867,
4796
+ "line": 889,
4768
4797
  "origin": "js-text-assign",
4769
4798
  "confidence": "certain"
4770
4799
  },
@@ -5182,6 +5211,15 @@
5182
5211
  "origin": "html-text",
5183
5212
  "confidence": "certain"
5184
5213
  },
5214
+ {
5215
+ "id": "dad3388b49c9",
5216
+ "surface": "builders-hall",
5217
+ "text": "Invitation rescinded.",
5218
+ "file": "modules/hall-ui/public/goals-page.js",
5219
+ "line": 692,
5220
+ "origin": "js-notice",
5221
+ "confidence": "certain"
5222
+ },
5185
5223
  {
5186
5224
  "id": "24f9e88c21f6",
5187
5225
  "surface": "builders-hall",
@@ -5196,7 +5234,7 @@
5196
5234
  "surface": "builders-hall",
5197
5235
  "text": "Invited.",
5198
5236
  "file": "modules/hall-ui/public/goals-render.js",
5199
- "line": 924,
5237
+ "line": 936,
5200
5238
  "origin": "js-notice",
5201
5239
  "confidence": "certain"
5202
5240
  },
@@ -5259,7 +5297,7 @@
5259
5297
  "surface": "builders-hall",
5260
5298
  "text": "Join goal",
5261
5299
  "file": "modules/hall-ui/public/goals-render.js",
5262
- "line": 787,
5300
+ "line": 798,
5263
5301
  "origin": "js-markup",
5264
5302
  "confidence": "certain"
5265
5303
  },
@@ -5277,7 +5315,7 @@
5277
5315
  "surface": "builders-hall",
5278
5316
  "text": "Join requests",
5279
5317
  "file": "modules/hall-ui/public/goals-render.js",
5280
- "line": 1012,
5318
+ "line": 1047,
5281
5319
  "origin": "js-markup",
5282
5320
  "confidence": "certain"
5283
5321
  },
@@ -5439,7 +5477,7 @@
5439
5477
  "surface": "builders-hall",
5440
5478
  "text": "Leave goal",
5441
5479
  "file": "modules/hall-ui/public/goals-render.js",
5442
- "line": 778,
5480
+ "line": 785,
5443
5481
  "origin": "js-markup",
5444
5482
  "confidence": "certain"
5445
5483
  },
@@ -6334,6 +6372,15 @@
6334
6372
  "origin": "html-text",
6335
6373
  "confidence": "certain"
6336
6374
  },
6375
+ {
6376
+ "id": "a290f6c14df9",
6377
+ "surface": "builders-hall",
6378
+ "text": "Not taking requests",
6379
+ "file": "modules/hall-ui/public/goals-render.js",
6380
+ "line": 793,
6381
+ "origin": "js-markup",
6382
+ "confidence": "certain"
6383
+ },
6337
6384
  {
6338
6385
  "id": "19adac0efed1",
6339
6386
  "surface": "builders-hall",
@@ -6501,7 +6548,16 @@
6501
6548
  "surface": "builders-hall",
6502
6549
  "text": "Only an open goal can be managed.",
6503
6550
  "file": "modules/hall-ui/public/goals-render.js",
6504
- "line": 964,
6551
+ "line": 976,
6552
+ "origin": "js-notice",
6553
+ "confidence": "certain"
6554
+ },
6555
+ {
6556
+ "id": "0bc1d1470e22",
6557
+ "surface": "builders-hall",
6558
+ "text": "Only an open goal can be managed.",
6559
+ "file": "modules/hall-ui/public/goals-render.js",
6560
+ "line": 992,
6505
6561
  "origin": "js-notice",
6506
6562
  "confidence": "certain"
6507
6563
  },
@@ -6510,7 +6566,16 @@
6510
6566
  "surface": "builders-hall",
6511
6567
  "text": "Only this goal's owner or a manager (or an Archon) may do this.",
6512
6568
  "file": "modules/hall-ui/public/goals-render.js",
6513
- "line": 963,
6569
+ "line": 975,
6570
+ "origin": "js-notice",
6571
+ "confidence": "certain"
6572
+ },
6573
+ {
6574
+ "id": "fa818ee444b4",
6575
+ "surface": "builders-hall",
6576
+ "text": "Only this goal's owner or a manager (or an Archon) may do this.",
6577
+ "file": "modules/hall-ui/public/goals-render.js",
6578
+ "line": 991,
6514
6579
  "origin": "js-notice",
6515
6580
  "confidence": "certain"
6516
6581
  },
@@ -6519,7 +6584,7 @@
6519
6584
  "surface": "builders-hall",
6520
6585
  "text": "Only this goal's owner or a manager (or an Archon) may invite.",
6521
6586
  "file": "modules/hall-ui/public/goals-render.js",
6522
- "line": 933,
6587
+ "line": 945,
6523
6588
  "origin": "js-notice",
6524
6589
  "confidence": "certain"
6525
6590
  },
@@ -6780,7 +6845,7 @@
6780
6845
  "surface": "builders-hall",
6781
6846
  "text": "Pending invitations",
6782
6847
  "file": "modules/hall-ui/public/goals-render.js",
6783
- "line": 1013,
6848
+ "line": 1048,
6784
6849
  "origin": "js-markup",
6785
6850
  "confidence": "certain"
6786
6851
  },
@@ -7410,7 +7475,7 @@
7410
7475
  "surface": "builders-hall",
7411
7476
  "text": "Request sent — the owner or a manager will review it.",
7412
7477
  "file": "modules/hall-ui/public/goals-render.js",
7413
- "line": 971,
7478
+ "line": 999,
7414
7479
  "origin": "js-notice",
7415
7480
  "confidence": "certain"
7416
7481
  },
@@ -7428,7 +7493,7 @@
7428
7493
  "surface": "builders-hall",
7429
7494
  "text": "Request withdrawn.",
7430
7495
  "file": "modules/hall-ui/public/goals-render.js",
7431
- "line": 986,
7496
+ "line": 1015,
7432
7497
  "origin": "js-notice",
7433
7498
  "confidence": "certain"
7434
7499
  },
@@ -7959,7 +8024,7 @@
7959
8024
  "surface": "builders-hall",
7960
8025
  "text": "Sign in to filter to your goals",
7961
8026
  "file": "modules/hall-ui/public/goals-page.js",
7962
- "line": 844,
8027
+ "line": 866,
7963
8028
  "origin": "js-text-assign",
7964
8029
  "confidence": "certain"
7965
8030
  },
@@ -8301,7 +8366,7 @@
8301
8366
  "surface": "builders-hall",
8302
8367
  "text": "Tasks could not be loaded.",
8303
8368
  "file": "modules/hall-ui/public/goals-render.js",
8304
- "line": 855,
8369
+ "line": 867,
8305
8370
  "origin": "js-markup",
8306
8371
  "confidence": "certain"
8307
8372
  },
@@ -8440,6 +8505,15 @@
8440
8505
  "origin": "js-markup",
8441
8506
  "confidence": "certain"
8442
8507
  },
8508
+ {
8509
+ "id": "4dd13bc5a35b",
8510
+ "surface": "builders-hall",
8511
+ "text": "The goal's leads have paused applications; they can still invite you",
8512
+ "file": "modules/hall-ui/public/goals-render.js",
8513
+ "line": 793,
8514
+ "origin": "js-markup",
8515
+ "confidence": "certain"
8516
+ },
8443
8517
  {
8444
8518
  "id": "797f02fa8295",
8445
8519
  "surface": "builders-hall",
@@ -8733,7 +8807,7 @@
8733
8807
  "surface": "builders-hall",
8734
8808
  "text": "This goal is Archon-mediated — request to join instead.",
8735
8809
  "file": "modules/hall-ui/public/goals-render.js",
8736
- "line": 947,
8810
+ "line": 959,
8737
8811
  "origin": "js-notice",
8738
8812
  "confidence": "certain"
8739
8813
  },
@@ -8751,7 +8825,7 @@
8751
8825
  "surface": "builders-hall",
8752
8826
  "text": "This goal is not open.",
8753
8827
  "file": "modules/hall-ui/public/goals-render.js",
8754
- "line": 934,
8828
+ "line": 946,
8755
8829
  "origin": "js-notice",
8756
8830
  "confidence": "certain"
8757
8831
  },
@@ -8760,7 +8834,7 @@
8760
8834
  "surface": "builders-hall",
8761
8835
  "text": "This goal is not open.",
8762
8836
  "file": "modules/hall-ui/public/goals-render.js",
8763
- "line": 950,
8837
+ "line": 962,
8764
8838
  "origin": "js-notice",
8765
8839
  "confidence": "certain"
8766
8840
  },
@@ -8769,7 +8843,7 @@
8769
8843
  "surface": "builders-hall",
8770
8844
  "text": "This goal is open — joining directly instead.",
8771
8845
  "file": "modules/hall-ui/public/goals-render.js",
8772
- "line": 978,
8846
+ "line": 1006,
8773
8847
  "origin": "js-notice",
8774
8848
  "confidence": "certain"
8775
8849
  },
@@ -8778,7 +8852,7 @@
8778
8852
  "surface": "builders-hall",
8779
8853
  "text": "This goal is private — request to join instead.",
8780
8854
  "file": "modules/hall-ui/public/goals-render.js",
8781
- "line": 948,
8855
+ "line": 960,
8782
8856
  "origin": "js-notice",
8783
8857
  "confidence": "certain"
8784
8858
  },
@@ -8787,7 +8861,16 @@
8787
8861
  "surface": "builders-hall",
8788
8862
  "text": "This goal is scoped to a protected module — only an Archon may add members.",
8789
8863
  "file": "modules/hall-ui/public/goals-render.js",
8790
- "line": 931,
8864
+ "line": 943,
8865
+ "origin": "js-notice",
8866
+ "confidence": "certain"
8867
+ },
8868
+ {
8869
+ "id": "ba404d3207f7",
8870
+ "surface": "builders-hall",
8871
+ "text": "This goal isn't taking requests right now.",
8872
+ "file": "modules/hall-ui/public/goals-render.js",
8873
+ "line": 1007,
8791
8874
  "origin": "js-notice",
8792
8875
  "confidence": "certain"
8793
8876
  },
@@ -9399,7 +9482,7 @@
9399
9482
  "surface": "builders-hall",
9400
9483
  "text": "You already have a pending request or invitation for this goal.",
9401
9484
  "file": "modules/hall-ui/public/goals-render.js",
9402
- "line": 976,
9485
+ "line": 1004,
9403
9486
  "origin": "js-notice",
9404
9487
  "confidence": "certain"
9405
9488
  },
@@ -9408,7 +9491,7 @@
9408
9491
  "surface": "builders-hall",
9409
9492
  "text": "You are already a member of this goal.",
9410
9493
  "file": "modules/hall-ui/public/goals-render.js",
9411
- "line": 977,
9494
+ "line": 1005,
9412
9495
  "origin": "js-notice",
9413
9496
  "confidence": "certain"
9414
9497
  },
@@ -9417,7 +9500,7 @@
9417
9500
  "surface": "builders-hall",
9418
9501
  "text": "You are not a member of this goal.",
9419
9502
  "file": "modules/hall-ui/public/goals-render.js",
9420
- "line": 949,
9503
+ "line": 961,
9421
9504
  "origin": "js-notice",
9422
9505
  "confidence": "certain"
9423
9506
  },
@@ -10317,7 +10400,7 @@
10317
10400
  "surface": "builders-hall",
10318
10401
  "text": "invited — awaiting their response",
10319
10402
  "file": "modules/hall-ui/public/goals-render.js",
10320
- "line": 1007,
10403
+ "line": 1039,
10321
10404
  "origin": "js-markup",
10322
10405
  "confidence": "certain"
10323
10406
  },
@@ -11784,7 +11867,7 @@
11784
11867
  "surface": "builders-hall",
11785
11868
  "text": "🔒 Request to join",
11786
11869
  "file": "modules/hall-ui/public/goals-render.js",
11787
- "line": 784,
11870
+ "line": 795,
11788
11871
  "origin": "js-markup",
11789
11872
  "confidence": "certain"
11790
11873
  },
@@ -0,0 +1,37 @@
1
+ -- core_229_goals_accepting_requests.sql — a private goal can be CLOSED to
2
+ -- join-requests without being closed as a goal (task 1002971, spec
3
+ -- docs/specs/bongos-v1-privacy-and-invites.md D6).
4
+ --
5
+ -- core_188 gave a goal `visibility`, and a private goal has always accepted
6
+ -- join-requests: the only "we're not taking applications" a lead could express
7
+ -- was to archive the goal, which is a different (and destructive) statement.
8
+ -- `accepting_requests` separates the two: a private goal with it FALSE still
9
+ -- runs, still invites, still shows in the list — it just refuses new
10
+ -- join-requests with honest copy instead of queueing asks nobody will answer.
11
+ --
12
+ -- INVITATIONS ARE UNAFFECTED BY DESIGN. Closing applications is about unsolicited
13
+ -- inbound; a lead reaching out to a specific builder is the deliberate act the
14
+ -- flag exists to protect. The flag is likewise MEANINGLESS on a public goal
15
+ -- (self-join needs no request), which is why the toggle renders only on private
16
+ -- goals — the column still exists on every row so the write path needs no
17
+ -- branch, and the refusal gate reads visibility first.
18
+ --
19
+ -- goals is a CORE-owned table (created in 160_goal_tier; ADR 0083 §Decision 5 —
20
+ -- a module migration may touch only its own <key>_ objects), so this lives in
21
+ -- core migrations/, core_-namespaced (ADR 0108 §5: a core migration numbered
22
+ -- above LEGACY_BARE_CORE_MAX=187 must carry the core_ prefix, else the fitness
23
+ -- namespacing check reds the build).
24
+ --
25
+ -- PURELY ADDITIVE + idempotent: DEFAULT true means every existing goal keeps
26
+ -- today's behaviour (a private goal takes requests), NOT NULL is safe because
27
+ -- the default backfills, and IF NOT EXISTS makes a replay a no-op.
28
+ -- Fresh-DB-replay safe; no hard-coded builder ids (learning 1238). migrate.sh
29
+ -- records this file's stem in schema_migrations (the modern 187 pattern — the
30
+ -- file does not self-insert).
31
+
32
+ BEGIN;
33
+
34
+ ALTER TABLE goals
35
+ ADD COLUMN IF NOT EXISTS accepting_requests boolean NOT NULL DEFAULT true;
36
+
37
+ COMMIT;
@@ -324,6 +324,8 @@ function createClient(opts = {}) {
324
324
  getGoalsId: (args) => request("GET", "/goals/{id}", { hasBody: false }, args),
325
325
  // PATCH /goals/{id} — rank: any-builder — PATCH /goals/:id
326
326
  patchGoalsId: (args) => request("PATCH", "/goals/{id}", { hasBody: true }, args),
327
+ // PATCH /goals/{id}/accepting-requests — rank: any-builder — PATCH /goals/:id/accepting-requests
328
+ patchGoalsIdAcceptingRequests: (args) => request("PATCH", "/goals/{id}/accepting-requests", { hasBody: true }, args),
327
329
  // POST /goals/{id}/archive — rank: metic+archon — POST /goals/:id/archive
328
330
  postGoalsIdArchive: (args) => request("POST", "/goals/{id}/archive", { hasBody: true }, args),
329
331
  // PATCH /goals/{id}/category — rank: any-builder — PATCH /goals/:id/category
@@ -334,6 +336,8 @@ function createClient(opts = {}) {
334
336
  postGoalsIdConflictsResolve: (args) => request("POST", "/goals/{id}/conflicts/resolve", { hasBody: true }, args),
335
337
  // POST /goals/{id}/invitations — rank: any-builder — POST /goals/:id/invitations
336
338
  postGoalsIdInvitations: (args) => request("POST", "/goals/{id}/invitations", { hasBody: true }, args),
339
+ // POST /goals/{id}/invitations/{reqId}/rescind — rank: any-builder — POST /goals/:id/invitations/:reqId/rescind
340
+ postGoalsIdInvitationsReqIdRescind: (args) => request("POST", "/goals/{id}/invitations/{reqId}/rescind", { hasBody: true }, args),
337
341
  // POST /goals/{id}/invitations/{reqId}/respond — rank: any-builder — POST /goals/:id/invitations/:reqId/respond
338
342
  postGoalsIdInvitationsReqIdRespond: (args) => request("POST", "/goals/{id}/invitations/{reqId}/respond", { hasBody: true }, args),
339
343
  // POST /goals/{id}/join — rank: any-builder — POST /goals/:id/join
@@ -678,9 +678,31 @@
678
678
  }
679
679
  }
680
680
 
681
+ // rescindInvitation (task 1002971) — the goal's own authority takes back an
682
+ // invitation nobody has answered yet, the manager-side mirror of the
683
+ // requester's Withdraw. Shares respondToJoinRequest's in-flight guard so a
684
+ // double-click can't fire two rescinds at one row.
685
+ async function rescindInvitation(goalId, reqId, btn) {
686
+ const key = `rescind:${reqId}`;
687
+ if (pendingActionsPending.has(key)) return;
688
+ pendingActionsPending.add(key);
689
+ if (btn) btn.disabled = true;
690
+ try {
691
+ await postJSON(`${API}/goals/${encodeURIComponent(goalId)}/invitations/${encodeURIComponent(reqId)}/rescind`, {});
692
+ toast('Invitation rescinded.');
693
+ await loadDetail(goalId, { force: true });
694
+ renderDetail(goalId);
695
+ } catch (err) {
696
+ toast((err && err.data && err.data.message) || 'Could not rescind that invitation just now.');
697
+ if (btn) btn.disabled = false;
698
+ } finally {
699
+ pendingActionsPending.delete(key);
700
+ }
701
+ }
702
+
681
703
  // task 1953 — every pending row on THIS goal, rendered for a manager: join
682
- // requests get Approve/Deny; outstanding invites are informational (only the
683
- // invitee can accept/decline theirs — the server has no manager-cancel route).
704
+ // requests get Approve/Deny; an outstanding invite the goal sent can be
705
+ // rescinded (task 1002971), and only the invitee can accept/decline theirs.
684
706
 
685
707
  // task 1953 — async membership-queue load, kicked off (fire-and-forget) from
686
708
  // renderDetail like loadGoalTasks: my own pending request on this goal (any
@@ -1060,6 +1082,8 @@
1060
1082
  if (approveBtn) { respondToJoinRequest(state.current, approveBtn.dataset.approveReq, 'approve', approveBtn); return; }
1061
1083
  const denyBtn = e.target.closest('[data-deny-req]');
1062
1084
  if (denyBtn) { respondToJoinRequest(state.current, denyBtn.dataset.denyReq, 'deny', denyBtn); return; }
1085
+ const rescindBtn = e.target.closest('[data-rescind-inv]');
1086
+ if (rescindBtn) { rescindInvitation(state.current, rescindBtn.dataset.rescindInv, rescindBtn); return; }
1063
1087
  });
1064
1088
  window.addEventListener('hashchange', route);
1065
1089
 
@@ -772,6 +772,13 @@
772
772
  const nextVisibility = g.visibility === 'private' ? 'public' : 'private';
773
773
  const label = g.visibility === 'private' ? '🔒 Private — make public' : 'Make private';
774
774
  parts.push(`<button class="btn-ghost" id="goal-visibility-btn" type="button" data-next-visibility="${escapeHtml(nextVisibility)}">${label}</button>`);
775
+ // task 1002971 (spec D6) — "closed to applications" is a PRIVATE-goal idea
776
+ // only: a public goal is self-join, so the toggle would say nothing there.
777
+ // Closing never touches invitations — a lead can always still invite.
778
+ if (g.visibility === 'private') {
779
+ const closing = g.accepting_requests !== false;
780
+ parts.push(`<button class="btn-ghost" id="goal-accepting-btn" type="button" data-next-accepting="${closing ? 'false' : 'true'}" title="${closing ? 'Stop taking join-requests; you can still invite people' : 'Take join-requests again'}">${closing ? 'Close to requests' : 'Open to requests'}</button>`);
781
+ }
775
782
  }
776
783
  if (g.status === 'open') {
777
784
  if (joined) {
@@ -780,6 +787,10 @@
780
787
  if (state.myPendingRequest) {
781
788
  parts.push('<span class="member-pill" title="Awaiting the owner/manager\'s approval">Requested</span>');
782
789
  parts.push('<button class="btn-ghost" id="goal-withdraw-btn" type="button">Withdraw</button>');
790
+ } else if (g.visibility === 'private' && g.accepting_requests === false) {
791
+ // task 1002971 — say so instead of offering a button the server will
792
+ // refuse; the same honest copy the API answers with.
793
+ parts.push('<span class="member-pill" title="The goal\'s leads have paused applications; they can still invite you">Not taking requests</span>');
783
794
  } else {
784
795
  parts.push('<button class="btn-ghost" id="goal-request-btn" type="button">🔒 Request to join</button>');
785
796
  }
@@ -791,6 +802,7 @@
791
802
  $('#goal-join-btn')?.addEventListener('click', () => joinLeave(g.id, 'join'));
792
803
  $('#goal-leave-btn')?.addEventListener('click', () => joinLeave(g.id, 'leave'));
793
804
  $('#goal-visibility-btn')?.addEventListener('click', (e) => toggleVisibility(g.id, e.currentTarget.dataset.nextVisibility));
805
+ $('#goal-accepting-btn')?.addEventListener('click', (e) => toggleAcceptingRequests(g.id, e.currentTarget.dataset.nextAccepting === 'true'));
794
806
  $('#goal-request-btn')?.addEventListener('click', () => requestToJoin(g.id));
795
807
  $('#goal-withdraw-btn')?.addEventListener('click', () => withdrawRequest(g.id, state.myPendingRequest && state.myPendingRequest.id));
796
808
  }
@@ -965,6 +977,22 @@
965
977
  else toast('Could not update visibility just now.');
966
978
  }
967
979
  }
980
+ // task 1002971 (spec D6) — open/close a PRIVATE goal to join-requests. Closing
981
+ // refuses new asks with honest copy; it never affects invitations, which is why
982
+ // the toast says so out loud rather than reading as "the goal is shut".
983
+ async function toggleAcceptingRequests(goalId, accepting) {
984
+ try {
985
+ await patchJSON(`${API}/goals/${encodeURIComponent(goalId)}/accepting-requests`, { accepting_requests: accepting });
986
+ toast(accepting ? 'This goal is taking join-requests again.' : 'Closed to join-requests — you can still invite people.');
987
+ await loadDetail(goalId, { force: true });
988
+ renderDetail(goalId);
989
+ } catch (err) {
990
+ const code = err?.data?.error;
991
+ if (code === 'not_owner_or_manager') toast('Only this goal\'s owner or a manager (or an Archon) may do this.');
992
+ else if (code === 'goal_not_open') toast('Only an open goal can be managed.');
993
+ else toast('Could not change that just now.');
994
+ }
995
+ }
968
996
  async function requestToJoin(goalId) {
969
997
  try {
970
998
  await postJSON(`${API}/goals/${encodeURIComponent(goalId)}/requests`, {});
@@ -976,6 +1004,7 @@
976
1004
  if (code === 'request_pending') toast('You already have a pending request or invitation for this goal.');
977
1005
  else if (code === 'already_a_member') toast('You are already a member of this goal.');
978
1006
  else if (code === 'goal_is_open') toast('This goal is open — joining directly instead.');
1007
+ else if (code === 'not_accepting_requests') toast('This goal isn\'t taking requests right now.');
979
1008
  else toast('Could not send the request just now.');
980
1009
  }
981
1010
  }
@@ -1004,9 +1033,15 @@
1004
1033
  <button class="btn-ghost" type="button" data-deny-req="${escapeHtml(String(r.id))}">Deny</button>
1005
1034
  </span>
1006
1035
  </li>`;
1036
+ // task 1002971 — an invitation the goal SENT can now be taken back by the
1037
+ // people who sent it (core_188's 'cancelled' = "withdrawn/rescinded by the
1038
+ // opener"); before this the only un-send was asking the invitee to decline.
1007
1039
  const inviteLi = (r) => `<li class="pending-row">
1008
1040
  <span class="pending-row__who">${escapeHtml(pendingRequestWho(r))}</span>
1009
1041
  <span class="pending-row__note">invited — awaiting their response</span>
1042
+ <span class="pending-row__actions">
1043
+ <button class="btn-ghost" type="button" data-rescind-inv="${escapeHtml(String(r.id))}">Rescind</button>
1044
+ </span>
1010
1045
  </li>`;
1011
1046
  const parts = [];
1012
1047
  if (requests.length) parts.push(`<div class="pending-group"><h3 class="pending-group__h">Join requests<span class="count-pill">${requests.length}</span></h3><ul class="pending-list">${requests.map(requestLi).join('')}</ul></div>`);
@@ -32,7 +32,7 @@ const { deriveRequiredRank, highestRank } = require('./db-rank-authz.js');
32
32
  // Shared column list so every goal read/return shapes a row identically.
33
33
 
34
34
  const GOAL_COLS =
35
- 'id, version_id, title, subtitle, description, scope_modules, status, visibility, created_by, succeeded_by_goal_id, sort_order, category_id, created_at, updated_at';
35
+ 'id, version_id, title, subtitle, description, scope_modules, status, visibility, accepting_requests, created_by, succeeded_by_goal_id, sort_order, category_id, created_at, updated_at';
36
36
 
37
37
  // Shared column list for a goal_membership_requests row (task 1951 / ADR 0112) —
38
38
  // the invite / join-request queue behind private goals. Every helper below shapes
@@ -567,6 +567,26 @@ async function setGoalVisibility({ goalId, visibility }, deps = {}) {
567
567
  return rows[0] ?? null;
568
568
  }
569
569
 
570
+ // setGoalAcceptingRequests — open or close a PRIVATE goal to new join-requests
571
+ // (task 1002971, spec D6). false = closed: the request path refuses with honest
572
+ // copy instead of queueing asks nobody will answer; invitations keep working, so
573
+ // a lead can still reach out to a specific builder. Meaningless on a public goal
574
+ // (self-join needs no request) — the column exists on every row, and the refusal
575
+ // gate is what reads visibility. Bumps updated_at. Returns the updated goal row,
576
+ // or null for an unknown id. The route gates WHO may call this
577
+ // (authorizeGoalManagement: owner|manager|Archon); this helper just writes.
578
+ async function setGoalAcceptingRequests({ goalId, acceptingRequests }, deps = {}) {
579
+ const activePool = (deps.pool && process.env.NODE_ENV === 'test') ? deps.pool : pool;
580
+ const { rows } = await activePool.query(
581
+ `UPDATE goals
582
+ SET accepting_requests = $2, updated_at = now()
583
+ WHERE id = $1
584
+ RETURNING ${GOAL_COLS}`,
585
+ [goalId, acceptingRequests]
586
+ );
587
+ return rows[0] ?? null;
588
+ }
589
+
570
590
  // createMembershipRequest — open a pending invite (kind='invite', a manager asks
571
591
  // builder_id in) or join-request (kind='request', the builder asks to join). The
572
592
  // partial-unique index (one pending row per goal+builder) means a duplicate ask
@@ -758,6 +778,7 @@ module.exports = {
758
778
  setGoalMembershipKind,
759
779
  setGoalStatus,
760
780
  setGoalSuccessor,
781
+ setGoalAcceptingRequests,
761
782
  setGoalVisibility,
762
783
  transferGoalOwnership,
763
784
  };