@bongos/core 1.19.511 → 1.19.513
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 +85 -50
- package/clients/bongos-client/README.md +1 -1
- package/clients/bongos-client/bongos-client.global.js +2 -0
- package/clients/bongos-client/index.cjs +2 -0
- package/clients/bongos-client/index.d.ts +3 -0
- package/clients/bongos-client/index.mjs +2 -0
- package/config/branding.neutral.json +3 -2
- package/docs/adr/0238-the-join-door-is-a-provisioning-settings-policy-key.md +57 -0
- package/docs/adr/0239-a-recommendation-is-a-table-that-carries-its-own-reason.md +177 -0
- package/docs/adr/README.md +2 -0
- package/docs/api/openapi.json +40 -3
- package/docs/api-reference.md +3 -2
- package/docs/copy-inventory.md +158 -151
- package/docs/copy-registry.json +313 -229
- package/docs/module-api-changelog.md +4 -0
- package/modules/dev-box/app/src/vendor/bongos-client.cjs +2 -0
- package/modules/hall-ui/public/hall-kit.js +309 -0
- package/modules/hall-ui/public/hall-render.js +10 -38
- package/modules/hall-ui/public/profile-charts.js +58 -65
- package/modules/hall-ui/public/profile.css +9 -18
- package/modules/hall-ui/public/style.css +101 -15
- package/modules/hall-ui/public/watch.css +6 -14
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/watch.js +139 -54
- package/modules/provisioning/provisioning.js +27 -1
- package/modules/provisioning/recommendations.js +260 -0
- package/modules/provisioning/routes/provisioning.js +29 -0
- package/modules/provisioning/tests/provisioning.mjs +17 -14
- package/modules/public-landing/public/projects.html +125 -0
- package/modules/ui-design/kit/serve.js +4 -3
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/branding.js +9 -0
- package/src/module-api.js +1 -1
- package/tests/currency_label.mjs +5 -4
- package/tests/hall_audit.mjs +84 -0
- package/tests/hall_kit_charts.mjs +328 -0
- package/tests/hall_profile_world.mjs +27 -6
- package/tests/helpers.mjs +161 -0
- package/tests/joinability_invite_only.mjs +10 -2
- package/tests/project_door_ui.mjs +267 -0
- package/tests/projects_hub.mjs +73 -1
- package/tests/projects_hub_pre_uat.mjs +4 -1
- package/tests/provision_settings_apply.mjs +8 -7
- package/tests/provisioning_recommendations.mjs +448 -0
- package/tests/provisioning_settings.mjs +65 -6
- package/tests/provisioning_settings_apply.mjs +3 -3
- package/tests/provisioning_settings_env.mjs +10 -8
- package/tests/task_detail_ui.mjs +1 -153
- package/tests/watch_applications_queue.mjs +381 -0
|
@@ -134,17 +134,11 @@
|
|
|
134
134
|
}
|
|
135
135
|
.pchart__title { margin: 0; font-size: 13.5px; font-weight: 500; color: var(--ink); }
|
|
136
136
|
.pchart__read { margin: 2px 0 var(--sp-hair); font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-soft); min-height: 1.4em; }
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.
|
|
141
|
-
|
|
142
|
-
.pchart__hair { stroke: var(--ink-faint); }
|
|
143
|
-
.pchart__dot { fill: var(--accent); stroke: var(--bg-card); stroke-width: 2; }
|
|
144
|
-
.pchart__col { fill: var(--accent); }
|
|
145
|
-
.pchart__col--empty { fill: var(--rule-soft); }
|
|
146
|
-
.pchart__end { font-family: var(--font-body); font-size: 11.5px; font-weight: 500; fill: var(--ink); }
|
|
147
|
-
.pchart__tick { font-family: var(--label-face); font-size: 10px; letter-spacing: var(--label-space); text-transform: var(--label-case); fill: var(--ink-faint); }
|
|
137
|
+
/* The plot itself — the svg, the line, the columns, the axis, the crosshair,
|
|
138
|
+
the ticks — is OTBKit's `.kit-plot*` in style.css (task 1003445). It was
|
|
139
|
+
written twice in this file's own script, and the goal's next charts want it
|
|
140
|
+
again. The dataviz spec moved verbatim; what stays below is this page's
|
|
141
|
+
FIGURE: the caption, the live readout, the table twin and the foot. */
|
|
148
142
|
.pchart__twin { margin-top: var(--sp-hair); }
|
|
149
143
|
.pchart__twin > summary { display: inline-flex; align-items: center; min-height: 24px; padding: 0 4px; margin: 0 -4px; font-family: var(--label-face); font-size: var(--label-size); letter-spacing: var(--label-space); text-transform: var(--label-case); color: var(--ink-soft); cursor: pointer; border-radius: var(--radius-sm); }
|
|
150
144
|
.pchart__twin > summary:hover { color: var(--ink); background: var(--glass-strong); }
|
|
@@ -153,13 +147,10 @@
|
|
|
153
147
|
.pchart__foot { margin: var(--sp-hair) 0 0; font-size: 12px; color: var(--ink-faint); }
|
|
154
148
|
.pchart__note { margin: 0; font-size: 14px; color: var(--ink-soft); }
|
|
155
149
|
/* the craft mix: label, bar, count — a row is its own table row */
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
.pmix__fill { display: block; height: 100%; background: var(--accent); border-radius: 0 4px 4px 0; }
|
|
161
|
-
.pmix__row:hover .pmix__fill { opacity: .8; }
|
|
162
|
-
.pmix__n { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
|
|
150
|
+
/* The craft mix is OTBKit.barSeriesHtml (task 1003445) — the .pmix family was
|
|
151
|
+
the hall's third copy of one horizontal bar. Only the vertical air around it
|
|
152
|
+
is this page's; the row, the track and the 24px floor are the kit's. */
|
|
153
|
+
.pchart .kit-bars { margin: var(--sp-hair) 0; }
|
|
163
154
|
@media (max-width: 720px) {
|
|
164
155
|
.pcharts { grid-template-columns: minmax(0, 1fr); }
|
|
165
156
|
}
|
|
@@ -1944,24 +1944,110 @@ main [id] { scroll-margin-top: calc(var(--topbar-h) + 16px); }
|
|
|
1944
1944
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
1945
1945
|
gap: 12px;
|
|
1946
1946
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1947
|
+
/* ---------- hall-kit charts (task 1003445) ----------
|
|
1948
|
+
The hall's ONE chart vocabulary. Before this the same horizontal bar was
|
|
1949
|
+
written three times — the Curve's kinds, Watch's Review-quality trend, and
|
|
1950
|
+
the profile's craft mix — each with its own class family, geometry and tone,
|
|
1951
|
+
and the line chart a fourth family beside them.
|
|
1952
|
+
|
|
1953
|
+
Three deliberate changes came with the merge, all of them the kit's defaults
|
|
1954
|
+
winning over a copy that had drifted: every row now holds the hall's 24px
|
|
1955
|
+
FLOOR (only the profile's copy remembered it, so the Curve's kind rows grow
|
|
1956
|
+
a few px), the hover dim applies to all three rather than the profile alone,
|
|
1957
|
+
and the value cell right-aligns (a no-op where its column is `auto`).
|
|
1958
|
+
Everything else is declaration-for-declaration what the three families
|
|
1959
|
+
said. */
|
|
1960
|
+
|
|
1961
|
+
.kit-chart { background: var(--surface-2); border-radius: var(--radius-sm); padding: 10px 14px 12px; }
|
|
1962
|
+
.kit-chart__title { font-size: 12px; font-weight: 600; color: var(--ink-soft); margin: 0 0 6px; }
|
|
1963
|
+
.kit-chart__empty { font-size: 12px; color: var(--ink-faint); }
|
|
1964
|
+
.kit-chart__foot { font-size: 11.5px; color: var(--ink-faint); margin-top: 4px; }
|
|
1965
|
+
|
|
1966
|
+
.kit-spark { width: 100%; height: 44px; display: block; }
|
|
1967
|
+
/* The stroke/fill here are the FALLBACK: sparklineHtml writes the caller's
|
|
1968
|
+
colour inline, because the Curve spends a different accent per chart. */
|
|
1969
|
+
.kit-spark__line { fill: none; stroke: var(--accent); stroke-width: 2; }
|
|
1970
|
+
.kit-spark__area { fill: color-mix(in srgb, var(--accent) 14%, transparent); stroke: none; }
|
|
1971
|
+
|
|
1972
|
+
/* Every difference between the three call sites is one of these six custom
|
|
1973
|
+
properties, which is what makes the fifth chart free: pass barSeriesHtml's
|
|
1974
|
+
labelWidth / valueWidth / barHeight / radius / track options, never a new
|
|
1975
|
+
class. */
|
|
1976
|
+
.kit-bars {
|
|
1977
|
+
--kit-bars-label-w: 110px;
|
|
1978
|
+
--kit-bars-val-w: auto;
|
|
1979
|
+
--kit-bars-h: 8px;
|
|
1980
|
+
--kit-bars-radius: 4px;
|
|
1981
|
+
--kit-bars-track: var(--surface-2);
|
|
1982
|
+
--kit-bars-fill: var(--accent);
|
|
1983
|
+
display: flex;
|
|
1984
|
+
flex-direction: column;
|
|
1985
|
+
gap: 6px;
|
|
1986
|
+
}
|
|
1987
|
+
.kit-bars__row {
|
|
1988
|
+
display: grid;
|
|
1989
|
+
grid-template-columns: var(--kit-bars-label-w) minmax(0, 1fr) var(--kit-bars-val-w);
|
|
1990
|
+
align-items: center;
|
|
1991
|
+
gap: 10px;
|
|
1992
|
+
/* The hall's 24px floor, held here so no call site has to remember it —
|
|
1993
|
+
the profile's mix was the only one of the three that did. */
|
|
1994
|
+
min-height: 24px;
|
|
1995
|
+
font-size: 12.5px;
|
|
1996
|
+
font-variant-numeric: tabular-nums;
|
|
1951
1997
|
}
|
|
1952
|
-
.
|
|
1953
|
-
.
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1998
|
+
.kit-bars__label { color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1999
|
+
.kit-bars__track {
|
|
2000
|
+
display: block;
|
|
2001
|
+
height: var(--kit-bars-h);
|
|
2002
|
+
background: var(--kit-bars-track);
|
|
2003
|
+
border-radius: var(--kit-bars-radius);
|
|
2004
|
+
overflow: hidden;
|
|
2005
|
+
}
|
|
2006
|
+
.kit-bars__fill {
|
|
2007
|
+
display: block;
|
|
2008
|
+
height: 100%;
|
|
2009
|
+
background: var(--kit-bars-fill);
|
|
2010
|
+
border-radius: var(--kit-bars-radius);
|
|
2011
|
+
/* A re-render (an SSE nudge, a lookback change) grows the bar to its new
|
|
2012
|
+
width rather than jumping. First paint does not animate: a freshly
|
|
2013
|
+
inserted element has no previous width to transition from. The hall's ONE
|
|
2014
|
+
reduced-motion kill switch (§the kill switch, at the end of this sheet)
|
|
2015
|
+
already zeroes this with !important — do NOT add a second @media block
|
|
2016
|
+
here, which is what `tests/hall_tokens.mjs` forbids and why the rule
|
|
2017
|
+
cannot rot in two places. */
|
|
2018
|
+
transition: width 200ms ease;
|
|
2019
|
+
}
|
|
2020
|
+
.kit-bars__row:hover .kit-bars__fill { opacity: .8; }
|
|
2021
|
+
.kit-bars__val { text-align: right; color: var(--ink-faint); }
|
|
2022
|
+
/* The neutral tone: a series read as data rather than as the page's own
|
|
2023
|
+
accent — Watch's trend, where the accent belongs to the pressable. */
|
|
2024
|
+
.kit-bars--neutral {
|
|
2025
|
+
--kit-bars-track: var(--rule-soft);
|
|
2026
|
+
--kit-bars-fill: var(--ink-soft);
|
|
2027
|
+
}
|
|
2028
|
+
.kit-bars--neutral .kit-bars__val { color: var(--ink); }
|
|
2029
|
+
|
|
2030
|
+
/* The PLOT: a step line or a column series on a time axis. Moved verbatim from
|
|
2031
|
+
profile.css's .pchart__* (task 1003445), where the same frame was drawn twice
|
|
2032
|
+
by profile-charts.js. The dataviz spec here is measured, not chosen — a 2px
|
|
2033
|
+
accent line, a 10% wash, a hairline solid axis, an r=4 end dot with a 2px
|
|
2034
|
+
card-coloured ring, and the ONE labelled value in ink rather than the series
|
|
2035
|
+
colour. tests/hall_kit_charts.mjs pins all five; do not hand-tune one.
|
|
2036
|
+
`tabindex` is on the svg because the chart is walkable by arrow key, so the
|
|
2037
|
+
focus ring is a real control's, not decoration. */
|
|
2038
|
+
.kit-plot { display: block; width: 100%; height: auto; overflow: visible; border-radius: var(--radius-sm); }
|
|
2039
|
+
.kit-plot:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
|
2040
|
+
.kit-plot__line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
|
|
2041
|
+
.kit-plot__area { fill: color-mix(in srgb, var(--accent) 10%, transparent); stroke: none; }
|
|
2042
|
+
.kit-plot__axis, .kit-plot__hair { stroke: var(--rule-soft); stroke-width: 1; }
|
|
2043
|
+
.kit-plot__hair { stroke: var(--ink-faint); }
|
|
2044
|
+
.kit-plot__dot { fill: var(--accent); stroke: var(--bg-card); stroke-width: 2; }
|
|
2045
|
+
.kit-plot__col { fill: var(--accent); }
|
|
2046
|
+
.kit-plot__col--empty { fill: var(--rule-soft); }
|
|
2047
|
+
.kit-plot__end { font-family: var(--font-body); font-size: 11.5px; font-weight: 500; fill: var(--ink); }
|
|
2048
|
+
.kit-plot__tick { font-family: var(--label-face); font-size: 10px; letter-spacing: var(--label-space); text-transform: var(--label-case); fill: var(--ink-faint); }
|
|
1958
2049
|
|
|
1959
2050
|
.curve__kinds { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; }
|
|
1960
|
-
.curve__kind { display: grid; grid-template-columns: 110px minmax(0, 1fr) auto; align-items: center; gap: 10px; font-size: 12.5px; }
|
|
1961
|
-
.curve__kind-label { color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
1962
|
-
.curve__kind-bar { height: 8px; background: var(--surface-2); border-radius: 4px; overflow: hidden; }
|
|
1963
|
-
.curve__kind-fill { height: 100%; background: var(--accent); border-radius: 4px; }
|
|
1964
|
-
.curve__kind-n { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
|
|
1965
2051
|
|
|
1966
2052
|
/* ---------- onboarding checklist ---------- */
|
|
1967
2053
|
|
|
@@ -55,20 +55,12 @@
|
|
|
55
55
|
/* The 14-day grade trend: a day, a bar, then the count and score. The bar is a
|
|
56
56
|
measurement, not a control, so it is ink on a hairline track and never the
|
|
57
57
|
accent. */
|
|
58
|
-
|
|
59
|
-
.watch-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
min-height: 28px;
|
|
65
|
-
font-size: 13px;
|
|
66
|
-
color: var(--ink-soft);
|
|
67
|
-
font-variant-numeric: tabular-nums;
|
|
68
|
-
}
|
|
69
|
-
.watch-trend__bar-track { height: 6px; overflow: hidden; border-radius: var(--radius-pill); background: var(--rule-soft); }
|
|
70
|
-
.watch-trend__bar-fill { display: block; height: 100%; background: var(--ink-soft); }
|
|
71
|
-
.watch-trend__val { text-align: right; color: var(--ink); }
|
|
58
|
+
/* The Review-quality trend is OTBKit.barSeriesHtml in its neutral tone (task
|
|
59
|
+
1003445) — the .watch-trend family was the hall's second copy of one
|
|
60
|
+
horizontal bar. What stays here is this page's own density: a taller row and
|
|
61
|
+
more air between the three columns than the kit's default. */
|
|
62
|
+
#mason-marks-trend .kit-bars { margin-top: 4px; gap: 0; }
|
|
63
|
+
#mason-marks-trend .kit-bars__row { min-height: 28px; gap: 12px; font-size: 13px; }
|
|
72
64
|
|
|
73
65
|
/* A link inside a ledger cell (a session's tasks) is a control at a 13.5px line
|
|
74
66
|
box: the padding buys the 24px floor and the negative margin hands it back. */
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
<!-- 1. ACCESS REQUESTS -->
|
|
60
60
|
<section class="scroll" id="gate-scroll" aria-labelledby="gate-h" hidden>
|
|
61
61
|
<h2 id="gate-h" class="scroll__h">Access requests</h2>
|
|
62
|
-
<p class="scroll__lede">People asking to join. Approve or dismiss each request.</p>
|
|
62
|
+
<p class="scroll__lede">People asking to join. Approve or dismiss each request, re-read the ones you already decided, and take back an approval before that person has signed in.</p>
|
|
63
63
|
<div id="gate-body"><span class="kit-skel"></span></div>
|
|
64
64
|
</section>
|
|
65
65
|
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
// family's .ov-figs, every sub-section is an .ov-group head, the access requests
|
|
27
27
|
// are oversight rows, and the five most recent sessions the page always fetched
|
|
28
28
|
// and threw away are now rendered under the tally.
|
|
29
|
+
//
|
|
30
|
+
// TASK 1002332 made the access-requests section a REVIEW queue rather than a
|
|
31
|
+
// decision button: every row says whether the platform vouched for the name on
|
|
32
|
+
// it (ADR 0201 — the public unauthenticated write means anyone may apply as
|
|
33
|
+
// anyone, so vouched/unvouched is load-bearing, not decoration), the three
|
|
34
|
+
// lifecycle states sit behind one switch, and an approval can be RESCINDED
|
|
35
|
+
// before its holder ever signs in — a transition the route has always taken and
|
|
36
|
+
// nothing on this page offered (ADR 0208).
|
|
29
37
|
|
|
30
38
|
(() => {
|
|
31
39
|
'use strict';
|
|
@@ -43,7 +51,7 @@
|
|
|
43
51
|
const { escapeHtml, $, num, fmtInt, fmtCompact } = window.OTB;
|
|
44
52
|
// Shared component kit (hall-kit.js, task 2166): tabs, ledger, pager, empty
|
|
45
53
|
// states — adopted here by task 2173, and by every list since task 1003469.
|
|
46
|
-
const { makeTabs, makeLedger, makePager, emptyStateHtml } = window.OTBKit;
|
|
54
|
+
const { makeTabs, makeLedger, makePager, makeFilterBar, emptyStateHtml } = window.OTBKit;
|
|
47
55
|
const fmtDate = (iso) => window.OTB.fmtDate(iso, { time: true });
|
|
48
56
|
const rankLabel = (rank) => window.OTB.rankLabel(rank, { empty: '' });
|
|
49
57
|
const toast = (msg) => window.OTB.toast(msg);
|
|
@@ -291,50 +299,131 @@
|
|
|
291
299
|
});
|
|
292
300
|
}
|
|
293
301
|
|
|
294
|
-
//
|
|
295
|
-
//
|
|
302
|
+
// The three lists the queue is, behind one switch. Reviewing is not only
|
|
303
|
+
// deciding: an owner has to re-read what they already decided, and RESCIND an
|
|
304
|
+
// approval given by mistake before the applicant ever signed in — the route
|
|
305
|
+
// has always taken that transition (ADR 0201 D4.2) and nothing offered it,
|
|
306
|
+
// because this page only ever fetched `?status=pending` (ADR 0208).
|
|
307
|
+
const GATE_VIEWS = [
|
|
308
|
+
{ value: 'pending', label: 'Waiting', empty: 'No pending access requests.' },
|
|
309
|
+
{ value: 'invited', label: 'Admitted', empty: 'Nobody has been admitted yet.' },
|
|
310
|
+
{ value: 'dismissed', label: 'Declined', empty: 'No declined requests.' },
|
|
311
|
+
];
|
|
312
|
+
let gateView = 'pending';
|
|
313
|
+
|
|
314
|
+
function gateEmptyMsg() {
|
|
315
|
+
return (GATE_VIEWS.find((v) => v.value === gateView) || GATE_VIEWS[0]).empty;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// The switch itself, rebuilt on every render because `section` replaces the
|
|
319
|
+
// whole body — the same lifecycle wireInviteForm already lives with.
|
|
320
|
+
function wireGateView(target) {
|
|
321
|
+
const mount = target.querySelector('#gate-view');
|
|
322
|
+
if (!mount) return;
|
|
323
|
+
makeFilterBar({
|
|
324
|
+
mount,
|
|
325
|
+
primary: [{
|
|
326
|
+
id: 'gate-status',
|
|
327
|
+
label: 'Show',
|
|
328
|
+
value: gateView,
|
|
329
|
+
options: GATE_VIEWS.map((v) => ({ value: v.value, label: v.label })),
|
|
330
|
+
}],
|
|
331
|
+
onChange: (vals) => {
|
|
332
|
+
const next = vals['gate-status'];
|
|
333
|
+
if (next === gateView) return;
|
|
334
|
+
gateView = next;
|
|
335
|
+
loadGate();
|
|
336
|
+
},
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Whether the platform vouched for this applicant, said on the row — and the
|
|
341
|
+
// single thing that makes the queue safe to read (ADR 0201, Consequences).
|
|
342
|
+
// POST /access-requests is the one PUBLIC unauthenticated write, so anyone may
|
|
343
|
+
// file a request under anyone's name; a row that merely LOOKED vouched is the
|
|
344
|
+
// whole impersonation surface the vouch exists to close. It is never authority
|
|
345
|
+
// (ADR 0016) — an unvouched applicant is admitted by the identical statement —
|
|
346
|
+
// it only says whether anyone confirmed who is asking.
|
|
347
|
+
function vouchPill(vouched) {
|
|
348
|
+
return vouched
|
|
349
|
+
? '<span class="fact-pill fact-pill--ok" title="The platform confirmed this request came from the account it names.">Identity vouched</span>'
|
|
350
|
+
: '<span class="fact-pill fact-pill--warn" title="Nobody vouched for this name — anyone can file a request naming anyone. Confirm who is asking before you admit them.">Name unvouched</span>';
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// What this row still offers. A waiting request has two decisions; an admitted
|
|
354
|
+
// one can still be rescinded (until they sign in, after which the server
|
|
355
|
+
// refuses and says so); a declined one is terminal here — re-admitting is the
|
|
356
|
+
// invite form above, which is what the server's own refusal names.
|
|
357
|
+
function gateRowActions(r) {
|
|
358
|
+
const id = escapeHtml(r.id);
|
|
359
|
+
if (r.status === 'invited') {
|
|
360
|
+
return ghost(`data-gate-action="dismissed" data-gate-from="invited" data-id="${id}"`, 'Rescind', true);
|
|
361
|
+
}
|
|
362
|
+
if (r.status === 'dismissed') return '';
|
|
363
|
+
return ghost(`data-gate-action="invited" data-id="${id}"`, 'Invite')
|
|
364
|
+
+ ghost(`data-gate-action="dismissed" data-id="${id}"`, 'Dismiss', true);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// One request, one oversight row: who, whether anyone vouched for them, their
|
|
368
|
+
// note as the sub line, when they asked (and who decided, once someone has) as
|
|
369
|
+
// the facts, and whatever decisions are still open on the right.
|
|
370
|
+
function gateRowHtml(r) {
|
|
371
|
+
const who = r.display_name
|
|
372
|
+
? `${escapeHtml(r.display_name)}<span class="ov-mono">@${escapeHtml(r.github_login)}</span>`
|
|
373
|
+
: `<span class="ov-mono">@${escapeHtml(r.github_login)}</span>`;
|
|
374
|
+
const note = r.note ? `<p class="ov-row__sub">“${escapeHtml(r.note)}”</p>` : '';
|
|
375
|
+
// A rescind overwrites the approver with the rescinder (ADR 0208), so this
|
|
376
|
+
// names who put the row in the state it is in now — which is what it says.
|
|
377
|
+
const by = r.resolved_by_login ? ` by <span class="ov-mono">@${escapeHtml(r.resolved_by_login)}</span>` : '';
|
|
378
|
+
const decided = r.resolved_at
|
|
379
|
+
? `<span class="ov-row__when">${r.status === 'invited' ? 'Admitted' : 'Declined'} ${escapeHtml(fmtDate(r.resolved_at))}${by}</span>`
|
|
380
|
+
: '';
|
|
381
|
+
const actions = gateRowActions(r);
|
|
382
|
+
return (
|
|
383
|
+
`<li class="ov-row" data-req-id="${escapeHtml(r.id)}">` +
|
|
384
|
+
'<div class="ov-row__main">' +
|
|
385
|
+
`<span class="ov-row__title">${who}</span>` +
|
|
386
|
+
note +
|
|
387
|
+
'<div class="ov-row__facts">' +
|
|
388
|
+
vouchPill(!!r.vouched) +
|
|
389
|
+
`<span class="ov-row__when">Asked ${escapeHtml(fmtDate(r.created_at))}</span>` +
|
|
390
|
+
decided +
|
|
391
|
+
'</div>' +
|
|
392
|
+
'</div>' +
|
|
393
|
+
(actions ? `<div class="ov-row__right"><div class="ov-row__actions">${actions}</div></div>` : '') +
|
|
394
|
+
'</li>'
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
296
398
|
function renderGate(data, target) {
|
|
297
399
|
const requests = Array.isArray(data.requests) ? data.requests : [];
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
400
|
+
// The attention tile counts WAITING work, never whatever list is on screen:
|
|
401
|
+
// reading the decided history must not repaint the board with a number that
|
|
402
|
+
// means something else. Its own sub line says "pending".
|
|
403
|
+
if (gateView === 'pending') setAttention('att-access', requests.length);
|
|
404
|
+
const head = inviteFormHtml() + '<div id="gate-view"></div>';
|
|
405
|
+
target.innerHTML = requests.length
|
|
406
|
+
? head + '<ul class="ov-rows">' + requests.map(gateRowHtml).join('') + '</ul>'
|
|
407
|
+
: head + emptyStateHtml(gateEmptyMsg());
|
|
408
|
+
|
|
409
|
+
if (requests.length) {
|
|
410
|
+
wireActions(target, '[data-gate-action]', async (btn) => {
|
|
411
|
+
const id = btn.getAttribute('data-id');
|
|
412
|
+
const status = btn.getAttribute('data-gate-action');
|
|
413
|
+
const rescinding = btn.getAttribute('data-gate-from') === 'invited';
|
|
414
|
+
await patchJson(`/access-requests/${encodeURIComponent(id)}`, { status });
|
|
415
|
+
toast(status === 'invited' ? 'Builder invited.'
|
|
416
|
+
: rescinding ? 'Approval rescinded — that sign-in no longer gets in.'
|
|
417
|
+
: 'Request dismissed.');
|
|
418
|
+
await loadGate();
|
|
419
|
+
}, 'Could not resolve');
|
|
303
420
|
}
|
|
304
|
-
target.innerHTML = inviteFormHtml() + '<ul class="ov-rows">' + requests
|
|
305
|
-
.map((r) => {
|
|
306
|
-
const who = r.display_name
|
|
307
|
-
? `${escapeHtml(r.display_name)}<span class="ov-mono">@${escapeHtml(r.github_login)}</span>`
|
|
308
|
-
: `<span class="ov-mono">@${escapeHtml(r.github_login)}</span>`;
|
|
309
|
-
const note = r.note ? `<p class="ov-row__sub">“${escapeHtml(r.note)}”</p>` : '';
|
|
310
|
-
return (
|
|
311
|
-
`<li class="ov-row" data-req-id="${escapeHtml(r.id)}">` +
|
|
312
|
-
'<div class="ov-row__main">' +
|
|
313
|
-
`<span class="ov-row__title">${who}</span>` +
|
|
314
|
-
note +
|
|
315
|
-
`<div class="ov-row__facts"><span class="ov-row__when">Asked ${escapeHtml(fmtDate(r.created_at))}</span></div>` +
|
|
316
|
-
'</div>' +
|
|
317
|
-
'<div class="ov-row__right"><div class="ov-row__actions">' +
|
|
318
|
-
ghost(`data-gate-action="invited" data-id="${escapeHtml(r.id)}"`, 'Invite') +
|
|
319
|
-
ghost(`data-gate-action="dismissed" data-id="${escapeHtml(r.id)}"`, 'Dismiss', true) +
|
|
320
|
-
'</div></div>' +
|
|
321
|
-
'</li>'
|
|
322
|
-
);
|
|
323
|
-
})
|
|
324
|
-
.join('') + '</ul>';
|
|
325
|
-
|
|
326
|
-
wireActions(target, '[data-gate-action]', async (btn) => {
|
|
327
|
-
const id = btn.getAttribute('data-id');
|
|
328
|
-
const status = btn.getAttribute('data-gate-action');
|
|
329
|
-
await patchJson(`/access-requests/${encodeURIComponent(id)}`, { status });
|
|
330
|
-
toast(status === 'invited' ? 'Builder invited.' : 'Request dismissed.');
|
|
331
|
-
await loadGate();
|
|
332
|
-
}, 'Could not resolve');
|
|
333
421
|
wireInviteForm(target);
|
|
422
|
+
wireGateView(target);
|
|
334
423
|
}
|
|
335
424
|
|
|
336
425
|
function loadGate() {
|
|
337
|
-
return section($('#gate-body'), () => fetchJson(
|
|
426
|
+
return section($('#gate-body'), () => fetchJson(`/access-requests?status=${encodeURIComponent(gateView)}`), renderGate, {
|
|
338
427
|
sealedMsg: 'Archons only. Access requests are not visible at your rank.',
|
|
339
428
|
errMsg: 'Could not load access requests.',
|
|
340
429
|
name: 'gate',
|
|
@@ -782,23 +871,19 @@
|
|
|
782
871
|
trendEl.innerHTML = emptyStateHtml('No trend to show yet.');
|
|
783
872
|
return;
|
|
784
873
|
}
|
|
785
|
-
|
|
874
|
+
// task 1003445: this was the hall's third copy of the same horizontal bar.
|
|
875
|
+
// It draws through OTBKit.barSeriesHtml now, in the NEUTRAL tone — on an
|
|
876
|
+
// oversight page the accent belongs to the pressable, not to the data —
|
|
877
|
+
// with its own geometry passed as options rather than a second class family.
|
|
786
878
|
trendEl.innerHTML =
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
`<span class="watch-trend__bar-track"><span class="watch-trend__bar-fill" style="width:${w}%"></span></span>` +
|
|
796
|
-
`<span class="watch-trend__val">${fmtInt(t.n)} · ${avg}</span>` +
|
|
797
|
-
'</div>'
|
|
798
|
-
);
|
|
799
|
-
})
|
|
800
|
-
.join('') +
|
|
801
|
-
'</div>' +
|
|
879
|
+
window.OTBKit.barSeriesHtml(
|
|
880
|
+
trend.map((t) => ({
|
|
881
|
+
label: fmtDay(t.day),
|
|
882
|
+
value: num(t.n),
|
|
883
|
+
valueLabel: `${fmtInt(t.n)} · ${(t.avg_score ?? '—') === '—' ? '—' : String(t.avg_score)}`,
|
|
884
|
+
})),
|
|
885
|
+
{ tone: 'neutral', labelWidth: '64px', valueWidth: '96px', barHeight: '6px', pill: true },
|
|
886
|
+
) +
|
|
802
887
|
foot('Per day: ships graded · avg score.');
|
|
803
888
|
}
|
|
804
889
|
|
|
@@ -118,11 +118,37 @@ const RESTART_SHAPES = new Set(['co-tenant', 'standalone']);
|
|
|
118
118
|
// (migration provisioning_009) is a dumb store with room for future keys.
|
|
119
119
|
// DEFAULT_SETTINGS is TODAY's behavior (public surfaces, apply-to-join),
|
|
120
120
|
// applied at read time so pre-existing rows need no data backfill.
|
|
121
|
+
//
|
|
122
|
+
// `visibility` is ADR 0182's STORED JOIN DOOR (BV1.R05, task 1002324) and it is a
|
|
123
|
+
// third axis, not a synonym of its neighbours — the three answer three different
|
|
124
|
+
// questions and an owner may set them independently:
|
|
125
|
+
//
|
|
126
|
+
// platform_visibility who may READ this project's own pages (its sign-in door)
|
|
127
|
+
// joinability HOW someone becomes a member (self-join / apply / invite)
|
|
128
|
+
// visibility how the project stands on the PLATFORM's map: named and
|
|
129
|
+
// open (public), named and closed (private), or present but
|
|
130
|
+
// anonymous (stealth)
|
|
131
|
+
//
|
|
132
|
+
// The name is ADR 0182 D1's deliberate choice — `goals.visibility` established the
|
|
133
|
+
// word and one word across both planes beat an accurate name used in one place —
|
|
134
|
+
// and D1 also records that it is a mild lie: this key does NOT decide whether the
|
|
135
|
+
// project is on the map. PUBLISHABILITY does (a name + a description, ADR 0182 D4),
|
|
136
|
+
// it is derived and never stored, and no value here can put a husk on the map.
|
|
137
|
+
//
|
|
138
|
+
// It is a POLICY, not a platform-drawn key: ADR 0182 D6 makes the door the CEILING
|
|
139
|
+
// that `joinability` may only narrow, and D1/D5 put that authority on the instance —
|
|
140
|
+
// so it rides web.env like its two neighbours and a restart applies it. The instance
|
|
141
|
+
// reads it back as `project.visibility` (src/branding.js), which is what lets a later
|
|
142
|
+
// task enforce the door there and report the value to the hub catalog (R08).
|
|
121
143
|
const SETTINGS_VOCAB = Object.freeze({
|
|
122
144
|
platform_visibility: ['public', 'gated'],
|
|
123
145
|
joinability: ['open', 'apply', 'invite_only'],
|
|
146
|
+
visibility: ['public', 'private', 'stealth'],
|
|
124
147
|
});
|
|
125
|
-
|
|
148
|
+
// Default = today's behavior on every axis, including the join door: ADR 0182's
|
|
149
|
+
// migration gives the hub column the same `public` default, "existing rows keep
|
|
150
|
+
// today's behaviour".
|
|
151
|
+
const DEFAULT_SETTINGS = Object.freeze({ platform_visibility: 'public', joinability: 'apply', visibility: 'public' });
|
|
126
152
|
|
|
127
153
|
// The project's PLANET (task 1003347, direction record §16): how the platform
|
|
128
154
|
// DRAWS it on the sky, in the owner's list and on the card — its template
|